| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- /**
- * 主页面 - 聊天界面
- *
- * 集成 json-render 生成式 UI
- * 支持动态渲染 MCP 工具调用结果
- * 支持组件交互事件(ActionProvider)
- *
- * 更新:ChatMessage 现在支持 markdown 混排渲染,
- * JSON 代码块会就地渲染为 json-render 组件
- */
- 'use client';
- import React, { useState, useCallback, useRef } from 'react';
- import { useChat } from '@/lib/hooks';
- 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 type { ComponentSpec } from '@/lib/json-render-catalog';
- // 消息类型
- interface Message {
- role: 'user' | 'assistant';
- content: string;
- }
- // 静态欢迎组件 spec
- const WELCOME_SPEC: ComponentSpec = {
- type: 'card',
- title: '欢迎使用 AI MCP Web UI',
- className: 'bg-gradient-to-br from-blue-50 to-purple-50 dark:from-blue-900/20 dark:to-purple-900/20 border-blue-200 dark:border-blue-800',
- children: [
- {
- type: 'text',
- content: '这是一个通用的 AI 助手界面,可以连接多个 MCP 服务器,提供强大的工具调用能力。',
- variant: 'body',
- className: 'mb-4'
- },
- {
- type: 'heading',
- level: 'h3',
- text: '可用的 MCP 服务器',
- className: 'mb-2 mt-4 text-gray-800 dark:text-gray-200'
- },
- {
- type: 'stack',
- direction: 'column',
- spacing: 1,
- className: 'mb-4',
- children: [
- {
- type: 'text',
- content: '🌐 Novel Translator MCP - 文本翻译工具',
- variant: 'body',
- className: 'text-sm text-gray-600 dark:text-gray-400'
- },
- {
- type: 'text',
- content: '📚 Novel Platform User MCP - 小说阅读功能',
- variant: 'body',
- className: 'text-sm text-gray-600 dark:text-gray-400'
- },
- {
- type: 'text',
- content: '⚙️ Novel Platform Admin MCP - 平台管理功能',
- variant: 'body',
- className: 'text-sm text-gray-600 dark:text-gray-400'
- }
- ]
- },
- {
- type: 'heading',
- level: 'h3',
- text: '快速开始',
- className: 'mb-3 mt-6 text-gray-800 dark:text-gray-200'
- },
- {
- type: 'suggestion-buttons',
- suggestions: [
- {
- label: '翻译一段文本',
- message: '请帮我把以下英文翻译成中文:Hello, how are you?',
- icon: '🌐'
- },
- {
- label: '查看小说列表',
- message: '请帮我获取小说列表',
- icon: '📚'
- },
- {
- label: '获取平台统计',
- message: '请帮我获取平台统计数据',
- icon: '📊'
- },
- {
- label: '查看 MCP 状态',
- message: '请帮我检查所有 MCP 服务器的连接状态',
- icon: '🔍'
- }
- ]
- }
- ]
- };
- export default function Home() {
- const [history, setHistory] = useState<Message[]>([]);
- const { sendMessage, isLoading, response, error, abort, specs } = useChat();
- const pendingMessageRef = useRef<string | null>(null);
- // 当加载完成时,将完整的助手消息添加到历史记录
- React.useEffect(() => {
- console.log('[useEffect] Triggered:', {
- isLoading,
- pendingMessage: pendingMessageRef.current,
- responseLength: response?.length || 0,
- responsePreview: response?.substring(0, 100),
- hasError: !!error,
- });
- // 添加新消息(加载完成且有待处理消息)
- if (!isLoading && pendingMessageRef.current) {
- const assistantMessage: Message = {
- role: 'assistant',
- content: response || (error ? `⚠️ ${error}` : ''),
- };
- console.log('[useEffect] Adding assistant message to history:', assistantMessage);
- setHistory((prev) => [...prev, assistantMessage]);
- pendingMessageRef.current = null;
- }
- }, [isLoading, response, 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) {
- setHistory((prev) => [
- ...prev,
- {
- role: 'assistant',
- content: response,
- },
- ]);
- pendingMessageRef.current = null;
- }
- }, [abort, response]);
- // 创建 action context 用于组件交互
- const actionContext = useCallback(() => ({
- sendMessage: handleSend,
- }), [handleSend]);
- return (
- <div className="flex flex-col h-screen-mobile min-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 ? (
- // 空状态:显示静态欢迎组件
- <ActionProvider actions={actionContext()}>
- <div className="flex items-start justify-center h-full pt-4 md:pt-8">
- <div className="w-full max-w-2xl px-2">
- <JsonRenderer spec={WELCOME_SPEC} />
- </div>
- </div>
- </ActionProvider>
- ) : (
- // 使用 ActionProvider 包装整个消息区域,使 ChatMessage 内部的 JsonRenderer 可以访问 ActionContext
- <ActionProvider actions={actionContext()}>
- <div className="space-y-3 md:space-y-4 messages-with-fixed-input">
- {history.map((msg, idx) => {
- console.log(`[Render] Message ${idx}:`, msg);
- return (
- <ChatMessage key={idx} role={msg.role} content={msg.content} />
- );
- })}
- {isLoading && response && (
- <ChatMessage role="assistant" content={response} specs={specs} isLoading />
- )}
- {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>
- </ActionProvider>
- )}
- </div>
- <div className="fixed-bottom-input md:relative border-t dark:border-gray-700 p-2 md:p-4 bg-white dark:bg-gray-800">
- <ChatInput onSend={handleSend} isLoading={isLoading} onAbort={handleAbort} />
- </div>
- </div>
- {/* 工具调用面板 - 仅在桌面端显示侧边栏 */}
- {/* 暂时隐藏,因为工具调用结果现在在 ChatMessage 中渲染 */}
- {/* {isLoading && (
- <ToolCallPanel toolCalls={[]} isLoading={isLoading} />
- )} */}
- </main>
- </div>
- );
- }
|