// 左栏:logo + 项目列表(git 图标)+ WS 状态脚 · 可折叠(折叠后仅图标) // 项目 logo:图片优先(原型无图),回退首字母色块 hsl(h 45% 60%) function ProjectLogo({ project, size = 26 }) { return ( {(project.name || '?')[0].toUpperCase()} ); } function MaestroGitIcon({ size = 16 }) { return ( ); } // 像素章鱼 mark(折叠态 logo) function MaestroMark({ scale = 2.2 }) { const ref = React.useRef(null); React.useEffect(() => { const MAP = ['.....LLLLL.....','...LLLLLLLLL...','..GGGGGGGGGGG..','..GGEEGGGEEGG..','..GGEEGGGEEGG..','..GGGGGGGGGGG..','...GGGGGGGGG...','...G..G.G..G...','...G..G.G..G...','..G...G.G...G..','..D...D.D...D..','.D...D...D...D.']; const INK = { L: '#79ec94', G: '#5fdd7d', D: '#2e6b3d', E: '#070908' }; const ctx = ref.current.getContext('2d'); MAP.forEach((row, y) => [...row].forEach((ch, x) => { if (INK[ch]) { ctx.fillStyle = INK[ch]; ctx.fillRect(x, y, 1, 1); } })); }, []); return ; } // 项目状态:运行中(青脉冲) / 暂停(faint) / 阻塞(琥珀) / 空闲(muted) function projStateMeta(state, t) { return ({ running: { color: 'var(--cyan)', pulse: true, label: t.projRunning }, paused: { color: 'var(--faint)', pulse: false, label: t.projPaused }, blocked: { color: 'var(--amber)', pulse: true, label: t.projBlocked }, idle: { color: 'var(--muted)', pulse: false, label: t.projIdle }, })[state] || { color: 'var(--muted)', pulse: false, label: t.projIdle }; } function ProjStatusDot({ meta, size = 7 }) { return ( ); } // 侧栏底部小行:图标 + 主文 + 副文(折叠时仅图标,hover 高亮) function SideRow({ icon, label, detail, onClick, collapsed, title, accent }) { const interactive = !!onClick; return (
{ e.currentTarget.style.background = 'var(--panel)'; } : undefined} onMouseLeave={interactive ? (e) => { e.currentTarget.style.background = 'transparent'; } : undefined}> {icon} {!collapsed ? ( {label} {detail ? {detail} : null} ) : null} {!collapsed && interactive ? : null}
); } const sIco = { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', style: { flex: 'none' } }; function IcoAgents() { return ; } function IcoGlobalCfg() { return ; } function UserRow({ user, t, collapsed }) { const [open, setOpen] = React.useState(false); const ref = React.useRef(null); React.useEffect(() => { if (!open) return; const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', onDoc); return () => document.removeEventListener('mousedown', onDoc); }, [open]); const avatar = ( {user.initial} ); return (
setOpen(!open)} title={user.name + ' · ' + user.plan} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: collapsed ? '10px 0' : '10px 12px', justifyContent: collapsed ? 'center' : 'flex-start', cursor: 'pointer', transition: 'background .1s' }} onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--panel)'; }} onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}> {avatar} {!collapsed ? ( {user.name} {user.plan} ) : null}
{open ? (
{user.name} {user.handle}
{t.gUserPlan} · {user.plan}
{[t.gUserSettings, t.gUserSignOut].map((label, i) => (
{ e.currentTarget.style.background = 'var(--panel-2)'; }} onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}>{label}
))}
) : null}
); } function fmtTokens(n) { if (n >= 1e6) return (n / 1e6).toFixed(1).replace(/\.0$/, '') + 'M'; if (n >= 1e3) return Math.round(n / 1e3) + 'K'; return String(n); } function AgentsRow({ global, summary, t, collapsed }) { const [open, setOpen] = React.useState(false); const [pos, setPos] = React.useState(null); const ref = React.useRef(null); const rowRef = React.useRef(null); React.useEffect(() => { if (!open) return; const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target) && rowRef.current && !rowRef.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', onDoc); return () => document.removeEventListener('mousedown', onDoc); }, [open]); const toggle = () => { if (!open && rowRef.current) { const r = rowRef.current.getBoundingClientRect(); const W = 286; const left = collapsed ? r.right + 6 : Math.min(r.left, window.innerWidth - W - 8); setPos({ left, bottom: window.innerHeight - r.top + 6, width: W }); } setOpen((o) => !o); }; const maxTok = Math.max(...summary.byProject.map((p) => p.tokens), 1); const detail = t.gAgentsDetail.replace('{p}', global.runningProjects).replace('{P}', global.projectCount).replace('{a}', global.runningAgents); return (
} accent="var(--cyan)" onClick={toggle} label={t.gAgents} detail={detail} title={t.gAgents + ' · ' + detail} />
{open && pos ? (
{t.gAgents.toUpperCase()} {t.apWeek}
{/* 总结 */}
{[ [fmtTokens(summary.tokensWeek), t.apTokens, 'var(--cyan)'], ['$' + summary.costWeek.toFixed(1), t.apCost, 'var(--green)'], [summary.runsWeek, t.apRuns, 'var(--ink)'], [summary.activeNow + ' / ' + global.maxAgents, t.apActive, summary.activeNow ? 'var(--cyan)' : 'var(--faint)'], ].map(([v, k, c], i) => (
{v}
{k}
))}
{/* 按项目 */}
{t.apPerProj}
{summary.byProject.map((p) => (
{p.name[0].toUpperCase()} {p.name} {p.active > 0 ? ( {p.active} ) : {t.apIdle}} {fmtTokens(p.tokens)}
{p.runs} {t.apRuns}
))}
) : null}
); } function GlobalConfigRow({ global, t, collapsed }) { const { Select } = window.MaestroDesignSystem_a6a290; const [open, setOpen] = React.useState(false); const [pos, setPos] = React.useState(null); const ref = React.useRef(null); const rowRef = React.useRef(null); React.useEffect(() => { if (!open) return; const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target) && rowRef.current && !rowRef.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', onDoc); return () => document.removeEventListener('mousedown', onDoc); }, [open]); const toggle = () => { if (!open && rowRef.current) { const r = rowRef.current.getBoundingClientRect(); const W = 250; // 优先在行上方对齐左缘;折叠态贴侧栏右侧 const left = collapsed ? r.right + 6 : Math.min(r.left, window.innerWidth - W - 8); setPos({ left, bottom: window.innerHeight - r.top + 6, width: W }); } setOpen((o) => !o); }; const stat = (k, v, accent) => (
{k} {v}
); return (
} onClick={toggle} label={t.gConfig} detail={t.gConfigDetail.replace('{n}', global.maxAgents).replace('{v}', global.daemon)} title={t.gConfig} />
{open && pos ? (
{t.gConfig.toUpperCase()}
{stat('daemon', global.daemon, 'var(--green)')} {stat('port', ':' + global.port)} {stat('uptime', global.uptime)} {stat(t.gAgents, global.runningAgents + ' / ' + global.maxAgents, 'var(--cyan)')}
) : null}
); } function Sidebar({ projects, currentId, onSelect, onNewProject, collapsed, onToggleCollapse, t, global, user, summary, onGlobalConfig }) { const { Button } = window.MaestroDesignSystem_a6a290; return ( ); } window.MaestroKitSidebar = Sidebar;