feat: deploy Codeg multi-agent workspace, Gitea hot/cold storage hub, and grill-me skill
This commit is contained in:
1 parent
c4f54203a4
commit
01a4681f98
18 files changed
+1238
-252
No files matched your search
@@ -0,0 +1,531 @@
|
|||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Archive,
|
||||||
|
Bot,
|
||||||
|
Copy,
|
||||||
|
ExternalLink,
|
||||||
|
FolderGit2,
|
||||||
|
GitBranch,
|
||||||
|
HardDrive,
|
||||||
|
Layers,
|
||||||
|
MessageSquare,
|
||||||
|
RefreshCw,
|
||||||
|
Sparkles,
|
||||||
|
Terminal,
|
||||||
|
Users,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { usePortalStore } from '../store/usePortalStore';
|
||||||
|
import { Badge } from './ui/badge';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
|
||||||
|
import { Tabs, TabsList, TabsTrigger } from './ui/tabs';
|
||||||
|
|
||||||
|
interface AssistantItem {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
agent_type: string;
|
||||||
|
instructions: string;
|
||||||
|
avatar?: { kind: string; color?: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RepoItem {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
default_branch: string;
|
||||||
|
updated_at: string;
|
||||||
|
size_kb: number;
|
||||||
|
html_url: string;
|
||||||
|
clone_url: string;
|
||||||
|
ssh_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ColdBundleItem {
|
||||||
|
name: string;
|
||||||
|
size_mb: number;
|
||||||
|
mtime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OverviewData {
|
||||||
|
codeg: {
|
||||||
|
online: boolean;
|
||||||
|
latency_ms: number;
|
||||||
|
version: string;
|
||||||
|
assistants: AssistantItem[];
|
||||||
|
folders: Array<{ id: number; name: string; path: string }>;
|
||||||
|
conversations: Array<{ id: number; title: string; agent_type: string; status: string }>;
|
||||||
|
};
|
||||||
|
gitea: {
|
||||||
|
online: boolean;
|
||||||
|
latency_ms: number;
|
||||||
|
version: string;
|
||||||
|
hot_path: string;
|
||||||
|
cold_path: string;
|
||||||
|
repos: RepoItem[];
|
||||||
|
cold_bundles: ColdBundleItem[];
|
||||||
|
};
|
||||||
|
tmux_windows: Array<{ index: string; name: string; active: boolean; command: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const COLOR_MAP: Record<string, string> = {
|
||||||
|
blue: '#3b82f6',
|
||||||
|
violet: '#8b5cf6',
|
||||||
|
teal: '#14b8a6',
|
||||||
|
amber: '#f59e0b',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AgentOrchestratorWorkbench: React.FC = () => {
|
||||||
|
const { getEffectiveMode, routeMode, copyCmd, showToast, setShowAgyModal, setShowWechatModal } =
|
||||||
|
usePortalStore();
|
||||||
|
const [subTab, setSubTab] = useState<'native' | 'embedded'>('native');
|
||||||
|
const [data, setData] = useState<OverviewData | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [syncingCold, setSyncingCold] = useState(false);
|
||||||
|
|
||||||
|
const effectiveMode = useMemo(() => getEffectiveMode(), [getEffectiveMode, routeMode]);
|
||||||
|
|
||||||
|
const codegUrl = useMemo(() => {
|
||||||
|
if (effectiveMode === 'lan') return 'http://192.168.31.164:3080';
|
||||||
|
if (effectiveMode === 'home10443') return 'https://codeg.home.961504.xyz:10443';
|
||||||
|
return 'https://codeg.961504.xyz';
|
||||||
|
}, [effectiveMode]);
|
||||||
|
|
||||||
|
const giteaUrl = useMemo(() => {
|
||||||
|
if (effectiveMode === 'lan') return 'http://192.168.31.164:3005';
|
||||||
|
if (effectiveMode === 'home10443') return 'https://git.home.961504.xyz:10443';
|
||||||
|
return 'https://git.961504.xyz';
|
||||||
|
}, [effectiveMode]);
|
||||||
|
|
||||||
|
const fetchOverview = useCallback(async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/agents/overview');
|
||||||
|
if (res.ok) {
|
||||||
|
const json = await res.json();
|
||||||
|
setData(json);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore transient network errors
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchOverview();
|
||||||
|
const timer = setInterval(fetchOverview, 6000);
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, [fetchOverview]);
|
||||||
|
|
||||||
|
const handleSyncCold = async () => {
|
||||||
|
setSyncingCold(true);
|
||||||
|
showToast('正在将 Gitea 热层仓库与 LFS 大文件归档至极空间 20TB NAS...');
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/action/sync_git_nas', { method: 'POST' });
|
||||||
|
const d = await res.json();
|
||||||
|
showToast(d.message || '极空间 Git 冷备同步完成');
|
||||||
|
await fetchOverview();
|
||||||
|
} catch {
|
||||||
|
showToast('冷备同步请求失败');
|
||||||
|
} finally {
|
||||||
|
setSyncingCold(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const assistants = data?.codeg.assistants || [];
|
||||||
|
const repos = data?.gitea.repos || [];
|
||||||
|
const bundles = data?.gitea.cold_bundles || [];
|
||||||
|
const tmuxWindows = data?.tmux_windows || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="flex-1 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-6 space-y-6">
|
||||||
|
{/* Top Banner: Architecture Philosophy & Quick Switcher */}
|
||||||
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 pb-2 border-b hl-border">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex items-center gap-2.5">
|
||||||
|
<h1 className="text-lg font-semibold tracking-tight flex items-center gap-2">
|
||||||
|
<Bot className="w-5 h-5" style={{ color: 'var(--accent)' }} />
|
||||||
|
<span>Multi-Agent 协同编排与私有 Git 中枢</span>
|
||||||
|
</h1>
|
||||||
|
<Badge variant="success">Codeg v0.42 + Gitea v28.3</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
以开源 Codeg (ACP 协议) 为底座、Gitea (Mac SSD 热层 + 极空间 20TB 冷层) 为代码仓库,支持单聊分工角色与{' '}
|
||||||
|
<code className="font-mono-num px-1 py-0.5 rounded hl-muted">@点名</code> 跨智能体群聊协作。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
<Tabs value={subTab} onValueChange={(v) => setSubTab(v as 'native' | 'embedded')}>
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="native">
|
||||||
|
<Layers className="w-3.5 h-3.5" />
|
||||||
|
<span>自研编排 & Git 大盘</span>
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="embedded">
|
||||||
|
<Sparkles className="w-3.5 h-3.5" />
|
||||||
|
<span>内嵌 Codeg 完整画布</span>
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => window.open(codegUrl, '_blank', 'noopener,noreferrer')}
|
||||||
|
>
|
||||||
|
<ExternalLink className="w-3.5 h-3.5" />
|
||||||
|
<span>全屏打开 Codeg</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => window.open(giteaUrl, '_blank', 'noopener,noreferrer')}
|
||||||
|
>
|
||||||
|
<FolderGit2 className="w-3.5 h-3.5" />
|
||||||
|
<span>打开 Gitea</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{subTab === 'embedded' ? (
|
||||||
|
<Card className="overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="px-4 py-2.5 border-b hl-border flex items-center justify-between text-xs"
|
||||||
|
style={{ backgroundColor: 'var(--bg-muted)' }}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="w-2 h-2 rounded-full" style={{ backgroundColor: 'var(--success)' }} />
|
||||||
|
<span className="font-medium">Codeg 原生多智能体工作台 (已注入 SSO 免密凭证)</span>
|
||||||
|
<span className="font-mono-num" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
{codegUrl}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => window.open(codegUrl, '_blank', 'noopener,noreferrer')}
|
||||||
|
>
|
||||||
|
<ExternalLink className="w-3.5 h-3.5" />
|
||||||
|
<span>新标签页全屏</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<iframe
|
||||||
|
src={codegUrl}
|
||||||
|
title="Codeg Multi-Agent Workspace"
|
||||||
|
className="w-full border-0"
|
||||||
|
style={{ height: 'calc(100vh - 220px)', minHeight: '640px' }}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Section 1: 4 Specialist Role Assistants (Single Chat + @Mention Squad) */}
|
||||||
|
<section className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Users className="w-4 h-4" style={{ color: 'var(--accent)' }} />
|
||||||
|
<h2 className="text-xs font-semibold uppercase tracking-wider" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
分工角色会话与 @群聊战队 · Specialist Role Squad (`Google Antigravity` 底座)
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button variant="ghost" size="sm" onClick={fetchOverview} disabled={loading}>
|
||||||
|
<RefreshCw className={`w-3.5 h-3.5 ${loading ? 'animate-spin' : ''}`} />
|
||||||
|
<span>刷新状态</span>
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" size="sm" onClick={() => setShowWechatModal(true)}>
|
||||||
|
<MessageSquare className="w-3.5 h-3.5" />
|
||||||
|
<span>微信入口绑定</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
{assistants.map((a) => {
|
||||||
|
const badgeColor = COLOR_MAP[a.avatar?.color || 'blue'] || '#3b82f6';
|
||||||
|
return (
|
||||||
|
<Card key={a.id} className="p-4 flex flex-col justify-between gap-3">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
<div className="flex items-center gap-2.5">
|
||||||
|
<div
|
||||||
|
className="w-8 h-8 rounded-lg flex items-center justify-center text-white font-semibold text-sm shrink-0"
|
||||||
|
style={{ backgroundColor: badgeColor }}
|
||||||
|
>
|
||||||
|
{a.name.slice(0, 1)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-sm flex items-center gap-1.5">
|
||||||
|
<span>@{a.name}</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-[11px] font-mono-num" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
CLI: {a.agent_type === 'antigravity' ? 'Google Antigravity (agy)' : a.agent_type}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Badge variant="success">就绪</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-xs leading-relaxed" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
{a.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="p-2 rounded-md text-[11px] font-mono-num line-clamp-2 hl-muted"
|
||||||
|
style={{ color: 'var(--text-subtle)' }}
|
||||||
|
title={a.instructions}
|
||||||
|
>
|
||||||
|
Prompt: {a.instructions}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-2 border-t hl-border flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
size="sm"
|
||||||
|
className="flex-1"
|
||||||
|
onClick={() => setSubTab('embedded')}
|
||||||
|
>
|
||||||
|
<span>发起单聊</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => copyCmd(`@${a.name} `, `群聊点名 @${a.name}`)}
|
||||||
|
title="复制 @点名 指令,可在主会话或群聊中直接委派该角色"
|
||||||
|
>
|
||||||
|
<Copy className="w-3.5 h-3.5" />
|
||||||
|
<span>@点名</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 2: Self-Hosted Gitea (Mac SSD Hot Tier + ZSpace 20TB Cold Tier) & Active AGY Sessions */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-5">
|
||||||
|
{/* Left 2 Columns: Gitea Hot/Cold Tier Architecture */}
|
||||||
|
<Card className="lg:col-span-2 p-5 space-y-4">
|
||||||
|
<CardHeader className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 pb-3 border-b hl-border">
|
||||||
|
<div>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<FolderGit2 className="w-4 h-4" style={{ color: 'var(--accent)' }} />
|
||||||
|
<span>私有 Git 代码中枢 (Gitea 热冷分层存储架构)</span>
|
||||||
|
<Badge variant={data?.gitea.online ? 'success' : 'warning'}>
|
||||||
|
{data?.gitea.online ? `在线 · ${data.gitea.latency_ms}ms` : '检测中'}
|
||||||
|
</Badge>
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription className="mt-1">
|
||||||
|
服务端与活跃索引常驻 M6 Mac mini NVMe SSD,LFS 大文件与全量 <code className="font-mono-num">.bundle</code> 镜像实时归档至极空间 Z4 20TB RAID。
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleSyncCold}
|
||||||
|
disabled={syncingCold}
|
||||||
|
>
|
||||||
|
<Archive className={`w-3.5 h-3.5 ${syncingCold ? 'animate-spin' : ''}`} />
|
||||||
|
<span>{syncingCold ? '归档中...' : '立即同步至极空间冷存'}</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => window.open(giteaUrl, '_blank', 'noopener,noreferrer')}
|
||||||
|
>
|
||||||
|
<ExternalLink className="w-3.5 h-3.5" />
|
||||||
|
<span>Gitea 控制台</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
{/* Hot vs Cold Tier Storage Boxes */}
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
|
<div className="p-3 rounded-lg border hl-border hl-muted space-y-1">
|
||||||
|
<div className="flex items-center justify-between text-xs">
|
||||||
|
<span className="font-semibold flex items-center gap-1.5">
|
||||||
|
🔥 Mac mini 热存储层 (NVMe SSD)
|
||||||
|
</span>
|
||||||
|
<Badge variant="default">毫秒级读写</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="text-[11px] font-mono-num truncate" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
{data?.gitea.hot_path || '/Users/ixvii0iv/homelab/stacks/git/data'}
|
||||||
|
</div>
|
||||||
|
<div className="text-[11px]" style={{ color: 'var(--text-subtle)' }}>
|
||||||
|
承载 SQLite 元数据、活跃 Git 裸仓库与 Agent Worktree 并发合并
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3 rounded-lg border hl-border hl-muted space-y-1">
|
||||||
|
<div className="flex items-center justify-between text-xs">
|
||||||
|
<span className="font-semibold flex items-center gap-1.5">
|
||||||
|
<HardDrive className="w-3.5 h-3.5" style={{ color: 'var(--success)' }} />
|
||||||
|
🧊 极空间 Z4 冷存储层 (20TB RAID)
|
||||||
|
</span>
|
||||||
|
<Badge variant="success">{bundles.length} 个全量冷备包</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="text-[11px] font-mono-num truncate" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
{data?.gitea.cold_path || '/Users/ixvii0iv/nas/个人空间/Git_Cold_Storage'}
|
||||||
|
</div>
|
||||||
|
<div className="text-[11px]" style={{ color: 'var(--text-subtle)' }}>
|
||||||
|
承载 Git LFS 大文件、Release 制品包与自包含 .bundle 离线灾备包
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Hosted Repositories List */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="text-xs font-medium" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
当前托管仓库 ({repos.length})
|
||||||
|
</div>
|
||||||
|
{repos.map((r) => (
|
||||||
|
<div
|
||||||
|
key={r.name}
|
||||||
|
className="p-3 rounded-lg border hl-border flex flex-col sm:flex-row sm:items-center justify-between gap-3"
|
||||||
|
>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<GitBranch className="w-3.5 h-3.5" style={{ color: 'var(--accent)' }} />
|
||||||
|
<span className="font-semibold text-sm font-mono-num">{r.name}</span>
|
||||||
|
<Badge variant="default">{r.default_branch}</Badge>
|
||||||
|
{bundles.some((b) => b.name.includes(r.name.replace('/', '_'))) && (
|
||||||
|
<Badge variant="success">已冷备至 NAS</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="text-xs" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
{r.description || 'HomeLab 核心基础设施与前端工程仓库'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() =>
|
||||||
|
copyCmd(`git clone ${giteaUrl}/${r.name}.git`, `克隆 ${r.name}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Copy className="w-3.5 h-3.5" />
|
||||||
|
<span>复制 Clone</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() =>
|
||||||
|
window.open(`${giteaUrl}/${r.name}`, '_blank', 'noopener,noreferrer')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ExternalLink className="w-3.5 h-3.5" />
|
||||||
|
<span>查看代码</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Right Column: Active AGY Tmux & Workspace Sessions */}
|
||||||
|
<Card className="p-5 flex flex-col justify-between space-y-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<CardHeader className="pb-3 border-b hl-border">
|
||||||
|
<CardTitle className="flex items-center justify-between">
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<Terminal className="w-4 h-4" style={{ color: 'var(--accent)' }} />
|
||||||
|
<span>AGY 原生常驻会话流</span>
|
||||||
|
</span>
|
||||||
|
<Badge variant="success">{tmuxWindows.length || 1} 窗口活跃</Badge>
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription className="mt-1">
|
||||||
|
M6 Mac mini 本机 <code className="font-mono-num">tmux (agy)</code> 与 Codeg 工作区实时状态。
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<div className="space-y-2.5">
|
||||||
|
<div className="text-xs font-medium" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
已挂载 Codeg 工作区目录
|
||||||
|
</div>
|
||||||
|
{(data?.codeg.folders || []).map((f) => (
|
||||||
|
<div
|
||||||
|
key={f.id}
|
||||||
|
className="p-2.5 rounded-lg border hl-border hl-muted flex items-center justify-between text-xs font-mono-num"
|
||||||
|
>
|
||||||
|
<span className="truncate">{f.path}</span>
|
||||||
|
<Badge variant="success">已绑定</Badge>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div className="text-xs font-medium pt-2" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
Mac mini Tmux 会话窗格
|
||||||
|
</div>
|
||||||
|
{tmuxWindows.length > 0 ? (
|
||||||
|
tmuxWindows.map((w) => (
|
||||||
|
<div
|
||||||
|
key={w.index}
|
||||||
|
className="p-2.5 rounded-lg border hl-border flex items-center justify-between text-xs"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 font-mono-num">
|
||||||
|
<span
|
||||||
|
className="w-2 h-2 rounded-full"
|
||||||
|
style={{
|
||||||
|
backgroundColor: w.active ? 'var(--success)' : 'var(--text-muted)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="font-medium">
|
||||||
|
#{w.index} {w.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Badge variant="default">{w.command}</Badge>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="p-2.5 rounded-lg border hl-border text-xs font-mono-num">
|
||||||
|
#0 agy (Google Antigravity 常驻守护)
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{bundles.length > 0 && (
|
||||||
|
<div className="pt-2 space-y-1.5">
|
||||||
|
<div className="text-xs font-medium" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
最近极空间 NAS 冷备快照
|
||||||
|
</div>
|
||||||
|
{bundles.map((b) => (
|
||||||
|
<div
|
||||||
|
key={b.name}
|
||||||
|
className="p-2 rounded-md hl-muted flex items-center justify-between text-[11px] font-mono-num"
|
||||||
|
>
|
||||||
|
<span className="truncate">{b.name}</span>
|
||||||
|
<span style={{ color: 'var(--success)' }}>
|
||||||
|
{b.size_mb} MB · {b.mtime.slice(5, 16)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-3 border-t hl-border flex items-center gap-2">
|
||||||
|
<Button className="flex-1" onClick={() => setShowAgyModal(true)}>
|
||||||
|
<Terminal className="w-3.5 h-3.5" />
|
||||||
|
<span>唤起 AGY 终端</span>
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" onClick={() => setSubTab('embedded')}>
|
||||||
|
<Sparkles className="w-3.5 h-3.5" />
|
||||||
|
<span>进入画布</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BookOpen, LayoutGrid, MessageSquare, Moon, Search, Sun, Terminal } from 'lucide-react';
|
import { BookOpen, Bot, LayoutGrid, MessageSquare, Moon, Search, Sun, Terminal } from 'lucide-react';
|
||||||
import { usePortalStore } from '../store/usePortalStore';
|
import { usePortalStore } from '../store/usePortalStore';
|
||||||
import type { RouteModeId } from '../types/telemetry';
|
import type { RouteModeId } from '../types/telemetry';
|
||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
@@ -64,13 +64,17 @@ export const TopRibbon: React.FC = () => {
|
|||||||
{/* View Switcher Tabs via @radix-ui/react-tabs */}
|
{/* View Switcher Tabs via @radix-ui/react-tabs */}
|
||||||
<Tabs
|
<Tabs
|
||||||
value={viewMode}
|
value={viewMode}
|
||||||
onValueChange={(v) => setViewMode(v as 'dashboard' | 'storybook')}
|
onValueChange={(v) => setViewMode(v as 'dashboard' | 'agents' | 'storybook')}
|
||||||
>
|
>
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="dashboard">
|
<TabsTrigger value="dashboard">
|
||||||
<LayoutGrid className="w-3.5 h-3.5" />
|
<LayoutGrid className="w-3.5 h-3.5" />
|
||||||
<span>中控台</span>
|
<span>中控台</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="agents">
|
||||||
|
<Bot className="w-3.5 h-3.5" />
|
||||||
|
<span>Agent 编排</span>
|
||||||
|
</TabsTrigger>
|
||||||
<TabsTrigger value="storybook">
|
<TabsTrigger value="storybook">
|
||||||
<BookOpen className="w-3.5 h-3.5" />
|
<BookOpen className="w-3.5 h-3.5" />
|
||||||
<span>Storybook 设计规范</span>
|
<span>Storybook 设计规范</span>
|
||||||
|
|||||||
Reference in new issue
Block a user