211 lines
6.8 KiB
TypeScript
211 lines
6.8 KiB
TypeScript
// app/chat-direct/page.tsx - 直接访问版本(跳过认证)
|
||
'use client';
|
||
|
||
import React, { useState, useRef, useEffect } from 'react';
|
||
import { mcpService } from '../../services/mcp-real';
|
||
|
||
export default function DirectChatPage() {
|
||
const [messages, setMessages] = useState<any[]>([]);
|
||
const [input, setInput] = useState('');
|
||
const [isLoading, setIsLoading] = useState(false);
|
||
const [tools, setTools] = useState<any[]>([]);
|
||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||
|
||
useEffect(() => {
|
||
loadTools();
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||
}, [messages]);
|
||
|
||
const loadTools = async () => {
|
||
try {
|
||
const availableTools = await mcpService.getTools();
|
||
setTools(availableTools);
|
||
console.log('Available tools:', availableTools);
|
||
} catch (error) {
|
||
console.error('Failed to load tools:', error);
|
||
}
|
||
};
|
||
|
||
const handleSend = async () => {
|
||
if (!input.trim() || isLoading) return;
|
||
|
||
const userMessage = {
|
||
id: Date.now().toString(),
|
||
role: 'user',
|
||
content: input,
|
||
timestamp: new Date(),
|
||
};
|
||
|
||
setMessages(prev => [...prev, userMessage]);
|
||
setInput('');
|
||
setIsLoading(true);
|
||
|
||
try {
|
||
const assistantMessage = {
|
||
id: (Date.now() + 1).toString(),
|
||
role: 'assistant',
|
||
content: '',
|
||
timestamp: new Date(),
|
||
isStreaming: true,
|
||
};
|
||
|
||
setMessages(prev => [...prev, assistantMessage]);
|
||
|
||
// 尝试流式响应
|
||
let fullContent = '';
|
||
try {
|
||
for await (const chunk of mcpService.streamMessage(userMessage.content)) {
|
||
fullContent += chunk;
|
||
setMessages(prev =>
|
||
prev.map(msg =>
|
||
msg.id === assistantMessage.id
|
||
? { ...msg, content: fullContent }
|
||
: msg
|
||
)
|
||
);
|
||
}
|
||
} catch (streamError) {
|
||
console.log('Stream failed, trying non-stream:', streamError);
|
||
// 如果流式失败,尝试普通请求
|
||
const response = await mcpService.sendMessage(userMessage.content);
|
||
fullContent = response;
|
||
}
|
||
|
||
// 更新最终消息
|
||
setMessages(prev =>
|
||
prev.map(msg =>
|
||
msg.id === assistantMessage.id
|
||
? { ...msg, content: fullContent || '没有收到回复', isStreaming: false }
|
||
: msg
|
||
)
|
||
);
|
||
} catch (error) {
|
||
console.error('Send message error:', error);
|
||
setMessages(prev => [
|
||
...prev,
|
||
{
|
||
id: Date.now().toString(),
|
||
role: 'system',
|
||
content: `错误: ${error.message}`,
|
||
timestamp: new Date(),
|
||
},
|
||
]);
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="flex h-screen bg-gray-50">
|
||
{/* 左侧工具栏 */}
|
||
<div className="w-64 bg-white border-r p-4">
|
||
<h3 className="font-bold mb-4">MCP 工具</h3>
|
||
<div className="space-y-2">
|
||
{tools.map(tool => (
|
||
<div
|
||
key={tool.name}
|
||
className="p-2 bg-gray-100 rounded hover:bg-gray-200 cursor-pointer"
|
||
onClick={() => {
|
||
setInput(`使用工具: ${tool.name}`);
|
||
}}
|
||
>
|
||
<div className="font-medium text-sm">{tool.name}</div>
|
||
<div className="text-xs text-gray-600">{tool.description}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<div className="mt-6 p-3 bg-blue-50 rounded">
|
||
<div className="text-sm font-medium">Max 用户</div>
|
||
<div className="text-xs text-gray-600">完全访问权限</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 主聊天区域 */}
|
||
<div className="flex-1 flex flex-col">
|
||
<div className="bg-white border-b p-4">
|
||
<h1 className="text-xl font-bold">AI Chat (Direct Access)</h1>
|
||
<p className="text-sm text-gray-600">MCP 服务直连 - 无需认证</p>
|
||
</div>
|
||
|
||
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
||
{messages.length === 0 && (
|
||
<div className="text-center text-gray-500 mt-10">
|
||
<p>开始对话吧!试试这些:</p>
|
||
<div className="mt-4 space-y-2">
|
||
<button
|
||
onClick={() => setInput('你好,介绍一下自己')}
|
||
className="text-blue-600 hover:underline block mx-auto"
|
||
>
|
||
"你好,介绍一下自己"
|
||
</button>
|
||
<button
|
||
onClick={() => setInput('列出可用的工具')}
|
||
className="text-blue-600 hover:underline block mx-auto"
|
||
>
|
||
"列出可用的工具"
|
||
</button>
|
||
<button
|
||
onClick={() => setInput('帮我写一段 Python 代码')}
|
||
className="text-blue-600 hover:underline block mx-auto"
|
||
>
|
||
"帮我写一段 Python 代码"
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{messages.map(msg => (
|
||
<div
|
||
key={msg.id}
|
||
className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}
|
||
>
|
||
<div
|
||
className={`max-w-2xl px-4 py-2 rounded-lg ${
|
||
msg.role === 'user'
|
||
? 'bg-blue-600 text-white'
|
||
: msg.role === 'system'
|
||
? 'bg-red-100 text-red-800'
|
||
: 'bg-gray-100 text-gray-900'
|
||
}`}
|
||
>
|
||
<div className="whitespace-pre-wrap">{msg.content}</div>
|
||
<div className="text-xs mt-1 opacity-70">
|
||
{msg.timestamp.toLocaleTimeString()}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
<div ref={messagesEndRef} />
|
||
</div>
|
||
|
||
<div className="border-t p-4 bg-white">
|
||
<div className="flex space-x-2">
|
||
<input
|
||
type="text"
|
||
value={input}
|
||
onChange={e => setInput(e.target.value)}
|
||
onKeyPress={e => e.key === 'Enter' && !e.shiftKey && handleSend()}
|
||
disabled={isLoading}
|
||
placeholder="输入消息... (回车发送)"
|
||
className="flex-1 px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
/>
|
||
<button
|
||
onClick={handleSend}
|
||
disabled={!input.trim() || isLoading}
|
||
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||
>
|
||
{isLoading ? '发送中...' : '发送'}
|
||
</button>
|
||
</div>
|
||
<div className="mt-2 text-xs text-gray-500">
|
||
提示:直接输入问题,或点击左侧工具名称快速使用
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
} |