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
|
@@ -953,11 +953,12 @@
|
|
|
953
953
|
const box = document.getElementById('messages');
|
|
954
954
|
const el = document.createElement('div');
|
|
955
955
|
el.className = 'msg ' + role;
|
|
956
|
-
|
|
956
|
+
const actionsHtml = (typeof renderMsgActions === 'function') ? renderMsgActions() : '';
|
|
957
|
+
if (extras.html) el.innerHTML = actionsHtml + extras.html;
|
|
957
958
|
else if (extras.file) {
|
|
958
|
-
el.innerHTML = '<span class="file-chip" data-path="' + escapeHtml(extras.file) + '" data-type="' + escapeHtml(extras.mime || 'file') + '">📎 ' + escapeHtml(extras.name) + '</span>';
|
|
959
|
+
el.innerHTML = actionsHtml + '<span class="file-chip" data-path="' + escapeHtml(extras.file) + '" data-type="' + escapeHtml(extras.mime || 'file') + '">📎 ' + escapeHtml(extras.name) + '</span>';
|
|
959
960
|
} else {
|
|
960
|
-
el.innerHTML = linkifyArtifacts(text);
|
|
961
|
+
el.innerHTML = actionsHtml + linkifyArtifacts(text);
|
|
961
962
|
}
|
|
962
963
|
box.appendChild(el);
|
|
963
964
|
box.scrollTop = box.scrollHeight;
|
|
@@ -1178,142 +1179,164 @@
|
|
|
1178
1179
|
});
|
|
1179
1180
|
|
|
1180
1181
|
|
|
1181
|
-
//
|
|
1182
|
-
let
|
|
1182
|
+
// ========== 截图功能(极简重写版) ==========
|
|
1183
|
+
let _screenshotBusy = false;
|
|
1183
1184
|
|
|
1184
1185
|
async function takeScreenshot() {
|
|
1185
|
-
if (
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1186
|
+
if (_screenshotBusy) { toast('正在截图中,按 ESC 可取消'); return; }
|
|
1187
|
+
_screenshotBusy = true;
|
|
1188
|
+
|
|
1189
|
+
const overlay = document.getElementById('cropOverlay');
|
|
1190
|
+
const selection = document.getElementById('cropSelection');
|
|
1191
|
+
let stream = null;
|
|
1192
|
+
let cleanup = null;
|
|
1193
|
+
|
|
1190
1194
|
try {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
let stream = null; // 浏览器模式下的视频流
|
|
1197
|
-
|
|
1198
|
-
// Electron 环境:用主进程截图,更稳定
|
|
1199
|
-
if (window.isElectron && window.electronAPI && window.electronAPI.screenshot) {
|
|
1200
|
-
toast('正在截取屏幕...');
|
|
1201
|
-
const result = await window.electronAPI.screenshot.capture();
|
|
1202
|
-
if (!result || !result.success) {
|
|
1203
|
-
toast('截图失败:' + (result?.error || '未知错误'));
|
|
1204
|
-
return;
|
|
1205
|
-
}
|
|
1206
|
-
// 加载截图为 Image 对象
|
|
1207
|
-
srcImage = new Image();
|
|
1208
|
-
srcImage.src = result.dataUrl;
|
|
1209
|
-
await new Promise((resolve, reject) => {
|
|
1210
|
-
srcImage.onload = resolve;
|
|
1211
|
-
srcImage.onerror = () => reject(new Error('截图加载失败'));
|
|
1212
|
-
});
|
|
1213
|
-
// 把截图显示到 video 元素的位置(用一个 img 覆盖)
|
|
1214
|
-
video.style.display = 'none';
|
|
1215
|
-
let bgImg = document.getElementById('cropBgImg');
|
|
1216
|
-
if (!bgImg) {
|
|
1217
|
-
bgImg = document.createElement('img');
|
|
1218
|
-
bgImg.id = 'cropBgImg';
|
|
1219
|
-
bgImg.style.cssText = 'position:fixed;top:0;left:0;width:100vw;height:100vh;object-fit:cover;z-index:9998;';
|
|
1220
|
-
overlay.appendChild(bgImg);
|
|
1221
|
-
}
|
|
1222
|
-
bgImg.src = result.dataUrl;
|
|
1223
|
-
bgImg.style.display = 'block';
|
|
1224
|
-
} else {
|
|
1225
|
-
// 浏览器模式:用 getDisplayMedia
|
|
1226
|
-
stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
|
|
1227
|
-
video.srcObject = stream;
|
|
1228
|
-
await video.play();
|
|
1229
|
-
video.style.display = 'block';
|
|
1230
|
-
const bgImg = document.getElementById('cropBgImg');
|
|
1231
|
-
if (bgImg) bgImg.style.display = 'none';
|
|
1232
|
-
}
|
|
1195
|
+
// 1. 获取屏幕共享流(请求高分辨率)
|
|
1196
|
+
toast('请选择要截取的屏幕或窗口...');
|
|
1197
|
+
stream = await navigator.mediaDevices.getDisplayMedia({
|
|
1198
|
+
video: { width: { ideal: 3840 }, height: { ideal: 2160 }, frameRate: { ideal: 30 } }
|
|
1199
|
+
});
|
|
1233
1200
|
|
|
1201
|
+
// 2. 用 video 播放流,等待元数据加载完成(确保 videoWidth/Height 有效)
|
|
1202
|
+
const video = document.createElement('video');
|
|
1203
|
+
video.style.display = 'none';
|
|
1204
|
+
video.srcObject = stream;
|
|
1205
|
+
video.muted = true;
|
|
1206
|
+
await new Promise((resolve, reject) => {
|
|
1207
|
+
video.onloadedmetadata = () => { video.play().then(resolve).catch(reject); };
|
|
1208
|
+
video.onerror = () => reject(new Error('视频加载失败'));
|
|
1209
|
+
setTimeout(() => reject(new Error('视频加载超时')), 5000);
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
const vw = video.videoWidth, vh = video.videoHeight;
|
|
1213
|
+
if (!vw || !vh) throw new Error('屏幕画面尺寸无效');
|
|
1214
|
+
|
|
1215
|
+
// 3. 隐藏当前页面,避免截到自己(抓帧后立即恢复)
|
|
1216
|
+
const docEl = document.documentElement;
|
|
1217
|
+
const prevVis = docEl.style.visibility;
|
|
1218
|
+
docEl.style.visibility = 'hidden';
|
|
1219
|
+
await new Promise(r => requestAnimationFrame(r));
|
|
1220
|
+
await new Promise(r => setTimeout(r, 200));
|
|
1221
|
+
|
|
1222
|
+
// 4. canvas 抓帧
|
|
1223
|
+
const capCanvas = document.createElement('canvas');
|
|
1224
|
+
capCanvas.width = vw; capCanvas.height = vh;
|
|
1225
|
+
capCanvas.getContext('2d').drawImage(video, 0, 0, vw, vh);
|
|
1226
|
+
const fullDataUrl = capCanvas.toDataURL('image/png');
|
|
1227
|
+
|
|
1228
|
+
// 5. 停止流 + 恢复页面
|
|
1229
|
+
stream.getTracks().forEach(t => t.stop());
|
|
1230
|
+
stream = null;
|
|
1231
|
+
docEl.style.visibility = prevVis;
|
|
1232
|
+
|
|
1233
|
+
// 6. 加载为 Image 对象供裁剪
|
|
1234
|
+
const srcImg = new Image();
|
|
1235
|
+
srcImg.src = fullDataUrl;
|
|
1236
|
+
await new Promise((resolve, reject) => {
|
|
1237
|
+
srcImg.onload = resolve;
|
|
1238
|
+
srcImg.onerror = () => reject(new Error('截图加载失败'));
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
// 7. 显示裁剪 overlay
|
|
1242
|
+
let bgImg = document.getElementById('cropBgImg');
|
|
1243
|
+
if (!bgImg) {
|
|
1244
|
+
bgImg = document.createElement('img');
|
|
1245
|
+
bgImg.id = 'cropBgImg';
|
|
1246
|
+
bgImg.style.cssText = 'position:fixed;inset:0;width:100vw;height:100vh;object-fit:scale-down;object-position:center;background:#000;z-index:9998;pointer-events:none;';
|
|
1247
|
+
overlay.appendChild(bgImg);
|
|
1248
|
+
}
|
|
1249
|
+
bgImg.src = fullDataUrl;
|
|
1250
|
+
bgImg.style.display = 'block';
|
|
1234
1251
|
overlay.style.display = 'block';
|
|
1235
1252
|
selection.style.display = 'none';
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
const
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
srcW = srcImage.naturalWidth;
|
|
1274
|
-
srcH = srcImage.naturalHeight;
|
|
1275
|
-
source = srcImage;
|
|
1276
|
-
} else {
|
|
1277
|
-
// 浏览器模式:从 video 裁剪
|
|
1278
|
-
srcW = video.videoWidth;
|
|
1279
|
-
srcH = video.videoHeight;
|
|
1280
|
-
source = video;
|
|
1281
|
-
}
|
|
1282
|
-
const scaleX = srcW / window.innerWidth, scaleY = srcH / window.innerHeight;
|
|
1283
|
-
const sx = Math.round(x * scaleX), sy = Math.round(y * scaleY);
|
|
1284
|
-
const sw = Math.round(w * scaleX), sh = Math.round(h * scaleY);
|
|
1285
|
-
const canvas = document.createElement('canvas');
|
|
1286
|
-
canvas.width = sw; canvas.height = sh;
|
|
1287
|
-
canvas.getContext('2d').drawImage(source, sx, sy, sw, sh, 0, 0, sw, sh);
|
|
1288
|
-
if (stream) stream.getTracks().forEach((t) => t.stop());
|
|
1289
|
-
const dataUrl = canvas.toDataURL('image/png');
|
|
1290
|
-
stageFile({ name: 'screenshot.png', mime: 'image/png', dataUrl });
|
|
1291
|
-
toast('截图已添加到输入框,可直接输入文字发送');
|
|
1292
|
-
} catch (err) { toast('截图失败:' + err.message); }
|
|
1293
|
-
screenshotRunning = false;
|
|
1294
|
-
};
|
|
1295
|
-
const onKey = (e) => {
|
|
1296
|
-
if (e.key === 'Escape') {
|
|
1297
|
-
overlay.style.display = 'none';
|
|
1298
|
-
if (stream) stream.getTracks().forEach((t) => t.stop());
|
|
1253
|
+
|
|
1254
|
+
// 8. 等待用户拖拽选择区域
|
|
1255
|
+
const result = await new Promise((resolve) => {
|
|
1256
|
+
let sx = 0, sy = 0, dragging = false;
|
|
1257
|
+
|
|
1258
|
+
const onDown = (e) => {
|
|
1259
|
+
dragging = true;
|
|
1260
|
+
sx = e.clientX; sy = e.clientY;
|
|
1261
|
+
selection.style.display = 'block';
|
|
1262
|
+
selection.style.left = sx + 'px';
|
|
1263
|
+
selection.style.top = sy + 'px';
|
|
1264
|
+
selection.style.width = '0px';
|
|
1265
|
+
selection.style.height = '0px';
|
|
1266
|
+
};
|
|
1267
|
+
const onMove = (e) => {
|
|
1268
|
+
if (!dragging) return;
|
|
1269
|
+
const x = Math.min(e.clientX, sx), y = Math.min(e.clientY, sy);
|
|
1270
|
+
const w = Math.abs(e.clientX - sx), h = Math.abs(e.clientY - sy);
|
|
1271
|
+
selection.style.left = x + 'px';
|
|
1272
|
+
selection.style.top = y + 'px';
|
|
1273
|
+
selection.style.width = w + 'px';
|
|
1274
|
+
selection.style.height = h + 'px';
|
|
1275
|
+
};
|
|
1276
|
+
const onUp = (e) => {
|
|
1277
|
+
if (!dragging) return;
|
|
1278
|
+
dragging = false;
|
|
1279
|
+
const x = Math.min(e.clientX, sx), y = Math.min(e.clientY, sy);
|
|
1280
|
+
const w = Math.abs(e.clientX - sx), h = Math.abs(e.clientY - sy);
|
|
1281
|
+
cleanup();
|
|
1282
|
+
if (w < 10 || h < 10) { resolve(null); return; }
|
|
1283
|
+
resolve({ x, y, w, h });
|
|
1284
|
+
};
|
|
1285
|
+
const onKey = (e) => {
|
|
1286
|
+
if (e.key === 'Escape') { cleanup(); resolve(null); }
|
|
1287
|
+
};
|
|
1288
|
+
|
|
1289
|
+
cleanup = () => {
|
|
1299
1290
|
window.removeEventListener('mousedown', onDown);
|
|
1300
1291
|
window.removeEventListener('mousemove', onMove);
|
|
1301
|
-
window.removeEventListener('mouseup',
|
|
1292
|
+
window.removeEventListener('mouseup', onUp);
|
|
1302
1293
|
window.removeEventListener('keydown', onKey);
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1294
|
+
overlay.style.display = 'none';
|
|
1295
|
+
selection.style.display = 'none';
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
window.addEventListener('mousedown', onDown);
|
|
1299
|
+
window.addEventListener('mousemove', onMove);
|
|
1300
|
+
window.addEventListener('mouseup', onUp);
|
|
1301
|
+
window.addEventListener('keydown', onKey);
|
|
1302
|
+
});
|
|
1303
|
+
|
|
1304
|
+
// 9. 用户取消
|
|
1305
|
+
if (!result) {
|
|
1306
|
+
toast('已取消截图');
|
|
1307
|
+
_screenshotBusy = false;
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// 10. 裁剪选中区域
|
|
1312
|
+
const scaleX = srcImg.naturalWidth / window.innerWidth;
|
|
1313
|
+
const scaleY = srcImg.naturalHeight / window.innerHeight;
|
|
1314
|
+
const cropCanvas = document.createElement('canvas');
|
|
1315
|
+
cropCanvas.width = Math.round(result.w * scaleX);
|
|
1316
|
+
cropCanvas.height = Math.round(result.h * scaleY);
|
|
1317
|
+
cropCanvas.getContext('2d').drawImage(
|
|
1318
|
+
srcImg,
|
|
1319
|
+
Math.round(result.x * scaleX), Math.round(result.y * scaleY),
|
|
1320
|
+
Math.round(result.w * scaleX), Math.round(result.h * scaleY),
|
|
1321
|
+
0, 0, cropCanvas.width, cropCanvas.height
|
|
1322
|
+
);
|
|
1323
|
+
const croppedUrl = cropCanvas.toDataURL('image/png');
|
|
1324
|
+
|
|
1325
|
+
// 11. 添加到输入框
|
|
1326
|
+
stageFile({ name: 'screenshot.png', mime: 'image/png', dataUrl: croppedUrl });
|
|
1327
|
+
toast('截图已添加到输入框');
|
|
1328
|
+
} catch (err) {
|
|
1329
|
+
if (err.name === 'NotAllowedError') {
|
|
1330
|
+
toast('已取消截图。也可用 Win+Shift+S 截图后粘贴');
|
|
1313
1331
|
} else {
|
|
1314
|
-
|
|
1332
|
+
console.error('[screenshot] 失败:', err);
|
|
1333
|
+
toast('截图失败:' + err.message);
|
|
1315
1334
|
}
|
|
1316
|
-
|
|
1335
|
+
if (stream) { try { stream.getTracks().forEach(t => t.stop()); } catch(e){} }
|
|
1336
|
+
if (cleanup) { try { cleanup(); } catch(e){} }
|
|
1337
|
+
else { overlay.style.display = 'none'; selection.style.display = 'none'; }
|
|
1338
|
+
} finally {
|
|
1339
|
+
_screenshotBusy = false;
|
|
1317
1340
|
}
|
|
1318
1341
|
}
|
|
1319
1342
|
|
|
@@ -12,6 +12,22 @@
|
|
|
12
12
|
*/
|
|
13
13
|
(function () {
|
|
14
14
|
if (window.__feihongMockInstalled) return;
|
|
15
|
+
|
|
16
|
+
// ========== 环境检测:仅在安卓离线模式下激活 Mock ==========
|
|
17
|
+
// Web 控制台通过 http://localhost:8080 加载,有真实 Node.js 后端,不应拦截 fetch。
|
|
18
|
+
// 安卓 WebView 通过 file:// 或 content:// 加载本地 HTML,无后端,才需要 Mock。
|
|
19
|
+
var proto = (location.protocol || '').toLowerCase();
|
|
20
|
+
var isAndroid = /android/i.test(navigator.userAgent || '');
|
|
21
|
+
var isLocalFile = proto === 'file:' || proto === 'content:';
|
|
22
|
+
// 仅在「安卓 + 本地文件协议」下激活 Mock;其他情况(Web 控制台、远程服务器)直接放行
|
|
23
|
+
if (!(isAndroid && isLocalFile)) {
|
|
24
|
+
console.log('[MockAPI] 检测到 HTTP 环境,跳过 Mock 拦截(mode=passthrough)');
|
|
25
|
+
window.__feihongMockInstalled = true;
|
|
26
|
+
window.FeiHongApp = window.FeiHongApp || {};
|
|
27
|
+
window.FeiHongApp.isMock = function () { return false; };
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
15
31
|
window.__feihongMockInstalled = true;
|
|
16
32
|
|
|
17
33
|
// ========== 远程服务器配置 ==========
|
|
@@ -70,6 +86,15 @@
|
|
|
70
86
|
return ok({ product: '飞虹 Code', version: '7.0.0', enterprise: true, signature: '安卓版 · 离线演示模式', mode: 'mock', time: new Date().toISOString() });
|
|
71
87
|
}
|
|
72
88
|
|
|
89
|
+
// 安全:加密公钥(Mock 模式下返回固定测试公钥,仅供前端加密流程演示)
|
|
90
|
+
if (lower === '/api/security/public-key') {
|
|
91
|
+
return ok({
|
|
92
|
+
ok: true,
|
|
93
|
+
publicKey: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmock-public-key-for-demo-only\n-----END PUBLIC KEY-----',
|
|
94
|
+
algorithm: 'RSA-OAEP-2048-SHA256'
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
73
98
|
// 模型
|
|
74
99
|
if (lower === '/api/models' && method === 'GET') return ok({ models: MODELS });
|
|
75
100
|
if (lower === '/api/models' && method === 'POST') {
|
|
@@ -10,9 +10,15 @@
|
|
|
10
10
|
let monacoInstance = null;
|
|
11
11
|
let initPromise = null;
|
|
12
12
|
const editors = new Map(); // containerId -> editor instance
|
|
13
|
+
let activeEditor = null; // 最近创建的编辑器(供 lint/诊断反馈定位)
|
|
13
14
|
let inlineCompletionProvider = null;
|
|
14
15
|
let completionItemProvider = null;
|
|
15
16
|
|
|
17
|
+
/** 获取当前活跃编辑器(P6-1 lint 反馈定位) */
|
|
18
|
+
function getActiveEditor() {
|
|
19
|
+
return activeEditor;
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
/** 根据文件扩展名推断 Monaco language id */
|
|
17
23
|
function detectLanguage(filePath) {
|
|
18
24
|
const ext = filePath.split('.').pop().toLowerCase();
|
|
@@ -98,6 +104,7 @@
|
|
|
98
104
|
|
|
99
105
|
const key = el.id || container;
|
|
100
106
|
editors.set(key, editor);
|
|
107
|
+
activeEditor = editor; // P6-1: 记录最近活跃编辑器供 lint 反馈定位
|
|
101
108
|
return editor;
|
|
102
109
|
}
|
|
103
110
|
|
|
@@ -121,11 +128,18 @@
|
|
|
121
128
|
* 注册内联补全 Provider(ghost text,Tab 接受)
|
|
122
129
|
* @param {function} fetchFn - 异步补全函数,参数 {filePath, content, offset},返回 {text: string}
|
|
123
130
|
*/
|
|
124
|
-
|
|
131
|
+
/**
|
|
132
|
+
* 注册行内补全 Provider(ghost text)
|
|
133
|
+
* @param {function} fetchFn - 异步补全函数,参数 {filePath, content, offset, token},返回 {text} 或 null
|
|
134
|
+
* @param {{getActiveEditor?:function, onAccept?:function}} [opts] - P6-1: getActiveEditor 取当前编辑器;onAccept 在补全被接受(插入)时回调插入文本
|
|
135
|
+
*/
|
|
136
|
+
function registerInlineCompletions(fetchFn, opts) {
|
|
137
|
+
const o = opts || {};
|
|
125
138
|
initMonaco().then((monaco) => {
|
|
126
139
|
if (inlineCompletionProvider) {
|
|
127
140
|
inlineCompletionProvider.dispose();
|
|
128
141
|
}
|
|
142
|
+
let lastText = '';
|
|
129
143
|
inlineCompletionProvider = monaco.languages.registerInlineCompletionsProvider('*', {
|
|
130
144
|
provideInlineCompletions: async function (model, position, context, token) {
|
|
131
145
|
try {
|
|
@@ -139,6 +153,7 @@
|
|
|
139
153
|
insertText: result.text,
|
|
140
154
|
filterText: result.text.slice(0, 20),
|
|
141
155
|
};
|
|
156
|
+
lastText = result.text;
|
|
142
157
|
return { items: [item] };
|
|
143
158
|
} catch (e) {
|
|
144
159
|
return { items: [] };
|
|
@@ -146,6 +161,28 @@
|
|
|
146
161
|
},
|
|
147
162
|
freeInlineCompletions: function () {},
|
|
148
163
|
});
|
|
164
|
+
|
|
165
|
+
// P6-1: accept 检测——编辑器内容变化且插入文本匹配最近补全 → onAccept(插入文本)
|
|
166
|
+
const bindAccept = function () {
|
|
167
|
+
const editor = typeof o.getActiveEditor === 'function' ? o.getActiveEditor() : null;
|
|
168
|
+
if (!editor || editor.__fhInlineAcceptBound) return;
|
|
169
|
+
editor.__fhInlineAcceptBound = true;
|
|
170
|
+
editor.onDidChangeModelContent((e) => {
|
|
171
|
+
if (!lastText) return;
|
|
172
|
+
for (const ch of e.changes || []) {
|
|
173
|
+
const ins = ch.text || '';
|
|
174
|
+
if (ins && (ins.startsWith(lastText) || lastText.startsWith(ins))) {
|
|
175
|
+
const accepted = ins.length >= lastText.length ? ins : lastText;
|
|
176
|
+
if (typeof o.onAccept === 'function') {
|
|
177
|
+
try { o.onAccept(accepted); } catch { /* lint 回调失败不阻塞编辑 */ }
|
|
178
|
+
}
|
|
179
|
+
lastText = '';
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
bindAccept();
|
|
149
186
|
});
|
|
150
187
|
}
|
|
151
188
|
|
|
@@ -190,6 +227,126 @@
|
|
|
190
227
|
});
|
|
191
228
|
}
|
|
192
229
|
|
|
230
|
+
/**
|
|
231
|
+
* 注册语义诊断 Provider(P4-2:编辑时错误波浪线)
|
|
232
|
+
* @param {function} fetchFn - 异步诊断函数,参数 {filePath, content},返回 [{line, column, endLine, endColumn, message, severity}]
|
|
233
|
+
* @param {function} [getEditor] - 获取当前编辑器实例(默认遍历已注册编辑器)
|
|
234
|
+
*/
|
|
235
|
+
function registerDiagnostics(fetchFn, getActiveEditor) {
|
|
236
|
+
initMonaco().then((monaco) => {
|
|
237
|
+
// P5-2: 诊断 hover 提示——hover 到有 marker 的行时展示错误/警告详情
|
|
238
|
+
const langSet = [
|
|
239
|
+
'typescript', 'javascript', 'json', 'markdown', 'html', 'css', 'scss', 'less',
|
|
240
|
+
'python', 'java', 'c', 'cpp', 'csharp', 'go', 'rust', 'php', 'ruby', 'swift',
|
|
241
|
+
'kotlin', 'scala', 'shell', 'sql', 'yaml', 'xml', 'protobuf', 'dockerfile',
|
|
242
|
+
'ini', 'plaintext',
|
|
243
|
+
];
|
|
244
|
+
const hoverDisp = monaco.languages.registerHoverProvider(langSet, {
|
|
245
|
+
provideHover(model, position) {
|
|
246
|
+
let editor;
|
|
247
|
+
try { editor = typeof getActiveEditor === 'function' ? getActiveEditor() : null; } catch { editor = null; }
|
|
248
|
+
if (!editor || editor.isDisposed?.()) return null;
|
|
249
|
+
const cur = editor.getModel();
|
|
250
|
+
if (!cur || model.uri.toString() !== cur.uri.toString()) return null;
|
|
251
|
+
const markers = monaco.editor.getModelMarkers({ resource: model.uri })
|
|
252
|
+
.filter((x) => x.startLineNumber <= position.lineNumber && position.lineNumber <= x.endLineNumber);
|
|
253
|
+
if (markers.length === 0) return null;
|
|
254
|
+
const md = markers.map((x) => {
|
|
255
|
+
const sevName = x.severity === monaco.MarkerSeverity.Error ? '错误' : x.severity === monaco.MarkerSeverity.Warning ? '警告' : '提示';
|
|
256
|
+
const icon = x.severity === monaco.MarkerSeverity.Error ? '⛔' : x.severity === monaco.MarkerSeverity.Warning ? '⚠️' : 'ℹ️';
|
|
257
|
+
return '**' + icon + ' ' + sevName + '** (L' + x.startLineNumber + ':' + x.startColumn + ') \n' + x.message;
|
|
258
|
+
}).join('\n\n---\n\n');
|
|
259
|
+
return {
|
|
260
|
+
contents: [{ value: '#### 飞虹 Code 诊断\n\n' + md, isTrusted: true }],
|
|
261
|
+
range: new monaco.Range(position.lineNumber, 1, position.lineNumber, 1),
|
|
262
|
+
};
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
hoverDisp; // 引用保持存活(Monaco 内部维护)
|
|
266
|
+
|
|
267
|
+
// 防抖 + 单飞,避免编辑时高频请求
|
|
268
|
+
let timer = null;
|
|
269
|
+
let inflight = false;
|
|
270
|
+
let queued = false;
|
|
271
|
+
const run = async function () {
|
|
272
|
+
if (inflight) { queued = true; return; }
|
|
273
|
+
const editor = typeof getActiveEditor === 'function' ? getActiveEditor() : null;
|
|
274
|
+
if (!editor || editor.isDisposed?.()) return;
|
|
275
|
+
const model = editor.getModel();
|
|
276
|
+
if (!model) return;
|
|
277
|
+
inflight = true;
|
|
278
|
+
try {
|
|
279
|
+
const filePath = model.uri.path || 'untitled';
|
|
280
|
+
const content = model.getValue();
|
|
281
|
+
const result = await fetchFn({ filePath, content });
|
|
282
|
+
if (!result || !Array.isArray(result.diagnostics)) {
|
|
283
|
+
monaco.editor.setModelMarkers(model, 'fhcode', []);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const markers = result.diagnostics.map((d) => {
|
|
287
|
+
const sev = d.severity === 1 || d.severity === 'error'
|
|
288
|
+
? monaco.MarkerSeverity.Error
|
|
289
|
+
: d.severity === 2 || d.severity === 'warning'
|
|
290
|
+
? monaco.MarkerSeverity.Warning
|
|
291
|
+
: monaco.MarkerSeverity.Info;
|
|
292
|
+
return {
|
|
293
|
+
severity: sev,
|
|
294
|
+
message: d.message || '',
|
|
295
|
+
startLineNumber: d.line ?? 1,
|
|
296
|
+
startColumn: d.column ?? 1,
|
|
297
|
+
endLineNumber: d.endLine ?? (d.line ?? 1),
|
|
298
|
+
endColumn: d.endColumn ?? (d.column ?? 1) + (d.message?.length || 8),
|
|
299
|
+
};
|
|
300
|
+
});
|
|
301
|
+
monaco.editor.setModelMarkers(model, 'fhcode', markers);
|
|
302
|
+
} catch {
|
|
303
|
+
/* 诊断失败静默(不阻塞编辑) */
|
|
304
|
+
} finally {
|
|
305
|
+
inflight = false;
|
|
306
|
+
if (queued) { queued = false; timer = setTimeout(run, 200); }
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
const schedule = function () {
|
|
310
|
+
clearTimeout(timer);
|
|
311
|
+
timer = setTimeout(run, 350);
|
|
312
|
+
};
|
|
313
|
+
// 绑定现有编辑器(createEditor 后由 ui 层调用 attachDiagnostics 触发监听)
|
|
314
|
+
const editor = typeof getActiveEditor === 'function' ? getActiveEditor() : null;
|
|
315
|
+
if (editor && !editor.__fhDiagnosticsBound) {
|
|
316
|
+
editor.__fhDiagnosticsBound = true;
|
|
317
|
+
editor.onDidChangeModelContent(schedule);
|
|
318
|
+
editor.onDidChangeModel(() => schedule());
|
|
319
|
+
}
|
|
320
|
+
return { run, schedule };
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* P6-1: 补全接受后的 lint 反馈——把 /api/lint 结果追加为编辑器波浪线(owner: fhcode-lint)
|
|
326
|
+
* @param {Array} issues - [{severity, line, column, message}]
|
|
327
|
+
*/
|
|
328
|
+
function showLintFeedback(issues) {
|
|
329
|
+
initMonaco().then((monaco) => {
|
|
330
|
+
const editor = activeEditor;
|
|
331
|
+
if (!editor || editor.isDisposed?.()) return;
|
|
332
|
+
const model = editor.getModel();
|
|
333
|
+
if (!model) return;
|
|
334
|
+
if (!Array.isArray(issues) || issues.length === 0) {
|
|
335
|
+
monaco.editor.setModelMarkers(model, 'fhcode-lint', []);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const markers = issues.map((d) => ({
|
|
339
|
+
severity: d.severity === 1 ? monaco.MarkerSeverity.Error : d.severity === 2 ? monaco.MarkerSeverity.Warning : monaco.MarkerSeverity.Info,
|
|
340
|
+
message: '补全校验:' + (d.message || ''),
|
|
341
|
+
startLineNumber: d.line ?? 1,
|
|
342
|
+
startColumn: d.column ?? 1,
|
|
343
|
+
endLineNumber: d.line ?? 1,
|
|
344
|
+
endColumn: (d.column ?? 1) + (d.message?.length || 20),
|
|
345
|
+
}));
|
|
346
|
+
monaco.editor.setModelMarkers(model, 'fhcode-lint', markers);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
193
350
|
/** 暴露全局 API */
|
|
194
351
|
global.FHMonaco = {
|
|
195
352
|
init: initMonaco,
|
|
@@ -197,7 +354,10 @@
|
|
|
197
354
|
getEditor: getEditor,
|
|
198
355
|
disposeEditor: disposeEditor,
|
|
199
356
|
detectLanguage: detectLanguage,
|
|
357
|
+
getActiveEditor: getActiveEditor,
|
|
200
358
|
registerInlineCompletions: registerInlineCompletions,
|
|
201
359
|
registerCompletionItems: registerCompletionItems,
|
|
360
|
+
registerDiagnostics: registerDiagnostics,
|
|
361
|
+
showLintFeedback: showLintFeedback,
|
|
202
362
|
};
|
|
203
363
|
})(window);
|