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
+58
View File
@@ -0,0 +1,58 @@
/**
* 格式化工具函数
*
* 日期格式化使用 date-fns(成熟第三方库,禁止自写)。
* Token 数、文件大小等简单格式化为 < 20 行纯函数,允许自写。
*
* @see standard/开发规范.md — 第一铁律
*/
import { formatDistanceToNow, format } from 'date-fns';
import { zhCN } from 'date-fns/locale';
// ===== 日期格式化(使用 date-fns=====
/** 相对时间(如 "3 分钟前"、"昨天" */
export function formatRelativeTime(timestamp: number): string {
return formatDistanceToNow(new Date(timestamp), { addSuffix: true, locale: zhCN });
}
/** 短时间格式(如 "14:30" */
export function formatTime(timestamp: number): string {
return format(new Date(timestamp), 'HH:mm');
}
// ===== Token 格式化(< 20 行纯函数,允许自写)=====
export function formatTokens(count: number): string {
if (count < 1000) return String(count);
if (count < 10000) return `${(count / 1000).toFixed(1)}K`;
if (count < 1000000) return `${Math.round(count / 1000)}K`;
return `${(count / 1000000).toFixed(1)}M`;
}
// ===== 文件大小格式化(< 20 行纯函数,允许自写)=====
export function formatFileSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
// ===== 耗时格式化(< 20 行纯函数,允许自写)=====
export function formatDuration(ms: number): string {
if (ms < 1000) return `${ms}ms`;
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
const minutes = Math.floor(ms / 60000);
const seconds = Math.round((ms % 60000) / 1000);
return `${minutes}m${seconds}s`;
}
// ===== 截断文本(< 20 行纯函数,允许自写)=====
export function truncate(text: string, maxLength: number): string {
if (text.length <= maxLength) return text;
return text.slice(0, maxLength - 1) + '…';
}