FOUNDRY
C8 Platform
← 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