Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions app/routes/calls_.record/new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -156,11 +157,12 @@ export default function RecordScreen({
<CallKentTextToSpeech
onAcceptAudio={({ audio, questionText }) => {
setAudio(audio)
const cleanedQuestion = questionText.trim()
const formattedNotes =
formatCallKentTextToSpeechNotes(questionText)
setPrefill({
fields: {
title: 'Call Kent question',
notes: cleanedQuestion,
notes: formattedNotes,
},
errors: {},
})
Expand Down
36 changes: 36 additions & 0 deletions app/utils/__tests__/call-kent-text-to-speech-notes.test.ts
Original file line number Diff line number Diff line change
@@ -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('')
})
13 changes: 13 additions & 0 deletions app/utils/call-kent-text-to-speech.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
7 changes: 7 additions & 0 deletions docs/agents/project-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).