← Tasks
[HK-02] useTranscript — data channel transcript accumulation hook (retry 1)
completedcode_genP1
Description
## HK-02: useTranscript — data channel transcript accumulation hook
Section: Hooks | Milestone: M3 | Owner: AG Lu
### Failing Tests
- **hook_file** (file_exists): File `c8-audio/hooks/useTranscript.ts` must exist
- **transcript_state** (grep): Pattern `TranscriptionReceived|transcript|segments` in `c8-audio/hooks/useTranscript.ts` (need 1+ matches)
### Acceptance Criteria
- File `c8-audio/hooks/useTranscript.ts` must exist
- File matching `c8-audio/hooks/useTranscript.ts` must contain pattern `TranscriptionReceived|transcript|segments`
### Codebase Conventions
- Trilingual comments: // EN: ... // VI: ... // ES: ...
- SPEC section refs in comments
- Zod at module level with .safeParse()
- (supabase as any) with eslint-disable
- NextResponse.json() responses
- Error format: { error: 'message' }
### Dependencies
- UI-01: FAIL
Assignee
-
Claimed By
Cloud Lu → Claude Sonnet
Created
1d ago
Completed
1d ago
Quality
Overall
89%
Pass
PASS
Graded By
-
Graded At
-
Cost
Input Tokens
-
Output Tokens
-
Est. USD
-
Result
{
"cost": {
"input_tokens": 298,
"estimated_usd": 0.035274,
"output_tokens": 2292
},
"model": "claude-sonnet-4-20250514",
"status": "success",
"transport": "anthropic_api",
"latency_ms": 28373,
"response_text": "I'll create the `useTranscript` hook for accumulating transcription data from data channels.\n\n```typescript\n// c8-audio/hooks/useTranscript.ts\n\nimport { useState, useCallback, useRef } from 'react';\nimport { z } from 'zod';\n\n// EN: Schema for transcription segments\n// VI: Schema cho các đoạn phiên âm\n// ES: Esquema para segmentos de transcripción\nconst TranscriptionSegmentSchema = z.object({\n id: z.string(),\n text: z.string(),\n timestamp: z.number(),\n confidence: z.number().min(0).max(1),\n isFinal: z.boolean(),\n speaker: z.string().optional()\n});\n\n// EN: Schema for transcription received events\n// VI: Schema cho sự kiện nhận phiên âm\n// ES: Esquema para eventos de transcripción recibidos\nconst TranscriptionReceivedSchema = z.object({\n type: z.literal('TranscriptionReceived'),\n segment: TranscriptionSegmentSchema,\n sessionId: z.string()\n});\n\ntype TranscriptionSegment = z.infer<typeof TranscriptionSegmentSchema>;\ntype TranscriptionReceived = z.infer<typeof TranscriptionReceivedSchema>;\n\ninterface TranscriptState {\n segments: TranscriptionSegment[];\n fullTranscript: string;\n isAccumulating: boolean;\n lastUpdated: number | null;\n}\n\ninterface UseTranscriptOptions {\n maxSegments?: number;\n autoCleanup?: boolean;\n debounceMs?: number;\n}\n\n// EN: Hook for accumulating transcript data from data channels - SPEC: HK-02\n// VI: Hook để tích lũy dữ liệu phiên âm từ các kênh dữ liệu - SPEC: HK-02\n// ES: Hook para acumular datos de transcripción de canales de datos - SPEC: HK-02\nexport function useTranscript(options: UseTranscriptOptions = {}) {\n const {\n maxSegments = 100,\n autoCleanup = true,\n debounceMs = 100\n } = options;\n\n const [transcript, setTranscript] = useState<TranscriptState>({\n segments: [],\n fullTranscript: '',\n isAccumulating: false,\n lastUpdated: null\n });\n\n const debounceRef = useRef<NodeJS.Timeout>();\n const segmentsRef = useRef<TranscriptionSegment[]>([]);\n\n // EN: Process incoming transcription data\n // VI: Xử lý dữ liệu phiên âm đến\n // ES: Procesar datos de transcripción entrantes\n const processTranscription = useCallback((data: unknown) => {\n const parseResult = TranscriptionReceivedSchema.safeParse(data);\n \n if (!parseResult.success) {\n console.warn('Invalid transcription data:', parseResult.error);\n return;\n }\n\n const { segment } = parseResult.data;\n \n // EN: Update segments reference immediately\n // VI: Cập nhật tham chiếu segments ngay lập tức\n // ES: Actualizar referencia de segmentos inmediatamente\n const existingIndex = segmentsRef.current.findIndex(s => s.id === segment.id);\n \n if (existingIndex >= 0) {\n segmentsRef.current[existingIndex] = segment;\n } else {\n segmentsRef.current.push(segment);\n \n // EN: Cleanup old segments if needed\n // VI: Dọn dẹp các đoạn cũ nếu cần\n // ES: Limpiar segmentos antiguos si es necesario\n if (autoCleanup && segmentsRef.current.length > maxSegments) {\n segmentsRef.current = segmentsRef.current.slice(-maxSegments);\n }\n }\n\n // EN: Debounce state updates\n // VI: Trì hoãn cập nhật trạng thái\n // ES: Retardar actualizaciones de estado\n if (debounceRef.current) {\n clearTimeout(debounceRef.current);\n }\n\n debounceRef.current = setTimeout(() => {\n const sortedSegments = [...segmentsRef.current].sort((a, b) => a.timestamp - b.timestamp);\n const fullTranscript = sortedSegments\n .filter(s => s.isFinal)\n .map(s => s.text)\n .join(' ')\n .trim();\n\n setTranscript({\n segments: sortedSegments,\n fullTranscript,\n isAccumulating: true,\n lastUpdated: Date.now()\n });\n }, debounceMs);\n }, [maxSegments, autoCleanup, debounceMs]);\n\n // EN: Start accumulating transcriptions\n // VI: Bắt đầu tích lũy phiên âm\n // ES: Comenzar a acumular transcripciones\n const startAccumulating = useCallback(() => {\n setTranscript(prev => ({\n ...prev,\n isAccumulating: true\n }));\n }, []);\n\n // EN: Stop accumulating transcriptions\n // VI: Dừng tích lũy phiên âm\n // ES: Detener acumulación de transcripciones\n const stopAccumulating = useCallback(() => {\n if (debounceRef.current) {\n clearTimeout(debounceRef.current);\n }\n \n setTranscript(prev => ({\n ...prev,\n isAccumulating: false\n }));\n }, []);\n\n // EN: Clear all transcript data\n // VI: Xóa tất cả dữ liệu phiên âm\n // ES: Limpiar todos los datos de transcripción\n const clearTranscript = useCallback(() => {\n if (debounceRef.current) {\n clearTimeout(debounceRef.current);\n }\n \n segmentsRef.current = [];\n setTranscript({\n segments: [],\n fullTranscript: '',\n isAccumulating: false,\n lastUpdated: null\n });\n }, []);\n\n // EN: Get segments by speaker\n // VI: Lấy các đoạn theo người nói\n // ES: Obtener segmentos por hablante\n const getSegmentsBySpeaker = useCallback((speaker: string) => {\n return transcript.segments.filter(segment => segment.speaker === speaker);\n }, [transcript.segments]);\n\n // EN: Get final transcript text only\n // VI: Chỉ lấy văn bản phiên âm cuối cùng\n // ES: Obtener solo texto de transcripción final\n const getFinalTranscript = useCallback(() => {\n return transcript.segments\n .filter(segment => segment.isFinal)\n .sort((a, b) => a.timestamp - b.timestamp)\n .map(segment => segment.text)\n .join(' ')\n .trim();\n }, [transcript.segments]);\n\n // EN: Cleanup on unmount\n // VI: Dọn dẹp khi unmount\n // ES: Limpiar al desmontar\n React.useEffect(() => {\n return () => {\n if (debounceRef.current) {\n clearTimeout(debounceRef.current);\n }\n };\n }, []);\n\n return {\n transcript,\n processTranscription,\n startAccumulating,\n stopAccumulating,\n clearTranscript,\n getSegmentsBySpeaker,\n getFinalTranscript,\n \n // EN: Computed properties\n // VI: Các thuộc tính được tính toán\n // ES: Propiedades computadas\n hasContent: transcript.segments.length > 0,\n finalSegmentCount: transcript.segments.filter(s => s.isFinal).length,\n pendingSegmentCount: transcript.segments.filter(s => !s.isFinal).length\n };\n}\n\nexport type { TranscriptionSegment, TranscriptionReceived, TranscriptState };\n```\n\nThis hook provides:\n\n1. **Transcript State Management**: Manages segments, full transcript text, and accumulation status\n2. **Data Processing**: Validates and processes `TranscriptionReceived` events with proper schemas\n3. **Segment Accumulation**: Handles incoming transcription segments with deduplication and sorting\n4. **Debounced Updates**: Prevents excessive re-renders during rapid transcription updates\n5. **Auto-cleanup**: Automatically manages segment history within specified limits\n6. **Speaker Filtering**: Provides methods to filter segments by speaker\n7. **Final vs Pending**: Distinguishes between final and pending transcription segments\n8. **Memory Management**: Proper cleanup of timeouts and references\n\nThe hook follows the codebase conventions with trilingual comments, SPEC references, and Zod validation with `.safeParse()`. It contains all required patterns: `TranscriptionReceived`, `transcript`, and `segments`."
}Task ID: 02eafaa9-27e5-4716-9813-ef9b73b2c985