218 lines
9.0 KiB
TypeScript
218 lines
9.0 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { useGatewayStore } from '../store/gatewayStore';
|
|
import { useChatStore } from '../store/chatStore';
|
|
import {
|
|
Wifi, WifiOff, Bot, BarChart3, Plug, RefreshCw,
|
|
MessageSquare, Cpu, Activity,
|
|
} from 'lucide-react';
|
|
|
|
export function RightPanel() {
|
|
const {
|
|
connectionState, gatewayVersion, error, clones, usageStats, pluginStatus,
|
|
connect, loadClones, loadUsageStats, loadPluginStatus,
|
|
} = useGatewayStore();
|
|
const { messages, currentModel } = useChatStore();
|
|
|
|
const connected = connectionState === 'connected';
|
|
|
|
// Load data when connected
|
|
useEffect(() => {
|
|
if (connected) {
|
|
loadClones();
|
|
loadUsageStats();
|
|
loadPluginStatus();
|
|
}
|
|
}, [connected]);
|
|
|
|
const handleReconnect = () => {
|
|
connect().catch(() => {});
|
|
};
|
|
|
|
const userMsgCount = messages.filter(m => m.role === 'user').length;
|
|
const assistantMsgCount = messages.filter(m => m.role === 'assistant').length;
|
|
const toolCallCount = messages.filter(m => m.role === 'tool').length;
|
|
|
|
return (
|
|
<aside className="w-72 bg-white border-l border-gray-200 flex flex-col flex-shrink-0">
|
|
{/* 顶部 */}
|
|
<div className="h-14 border-b border-gray-100 flex items-center justify-between px-4 flex-shrink-0">
|
|
<div className="flex items-center gap-2">
|
|
<Activity className="w-4 h-4 text-gray-500" />
|
|
<span className="font-medium text-gray-700 text-sm">系统状态</span>
|
|
</div>
|
|
{connected && (
|
|
<button
|
|
onClick={() => { loadUsageStats(); loadPluginStatus(); loadClones(); }}
|
|
className="p-1 text-gray-400 hover:text-orange-500 rounded transition-colors"
|
|
title="刷新数据"
|
|
>
|
|
<RefreshCw className="w-3.5 h-3.5" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex-1 overflow-y-auto custom-scrollbar p-4 space-y-4">
|
|
{/* Gateway 连接状态 */}
|
|
<div className={`rounded-lg border p-3 ${connected ? 'bg-green-50 border-green-200' : 'bg-gray-50 border-gray-200'}`}>
|
|
<div className="flex items-center gap-2 mb-2">
|
|
{connected ? (
|
|
<Wifi className="w-4 h-4 text-green-600" />
|
|
) : (
|
|
<WifiOff className="w-4 h-4 text-gray-400" />
|
|
)}
|
|
<span className={`text-xs font-semibold ${connected ? 'text-green-700' : 'text-gray-600'}`}>
|
|
Gateway {connected ? '已连接' : connectionState === 'connecting' ? '连接中...' : connectionState === 'reconnecting' ? '重连中...' : '未连接'}
|
|
</span>
|
|
</div>
|
|
<div className="space-y-1 text-xs">
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-500">地址</span>
|
|
<span className="text-gray-700 font-mono">127.0.0.1:18789</span>
|
|
</div>
|
|
{gatewayVersion && (
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-500">版本</span>
|
|
<span className="text-gray-700">{gatewayVersion}</span>
|
|
</div>
|
|
)}
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-500">当前模型</span>
|
|
<span className="text-orange-600 font-medium">{currentModel}</span>
|
|
</div>
|
|
</div>
|
|
{!connected && connectionState !== 'connecting' && (
|
|
<button
|
|
onClick={handleReconnect}
|
|
className="mt-2 w-full text-xs bg-orange-500 text-white rounded py-1.5 hover:bg-orange-600 transition-colors"
|
|
>
|
|
连接 Gateway
|
|
</button>
|
|
)}
|
|
{error && (
|
|
<p className="mt-2 text-xs text-red-500 truncate" title={error}>{error}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* 当前会话 */}
|
|
<div className="bg-gray-50 rounded-lg border border-gray-100 p-3">
|
|
<h3 className="text-xs font-semibold text-gray-700 mb-2 flex items-center gap-1.5">
|
|
<MessageSquare className="w-3.5 h-3.5" />
|
|
当前会话
|
|
</h3>
|
|
<div className="space-y-1.5 text-xs">
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-500">用户消息</span>
|
|
<span className="font-medium text-gray-900">{userMsgCount}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-500">助手回复</span>
|
|
<span className="font-medium text-gray-900">{assistantMsgCount}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-500">工具调用</span>
|
|
<span className="font-medium text-gray-900">{toolCallCount}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-500">总消息数</span>
|
|
<span className="font-medium text-orange-600">{messages.length}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 分身 */}
|
|
<div className="bg-gray-50 rounded-lg border border-gray-100 p-3">
|
|
<h3 className="text-xs font-semibold text-gray-700 mb-2 flex items-center gap-1.5">
|
|
<Bot className="w-3.5 h-3.5" />
|
|
分身
|
|
</h3>
|
|
{clones.length > 0 ? (
|
|
<div className="space-y-1.5">
|
|
{clones.slice(0, 5).map(c => (
|
|
<div key={c.id} className="flex items-center gap-2 text-xs">
|
|
<div className="w-5 h-5 bg-gradient-to-br from-orange-400 to-red-500 rounded-md flex items-center justify-center text-white text-[10px]">
|
|
<Bot className="w-3 h-3" />
|
|
</div>
|
|
<span className="text-gray-700 truncate">{c.name}</span>
|
|
</div>
|
|
))}
|
|
{clones.length > 5 && (
|
|
<p className="text-xs text-gray-400">+{clones.length - 5} 个分身</p>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<p className="text-xs text-gray-400">
|
|
{connected ? '暂无分身,在左侧栏创建' : '连接后可用'}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* 用量统计 */}
|
|
{usageStats && (
|
|
<div className="bg-gray-50 rounded-lg border border-gray-100 p-3">
|
|
<h3 className="text-xs font-semibold text-gray-700 mb-2 flex items-center gap-1.5">
|
|
<BarChart3 className="w-3.5 h-3.5" />
|
|
用量统计
|
|
</h3>
|
|
<div className="space-y-1.5 text-xs">
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-500">总会话数</span>
|
|
<span className="font-medium text-gray-900">{usageStats.totalSessions}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-500">总消息数</span>
|
|
<span className="font-medium text-gray-900">{usageStats.totalMessages}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-500">总 Token</span>
|
|
<span className="font-medium text-gray-900">{usageStats.totalTokens.toLocaleString()}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 插件状态 */}
|
|
{pluginStatus.length > 0 && (
|
|
<div className="bg-gray-50 rounded-lg border border-gray-100 p-3">
|
|
<h3 className="text-xs font-semibold text-gray-700 mb-2 flex items-center gap-1.5">
|
|
<Plug className="w-3.5 h-3.5" />
|
|
插件 ({pluginStatus.length})
|
|
</h3>
|
|
<div className="space-y-1 text-xs">
|
|
{pluginStatus.map((p: any, i: number) => (
|
|
<div key={i} className="flex justify-between">
|
|
<span className="text-gray-600 truncate">{p.name || p.id}</span>
|
|
<span className={p.status === 'active' ? 'text-green-600' : 'text-gray-400'}>
|
|
{p.status === 'active' ? '运行中' : '已停止'}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 系统信息 */}
|
|
<div className="bg-gray-50 rounded-lg border border-gray-100 p-3">
|
|
<h3 className="text-xs font-semibold text-gray-700 mb-2 flex items-center gap-1.5">
|
|
<Cpu className="w-3.5 h-3.5" />
|
|
系统信息
|
|
</h3>
|
|
<div className="space-y-1.5 text-xs">
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-500">ZCLAW 版本</span>
|
|
<span className="text-gray-700">v0.2.0</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-500">协议版本</span>
|
|
<span className="text-gray-700">Gateway v3</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-500">平台</span>
|
|
<span className="text-gray-700">Tauri 2.0</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|