feat: TypeScript + Electron v2 重构 - 纯桌面版

- 全面迁移到 TypeScript,严格类型定义
- 放弃 Web 版,专注 Electron 桌面应用
- 主进程模块化:main.ts, preload.ts, menu.ts, tray.ts, ipc.ts, utils.ts
- 渲染进程完整迁移所有功能组件
- 删除 PWA 相关文件 (sw.js, manifest.json)
- 删除 Web 版降级逻辑
- 保留所有核心功能:流式对话、多模型、Think推理、多模态、RAG知识库、Agent预设、历史管理
- 保留 Windows 11 Fluent Design 暗色主题样式
This commit is contained in:
thzxx
2026-04-06 03:04:20 +08:00
parent 0924497cd6
commit 7ca0e33d77
33 changed files with 7265 additions and 15 deletions
+115
View File
@@ -0,0 +1,115 @@
/**
* Utils - 通用工具函数
*/
/** 生成唯一 ID */
export function generateId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
/** 格式化时间戳 */
export function formatTime(ts: number): string {
const d = new Date(ts);
const pad = (n: number): string => String(n).padStart(2, '0');
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
/** 截断文本 */
export function truncate(str: string, len = 50): string {
if (!str) return '';
return str.length > len ? str.substring(0, len) + '...' : str;
}
/** 防抖 */
export function debounce<T extends (...args: unknown[]) => unknown>(fn: T, ms = 300): (...args: Parameters<T>) => void {
let timer: ReturnType<typeof setTimeout>;
return (...args: Parameters<T>) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), ms);
};
}
/** 格式化字节大小 */
export function formatSize(bytes: number): string {
if (!bytes) return '';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let i = 0;
let size = bytes;
while (size >= 1024 && i < units.length - 1) {
size /= 1024;
i++;
}
return `${size.toFixed(1)} ${units[i]}`;
}
/** HTML 转义 */
export function escapeHtml(str: string): string {
const map: Record<string, string> = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
return String(str).replace(/[&<>"']/g, (c) => map[c]);
}
/** 下载文件 */
export function downloadFile(filename: string, content: string, type: string): void {
const blob = new Blob([content], { type: type + ';charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
/** File → Base64(去前缀) */
export function fileToBase64(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const result = reader.result as string;
const base64 = result.split(',')[1];
resolve(base64);
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
/** File → 文本内容 */
export function fileToText(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = reject;
reader.readAsText(file);
});
}
/** 根据文件名检测语言标识(用于 Markdown 代码块) */
export function detectLanguage(filename: string): string {
const ext = filename.split('.').pop()?.toLowerCase() || '';
const map: Record<string, string> = {
py: 'python', pyw: 'python', pyi: 'python',
js: 'javascript', mjs: 'javascript', cjs: 'javascript',
ts: 'typescript', tsx: 'tsx', jsx: 'jsx',
java: 'java', class: 'java',
c: 'c', h: 'c',
cpp: 'cpp', cc: 'cpp', cxx: 'cpp', hpp: 'cpp',
go: 'go', rs: 'rust', rb: 'ruby', php: 'php',
sh: 'bash', bash: 'bash', zsh: 'bash', fish: 'bash',
sql: 'sql', html: 'html', htm: 'html', css: 'css',
json: 'json', jsonl: 'json',
xml: 'xml', svg: 'svg',
yaml: 'yaml', yml: 'yaml', toml: 'toml',
ini: 'ini', cfg: 'ini', conf: 'ini',
md: 'markdown', markdown: 'markdown', rst: 'rst',
txt: '', csv: 'csv', tsv: 'csv', log: '',
swift: 'swift', kt: 'kotlin', kts: 'kotlin',
scala: 'scala', lua: 'lua', pl: 'perl', r: 'r',
m: 'matlab', mm: 'objectivec',
cs: 'csharp', fs: 'fsharp', vb: 'vbnet',
ex: 'elixir', exs: 'elixir', erl: 'erlang',
hs: 'haskell', clj: 'clojure', lisp: 'lisp',
dockerfile: 'dockerfile', makefile: 'makefile',
diff: 'diff', patch: 'diff',
};
return map[ext] ?? ext;
}