feihong-code 7.2.0 → 7.6.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 +54 -1
- package/.github/workflows/release.yml +30 -0
- package/.github/workflows/swebench-real.yml +84 -0
- package/CHANGELOG.md +41 -39
- package/CONTRIBUTING.md +9 -2
- package/README.md +56 -6
- package/README.self-evolve.md +279 -274
- package/dist/agent/change-manager.js +1 -1
- package/dist/agent/code-rag.js +1 -1
- package/dist/agent/code-review.js +1 -1
- package/dist/agent/code-writer.js +1 -1
- package/dist/agent/completion-engine.js +76 -45
- package/dist/agent/completion-engine.js.map +1 -1
- 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 +58 -0
- package/dist/agent/context-compactor.js.map +1 -1
- package/dist/agent/experience.js +1 -1
- package/dist/agent/layered-memory.js +1 -1
- package/dist/agent/lint.js +121 -0
- package/dist/agent/lint.js.map +1 -0
- package/dist/agent/orchestrator.js +106 -31
- package/dist/agent/orchestrator.js.map +1 -1
- package/dist/agent/parallel-orchestrator.js +1 -1
- package/dist/agent/planner.js +8 -3
- package/dist/agent/planner.js.map +1 -1
- package/dist/agent/prompts.js +55 -55
- package/dist/agent/quality-gate.js +1 -1
- package/dist/agent/repo-context.js +1 -1
- package/dist/agent/repo-reader.js +1 -1
- package/dist/agent/repo-underwriter.js +1 -1
- package/dist/agent/self-correction.js +25 -25
- package/dist/agent/self-heal.js +32 -27
- package/dist/agent/self-heal.js.map +1 -1
- package/dist/agent/self-improver.js +1 -1
- package/dist/agent/subagent-summary.js +1 -1
- package/dist/agent/subagent.js +1 -1
- package/dist/agent/swe-agent.js +1 -1
- package/dist/agent/swe-verifier.js +1 -1
- package/dist/agent/symbol-index.js +1 -1
- package/dist/agent/team.js +1 -1
- package/dist/agent/type-checker.js +1 -1
- package/dist/cli/commands.js +16 -0
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/index.js +66 -2
- 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/repl.js +1 -1
- package/dist/cli/run.js +168 -42
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/self-evolve-cli.js +168 -0
- package/dist/cli/tui-run.js +79 -0
- package/dist/cli/tui-run.js.map +1 -0
- package/dist/cli/tui.js +1 -1
- package/dist/cli/tui.js.map +1 -1
- package/dist/cli/version.js +3 -3
- package/dist/cli/version.js.map +1 -1
- package/dist/enterprise/audit.js +1 -1
- package/dist/enterprise/index.js +1 -1
- package/dist/enterprise/policy.js +1 -1
- package/dist/enterprise/quota.js +1 -1
- package/dist/enterprise/tenant.js +1 -1
- package/dist/harness/executor.js +1 -1
- package/dist/harness/index.js +1 -1
- package/dist/harness/loader.js +1 -1
- package/dist/harness/types.js +1 -1
- package/dist/harness/verifier.js +52 -12
- package/dist/harness/verifier.js.map +1 -1
- package/dist/integrations/feishu-bridge.js +348 -0
- package/dist/integrations/feishu-bridge.js.map +1 -0
- package/dist/integrations/github-mcp.js +137 -0
- package/dist/integrations/github-mcp.js.map +1 -0
- package/dist/integrations/wechat-bridge.js +499 -0
- package/dist/integrations/wechat-bridge.js.map +1 -0
- package/dist/integrations/yuanbao-bridge.js +326 -0
- package/dist/integrations/yuanbao-bridge.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/models/model-router.js +1 -1
- package/dist/models/model.dto.js +1 -1
- package/dist/models/providers/ollama.provider.js +1 -1
- package/dist/models/providers/openai-compatible.provider.js +1 -1
- package/dist/plugins/manager.js +50 -2
- package/dist/plugins/manager.js.map +1 -1
- package/dist/plugins/plugin-loader.js +1 -1
- package/dist/runtime/event-log.js +1 -1
- package/dist/runtime/git.js +1 -1
- package/dist/runtime/hooks.js +71 -4
- package/dist/runtime/hooks.js.map +1 -1
- package/dist/runtime/session-persist.js +1 -1
- package/dist/runtime/worktree.js +1 -1
- package/dist/self-evolve/manager.js +517 -401
- package/dist/self-evolve/self-heal-scheduler.js +160 -0
- package/dist/self-evolve/self-heal-scheduler.js.map +1 -0
- package/dist/shared/concurrency.js +1 -1
- package/dist/shared/config.js +4 -2
- package/dist/shared/config.js.map +1 -1
- package/dist/shared/errors.js +1 -1
- package/dist/shared/i18n.js +109 -101
- package/dist/shared/i18n.js.map +1 -1
- package/dist/shared/secure-store.js +12 -5
- package/dist/shared/secure-store.js.map +1 -1
- package/dist/shared/types.js +1 -1
- package/dist/skills/goal.js +1 -1
- package/dist/skills/grill.js +1 -1
- package/dist/skills/plan.js +1 -1
- package/dist/skills/pua-hooks.js +273 -0
- package/dist/skills/pua-hooks.js.map +1 -0
- package/dist/skills/self-heal.js +9 -3
- package/dist/skills/self-heal.js.map +1 -1
- package/dist/skills/skill-loader.js +1 -1
- package/dist/skills/skill-market.js +84 -8
- package/dist/skills/skill-market.js.map +1 -1
- package/dist/tools/analysis/code-analyzer.js +1 -1
- package/dist/tools/file/edit.tool.js +18 -3
- package/dist/tools/file/edit.tool.js.map +1 -1
- package/dist/tools/file/list.tool.js +1 -1
- package/dist/tools/file/read.tool.js +1 -1
- package/dist/tools/file/write.tool.js +1 -1
- package/dist/tools/generator/code-generator.js +41 -41
- package/dist/tools/generator/test-generator.js +10 -10
- package/dist/tools/index.js +1 -1
- package/dist/tools/mcp/index.js +1 -1
- package/dist/tools/mcp/mcp-client.js +1 -1
- package/dist/tools/safe-path.js +1 -1
- package/dist/tools/search/grep.tool.js +1 -1
- package/dist/tools/shell/exec.js +24 -2
- package/dist/tools/shell/exec.js.map +1 -1
- package/dist/tools/shell/run-shell.tool.js +1 -1
- package/dist/tools/skills/load-skill.tool.js +1 -1
- package/dist/tools/tool.registry.js +10 -1
- package/dist/tools/tool.registry.js.map +1 -1
- package/dist/tools/verify/build-check.tool.js +1 -1
- package/dist/tools/verify/test-run.tool.js +1 -1
- package/dist/tools/web/web.tool.js +1 -1
- package/dist/voice/voice-programming.js +32 -6
- package/dist/voice/voice-programming.js.map +1 -1
- package/dist/web/auth.js +34 -2
- package/dist/web/auth.js.map +1 -1
- package/dist/web/channels.js +1 -1
- package/dist/web/extra-apis.js +440 -0
- package/dist/web/extra-apis.js.map +1 -0
- package/dist/web/public/css/style.css +99 -72
- package/dist/web/public/index.html +44 -1
- package/dist/web/public/js/api.js +64 -2
- package/dist/web/public/js/app.js +151 -128
- package/dist/web/public/js/mock-api.js +25 -0
- package/dist/web/public/js/monaco-editor.js +161 -1
- package/dist/web/public/js/ui.js +292 -56
- package/dist/web/public/service-worker.js +5 -29
- package/dist/web/server.js +178 -61
- package/dist/web/server.js.map +1 -1
- package/dist/web/task-queue.js +6 -21
- package/dist/web/task-queue.js.map +1 -1
- package/docs/App/344/275/277/347/224/250/350/257/264/346/230/216/344/271/246.md +1 -1
- package/docs/App/346/212/200/346/234/257/350/257/264/346/230/216/344/271/246.md +1 -1
- package/docs/BENCHMARK_REPORT_en.md +62 -5
- package/docs/BENCHMARK_REPORT_zh.md +66 -9
- package/docs/DPA.md +65 -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 +69 -0
- package/docs/Deployment_Guide_EN.md +1 -1
- package/docs/SECURITY_WHITEPAPER.md +74 -0
- package/docs/SELF-EVOLVE-GUIDE.md +1 -1
- package/docs/SWE_BENCH_REPORT.md +73 -0
- package/docs/TECHNICAL_SPEC_en.md +1 -1
- package/docs/TECHNICAL_SPEC_zh.md +1 -1
- package/docs/Technical_Manual_EN.md +1 -1
- package/docs/UPDATE_NOTES_en.md +1 -1
- package/docs/UPDATE_NOTES_zh.md +1 -1
- package/docs/UPGRADE_7.2.0.md +309 -0
- package/docs/UPGRADE_GUIDE_7_5.md +86 -0
- package/docs/UPGRADE_GUIDE_7_6.md +153 -0
- package/docs/User_Manual_EN.md +1 -1
- package/docs/VOLTAGENT_SKILLS_CATALOG.md +95 -0
- package/docs/error-codes.md +1 -1
- package/docs/geo-feihong-code.html +391 -0
- package/docs//344/272/247/345/223/201/345/274/200/345/217/221/346/226/207/346/241/243.md +5 -3
- package/docs//344/275/277/347/224/250/350/257/264/346/230/216/344/271/246.md +140 -231
- 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 +84 -0
- package/docs//346/212/200/346/234/257/350/257/264/346/230/216/344/271/246.md +240 -149
- package/docs//351/203/250/347/275/262/350/257/264/346/230/216/344/271/246.md +2 -2
- package/electron/main.js +4 -202
- package/package.json +182 -172
- 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/docs/DEPLOYMENT.md +0 -382
- package/docs/MODEL_FINETUNING.md +0 -405
package/dist/web/public/js/ui.js
CHANGED
|
@@ -98,6 +98,7 @@
|
|
|
98
98
|
|
|
99
99
|
// 长期记忆:编辑按钮
|
|
100
100
|
document.getElementById('memEditLong')?.addEventListener('click', () => {
|
|
101
|
+
bindGhostTextOnce(); // P1-1: 编辑时启用 ghost text 补全
|
|
101
102
|
const content = document.getElementById('memLongContent');
|
|
102
103
|
const editor = document.getElementById('memLongEditor');
|
|
103
104
|
const editBtn = document.getElementById('memEditLong');
|
|
@@ -748,6 +749,33 @@
|
|
|
748
749
|
|
|
749
750
|
/* ========== P0-1: Monaco 补全 Provider 注册(内联 ghost text + 补全弹窗) ========== */
|
|
750
751
|
let _monacoCompletionsRegistered = false;
|
|
752
|
+
|
|
753
|
+
// P6-1: 补全接受后自动 lint——调 /api/lint 校验插入代码,异常追加 fhcode-lint 波浪线 + 提示
|
|
754
|
+
let _lintFeedbackShown = false;
|
|
755
|
+
async function lintAfterAccept(inserted) {
|
|
756
|
+
if (!inserted || !inserted.trim()) return;
|
|
757
|
+
try {
|
|
758
|
+
const d = await api('/api/lint', 'POST', { code: inserted, language: '' });
|
|
759
|
+
if (!d || !d.ok) return;
|
|
760
|
+
if (d.clean) {
|
|
761
|
+
_lintFeedbackShown = false;
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (typeof FHMonaco !== 'undefined' && FHMonaco.showLintFeedback) {
|
|
765
|
+
FHMonaco.showLintFeedback(d.errors || []);
|
|
766
|
+
}
|
|
767
|
+
const errCount = (d.errors || []).filter((x) => x.severity === 1).length;
|
|
768
|
+
if (errCount > 0 && !_lintFeedbackShown) {
|
|
769
|
+
_lintFeedbackShown = true;
|
|
770
|
+
const bar = document.getElementById('statusMessage');
|
|
771
|
+
if (bar) bar.textContent = '⚠️ 补全已接受,但校验发现 ' + errCount + ' 个语法错误(已标注)';
|
|
772
|
+
setTimeout(() => { if (bar && bar.textContent.includes('校验发现')) bar.textContent = ''; }, 6000);
|
|
773
|
+
}
|
|
774
|
+
} catch (e) {
|
|
775
|
+
/* lint 反馈失败静默 */
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
751
779
|
function registerMonacoCompletions() {
|
|
752
780
|
if (_monacoCompletionsRegistered || typeof FHMonaco === 'undefined') return;
|
|
753
781
|
_monacoCompletionsRegistered = true;
|
|
@@ -769,6 +797,10 @@
|
|
|
769
797
|
} catch (e) {
|
|
770
798
|
return null;
|
|
771
799
|
}
|
|
800
|
+
}, {
|
|
801
|
+
getActiveEditor: function () { return (typeof FHMonaco !== 'undefined') ? FHMonaco.getActiveEditor() : null; },
|
|
802
|
+
// P6-1: 补全被接受后自动 lint 反馈
|
|
803
|
+
onAccept: function (inserted) { lintAfterAccept(inserted); },
|
|
772
804
|
});
|
|
773
805
|
|
|
774
806
|
// 补全项弹窗(Ctrl+Space 触发,↑↓选择,Enter接受)—— Monaco 原生支持
|
|
@@ -791,6 +823,19 @@
|
|
|
791
823
|
return [];
|
|
792
824
|
}
|
|
793
825
|
});
|
|
826
|
+
|
|
827
|
+
// P4-2: 语义诊断波浪线(编辑防抖调 /api/lsp/diagnostics,setModelMarkers 标注)
|
|
828
|
+
FHMonaco.registerDiagnostics(async function ({ filePath }) {
|
|
829
|
+
try {
|
|
830
|
+
const rel = String(filePath).replace(/^\/+/, '');
|
|
831
|
+
if (!rel || rel === 'untitled') return { diagnostics: [] };
|
|
832
|
+
const q = 'cwd=' + encodeURIComponent('') + '&file=' + encodeURIComponent(rel);
|
|
833
|
+
const d = await api('/api/lsp/diagnostics?' + q, 'GET');
|
|
834
|
+
return Array.isArray(d.diagnostics) ? { diagnostics: d.diagnostics } : { diagnostics: [] };
|
|
835
|
+
} catch (e) {
|
|
836
|
+
return { diagnostics: [] };
|
|
837
|
+
}
|
|
838
|
+
}, function () { return (typeof monacoEditor !== 'undefined') ? monacoEditor : null; });
|
|
794
839
|
}
|
|
795
840
|
|
|
796
841
|
function switchRightTab(name) {
|
|
@@ -803,6 +848,7 @@
|
|
|
803
848
|
document.getElementById('designPanel')?.classList.toggle('active', name === 'design');
|
|
804
849
|
document.getElementById('gitPanel')?.classList.toggle('active', name === 'git');
|
|
805
850
|
document.getElementById('teamPanel')?.classList.toggle('active', name === 'team');
|
|
851
|
+
document.getElementById('capabilitiesPanel')?.classList.toggle('active', name === 'capabilities');
|
|
806
852
|
if (name === 'changes') {
|
|
807
853
|
bindChangesButtonsOnce();
|
|
808
854
|
loadChanges();
|
|
@@ -818,6 +864,10 @@
|
|
|
818
864
|
bindTeamButtonsOnce();
|
|
819
865
|
loadTeamData();
|
|
820
866
|
}
|
|
867
|
+
if (name === 'capabilities') {
|
|
868
|
+
bindCapabilitiesOnce();
|
|
869
|
+
loadCapabilitiesPlugins();
|
|
870
|
+
}
|
|
821
871
|
}
|
|
822
872
|
|
|
823
873
|
// 变更面板顶部按钮只绑定一次
|
|
@@ -860,19 +910,40 @@
|
|
|
860
910
|
}
|
|
861
911
|
|
|
862
912
|
/* ========== P3: 多文件变更面板 ========== */
|
|
913
|
+
let _autoConflictChecked = false; // P1-2: 每次会话仅自动检测一次
|
|
863
914
|
async function loadChanges() {
|
|
864
915
|
try {
|
|
865
916
|
const d = await api('/api/changes', 'GET');
|
|
866
917
|
renderChanges(d);
|
|
918
|
+
// P1-2 增强:加载后自动检测一次冲突(无需手动点击)
|
|
919
|
+
if (!_autoConflictChecked && Array.isArray(d.changes) && d.changes.length > 0) {
|
|
920
|
+
_autoConflictChecked = true;
|
|
921
|
+
try {
|
|
922
|
+
const dc = await api('/api/changes/detect-conflicts', 'POST');
|
|
923
|
+
_conflictFiles = Array.isArray(dc.conflicts) ? dc.conflicts.map(c => c.path || c) : [];
|
|
924
|
+
if (_conflictFiles.length > 0) renderChanges(d);
|
|
925
|
+
} catch { /* 自动检测失败不阻塞 */ }
|
|
926
|
+
}
|
|
867
927
|
} catch (e) {
|
|
868
928
|
document.getElementById('changesList').innerHTML = '<div class="muted" style="text-align:center;padding:20px;">加载失败:' + escapeHtml(e.message) + '</div>';
|
|
869
929
|
}
|
|
870
930
|
}
|
|
871
931
|
|
|
932
|
+
// P6-3: 多文件 diff 视图状态——每文件显示模式(inline/side)+ 折叠集合
|
|
933
|
+
const _diffModes = {};
|
|
934
|
+
const _collapsedPaths = new Set();
|
|
935
|
+
|
|
872
936
|
function renderChanges(data) {
|
|
873
937
|
const changes = Array.isArray(data.changes) ? data.changes : [];
|
|
874
938
|
const count = changes.length;
|
|
875
939
|
document.getElementById('changesCount').textContent = count;
|
|
940
|
+
// P1-2 增强:冲突计数 badge
|
|
941
|
+
const conflictBadge = document.getElementById('changesConflictCount');
|
|
942
|
+
if (conflictBadge) {
|
|
943
|
+
const n = _conflictFiles.filter(cf => changes.some(c => c.path === cf || c.path.endsWith(cf) || cf.endsWith(c.path))).length;
|
|
944
|
+
conflictBadge.style.display = n > 0 ? 'inline' : 'none';
|
|
945
|
+
conflictBadge.textContent = n + ' 冲突';
|
|
946
|
+
}
|
|
876
947
|
const list = document.getElementById('changesList');
|
|
877
948
|
if (!count) {
|
|
878
949
|
list.innerHTML = '<div class="muted" style="text-align:center;padding:30px;font-size:12px;">暂无暂存变更<br/><span style="font-size:11px;">AI 生成的文件修改会先暂存在这里,审批后才写入磁盘</span></div>';
|
|
@@ -887,35 +958,58 @@
|
|
|
887
958
|
const hunks = Array.isArray(c.hunks) ? c.hunks : [];
|
|
888
959
|
let hunksHtml = '';
|
|
889
960
|
if (hunks.length) {
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
961
|
+
const mode = _diffModes[c.path] || 'inline';
|
|
962
|
+
if (mode === 'side') {
|
|
963
|
+
// P6-3: 并排 diff 视图(左=原文件,右=新文件,行级对齐)
|
|
964
|
+
hunksHtml = '<div style="margin-top:6px;">' + hunks.map((h) => {
|
|
965
|
+
const rows = (h.lines || []).map((l) => {
|
|
966
|
+
const left = (l.type === 'del' || l.type === 'ctx')
|
|
967
|
+
? '<div class="' + (l.type === 'del' ? 'diff-del' : 'diff-ctx') + '">' + escapeHtml(l.content) + '</div>'
|
|
968
|
+
: '<div class="diff-ctx" style="color:transparent;">·</div>';
|
|
969
|
+
const right = (l.type === 'add' || l.type === 'ctx')
|
|
970
|
+
? '<div class="' + (l.type === 'add' ? 'diff-add' : 'diff-ctx') + '">' + escapeHtml(l.content) + '</div>'
|
|
971
|
+
: '<div class="diff-ctx" style="color:transparent;">·</div>';
|
|
972
|
+
return '<div style="display:grid;grid-template-columns:1fr 1fr;border-bottom:1px solid var(--line, #222);">' + left + right + '</div>';
|
|
973
|
+
}).join('');
|
|
974
|
+
return '<div style="border:1px solid var(--border);border-radius:4px;overflow:hidden;margin-bottom:6px;">'
|
|
975
|
+
+ '<div style="display:flex;justify-content:space-between;align-items:center;padding:2px 6px;background:var(--bg);font-size:11px;">'
|
|
976
|
+
+ '<span class="muted">@@ ' + escapeHtml(String(h.header || '')) + '</span>'
|
|
977
|
+
+ '<span class="muted" style="font-size:10px;">← 原文件 | 新文件 →</span></div>'
|
|
978
|
+
+ rows + '</div>';
|
|
979
|
+
}).join('') + '</div>';
|
|
980
|
+
} else {
|
|
981
|
+
hunksHtml = '<div style="margin-top:6px;">' + hunks.map((h, hi) => {
|
|
982
|
+
const lines = (h.lines || []).map(l => {
|
|
983
|
+
const cls = l.type === 'add' ? 'diff-add' : l.type === 'del' ? 'diff-del' : 'diff-ctx';
|
|
984
|
+
return '<div class="' + cls + '">' + escapeHtml(l.content) + '</div>';
|
|
985
|
+
}).join('');
|
|
986
|
+
return '<div class="diff-hunk" style="margin-bottom:6px;border:1px solid var(--border);border-radius:4px;overflow:hidden;">'
|
|
987
|
+
+ '<div style="display:flex;justify-content:space-between;align-items:center;padding:3px 6px;background:var(--bg);font-size:11px;">'
|
|
988
|
+
+ '<span class="muted">@@ ' + escapeHtml(String(h.header || '')) + '</span>'
|
|
989
|
+
+ '<span style="display:flex;gap:4px;">'
|
|
990
|
+
+ '<button class="ghost-btn" style="font-size:10px;padding:1px 6px;" data-act="accept-hunk" data-idx="' + idx + '" data-hunk="' + hi + '">✓</button>'
|
|
991
|
+
+ '<button class="ghost-btn" style="font-size:10px;padding:1px 6px;" data-act="reject-hunk" data-idx="' + idx + '" data-hunk="' + hi + '">✕</button>'
|
|
992
|
+
+ '</span></div>'
|
|
993
|
+
+ '<div style="font-family:monospace;font-size:11px;line-height:1.5;">' + lines + '</div></div>';
|
|
994
|
+
}).join('') + '</div>';
|
|
995
|
+
}
|
|
904
996
|
}
|
|
997
|
+
const collapsed = _collapsedPaths.has(c.path);
|
|
905
998
|
return '<div class="change-item" style="padding:8px;margin-bottom:8px;' + conflictBorder + 'border-radius:6px;background:var(--card);">'
|
|
906
999
|
+ '<div style="display:flex;justify-content:space-between;align-items:center;gap:6px;">'
|
|
907
|
-
+ '<div style="flex:1;min-width:0;">'
|
|
908
|
-
+ '<div style="font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;"
|
|
1000
|
+
+ '<div style="flex:1;min-width:0;cursor:pointer;" data-act="toggle-collapse" data-idx="' + idx + '" title="点击折叠/展开">'
|
|
1001
|
+
+ '<div style="font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' + (collapsed ? '▸ ' : '▾ ') + escapeHtml(c.path) + conflictBadge + '</div>'
|
|
909
1002
|
+ '<div style="font-size:11px;margin-top:2px;"><span style="color:' + typeColor + ';">' + typeLabel + '</span>'
|
|
910
1003
|
+ (c.additions != null ? ' · <span style="color:#2d8a4e;">+' + c.additions + '</span>' : '')
|
|
911
1004
|
+ (c.deletions != null ? ' · <span style="color:#c0392b;">-' + c.deletions + '</span>' : '')
|
|
912
1005
|
+ ' · <span class="muted">' + escapeHtml(c.status || 'pending') + '</span></div>'
|
|
913
1006
|
+ '</div>'
|
|
914
1007
|
+ '<div style="display:flex;gap:4px;flex-shrink:0;">'
|
|
1008
|
+
+ (hunks.length ? '<button class="ghost-btn" style="font-size:11px;padding:3px 8px;" data-act="toggle-mode" data-idx="' + idx + '">' + ((_diffModes[c.path] || 'inline') === 'side' ? '内联' : '并排') + '</button>' : '')
|
|
915
1009
|
+ '<button class="ghost-btn" style="font-size:11px;padding:3px 8px;background:#2d5a3d;color:#fff;" data-act="accept" data-idx="' + idx + '">接受</button>'
|
|
916
1010
|
+ '<button class="ghost-btn" style="font-size:11px;padding:3px 8px;" data-act="reject" data-idx="' + idx + '">拒绝</button>'
|
|
917
1011
|
+ '</div></div>'
|
|
918
|
-
+ hunksHtml
|
|
1012
|
+
+ (collapsed ? '' : hunksHtml)
|
|
919
1013
|
+ '</div>';
|
|
920
1014
|
}).join('');
|
|
921
1015
|
// 绑定事件
|
|
@@ -940,6 +1034,11 @@
|
|
|
940
1034
|
const hi = parseInt(btn.getAttribute('data-hunk'));
|
|
941
1035
|
await api('/api/changes/hunk/reject', 'POST', { path: change.path, hunkIndex: hi });
|
|
942
1036
|
toast('已拒绝 Hunk');
|
|
1037
|
+
} else if (act === 'toggle-mode') {
|
|
1038
|
+
_diffModes[change.path] = (_diffModes[change.path] || 'inline') === 'side' ? 'inline' : 'side';
|
|
1039
|
+
} else if (act === 'toggle-collapse') {
|
|
1040
|
+
if (_collapsedPaths.has(change.path)) _collapsedPaths.delete(change.path);
|
|
1041
|
+
else _collapsedPaths.add(change.path);
|
|
943
1042
|
}
|
|
944
1043
|
loadChanges();
|
|
945
1044
|
} catch (e) { toast('操作失败:' + e.message); }
|
|
@@ -983,6 +1082,10 @@
|
|
|
983
1082
|
const out = (d.output || '').toString();
|
|
984
1083
|
return '<div class="step result ' + (ok ? 'ok' : 'fail') + '"><div class="res-head">' + (ok ? '✅' : '❌') + ' ' + escapeHtml(d.name || '') + ' ' + (ok ? '成功' : '失败') + '</div>' + (out.trim() ? '<div class="res-out">' + escapeHtml(out).slice(0, 500) + '</div>' : '') + '</div>';
|
|
985
1084
|
} else if (step.type === 'self-heal') {
|
|
1085
|
+
// 区分「普通反思重试」与「连续失败换路线继续」
|
|
1086
|
+
if (d.strategy === 'bypass-and-continue') {
|
|
1087
|
+
return '<div class="step note">🔄 连续失败已自动换路线:放弃当前方案,正在尝试<b>其他方式继续任务</b>(第 ' + (d.iteration != null ? d.iteration : '?') + ' 轮)</div>';
|
|
1088
|
+
}
|
|
986
1089
|
return '<div class="step note">🩹 进入自愈:检测到 <b>' + escapeHtml(d.category || '') + '</b> 类错误,已注入反思重试(第 ' + (d.iteration != null ? d.iteration : '?') + ' 轮)</div>';
|
|
987
1090
|
} else if (step.type === 'context.compact') {
|
|
988
1091
|
return '<div class="step note">📦 上下文压缩:从 ' + (d.originalLength != null ? d.originalLength : '?') + ' 条压缩至 ' + (d.compressedLength != null ? d.compressedLength : '?') + ' 条,保留首条目标与近期对话</div>';
|
|
@@ -1082,48 +1185,10 @@
|
|
|
1082
1185
|
}
|
|
1083
1186
|
html += '<div class="msg assistant error-msg">任务遇到问题:' + escapeHtml(task.error || '未知错误') + '</div>';
|
|
1084
1187
|
} else if (task.status === 'running') {
|
|
1085
|
-
//
|
|
1086
|
-
let waitTip = '';
|
|
1087
|
-
if (task.createdAt) {
|
|
1088
|
-
const elapsed = Math.floor((Date.now() - new Date(task.createdAt).getTime()) / 1000);
|
|
1089
|
-
if (elapsed > 5) {
|
|
1090
|
-
const mins = Math.floor(elapsed / 60);
|
|
1091
|
-
const secs = elapsed % 60;
|
|
1092
|
-
const timeStr = mins > 0 ? `${mins}分${secs}秒` : `${secs}秒`;
|
|
1093
|
-
waitTip = `<span style="color:var(--muted);font-size:12px;margin-left:8px;">已等待 ${timeStr}</span>`;
|
|
1094
|
-
}
|
|
1095
|
-
// 分级超时警告:让用户明确感知任务可能卡住,而非无限期"思考中"
|
|
1096
|
-
if (elapsed >= 300 && elapsed < 900) {
|
|
1097
|
-
// 5-15 分钟:橙色温和提醒
|
|
1098
|
-
waitTip += '<div style="color:#e65100;font-size:12px;margin-top:8px;line-height:1.6;background:#fff3e0;padding:8px 10px;border-radius:6px;border-left:3px solid #ff9800;">'
|
|
1099
|
-
+ '⚠️ 已等待较久,模型可能正在处理复杂推理或执行耗时操作。'
|
|
1100
|
-
+ '<br>若长时间无任何输出,可点击右上角「停止」后重新提交。'
|
|
1101
|
-
+ '</div>';
|
|
1102
|
-
} else if (elapsed >= 900 && elapsed < 1500) {
|
|
1103
|
-
// 15-25 分钟:红色明确警告
|
|
1104
|
-
waitTip += '<div style="color:#c62828;font-size:12px;margin-top:8px;line-height:1.6;background:#ffebee;padding:8px 10px;border-radius:6px;border-left:3px solid #e53935;">'
|
|
1105
|
-
+ '🚨 任务可能已卡住(已等待超过15分钟无输出)。'
|
|
1106
|
-
+ '<br>常见原因:模型 API 无响应、网络中断、或工具调用死锁。'
|
|
1107
|
-
+ '<br>建议点击下方按钮停止任务,检查模型配置后重新提交。'
|
|
1108
|
-
+ '<br><button onclick="document.getElementById(\'tchStop\')?.click()" style="margin-top:6px;background:#e53935;color:#fff;border:none;padding:4px 14px;border-radius:5px;cursor:pointer;font-size:12px;">⏹ 停止当前任务</button>'
|
|
1109
|
-
+ '</div>';
|
|
1110
|
-
} else if (elapsed >= 1500) {
|
|
1111
|
-
// 25 分钟以上:紧急警告,提示即将自动超时
|
|
1112
|
-
const remain = Math.max(0, 30 - Math.floor(elapsed / 60));
|
|
1113
|
-
waitTip += '<div style="color:#b71c1c;font-size:12px;margin-top:8px;line-height:1.6;background:#ffcdd2;padding:8px 10px;border-radius:6px;border-left:3px solid #c62828;font-weight:500;">'
|
|
1114
|
-
+ `⛔ 任务即将到达 30 分钟自动超时限制(约 ${remain} 分钟后自动终止)。`
|
|
1115
|
-
+ '<br>可立即点击下方按钮手动停止,或等待系统自动终止。'
|
|
1116
|
-
+ '<br><button onclick="document.getElementById(\'tchStop\')?.click()" style="margin-top:6px;background:#c62828;color:#fff;border:none;padding:4px 14px;border-radius:5px;cursor:pointer;font-size:12px;">⏹ 立即停止任务</button>'
|
|
1117
|
-
+ '</div>';
|
|
1118
|
-
} else if (elapsed > 60) {
|
|
1119
|
-
// 1-5 分钟:普通提示
|
|
1120
|
-
waitTip += '<div style="color:var(--muted);font-size:12px;margin-top:6px;line-height:1.5;">模型正在深度思考或执行复杂操作,请耐心等待...</div>';
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1188
|
+
// 仅显示思考动画,不显示已等待时间
|
|
1123
1189
|
html += '<div class="thinking-indicator">'
|
|
1124
1190
|
+ '<span class="dot"></span><span class="dot"></span><span class="dot"></span>'
|
|
1125
1191
|
+ '<span>思考中...</span>'
|
|
1126
|
-
+ waitTip
|
|
1127
1192
|
+ '</div>';
|
|
1128
1193
|
} else if (task.status === 'queued') {
|
|
1129
1194
|
html += '<div class="msg sys">任务已入队,等待执行…</div>';
|
|
@@ -1845,11 +1910,57 @@
|
|
|
1845
1910
|
document.getElementById('teamTaskCount').textContent = d.stats?.totalTasks || 0;
|
|
1846
1911
|
renderTeamMembers();
|
|
1847
1912
|
renderTeamTasks();
|
|
1913
|
+
renderTeamOverview(d);
|
|
1848
1914
|
} catch (e) {
|
|
1849
1915
|
toast('团队数据加载失败: ' + (e.message || '网络错误'));
|
|
1850
1916
|
}
|
|
1851
1917
|
}
|
|
1852
1918
|
|
|
1919
|
+
// P7-4: 多 agent 协作总览——任务状态漏斗 + 成员负载分布可视化
|
|
1920
|
+
function renderTeamOverview(d) {
|
|
1921
|
+
const box = document.getElementById('teamOverview');
|
|
1922
|
+
if (!box) return;
|
|
1923
|
+
const tasks = d?.team?.tasks || [];
|
|
1924
|
+
const members = d?.team?.members || [];
|
|
1925
|
+
if (!tasks.length) { box.style.display = 'none'; return; }
|
|
1926
|
+
box.style.display = 'block';
|
|
1927
|
+
const statusColors = { todo: '#7f8c8d', 'in-progress': '#3498db', review: '#e67e22', done: '#2d5a3d' };
|
|
1928
|
+
const statusLabels = { todo: '待办', 'in-progress': '进行中', review: '评审', done: '完成' };
|
|
1929
|
+
const order = ['todo', 'in-progress', 'review', 'done'];
|
|
1930
|
+
const counts = {};
|
|
1931
|
+
order.forEach((s) => { counts[s] = 0; });
|
|
1932
|
+
tasks.forEach((t) => { counts[t.status] = (counts[t.status] || 0) + 1; });
|
|
1933
|
+
const max = Math.max(1, ...Object.values(counts));
|
|
1934
|
+
const funnelHtml = '<div style="display:flex;flex-direction:column;gap:3px;margin-bottom:8px;">'
|
|
1935
|
+
+ order.map((s) => {
|
|
1936
|
+
const n = counts[s] || 0;
|
|
1937
|
+
const pct = Math.round((n / max) * 100);
|
|
1938
|
+
return '<div style="display:flex;align-items:center;gap:6px;font-size:10px;">'
|
|
1939
|
+
+ '<span style="width:40px;color:var(--muted);">' + statusLabels[s] + '</span>'
|
|
1940
|
+
+ '<div style="flex:1;background:var(--bg);border-radius:3px;height:12px;overflow:hidden;">'
|
|
1941
|
+
+ '<div style="width:' + pct + '%;height:100%;background:' + statusColors[s] + ';border-radius:3px;"></div></div>'
|
|
1942
|
+
+ '<span style="width:24px;text-align:right;font-weight:600;">' + n + '</span>'
|
|
1943
|
+
+ '</div>';
|
|
1944
|
+
}).join('') + '</div>';
|
|
1945
|
+
// 成员负载:按创建人统计任务数(top 5)
|
|
1946
|
+
const byMember = {};
|
|
1947
|
+
tasks.forEach((t) => { byMember[t.createdBy || '未指派'] = (byMember[t.createdBy || '未指派'] || 0) + 1; });
|
|
1948
|
+
const loadRows = Object.entries(byMember).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, n]) => {
|
|
1949
|
+
const pct = Math.round((n / Math.max(1, ...Object.values(byMember))) * 100);
|
|
1950
|
+
return '<div style="display:flex;align-items:center;gap:6px;font-size:10px;">'
|
|
1951
|
+
+ '<span style="width:70px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' + escapeHtml(name) + '</span>'
|
|
1952
|
+
+ '<div style="flex:1;background:var(--bg);border-radius:3px;height:10px;overflow:hidden;">'
|
|
1953
|
+
+ '<div style="width:' + pct + '%;height:100%;background:#9b59b6;border-radius:3px;"></div></div>'
|
|
1954
|
+
+ '<span style="width:24px;text-align:right;font-weight:600;">' + n + '</span>'
|
|
1955
|
+
+ '</div>';
|
|
1956
|
+
}).join('');
|
|
1957
|
+
box.innerHTML = '<div style="background:var(--card);border:1px solid var(--border);border-radius:6px;padding:8px;">'
|
|
1958
|
+
+ '<div style="font-size:11px;font-weight:600;margin-bottom:6px;">📊 协作总览</div>'
|
|
1959
|
+
+ '<div style="font-size:10px;color:var(--muted);margin-bottom:4px;">任务状态</div>' + funnelHtml
|
|
1960
|
+
+ (loadRows ? '<div style="font-size:10px;color:var(--muted);margin:6px 0 4px;">成员负载(按创建人)</div>' + loadRows : '')
|
|
1961
|
+
+ '</div>';
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1853
1964
|
function renderTeamMembers() {
|
|
1854
1965
|
const container = document.getElementById('teamMembersList');
|
|
1855
1966
|
const members = _teamData?.team?.members || [];
|
|
@@ -1979,3 +2090,128 @@
|
|
|
1979
2090
|
loadTeamData();
|
|
1980
2091
|
} catch (e) { toast('创建失败: ' + (e.message || '网络错误')); }
|
|
1981
2092
|
}
|
|
2093
|
+
|
|
2094
|
+
/* ========== P0-3: 能力中心(v7.2 新能力前端接线)========== */
|
|
2095
|
+
let _capabilitiesBound = false;
|
|
2096
|
+
function bindCapabilitiesOnce() {
|
|
2097
|
+
if (_capabilitiesBound) return;
|
|
2098
|
+
_capabilitiesBound = true;
|
|
2099
|
+
document.getElementById('capVoiceBtn')?.addEventListener('click', runCapabilityVoice);
|
|
2100
|
+
document.getElementById('capVoiceInput')?.addEventListener('keydown', (e) => { if (e.key === 'Enter') runCapabilityVoice(); });
|
|
2101
|
+
document.getElementById('capKnowledgeSearchBtn')?.addEventListener('click', runCapabilityKnowledge);
|
|
2102
|
+
document.getElementById('capKnowledgeQuery')?.addEventListener('keydown', (e) => { if (e.key === 'Enter') runCapabilityKnowledge(); });
|
|
2103
|
+
document.getElementById('capPluginsBtn')?.addEventListener('click', loadCapabilitiesPlugins);
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
async function runCapabilityVoice() {
|
|
2107
|
+
const input = document.getElementById('capVoiceInput');
|
|
2108
|
+
const out = document.getElementById('capVoiceResult');
|
|
2109
|
+
const text = (input?.value || '').trim();
|
|
2110
|
+
if (!text) { if (out) out.textContent = '请输入语音指令文本'; return; }
|
|
2111
|
+
if (out) out.textContent = '解析中…';
|
|
2112
|
+
try {
|
|
2113
|
+
const d = await apiVoiceParse(text);
|
|
2114
|
+
if (!d.ok) { if (out) out.textContent = '解析失败: ' + (d.error || ''); return; }
|
|
2115
|
+
const c = d.command || {};
|
|
2116
|
+
if (out) out.textContent = '→ ' + (c.type || '?') + (c.params && Object.keys(c.params).length ? ' 参数: ' + JSON.stringify(c.params) : '') + ' 置信度: ' + (c.confidence ?? '?');
|
|
2117
|
+
} catch (e) { if (out) out.textContent = '请求失败: ' + (e.message || '网络错误'); }
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
async function runCapabilityKnowledge() {
|
|
2121
|
+
const input = document.getElementById('capKnowledgeQuery');
|
|
2122
|
+
const out = document.getElementById('capKnowledgeResult');
|
|
2123
|
+
const query = (input?.value || '').trim();
|
|
2124
|
+
if (!query) { if (out) out.innerHTML = '<div class="muted">请输入搜索关键词</div>'; return; }
|
|
2125
|
+
if (out) out.innerHTML = '<div class="muted">搜索中…</div>';
|
|
2126
|
+
try {
|
|
2127
|
+
const d = await apiKnowledgeSearch(query);
|
|
2128
|
+
if (!d.ok) { if (out) out.innerHTML = '<div>搜索失败: ' + (d.error || '') + '</div>'; return; }
|
|
2129
|
+
const list = d.results || [];
|
|
2130
|
+
if (!list.length) { if (out) out.innerHTML = '<div class="muted">未找到匹配资料</div>'; return; }
|
|
2131
|
+
if (out) out.innerHTML = list.map((r) => {
|
|
2132
|
+
const t = r.document?.title || r.title || '未命名';
|
|
2133
|
+
const id = r.document?.id || r.id || '';
|
|
2134
|
+
return '<div style="padding:3px 0;border-bottom:1px solid var(--border,#eee);">📄 ' + t +
|
|
2135
|
+
(id ? ' <span style="color:var(--muted);font-size:10px;">' + id.slice(0, 18) + '</span>' : '') + '</div>';
|
|
2136
|
+
}).join('');
|
|
2137
|
+
} catch (e) { if (out) out.innerHTML = '<div>请求失败: ' + (e.message || '网络错误') + '</div>'; }
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
async function loadCapabilitiesPlugins() {
|
|
2141
|
+
const out = document.getElementById('capPluginsResult');
|
|
2142
|
+
if (!out) return;
|
|
2143
|
+
out.innerHTML = '<div class="muted">加载中…</div>';
|
|
2144
|
+
try {
|
|
2145
|
+
const d = await apiPluginsMarket('', '');
|
|
2146
|
+
if (!d.ok) { out.innerHTML = '<div>加载失败: ' + (d.error || '') + '</div>'; return; }
|
|
2147
|
+
const list = d.plugins || [];
|
|
2148
|
+
if (!list.length) { out.innerHTML = '<div class="muted">插件市场为空</div>'; return; }
|
|
2149
|
+
out.innerHTML = list.slice(0, 15).map((p) =>
|
|
2150
|
+
'<div style="padding:3px 0;border-bottom:1px solid var(--border,#eee);">🧩 ' + (p.name || p.id) +
|
|
2151
|
+
' <span style="color:var(--muted);font-size:10px;">v' + (p.version || '?') + '</span>' +
|
|
2152
|
+
'<div style="font-size:10px;color:var(--muted);">' + (p.description || '').slice(0, 40) + '</div></div>'
|
|
2153
|
+
).join('') + (list.length > 15 ? '<div class="muted" style="font-size:10px;margin-top:4px;">…共 ' + list.length + ' 个</div>' : '');
|
|
2154
|
+
} catch (e) { out.innerHTML = '<div>请求失败: ' + (e.message || '网络错误') + '</div>'; }
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
/* ========== P1-1: ghost text 补全(记忆编辑器)========== */
|
|
2158
|
+
let _ghostTimer = null;
|
|
2159
|
+
let _ghostSuggestion = '';
|
|
2160
|
+
let _ghostBound = false;
|
|
2161
|
+
|
|
2162
|
+
function bindGhostTextOnce() {
|
|
2163
|
+
if (_ghostBound) return;
|
|
2164
|
+
_ghostBound = true;
|
|
2165
|
+
const editor = document.getElementById('memLongEditor');
|
|
2166
|
+
const bar = document.getElementById('memGhostBar');
|
|
2167
|
+
if (!editor || !bar) return;
|
|
2168
|
+
editor.addEventListener('input', () => {
|
|
2169
|
+
clearTimeout(_ghostTimer);
|
|
2170
|
+
_ghostTimer = setTimeout(() => requestGhost(editor), 350);
|
|
2171
|
+
});
|
|
2172
|
+
editor.addEventListener('keydown', (e) => {
|
|
2173
|
+
if (e.key === 'Tab' && _ghostSuggestion) {
|
|
2174
|
+
e.preventDefault();
|
|
2175
|
+
acceptGhost(editor);
|
|
2176
|
+
} else if (e.key === 'Escape') {
|
|
2177
|
+
clearGhost();
|
|
2178
|
+
}
|
|
2179
|
+
});
|
|
2180
|
+
editor.addEventListener('blur', () => setTimeout(clearGhost, 250));
|
|
2181
|
+
// 编辑框显示时绑定一次
|
|
2182
|
+
document.getElementById('memLongEditor')?.addEventListener('focus', () => { /* 已绑定 */ });
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
async function requestGhost(editor) {
|
|
2186
|
+
const content = editor.value;
|
|
2187
|
+
const bar = document.getElementById('memGhostBar');
|
|
2188
|
+
if (!content.trim() || content.length < 10) { clearGhost(); return; }
|
|
2189
|
+
try {
|
|
2190
|
+
const d = await apiCompletion('memory-note.md', content, { language: 'markdown', mode: 'quick' });
|
|
2191
|
+
if (d.ok && Array.isArray(d.suggestions) && d.suggestions.length) {
|
|
2192
|
+
const t = d.suggestions[0].text || '';
|
|
2193
|
+
if (t && !content.endsWith(t)) {
|
|
2194
|
+
_ghostSuggestion = t;
|
|
2195
|
+
document.getElementById('memGhostText').textContent = t.slice(0, 200);
|
|
2196
|
+
bar.style.display = 'block';
|
|
2197
|
+
} else { clearGhost(); }
|
|
2198
|
+
} else { clearGhost(); }
|
|
2199
|
+
} catch { clearGhost(); }
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
function acceptGhost(editor) {
|
|
2203
|
+
if (!_ghostSuggestion) return;
|
|
2204
|
+
const cur = editor.selectionStart ?? editor.value.length;
|
|
2205
|
+
const end = editor.selectionEnd ?? cur;
|
|
2206
|
+
editor.value = editor.value.slice(0, cur) + _ghostSuggestion + editor.value.slice(end);
|
|
2207
|
+
const pos = cur + _ghostSuggestion.length;
|
|
2208
|
+
editor.selectionStart = editor.selectionEnd = pos;
|
|
2209
|
+
try { editor.dispatchEvent(new Event('input')); } catch {}
|
|
2210
|
+
clearGhost();
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
function clearGhost() {
|
|
2214
|
+
_ghostSuggestion = '';
|
|
2215
|
+
const bar = document.getElementById('memGhostBar');
|
|
2216
|
+
if (bar) bar.style.display = 'none';
|
|
2217
|
+
}
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* 支持离线缓存、PWA 安装、移动端适配
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const CACHE_NAME = 'feihong-code-
|
|
7
|
-
const STATIC_CACHE = 'feihong-static-
|
|
8
|
-
const RUNTIME_CACHE = 'feihong-runtime-
|
|
6
|
+
const CACHE_NAME = 'feihong-code-v2';
|
|
7
|
+
const STATIC_CACHE = 'feihong-static-v2';
|
|
8
|
+
const RUNTIME_CACHE = 'feihong-runtime-v2';
|
|
9
9
|
|
|
10
10
|
// 静态资源缓存列表
|
|
11
11
|
const STATIC_ASSETS = [
|
|
@@ -60,33 +60,9 @@ self.addEventListener('fetch', (event) => {
|
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
// API
|
|
63
|
+
// API 请求:直接透传,不缓存(API 响应是动态的,缓存会导致公钥/认证/模型配置等数据过期)
|
|
64
64
|
if (url.pathname.startsWith('/api/')) {
|
|
65
|
-
|
|
66
|
-
fetch(request)
|
|
67
|
-
.then((response) => {
|
|
68
|
-
// 缓存成功的 API 响应
|
|
69
|
-
if (response.status === 200) {
|
|
70
|
-
const clone = response.clone();
|
|
71
|
-
caches.open(RUNTIME_CACHE).then((cache) => cache.put(request, clone));
|
|
72
|
-
}
|
|
73
|
-
return response;
|
|
74
|
-
})
|
|
75
|
-
.catch(() => {
|
|
76
|
-
return caches.match(request).then((cached) => {
|
|
77
|
-
if (cached) return cached;
|
|
78
|
-
// 返回离线错误
|
|
79
|
-
return new Response(JSON.stringify({
|
|
80
|
-
ok: false,
|
|
81
|
-
error: '网络连接失败,显示离线数据',
|
|
82
|
-
offline: true,
|
|
83
|
-
}), {
|
|
84
|
-
status: 503,
|
|
85
|
-
headers: { 'Content-Type': 'application/json' },
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
})
|
|
89
|
-
);
|
|
65
|
+
// 安全相关 API 强制不缓存,直接走网络
|
|
90
66
|
return;
|
|
91
67
|
}
|
|
92
68
|
|