"use client"; import { useControllableState } from "@radix-ui/react-use-controllable-state"; import { cn } from "@/lib/utils"; import type { Experimental_TranscriptionResult as TranscriptionResult } from "ai"; import type { ComponentProps, ReactNode } from "react"; import { createContext, useCallback, useContext, useMemo } from "react"; type TranscriptionSegment = TranscriptionResult["segments"][number]; interface TranscriptionContextValue { segments: TranscriptionSegment[]; currentTime: number; onTimeUpdate: (time: number) => void; onSeek?: (time: number) => void; } const TranscriptionContext = createContext( null ); const useTranscription = () => { const context = useContext(TranscriptionContext); if (!context) { throw new Error( "Transcription components must be used within Transcription" ); } return context; }; export type TranscriptionProps = Omit, "children"> & { segments: TranscriptionSegment[]; currentTime?: number; onSeek?: (time: number) => void; children: (segment: TranscriptionSegment, index: number) => ReactNode; }; export const Transcription = ({ segments, currentTime: externalCurrentTime, onSeek, className, children, ...props }: TranscriptionProps) => { const [currentTime, setCurrentTime] = useControllableState({ defaultProp: 0, onChange: onSeek, prop: externalCurrentTime, }); const contextValue = useMemo( () => ({ currentTime, onSeek, onTimeUpdate: setCurrentTime, segments }), [currentTime, onSeek, setCurrentTime, segments] ); return (
{segments .filter((segment) => segment.text.trim()) .map((segment, index) => children(segment, index))}
); }; export type TranscriptionSegmentProps = ComponentProps<"button"> & { segment: TranscriptionSegment; index: number; }; export const TranscriptionSegment = ({ segment, index, className, onClick, ...props }: TranscriptionSegmentProps) => { const { currentTime, onSeek } = useTranscription(); const isActive = currentTime >= segment.startSecond && currentTime < segment.endSecond; const isPast = currentTime >= segment.endSecond; const handleClick = useCallback( (event: React.MouseEvent) => { if (onSeek) { onSeek(segment.startSecond); } onClick?.(event); }, [onSeek, segment.startSecond, onClick] ); return ( ); };