import { Button, Spin } from "antd"; import { Tooltip } from "@/components/ui/base/tooltip"; import { useEffect, useRef, useState } from "react"; import { Check, Mic, Square, X } from "lucide-react"; import { AudioWaveform } from "./audio-waveform"; import { useSpeechRecognition } from "@/hooks/use-speech-recognition"; import { useVoiceRecording } from "@/hooks/use-voice-recording"; import { canvasThemes } from "@/lib/canvas-theme"; import { useThemeStore } from "@/stores/use-theme-store"; type VoiceRecordingInlineProps = { /** 转写完成回调,返回转写文本 */ onTranscribed: (text: string) => void; /** 取消录制回调 */ onCancel: () => void; }; type TranscribeState = "idle" | "transcribing" | "done" | "error"; /** * 内联语音录制条(输入行内展示) * 挂载后自动开始录音,显示波形动画;点击停止后自动转写为文字(浏览器 Web Speech API,无需后端与 API Key) */ export function VoiceRecordingInline({ onTranscribed, onCancel }: VoiceRecordingInlineProps) { const theme = canvasThemes[useThemeStore((state) => state.theme)]; const { state, waveform, duration, error: recorderError, start: startRecording, stop: stopRecording, cancel: cancelRecording, } = useVoiceRecording({ maxDuration: 60, }); const { supported: speechSupported, error: speechError, start: startSpeech, stop: stopSpeech, cancel: cancelSpeech, } = useSpeechRecognition(); const [transcribeState, setTranscribeState] = useState("idle"); const [transcribeError, setTranscribeError] = useState(""); const stopRequestedRef = useRef(false); const wasRecordingRef = useRef(false); // 挂载后自动开始录音与语音识别,卸载时清理 useEffect(() => { if (!speechSupported) { setTranscribeError("当前浏览器不支持语音识别,请使用 Chrome 或 Edge 浏览器"); setTranscribeState("error"); return; } void startRecording(); startSpeech(); return () => { cancelRecording(); cancelSpeech(); }; }, [startRecording, cancelRecording, startSpeech, cancelSpeech, speechSupported]); const handleStop = async () => { if (stopRequestedRef.current || transcribeState !== "idle") return; stopRequestedRef.current = true; setTranscribeState("transcribing"); setTranscribeError(""); const text = await stopSpeech(); void stopRecording(); const trimmed = text.trim(); if (!trimmed) { setTranscribeError("未识别到语音内容,请重试"); setTranscribeState("error"); stopRequestedRef.current = false; return; } setTranscribeState("done"); // 短暂展示成功状态后回调 window.setTimeout(() => onTranscribed(trimmed), 600); }; // 录音达到最大时长自动停止时,自动触发转写 useEffect(() => { if (state === "recording") wasRecordingRef.current = true; if (state === "idle" && wasRecordingRef.current && transcribeState === "idle" && !stopRequestedRef.current) { wasRecordingRef.current = false; void handleStop(); } }, [state, transcribeState]); const handleRetry = () => { stopRequestedRef.current = false; wasRecordingRef.current = false; setTranscribeError(""); setTranscribeState("idle"); void startRecording(); startSpeech(); }; const formatDuration = (seconds: number) => { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, "0")}`; }; const displayError = recorderError || (speechError ? speechError.message : "") || (transcribeState === "error" ? transcribeError : ""); return (
{displayError ? (
{displayError} {speechSupported ? (
) : transcribeState === "transcribing" ? (
正在转写...
) : transcribeState === "done" ? (
转写完成
) : ( <> {state === "recording" ? ( {formatDuration(duration)} ) : null}
); }