迁移到官方 @json-render/react 的 useChatUI hook,实现更优雅的 流式响应和 spec 渲染机制。 ## 核心变更 ### 新增文件 - frontend-v2/app/api/chat/route.ts - Next.js API Route, 使用 Vercel AI SDK 的 streamText() + @ai-sdk/mcp ### 前端改造 - frontend-v2/app/page.tsx - 使用官方 useChatUI 替代自定义 useChat - frontend-v2/components/ChatMessage.tsx - 简化组件,使用官方 spec 解析 - frontend-v2/lib/hooks.ts - 标记为 DEPRECATED,保留供参考 ### 依赖更新 - ai@6.0.116 - Vercel AI SDK - @ai-sdk/anthropic@3.0.58 - Anthropic 集成 - @ai-sdk/mcp@1.0.25 - MCP 客户端 ## 验收标准 - ✅ AC1: API Route 返回文本流响应 - ✅ AC2: 前端使用 useChatUI - ✅ AC3: useChatUI 内部解析 JSONL patches - ⏳ AC4-6: 需要运行时测试 - ✅ AC7: npm run build 成功 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9.7 KiB
9.7 KiB
title, slug, created, status, step
| title | slug | created | status | step |
|---|---|---|---|---|
| Quick Spec: json-render 官方 Hook 重构 | json-render-official-hook-refactor | 2026-03-20 | complete | 1 |
Quick Spec: json-render 官方 Hook 重构
目标
将前端聊天功能从自定义实现迁移到官方 @json-render/react 的 hooks 和 Vercel AI SDK,实现:
- 使用 AI SDK 的
useChathook 替代自定义useChat - 使用官方
useJsonRenderMessage从消息 parts 中提取 UI spec - 新建 Next.js API Route 处理流式响应
- 保留 Python 后端用于 MCP 服务调用
技术方案
架构变更
当前架构:
┌─────────────────┐ POST /api/chat/stream ┌─────────────────┐
│ 前端 (自定义 │ ─────────────────────────→ │ Python FastAPI │
│ useChat hook) │ SSE events │ (Claude SDK) │
│ 手动 SSE 解析 │ ←───────────────────────────│ MCP 工具调用 │
│ specFromToolCall│ │ 手动生成 spec │
└─────────────────┘ └─────────────────┘
目标架构:
┌─────────────────────┐ POST /api/chat ┌─────────────────────┐
│ 前端 (AI SDK │ ─────────────────→ │ Next.js API Route │
│ useChat hook) │ │ (Vercel AI SDK) │
│ useJsonRenderMessage│ ←───────────────── │ streamText() │
│ JsonRenderer │ UI Stream │ pipeJsonRender() │
└─────────────────────┘ └──────────┬──────────┘
│
POST /api/mcp/invoke
↓
┌─────────────────────┐
│ Python FastAPI │
│ (MCP 服务代理) │
│ 工具调用执行 │
│ 返回 JSON 结果 │
└─────────────────────┘
核心变更
-
新建 Next.js API Route (
/api/chat/route.ts)- 使用 Vercel AI SDK 的
streamText()调用 Claude API - 使用
pipeJsonRender()生成 JSONL patches - 使用
createUIMessageStreamResponse()返回流式响应
- 使用 Vercel AI SDK 的
-
前端使用官方 hooks
- 替换自定义
useChat→ AI SDKuseChat - 使用
useJsonRenderMessage从message.parts提取 spec - 让 AI 生成 JSONL patches 而非手动转换
- 替换自定义
-
Python 后端角色变更
- 保留 MCP 工具调用逻辑
- 新增
/api/mcp/invoke端点供 Next.js 调用 - 不再处理 UI 生成逻辑
-
删除冗余代码
- 删除
specFromToolCall手动转换函数 - 删除自定义 SSE 解析逻辑
- 删除
extractJsonFromMarkdown函数
- 删除
详细设计
1. Next.js API Route
文件: frontend-v2/app/api/chat/route.ts
import {
convertToModelMessages,
createUIMessageStream,
createUIMessageStreamResponse,
streamText,
type UIMessage,
} from 'ai';
import { pipeJsonRender } from '@json-render/core';
import { gateway } from '@ai-sdk/gateway';
import { catalog } from '@/lib/catalog';
const AGENT_INSTRUCTIONS = `You are a helpful assistant...
${catalog.prompt({ mode: 'inline' })}`;
export const maxDuration = 60;
export async function POST(req: Request) {
const body = await req.json();
const uiMessages: UIMessage[] = body.messages;
if (!uiMessages || !Array.isArray(uiMessages) || uiMessages.length === 0) {
return new Response(
JSON.stringify({ error: 'messages array is required' }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
);
}
const modelMessages = await convertToModelMessages(uiMessages);
// 调用 MCP 工具(通过 Python 后端代理)
const tools = await fetchMcpTools();
const toolResults = await invokeMcpTools(modelMessages, tools);
// 使用 Vercel AI SDK 流式生成
const result = streamText({
model: gateway(process.env.AI_GATEWAY_MODEL || 'anthropic/claude-sonnet-4.6'),
system: AGENT_INSTRUCTIONS,
messages: modelMessages,
tools: toolResults,
maxSteps: 5,
});
// 创建 UI 消息流并应用 json-render 转换
const stream = createUIMessageStream({
execute: async ({ writer }) => {
writer.merge(pipeJsonRender(result.toUIMessageStream()));
},
});
return createUIMessageStreamResponse({ stream });
}
2. 前端 Hook 改造
文件: frontend-v2/app/page.tsx
'use client';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport, type UIMessage } from 'ai';
import { SPEC_DATA_PART, SPEC_DATA_PART_TYPE, type SpecDataPart } from '@json-render/core';
import { useJsonRenderMessage } from '@json-render/react';
import { JsonRenderer } from '@/components/JsonRenderer';
type AppDataParts = { [SPEC_DATA_PART]: SpecDataPart };
type AppMessage = UIMessage<unknown, AppDataParts>;
const transport = new DefaultChatTransport({ api: '/api/chat' });
export default function ChatPage() {
const { messages, sendMessage, status, error } = useChat<AppMessage>({ transport });
const isStreaming = status === 'streaming' || status === 'submitted';
return (
<div className="chat-container">
{messages.map((message) => (
<MessageBubble key={message.id} message={message} isStreaming={isStreaming} />
))}
</div>
);
}
function MessageBubble({ message, isStreaming }: { message: AppMessage; isStreaming: boolean }) {
const { spec, text, hasSpec } = useJsonRenderMessage(message.parts);
return (
<div className="message">
{text && <div className="text-content">{text}</div>}
{hasSpec && <JsonRenderer spec={spec} loading={isStreaming} />}
</div>
);
}
3. MCP 工具代理端点
Python 后端新增: backend/app_fastapi.py
@app.post("/api/mcp/invoke")
async def mcp_invoke(request: Request):
"""
MCP 工具调用代理端点
接收 Next.js API Route 的工具调用请求,
调用对应的 MCP 服务,返回 JSON 结果。
"""
body = await request.json()
tool_name = body.get("tool")
args = body.get("args", {})
mcp_tokens = body.get("mcp_tokens", {})
# 调用 MCP 工具
result = await invoke_mcp_tool(tool_name, args, mcp_tokens)
return JSONResponse({
"success": True,
"result": result
})
4. 文件变更清单
| 操作 | 文件路径 | 说明 |
|---|---|---|
| 新建 | frontend-v2/app/api/chat/route.ts |
Next.js API Route,使用 AI SDK |
| 新建 | frontend-v2/lib/mcp-proxy.ts |
MCP 工具调用代理客户端 |
| 修改 | frontend-v2/app/page.tsx |
使用官方 useChat hook |
| 修改 | frontend-v2/components/ChatMessage.tsx |
使用 useJsonRenderMessage |
| 删除 | frontend-v2/lib/hooks.ts (useChat) |
替换为 AI SDK |
| 删除 | frontend-v2/lib/json-render-catalog.tsx (specFromToolCall) |
不再需要手动转换 |
| 修改 | backend/app_fastapi.py |
新增 /api/mcp/invoke 端点 |
5. 依赖更新
frontend-v2/package.json 新增:
{
"dependencies": {
"ai": "^4.0.0",
"@ai-sdk/gateway": "^1.0.0",
"@json-render/core": "^0.14.1",
"@json-render/react": "^0.14.1"
}
}
环境变量
frontend-v2/.env.local:
AI_GATEWAY_MODEL=anthropic/claude-sonnet-4.6
AI_GATEWAY_API_KEY=your-api-key
MCP_PROXY_URL=http://localhost:8081/api/mcp
迁移步骤
Phase 1: 基础设施准备
- 安装 Vercel AI SDK 依赖
- 创建
/api/chat/route.ts基础框架 - 创建 MCP 代理客户端
Phase 2: 前端改造
- 修改
page.tsx使用 AI SDKuseChat - 修改
ChatMessage使用useJsonRenderMessage - 删除自定义
useChathook
Phase 3: 后端适配
- Python 后端新增
/api/mcp/invoke端点 - 保留现有
/api/chat/stream作为备选
Phase 4: 清理
- 删除
specFromToolCall函数 - 删除自定义 SSE 解析逻辑
- 更新 CLAUDE.md 文档
风险与缓解
| 风险 | 影响 | 缓解措施 |
|---|---|---|
| Vercel AI SDK 兼容性 | Next.js 版本要求 | 确认 Next.js 14.2.18 兼容 |
| MCP 工具调用延迟 | 增加一跳网络请求 | 使用本地代理,延迟 <10ms |
| 流式响应格式变更 | 前端需要适配 | 保留旧端点作为回退 |
测试计划
-
单元测试
useJsonRenderMessage正确提取 spec 和 text- MCP 代理客户端正确传递参数
-
集成测试
- 完整聊天流程:发送消息 → 工具调用 → UI 渲染
- 多轮对话状态保持
-
回归测试
- 所有现有 MCP 工具功能正常
- json-render 组件渲染正常
参考文件
/tmp/json-render/packages/react/src/hooks.ts- 官方 hooks 源码/tmp/json-render/examples/chat/app/page.tsx- 官方示例前端/tmp/json-render/examples/chat/app/api/generate/route.ts- 官方示例 API/tmp/json-render/examples/chat/lib/agent.ts- Agent 配置
状态
- Step 1: 创建技术规格
- Step 2: 列出文件变更
- Step 3: 定义任务列表
- Step 4: 准备实施