@sanqianx/project-knowledge 4.7.0 → 4.8.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/.agents/plugins/marketplace.json +17 -17
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +817 -799
- package/README.md +48 -36
- package/_modules/knowledge-engine/README.md +88 -0
- package/_modules/knowledge-engine/package.json +38 -0
- package/_modules/knowledge-engine/src/analysis-contract.js +25 -0
- package/_modules/knowledge-engine/src/analysis-worker.js +54 -0
- package/_modules/knowledge-engine/src/bin.js +52 -0
- package/_modules/knowledge-engine/src/bridge-client.js +24 -0
- package/_modules/knowledge-engine/src/coordinator.js +255 -0
- package/_modules/knowledge-engine/src/headless-analyzer.js +75 -0
- package/_modules/knowledge-engine/src/memories.js +85 -0
- package/_modules/knowledge-engine/src/migration.js +88 -0
- package/_modules/knowledge-engine/src/retrieval.js +116 -0
- package/_modules/knowledge-engine/src/runtime/README.md +7 -0
- package/_modules/knowledge-engine/src/runtime/atomic-file.js +204 -0
- package/_modules/knowledge-engine/src/runtime/automation-config.js +140 -0
- package/_modules/knowledge-engine/src/runtime/commit-conversation-binder.js +249 -0
- package/_modules/knowledge-engine/src/runtime/commit-processing-ledger.js +26 -0
- package/_modules/knowledge-engine/src/runtime/commit-prompt.js +202 -0
- package/_modules/knowledge-engine/src/runtime/commit-reconciler.js +432 -0
- package/_modules/knowledge-engine/src/runtime/content-hash.js +4 -0
- package/_modules/knowledge-engine/src/runtime/contracts.js +279 -0
- package/_modules/knowledge-engine/src/runtime/conversation-exclusions.js +204 -0
- package/_modules/knowledge-engine/src/runtime/conversation-store.js +387 -0
- package/_modules/knowledge-engine/src/runtime/evidence-bundle.js +228 -0
- package/_modules/knowledge-engine/src/runtime/git-runner.js +62 -0
- package/_modules/knowledge-engine/src/runtime/knowledge-promotion.js +444 -0
- package/_modules/knowledge-engine/src/runtime/knowledge-retrieval-service.js +402 -0
- package/_modules/knowledge-engine/src/runtime/layout.js +44 -0
- package/_modules/knowledge-engine/src/runtime/markdown-knowledge-indexer.js +230 -0
- package/_modules/knowledge-engine/src/runtime/scanner.js +298 -0
- package/_modules/knowledge-engine/src/server.js +155 -0
- package/_modules/knowledge-engine/src/store.js +239 -0
- package/_modules/knowledge-engine/ui/app.css +172 -0
- package/_modules/knowledge-engine/ui/app.js +297 -0
- package/_modules/knowledge-engine/ui/index.html +95 -0
- package/_site/README.md +30 -30
- package/_site/_test/ai-profile-resolver-test.js +137 -137
- package/_site/_test/automation-queue-test.js +14 -14
- package/_site/_test/automation-ui-test.js +40 -40
- package/_site/_test/background-task-registry-test.js +43 -43
- package/_site/_test/baseline-schema-test.js +91 -91
- package/_site/_test/chat-claudecodeui-match-test.js +92 -92
- package/_site/_test/claude-executable-discovery-test.js +62 -62
- package/_site/_test/claude-workbench-test.js +170 -170
- package/_site/_test/codex-conversation-projection-test.js +2 -1
- package/_site/_test/commit-conversation-binding-test.js +95 -95
- package/_site/_test/commit-evidence-test.js +110 -110
- package/_site/_test/conversation-api-test.js +109 -109
- package/_site/_test/conversation-store-test.js +136 -136
- package/_site/_test/data-dir-migration-test.js +57 -57
- package/_site/_test/desktop-browser-compat-test.js +28 -28
- package/_site/_test/desktop-hook-runtime-regression-test.js +204 -204
- package/_site/_test/explicit-commit-processor-test.js +37 -37
- package/_site/_test/fixtures/make-git-repos.js +12 -1
- package/_site/_test/folder-picker-output-test.js +19 -19
- package/_site/_test/full-integration-e2e-test.js +165 -165
- package/_site/_test/git-validation-test.js +2 -2
- package/_site/_test/hook-runtime-endpoint-test.js +88 -88
- package/_site/_test/hook-status-repair-api-test.js +152 -152
- package/_site/_test/hook-trigger-test.js +85 -85
- package/_site/_test/import-preflight-api-test.js +234 -233
- package/_site/_test/index-writer-concurrency-test.js +135 -135
- package/_site/_test/integration-adapters-test.js +171 -171
- package/_site/_test/integration-surface-coverage-test.js +164 -164
- package/_site/_test/knowledge-language-control-test.js +170 -170
- package/_site/_test/knowledge-promotion-recovery-test.js +260 -260
- package/_site/_test/knowledge-query-test.js +54 -54
- package/_site/_test/knowledge-retrieval-service-test.js +77 -77
- package/_site/_test/knowledge-storage-startup-test.js +52 -52
- package/_site/_test/knowledge-store-logs-supervision-test.js +89 -89
- package/_site/_test/legacy-forward-compat-test.js +257 -257
- package/_site/_test/legacy-project-upgrade-e2e-test.js +363 -363
- package/_site/_test/logging-api-test.js +89 -89
- package/_site/_test/logging-sse-no-gap-test.js +88 -88
- package/_site/_test/logging-ui-test.js +106 -104
- package/_site/_test/markdown-delta-overlay-test.js +85 -85
- package/_site/_test/markdown-maintenance-api-test.js +75 -75
- package/_site/_test/mcp-server-test.js +149 -149
- package/_site/_test/module-artifact-integrity-test.js +27 -0
- package/_site/_test/module-boundary-test.js +30 -0
- package/_site/_test/module-bridge-eventbridge-test.js +7 -0
- package/_site/_test/module-model-configuration-test.js +43 -0
- package/_site/_test/module-process-lifecycle-test.js +24 -0
- package/_site/_test/module-stream-proxy-test.js +34 -0
- package/_site/_test/non-release-ci-test.js +34 -34
- package/_site/_test/offline-boundary-isolation-test.js +32 -32
- package/_site/_test/p0-data-migration-characterization-test.js +37 -37
- package/_site/_test/p0-e2e-gate-test.js +347 -347
- package/_site/_test/packaged-ui-smoke-test.js +11 -9
- package/_site/_test/path-consistency-test.js +151 -151
- package/_site/_test/pending-sweep-test.js +9 -9
- package/_site/_test/post-commit-automation-test.js +87 -87
- package/_site/_test/product-diagnostics-ui-test.js +75 -0
- package/_site/_test/product-import-ui-test.js +92 -0
- package/_site/_test/project-delete-recovery-test.js +64 -64
- package/_site/_test/project-goal-editor-test.js +144 -144
- package/_site/_test/project-layout-v2-migration-test.js +127 -127
- package/_site/_test/project-lifecycle-transaction-test.js +106 -106
- package/_site/_test/prompt-settings-test.js +115 -115
- package/_site/_test/protected-architecture-gate-test.js +122 -122
- package/_site/_test/refactor-characterization-test.js +36 -36
- package/_site/_test/release-version-sync-test.js +1 -1
- package/_site/_test/requirement-recorder-test.js +173 -173
- package/_site/_test/run-all-tests.js +159 -156
- package/_site/_test/server-runtime-migration-safety-test.js +27 -27
- package/_site/_test/shared-contracts-test.js +58 -58
- package/_site/_test/startup-analysis-disabled-test.js +10 -10
- package/_site/_test/storage-foundation-test.js +78 -78
- package/_site/_test/structured-logger-test.js +101 -101
- package/_site/_test/tracking-start-test.js +108 -108
- package/_site/_test/v4122-upgrade-data-contract-test.js +56 -56
- package/_site/_test/workbench-permission-test.js +117 -117
- package/_site/_test/workspace-ui-contract-test.js +28 -43
- package/_site/lib/ai-profile-resolver.js +78 -78
- package/_site/lib/ai-workspace.js +101 -101
- package/_site/lib/automation-config.js +140 -140
- package/_site/lib/bridge-adapter.js +1 -0
- package/_site/lib/bridge-consumer-service.js +402 -402
- package/_site/lib/claude-cli-runner.js +1510 -1510
- package/_site/lib/commit-conversation-binder.js +234 -234
- package/_site/lib/commit-processing-ledger.js +27 -27
- package/_site/lib/commit-prompt.js +202 -202
- package/_site/lib/commit-reconciler.js +406 -406
- package/_site/lib/contracts.js +284 -279
- package/_site/lib/conversation-query-service.js +144 -144
- package/_site/lib/conversation-store.js +385 -385
- package/_site/lib/data-dir.js +74 -74
- package/_site/lib/data-state-classifier.js +40 -40
- package/_site/lib/engine-cutover.js +97 -0
- package/_site/lib/evidence-bundle.js +228 -228
- package/_site/lib/folder-picker-output.js +21 -21
- package/_site/lib/git-runner.js +62 -62
- package/_site/lib/github-team-store.js +1172 -1172
- package/_site/lib/hook-manager.js +203 -202
- package/_site/lib/index-service.js +173 -173
- package/_site/lib/integration-installer.js +907 -907
- package/_site/lib/kb-framework.js +176 -176
- package/_site/lib/kb-validator.js +124 -124
- package/_site/lib/knowledge-promotion.js +435 -435
- package/_site/lib/knowledge-retrieval-service.js +401 -401
- package/_site/lib/knowledge-tool-runtime.js +359 -359
- package/_site/lib/legacy-data-manifest.js +26 -26
- package/_site/lib/llm-client.js +161 -161
- package/_site/lib/markdown-knowledge-indexer.js +230 -230
- package/_site/lib/migration-service.js +442 -442
- package/_site/lib/module-bridge.js +198 -33
- package/_site/lib/post-commit-automation.js +87 -87
- package/_site/lib/project-lifecycle-service.js +519 -497
- package/_site/lib/project-store.js +268 -262
- package/_site/lib/requirement-recorder.js +278 -278
- package/_site/lib/runtime-endpoint.js +155 -155
- package/_site/lib/scanner.js +298 -298
- package/_site/lib/server-app.js +1590 -1520
- package/_site/lib/settings-store.js +133 -113
- package/_site/lib/storage-layout.js +162 -162
- package/_site/lib/structured-logger.js +574 -574
- package/_site/scripts/folder-picker.ps1 +156 -156
- package/_site/scripts/hook-trigger.js +157 -156
- package/_site/scripts/install-module-candidates.js +69 -0
- package/_site/scripts/pack-module-candidates.js +72 -0
- package/_site/scripts/sync-release-version.js +2 -2
- package/_site/scripts/vendor-modules.js +47 -0
- package/_site/scripts/verify-module-runtime.js +45 -0
- package/_site/scripts/verify-product-runtime.js +34 -0
- package/bin/project-knowledge-mcp.js +194 -194
- package/docs/README.zh-CN.md +34 -25
- package/docs/project-registry-schema.md +22 -22
- package/docs/testing-strategy.md +33 -33
- package/module-runtime-manifest.json +220 -0
- package/package.json +13 -9
- package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.mcp.json +1 -1
- package/plugins/project-knowledge/opencode/project-knowledge.md +3 -3
- package/plugins/project-knowledge/skills/project-knowledge/SKILL.md +28 -28
- package/templates/project-readme.md +34 -34
- package/ui/favicon.svg +38 -38
- package/ui/index.html +51 -149
- package/ui/product.css +9 -0
- package/ui/product.js +386 -0
- package/vendor-manifest.json +46 -0
- package/_site/_test/import-ui-flow-test.js +0 -171
- package/_site/_test/project-control-panel-task14-test.js +0 -63
- package/_site/_test/task15-20-ui-flow-test.js +0 -148
- package/_site/_test/ui-i18n-toggle-test.js +0 -114
- package/_site/_test/ui-smoke-test.js +0 -73
- package/ui/app.css +0 -58
- package/ui/app.js +0 -607
- package/ui/i18n.js +0 -146
package/ui/product.js
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
'use strict';
|
|
3
|
+
const $ = id => document.getElementById(id);
|
|
4
|
+
const all = selector => [...document.querySelectorAll(selector)];
|
|
5
|
+
const titles = { home: '智脑首页', work: '开始工作', search: '知识检索', memory: '长期记忆', projects: '领域与项目', activity: '活动记录' };
|
|
6
|
+
const modules = { terminal: 'Agent 工作台', vectorHub: '知识索引', eventBridge: '开发事件', engine: '知识引擎' };
|
|
7
|
+
const frames = {
|
|
8
|
+
terminal: { id: 'workFrame', url: '/modules/terminal/agent-terminal/?embed=1&apiBase=/api/terminal', view: 'work', ready: false },
|
|
9
|
+
vectorHub: { id: 'vectorFrame', url: '/modules/vectorhub/?embed=1&apiBase=/api/vectorhub&engineBase=/api/engine', view: 'search', ready: false },
|
|
10
|
+
eventBridge: { id: 'bridgeFrame', url: '/modules/eventbridge/?embed=1&apiBase=/api/eventbridge', view: 'activity', ready: false },
|
|
11
|
+
engine: { id: 'engineFrame', url: '/modules/engine/?embed=1&apiBase=/api/engine', ready: false },
|
|
12
|
+
};
|
|
13
|
+
const state = { brain: { id: 'brain-default', name: '我的智脑', domains: [] }, root: '', projects: [], projectId: localStorage.getItem('kb.project') || '', view: 'home', scope: 'project', memoryTab: 'confirmed', activityTab: 'updates', health: {}, snapshot: { projects: [], runs: [], events: [], memories: [] }, engineAvailable: false, epoch: 0, contexts: new Map(), stream: null, refreshing: false, importId: '', preflight: null, preflightEpoch: 0 };
|
|
14
|
+
const esc = value => String(value ?? '').replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
15
|
+
const activeProject = () => state.projects.find(p => p.projectId === state.projectId);
|
|
16
|
+
const domainName = id => state.brain.domains.find(d => d.id === id)?.name || '未分组';
|
|
17
|
+
const projectName = id => state.projects.find(p => p.projectId === id)?.name || id;
|
|
18
|
+
const time = value => value ? new Date(value).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : '—';
|
|
19
|
+
const label = run => ({ queued: '排队', running: '维护中', succeeded: '已更新 · 可检索', 'index-pending': '知识已更新 · 索引待修复', failed: '维护失败' })[run.state] || run.state;
|
|
20
|
+
const badge = (text, status = '') => `<span class="status ${esc(status)}">${esc(text)}</span>`;
|
|
21
|
+
let toastTimer, refreshTimer, preflightTimer, editingMemoryId, logStream, logEpoch = 0, referenceEpoch = 0, snapshotEpoch = 0, resumeImport = false;
|
|
22
|
+
async function api(url, options = {}) {
|
|
23
|
+
const { allowNotReady, ...init } = options;
|
|
24
|
+
if (init.body && typeof init.body !== 'string') init.body = JSON.stringify(init.body);
|
|
25
|
+
if (init.body) init.headers = { 'Content-Type': 'application/json', ...init.headers };
|
|
26
|
+
const response = await fetch(url, init);
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
if (!response.ok || data.ok === false && !allowNotReady) throw Object.assign(new Error(data.error?.message || data.message || (typeof data.error === 'string' ? data.error : `请求失败 (${response.status})`)), { code: data.error?.code || data.error });
|
|
29
|
+
return data;
|
|
30
|
+
}
|
|
31
|
+
function toast(text) { $('toast').textContent = text; $('toast').hidden = false; clearTimeout(toastTimer); toastTimer = setTimeout(() => { $('toast').hidden = true; }, 4000); }
|
|
32
|
+
function on(id, event, callback) { $(id).addEventListener(event, e => Promise.resolve(callback(e)).catch(error => toast(error.message))); }
|
|
33
|
+
function post(key, message) { const frame = frames[key]; if (frame.ready) $(frame.id).contentWindow.postMessage(message, location.origin); }
|
|
34
|
+
function initializeFrame(key) { const frame = frames[key]; if (!frame.initialized) { $(frame.id).src = frame.url; frame.initialized = true; } }
|
|
35
|
+
function frameVisible(key) { return key === 'engine' ? $('engineDialog').open : state.view === frames[key].view && (key !== 'eventBridge' || state.activityTab === 'sessions'); }
|
|
36
|
+
function frameContext(key) {
|
|
37
|
+
const project = activeProject();
|
|
38
|
+
const frame = frames[key];
|
|
39
|
+
const ref = key === 'terminal' ? project?.modules.terminal.projectId || project?.workspacePath : key === 'eventBridge' ? project?.modules.eventBridge.projectId || project?.workspacePath : project?.projectId;
|
|
40
|
+
frame.requestId = `${state.epoch}:${key}`;
|
|
41
|
+
post(key, { type: 'kb:theme', theme: document.documentElement.dataset.theme });
|
|
42
|
+
post(key, { type: 'kb:scope', scope: state.scope });
|
|
43
|
+
post(key, { type: 'kb:sidebar', visible: key === 'terminal' });
|
|
44
|
+
post(key, { type: 'kb:select-project', project: ref || '', projectId: project?.projectId || '', requestId: frame.requestId });
|
|
45
|
+
post(key, { type: 'kb:visibility', visible: frameVisible(key) });
|
|
46
|
+
}
|
|
47
|
+
function setView(view) {
|
|
48
|
+
if (!titles[view]) view = 'home';
|
|
49
|
+
state.view = view;
|
|
50
|
+
history.replaceState(null, '', `#${view}`);
|
|
51
|
+
all('[data-view]').forEach(button => button.classList.toggle('active', button.dataset.view === view));
|
|
52
|
+
all('.view').forEach(section => section.classList.toggle('active', section.id === `view-${view}`));
|
|
53
|
+
$('pageTitle').textContent = titles[view];
|
|
54
|
+
if (view === 'work' && activeProject()) initializeFrame('terminal');
|
|
55
|
+
if (view === 'search' && activeProject()) initializeFrame('vectorHub');
|
|
56
|
+
if (view === 'activity' && state.activityTab === 'sessions' && activeProject()) initializeFrame('eventBridge');
|
|
57
|
+
Object.keys(frames).forEach(key => post(key, { type: 'kb:visibility', visible: frameVisible(key) }));
|
|
58
|
+
render();
|
|
59
|
+
}
|
|
60
|
+
function selectProject(id) {
|
|
61
|
+
if (id === state.projectId && activeProject()) return;
|
|
62
|
+
state.projectId = id; state.epoch++;
|
|
63
|
+
referenceEpoch++; $('referenceDialog').close();
|
|
64
|
+
if ($('logsDialog').open) loadLogs().catch(error => toast(error.message));
|
|
65
|
+
localStorage.setItem('kb.project', id);
|
|
66
|
+
$('projectSelect').value = id;
|
|
67
|
+
Object.keys(frames).forEach(frameContext);
|
|
68
|
+
render(); setView(state.view);
|
|
69
|
+
}
|
|
70
|
+
function renderProjectOptions() {
|
|
71
|
+
$('projectSelect').innerHTML = state.projects.length ? state.projects.map(p => `<option value="${esc(p.projectId)}">${esc(p.name)}</option>`).join('') : '<option value="">尚未导入项目</option>';
|
|
72
|
+
$('projectSelect').disabled = !state.projects.length;
|
|
73
|
+
$('projectSelect').value = state.projectId;
|
|
74
|
+
}
|
|
75
|
+
function render() {
|
|
76
|
+
const project = activeProject();
|
|
77
|
+
$('brainLabel').textContent = state.brain.name; $('homeTitle').textContent = state.brain.name;
|
|
78
|
+
$('breadcrumb').textContent = project ? `${state.brain.name} / ${domainName(project.domainId)} / ${project.name}` : state.brain.name;
|
|
79
|
+
$('workEmpty').hidden = !!project; $('workLayout').hidden = !project;
|
|
80
|
+
$('searchEmpty').hidden = !!project; $('vectorFrame').parentElement.hidden = !project;
|
|
81
|
+
const candidates = state.snapshot.memories.filter(m => m.status === 'candidate');
|
|
82
|
+
$('memoryCount').textContent = candidates.length; $('memoryCount').hidden = !candidates.length; $('candidateCount').textContent = candidates.length || '';
|
|
83
|
+
if (state.view === 'home') renderHome(candidates);
|
|
84
|
+
if (state.view === 'projects') renderProjects();
|
|
85
|
+
if (state.view === 'memory') renderMemories();
|
|
86
|
+
if (state.view === 'activity') renderActivity();
|
|
87
|
+
renderContext();
|
|
88
|
+
}
|
|
89
|
+
function renderHome(candidates) {
|
|
90
|
+
if (!state.projects.length) { $('homeContent').innerHTML = '<div class="empty"><h2>从第一个项目开始</h2><p>选择代码目录和知识目录,保留项目原有文件。</p><button class="primary" data-action="import">导入第一个项目</button></div>'; return; }
|
|
91
|
+
const runs = state.snapshot.runs.filter(r => !state.projectId || r.projectId === state.projectId);
|
|
92
|
+
const issues = runs.filter(r => ['failed', 'index-pending'].includes(r.state));
|
|
93
|
+
const scopeIssues = (state.snapshot.sharedScopes || []).filter(s => s.brainId === state.brain.id && s.index?.status === 'pending');
|
|
94
|
+
const projectRows = state.projects.map(p => `<div class="row"><div><b>${esc(p.name)}</b><small>${esc(domainName(p.domainId))}</small></div>${badge(Object.values(p.modules).every(m => m.registered) ? '已连接' : '接线待恢复', Object.values(p.modules).every(m => m.registered) ? 'up' : 'issue')}<button data-select-project="${esc(p.projectId)}">打开</button></div>`).join('');
|
|
95
|
+
const updates = runs.slice(0, 5).map(r => `<div class="row"><time>${time(r.startedAt)}</time><div><b>${esc(projectName(r.projectId))}</b><small>${esc(r.commitSha.slice(0, 8))} · ${esc(label(r))}</small></div></div>`).join('') || '<p>尚无维护记录。导入时不会分析历史提交。</p>';
|
|
96
|
+
$('homeContent').innerHTML = `<div class="grid2"><div class="stack"><section class="panel"><h2>领域与项目</h2>${projectRows}</section><section class="panel"><h2>最近知识更新</h2>${state.engineAvailable ? updates : '<p>知识引擎暂时不可用,恢复后显示真实记录。</p>'}</section></div><div class="stack"><section class="panel"><h2>待确认记忆</h2>${candidates.length ? candidates.slice(0, 3).map(m => `<div class="row"><div><b>${esc(m.text)}</b><small>${esc(projectName(m.evidence.projectId))} · ${esc(m.evidence.commitSha.slice(0, 8))}</small></div></div>`).join('') + '<button data-go="memory">查看候选箱</button>' : '<p>没有待确认记忆。智脑不会自动把项目行为当作你的偏好。</p>'}</section><section class="panel"><h2>需要处理</h2>${issues.length ? issues.slice(0, 3).map(r => `<div class="row"><div><b>${esc(projectName(r.projectId))}</b><small>${esc(r.error?.message || label(r))}</small></div></div>`).join('') + '<button data-go="activity">查看运行详情</button>' : scopeIssues.length ? '' : '<p>没有待处理的维护异常。</p>'}${scopeIssues.map(s => `<div class="row"><div><b>${esc(s.name)}</b><small>${esc(s.index.message)}</small></div></div>`).join('')}${scopeIssues.length ? '<button data-action="system">检查索引连接</button>' : ''}${!state.engineAvailable ? '<p>知识引擎离线,请检查系统状态。</p>' : ''}</section></div></div>`;
|
|
97
|
+
}
|
|
98
|
+
function renderProjects() {
|
|
99
|
+
const group = (id, name) => `<h3>${esc(name)}</h3>${state.projects.filter(p => (p.domainId || '') === id).map(p => `<button class="${p.projectId === state.projectId ? 'active' : ''}" data-select-project="${esc(p.projectId)}">${esc(p.name)}</button>`).join('') || '<small>暂无项目</small>'}`;
|
|
100
|
+
$('projectTree').innerHTML = `<h2>${esc(state.brain.name)}</h2>${state.brain.domains.map(d => group(d.id, d.name)).join('')}${group('', '未分组')}`;
|
|
101
|
+
const project = activeProject();
|
|
102
|
+
if (!project) { $('projectDetail').innerHTML = '<div class="empty"><h2>尚未导入项目</h2><button class="primary" data-action="import">导入第一个项目</button></div>'; return; }
|
|
103
|
+
$('projectDetail').innerHTML = `<h2>${esc(project.name)}</h2><div class="detail-label">代码目录</div><div class="detail-path">${esc(project.workspacePath)}</div><div class="detail-label">知识目录</div><div class="detail-path">${esc(project.knowledgePath)}</div><label>所属领域<select id="projectDomain"><option value="">未分组</option>${state.brain.domains.map(d => `<option value="${esc(d.id)}" ${d.id === project.domainId ? 'selected' : ''}>${esc(d.name)}</option>`).join('')}</select></label><h3>项目连接</h3>${Object.entries(modules).map(([key, name]) => `<div class="row"><div>${esc(name)}</div>${badge(project.modules[key]?.registered ? '已连接' : '待恢复', project.modules[key]?.registered ? 'up' : 'issue')}</div>`).join('')}<div class="actions"><button class="primary" data-go="work">开始工作</button><button data-action="repair">修复接线</button><button data-action="unbind">解绑并保留数据</button></div>`;
|
|
104
|
+
}
|
|
105
|
+
function renderMemories() {
|
|
106
|
+
all('[data-memory-tab]').forEach(button => button.classList.toggle('active', button.dataset.memoryTab === state.memoryTab));
|
|
107
|
+
const memories = state.snapshot.memories.filter(m => m.status === state.memoryTab);
|
|
108
|
+
$('memoryContent').innerHTML = !state.engineAvailable ? '<div class="empty">知识引擎暂时不可用。未读取到的记忆不会显示为已确认。</div>' : memories.length ? `<div class="memory-grid">${memories.map(m => `<article class="memory-card"><p>${esc(m.text)}</p><small>${esc(projectName(m.evidence.projectId))} · commit ${esc(m.evidence.commitSha.slice(0, 8))}<br>${esc(m.evidence.entryId)}</small><div class="actions"><button data-memory-action="evidence" data-memory-id="${esc(m.id)}">查看依据</button><button data-memory-action="edit" data-memory-id="${esc(m.id)}">编辑</button>${m.status === 'candidate' ? `<button class="primary" data-memory-action="confirm" data-memory-id="${esc(m.id)}">确认</button><button data-memory-action="ignore" data-memory-id="${esc(m.id)}">忽略</button>` : badge('已确认', 'up')}</div></article>`).join('')}</div>` : `<div class="empty">${state.memoryTab === 'confirmed' ? '还没有已确认的长期记忆。' : '候选箱为空。维护产物中的有据偏好会进入这里,等待你的确认。'}</div>`;
|
|
109
|
+
}
|
|
110
|
+
function renderActivity() {
|
|
111
|
+
all('[data-activity-tab]').forEach(button => button.classList.toggle('active', button.dataset.activityTab === state.activityTab));
|
|
112
|
+
$('activityUpdates').hidden = state.activityTab !== 'updates'; $('activitySessions').hidden = state.activityTab !== 'sessions';
|
|
113
|
+
const runs = state.snapshot.runs.filter(r => r.projectId === state.projectId);
|
|
114
|
+
const open = new Set(all('.run[open]').map(d => d.dataset.runId));
|
|
115
|
+
$('activityUpdates').innerHTML = state.engineAvailable ? runs.map(r => `<details class="run" data-run-id="${esc(r.runId)}" ${open.has(r.runId) ? 'open' : ''}><summary><time>${time(r.startedAt)}</time><strong>${esc(projectName(r.projectId))} · ${esc(r.commitSha.slice(0, 8))}</strong>${badge(label(r), ['failed', 'index-pending'].includes(r.state) ? 'issue' : r.state === 'succeeded' ? 'up' : '')}</summary><div><p>提交:${esc(r.commitSha)}<br>阶段:${esc(r.phase)} · 入口:${esc(r.trigger)}<br>知识晋升:${time(r.promotedAt)} · 索引:${esc(r.indexState)}</p>${r.error ? `<p class="error">${esc(r.error.message)} (${esc(r.error.code)})</p>` : ''}${(r.staging?.operations || []).map(op => `<div class="operation">${esc(op.operation)} · ${esc(op.path)}<br>${esc(op.reason)}</div>`).join('')}${['failed', 'index-pending'].includes(r.state) ? `<button data-retry-run="${esc(r.runId)}">重试失败阶段</button>` : ''}</div></details>`).join('') || '<div class="empty">当前项目还没有知识维护记录。</div>' : '<div class="empty">知识引擎离线。记录仍保留在本机,连接恢复后自动读取。</div>';
|
|
116
|
+
}
|
|
117
|
+
function renderContext() {
|
|
118
|
+
const saved = state.contexts.get(state.projectId);
|
|
119
|
+
const context = saved?.status === 'not-retrieved' ? null : saved;
|
|
120
|
+
$('contextScope').textContent = context ? ({ ready: '已检索知识', 'file-fallback': '索引暂不可用,已检索原文件', unavailable: '知识服务不可用,未注入上下文', unregistered: '项目尚未登记知识库', disabled: '未启用知识连接' })[context.status] || '本次任务上下文' : '任务发送前自动检索';
|
|
121
|
+
$('contextContent').classList.toggle('small-empty', !context?.references?.length);
|
|
122
|
+
const seen = new Set();
|
|
123
|
+
const references = (context?.references || []).map((r, index) => ({ ...r, index })).filter(r => { const key = `${r.spaceId}:${r.projectId}:${r.entryId}:${r.commitSha || ''}`; if (seen.has(key)) return false; seen.add(key); return true; });
|
|
124
|
+
$('contextContent').classList.toggle('empty', !references.length);
|
|
125
|
+
$('contextContent').innerHTML = references.length ? references.map(r => `<button class="reference" data-context-index="${r.index}"><b>${esc(r.title)}</b><small>${esc(projectName(r.projectId))} / ${esc(r.entryId)}${r.commitSha ? ` · ${esc(r.commitSha.slice(0, 8))}` : ''}</small><p>${esc(r.text.slice(0, 180))}</p></button>`).join('') : context ? '本次任务未找到可引用的知识。' : '发送任务后显示实际检索到的项目事实、领域经验和已确认记忆。';
|
|
126
|
+
}
|
|
127
|
+
async function openReference(reference) {
|
|
128
|
+
const id = state.projectId;
|
|
129
|
+
const epoch = ++referenceEpoch;
|
|
130
|
+
$('referenceTitle').textContent = reference.title || reference.entryId;
|
|
131
|
+
$('referenceMeta').textContent = `${projectName(reference.projectId)} / ${reference.entryId}${reference.commitSha ? ` · commit ${reference.commitSha}` : ''}`;
|
|
132
|
+
$('referenceContent').textContent = reference.text || '读取知识文件…'; $('referenceDialog').showModal();
|
|
133
|
+
if (reference.entryId) {
|
|
134
|
+
const result = await api('/api/engine/get', { method: 'POST', body: { projectId: state.projects.some(p => p.projectId === reference.projectId) ? reference.projectId : id, spaceId: reference.spaceId, entryId: reference.entryId } });
|
|
135
|
+
if (epoch === referenceEpoch && id === state.projectId && $('referenceDialog').open) $('referenceContent').textContent = result.chunks.map(c => c.chunk_text).join('\n');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function refreshSnapshot() {
|
|
139
|
+
const epoch = ++snapshotEpoch;
|
|
140
|
+
try { const snapshot = await api('/api/engine/snapshot'); if (epoch !== snapshotEpoch || !navigator.onLine) return; state.snapshot = snapshot; state.engineAvailable = true; }
|
|
141
|
+
catch { if (epoch !== snapshotEpoch) return; state.engineAvailable = false; }
|
|
142
|
+
render();
|
|
143
|
+
}
|
|
144
|
+
function scheduleRefresh() { if (!navigator.onLine) return; clearTimeout(refreshTimer); refreshTimer = setTimeout(() => { refreshSnapshot(); post('eventBridge', { type: 'kb:refresh' }); }, 160); }
|
|
145
|
+
function connectProgress() {
|
|
146
|
+
if (!navigator.onLine || !state.health.engine?.up || state.stream) return;
|
|
147
|
+
const stream = new EventSource('/api/engine/progress'); state.stream = stream;
|
|
148
|
+
stream.addEventListener('snapshot', () => { if (state.stream !== stream) return; $('activityConnection').textContent = '已连接实时更新'; scheduleRefresh(); });
|
|
149
|
+
stream.addEventListener('progress', () => { if (state.stream === stream) scheduleRefresh(); });
|
|
150
|
+
stream.onerror = () => { if (state.stream === stream) $('activityConnection').textContent = '连接中断,正在恢复快照'; };
|
|
151
|
+
}
|
|
152
|
+
async function refreshRegistry() {
|
|
153
|
+
if (!navigator.onLine || state.refreshing) return;
|
|
154
|
+
state.refreshing = true;
|
|
155
|
+
try {
|
|
156
|
+
const [brain, projects, health] = await Promise.all([api('/api/brain'), api('/api/projects/aggregated'), api('/api/modules/health')]);
|
|
157
|
+
state.brain = brain.brain; state.root = brain.knowledgeRoot; state.projects = projects.projects; state.health = health.modules; state.ownership = health.ownership;
|
|
158
|
+
if (!activeProject()) { state.projectId = state.projects[0]?.projectId || ''; state.epoch++; Object.keys(frames).forEach(frameContext); }
|
|
159
|
+
renderProjectOptions(); renderSystem(); connectProgress(); await refreshSnapshot(); setView(state.view);
|
|
160
|
+
} catch (error) { toast(error.message); }
|
|
161
|
+
finally { state.refreshing = false; connectProgress(); }
|
|
162
|
+
}
|
|
163
|
+
function renderSystem() {
|
|
164
|
+
const down = Object.keys(modules).filter(key => !state.health[key]?.up);
|
|
165
|
+
const pending = state.ownership?.owner === 'engine' && !state.ownership.ready;
|
|
166
|
+
$('systemDot').className = `dot ${down.length || pending ? 'issue' : 'up'}`;
|
|
167
|
+
$('systemTitle').textContent = pending ? '知识引擎接管待恢复' : down.length ? '部分服务需要处理' : '系统连接正常';
|
|
168
|
+
$('systemDetail').textContent = pending ? state.ownership.message || '迁移未完成,原知识与提交证据保留。' : down.length ? down.map(k => modules[k]).join('、') : '本机知识服务已连接';
|
|
169
|
+
$('moduleHealth').innerHTML = Object.entries(modules).map(([key, name]) => `<div class="row"><div><b>${esc(name)}</b><small>${esc(state.health[key]?.url || '')}</small></div>${badge(state.health[key]?.up ? '在线' : '离线', state.health[key]?.up ? 'up' : 'issue')}</div>`).join('');
|
|
170
|
+
if (!navigator.onLine) {
|
|
171
|
+
$('systemDot').className = 'dot issue'; $('systemTitle').textContent = '网页连接中断';
|
|
172
|
+
$('systemDetail').textContent = '恢复连接后自动读取状态,后台维护不受影响。';
|
|
173
|
+
$('moduleHealth').innerHTML = '<p>当前网页无法确认服务状态,连接恢复后重新读取。</p>';
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function openSystem() {
|
|
177
|
+
$('systemDrawer').hidden = false; $('drawerBackdrop').hidden = false;
|
|
178
|
+
$('brainName').value = state.brain.name; $('knowledgeRoot').value = state.root;
|
|
179
|
+
await loadMaintenance();
|
|
180
|
+
}
|
|
181
|
+
async function loadLogs() {
|
|
182
|
+
const epoch = ++logEpoch;
|
|
183
|
+
logStream?.close(); logStream = null;
|
|
184
|
+
const params = new URLSearchParams({ pageSize: '100', q: $('logsSearch').value.trim() });
|
|
185
|
+
if (state.projectId) params.set('projectId', state.projectId);
|
|
186
|
+
const result = await api(`/api/logs?${params}`);
|
|
187
|
+
if (epoch !== logEpoch || !$('logsDialog').open) return;
|
|
188
|
+
$('logsDialog').dataset.streamCursor = result.streamCursor || '';
|
|
189
|
+
$('logsContent').replaceChildren();
|
|
190
|
+
for (const record of [...(result.entries || [])].reverse()) appendLog(record);
|
|
191
|
+
$('logsStatus').textContent = `${(result.entries || []).length} 条诊断记录 · 当前项目`;
|
|
192
|
+
if (!$('logsSearch').value.trim()) {
|
|
193
|
+
const streamParams = new URLSearchParams({ streamCursor: result.streamCursor || '' });
|
|
194
|
+
if (state.projectId) streamParams.set('projectId', state.projectId);
|
|
195
|
+
logStream = new EventSource(`/api/logs/stream?${streamParams}`);
|
|
196
|
+
logStream.addEventListener('logs/appended', event => { if (epoch === logEpoch && $('logsDialog').open) appendLog(JSON.parse(event.data).record); });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function appendLog(record) {
|
|
200
|
+
const row = document.createElement('details'); row.className = 'log-entry';
|
|
201
|
+
row.dataset.level = record.level;
|
|
202
|
+
const summary = document.createElement('summary'); summary.className = 'row';
|
|
203
|
+
const date = document.createElement('time'); date.textContent = time(record.ts || record.at);
|
|
204
|
+
const text = document.createElement('pre'); text.textContent = record.message || '';
|
|
205
|
+
const detail = document.createElement('pre'); detail.className = 'log-detail';
|
|
206
|
+
detail.textContent = JSON.stringify(record, null, 2);
|
|
207
|
+
summary.append(date, text); row.append(summary, detail); $('logsContent').append(row);
|
|
208
|
+
while ($('logsContent').childElementCount > 100) $('logsContent').firstElementChild.remove();
|
|
209
|
+
}
|
|
210
|
+
function closeSystem() {
|
|
211
|
+
$('systemDrawer').hidden = true; $('drawerBackdrop').hidden = true;
|
|
212
|
+
if (resumeImport) {
|
|
213
|
+
resumeImport = false;
|
|
214
|
+
$('importDialog').showModal();
|
|
215
|
+
checkImport().catch(error => toast(error.message));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async function loadMaintenance() {
|
|
219
|
+
try {
|
|
220
|
+
const [profiles, saved] = await Promise.all([api('/api/terminal/profiles'), api('/api/terminal/maintenance-default')]);
|
|
221
|
+
const list = Array.isArray(profiles) ? profiles : profiles.profiles || [];
|
|
222
|
+
$('maintenanceModel').innerHTML = '<option value="">尚未选择后台默认模型</option>' + list.filter(p => p.enabled && p.credentialConfigured).map(p => `<option value="${esc(p.id)}">${esc(p.name)} · ${esc(p.models.default)}</option>`).join('');
|
|
223
|
+
$('maintenanceModel').value = saved.profileId || '';
|
|
224
|
+
$('configurationStatus').textContent = saved.profileId ? '此默认模型仅用于后台维护。' : '先配置供应商,再选择后台默认模型。';
|
|
225
|
+
const embedding = await api('/api/terminal/embedding-config');
|
|
226
|
+
$('embeddingStatus').textContent = embedding.configured ? `已配置 · ${embedding.config.model}` : '未配置,使用文本检索降级。';
|
|
227
|
+
const capture = (await api('/api/terminal/health')).conversationCapture;
|
|
228
|
+
$('captureStatus').textContent = !capture ? '工作台对话接线尚未启用。' : capture.error ? `工作台对话待恢复 · ${capture.pending} 条事件保留在本机,请检查接线。` : capture.pending ? `工作台对话正在同步 · ${capture.pending} 条待写入。` : capture.enabled ? '工作台对话自动保存到开发会话记录。' : '工作台对话捕获已关闭。';
|
|
229
|
+
} catch { $('maintenanceModel').innerHTML = '<option value="">工作台暂时不可用</option>'; $('configurationStatus').textContent = '无法读取模型配置,请恢复工作台服务。'; }
|
|
230
|
+
}
|
|
231
|
+
async function openEmbedding() {
|
|
232
|
+
const [profiles, embedding] = await Promise.all([api('/api/terminal/profiles'), api('/api/terminal/embedding-config')]);
|
|
233
|
+
const list = Array.isArray(profiles) ? profiles : profiles.profiles || [];
|
|
234
|
+
$('embeddingProfile').innerHTML = '<option value="">独立凭据</option>' + list.filter(p => p.enabled && p.credentialConfigured).map(p => `<option value="${esc(p.id)}">${esc(p.name)}</option>`).join('');
|
|
235
|
+
const saved = embedding.config || {};
|
|
236
|
+
$('embeddingProfile').value = saved.profileId || '';
|
|
237
|
+
$('embeddingProvider').value = saved.provider || 'minimax';
|
|
238
|
+
$('embeddingModel').value = saved.model || 'embo-01';
|
|
239
|
+
$('embeddingEndpoint').value = saved.endpoint || '';
|
|
240
|
+
$('embeddingSecret').value = '';
|
|
241
|
+
$('embeddingSecretLabel').hidden = !!saved.profileId;
|
|
242
|
+
$('embeddingFeedback').textContent = embedding.configured ? '配置已保存。留空凭据不会覆盖现有密钥。' : '向量检索尚未配置。';
|
|
243
|
+
closeSystem(); $('embeddingDialog').showModal();
|
|
244
|
+
}
|
|
245
|
+
async function saveEmbedding(enabled = true) {
|
|
246
|
+
const body = enabled ? { provider: $('embeddingProvider').value, model: $('embeddingModel').value, endpoint: $('embeddingEndpoint').value, profileId: $('embeddingProfile').value || null, secret: $('embeddingSecret').value } : { enabled: false };
|
|
247
|
+
await api('/api/terminal/embedding-config', { method: 'PUT', body });
|
|
248
|
+
$('embeddingSecret').value = '';
|
|
249
|
+
const result = await api('/api/modules/configure', { method: 'POST', body: {} });
|
|
250
|
+
$('embeddingFeedback').textContent = !enabled ? '已停用向量模型,知识文件保留。' : result.configuration.embedding ? '已保存并同步到向量检索服务。' : '配置已保存,向量服务待恢复。请检查模块连接。';
|
|
251
|
+
await loadMaintenance();
|
|
252
|
+
}
|
|
253
|
+
async function repair() {
|
|
254
|
+
const project = activeProject(); if (!project) return toast('请先导入项目。');
|
|
255
|
+
const result = await api(`/api/projects/${encodeURIComponent(project.projectId)}/module-register`, { method: 'POST', body: {} });
|
|
256
|
+
const pending = Object.entries(result.modules).filter(([, v]) => !v.ok).map(([key]) => modules[key]);
|
|
257
|
+
$('wiringFeedback').textContent = pending.length ? `项目已登记,待恢复:${pending.join('、')}` : '四个模块已连接。';
|
|
258
|
+
toast($('wiringFeedback').textContent); await refreshRegistry(); Object.keys(frames).forEach(frameContext);
|
|
259
|
+
}
|
|
260
|
+
async function pick(target) {
|
|
261
|
+
const result = await api('/api/terminal/system/pick-folder');
|
|
262
|
+
if (result.cancelled || !result.path) return;
|
|
263
|
+
$(target).value = result.path; $(target).dispatchEvent(new Event('input', { bubbles: true }));
|
|
264
|
+
}
|
|
265
|
+
function openImport() {
|
|
266
|
+
resumeImport = false; $('setupImportRoot').hidden = true;
|
|
267
|
+
$('importForm').reset(); state.importId = `project-${crypto.randomUUID()}`; state.preflight = null; state.preflightEpoch++;
|
|
268
|
+
$('importDomain').innerHTML = '<option value="">未分组</option>' + state.brain.domains.map(d => `<option value="${esc(d.id)}">${esc(d.name)}</option>`).join('');
|
|
269
|
+
$('importDomain').value = activeProject()?.domainId || '';
|
|
270
|
+
$('gitInitChoice').hidden = true; $('confirmImport').disabled = true; $('preflightStatus').textContent = '填写目录后检查导入条件。'; $('importDialog').showModal();
|
|
271
|
+
}
|
|
272
|
+
function importInput() { return { projectId: state.importId, brainId: state.brain.id, domainId: $('importDomain').value || null, localPath: $('importCode').value.trim(), knowledgePath: $('importKnowledge').value.trim(), displayName: $('importName').value.trim(), knowledgeLanguage: 'zh-CN', confirmGitInit: $('importForm').elements.confirmGitInit.checked }; }
|
|
273
|
+
const problemLabels = { KNOWLEDGE_ROOT_MISSING: '请在系统状态中设置知识根目录。', AI_PROFILE_REQUIRED: '请配置可用模型;后台默认模型在系统状态中设置。', DUPLICATE_PROJECT: '此项目已导入,请打开已有项目。', HOOK_CONFLICT: '存在第三方 Hook,不会覆盖。请先检查冲突。', PATH_MISSING: '代码目录不存在。', PATH_REQUIRED: '请选择代码目录。' };
|
|
274
|
+
async function checkImport() {
|
|
275
|
+
const epoch = ++state.preflightEpoch; $('confirmImport').disabled = true;
|
|
276
|
+
$('setupImportRoot').hidden = true;
|
|
277
|
+
if (!$('importCode').value.trim()) return;
|
|
278
|
+
$('preflightStatus').textContent = '正在检查目录、仓库与接线条件…';
|
|
279
|
+
try {
|
|
280
|
+
const result = await api('/api/projects/preflight-import', { method: 'POST', body: importInput(), allowNotReady: true });
|
|
281
|
+
if (epoch !== state.preflightEpoch || !$('importDialog').open) return;
|
|
282
|
+
state.preflight = result; $('gitInitChoice').hidden = !result.plannedGitInit;
|
|
283
|
+
const messages = result.problems.map(p => problemLabels[p.code] || p.message);
|
|
284
|
+
$('setupImportRoot').hidden = !result.problems.some(p => p.code === 'KNOWLEDGE_ROOT_MISSING');
|
|
285
|
+
$('preflightStatus').textContent = result.ready ? result.plannedGitInit ? '目录检查通过。请确认初始化 Git;不分析历史。' : '检查通过。只建立当前提交的跟踪基线,不分析历史。' : [...new Set(messages)].join('\n');
|
|
286
|
+
$('preflightStatus').classList.toggle('issue', !result.ready);
|
|
287
|
+
$('confirmImport').disabled = !result.ready || result.plannedGitInit && !$('importForm').elements.confirmGitInit.checked || !$('importKnowledge').value.trim();
|
|
288
|
+
} catch (error) { if (epoch === state.preflightEpoch) $('preflightStatus').textContent = error.message; }
|
|
289
|
+
}
|
|
290
|
+
document.addEventListener('click', e => {
|
|
291
|
+
const button = e.target.closest('button'); if (!button) return;
|
|
292
|
+
const execute = async () => {
|
|
293
|
+
if (button.dataset.view) setView(button.dataset.view);
|
|
294
|
+
if (button.dataset.go) { if (button.dataset.go === 'memory') state.memoryTab = 'candidate'; setView(button.dataset.go); }
|
|
295
|
+
if (button.dataset.close) $(button.dataset.close).close();
|
|
296
|
+
if (button.dataset.selectProject) selectProject(button.dataset.selectProject);
|
|
297
|
+
if (button.dataset.scope) { state.scope = button.dataset.scope; all('[data-scope]').forEach(b => b.classList.toggle('active', b.dataset.scope === state.scope)); post('vectorHub', { type: 'kb:scope', scope: state.scope }); }
|
|
298
|
+
if (button.dataset.memoryTab) { state.memoryTab = button.dataset.memoryTab; renderMemories(); }
|
|
299
|
+
if (button.dataset.activityTab) { state.activityTab = button.dataset.activityTab; setView('activity'); post('eventBridge', { type: 'kb:visibility', visible: state.activityTab === 'sessions' }); post('eventBridge', { type: 'kb:refresh' }); }
|
|
300
|
+
if (button.dataset.action === 'import') openImport();
|
|
301
|
+
if (button.dataset.action === 'repair') await repair();
|
|
302
|
+
if (button.dataset.action === 'system') await openSystem();
|
|
303
|
+
if (button.dataset.action === 'unbind') { const p = activeProject(); if (p && confirm(`解绑 ${p.name}?代码、知识文件和会话历史将保留。`)) { await api(`/api/projects/${encodeURIComponent(p.projectId)}`, { method: 'DELETE', body: { deleteKnowledge: false } }); toast('已解绑,数据仍保留。'); await refreshRegistry(); } }
|
|
304
|
+
if (button.dataset.retryRun) { await api(`/api/engine/runs/${encodeURIComponent(button.dataset.retryRun)}/retry`, { method: 'POST', body: {} }); await refreshSnapshot(); }
|
|
305
|
+
if (button.dataset.contextIndex !== undefined) await openReference(state.contexts.get(state.projectId).references[Number(button.dataset.contextIndex)]);
|
|
306
|
+
if (button.dataset.memoryAction) {
|
|
307
|
+
const memory = state.snapshot.memories.find(m => m.id === button.dataset.memoryId); if (!memory) return;
|
|
308
|
+
const action = button.dataset.memoryAction;
|
|
309
|
+
if (action === 'evidence') await openReference({ ...memory.evidence, text: memory.evidence.quote, title: '记忆来源依据' });
|
|
310
|
+
else if (action === 'edit') { editingMemoryId = memory.id; $('memoryText').value = memory.text; $('memoryEvidence').textContent = `${projectName(memory.evidence.projectId)} · ${memory.evidence.commitSha.slice(0, 8)} / ${memory.evidence.entryId}`; $('memoryDialog').showModal(); }
|
|
311
|
+
else { await api(`/api/engine/memories/${encodeURIComponent(memory.id)}`, { method: 'PATCH', body: { status: action === 'confirm' ? 'confirmed' : 'ignored' } }); await refreshSnapshot(); }
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
execute().catch(error => toast(error.message));
|
|
315
|
+
});
|
|
316
|
+
document.addEventListener('change', e => { if (e.target.id === 'projectDomain') api(`/api/projects/${encodeURIComponent(state.projectId)}`, { method: 'PATCH', body: { domainId: e.target.value || null } }).then(refreshRegistry).catch(error => toast(error.message)); });
|
|
317
|
+
window.addEventListener('message', event => {
|
|
318
|
+
if (event.origin !== location.origin || !event.data || typeof event.data !== 'object') return;
|
|
319
|
+
const key = Object.keys(frames).find(k => $(frames[k].id).contentWindow === event.source); if (!key) return;
|
|
320
|
+
const message = event.data;
|
|
321
|
+
if (message.type === 'kb:ready') { frames[key].ready = true; frameContext(key); if (frames[key].openSettings) { post(key, { type: 'kb:open-settings' }); frames[key].openSettings = false; } }
|
|
322
|
+
if (message.requestId && message.requestId !== frames[key].requestId) return;
|
|
323
|
+
if (message.type === 'kb:knowledge' && key === 'terminal') {
|
|
324
|
+
const project = state.projects.find(p => p.modules.terminal.projectId === message.projectId);
|
|
325
|
+
if (project) { state.contexts.set(project.projectId, message.knowledge); if (project.projectId === state.projectId) renderContext(); }
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
on('projectSelect', 'change', e => selectProject(e.target.value));
|
|
329
|
+
on('themeToggle', 'click', () => { document.documentElement.dataset.theme = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'; localStorage.setItem('kb.theme', document.documentElement.dataset.theme); Object.keys(frames).forEach(key => post(key, { type: 'kb:theme', theme: document.documentElement.dataset.theme })); });
|
|
330
|
+
on('openSystem', 'click', openSystem); on('closeSystem', 'click', closeSystem); on('drawerBackdrop', 'click', closeSystem);
|
|
331
|
+
on('repairWiring', 'click', repair);
|
|
332
|
+
on('configureProviders', 'click', () => { closeSystem(); setView('work'); initializeFrame('terminal'); if (frames.terminal.ready) post('terminal', { type: 'kb:open-settings' }); else frames.terminal.openSettings = true; });
|
|
333
|
+
on('saveMaintenance', 'click', async () => { await api('/api/terminal/maintenance-default', { method: 'PUT', body: { profileId: $('maintenanceModel').value || null } }); const result = await api('/api/modules/configure', { method: 'POST', body: {} }); $('configurationStatus').textContent = result.configuration.model ? '后台默认模型已保存并同步。' : '配置已保存,后台运行配置尚未同步,请检查模块连接。'; });
|
|
334
|
+
on('openEmbedding', 'click', openEmbedding);
|
|
335
|
+
on('embeddingProfile', 'change', () => { $('embeddingSecretLabel').hidden = !!$('embeddingProfile').value; });
|
|
336
|
+
on('embeddingProvider', 'change', () => { $('embeddingModel').value = $('embeddingProvider').value === 'minimax' ? 'embo-01' : 'text-embedding-3-small'; });
|
|
337
|
+
on('embeddingForm', 'submit', async event => { event.preventDefault(); await saveEmbedding(); });
|
|
338
|
+
on('disableEmbedding', 'click', () => saveEmbedding(false));
|
|
339
|
+
on('embeddingDialog', 'close', () => { $('embeddingSecret').value = ''; });
|
|
340
|
+
on('saveBrainSettings', 'click', async () => { await api('/api/brain', { method: 'PATCH', body: { name: $('brainName').value } }); await api('/api/settings', { method: 'PATCH', body: { knowledge: { rootPath: $('knowledgeRoot').value } } }); toast('设置已保存,已有知识未移动。'); await refreshRegistry(); if (resumeImport) closeSystem(); });
|
|
341
|
+
on('setupImportRoot', 'click', async () => { $('importDialog').close(); resumeImport = true; await openSystem(); $('knowledgeRoot').focus(); });
|
|
342
|
+
on('pickRoot', 'click', () => pick('knowledgeRoot')); on('pickCode', 'click', () => pick('importCode')); on('pickKnowledge', 'click', () => pick('importKnowledge'));
|
|
343
|
+
on('openEngine', 'click', () => { closeSystem(); initializeFrame('engine'); $('engineDialog').showModal(); post('engine', { type: 'kb:visibility', visible: true }); });
|
|
344
|
+
on('engineDialog', 'close', () => post('engine', { type: 'kb:visibility', visible: false }));
|
|
345
|
+
on('openLogs', 'click', async () => { closeSystem(); $('logsDialog').showModal(); await loadLogs(); });
|
|
346
|
+
on('logsDialog', 'close', () => { logEpoch++; logStream?.close(); logStream = null; });
|
|
347
|
+
on('copyReference', 'click', async () => {
|
|
348
|
+
const text = $('referenceMeta').textContent + '\n\n' + $('referenceContent').textContent;
|
|
349
|
+
try { await navigator.clipboard.writeText(text); }
|
|
350
|
+
catch { const field = document.createElement('textarea'); field.value = text; document.body.append(field); field.select(); const copied = document.execCommand('copy'); field.remove(); if (!copied) throw new Error('复制失败,请选择引用内容手动复制。'); }
|
|
351
|
+
toast('已复制知识与来源。');
|
|
352
|
+
});
|
|
353
|
+
on('loadLogs', 'click', loadLogs);
|
|
354
|
+
on('exportLogs', 'click', async () => { const params = new URLSearchParams({ q: $('logsSearch').value.trim() }); if (state.projectId) params.set('projectId', state.projectId); const response = await fetch(`/api/logs/export?${params}`); if (!response.ok) throw new Error('诊断导出失败。'); const blob = await response.blob(); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'knowledge-diagnostics.jsonl'; link.click(); setTimeout(() => URL.revokeObjectURL(url), 1000); });
|
|
355
|
+
on('createDomain', 'click', () => { $('domainForm').reset(); $('domainDialog').showModal(); });
|
|
356
|
+
on('domainForm', 'submit', async e => { e.preventDefault(); await api('/api/domains', { method: 'POST', body: Object.fromEntries(new FormData(e.target)) }); $('domainDialog').close(); await refreshRegistry(); });
|
|
357
|
+
on('memoryForm', 'submit', async e => { e.preventDefault(); await api(`/api/engine/memories/${encodeURIComponent(editingMemoryId)}`, { method: 'PATCH', body: { text: $('memoryText').value } }); $('memoryDialog').close(); await refreshSnapshot(); });
|
|
358
|
+
on('importForm', 'input', e => {
|
|
359
|
+
if (e.target.id === 'importCode') { const name = e.target.value.trim().split(/[\\/]/).filter(Boolean).pop() || ''; $('importName').value = name; if (state.root) $('importKnowledge').value = `${state.root.replace(/[\\/]+$/, '')}/${name}-${state.importId.slice(-6)}`; }
|
|
360
|
+
clearTimeout(preflightTimer); $('confirmImport').disabled = true; preflightTimer = setTimeout(checkImport, 250);
|
|
361
|
+
});
|
|
362
|
+
on('importForm', 'submit', async e => {
|
|
363
|
+
e.preventDefault(); if (!state.preflight?.ready || state.preflight.plannedGitInit && !$('importForm').elements.confirmGitInit.checked) return;
|
|
364
|
+
$('confirmImport').disabled = true; $('confirmImport').textContent = '正在登记…';
|
|
365
|
+
try { const result = await api('/api/projects/import', { method: 'POST', body: importInput() }); state.projectId = result.projectId; state.epoch++; $('importDialog').close(); await refreshRegistry(); Object.keys(frames).forEach(frameContext); setView('projects'); toast(Object.values(result.modules).every(m => m.ok) ? '项目已导入,四个模块已连接。' : '项目已导入,部分接线待恢复。'); }
|
|
366
|
+
finally { $('confirmImport').textContent = '导入项目'; if ($('importDialog').open) checkImport(); }
|
|
367
|
+
});
|
|
368
|
+
window.addEventListener('hashchange', () => setView(location.hash.slice(1)));
|
|
369
|
+
window.addEventListener('offline', () => {
|
|
370
|
+
snapshotEpoch++; clearTimeout(refreshTimer);
|
|
371
|
+
state.stream?.close(); state.stream = null; logStream?.close(); logStream = null; logEpoch++;
|
|
372
|
+
state.engineAvailable = false; $('activityConnection').textContent = '连接中断,恢复后自动读取快照';
|
|
373
|
+
renderSystem(); render();
|
|
374
|
+
});
|
|
375
|
+
window.addEventListener('online', () => {
|
|
376
|
+
$('activityConnection').textContent = '正在恢复快照'; refreshRegistry();
|
|
377
|
+
if ($('logsDialog').open) loadLogs().catch(error => toast(error.message));
|
|
378
|
+
});
|
|
379
|
+
window.addEventListener('beforeunload', () => { state.stream?.close(); logStream?.close(); });
|
|
380
|
+
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeSystem(); });
|
|
381
|
+
document.documentElement.dataset.theme = localStorage.getItem('kb.theme') === 'dark' ? 'dark' : 'light';
|
|
382
|
+
all('[data-scope]').forEach(button => button.classList.toggle('active', button.dataset.scope === state.scope));
|
|
383
|
+
state.view = titles[location.hash.slice(1)] ? location.hash.slice(1) : 'home';
|
|
384
|
+
setView(state.view); refreshRegistry();
|
|
385
|
+
setInterval(() => { if (!document.hidden) refreshRegistry(); }, 30000);
|
|
386
|
+
})();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "project-knowledge/vendor-runtime/v1",
|
|
3
|
+
"modules": {
|
|
4
|
+
"knowledge-engine": {
|
|
5
|
+
"name": "@sanqianx/ai-coding-knowledge-engine",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"root": "_modules/knowledge-engine",
|
|
8
|
+
"files": {
|
|
9
|
+
"README.md": "27e06c47c58e75e3081296cbdb2885df2d346fbd87bd7e415254db501d8ef35d",
|
|
10
|
+
"package.json": "f6949c331796b8351e55293d2be5a18b4ac7a203b9270c564eeefd692a006daf",
|
|
11
|
+
"src/analysis-contract.js": "90354d1e9b4eff74fdd9d47e097cdb5d47944785c2be66811f64e5958c94d8e7",
|
|
12
|
+
"src/analysis-worker.js": "a105cabe6e1573b2d986b1e7a5aaf615e2fb53524156fc8e4e2c0a71f2c97e55",
|
|
13
|
+
"src/bin.js": "22208907431d0f3a971a669c7e5eb44b3b7af9bbb36fd7ba17c3b90c4288bd70",
|
|
14
|
+
"src/bridge-client.js": "ae0dba21e4286694bcd9e064ca36564ec997969ae1eb1ce4f8a6665e447cbffe",
|
|
15
|
+
"src/coordinator.js": "4e82cce47c8c16294ddc77eb946eb1c3a2f8bfd8918e05742c80845ed8ecf10c",
|
|
16
|
+
"src/headless-analyzer.js": "2473b28d4bf06088abbcafa21d491560a9ca0ab100024c912a36bc6f981d8576",
|
|
17
|
+
"src/memories.js": "0a8667e6daba99fa5022393951a28e96cdadf98af85b6af9a6be96c7f4e896fc",
|
|
18
|
+
"src/migration.js": "b018871727f3277e4a24350fef1911fbb89e2158578339210d88efc2e309be32",
|
|
19
|
+
"src/retrieval.js": "acbf76bb93fd13dac391c2e918891a56ace869db5583d2a5994641b6782496d6",
|
|
20
|
+
"src/runtime/README.md": "9be676eca76a7857b34d7357194e6f0ee733c22e61d962d53984715dbec7f673",
|
|
21
|
+
"src/runtime/atomic-file.js": "9846b4b69db5ded811acf19a1181ef93615d93efaa542b858e552a7f3124ad23",
|
|
22
|
+
"src/runtime/automation-config.js": "6498c5397be12b155829740595a1553d4dcd6eaa7db6ced3ef88fdc60218f00e",
|
|
23
|
+
"src/runtime/commit-conversation-binder.js": "4f5a7a13c3dfac3765ad70e54de351f5d81e0ed4b2b2d7ecb90ef77cb29e0c15",
|
|
24
|
+
"src/runtime/commit-processing-ledger.js": "455aa1ac4e212c0b63d9df99e49aa94703cd24145e41aa9794f16274b2ec2e18",
|
|
25
|
+
"src/runtime/commit-prompt.js": "11856175a405c00514add6a540324a0efe3d7e5dd872c9e8a0b77f9f40abc551",
|
|
26
|
+
"src/runtime/commit-reconciler.js": "27a894ac7588701fc56e19811cbf266664357e8ac9beb9ffc4eccfc1f8e75b46",
|
|
27
|
+
"src/runtime/content-hash.js": "201ee7dcc054310f14097a00058cf9b28fa913367eef616e0fdd8facbf3c6c41",
|
|
28
|
+
"src/runtime/contracts.js": "567fbace85ad0534fb0119f5c0e57d73acc0b76cc799af93d778c5ecbade53df",
|
|
29
|
+
"src/runtime/conversation-exclusions.js": "ef8545034b862d384fb460140490bb531b58d94992f5e2222ebaaa079b44fa63",
|
|
30
|
+
"src/runtime/conversation-store.js": "8ea0135588e40dfe0bab0288220a0fb481a470855558ae38b8274d57ce55fc39",
|
|
31
|
+
"src/runtime/evidence-bundle.js": "33d4614849d5e1d69aba8b75c3a43c18b7b6f94e5c78654c82ef631331cc94cc",
|
|
32
|
+
"src/runtime/git-runner.js": "b74fe197bd4dcf3e70635ef7b1f2615edf965c1d9a3ffe5406aabd41b76d74a9",
|
|
33
|
+
"src/runtime/knowledge-promotion.js": "c24aba505caf11cdc8faba763beffc65956c1330ef28ed5fbd6ae16eac640fa6",
|
|
34
|
+
"src/runtime/knowledge-retrieval-service.js": "75444f495a9ad0720af0e57b8d27d7ac1325c78a768f4f68ebd86ebc2524945e",
|
|
35
|
+
"src/runtime/layout.js": "c48fa1db62e5df1edefe13218e470c5ceea4ef2781293512c2c18e5c2ba0c7e9",
|
|
36
|
+
"src/runtime/markdown-knowledge-indexer.js": "44a76a9adefad07e78f77b1106b10d6c1cc7873d76362df4c546892b21f3939c",
|
|
37
|
+
"src/runtime/scanner.js": "d99fa8d74a6fbbaae75ca49f62127e14cf9e48b1404e4b4d597ef39df5441410",
|
|
38
|
+
"src/server.js": "e2d983eaa9f4ffc240caab467fad8e21c6124779ac3f27c9cec2c4f06827ef65",
|
|
39
|
+
"src/store.js": "b778d93b97cd6a1174fccd3a5de1f2b170934a262e11e4447e7fdc698ac386e0",
|
|
40
|
+
"ui/app.css": "7ad370c269e60a7b5ec907d81a607b2d16bd07b2d7c22a81f102dab51eeb2889",
|
|
41
|
+
"ui/app.js": "5f4f6fc6faeb951938a821e846e948de8859d7c908e04f6f93bf73208493993e",
|
|
42
|
+
"ui/index.html": "ce3565cfefd4838b11d37a91a906ef26ca5a1834844fe3f7c897b56851a3c418"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|