feihong-code 7.0.0 → 7.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +180 -140
- package/.github/workflows/release.yml +200 -0
- package/CHANGELOG.md +94 -65
- package/README.md +609 -586
- package/dist/agent/browser-agent.js +354 -0
- package/dist/agent/browser-agent.js.map +1 -0
- package/dist/agent/change-manager.js +433 -0
- package/dist/agent/change-manager.js.map +1 -0
- package/dist/agent/code-rag.js +293 -0
- package/dist/agent/code-rag.js.map +1 -0
- package/dist/agent/code-writer.js +13 -1
- package/dist/agent/code-writer.js.map +1 -1
- package/dist/agent/completion-engine.js +553 -0
- package/dist/agent/completion-engine.js.map +1 -0
- package/dist/agent/completion-postprocess.js +109 -0
- package/dist/agent/completion-postprocess.js.map +1 -0
- package/dist/agent/context-budget.js +121 -0
- package/dist/agent/context-budget.js.map +1 -0
- package/dist/agent/context-compactor.js +235 -10
- package/dist/agent/context-compactor.js.map +1 -1
- package/dist/agent/custom-agent.js +368 -0
- package/dist/agent/custom-agent.js.map +1 -0
- package/dist/agent/design-to-code.js +218 -0
- package/dist/agent/design-to-code.js.map +1 -0
- package/dist/agent/event-driven-agent.js +359 -0
- package/dist/agent/event-driven-agent.js.map +1 -0
- package/dist/agent/git-integration.js +324 -0
- package/dist/agent/git-integration.js.map +1 -0
- package/dist/agent/layered-memory.js +329 -0
- package/dist/agent/layered-memory.js.map +1 -0
- package/dist/agent/lint.js +121 -0
- package/dist/agent/lint.js.map +1 -0
- package/dist/agent/multi-agent.js +359 -0
- package/dist/agent/multi-agent.js.map +1 -0
- package/dist/agent/orchestrator.js +136 -25
- package/dist/agent/orchestrator.js.map +1 -1
- package/dist/agent/prompts.js +54 -54
- package/dist/agent/quality-gate.js +139 -0
- package/dist/agent/quality-gate.js.map +1 -1
- package/dist/agent/self-correction.js +274 -0
- package/dist/agent/self-correction.js.map +1 -0
- package/dist/agent/self-heal.js +47 -17
- package/dist/agent/self-heal.js.map +1 -1
- package/dist/agent/solo-agent.js +321 -0
- package/dist/agent/solo-agent.js.map +1 -0
- package/dist/agent/symbol-index.js +258 -28
- package/dist/agent/symbol-index.js.map +1 -1
- package/dist/agent/team-collaboration.js +252 -0
- package/dist/agent/team-collaboration.js.map +1 -0
- package/dist/agent/type-checker.js +229 -0
- package/dist/agent/type-checker.js.map +1 -0
- package/dist/cli/commands.js +9 -0
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/index.js +9 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/multi-pane-tui.js +217 -0
- package/dist/cli/multi-pane-tui.js.map +1 -0
- package/dist/cli/run.js +130 -40
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/tui-run.js +79 -0
- package/dist/cli/tui-run.js.map +1 -0
- package/dist/cli/tui.js.map +1 -1
- package/dist/cli/version.js +1 -1
- package/dist/integrations/collaboration.js +219 -0
- package/dist/integrations/collaboration.js.map +1 -0
- package/dist/integrations/figma.js +307 -0
- package/dist/integrations/figma.js.map +1 -0
- package/dist/integrations/sso.js +327 -0
- package/dist/integrations/sso.js.map +1 -0
- package/dist/knowledge/library.js +301 -0
- package/dist/knowledge/library.js.map +1 -0
- package/dist/lsp/lsp-client.js +183 -0
- package/dist/lsp/lsp-client.js.map +1 -0
- package/dist/lsp/lsp-service.js +159 -0
- package/dist/lsp/lsp-service.js.map +1 -0
- package/dist/plugins/manager.js +380 -0
- package/dist/plugins/manager.js.map +1 -0
- package/dist/runtime/event-log.js.map +1 -1
- package/dist/shared/i18n.js +108 -100
- package/dist/shared/i18n.js.map +1 -1
- package/dist/tools/browser/browser.tool.js +217 -0
- package/dist/tools/browser/browser.tool.js.map +1 -0
- package/dist/tools/generator/code-generator.js +40 -40
- package/dist/tools/generator/test-generator.js +9 -9
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/shell/exec.js +73 -11
- package/dist/tools/shell/exec.js.map +1 -1
- package/dist/tools/shell/run-shell.tool.js +8 -2
- package/dist/tools/shell/run-shell.tool.js.map +1 -1
- package/dist/training/completion-evaluator.js +213 -0
- package/dist/training/completion-evaluator.js.map +1 -0
- package/dist/training/fim-data.js +248 -0
- package/dist/training/fim-data.js.map +1 -0
- package/dist/voice/voice-programming.js +223 -0
- package/dist/voice/voice-programming.js.map +1 -0
- package/dist/web/extra-apis.js +440 -0
- package/dist/web/extra-apis.js.map +1 -0
- package/dist/web/public/css/style.css +1729 -1671
- package/dist/web/public/index.html +1242 -990
- package/dist/web/public/js/api.js +555 -382
- package/dist/web/public/js/app.js +2210 -2210
- package/dist/web/public/js/mock-api.js +234 -0
- package/dist/web/public/js/monaco-editor.js +363 -0
- package/dist/web/public/js/ui.js +2251 -1059
- package/dist/web/public/js/utils.js +214 -214
- package/dist/web/public/manifest.json +59 -0
- package/dist/web/public/service-worker.js +173 -0
- package/dist/web/server.js +1116 -45
- package/dist/web/server.js.map +1 -1
- package/dist/web/task-queue.js +22 -2
- package/dist/web/task-queue.js.map +1 -1
- package/docs/BENCHMARK_REPORT_en.md +386 -329
- package/docs/BENCHMARK_REPORT_zh.md +386 -329
- package/docs/DPA.md +63 -0
- package/docs/DPA/346/225/260/346/215/256/345/244/204/347/220/206/345/215/217/350/256/256/346/250/241/346/235/277_v1.0.md +67 -0
- package/docs/SECURITY_WHITEPAPER.md +74 -0
- package/docs/SWE_BENCH_REPORT.md +55 -0
- package/docs/TECHNICAL_SPEC_en.md +292 -292
- package/docs/TECHNICAL_SPEC_zh.md +292 -292
- package/docs/UPDATE_NOTES_en.md +159 -159
- package/docs/UPDATE_NOTES_zh.md +159 -159
- package/docs/UPGRADE_7.2.0.md +309 -0
- package/docs/UPGRADE_GUIDE_7_5.md +86 -0
- package/docs/geo-feihong-code.html +391 -0
- package/docs//345/205/250/351/235/242/345/244/215/347/233/230_50/350/275/256/346/267/261/345/272/246/345/256/241/350/256/241_v7.2.0.md +208 -0
- package/docs//345/205/250/351/235/242/345/244/215/347/233/230_6/351/241/271/351/207/215/347/202/271/346/240/270/346/237/245_v7.2.0.md +152 -0
- package/docs//345/205/250/351/235/242/345/244/215/347/233/230_/350/277/220/350/241/214/346/227/266/345/256/236/346/265/213/347/211/210_v7.2.0.md +271 -0
- package/docs//345/220/210/350/247/204/350/256/244/350/257/201/345/207/206/345/244/207/346/270/205/345/215/225_v7.2.0.md +59 -0
- package/docs//345/256/211/345/205/250/347/231/275/347/232/256/344/271/246_v7.2.0.md +82 -0
- package/electron/main.js +404 -404
- package/package.json +175 -167
- package/templates/skills/api-design/SKILL.md +27 -0
- package/templates/skills/code-review/SKILL.md +35 -0
- package/templates/skills/dependency-upgrade/SKILL.md +29 -0
- package/templates/skills/doc-gen/SKILL.md +29 -0
- package/templates/skills/git-flow/SKILL.md +26 -0
- package/templates/skills/onboarding/SKILL.md +27 -0
- package/templates/skills/performance/SKILL.md +29 -0
- package/templates/skills/refactor/SKILL.md +28 -0
- package/templates/skills/security-audit/SKILL.md +28 -0
- package/templates/skills/test-gen/SKILL.md +30 -0
- package/tool-schema.json +197 -197
- package/dist/hello.js +0 -14
- package/dist/hello.js.map +0 -1
- package/dist/self-evolve/hook.ts +0 -80
- package/dist/self-evolve/manager.d.ts +0 -8
- package/dist/web/web-config.js +0 -143
- package/dist/web/web-config.js.map +0 -1
|
@@ -1,214 +1,214 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 工具函数模块:纯函数,无状态依赖,可被任意模块调用
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
function formatMarkdown(text) {
|
|
6
|
-
// 简单 Markdown 转换
|
|
7
|
-
return text
|
|
8
|
-
.replace(/&/g, '&')
|
|
9
|
-
.replace(/</g, '<')
|
|
10
|
-
.replace(/>/g, '>')
|
|
11
|
-
.replace(/^### (.*$)/gm, '<h3>$1</h3>')
|
|
12
|
-
.replace(/^## (.*$)/gm, '<h2>$1</h2>')
|
|
13
|
-
.replace(/^# (.*$)/gm, '<h1>$1</h1>')
|
|
14
|
-
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
15
|
-
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
16
|
-
.replace(/`(.*?)`/g, '<code>$1</code>')
|
|
17
|
-
.replace(/\n/g, '<br>');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function getDirectoryName(path) {
|
|
21
|
-
if (!path || path === '' || path === '/') return '/';
|
|
22
|
-
if (/^[A-Za-z]:$/.test(path)) return path + '\\';
|
|
23
|
-
if (/^[A-Za-z]:\\$/.test(path)) return path;
|
|
24
|
-
const parts = path.split(/[/\\]/).filter(Boolean);
|
|
25
|
-
if (parts.length <= 1) {
|
|
26
|
-
const driveMatch = parts[0].match(/^([A-Za-z]):$/);
|
|
27
|
-
if (driveMatch) return driveMatch[1] + ':\\';
|
|
28
|
-
return parts[0] || '/';
|
|
29
|
-
}
|
|
30
|
-
const parentParts = parts.slice(0, -1);
|
|
31
|
-
if (/^[A-Za-z]:$/.test(parentParts[0])) {
|
|
32
|
-
return parentParts[0] + '\\' + parentParts.slice(1).join('\\');
|
|
33
|
-
}
|
|
34
|
-
return parentParts.join('\\');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** 渲染空状态提示,减少重复的 innerHTML 赋值代码 */
|
|
38
|
-
function renderEmpty(el, text) {
|
|
39
|
-
if (el) el.innerHTML = '<div class="empty">' + (text || '') + '</div>';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function extractUrl(text) {
|
|
43
|
-
if (!text) return '';
|
|
44
|
-
const m = text.match(/(https?:\/\/[^\s]+)/);
|
|
45
|
-
return m ? m[1] : '';
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function linkifyArtifacts(text) {
|
|
49
|
-
let html = escapeHtml(text);
|
|
50
|
-
// 占位符保护:先提取已识别的片段,避免后续正则把已插入的 <span> 标签二次包裹
|
|
51
|
-
const tokens = [];
|
|
52
|
-
const protect = (s) => { const k = '\u0000TOK' + tokens.length + '\u0000'; tokens.push(s); return k; };
|
|
53
|
-
// 1) URL:排除结尾标点与 HTML 实体
|
|
54
|
-
html = html.replace(/(https?:\/\/[^\s<>"'()]+)/g, (m) => protect('<span class="artifact" data-kind="url" data-path="' + m + '">' + m + '</span>'));
|
|
55
|
-
// 2) Windows 绝对路径(支持正/反斜杠、中文、空格,排除结尾标点)
|
|
56
|
-
html = html.replace(/([A-Za-z]:[\\\/][^\s<>"'()]+?)(?=[\s<>"',。;:、))]|$)/g, (m) => protect('<span class="artifact" data-kind="file" data-path="' + m + '">' + m + '</span>'));
|
|
57
|
-
// 3) Unix 风格绝对路径(至少两级目录,避免误匹配 "2023/2024"、"a/b" 等相对写法)
|
|
58
|
-
html = html.replace(/(\/(?:[A-Za-z0-9_\-.]+\/){1,}[A-Za-z0-9_\-.]*)/g, (m) => protect('<span class="artifact" data-kind="file" data-path="' + m + '">' + m + '</span>'));
|
|
59
|
-
tokens.forEach((s, i) => { html = html.split('\u0000TOK' + i + '\u0000').join(s); });
|
|
60
|
-
return html;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function formatSize(n) {
|
|
64
|
-
if (!Number.isFinite(n)) return '';
|
|
65
|
-
if (n < 1024) return n + ' B';
|
|
66
|
-
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KB';
|
|
67
|
-
return (n / 1024 / 1024).toFixed(2) + ' MB';
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function estimateDataUrlSize(dataUrl) {
|
|
71
|
-
// base64 长度 × 0.75 估算字节
|
|
72
|
-
const b64 = (dataUrl || '').split(',')[1] || '';
|
|
73
|
-
return Math.round(b64.length * 0.75);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function formatToolArgs(args) {
|
|
77
|
-
if (!args || typeof args !== 'object') return '';
|
|
78
|
-
const keys = Object.keys(args);
|
|
79
|
-
if (!keys.length) return '';
|
|
80
|
-
const rows = keys.map(function (k) {
|
|
81
|
-
let v = args[k];
|
|
82
|
-
if (v == null) v = '';
|
|
83
|
-
else if (typeof v === 'object') v = JSON.stringify(v);
|
|
84
|
-
return '<div><span style="color:var(--ink-2,#6b7280);">' + escapeHtml(k) + ':</span> ' + escapeHtml(String(v)) + '</div>';
|
|
85
|
-
});
|
|
86
|
-
return '<div class="tc-args">' + rows.join('') + '</div>';
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function renderMarkdown(text) {
|
|
90
|
-
let html = escapeHtml(text == null ? '' : text);
|
|
91
|
-
const codeBlocks = [];
|
|
92
|
-
html = html.replace(/```(\w*)\n?([\s\S]*?)```/g, function (_, lang, code) {
|
|
93
|
-
const idx = codeBlocks.length;
|
|
94
|
-
codeBlocks.push('<pre style="background:var(--bg,#f5f6fa);padding:10px;border-radius:8px;overflow-x:auto;font-size:12px;margin:6px 0;white-space:pre-wrap;word-break:break-word;"><code>' + code + '</code></pre>');
|
|
95
|
-
return '\x00CB' + idx + '\x00';
|
|
96
|
-
});
|
|
97
|
-
html = html.replace(/`([^`]+)`/g, '<code style="background:var(--bg,#f5f6fa);padding:1px 5px;border-radius:4px;font-size:12px;">$1</code>');
|
|
98
|
-
html = html.replace(/\*\*([^*]+)\*\*/g, '<b>$1</b>');
|
|
99
|
-
html = html.replace(/^### (.*)$/gm, '<div style="font-weight:700;margin:8px 0 4px;">$1</div>');
|
|
100
|
-
html = html.replace(/^## (.*)$/gm, '<div style="font-weight:700;font-size:15px;margin:10px 0 4px;">$1</div>');
|
|
101
|
-
html = html.replace(/^# (.*)$/gm, '<div style="font-weight:700;font-size:16px;margin:10px 0 4px;">$1</div>');
|
|
102
|
-
html = html.replace(/^[-*] (.*)$/gm, '<div style="padding-left:14px;">• $1</div>');
|
|
103
|
-
html = html.replace(/^(\d+)\. (.*)$/gm, '<div style="padding-left:20px;">$1. $2</div>');
|
|
104
|
-
html = html.replace(/(https?:\/\/[^\s<>"'()]+)/g, '<a href="$1" style="color:var(--brand,#4f6ef7);text-decoration:underline;" target="_blank">$1</a>');
|
|
105
|
-
html = html.replace(/\n/g, '<br>');
|
|
106
|
-
html = html.replace(/\x00CB(\d+)\x00/g, function (_, idx) { return codeBlocks[parseInt(idx)]; });
|
|
107
|
-
return html;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// 纯文本渲染:只转义HTML、换行和链接,不转标题/列表/加粗/代码块等格式
|
|
111
|
-
// 用于对话流展示,让输出像普通人聊天一样清爽
|
|
112
|
-
function renderPlainText(text) {
|
|
113
|
-
let html = escapeHtml(stripMarkdown(text == null ? '' : text));
|
|
114
|
-
html = html.replace(/(https?:\/\/[^\s<>"'()]+)/g, '<a href="$1" style="color:var(--brand,#4f6ef7);text-decoration:underline;" target="_blank">$1</a>');
|
|
115
|
-
html = html.replace(/\n/g, '<br>');
|
|
116
|
-
return html;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// 把 Markdown 格式符号全部去掉,只保留纯文本内容
|
|
120
|
-
function stripMarkdown(text) {
|
|
121
|
-
let s = String(text);
|
|
122
|
-
// 去掉代码块 ```...``` 里的标记,保留内容
|
|
123
|
-
s = s.replace(/```[\w]*\n?([\s\S]*?)```/g, '$1');
|
|
124
|
-
// 去掉行内代码 `code` 的反引号,保留内容
|
|
125
|
-
s = s.replace(/`([^`]+)`/g, '$1');
|
|
126
|
-
// 去掉加粗 **text** 和 __text__
|
|
127
|
-
s = s.replace(/\*\*([^*]+)\*\*/g, '$1');
|
|
128
|
-
s = s.replace(/__([^_]+)__/g, '$1');
|
|
129
|
-
// 去掉斜体 *text* 和 _text_(注意不要和列表冲突)
|
|
130
|
-
s = s.replace(/\*([^*\n]+)\*/g, '$1');
|
|
131
|
-
s = s.replace(/_([^_\n]+)_/g, '$1');
|
|
132
|
-
// 去掉标题 # ## ### 等,保留标题文字
|
|
133
|
-
s = s.replace(/^#{1,6}\s+/gm, '');
|
|
134
|
-
// 去掉无序列表符号 * - + (行首)
|
|
135
|
-
s = s.replace(/^[\s]*[-*+]\s+/gm, '');
|
|
136
|
-
// 去掉有序列表符号 1. 2. 等(行首)
|
|
137
|
-
s = s.replace(/^[\s]*\d+\.\s+/gm, '');
|
|
138
|
-
// 去掉引用符号 > (行首)
|
|
139
|
-
s = s.replace(/^[\s]*>\s?/gm, '');
|
|
140
|
-
// 去掉链接 [text](url) 只保留 text
|
|
141
|
-
s = s.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
|
|
142
|
-
// 去掉图片  只保留 alt
|
|
143
|
-
s = s.replace(/!\[([^\]]*)\]\([^)]+\)/g, '$1');
|
|
144
|
-
// 去掉分割线 --- *** ___
|
|
145
|
-
s = s.replace(/^[-*_]{3,}\s*$/gm, '');
|
|
146
|
-
// 去掉行号引用 L123、ln 456、第789行这种
|
|
147
|
-
s = s.replace(/\b[Ll][nN]?\s*\d+(-\d+)?/g, '');
|
|
148
|
-
s = s.replace(/第\d+行/g, '');
|
|
149
|
-
// 去掉多余的空行(连续3个以上换行变成2个)
|
|
150
|
-
s = s.replace(/\n{3,}/g, '\n\n');
|
|
151
|
-
return s.trim();
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function statusBadge(s) { return '<span class="badge ' + s + '">' + s + '</span>'; }
|
|
155
|
-
|
|
156
|
-
function closeModal(id) { document.getElementById(id).classList.remove('show'); }
|
|
157
|
-
|
|
158
|
-
function openModal(id) { document.getElementById(id).classList.add('show'); }
|
|
159
|
-
|
|
160
|
-
function toast(msg) {
|
|
161
|
-
const el = document.getElementById('toast');
|
|
162
|
-
el.textContent = msg; el.classList.add('show');
|
|
163
|
-
clearTimeout(el._t); el._t = setTimeout(() => el.classList.remove('show'), 2200);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function fmtTime(iso) { try { return new Date(iso).toLocaleString(); } catch { return iso || ''; } }
|
|
167
|
-
|
|
168
|
-
function escapeHtml(s) { return String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); }
|
|
169
|
-
|
|
170
|
-
function applyI18nToEl(el) {
|
|
171
|
-
const key = el.getAttribute('data-i18n');
|
|
172
|
-
if (!key) return;
|
|
173
|
-
const dict = I18N[currentLang] || I18N.zh;
|
|
174
|
-
const tag = el.tagName;
|
|
175
|
-
|
|
176
|
-
// 1) 输入类控件:只处理 placeholder,用 ph.<id> 专用键;缺失则保留中文原文
|
|
177
|
-
if (tag === 'INPUT' || tag === 'TEXTAREA') {
|
|
178
|
-
if (el._i18nPhZh === undefined) el._i18nPhZh = el.getAttribute('placeholder') || '';
|
|
179
|
-
const phKey = 'ph.' + (el.id || '');
|
|
180
|
-
if (dict[phKey] != null) el.placeholder = dict[phKey];
|
|
181
|
-
else el.placeholder = el._i18nPhZh;
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// 2) <select>:只翻 title,内容交给 renderModelSelect 等渲染函数管理
|
|
186
|
-
if (tag === 'SELECT') {
|
|
187
|
-
if (el._i18nTitleZh === undefined) el._i18nTitleZh = el.getAttribute('title') || '';
|
|
188
|
-
const tiKey = 'title.' + (el.id || '');
|
|
189
|
-
if (dict[tiKey] != null) el.title = dict[tiKey];
|
|
190
|
-
else el.title = el._i18nTitleZh;
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// 3) 含元素子节点:保留子元素,只替换 title 与直接文本节点
|
|
195
|
-
if (el.firstElementChild) {
|
|
196
|
-
if (el.hasAttribute('title')) el.title = t(key);
|
|
197
|
-
let done = false;
|
|
198
|
-
el.childNodes.forEach((node) => {
|
|
199
|
-
if (node.nodeType === 3 && node.nodeValue.trim()) {
|
|
200
|
-
node.nodeValue = done ? '' : t(key);
|
|
201
|
-
done = true;
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// 4) 纯文本元素:直接覆盖
|
|
208
|
-
el.textContent = t(key);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function t(key) {
|
|
212
|
-
const dict = I18N[currentLang] || I18N.zh;
|
|
213
|
-
return dict[key] || key;
|
|
214
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 工具函数模块:纯函数,无状态依赖,可被任意模块调用
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
function formatMarkdown(text) {
|
|
6
|
+
// 简单 Markdown 转换
|
|
7
|
+
return text
|
|
8
|
+
.replace(/&/g, '&')
|
|
9
|
+
.replace(/</g, '<')
|
|
10
|
+
.replace(/>/g, '>')
|
|
11
|
+
.replace(/^### (.*$)/gm, '<h3>$1</h3>')
|
|
12
|
+
.replace(/^## (.*$)/gm, '<h2>$1</h2>')
|
|
13
|
+
.replace(/^# (.*$)/gm, '<h1>$1</h1>')
|
|
14
|
+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
15
|
+
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
16
|
+
.replace(/`(.*?)`/g, '<code>$1</code>')
|
|
17
|
+
.replace(/\n/g, '<br>');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getDirectoryName(path) {
|
|
21
|
+
if (!path || path === '' || path === '/') return '/';
|
|
22
|
+
if (/^[A-Za-z]:$/.test(path)) return path + '\\';
|
|
23
|
+
if (/^[A-Za-z]:\\$/.test(path)) return path;
|
|
24
|
+
const parts = path.split(/[/\\]/).filter(Boolean);
|
|
25
|
+
if (parts.length <= 1) {
|
|
26
|
+
const driveMatch = parts[0].match(/^([A-Za-z]):$/);
|
|
27
|
+
if (driveMatch) return driveMatch[1] + ':\\';
|
|
28
|
+
return parts[0] || '/';
|
|
29
|
+
}
|
|
30
|
+
const parentParts = parts.slice(0, -1);
|
|
31
|
+
if (/^[A-Za-z]:$/.test(parentParts[0])) {
|
|
32
|
+
return parentParts[0] + '\\' + parentParts.slice(1).join('\\');
|
|
33
|
+
}
|
|
34
|
+
return parentParts.join('\\');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 渲染空状态提示,减少重复的 innerHTML 赋值代码 */
|
|
38
|
+
function renderEmpty(el, text) {
|
|
39
|
+
if (el) el.innerHTML = '<div class="empty">' + (text || '') + '</div>';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function extractUrl(text) {
|
|
43
|
+
if (!text) return '';
|
|
44
|
+
const m = text.match(/(https?:\/\/[^\s]+)/);
|
|
45
|
+
return m ? m[1] : '';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function linkifyArtifacts(text) {
|
|
49
|
+
let html = escapeHtml(text);
|
|
50
|
+
// 占位符保护:先提取已识别的片段,避免后续正则把已插入的 <span> 标签二次包裹
|
|
51
|
+
const tokens = [];
|
|
52
|
+
const protect = (s) => { const k = '\u0000TOK' + tokens.length + '\u0000'; tokens.push(s); return k; };
|
|
53
|
+
// 1) URL:排除结尾标点与 HTML 实体
|
|
54
|
+
html = html.replace(/(https?:\/\/[^\s<>"'()]+)/g, (m) => protect('<span class="artifact" data-kind="url" data-path="' + m + '">' + m + '</span>'));
|
|
55
|
+
// 2) Windows 绝对路径(支持正/反斜杠、中文、空格,排除结尾标点)
|
|
56
|
+
html = html.replace(/([A-Za-z]:[\\\/][^\s<>"'()]+?)(?=[\s<>"',。;:、))]|$)/g, (m) => protect('<span class="artifact" data-kind="file" data-path="' + m + '">' + m + '</span>'));
|
|
57
|
+
// 3) Unix 风格绝对路径(至少两级目录,避免误匹配 "2023/2024"、"a/b" 等相对写法)
|
|
58
|
+
html = html.replace(/(\/(?:[A-Za-z0-9_\-.]+\/){1,}[A-Za-z0-9_\-.]*)/g, (m) => protect('<span class="artifact" data-kind="file" data-path="' + m + '">' + m + '</span>'));
|
|
59
|
+
tokens.forEach((s, i) => { html = html.split('\u0000TOK' + i + '\u0000').join(s); });
|
|
60
|
+
return html;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function formatSize(n) {
|
|
64
|
+
if (!Number.isFinite(n)) return '';
|
|
65
|
+
if (n < 1024) return n + ' B';
|
|
66
|
+
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KB';
|
|
67
|
+
return (n / 1024 / 1024).toFixed(2) + ' MB';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function estimateDataUrlSize(dataUrl) {
|
|
71
|
+
// base64 长度 × 0.75 估算字节
|
|
72
|
+
const b64 = (dataUrl || '').split(',')[1] || '';
|
|
73
|
+
return Math.round(b64.length * 0.75);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function formatToolArgs(args) {
|
|
77
|
+
if (!args || typeof args !== 'object') return '';
|
|
78
|
+
const keys = Object.keys(args);
|
|
79
|
+
if (!keys.length) return '';
|
|
80
|
+
const rows = keys.map(function (k) {
|
|
81
|
+
let v = args[k];
|
|
82
|
+
if (v == null) v = '';
|
|
83
|
+
else if (typeof v === 'object') v = JSON.stringify(v);
|
|
84
|
+
return '<div><span style="color:var(--ink-2,#6b7280);">' + escapeHtml(k) + ':</span> ' + escapeHtml(String(v)) + '</div>';
|
|
85
|
+
});
|
|
86
|
+
return '<div class="tc-args">' + rows.join('') + '</div>';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function renderMarkdown(text) {
|
|
90
|
+
let html = escapeHtml(text == null ? '' : text);
|
|
91
|
+
const codeBlocks = [];
|
|
92
|
+
html = html.replace(/```(\w*)\n?([\s\S]*?)```/g, function (_, lang, code) {
|
|
93
|
+
const idx = codeBlocks.length;
|
|
94
|
+
codeBlocks.push('<pre style="background:var(--bg,#f5f6fa);padding:10px;border-radius:8px;overflow-x:auto;font-size:12px;margin:6px 0;white-space:pre-wrap;word-break:break-word;"><code>' + code + '</code></pre>');
|
|
95
|
+
return '\x00CB' + idx + '\x00';
|
|
96
|
+
});
|
|
97
|
+
html = html.replace(/`([^`]+)`/g, '<code style="background:var(--bg,#f5f6fa);padding:1px 5px;border-radius:4px;font-size:12px;">$1</code>');
|
|
98
|
+
html = html.replace(/\*\*([^*]+)\*\*/g, '<b>$1</b>');
|
|
99
|
+
html = html.replace(/^### (.*)$/gm, '<div style="font-weight:700;margin:8px 0 4px;">$1</div>');
|
|
100
|
+
html = html.replace(/^## (.*)$/gm, '<div style="font-weight:700;font-size:15px;margin:10px 0 4px;">$1</div>');
|
|
101
|
+
html = html.replace(/^# (.*)$/gm, '<div style="font-weight:700;font-size:16px;margin:10px 0 4px;">$1</div>');
|
|
102
|
+
html = html.replace(/^[-*] (.*)$/gm, '<div style="padding-left:14px;">• $1</div>');
|
|
103
|
+
html = html.replace(/^(\d+)\. (.*)$/gm, '<div style="padding-left:20px;">$1. $2</div>');
|
|
104
|
+
html = html.replace(/(https?:\/\/[^\s<>"'()]+)/g, '<a href="$1" style="color:var(--brand,#4f6ef7);text-decoration:underline;" target="_blank">$1</a>');
|
|
105
|
+
html = html.replace(/\n/g, '<br>');
|
|
106
|
+
html = html.replace(/\x00CB(\d+)\x00/g, function (_, idx) { return codeBlocks[parseInt(idx)]; });
|
|
107
|
+
return html;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 纯文本渲染:只转义HTML、换行和链接,不转标题/列表/加粗/代码块等格式
|
|
111
|
+
// 用于对话流展示,让输出像普通人聊天一样清爽
|
|
112
|
+
function renderPlainText(text) {
|
|
113
|
+
let html = escapeHtml(stripMarkdown(text == null ? '' : text));
|
|
114
|
+
html = html.replace(/(https?:\/\/[^\s<>"'()]+)/g, '<a href="$1" style="color:var(--brand,#4f6ef7);text-decoration:underline;" target="_blank">$1</a>');
|
|
115
|
+
html = html.replace(/\n/g, '<br>');
|
|
116
|
+
return html;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 把 Markdown 格式符号全部去掉,只保留纯文本内容
|
|
120
|
+
function stripMarkdown(text) {
|
|
121
|
+
let s = String(text);
|
|
122
|
+
// 去掉代码块 ```...``` 里的标记,保留内容
|
|
123
|
+
s = s.replace(/```[\w]*\n?([\s\S]*?)```/g, '$1');
|
|
124
|
+
// 去掉行内代码 `code` 的反引号,保留内容
|
|
125
|
+
s = s.replace(/`([^`]+)`/g, '$1');
|
|
126
|
+
// 去掉加粗 **text** 和 __text__
|
|
127
|
+
s = s.replace(/\*\*([^*]+)\*\*/g, '$1');
|
|
128
|
+
s = s.replace(/__([^_]+)__/g, '$1');
|
|
129
|
+
// 去掉斜体 *text* 和 _text_(注意不要和列表冲突)
|
|
130
|
+
s = s.replace(/\*([^*\n]+)\*/g, '$1');
|
|
131
|
+
s = s.replace(/_([^_\n]+)_/g, '$1');
|
|
132
|
+
// 去掉标题 # ## ### 等,保留标题文字
|
|
133
|
+
s = s.replace(/^#{1,6}\s+/gm, '');
|
|
134
|
+
// 去掉无序列表符号 * - + (行首)
|
|
135
|
+
s = s.replace(/^[\s]*[-*+]\s+/gm, '');
|
|
136
|
+
// 去掉有序列表符号 1. 2. 等(行首)
|
|
137
|
+
s = s.replace(/^[\s]*\d+\.\s+/gm, '');
|
|
138
|
+
// 去掉引用符号 > (行首)
|
|
139
|
+
s = s.replace(/^[\s]*>\s?/gm, '');
|
|
140
|
+
// 去掉链接 [text](url) 只保留 text
|
|
141
|
+
s = s.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
|
|
142
|
+
// 去掉图片  只保留 alt
|
|
143
|
+
s = s.replace(/!\[([^\]]*)\]\([^)]+\)/g, '$1');
|
|
144
|
+
// 去掉分割线 --- *** ___
|
|
145
|
+
s = s.replace(/^[-*_]{3,}\s*$/gm, '');
|
|
146
|
+
// 去掉行号引用 L123、ln 456、第789行这种
|
|
147
|
+
s = s.replace(/\b[Ll][nN]?\s*\d+(-\d+)?/g, '');
|
|
148
|
+
s = s.replace(/第\d+行/g, '');
|
|
149
|
+
// 去掉多余的空行(连续3个以上换行变成2个)
|
|
150
|
+
s = s.replace(/\n{3,}/g, '\n\n');
|
|
151
|
+
return s.trim();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function statusBadge(s) { return '<span class="badge ' + s + '">' + s + '</span>'; }
|
|
155
|
+
|
|
156
|
+
function closeModal(id) { document.getElementById(id).classList.remove('show'); }
|
|
157
|
+
|
|
158
|
+
function openModal(id) { document.getElementById(id).classList.add('show'); }
|
|
159
|
+
|
|
160
|
+
function toast(msg) {
|
|
161
|
+
const el = document.getElementById('toast');
|
|
162
|
+
el.textContent = msg; el.classList.add('show');
|
|
163
|
+
clearTimeout(el._t); el._t = setTimeout(() => el.classList.remove('show'), 2200);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function fmtTime(iso) { try { return new Date(iso).toLocaleString(); } catch { return iso || ''; } }
|
|
167
|
+
|
|
168
|
+
function escapeHtml(s) { return String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); }
|
|
169
|
+
|
|
170
|
+
function applyI18nToEl(el) {
|
|
171
|
+
const key = el.getAttribute('data-i18n');
|
|
172
|
+
if (!key) return;
|
|
173
|
+
const dict = I18N[currentLang] || I18N.zh;
|
|
174
|
+
const tag = el.tagName;
|
|
175
|
+
|
|
176
|
+
// 1) 输入类控件:只处理 placeholder,用 ph.<id> 专用键;缺失则保留中文原文
|
|
177
|
+
if (tag === 'INPUT' || tag === 'TEXTAREA') {
|
|
178
|
+
if (el._i18nPhZh === undefined) el._i18nPhZh = el.getAttribute('placeholder') || '';
|
|
179
|
+
const phKey = 'ph.' + (el.id || '');
|
|
180
|
+
if (dict[phKey] != null) el.placeholder = dict[phKey];
|
|
181
|
+
else el.placeholder = el._i18nPhZh;
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 2) <select>:只翻 title,内容交给 renderModelSelect 等渲染函数管理
|
|
186
|
+
if (tag === 'SELECT') {
|
|
187
|
+
if (el._i18nTitleZh === undefined) el._i18nTitleZh = el.getAttribute('title') || '';
|
|
188
|
+
const tiKey = 'title.' + (el.id || '');
|
|
189
|
+
if (dict[tiKey] != null) el.title = dict[tiKey];
|
|
190
|
+
else el.title = el._i18nTitleZh;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 3) 含元素子节点:保留子元素,只替换 title 与直接文本节点
|
|
195
|
+
if (el.firstElementChild) {
|
|
196
|
+
if (el.hasAttribute('title')) el.title = t(key);
|
|
197
|
+
let done = false;
|
|
198
|
+
el.childNodes.forEach((node) => {
|
|
199
|
+
if (node.nodeType === 3 && node.nodeValue.trim()) {
|
|
200
|
+
node.nodeValue = done ? '' : t(key);
|
|
201
|
+
done = true;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 4) 纯文本元素:直接覆盖
|
|
208
|
+
el.textContent = t(key);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function t(key) {
|
|
212
|
+
const dict = I18N[currentLang] || I18N.zh;
|
|
213
|
+
return dict[key] || key;
|
|
214
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "飞虹 Code",
|
|
3
|
+
"short_name": "飞虹Code",
|
|
4
|
+
"description": "终端 AI 编程智能体,对标 Muse Code / Cursor",
|
|
5
|
+
"start_url": "/",
|
|
6
|
+
"display": "standalone",
|
|
7
|
+
"background_color": "#1a1a2e",
|
|
8
|
+
"theme_color": "#6366f1",
|
|
9
|
+
"orientation": "any",
|
|
10
|
+
"scope": "/",
|
|
11
|
+
"lang": "zh-CN",
|
|
12
|
+
"dir": "ltr",
|
|
13
|
+
"categories": ["developer-tools", "productivity", "utilities"],
|
|
14
|
+
"icons": [
|
|
15
|
+
{
|
|
16
|
+
"src": "/icons/icon-192.png",
|
|
17
|
+
"sizes": "192x192",
|
|
18
|
+
"type": "image/png",
|
|
19
|
+
"purpose": "any maskable"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"src": "/icons/icon-512.png",
|
|
23
|
+
"sizes": "512x512",
|
|
24
|
+
"type": "image/png",
|
|
25
|
+
"purpose": "any maskable"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"shortcuts": [
|
|
29
|
+
{
|
|
30
|
+
"name": "新建任务",
|
|
31
|
+
"short_name": "新任务",
|
|
32
|
+
"description": "创建新的 AI 编程任务",
|
|
33
|
+
"url": "/?action=new-task",
|
|
34
|
+
"icons": [{ "src": "/icons/icon-192.png", "sizes": "192x192" }]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "代码补全",
|
|
38
|
+
"short_name": "补全",
|
|
39
|
+
"description": "快速代码补全",
|
|
40
|
+
"url": "/?action=completion",
|
|
41
|
+
"icons": [{ "src": "/icons/icon-192.png", "sizes": "192x192" }]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "代码审查",
|
|
45
|
+
"short_name": "审查",
|
|
46
|
+
"description": "AI 代码审查",
|
|
47
|
+
"url": "/?action=review",
|
|
48
|
+
"icons": [{ "src": "/icons/icon-192.png", "sizes": "192x192" }]
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"prefer_related_applications": false,
|
|
52
|
+
"features": [
|
|
53
|
+
"Cross Platform",
|
|
54
|
+
"AI Code Completion",
|
|
55
|
+
"Multi-agent Collaboration",
|
|
56
|
+
"Voice Programming",
|
|
57
|
+
"Knowledge Library"
|
|
58
|
+
]
|
|
59
|
+
}
|