feat(web): 实时对话 - 第一阶段输入行内联语音录制与浏览器 STT 转写 (#165)
Co-authored-by: zhaoyue <zhaoyue@rongshantech.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import { Link } from "react-router";
|
||||
|
||||
import { AssetMediaPreview } from "@/components/asset-media-preview";
|
||||
import { CanvasResourceMentionTextarea } from "@/components/canvas/canvas-resource-mention-textarea";
|
||||
import { VoiceRecordingButton } from "@/components/conversation/voice-recording-button";
|
||||
import { ModelPicker } from "@/components/model-picker";
|
||||
import { canvasResourceMentionToken } from "@/lib/canvas/canvas-resource-references";
|
||||
import { createClientId } from "@/lib/client-id";
|
||||
@@ -746,6 +747,10 @@ function CreationComposer(props: ComposerProps) {
|
||||
</div>
|
||||
<footer className="creation-chat-dock">
|
||||
<div className="creation-chat-controls">
|
||||
<VoiceRecordingButton
|
||||
disabled={props.busy}
|
||||
onTranscribed={(text) => props.setPrompt(props.prompt.trim() ? `${props.prompt} ${text}` : text)}
|
||||
/>
|
||||
<ModePicker mode={props.mode} onModeChange={props.onModeChange} />
|
||||
<ModelPicker config={props.config} value={props.model} onChange={props.onModelChange} capability={props.mode} className="creation-model-picker" placeholder={`选择${modeLabels[props.mode]}模型`} showSelectedPrice={false} variant="creation" />
|
||||
{props.mode === "video" || (props.mode === "image" && imageSettingsSupported) ? <GenerationSettingsMenu {...props} /> : null}
|
||||
|
||||
107
web/src/pages/test-voice-recording.tsx
Normal file
107
web/src/pages/test-voice-recording.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import { useState } from "react";
|
||||
import { Button, Card, message } from "antd";
|
||||
import { Mic, Send } from "lucide-react";
|
||||
|
||||
import { VoiceRecordingButton } from "@/components/conversation/voice-recording-button";
|
||||
import { canvasThemes } from "@/lib/canvas-theme";
|
||||
import { useThemeStore } from "@/stores/use-theme-store";
|
||||
|
||||
/**
|
||||
* 语音录制功能测试页面
|
||||
* 验证输入行内联波形录制和 STT 转写闭环
|
||||
*/
|
||||
export default function TestVoiceRecording() {
|
||||
const theme = canvasThemes[useThemeStore((state) => state.theme)];
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [sending, setSending] = useState(false);
|
||||
|
||||
const handleTranscribed = (text: string) => {
|
||||
// 转写结果填入输入框,供用户确认或直接发送
|
||||
setPrompt((current) => (current.trim() ? `${current} ${text}` : text));
|
||||
message.success("语音已转写为文字");
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!prompt.trim()) return;
|
||||
setSending(true);
|
||||
try {
|
||||
// 原型阶段:发送到对话入口由调用方接入;此处仅输出
|
||||
console.log("发送文本:", prompt);
|
||||
message.success("发送成功(原型阶段,仅控制台输出)");
|
||||
setPrompt("");
|
||||
} finally {
|
||||
setSending(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="min-h-screen p-8" style={{ background: theme.spatial.surface }}>
|
||||
<div className="mx-auto max-w-2xl">
|
||||
<Card
|
||||
title={
|
||||
<div className="flex items-center gap-2">
|
||||
<Mic className="size-5" style={{ color: theme.accent.primary }} />
|
||||
<span>实时对话功能测试(MVP)</span>
|
||||
</div>
|
||||
}
|
||||
style={{ background: theme.spatial.elevated, borderColor: theme.toolbar.border }}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{/* 文本输入 */}
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium" style={{ color: theme.node.text }}>
|
||||
文本输入(语音转写结果会自动填入)
|
||||
</label>
|
||||
<textarea
|
||||
value={prompt}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
placeholder="输入消息,或点击实时对话按钮用语音输入..."
|
||||
className="w-full rounded-lg border px-3 py-2 text-sm outline-none transition focus:border-current"
|
||||
style={{
|
||||
background: theme.node.fill,
|
||||
borderColor: theme.toolbar.border,
|
||||
color: theme.node.text,
|
||||
minHeight: 80,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 音频预览 */}
|
||||
|
||||
{/* 控制栏:实时对话按钮(点击后在输入行展开波形录制条) */}
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex flex-1 items-center gap-2">
|
||||
<VoiceRecordingButton onTranscribed={handleTranscribed} />
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<Send className="size-4" />}
|
||||
disabled={!prompt.trim()}
|
||||
loading={sending}
|
||||
onClick={handleSubmit}
|
||||
style={{ background: theme.accent.primary, borderColor: theme.accent.primary }}
|
||||
>
|
||||
发送
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 说明 */}
|
||||
<div className="rounded-lg border p-3 text-xs" style={{ background: theme.toolbar.panel, borderColor: theme.toolbar.border, color: theme.node.muted }}>
|
||||
<div className="font-semibold" style={{ color: theme.node.text }}>
|
||||
使用说明:
|
||||
</div>
|
||||
<ul className="mt-1 list-inside list-disc space-y-1">
|
||||
<li>点击麦克风按钮,输入行内展开波形录制条并自动开始录音</li>
|
||||
<li>波形动画实时显示音量变化</li>
|
||||
<li>点击停止按钮完成录制,自动上传并转写</li>
|
||||
<li>转写结果自动填入输入框,可编辑后发送</li>
|
||||
<li>转写失败时在录制条内提示,可点击麦克风重试</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user