diff --git a/app/routes/calls_.record/new.tsx b/app/routes/calls_.record/new.tsx index 967557943..a46f99235 100644 --- a/app/routes/calls_.record/new.tsx +++ b/app/routes/calls_.record/new.tsx @@ -12,6 +12,7 @@ import { H4, Paragraph } from '#app/components/typography.tsx' import { type RootLoaderType } from '#app/root.tsx' import { CallKentTextToSpeech } from '#app/routes/resources/calls/text-to-speech.tsx' import { type KCDHandle } from '#app/types.ts' +import { formatCallKentTextToSpeechNotes } from '#app/utils/call-kent-text-to-speech.ts' import { getEnv } from '#app/utils/env.server.ts' import { type SerializeFrom } from '#app/utils/serialize-from.ts' import { type Route } from './+types/new' @@ -156,11 +157,12 @@ export default function RecordScreen({ { setAudio(audio) - const cleanedQuestion = questionText.trim() + const formattedNotes = + formatCallKentTextToSpeechNotes(questionText) setPrefill({ fields: { title: 'Call Kent question', - notes: cleanedQuestion, + notes: formattedNotes, }, errors: {}, }) diff --git a/app/utils/__tests__/call-kent-text-to-speech-notes.test.ts b/app/utils/__tests__/call-kent-text-to-speech-notes.test.ts new file mode 100644 index 000000000..077ef0117 --- /dev/null +++ b/app/utils/__tests__/call-kent-text-to-speech-notes.test.ts @@ -0,0 +1,36 @@ +import { expect, test } from 'vitest' +import { + AI_VOICE_DISCLOSURE_PREFIX, + formatCallKentTextToSpeechNotes, +} from '../call-kent-text-to-speech.ts' + +test('formats typed-question notes with AI disclosure prefix', () => { + const notes = formatCallKentTextToSpeechNotes(' Hello from a typed call. ') + expect(notes).toBe( + `${AI_VOICE_DISCLOSURE_PREFIX}\nTyped question: Hello from a typed call.`, + ) +}) + +test('preserves internal newlines in typed question', () => { + const question = 'First line\nSecond line' + const notes = formatCallKentTextToSpeechNotes(question) + expect(notes).toBe( + `${AI_VOICE_DISCLOSURE_PREFIX}\nTyped question: ${question}`, + ) +}) + +test('removes duplicate AI disclosure from typed question', () => { + const question = `${AI_VOICE_DISCLOSURE_PREFIX} Hello from a typed call.` + const notes = formatCallKentTextToSpeechNotes(question) + expect(notes).toBe( + `${AI_VOICE_DISCLOSURE_PREFIX}\nTyped question: Hello from a typed call.`, + ) +}) + +test('returns empty string for empty input', () => { + expect(formatCallKentTextToSpeechNotes('')).toBe('') +}) + +test('returns empty string for whitespace-only input', () => { + expect(formatCallKentTextToSpeechNotes(' ')).toBe('') +}) diff --git a/app/utils/call-kent-text-to-speech.ts b/app/utils/call-kent-text-to-speech.ts index b4f8704f7..9ce363f89 100644 --- a/app/utils/call-kent-text-to-speech.ts +++ b/app/utils/call-kent-text-to-speech.ts @@ -10,6 +10,19 @@ export const callKentTextToSpeechConstraints = { export const AI_VOICE_DISCLOSURE_PREFIX = `This caller's voice was generated by AI.` +export function formatCallKentTextToSpeechNotes(questionText: string) { + const cleaned = questionText.trim() + if (!cleaned) return cleaned + + // Callers can paste the disclosure prefix into the textarea. Avoid duplicating + // it when formatting notes for the call record. + const prefixLower = AI_VOICE_DISCLOSURE_PREFIX.toLowerCase() + const questionBody = cleaned.toLowerCase().startsWith(prefixLower) + ? cleaned.slice(AI_VOICE_DISCLOSURE_PREFIX.length).trimStart() + : cleaned + return `${AI_VOICE_DISCLOSURE_PREFIX}\nTyped question: ${questionBody}` +} + // @cf/deepgram/aura-2-en supports many speakers; we offer a curated subset. const auraSpeakers = [ 'apollo', diff --git a/docs/agents/project-context.md b/docs/agents/project-context.md index 76c00ea42..d99a4902f 100644 --- a/docs/agents/project-context.md +++ b/docs/agents/project-context.md @@ -39,3 +39,10 @@ reference: It's populated on first request or via `npm run prime-cache:mocks`. - Content is filesystem-based: blog posts are MDX files in `content/blog/`. Changes to content files are auto-detected by the dev server's file watcher. + +## Cloud / headless manual testing + +- In cloud VMs, Chrome may block camera/microphone access by default. Visiting + `/calls/record/new` can hit the route ErrorBoundary unless `localhost` is + allowed mic/camera access in site settings (even if you intend to use the + typed question → text-to-speech path).