Skip to content

AI Chat 与 Streaming ​

为什么不用重型 SDK ​

Vercel AI SDK 已支持 Vue,但模板只需要文本流、取消、重试和 Provider 替换。直接接入会同时带入其服务端协议与 UI message model。v2 使用轻量标准接口:

ts
interface ChatProvider {
  chat(
    messages: readonly ChatMessage[],
    options?: ChatOptions,
  ): AsyncIterable<ChatChunk>;
}

未来可以增加 OpenAI、Claude、Gemini、DeepSeek 或 Vercel data-stream adapter,而页面不用变化。

运行链路 ​

  1. ChatComposer 提交文本并处理 Enter、IME、自动增高。
  2. useStreamingChat 添加 user 与 streaming assistant message。
  3. FetchStreamChatProvider POST /api/ai/chat。
  4. parseSseStream 从 ReadableStream 解析跨网络 chunk 的 SSE event。
  5. 每个 delta 追加到消息;done 完成;error 进入可重试状态。
  6. Stop 调用 AbortController.abort(),请求立即终止并标记 stopped。

开发 Mock 是真实 chunked SSE middleware,不是一次性 setTimeout 响应。生产环境 Mock 默认关闭,真实后端需要保持 text/event-stream、关闭代理缓冲,并及时处理客户端断开。

配套 FastAPI AI 服务位于独立 backend 工作区,提供相同的 /api/ai/chat 协议、Mock/OpenAI-compatible Provider、可选 Redis 限流、服务 Token、Request ID、健康检查与测试。常规认证和 CRUD 由 Gin 服务负责,避免两个运行时职责重叠。

真实联调时,Gin 登录 JWT 会由 FetchStreamChatProvider 自动加入 AI 请求。FastAPI 使用同一 JWT_SECRET、issuer 和 audience 验证用户,并按用户 subject 限流;无效或过期 Token 会触发与 Axios 相同的 401 会话清理。Compose 默认设置 AI_AUTH_REQUIRED=true,纯前端 Mock 模式仍可直接体验页面。

Markdown 安全 ​

Markdown-it 禁止 raw HTML,渲染结果再由 DOMPurify 移除脚本、iframe、form、style 和危险 URL。不要直接信任模型 HTML。较重的 Shiki 没有默认安装;需要高亮时应在 AI route 内动态加载。

移动端 ​

页面使用 VisualViewport 跟随软键盘高度,输入栏包含 safe-area,textarea 自动增高,用户离开底部时显示 Scroll To Bottom,且不会强制抢回滚动位置。

按语言加载聊天建议 ​

聊天空状态通过 JSON 请求加载 GET /api/ai/suggestions。Query 按语言区分,五分钟内视为新鲜数据,不自动重试。加载中、结果为空或请求失败时继续显示本地翻译建议。点击建议仍走原有发送与停止流程,不改变 SSE 协议,也不保存会话历史。

客户端把 zh-* 映射为 zh-CN、ja-* 映射为 ja-JP,其他语言映射为 en-US。FastAPI 本身只接受这三个精确值,省略时默认 en-US,非法值返回 HTTP 422。语言由显式 locale 查询参数决定,不能只依赖 Accept-Language。

单应用通过 VITE_AI_API_BASE_URL(默认 /api/ai)请求建议,通过 VITE_AI_API_TARGET 配置开发代理;常规业务请求发往 Gin。JSON 请求位于 src/api/modules/ai.ts,流式协议仍由 Provider/composable 管理。

ts
import { getChatSuggestions } from '@/api/modules/ai';

const prompts = await getChatSuggestions(locale, signal, '/api/ai');

流的结束与清理 ​

解析器同时支持旧 Mock 的 start.conversationId / done 和 FastAPI 的 start.id / finish,对外统一成 ChatChunk。UTF-8 字符和 CRLF 分隔符可以跨网络分片;遇到结束事件后停止消费,提前退出或解析失败时取消 reader 并释放锁。HTTP 失败响应会保留服务端的 message 与 Request ID。

停止或清空会立即释放发送状态,并请求取消底层传输。即使自定义 Provider 忽略取消,旧响应也不能改写新会话的消息、ID、用量或错误。在组件 setup 或 Vue effect scope 内使用时,销毁 scope 会自动停止生成;在 scope 外使用时由调用方执行 stop()。

聊天滚动只监听消息数量、最后一条内容和生成状态,在 DOM 更新后处理,避免每个 token 深度遍历全部历史。复制失败会显示提示;提示在 1.2 秒后清除,新对话或组件销毁后不再显示旧复制结果。

API · 测试

基于 MIT License 发布