feat: MetonaAI Desktop 初始项目

- Electron + React + TypeScript 架构
- 三栏布局: Sidebar | ChatPanel | DetailPanel
- 9 个内置工具 (文件系统/网络/记忆/命令)
- SQLite 持久化 (better-sqlite3)
- MUI 暗色/亮色主题系统
- Agent Loop ReAct 状态机引擎
- DeepSeek / Agnes AI / Ollama Provider 适配器
- MCP 协议集成
- 系统托盘 + 全局快捷键
- Tailwind CSS v4 + Tailwind Merge
- 修复: Sidebar 缺失 TextField 导入导致黑屏
This commit is contained in:
thzxx
2026-06-27 21:33:27 +08:00
commit 1d185db6b3
109 changed files with 39155 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
/**
* TokenUsage — Token 统计图表
*
* 进度条可视化 + 数值显示。
*/
import { Box, Typography, Stack, LinearProgress } from '@mui/material';
import { Zap } from 'lucide-react';
import { useAgentStore } from '@renderer/stores/agent-store';
import { formatTokens } from '@renderer/lib/formatters';
import { cn } from '@renderer/lib/cn';
export function TokenUsage(): React.JSX.Element {
const tokenUsage = useAgentStore((s) => s.tokenUsage);
const maxIterations = useAgentStore((s) => s.maxIterations);
const currentIteration = useAgentStore((s) => s.currentIteration);
if (tokenUsage.totalTokens === 0) {
return (
<Box sx={{ mt: 2, pt: 2, borderTop: 1, borderColor: 'divider' }}>
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1.5 }}>
<Zap size={14} style={{ color: '#fbbf24' }} />
<Typography variant="caption" sx={{ fontWeight: 600, textTransform: 'uppercase', letterSpacing: 1, color: 'text.secondary' }}>Token </Typography>
</Stack>
<Typography variant="caption" sx={{ textAlign: 'center', py: 1, display: 'block', color: 'text.disabled' }}> Agent ...</Typography>
</Box>
);
}
const maxTokens = 128_000;
const usagePercent = Math.min((tokenUsage.totalTokens / maxTokens) * 100, 100);
const inputPercent = tokenUsage.totalTokens > 0 ? (tokenUsage.inputTokens / tokenUsage.totalTokens) * 100 : 0;
return (
<Box sx={{ mt: 2, pt: 2, borderTop: 1, borderColor: 'divider' }}>
{/* 标题 */}
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1.5 }}>
<Zap size={14} style={{ color: '#fbbf24' }} />
<Typography variant="caption" sx={{ fontWeight: 600, textTransform: 'uppercase', letterSpacing: 1, color: 'text.secondary' }}>Token </Typography>
</Stack>
{/* 进度条 */}
<Box sx={{ width: '100%', height: 6, borderRadius: 3, overflow: 'hidden', mb: 2, bgcolor: 'action.hover', display: 'flex' }}>
<Box sx={{ height: '100%', width: `${inputPercent}%`, bgcolor: '#22d3ee', transition: 'width 300ms', borderRadius: '3px 0 0 3px' }} />
<Box sx={{ height: '100%', width: `${100 - inputPercent}%`, bgcolor: '#a855f7', transition: 'width 300ms', borderRadius: '0 3px 3px 0' }} />
</Box>
{/* 数值网格 */}
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1.5, mb: 1.5 }}>
<StatCard label="输入" value={formatTokens(tokenUsage.inputTokens)} color="#22d3ee" />
<StatCard label="输出" value={formatTokens(tokenUsage.outputTokens)} color="#a855f7" />
</Box>
{/* 底部汇总 */}
<Stack spacing={0.5}>
<SummaryRow label="总计" value={formatTokens(tokenUsage.totalTokens)} highlight />
<SummaryRow label="上下文" value={`${usagePercent.toFixed(1)}%`}
color={usagePercent > 80 ? 'error.main' : usagePercent > 60 ? 'warning.main' : 'text.secondary'} />
<SummaryRow label="迭代" value={`${currentIteration} / ${maxIterations}`} />
</Stack>
</Box>
);
}
function StatCard({ label, value, color }: { label: string; value: string; color: string }) {
return (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, px: 1.5, py: 1, borderRadius: 1.5, bgcolor: 'action.hover' }}>
<Box sx={{ width: 8, height: 8, borderRadius: '50%', bgcolor: color, flexShrink: 0 }} />
<Typography variant="caption" sx={{ color: 'text.secondary', fontSize: 11 }}>{label}</Typography>
<Typography variant="caption" sx={{ ml: 'auto', fontFamily: 'monospace', fontWeight: 600, color: 'text.primary', fontSize: 12 }}>{value}</Typography>
</Box>
);
}
function SummaryRow({ label, value, highlight, color }: { label: string; value: string; highlight?: boolean; color?: string }) {
return (
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ py: 0.25 }}>
<Typography variant="caption" sx={{ color: 'text.secondary', fontSize: 11 }}>{label}</Typography>
<Typography variant="caption" sx={{ fontFamily: 'monospace', fontWeight: highlight ? 600 : 400, color: color ?? (highlight ? 'text.primary' : 'text.secondary'), fontSize: highlight ? 12 : 11 }}>{value}</Typography>
</Stack>
);
}