- 引入AgentTeamOrchestrator支持多agent协同任务执行 - 增加第三方swarms库依赖并配置git协议替换以改善包管理 - 扩展DelegationManager支持团队任务调度和进度跟踪 - 实现中文bigram分词算法提升中文任务检索准确性 - 调整A2AClient和DelegationManager超时时间从30秒增至600秒 - 优化AgentRunResult状态判断逻辑增加有意义摘要检测 - 修改Dockerfile配置npm仓库镜像地址和git协议映射 - 更新CLI命令行接口支持网关端口配置传递 - 调整提供者超时配置机制增强请求稳定性 - 移除过时的support_group字段简化agent描述符结构 - 增强错误处理和进度事件报告机制改进用户体验
246 lines
10 KiB
TypeScript
246 lines
10 KiB
TypeScript
'use client';
|
|
|
|
import React from 'react';
|
|
import Link from 'next/link';
|
|
import Image from 'next/image';
|
|
import { usePathname, useRouter } from 'next/navigation';
|
|
import { MessageSquare, Activity, Clock, Puzzle, Blocks, FolderOpen, Store, LogIn, UserPlus, Bot, ServerCog, Mail, LogOut, ChevronDown } from 'lucide-react';
|
|
import { logout } from '@/lib/api';
|
|
import { LanguageSwitcher } from '@/components/LanguageSwitcher';
|
|
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
|
import { appConnectionStatusLabel } from '@/lib/i18n/common';
|
|
import { pickAppText } from '@/lib/i18n/core';
|
|
import { useAppI18n } from '@/lib/i18n/provider';
|
|
import { useChatStore } from '@/lib/store';
|
|
|
|
type NavItem = {
|
|
key:
|
|
| 'chat'
|
|
| 'status'
|
|
| 'office'
|
|
| 'skills'
|
|
| 'plugins'
|
|
| 'agents'
|
|
| 'mcp'
|
|
| 'outlook'
|
|
| 'marketplace'
|
|
| 'files';
|
|
href: string;
|
|
icon: React.ComponentType<{ className?: string }>;
|
|
matchPrefixes?: string[];
|
|
};
|
|
|
|
const NAV_ITEMS: NavItem[] = [
|
|
{ key: 'chat', href: '/', icon: MessageSquare },
|
|
{ key: 'status', href: '/status', icon: Activity },
|
|
{ key: 'office', href: '/office', icon: Clock, matchPrefixes: ['/office', '/cron'] },
|
|
{ key: 'skills', href: '/skills', icon: Puzzle },
|
|
{ key: 'plugins', href: '/plugins', icon: Blocks },
|
|
{ key: 'agents', href: '/agents', icon: Bot },
|
|
{ key: 'mcp', href: '/mcp', icon: ServerCog },
|
|
{ key: 'outlook', href: '/outlook', icon: Mail },
|
|
{ key: 'marketplace', href: '/marketplace', icon: Store },
|
|
{ key: 'files', href: '/files', icon: FolderOpen },
|
|
];
|
|
|
|
const AUTH_ITEMS = [
|
|
{ key: 'login', href: '/login', icon: LogIn },
|
|
{ key: 'register', href: '/register', icon: UserPlus },
|
|
] as const;
|
|
|
|
function ConnectionDot() {
|
|
const { locale } = useAppI18n();
|
|
const wsStatus = useChatStore((s) => s.wsStatus);
|
|
const nanobotReady = useChatStore((s) => s.nanobotReady);
|
|
|
|
const isOnline = wsStatus === 'connected' && nanobotReady === true;
|
|
const isChecking = wsStatus === 'connected' && nanobotReady === null;
|
|
const isConnecting = wsStatus === 'connecting' || isChecking;
|
|
const isOffline = wsStatus === 'disconnected' || (wsStatus === 'connected' && nanobotReady === false);
|
|
|
|
const color = isOnline
|
|
? 'bg-green-500'
|
|
: isConnecting
|
|
? 'bg-yellow-500'
|
|
: 'bg-red-500';
|
|
|
|
const label = appConnectionStatusLabel(wsStatus, nanobotReady, locale);
|
|
|
|
return (
|
|
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
<span className={`w-2 h-2 rounded-full ${color}`} />
|
|
<span>{label}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const Header = () => {
|
|
const { locale } = useAppI18n();
|
|
const pathname = usePathname();
|
|
const router = useRouter();
|
|
const user = useChatStore((s) => s.user);
|
|
const isAuthLoading = useChatStore((s) => s.isAuthLoading);
|
|
const setUser = useChatStore((s) => s.setUser);
|
|
|
|
const navLabel = React.useCallback((key: NavItem['key']) => {
|
|
if (key === 'chat') return pickAppText(locale, '对话', 'Chat');
|
|
if (key === 'status') return pickAppText(locale, '状态', 'Status');
|
|
if (key === 'office') return pickAppText(locale, '任务管理', 'Tasks');
|
|
if (key === 'skills') return pickAppText(locale, '技能', 'Skills');
|
|
if (key === 'plugins') return pickAppText(locale, '插件', 'Plugins');
|
|
if (key === 'agents') return pickAppText(locale, '智能体', 'Agents');
|
|
if (key === 'mcp') return 'MCP';
|
|
if (key === 'outlook') return 'Outlook';
|
|
if (key === 'marketplace') return pickAppText(locale, '市场', 'Marketplace');
|
|
return pickAppText(locale, '文件', 'Files');
|
|
}, [locale]);
|
|
|
|
const authLabel = React.useCallback((key: 'login' | 'register') => (
|
|
key === 'login'
|
|
? pickAppText(locale, '登录', 'Sign In')
|
|
: pickAppText(locale, '注册', 'Sign Up')
|
|
), [locale]);
|
|
|
|
const handleLogout = async () => {
|
|
await logout();
|
|
setUser(null);
|
|
router.replace('/login');
|
|
router.refresh();
|
|
};
|
|
|
|
const userInitial = (user?.username || user?.email || '?').trim().charAt(0).toUpperCase();
|
|
|
|
return (
|
|
<header className="fixed top-0 left-0 right-0 bg-background border-b border-border z-50">
|
|
<div className="max-w-[1720px] mx-auto px-5 sm:px-6 lg:px-8 xl:px-10">
|
|
<div className="flex items-center h-16 gap-6">
|
|
<Link href="/" className="flex shrink-0 items-center gap-3 pr-2">
|
|
<Image
|
|
src="/boardware-logo.jpg"
|
|
alt="Boardware logo"
|
|
width={40}
|
|
height={32}
|
|
className="h-8 w-10 shrink-0 rounded-sm bg-white object-contain p-0.5"
|
|
/>
|
|
<span className="whitespace-nowrap text-[1.05rem] font-semibold leading-none tracking-tight sm:text-[1.15rem]">
|
|
Boardware Agent Sandbox
|
|
</span>
|
|
</Link>
|
|
|
|
<div className="flex min-w-0 flex-1 items-center justify-end gap-3">
|
|
<nav className="flex min-w-0 flex-1 items-center gap-1 overflow-x-auto whitespace-nowrap [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
|
|
{NAV_ITEMS.map((item) => {
|
|
const isActive =
|
|
item.href === '/'
|
|
? pathname === '/'
|
|
: item.matchPrefixes?.some((prefix) => pathname.startsWith(prefix)) ?? pathname.startsWith(item.href);
|
|
const Icon = item.icon;
|
|
return (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
className={`flex shrink-0 items-center gap-1.5 rounded-md px-3 py-2 text-sm font-medium transition-colors ${
|
|
isActive
|
|
? 'bg-primary text-primary-foreground'
|
|
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
|
|
}`}
|
|
>
|
|
<Icon className="w-4 h-4" />
|
|
{navLabel(item.key)}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
<div className="flex shrink-0 items-center gap-2 border-l border-border pl-4">
|
|
<LanguageSwitcher />
|
|
{user ? (
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<button
|
|
type="button"
|
|
className="flex items-center gap-2 rounded-full border border-border/70 bg-background px-2 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
|
|
>
|
|
<Avatar className="h-8 w-8 border border-border/60">
|
|
<AvatarFallback className="bg-primary text-xs font-semibold text-primary-foreground">
|
|
{userInitial}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<span className="hidden max-w-28 truncate sm:block">{user.username}</span>
|
|
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
|
</button>
|
|
</PopoverTrigger>
|
|
<PopoverContent align="end" className="w-80 rounded-3xl border-border/70 p-0 shadow-2xl">
|
|
<div className="overflow-hidden rounded-3xl bg-gradient-to-b from-slate-50 via-slate-50 to-white">
|
|
<div className="border-b border-border/60 px-6 py-5">
|
|
<p className="truncate text-center text-sm font-medium text-muted-foreground">
|
|
{user.email}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex flex-col items-center gap-4 px-6 py-6 text-center">
|
|
<Avatar className="h-24 w-24 border-4 border-white shadow-sm">
|
|
<AvatarFallback className="bg-primary text-4xl font-semibold text-primary-foreground">
|
|
{userInitial}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div className="space-y-1">
|
|
<p className="text-2xl font-semibold tracking-tight text-foreground">
|
|
{pickAppText(locale, `${user.username},你好!`, `Hi, ${user.username}`)}
|
|
</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
{pickAppText(locale, '当前已登录到你的工作区实例。', 'You are currently signed in to your workspace instance.')}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="border-t border-border/60 bg-white/90 px-4 py-4">
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={handleLogout}
|
|
className="h-12 w-full justify-center rounded-2xl text-sm font-semibold"
|
|
>
|
|
<LogOut className="mr-2 h-4 w-4" />
|
|
{pickAppText(locale, '退出登录', 'Sign Out')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
) : !isAuthLoading ? (
|
|
AUTH_ITEMS.map((item) => {
|
|
const isActive = pathname.startsWith(item.href);
|
|
const Icon = item.icon;
|
|
return (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
className={`flex items-center gap-1.5 rounded-md px-3 py-2 text-sm font-medium transition-colors ${
|
|
isActive
|
|
? 'bg-primary text-primary-foreground'
|
|
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
|
|
}`}
|
|
>
|
|
<Icon className="w-4 h-4" />
|
|
{authLabel(item.key)}
|
|
</Link>
|
|
);
|
|
})
|
|
) : null}
|
|
</div>
|
|
|
|
<div className="shrink-0 border-l border-border pl-4">
|
|
<ConnectionDot />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
};
|
|
|
|
export default Header;
|