|
|
@@ -0,0 +1,176 @@
|
|
|
+/**
|
|
|
+ * 主页面 - 聊天界面
|
|
|
+ *
|
|
|
+ * 集成 json-render 生成式 UI
|
|
|
+ * 支持动态渲染 MCP 工具调用结果
|
|
|
+ * 支持组件交互事件(ActionProvider)
|
|
|
+ */
|
|
|
+'use client';
|
|
|
+
|
|
|
+import React, { useState, useCallback, useEffect, useRef } from 'react';
|
|
|
+import { useChat } from '@/lib/hooks';
|
|
|
+import ChatInput from '@/components/ChatInput';
|
|
|
+import ChatMessage from '@/components/ChatMessage';
|
|
|
+import Header from '@/components/Header';
|
|
|
+import ToolCallPanel from '@/components/ToolCallPanel';
|
|
|
+import JsonRenderer from '@/components/JsonRenderer';
|
|
|
+import type { ComponentSpec } from '@/lib/json-render-catalog';
|
|
|
+import { ActionProvider } from '@/lib/action-context';
|
|
|
+
|
|
|
+// 消息类型扩展
|
|
|
+interface ExtendedMessage {
|
|
|
+ role: 'user' | 'assistant';
|
|
|
+ content: string;
|
|
|
+ specs?: ComponentSpec[]; // json-render 组件 specs
|
|
|
+ toolCalls?: Array<{ tool: string; result: unknown }>;
|
|
|
+}
|
|
|
+
|
|
|
+export default function Home() {
|
|
|
+ const [history, setHistory] = useState<ExtendedMessage[]>([]);
|
|
|
+ const { sendMessage, isLoading, response, toolCalls, specs, error, abort } = useChat();
|
|
|
+ const pendingMessageRef = useRef<string | null>(null);
|
|
|
+
|
|
|
+ // 当加载完成时,将完整的助手消息添加到历史记录
|
|
|
+ useEffect(() => {
|
|
|
+ console.log('[useEffect] Triggered:', {
|
|
|
+ isLoading,
|
|
|
+ pendingMessage: pendingMessageRef.current,
|
|
|
+ responseLength: response?.length || 0,
|
|
|
+ responsePreview: response?.substring(0, 100),
|
|
|
+ specsCount: specs?.length || 0,
|
|
|
+ toolCallsCount: toolCalls?.length || 0,
|
|
|
+ hasError: !!error,
|
|
|
+ });
|
|
|
+ if (!isLoading && pendingMessageRef.current) {
|
|
|
+ // 即使没有响应内容,也添加消息(可能只有工具调用结果)
|
|
|
+ const assistantMessage = {
|
|
|
+ role: 'assistant' as const,
|
|
|
+ content: response || (error ? `⚠️ ${error}` : ''),
|
|
|
+ specs: specs && specs.length > 0 ? specs : undefined,
|
|
|
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
|
+ };
|
|
|
+ console.log('[useEffect] Adding assistant message to history:', assistantMessage);
|
|
|
+ setHistory((prev) => [...prev, assistantMessage]);
|
|
|
+ pendingMessageRef.current = null;
|
|
|
+ }
|
|
|
+ }, [isLoading, response, toolCalls, specs, error]);
|
|
|
+
|
|
|
+ const handleSend = async (message: string) => {
|
|
|
+ // 添加用户消息到历史记录
|
|
|
+ setHistory((prev) => [...prev, { role: 'user', content: message }]);
|
|
|
+ pendingMessageRef.current = message;
|
|
|
+
|
|
|
+ // 开始流式对话(不等待完成,由 useEffect 处理完成后的消息)
|
|
|
+ sendMessage(message, history);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleAbort = useCallback(() => {
|
|
|
+ abort();
|
|
|
+ // 将当前的部分响应添加到历史记录
|
|
|
+ if (pendingMessageRef.current && (response || toolCalls.length > 0 || specs.length > 0)) {
|
|
|
+ setHistory((prev) => [
|
|
|
+ ...prev,
|
|
|
+ {
|
|
|
+ role: 'assistant',
|
|
|
+ content: response,
|
|
|
+ specs: specs && specs.length > 0 ? specs : undefined,
|
|
|
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
|
+ },
|
|
|
+ ]);
|
|
|
+ pendingMessageRef.current = null;
|
|
|
+ }
|
|
|
+ }, [abort, response, toolCalls, specs]);
|
|
|
+
|
|
|
+ // 创建 action context 用于组件交互
|
|
|
+ const actionContext = useCallback(() => ({
|
|
|
+ sendMessage: handleSend,
|
|
|
+ }), [handleSend]);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="flex flex-col h-screen bg-gray-50 dark:bg-gray-900">
|
|
|
+ <Header />
|
|
|
+
|
|
|
+ <main className="flex-1 overflow-hidden flex">
|
|
|
+ {/* 聊天区域 */}
|
|
|
+ <div className="flex-1 flex flex-col min-w-0">
|
|
|
+ <div className="flex-1 overflow-y-auto p-3 md:p-4">
|
|
|
+ {history.length === 0 ? (
|
|
|
+ <div className="flex items-center justify-center h-full text-gray-400">
|
|
|
+ <div className="text-center px-4">
|
|
|
+ <h2 className="text-xl md:text-2xl font-semibold mb-2">欢迎使用 AI MCP Web UI</h2>
|
|
|
+ <p className="text-sm md:text-base">开始与 AI 对话,支持 MCP 工具调用和生成式 UI</p>
|
|
|
+ <div className="mt-4 text-xs md:text-sm space-y-1">
|
|
|
+ <p>✨ 支持 json-render 动态组件渲染</p>
|
|
|
+ <p>🔧 MCP 工具调用结果可视化</p>
|
|
|
+ <p>🌐 翻译、小说阅读等专用组件</p>
|
|
|
+ <p>👆 点击组件可自动发送消息</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <div className="space-y-3 md:space-y-4 pb-20 md:pb-4">
|
|
|
+ {history.map((msg, idx) => {
|
|
|
+ console.log(`[Render] Message ${idx}:`, msg);
|
|
|
+ return (
|
|
|
+ <div key={idx} className="px-1">
|
|
|
+ <ChatMessage role={msg.role} content={msg.content} />
|
|
|
+ {/* 渲染 json-render specs - 使用 ActionProvider 包装 */}
|
|
|
+ {msg.specs && msg.specs.length > 0 && (
|
|
|
+ <ActionProvider actions={actionContext()}>
|
|
|
+ <div className="mt-2 md:mt-3 ml-2 md:ml-4 mr-2">
|
|
|
+ <JsonRenderer specs={msg.specs} className="max-w-full md:max-w-2xl" />
|
|
|
+ </div>
|
|
|
+ </ActionProvider>
|
|
|
+ )}
|
|
|
+ {/* 回退:渲染 toolCalls (用于调试) */}
|
|
|
+ {!msg.specs && msg.toolCalls && msg.toolCalls.length > 0 && (
|
|
|
+ <ActionProvider actions={actionContext()}>
|
|
|
+ <div className="mt-2 md:mt-3 ml-2 md:ml-4 mr-2">
|
|
|
+ <JsonRenderer toolCalls={msg.toolCalls} className="max-w-full md:max-w-2xl" />
|
|
|
+ </div>
|
|
|
+ </ActionProvider>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ {isLoading && response && (
|
|
|
+ <ChatMessage role="assistant" content={response} isLoading />
|
|
|
+ )}
|
|
|
+ {/* 加载中的工具调用结果 */}
|
|
|
+ {isLoading && specs.length > 0 && (
|
|
|
+ <ActionProvider actions={actionContext()}>
|
|
|
+ <div className="ml-2 md:ml-4 mt-2 md:mt-3 mr-2">
|
|
|
+ <JsonRenderer specs={specs} isLoading className="max-w-full md:max-w-2xl" />
|
|
|
+ </div>
|
|
|
+ </ActionProvider>
|
|
|
+ )}
|
|
|
+ {/* 加载中的工具调用 (回退) */}
|
|
|
+ {isLoading && specs.length === 0 && toolCalls.length > 0 && (
|
|
|
+ <ActionProvider actions={actionContext()}>
|
|
|
+ <div className="ml-2 md:ml-4 mt-2 md:mt-3 mr-2">
|
|
|
+ <JsonRenderer toolCalls={toolCalls} isLoading className="max-w-full md:max-w-2xl" />
|
|
|
+ </div>
|
|
|
+ </ActionProvider>
|
|
|
+ )}
|
|
|
+ {error && (
|
|
|
+ <div className="bg-red-100 dark:bg-red-900/30 border border-red-400 dark:border-red-600 text-red-700 dark:text-red-300 px-3 py-2 md:px-4 md:py-3 rounded-lg mx-1">
|
|
|
+ <p className="text-sm">{error}</p>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="border-t dark:border-gray-700 p-2 md:p-4 pb-safe bg-white dark:bg-gray-800">
|
|
|
+ <ChatInput onSend={handleSend} isLoading={isLoading} onAbort={handleAbort} />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 工具调用面板 - 仅在桌面端显示侧边栏 */}
|
|
|
+ {(toolCalls.length > 0 || isLoading) && (
|
|
|
+ <ToolCallPanel toolCalls={toolCalls} isLoading={isLoading} />
|
|
|
+ )}
|
|
|
+ </main>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|