Files
Compass/web/components/llm-extract-section.tsx
MartinBraquet 4c76b643e3 Add voice recording, transcription, and audio player components
- Implemented `AudioPlayer` for custom audio playback controls.
- Added `useAudioRecorder` hook for managing microphone audio recording with visual feedback.
- Introduced audio transcription API integration using OpenAI Whisper.
- Created `blobToBase64` utility to encode recorded blobs for API consumption.
- Added IndexedDB-based `recording-store` for saving in-progress recordings.
- Developed `VoiceAutofillSection` for guided recording, playback, transcription, and editing.
2026-07-25 18:01:58 +02:00

84 lines
2.8 KiB
TypeScript

import {useEditorState} from '@tiptap/react'
import {isUrl} from 'common/parsing'
import {useT} from 'web/lib/locale'
import {BaseTextEditor} from './bio/editable-bio'
import {Button} from './buttons/button'
import {Col} from './layout/col'
interface LLMExtractSectionProps {
parsingEditor: any
setParsingEditor: (editor: any) => void
isExtracting: boolean
isSubmitting: boolean
onExtract: () => void
progress: number
}
export function LLMExtractSection({
parsingEditor,
setParsingEditor,
isExtracting,
isSubmitting,
onExtract,
progress,
}: LLMExtractSectionProps) {
const t = useT()
// Subscribe to just the editor's text so only this small section re-renders per keystroke — enough
// to keep the button's URL-vs-text label live. Previously `onChange` did `setParsingEditor({...})`
// on every keystroke, which re-rendered the entire profile form (and produced a broken non-Editor
// copy), which is what made typing here lag.
const parsingText =
useEditorState({
editor: parsingEditor ?? null,
selector: ({editor}) => editor?.getText?.() ?? '',
}) ?? ''
return (
<Col className={'gap-4'}>
<div className="">
{t(
'profile.llm.extract.description',
'Auto-fill your profile by dropping a link (Notion, Google Docs, personal website, etc.) or pasting your content directly.',
)}
</div>
<div className="guidance">
{t(
'profile.llm.extract.guidance',
'Heads up: we use Google AI to extract your info. As we pay for the service, Google should not use this content to improve their models — but we never know. Prefer to keep things fully internal? Just fill the form manually — no AI involved.',
)}
</div>
<BaseTextEditor
onEditor={(e) => {
if (e) setParsingEditor(e)
}}
placeholder={t(
'profile.llm.extract.placeholder',
'Insert a URL or paste your profile content here.',
)}
/>
{isExtracting && (
<div className="w-full h-2 bg-canvas-200 rounded-full overflow-hidden">
<div
className="h-full bg-primary-500 transition-all duration-100 ease-linear rounded-full"
style={{width: `${Math.min(progress, 100)}%`}}
/>
</div>
)}
<Button
color="indigo"
onClick={onExtract}
disabled={isExtracting || !parsingEditor?.getJSON?.() || isSubmitting}
loading={isExtracting}
className="self-start"
>
{isExtracting
? t('profile.llm.extract.button_extracting', 'Extracting Profile Data')
: isUrl(parsingText)
? t('profile.llm.extract.button_url', 'Extract Profile Data from URL')
: t('profile.llm.extract.button_text', 'Extract Profile Data from Text')}
</Button>
</Col>
)
}