feat: 完善端到端对话闭环,增强消息渲染与错误处理

- ChatPanel:流式光标动画、token/延迟元数据、自动滚动、三种空状态
- Toast 组件:错误码映射为中文友好文案,3 秒自动消失
- useVisionSession:stt_result 流式更新、防重复发送、打断保存未完成内容、结束清理全部状态
- App:断线提示、视频 loading 遮罩、按钮连接中状态
- ChatMessage 增加 latencyMs、model 字段

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-13 11:43:16 +08:00
parent 9ccd4d6238
commit 734c93a866
8 changed files with 357 additions and 31 deletions

View File

@@ -0,0 +1,51 @@
// ============================================================
// Toast — 轻量通知组件
// 职责3 秒自动消失的通知提示
// ============================================================
import { useCallback, useEffect, useState } from "react";
import { registerToastSetter, type ToastItem } from "../../lib/toast";
export type { ToastItem };
/** Toast 容器组件,放在 App 根部 */
export function ToastContainer() {
const [toasts, setToasts] = useState<ToastItem[]>([]);
// 注册全局 setter
useEffect(() => {
registerToastSetter(setToasts);
return () => registerToastSetter(null);
}, []);
const dismiss = useCallback((id: number) => {
setToasts((prev) => prev.filter((t) => t.id !== id));
}, []);
return (
<div className="toast-container">
{toasts.map((t) => (
<ToastItemView key={t.id} item={t} onDismiss={dismiss} />
))}
</div>
);
}
function ToastItemView({
item,
onDismiss,
}: {
item: ToastItem;
onDismiss: (id: number) => void;
}) {
useEffect(() => {
const timer = setTimeout(() => onDismiss(item.id), 3000);
return () => clearTimeout(timer);
}, [item.id, onDismiss]);
return (
<div className={`toast toast--${item.type}`} onClick={() => onDismiss(item.id)}>
{item.message}
</div>
);
}