- 引入 DirectAnnouncementCallback 类型用于处理直连模式下的公告 - 在 DelegationManager 中添加 _direct_announcement_callback 属性和设置方法 - 实现 _notify_direct_announcement 方法用于在非总线模式下将公告回写到本地会话 - 在委托取消、完成和分组完成时添加对直连公告的通知逻辑 feat(web): 增加 WebSocket 广播器支持实时会话更新通知 - 创建 WebSocketBroadcaster 类用于跟踪认证的 WebSocket 连接并广播 JSON 事件 - 在应用启动时初始化 websocket_broadcaster 实例 - 实现连接注册、注销和消息广播功能 - 添加过期连接清理机制 feat(agent): 新增系统公告处理方法支持本地处理 - 在 AgentLoop 中添加 process_system_announcement 方法用于在无常驻 run() 场景下处理系统公告 - 创建 InboundMessage 并通过 _process_message 进行处理 feat(cron): 改进定时任务的会话路由解析和实时更新 - 添加 _resolve_cron_session_key 和 _infer_cron_route_from_session_key 辅助函数 - 在 cron 任务执行完成后通过 WebSocket 广播会话更新事件 - 在添加定时任务时自动推断目标会话的渠道和聊天 ID refactor: 项目名称从 Boardware Genius 统一改为 Boardware Agent Sandbox - 更新前端页面标题和描述文本中的产品名称 - 添加新的品牌 Logo 图片资源 - 在前端布局中使用新的 Logo 显示 - 更新授权门户中的品牌信息和 Logo 显示 feat(frontend): 添加会话更新事件监听实现消息自动刷新 - 定义 SessionUpdatedEvent 类型接口 - 在 ChatPage 中添加会话更新事件的处理逻辑 - 当收到会话更新事件时自动重新加载会话列表和当前会话消息 feat(api): 扩展定时任务 API 支持会话键参数 - 在 addCronJob API 参数中添加 session_key 字段 - 更新前端 Cron 页面的表单处理以传递当前会话键
236 lines
6.8 KiB
TypeScript
236 lines
6.8 KiB
TypeScript
'use client';
|
||
|
||
import React, { useEffect, useState } from 'react';
|
||
import {
|
||
CheckCircle2,
|
||
XCircle,
|
||
AlertCircle,
|
||
RefreshCw,
|
||
Server,
|
||
Cpu,
|
||
Radio,
|
||
Key,
|
||
Loader2,
|
||
} from 'lucide-react';
|
||
import { getStatus } from '@/lib/api';
|
||
import { Button } from '@/components/ui/button';
|
||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||
import { Badge } from '@/components/ui/badge';
|
||
import { Separator } from '@/components/ui/separator';
|
||
import type { SystemStatus } from '@/types';
|
||
|
||
export default function StatusPage() {
|
||
const [status, setStatus] = useState<SystemStatus | null>(null);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
const loadStatus = async () => {
|
||
setLoading(true);
|
||
setError(null);
|
||
try {
|
||
const data = await getStatus();
|
||
setStatus(data);
|
||
} catch (err: any) {
|
||
setError(err.message || '连接后端失败');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
loadStatus();
|
||
}, []);
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="flex items-center justify-center py-20">
|
||
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="max-w-4xl mx-auto p-6">
|
||
<Card className="border-destructive">
|
||
<CardContent className="pt-6">
|
||
<div className="flex items-center gap-3 text-destructive">
|
||
<AlertCircle className="w-5 h-5" />
|
||
<div>
|
||
<p className="font-medium">无法连接到 Boardware Agent Sandbox 后端</p>
|
||
<p className="text-sm text-muted-foreground mt-1">{error}</p>
|
||
<p className="text-sm text-muted-foreground mt-1">
|
||
请确认后端已启动:<code className="bg-muted px-1 rounded">nanobot web</code>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<Button onClick={loadStatus} variant="outline" size="sm" className="mt-4">
|
||
<RefreshCw className="w-4 h-4 mr-2" />
|
||
重试
|
||
</Button>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (!status) return null;
|
||
|
||
return (
|
||
<div className="max-w-4xl mx-auto p-6 space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<h1 className="text-2xl font-bold">系统状态</h1>
|
||
<Button onClick={loadStatus} variant="outline" size="sm">
|
||
<RefreshCw className="w-4 h-4 mr-2" />
|
||
刷新
|
||
</Button>
|
||
</div>
|
||
|
||
{/* System Info */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2 text-base">
|
||
<Server className="w-4 h-4" />
|
||
系统信息
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-3">
|
||
<InfoRow
|
||
label="配置"
|
||
value={status.config_path}
|
||
ok={status.config_exists}
|
||
/>
|
||
<InfoRow
|
||
label="工作区"
|
||
value={status.workspace}
|
||
ok={status.workspace_exists}
|
||
/>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Model Config */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2 text-base">
|
||
<Cpu className="w-4 h-4" />
|
||
智能体配置
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-3">
|
||
<InfoRow label="模型" value={status.model} />
|
||
<InfoRow label="最大令牌数" value={String(status.max_tokens)} />
|
||
<InfoRow label="温度" value={String(status.temperature)} />
|
||
<InfoRow
|
||
label="最大工具迭代次数"
|
||
value={String(status.max_tool_iterations)}
|
||
/>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Providers */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2 text-base">
|
||
<Key className="w-4 h-4" />
|
||
提供商
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||
{status.providers.map((p) => (
|
||
<div
|
||
key={p.name}
|
||
className="flex items-center gap-2 text-sm"
|
||
>
|
||
{p.has_key ? (
|
||
<CheckCircle2 className="w-4 h-4 text-green-500" />
|
||
) : (
|
||
<XCircle className="w-4 h-4 text-muted-foreground/40" />
|
||
)}
|
||
<span className={p.has_key ? '' : 'text-muted-foreground'}>
|
||
{p.name}
|
||
</span>
|
||
{p.detail && (
|
||
<span className="text-xs text-muted-foreground truncate">
|
||
{p.detail}
|
||
</span>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Channels */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2 text-base">
|
||
<Radio className="w-4 h-4" />
|
||
通道
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||
{status.channels.map((ch) => (
|
||
<div key={ch.name} className="flex items-center gap-2 text-sm">
|
||
<Badge
|
||
variant={ch.enabled ? 'default' : 'secondary'}
|
||
className="text-xs"
|
||
>
|
||
{ch.enabled ? '开启' : '关闭'}
|
||
</Badge>
|
||
<span className="capitalize">{ch.name}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Cron Summary */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2 text-base">
|
||
<AlertCircle className="w-4 h-4" />
|
||
调度器
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-3">
|
||
<InfoRow
|
||
label="状态"
|
||
value={status.cron.enabled ? '运行中' : '已停止'}
|
||
ok={status.cron.enabled}
|
||
/>
|
||
<InfoRow label="任务数" value={String(status.cron.jobs)} />
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function InfoRow({
|
||
label,
|
||
value,
|
||
ok,
|
||
}: {
|
||
label: string;
|
||
value: string;
|
||
ok?: boolean;
|
||
}) {
|
||
return (
|
||
<div className="flex items-center justify-between text-sm">
|
||
<span className="text-muted-foreground">{label}</span>
|
||
<div className="flex items-center gap-2">
|
||
<code className="bg-muted px-2 py-0.5 rounded text-xs max-w-[400px] truncate">
|
||
{value}
|
||
</code>
|
||
{ok !== undefined &&
|
||
(ok ? (
|
||
<CheckCircle2 className="w-4 h-4 text-green-500" />
|
||
) : (
|
||
<XCircle className="w-4 h-4 text-destructive" />
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|