Files
ai-mcp-web-ui/planning-artifacts/quick-spec-json-render-official-hook.md
Claude AI 6957fd6fda feat: json-render 官方 Hook 重构完成
迁移到官方 @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>
2026-03-23 06:38:18 +00:00

9.7 KiB
Raw Permalink Blame History

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 的 useChat hook 替代自定义 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 结果      │
                                           └─────────────────────┘

核心变更

  1. 新建 Next.js API Route (/api/chat/route.ts)

    • 使用 Vercel AI SDK 的 streamText() 调用 Claude API
    • 使用 pipeJsonRender() 生成 JSONL patches
    • 使用 createUIMessageStreamResponse() 返回流式响应
  2. 前端使用官方 hooks

    • 替换自定义 useChat → AI SDK useChat
    • 使用 useJsonRenderMessagemessage.parts 提取 spec
    • 让 AI 生成 JSONL patches 而非手动转换
  3. Python 后端角色变更

    • 保留 MCP 工具调用逻辑
    • 新增 /api/mcp/invoke 端点供 Next.js 调用
    • 不再处理 UI 生成逻辑
  4. 删除冗余代码

    • 删除 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: 基础设施准备

  1. 安装 Vercel AI SDK 依赖
  2. 创建 /api/chat/route.ts 基础框架
  3. 创建 MCP 代理客户端

Phase 2: 前端改造

  1. 修改 page.tsx 使用 AI SDK useChat
  2. 修改 ChatMessage 使用 useJsonRenderMessage
  3. 删除自定义 useChat hook

Phase 3: 后端适配

  1. Python 后端新增 /api/mcp/invoke 端点
  2. 保留现有 /api/chat/stream 作为备选

Phase 4: 清理

  1. 删除 specFromToolCall 函数
  2. 删除自定义 SSE 解析逻辑
  3. 更新 CLAUDE.md 文档

风险与缓解

风险 影响 缓解措施
Vercel AI SDK 兼容性 Next.js 版本要求 确认 Next.js 14.2.18 兼容
MCP 工具调用延迟 增加一跳网络请求 使用本地代理,延迟 <10ms
流式响应格式变更 前端需要适配 保留旧端点作为回退

测试计划

  1. 单元测试

    • useJsonRenderMessage 正确提取 spec 和 text
    • MCP 代理客户端正确传递参数
  2. 集成测试

    • 完整聊天流程:发送消息 → 工具调用 → UI 渲染
    • 多轮对话状态保持
  3. 回归测试

    • 所有现有 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: 准备实施