Note Transformer and Editor

https://claude.ai/public/artifacts/1968d1a0-0e53-4b78-8978-6c441b753012

https://claude.ai/chat/8b6609bd-3e84-4767-8510-abfe521ef927?artifactId=remixed-0f4e84bc

import React, { useState } from 'react';

import { Copy, FileText, MessageSquare, Mail, Calendar, Clock, User, Building } from 'lucide-react';

const TRANSLATIONS = {

"en-US": {

"appTitle": "Professional Note Transformer",

"appDescription": "Transform raw notes into polished, professional formats",

"interviewNotes": "Interview Notes",

"meetingNotes": "Meeting Notes",

"input": "Input",

"context": "Context",

"position": "Position",

"candidateName": "Candidate Name",

"interviewType": "Interview Type",

"duration": "Duration",

"meetingTitle": "Meeting Title",

"attendees": "Attendees",

"date": "Date",

"rawNotes": "Raw Notes",

"rawNotesPlaceholder": "Enter your raw notes here...",

"outputFormat": "Output Format",

"evaluationScorecard": "Evaluation Scorecard",

"slackUpdate": "Slack Update",

"emailSummary": "Email Summary",

"googleDoc": "Google Doc",

"transforming": "Transforming...",

"transformNotes": "Transform Notes",

"output": "Output",

"copy": "Copy",

"transformedNotesPlaceholder": "Transformed notes will appear here"

},

/* LOCALE_PLACEHOLDER_START */

"es-ES": {

"appTitle": "Transformador Profesional de Notas",

"appDescription": "Transforma notas sin procesar en formatos profesionales pulidos",

"interviewNotes": "Notas de Entrevista",

"meetingNotes": "Notas de Reunión",

"input": "Entrada",

"context": "Contexto",

"position": "Posición",

"candidateName": "Nombre del Candidato",

"interviewType": "Tipo de Entrevista",

"duration": "Duración",

"meetingTitle": "Título de la Reunión",

"attendees": "Asistentes",

"date": "Fecha",

"rawNotes": "Notas Sin Procesar",

"rawNotesPlaceholder": "Ingresa tus notas sin procesar aquí...",

"outputFormat": "Formato de Salida",

"evaluationScorecard": "Tarjeta de Evaluación",

"slackUpdate": "Actualización de Slack",

"emailSummary": "Resumen de Email",

"googleDoc": "Documento de Google",

"transforming": "Transformando...",

"transformNotes": "Transformar Notas",

"output": "Salida",

"copy": "Copiar",

"transformedNotesPlaceholder": "Las notas transformadas aparecerán aquí"

}

/* LOCALE_PLACEHOLDER_END */

};

const appLocale = '{{APP_LOCALE}}';

const browserLocale = navigator.languages?.[0] || navigator.language || 'en-US';

const findMatchingLocale = (locale) => {

if (TRANSLATIONS[locale]) return locale;

const lang = locale.split('-')[0];

const match = Object.keys(TRANSLATIONS).find(key => key.startsWith(lang + '-'));

return match || 'en-US';

};

const locale = (appLocale !== '{{APP_LOCALE}}') ? findMatchingLocale(appLocale) : findMatchingLocale(browserLocale);

const t = (key) => TRANSLATIONS[locale]?.[key] || TRANSLATIONS['en-US'][key] || key;

const NoteTransformer = () => {

const [noteType, setNoteType] = useState('interview');

const [context, setContext] = useState({

interview: {

position: '',

candidate: '',

interviewType: '',

duration: ''

},

meeting: {

title: '',

attendees: '',

date: '',

duration: ''

}

});

const [rawNotes, setRawNotes] = useState('');

const [finalUseCase, setFinalUseCase] = useState('');

const [output, setOutput] = useState('');

const [isTransforming, setIsTransforming] = useState(false);

const sampleData = {

interview: {

context: {

position: 'Senior Frontend Developer',

candidate: 'John Smith',

interviewType: 'Technical',

duration: '45 mins'

},

notes: `Candidate: John Smith

- Strong React exp (4 yrs)

- Worked w/ TypeScript, Next.js

- q about state mgmt → mentioned Redux, Context API

- Tech challenge: built todo app w/ good structure

- Soft skills: communicates well, asks good qs

- Concerns: limited backend exp

- Salary expectation: 120-130k

- Available: 2 weeks notice

- Team fit: seems collaborative`

},

meeting: {

context: {

title: 'Q1 Planning Meeting',

attendees: 'Team leads, Product Manager',

date: '2025-01-15',

duration: '60 mins'

},

notes: `Agenda: Q1 roadmap planning

- Revenue target: 2M (up 15% from Q4)

- New feature: mobile app launch

- Timeline: design complete by Feb 15, dev by Mar 30

- Resources: need 2 more devs

- Budget: 500k allocated

- Risks: tight timeline, resource constraints

- Next steps: hiring plan, design kickoff

- Follow-up: weekly check-ins starting next Mon`

}

};

const useCaseOptions = {

interview: [

{ value: 'evaluation', label: t('evaluationScorecard'), icon: FileText },

{ value: 'slack', label: t('slackUpdate'), icon: MessageSquare },

{ value: 'email', label: t('emailSummary'), icon: Mail }

],

meeting: [

{ value: 'googledoc', label: t('googleDoc'), icon: FileText },

{ value: 'slack', label: t('slackUpdate'), icon: MessageSquare },

{ value: 'email', label: t('emailSummary'), icon: Mail }

]

};

const contextFields = {

interview: [

{ key: 'position', label: t('position'), icon: Building },

{ key: 'candidate', label: t('candidateName'), icon: User },

{ key: 'interviewType', label: t('interviewType'), icon: Calendar },

{ key: 'duration', label: t('duration'), icon: Clock }

],

meeting: [

{ key: 'title', label: t('meetingTitle'), icon: Building },

{ key: 'attendees', label: t('attendees'), icon: User },

{ key: 'date', label: t('date'), icon: Calendar },

{ key: 'duration', label: t('duration'), icon: Clock }

]

};

const handleContextChange = (field, value) => {

setContext(prev => ({

...prev,

[noteType]: {

...prev[noteType],

[field]: value

}

}));

};

const getTransformationPrompt = () => {

const currentContext = context[noteType];

const contextString = Object.entries(currentContext)

.map(([key, value]) => ${key}: ${value})

.join(', ');

const basePrompt = `Please respond in ${locale} language. Transform the following raw notes into a professional ${finalUseCase} format.

Context: ${contextString}

Raw Notes:

${rawNotes}

Instructions:

- Expand abbreviations naturally (q → questions, w/ → with, exp → experience, etc.)

- Maintain the original meaning and content

- Structure appropriately for ${finalUseCase}

- Use professional language while preserving key details

- Keep the tone appropriate for the intended use case

Format the output as a ${finalUseCase} would appear in a professional setting.`;

return basePrompt;

};

const transformNotes = async () => {

if (!rawNotes.trim()) return;

setIsTransforming(true);

try {

const response = await fetch("https://api.anthropic.com/v1/messages", {

method: "POST",

headers: {

"Content-Type": "application/json",

},

body: JSON.stringify({

model: "claude-sonnet-4-20250514",

max_tokens: 2000,

messages: [

{ role: "user", content: getTransformationPrompt() }

]

})

});

if (!response.ok) {

throw new Error(`API request failed: ${response.status}`);

}

const data = await response.json();

setOutput(data.content[0].text);

} catch (error) {

console.error("Error transforming notes:", error);

setOutput("Error: Unable to transform notes. Please try again.");

} finally {

setIsTransforming(false);

}

};

const copyToClipboard = async () => {

try {

await navigator.clipboard.writeText(output);

// You could add a toast notification here

} catch (err) {

console.error('Failed to copy:', err);

}

};

return (

<div className="min-h-screen bg-gray-50 p-6">

<div className="max-w-7xl mx-auto">

<div className="mb-8">

<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('appTitle')}</h1>

<p className="text-gray-600">{t('appDescription')}</p>

</div>

{/* Note Type Selector */}

<div className="mb-6">

<div className="bg-gray-100 p-1 rounded-lg inline-flex">

<button

onClick={() => setNoteType('interview')}

className={`px-6 py-2 rounded-md font-medium transition-all duration-200 ${

noteType === 'interview'

? 'bg-white text-blue-600 shadow-sm'

: 'text-gray-600 hover:text-gray-800'

}`}

>

{t('interviewNotes')}

</button>

<button

onClick={() => setNoteType('meeting')}

className={`px-6 py-2 rounded-md font-medium transition-all duration-200 ${

noteType === 'meeting'

? 'bg-white text-blue-600 shadow-sm'

: 'text-gray-600 hover:text-gray-800'

}`}

>

{t('meetingNotes')}

</button>

</div>

</div>

<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">

{/* Input Column */}

<div className="space-y-6">

<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">

<h2 className="text-xl font-semibold text-gray-900 mb-4">{t('input')}</h2>

{/* Context Fields */}

<div className="space-y-4 mb-6">

<h3 className="text-lg font-medium text-gray-800">{t('context')}</h3>

<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">

{contextFields[noteType].map(field => {

const Icon = field.icon;

return (

<div key={field.key} className="relative">

<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">

<Icon className="h-5 w-5 text-gray-400" />

</div>

<input

type="text"

placeholder={field.label}

value={context[noteType][field.key]}

onChange={(e) => handleContextChange(field.key, e.target.value)}

className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"

/>

</div>

);

})}

</div>

</div>

{/* Raw Notes Input */}

<div className="space-y-4">

<h3 className="text-lg font-medium text-gray-800">{t('rawNotes')}</h3>

<textarea

value={rawNotes}

onChange={(e) => setRawNotes(e.target.value)}

placeholder={t('rawNotesPlaceholder')}

rows={12}

className="w-full p-4 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"

/>

</div>

{/* Final Use Case Selector */}

<div className="space-y-4">

<h3 className="text-lg font-medium text-gray-800">{t('outputFormat')}</h3>

<div className="flex flex-wrap gap-2">

{useCaseOptions[noteType].map(option => {

const Icon = option.icon;

return (

<button

key={option.value}

onClick={() => setFinalUseCase(option.value)}

className={`flex items-center space-x-2 px-4 py-2 rounded-lg font-medium transition-colors ${

finalUseCase === option.value

? 'bg-blue-600 text-white'

: 'bg-gray-100 text-gray-700 hover:bg-gray-200'

}`}

>

<Icon className="h-4 w-4" />

<span>{option.label}</span>

</button>

);

})}

</div>

</div>

{/* Transform Button */}

<button

onClick={transformNotes}

disabled={!rawNotes.trim() || isTransforming}

className="w-full mt-6 bg-blue-600 text-white py-3 px-6 rounded-lg font-medium hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors"

>

{isTransforming ? t('transforming') : t('transformNotes')}

</button>

</div>

</div>

{/* Output Column */}

<div className="space-y-6">

<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">

<div className="flex justify-between items-center mb-4">

<h2 className="text-xl font-semibold text-gray-900">{t('output')}</h2>

{output && (

<button

onClick={copyToClipboard}

className="flex items-center space-x-2 text-blue-600 hover:text-blue-800 font-medium"

>

<Copy className="h-4 w-4" />

<span>{t('copy')}</span>

</button>

)}

</div>

<div className="min-h-[400px] p-4 bg-gray-50 rounded-lg border border-gray-200">

{output ? (

<div className="whitespace-pre-wrap text-gray-800 leading-relaxed">

{output}

</div>

) : (

<div className="text-gray-500 text-center py-20">

<FileText className="h-12 w-12 mx-auto mb-4 text-gray-400" />

<p>{t('transformedNotesPlaceholder')}</p>

</div>

)}

</div>

</div>

</div>

</div>

</div>

</div>

);

};

export default NoteTransformer;