diff --git a/frontend-v2/app/page.tsx b/frontend-v2/app/page.tsx
index 7b56728..931b11f 100644
--- a/frontend-v2/app/page.tsx
+++ b/frontend-v2/app/page.tsx
@@ -1,19 +1,21 @@
/**
* 主页面 - 聊天界面
*
- * 使用官方 useChatUI hook 集成 json-render 生成式 UI
+ * 使用 Vercel AI SDK useChat hook
* 支持动态渲染 MCP 工具调用结果
* 支持组件交互事件(ActionProvider)
+ * 支持 MCP Token 自动传递
*/
'use client';
import React, { useCallback } from 'react';
-import { useChatUI, Renderer } from '@json-render/react';
+import { useChat } from 'ai/react';
import ChatInput from '@/components/ChatInput';
+import ChatMessage from '@/components/ChatMessage';
import Header from '@/components/Header';
import JsonRenderer from '@/components/JsonRenderer';
import { ActionProvider } from '@/lib/action-context';
-import { registry } from '@/lib/registry';
+import { McpTokenManager } from '@/lib/mcp-token-manager';
import type { ComponentSpec } from '@/lib/json-render-catalog';
// 静态欢迎组件 spec
@@ -95,13 +97,43 @@ const WELCOME_SPEC: ComponentSpec = {
};
export default function Home() {
- const { messages, isStreaming, send, clear } = useChatUI({
+ const mcpTokenManager = McpTokenManager.getInstance();
+
+ const { messages, input, handleInputChange, handleSubmit, isLoading, stop } = useChat({
api: '/api/chat',
+ headers: async () => {
+ const tokens = mcpTokenManager.getAllTokens();
+ const enabledMcpList = mcpTokenManager.getEnabledMcpList();
+ return {
+ 'X-MCP-Tokens': JSON.stringify(tokens),
+ 'X-MCP-Enabled': JSON.stringify(enabledMcpList),
+ };
+ },
});
+ // 兼容旧代码的 send 函数
+ const send = async (text: string) => {
+ // 设置输入值并提交表单
+ const formEvent = new Event('submit', { bubbles: true, cancelable: true });
+ await handleSubmit(formEvent);
+ };
+
const actionContext = useCallback(() => ({
- sendMessage: send,
- }), [send]);
+ sendMessage: (text: string) => {
+ // 直接设置 input 值并提交
+ const form = document.querySelector('form') as HTMLFormElement;
+ if (form) {
+ const textarea = form.querySelector('textarea') as HTMLTextAreaElement;
+ if (textarea) {
+ textarea.value = text;
+ const event = new Event('input', { bubbles: true });
+ textarea.dispatchEvent(event);
+ }
+ const submitEvent = new Event('submit', { bubbles: true, cancelable: true });
+ form.dispatchEvent(submitEvent);
+ }
+ },
+ }), []);
return (
@@ -113,37 +145,34 @@ export default function Home() {
- { if (action === "sendMessage") { send(params?.message || ""); } }} />
+ {
+ if (action === "sendMessage") {
+ actionContext().sendMessage(params?.message || "");
+ }
+ }} />
) : (
-
-
- {messages.map((msg) => (
-
- {msg.role === 'user' ? (
-
- ) : (
-
- {msg.text &&
{msg.text}
}
- {msg.spec &&
}
-
- )}
-
- ))}
- {isStreaming &&
思考中...
}
-
-
+
+ {messages.map((msg, idx) => (
+
+ ))}
+
)}
-
-
-
+
diff --git a/frontend-v2/components/ChatInput.tsx b/frontend-v2/components/ChatInput.tsx
index 5086ae1..63f5f58 100644
--- a/frontend-v2/components/ChatInput.tsx
+++ b/frontend-v2/components/ChatInput.tsx
@@ -1,33 +1,69 @@
/**
* 聊天输入组件 - 移动端优化
+ *
+ * 支持两种模式:
+ * 1. 受控模式:传入 value, onChange, onSubmit
+ * 2. 非受控模式:传入 onSend (兼容旧代码)
*/
'use client';
import { useState, FormEvent, KeyboardEvent, useRef, useEffect } from 'react';
interface ChatInputProps {
- onSend: (message: string) => void;
- isLoading: boolean;
- onAbort: () => void;
+ // 受控模式 props (useChat)
+ value?: string;
+ onChange?: (e: React.ChangeEvent) => void;
+ onSubmit?: (e: React.FormEvent) => void;
+ isLoading?: boolean;
+ onStop?: () => void;
+
+ // 非受控模式 props (兼容旧代码)
+ onSend?: (message: string) => void;
+ onAbort?: () => void;
}
-export default function ChatInput({ onSend, isLoading, onAbort }: ChatInputProps) {
- const [input, setInput] = useState('');
+export default function ChatInput({
+ // 受控模式 props
+ value, onChange, onSubmit, isLoading, onStop,
+ // 非受控模式 props
+ onSend, onAbort
+}: ChatInputProps) {
+ // 检测是否为受控模式
+ const isControlled = value !== undefined && onChange !== undefined;
+
+ const [localInput, setLocalInput] = useState('');
const textareaRef = useRef(null);
+ // 使用受控或非受控的值
+ const inputValue = isControlled ? value : localInput;
+ const loading = isLoading ?? false;
+ const abortHandler = onStop ?? onAbort;
+
// 自动调整 textarea 高度
useEffect(() => {
if (textareaRef.current) {
textareaRef.current.style.height = 'auto';
textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 150)}px`;
}
- }, [input]);
+ }, [inputValue]);
+
+ const handleChange = (e: React.ChangeEvent) => {
+ if (isControlled) {
+ onChange(e);
+ } else {
+ setLocalInput(e.target.value);
+ }
+ };
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
- if (input.trim() && !isLoading) {
- onSend(input.trim());
- setInput('');
+ if (onSubmit) {
+ // 受控模式:直接调用 onSubmit
+ onSubmit(e);
+ } else if (onSend && inputValue.trim() && !loading) {
+ // 非受控模式:调用 onSend 并清空输入
+ onSend(inputValue.trim());
+ setLocalInput('');
// 重置 textarea 高度
if (textareaRef.current) {
textareaRef.current.style.height = 'auto';
@@ -47,28 +83,28 @@ export default function ChatInput({ onSend, isLoading, onAbort }: ChatInputProps
{/* 移动端:图标按钮 */}
- {isLoading ? (
+ {loading ? (