noteconnection 1.7.0 → 1.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/LICENSE +674 -21
- package/README.md +242 -62
- package/dist/src/agent_workspace.contract.parity.test.js +475 -0
- package/dist/src/agent_workspace.frontend.test.js +9989 -0
- package/dist/src/agent_workspace.locale.contract.test.js +95 -0
- package/dist/src/agent_workspace.runtime.behavior.test.js +5072 -0
- package/dist/src/copy.assets.contract.test.js +130 -0
- package/dist/src/core/PathBridge.js +28 -3
- package/dist/src/export/WorkspaceExportBundle.js +1130 -0
- package/dist/src/export/WorkspaceExportBundle.test.js +1480 -0
- package/dist/src/export/index.js +18 -0
- package/dist/src/export/types.js +2 -0
- package/dist/src/fixrisk.issue.verifier.contract.test.js +3 -1
- package/dist/src/foundation.ann.runtime.contract.test.js +81 -0
- package/dist/src/foundation.release.evidence.contract.test.js +406 -0
- package/dist/src/foundation.sqlite.runtime.contract.test.js +82 -0
- package/dist/src/frontend/README.md +242 -62
- package/dist/src/frontend/agent_workspace.js +5353 -0
- package/dist/src/frontend/agent_workspace_runtime.js +4434 -0
- package/dist/src/frontend/analysis.js +31 -4
- package/dist/src/frontend/app.js +2178 -62
- package/dist/src/frontend/focus_mode_interactions.js +33 -0
- package/dist/src/frontend/godot_future_path_renderer.js +540 -0
- package/dist/src/frontend/godot_tree_interactions.js +178 -0
- package/dist/src/frontend/graph_state.mjs +105 -0
- package/dist/src/frontend/hosted_future_path_runtime.js +157 -0
- package/dist/src/frontend/i18n.mjs +186 -0
- package/dist/src/frontend/index.html +641 -266
- package/dist/src/frontend/layout_gpu.js +12 -4
- package/dist/src/frontend/locales/en.json +721 -5
- package/dist/src/frontend/locales/zh.json +721 -5
- package/dist/src/frontend/main.mjs +60 -0
- package/dist/src/frontend/markdown_runtime.js +827 -0
- package/dist/src/frontend/notemd.css +49 -0
- package/dist/src/frontend/notemd.html +7 -1
- package/dist/src/frontend/notemd.js +64 -0
- package/dist/src/frontend/path.html +107 -0
- package/dist/src/frontend/path_app.js +2108 -130
- package/dist/src/frontend/path_layout.mjs +143 -0
- package/dist/src/frontend/path_mermaid_utils.mjs +108 -0
- package/dist/src/frontend/path_modules_bridge.js +486 -0
- package/dist/src/frontend/path_state.mjs +118 -0
- package/dist/src/frontend/path_styles.css +146 -0
- package/dist/src/frontend/path_worker_bridge.mjs +85 -0
- package/dist/src/frontend/reader.js +393 -33
- package/dist/src/frontend/runtime_bridge.js +67 -54
- package/dist/src/frontend/runtime_bridge.mjs +279 -0
- package/dist/src/frontend/settings.js +130 -12
- package/dist/src/frontend/source_manager.js +190 -21
- package/dist/src/frontend/styles.css +2720 -72
- package/dist/src/frontend/workbench_state.mjs +101 -0
- package/dist/src/frontend/workspace_panes.js +10168 -0
- package/dist/src/frontend.locale.contract.test.js +62 -0
- package/dist/src/godot.sidecar.bootstrap.contract.test.js +244 -0
- package/dist/src/indexing/IndexLifecycle.js +195 -0
- package/dist/src/indexing/IndexLifecycle.test.js +49 -0
- package/dist/src/indexing/SegmentBuilder.js +64 -0
- package/dist/src/indexing/UnitBuilder.js +48 -0
- package/dist/src/indexing/types.js +2 -0
- package/dist/src/knowledge.api.contract.test.js +170 -0
- package/dist/src/learning/KnowledgeLearningPlatform.js +10386 -0
- package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +327 -0
- package/dist/src/learning/KnowledgeLearningPlatform.program-f.test.js +99 -0
- package/dist/src/learning/KnowledgeLearningPlatform.test.js +2971 -0
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +2974 -0
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +3928 -0
- package/dist/src/learning/answerReleaseReview.js +4319 -0
- package/dist/src/learning/answerReleaseReview.test.js +2888 -0
- package/dist/src/learning/api.js +2 -0
- package/dist/src/learning/conversationComposer.js +1480 -0
- package/dist/src/learning/conversationComposer.test.js +1817 -0
- package/dist/src/learning/domains/ConversationManager.js +53 -0
- package/dist/src/learning/domains/KnowledgeIngestor.js +238 -0
- package/dist/src/learning/domains/KnowledgeQuerier.js +187 -0
- package/dist/src/learning/domains/MasteryEngine.js +387 -0
- package/dist/src/learning/domains/MemoryPolicyManager.js +408 -0
- package/dist/src/learning/domains/QualityEvaluator.js +307 -0
- package/dist/src/learning/domains/TutorRouter.js +313 -0
- package/dist/src/learning/domains/index.js +33 -0
- package/dist/src/learning/domains/types.js +7 -0
- package/dist/src/learning/errors.js +29 -0
- package/dist/src/learning/evidenceContextAssembler.js +1176 -0
- package/dist/src/learning/evidenceContextAssembler.test.js +6332 -0
- package/dist/src/learning/graphContextAssembler.js +870 -0
- package/dist/src/learning/graphContextAssembler.test.js +1033 -0
- package/dist/src/learning/index.js +28 -0
- package/dist/src/learning/queryBackend.js +1898 -0
- package/dist/src/learning/queryBackend.test.js +955 -0
- package/dist/src/learning/ragContextPack.js +257 -0
- package/dist/src/learning/ragContextPack.test.js +160 -0
- package/dist/src/learning/ragPublicText.js +38 -0
- package/dist/src/learning/ragSufficiencyJudge.js +161 -0
- package/dist/src/learning/ragSufficiencyJudge.test.js +177 -0
- package/dist/src/learning/ragSufficiencyProviderJudge.js +227 -0
- package/dist/src/learning/ragSufficiencyProviderJudge.test.js +156 -0
- package/dist/src/learning/requestNormalization.js +198 -0
- package/dist/src/learning/runtimeCapability.js +4677 -0
- package/dist/src/learning/runtimeCapability.test.js +3635 -0
- package/dist/src/learning/store.js +1240 -0
- package/dist/src/learning/store.test.js +1126 -0
- package/dist/src/learning/tutorAdapter.js +2 -0
- package/dist/src/learning/types.js +2 -0
- package/dist/src/learning/vectorAccelerationAdapter.js +942 -0
- package/dist/src/learning/vectorAccelerationAdapter.test.js +382 -0
- package/dist/src/lfs.asset.policy.contract.test.js +153 -0
- package/dist/src/license.policy.contract.test.js +66 -0
- package/dist/src/memory/MemoryGovernance.js +74 -0
- package/dist/src/memory/MemoryGovernance.test.js +46 -0
- package/dist/src/memory/types.js +2 -0
- package/dist/src/mermaid.frontend.guard.contract.test.js +77 -0
- package/dist/src/middleware/auth.js +17 -0
- package/dist/src/middleware/body-parser.js +45 -0
- package/dist/src/middleware/cors.js +44 -0
- package/dist/src/middleware/index.js +21 -0
- package/dist/src/middleware/request-trace.js +96 -0
- package/dist/src/notemd/AppConfigToml.js +6 -4
- package/dist/src/notemd/MermaidProcessor.js +400 -50
- package/dist/src/notemd/NotemdService.js +498 -13
- package/dist/src/notemd/PromptManager.js +15 -0
- package/dist/src/notemd/cli/commands.js +357 -0
- package/dist/src/notemd/cli/dispatcher.js +225 -0
- package/dist/src/notemd/cli/index.js +169 -0
- package/dist/src/notemd/cli/parser.js +68 -0
- package/dist/src/notemd/cli/types.js +2 -0
- package/dist/src/notemd/constants.js +43 -0
- package/dist/src/notemd/diagram/diagramGenerationService.js +78 -0
- package/dist/src/notemd/diagram/diagramSpec.js +79 -0
- package/dist/src/notemd/diagram/diagramSpecResponseParser.js +131 -0
- package/dist/src/notemd/diagram/intent.js +95 -0
- package/dist/src/notemd/diagram/planner.js +71 -0
- package/dist/src/notemd/diagram/prompts/diagramSpecPrompt.js +42 -0
- package/dist/src/notemd/diagram/types.js +18 -0
- package/dist/src/notemd/index.js +26 -0
- package/dist/src/notemd/operations/capabilityManifest.js +23 -0
- package/dist/src/notemd/operations/cliContracts.js +17 -0
- package/dist/src/notemd/operations/configProfileCommands.js +85 -0
- package/dist/src/notemd/operations/registry.contract.test.js +95 -0
- package/dist/src/notemd/operations/registry.js +991 -0
- package/dist/src/notemd/operations/types.js +2 -0
- package/dist/src/notemd/providerDiagnostics.js +220 -0
- package/dist/src/notemd/providerProfiles.js +42 -0
- package/dist/src/notemd/providerTemplates.js +231 -0
- package/dist/src/notemd/search/DuckDuckGoProvider.js +39 -0
- package/dist/src/notemd/search/SearchManager.js +13 -0
- package/dist/src/notemd/search/SearchProvider.js +2 -0
- package/dist/src/notemd/search/TavilyProvider.js +44 -0
- package/dist/src/notemd.agent.manifest.test.js +85 -0
- package/dist/src/notemd.api.contract.test.js +14 -1
- package/dist/src/notemd.app_config_toml.test.js +2 -1
- package/dist/src/notemd.batch.workflow.test.js +117 -0
- package/dist/src/notemd.cli.e2e.test.js +136 -0
- package/dist/src/notemd.core.test.js +51 -0
- package/dist/src/notemd.diagram.pipeline.test.js +233 -0
- package/dist/src/notemd.providerTemplates.test.js +34 -0
- package/dist/src/notemd.server.integration.test.js +143 -35
- package/dist/src/notemd.workflow.pipeline.test.js +162 -0
- package/dist/src/pathbridge.handshake.contract.test.js +16 -2
- package/dist/src/pathmode.background.contract.test.js +69 -0
- package/dist/src/pathmode.settings.api.contract.test.js +9 -0
- package/dist/src/pkg.sidecar.contract.test.js +9 -3
- package/dist/src/platform/ExportProfile.js +58 -0
- package/dist/src/platform/PlatformCapabilities.js +45 -0
- package/dist/src/platform/PlatformCapabilities.test.js +30 -0
- package/dist/src/platform/RenderMaterializer.js +33 -0
- package/dist/src/platform/RenderMaterializer.test.js +32 -0
- package/dist/src/query_backend.external_http.integration.test.js +410 -0
- package/dist/src/reader_renderer.js +404 -3
- package/dist/src/reader_renderer.test.js +87 -0
- package/dist/src/release.godot.mirror.contract.test.js +73 -0
- package/dist/src/resources/ResourceRegistry.js +223 -0
- package/dist/src/resources/ResourceRegistry.test.js +61 -0
- package/dist/src/resources/types.js +2 -0
- package/dist/src/routes/agentWorkspaceDiagnostics.js +173 -0
- package/dist/src/routes/data.js +267 -0
- package/dist/src/routes/diagnostics.js +51 -0
- package/dist/src/routes/index.js +23 -0
- package/dist/src/routes/knowledge.js +968 -0
- package/dist/src/routes/markdown.js +287 -0
- package/dist/src/routes/notemd.js +565 -0
- package/dist/src/routes/registry.contract.test.js +130 -0
- package/dist/src/routes/render.js +285 -0
- package/dist/src/routes/runtimeRunbookRouteOps.js +149 -0
- package/dist/src/routes/runtimeRunbookRouteOps.test.js +194 -0
- package/dist/src/routes/settings.js +6 -0
- package/dist/src/routes/staticFiles.js +94 -0
- package/dist/src/routes/types.js +2 -0
- package/dist/src/runtime.transport.adapter.contract.test.js +81 -0
- package/dist/src/server.js +11090 -1549
- package/dist/src/server.migration.test.js +193 -21
- package/dist/src/server.port.fallback.contract.test.js +63 -0
- package/dist/src/session/SessionStateStore.js +81 -0
- package/dist/src/session/SessionStateStore.test.js +58 -0
- package/dist/src/session/types.js +2 -0
- package/dist/src/settings.runtime.contract.test.js +50 -0
- package/dist/src/shared/types.contract.test.js +107 -0
- package/dist/src/shared/types.js +22 -0
- package/dist/src/sidecar.replacement.boundary.contract.test.js +128 -0
- package/dist/src/sidecar.supply.readiness.contract.test.js +144 -0
- package/dist/src/source_manager.loadflow.test.js +46 -0
- package/dist/src/startup.layout.snapshot.contract.test.js +57 -0
- package/dist/src/tauri.frontend.build.contract.test.js +60 -0
- package/dist/src/tauri.sidecar.cleanup.contract.test.js +21 -0
- package/dist/src/utils/RuntimePaths.js +4 -13
- package/dist/src/utils/platform.js +153 -0
- package/dist/src/workflows/WorkflowArtifactStore.js +96 -0
- package/dist/src/workflows/WorkflowArtifactStore.test.js +80 -0
- package/dist/src/workflows/types.js +2 -0
- package/dist/src/workspace/WorkspaceRegistry.js +122 -0
- package/dist/src/workspace/WorkspaceRegistry.test.js +29 -0
- package/dist/src/workspace/types.js +2 -0
- package/package.json +53 -10
package/dist/src/frontend/app.js
CHANGED
|
@@ -27,6 +27,385 @@ function readStartupPerfProfileOverride() {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
const mermaidErrorGuardState = {
|
|
31
|
+
observer: null,
|
|
32
|
+
toastHost: null,
|
|
33
|
+
seenAtBySignature: new Map(),
|
|
34
|
+
renderGuardInstalled: false,
|
|
35
|
+
periodicSweepHandle: null,
|
|
36
|
+
suppressedArtifacts: [],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function isMermaidErrorArtifactText(text) {
|
|
40
|
+
const normalized = String(text || '').toLowerCase();
|
|
41
|
+
if (!normalized) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return (
|
|
45
|
+
normalized.includes('syntax error in text') ||
|
|
46
|
+
normalized.includes('lexical error on line') ||
|
|
47
|
+
normalized.includes('parse error on line') ||
|
|
48
|
+
normalized.includes('mermaid version') ||
|
|
49
|
+
normalized.includes('diagram syntax error')
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeMermaidErrorArtifactText(text) {
|
|
54
|
+
return String(text || '')
|
|
55
|
+
.replace(/\s+/g, ' ')
|
|
56
|
+
.trim()
|
|
57
|
+
.slice(0, 240);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function registerMermaidSuppressedArtifact(detail, context = {}) {
|
|
61
|
+
const signature = normalizeMermaidErrorArtifactText(detail);
|
|
62
|
+
if (!signature) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const entry = {
|
|
66
|
+
signature,
|
|
67
|
+
detectedAt: new Date().toISOString(),
|
|
68
|
+
context,
|
|
69
|
+
};
|
|
70
|
+
mermaidErrorGuardState.suppressedArtifacts.push(entry);
|
|
71
|
+
if (mermaidErrorGuardState.suppressedArtifacts.length > 20) {
|
|
72
|
+
mermaidErrorGuardState.suppressedArtifacts.shift();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function ensureMermaidErrorToastHost() {
|
|
77
|
+
if (mermaidErrorGuardState.toastHost && mermaidErrorGuardState.toastHost.isConnected) {
|
|
78
|
+
return mermaidErrorGuardState.toastHost;
|
|
79
|
+
}
|
|
80
|
+
const host = document.createElement('div');
|
|
81
|
+
host.id = 'mermaid-error-toast-stack';
|
|
82
|
+
host.className = 'mermaid-error-toast-stack';
|
|
83
|
+
document.body.appendChild(host);
|
|
84
|
+
mermaidErrorGuardState.toastHost = host;
|
|
85
|
+
return host;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function emitMermaidErrorToast(detail) {
|
|
89
|
+
const signature = normalizeMermaidErrorArtifactText(detail);
|
|
90
|
+
if (!signature) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
const lastSeenAt = mermaidErrorGuardState.seenAtBySignature.get(signature) || 0;
|
|
95
|
+
if ((now - lastSeenAt) < 8000) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
mermaidErrorGuardState.seenAtBySignature.set(signature, now);
|
|
99
|
+
|
|
100
|
+
const host = ensureMermaidErrorToastHost();
|
|
101
|
+
const toast = document.createElement('div');
|
|
102
|
+
toast.className = 'mermaid-error-toast';
|
|
103
|
+
|
|
104
|
+
const title = document.createElement('div');
|
|
105
|
+
title.className = 'mermaid-error-toast-title';
|
|
106
|
+
title.textContent = 'Mermaid diagram skipped';
|
|
107
|
+
toast.appendChild(title);
|
|
108
|
+
|
|
109
|
+
const summary = document.createElement('div');
|
|
110
|
+
summary.className = 'mermaid-error-toast-summary';
|
|
111
|
+
summary.textContent = 'A malformed Mermaid block was suppressed to keep the workspace readable.';
|
|
112
|
+
toast.appendChild(summary);
|
|
113
|
+
|
|
114
|
+
const detailLine = document.createElement('div');
|
|
115
|
+
detailLine.className = 'mermaid-error-toast-detail';
|
|
116
|
+
detailLine.textContent = signature;
|
|
117
|
+
toast.appendChild(detailLine);
|
|
118
|
+
|
|
119
|
+
host.appendChild(toast);
|
|
120
|
+
while (host.childElementCount > 3) {
|
|
121
|
+
host.removeChild(host.firstElementChild);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
window.setTimeout(() => {
|
|
125
|
+
if (toast.parentNode) {
|
|
126
|
+
toast.parentNode.removeChild(toast);
|
|
127
|
+
}
|
|
128
|
+
}, 6500);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function createInlineMermaidErrorNotice(detail) {
|
|
132
|
+
const wrapper = document.createElement('div');
|
|
133
|
+
wrapper.className = 'mermaid-inline-guard';
|
|
134
|
+
|
|
135
|
+
const title = document.createElement('div');
|
|
136
|
+
title.className = 'mermaid-inline-guard-title';
|
|
137
|
+
title.textContent = 'Mermaid diagram unavailable';
|
|
138
|
+
wrapper.appendChild(title);
|
|
139
|
+
|
|
140
|
+
const summary = document.createElement('div');
|
|
141
|
+
summary.className = 'mermaid-inline-guard-summary';
|
|
142
|
+
summary.textContent = 'Malformed Mermaid content was suppressed in this view.';
|
|
143
|
+
wrapper.appendChild(summary);
|
|
144
|
+
|
|
145
|
+
if (detail) {
|
|
146
|
+
const detailLine = document.createElement('div');
|
|
147
|
+
detailLine.className = 'mermaid-inline-guard-detail';
|
|
148
|
+
detailLine.textContent = normalizeMermaidErrorArtifactText(detail);
|
|
149
|
+
wrapper.appendChild(detailLine);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return wrapper;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function resolveMermaidErrorArtifactHost(node) {
|
|
156
|
+
if (!node || typeof node.closest !== 'function') {
|
|
157
|
+
return node;
|
|
158
|
+
}
|
|
159
|
+
return (
|
|
160
|
+
node.closest('.mermaid-render-host-offscreen') ||
|
|
161
|
+
node.closest('.mermaid-render-failed') ||
|
|
162
|
+
node.closest('.mermaid') ||
|
|
163
|
+
node.closest('.reader-block') ||
|
|
164
|
+
node.closest('svg') ||
|
|
165
|
+
node
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function isProtectedMermaidSuppressionHost(host) {
|
|
170
|
+
if (!host || host.nodeType !== Node.ELEMENT_NODE) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
return (
|
|
174
|
+
host === document.body ||
|
|
175
|
+
host === document.documentElement ||
|
|
176
|
+
host === document.head ||
|
|
177
|
+
host.id === 'graph-wrapper' ||
|
|
178
|
+
host.id === 'path-container' ||
|
|
179
|
+
host.id === 'reading-window' ||
|
|
180
|
+
host.id === 'reading-content-box' ||
|
|
181
|
+
host.id === 'reading-body'
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function collectMermaidErrorArtifactCandidates(root) {
|
|
186
|
+
const hosts = [];
|
|
187
|
+
const hostSet = new Set();
|
|
188
|
+
const candidates = [];
|
|
189
|
+
|
|
190
|
+
if (!root) {
|
|
191
|
+
return hosts;
|
|
192
|
+
}
|
|
193
|
+
if (
|
|
194
|
+
root.nodeType === Node.ELEMENT_NODE &&
|
|
195
|
+
root !== document.body &&
|
|
196
|
+
root !== document.documentElement &&
|
|
197
|
+
root !== document.head
|
|
198
|
+
) {
|
|
199
|
+
candidates.push(root);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const queryRoot = root.nodeType === Node.ELEMENT_NODE ? root : document.body;
|
|
203
|
+
if (queryRoot && typeof queryRoot.querySelectorAll === 'function') {
|
|
204
|
+
queryRoot.querySelectorAll('svg, .mermaid, div, section, article, aside, img, foreignObject').forEach((node) => candidates.push(node));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
candidates.forEach((candidate) => {
|
|
208
|
+
if (!candidate || candidate.nodeType !== Node.ELEMENT_NODE) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const element = candidate;
|
|
212
|
+
const text = normalizeMermaidErrorArtifactText(
|
|
213
|
+
element.textContent ||
|
|
214
|
+
element.getAttribute?.('alt') ||
|
|
215
|
+
element.getAttribute?.('aria-label') ||
|
|
216
|
+
''
|
|
217
|
+
);
|
|
218
|
+
if (!isMermaidErrorArtifactText(text)) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const host = resolveMermaidErrorArtifactHost(element);
|
|
222
|
+
if (!host || hostSet.has(host) || isProtectedMermaidSuppressionHost(host)) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
hostSet.add(host);
|
|
226
|
+
hosts.push(host);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
return hosts;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function suppressMermaidErrorArtifacts(root, context = {}) {
|
|
233
|
+
if (!root) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const candidates = collectMermaidErrorArtifactCandidates(root);
|
|
238
|
+
candidates.forEach((candidate) => {
|
|
239
|
+
if (!candidate || candidate.dataset?.mermaidErrorSuppressed === '1') {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const text = normalizeMermaidErrorArtifactText(candidate.textContent || '');
|
|
243
|
+
if (!isMermaidErrorArtifactText(text)) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const host = resolveMermaidErrorArtifactHost(candidate);
|
|
248
|
+
if (!host || host.dataset?.mermaidErrorSuppressed === '1') {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (host.dataset) {
|
|
252
|
+
host.dataset.mermaidErrorSuppressed = '1';
|
|
253
|
+
}
|
|
254
|
+
registerMermaidSuppressedArtifact(text, {
|
|
255
|
+
hostTag: host.tagName || '',
|
|
256
|
+
hostId: host.id || '',
|
|
257
|
+
hostClass: host.className || '',
|
|
258
|
+
...context,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const shouldKeepInline = Boolean(
|
|
262
|
+
host.closest('#reading-window') ||
|
|
263
|
+
host.closest('#reading-body') ||
|
|
264
|
+
host.closest('.notemd-embed-shell')
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
if (shouldKeepInline) {
|
|
268
|
+
const inlineNotice = createInlineMermaidErrorNotice(text);
|
|
269
|
+
if (host.parentNode) {
|
|
270
|
+
host.parentNode.replaceChild(inlineNotice, host);
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
emitMermaidErrorToast(text);
|
|
274
|
+
if (host.parentNode) {
|
|
275
|
+
host.parentNode.removeChild(host);
|
|
276
|
+
} else {
|
|
277
|
+
host.style.display = 'none';
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function installMermaidRuntimeGuards() {
|
|
284
|
+
const mermaid = window.mermaid;
|
|
285
|
+
if (!mermaid || mermaidErrorGuardState.renderGuardInstalled === true || mermaid.__noteConnectionErrorGuardInstalled === true) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (typeof mermaid.render === 'function') {
|
|
290
|
+
const originalRender = mermaid.render.bind(mermaid);
|
|
291
|
+
mermaid.render = async function(...args) {
|
|
292
|
+
const result = await originalRender(...args);
|
|
293
|
+
if (result && typeof result.svg === 'string' && isMermaidErrorArtifactText(result.svg)) {
|
|
294
|
+
registerMermaidSuppressedArtifact(result.svg, {
|
|
295
|
+
source: 'mermaid.render',
|
|
296
|
+
});
|
|
297
|
+
throw new Error('Suppressed Mermaid error SVG before it could be mounted.');
|
|
298
|
+
}
|
|
299
|
+
return result;
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (typeof mermaid.run === 'function') {
|
|
304
|
+
const originalRun = mermaid.run.bind(mermaid);
|
|
305
|
+
mermaid.run = async function(options) {
|
|
306
|
+
const result = await originalRun(options);
|
|
307
|
+
try {
|
|
308
|
+
if (options && Array.isArray(options.nodes) && options.nodes.length > 0) {
|
|
309
|
+
options.nodes.forEach((node) => suppressMermaidErrorArtifacts(node, {
|
|
310
|
+
source: 'mermaid.run.nodes',
|
|
311
|
+
}));
|
|
312
|
+
} else {
|
|
313
|
+
suppressMermaidErrorArtifacts(document.body, {
|
|
314
|
+
source: 'mermaid.run.document',
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
} catch (_error) {
|
|
318
|
+
// Guard should never break main rendering.
|
|
319
|
+
}
|
|
320
|
+
return result;
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
mermaid.__noteConnectionErrorGuardInstalled = true;
|
|
325
|
+
mermaidErrorGuardState.renderGuardInstalled = true;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function exposeMermaidDebugInterface() {
|
|
329
|
+
const existing = (window.__NC_DEBUG__ && typeof window.__NC_DEBUG__ === 'object')
|
|
330
|
+
? window.__NC_DEBUG__
|
|
331
|
+
: {};
|
|
332
|
+
window.__NC_DEBUG__ = {
|
|
333
|
+
...existing,
|
|
334
|
+
scanMermaidErrorArtifacts: () => collectMermaidErrorArtifactCandidates(document.body).map((host) => ({
|
|
335
|
+
tag: host.tagName || '',
|
|
336
|
+
id: host.id || '',
|
|
337
|
+
className: host.className || '',
|
|
338
|
+
text: normalizeMermaidErrorArtifactText(host.textContent || ''),
|
|
339
|
+
})),
|
|
340
|
+
suppressMermaidErrorArtifactsNow: () => {
|
|
341
|
+
suppressMermaidErrorArtifacts(document.body, { source: 'manual-debug-scan' });
|
|
342
|
+
return mermaidErrorGuardState.suppressedArtifacts.slice();
|
|
343
|
+
},
|
|
344
|
+
getMermaidGuardState: () => ({
|
|
345
|
+
renderGuardInstalled: mermaidErrorGuardState.renderGuardInstalled,
|
|
346
|
+
suppressedArtifacts: mermaidErrorGuardState.suppressedArtifacts.slice(),
|
|
347
|
+
}),
|
|
348
|
+
captureRuntimeState: () => ({
|
|
349
|
+
url: window.location.href,
|
|
350
|
+
title: document.title,
|
|
351
|
+
mermaidGuardState: {
|
|
352
|
+
renderGuardInstalled: mermaidErrorGuardState.renderGuardInstalled,
|
|
353
|
+
suppressedArtifacts: mermaidErrorGuardState.suppressedArtifacts.slice(),
|
|
354
|
+
},
|
|
355
|
+
activeMermaidErrors: collectMermaidErrorArtifactCandidates(document.body).map((host) => ({
|
|
356
|
+
tag: host.tagName || '',
|
|
357
|
+
id: host.id || '',
|
|
358
|
+
className: host.className || '',
|
|
359
|
+
text: normalizeMermaidErrorArtifactText(host.textContent || ''),
|
|
360
|
+
})),
|
|
361
|
+
}),
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function installMermaidErrorArtifactObserver() {
|
|
366
|
+
installMermaidRuntimeGuards();
|
|
367
|
+
exposeMermaidDebugInterface();
|
|
368
|
+
|
|
369
|
+
if (mermaidErrorGuardState.observer || typeof MutationObserver === 'undefined') {
|
|
370
|
+
suppressMermaidErrorArtifacts(document.body, { source: 'observer-reuse' });
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
mermaidErrorGuardState.observer = new MutationObserver((mutations) => {
|
|
375
|
+
mutations.forEach((mutation) => {
|
|
376
|
+
mutation.addedNodes.forEach((node) => {
|
|
377
|
+
if (!node || node.nodeType !== Node.ELEMENT_NODE) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
suppressMermaidErrorArtifacts(node, { source: 'mutation-observer' });
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
mermaidErrorGuardState.observer.observe(document.body, {
|
|
386
|
+
childList: true,
|
|
387
|
+
subtree: true,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
suppressMermaidErrorArtifacts(document.body, { source: 'observer-install' });
|
|
391
|
+
window.setTimeout(() => {
|
|
392
|
+
installMermaidRuntimeGuards();
|
|
393
|
+
suppressMermaidErrorArtifacts(document.body, { source: 'delayed-sweep-1200' });
|
|
394
|
+
}, 1200);
|
|
395
|
+
window.setTimeout(() => {
|
|
396
|
+
installMermaidRuntimeGuards();
|
|
397
|
+
suppressMermaidErrorArtifacts(document.body, { source: 'delayed-sweep-3200' });
|
|
398
|
+
}, 3200);
|
|
399
|
+
if (mermaidErrorGuardState.periodicSweepHandle === null) {
|
|
400
|
+
mermaidErrorGuardState.periodicSweepHandle = window.setInterval(() => {
|
|
401
|
+
installMermaidRuntimeGuards();
|
|
402
|
+
suppressMermaidErrorArtifacts(document.body, { source: 'periodic-sweep' });
|
|
403
|
+
}, 750);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
installMermaidErrorArtifactObserver();
|
|
408
|
+
|
|
30
409
|
function resolveRuntimePlatform(runtimeCaps) {
|
|
31
410
|
const rawPlatform = runtimeCaps && typeof runtimeCaps.platform === 'string'
|
|
32
411
|
? runtimeCaps.platform.trim().toLowerCase()
|
|
@@ -285,7 +664,9 @@ const startupLayoutSnapshotState = {
|
|
|
285
664
|
pendingRecord: null,
|
|
286
665
|
restorePromise: null,
|
|
287
666
|
saveHandle: null,
|
|
288
|
-
lastSaveAtMs: 0
|
|
667
|
+
lastSaveAtMs: 0,
|
|
668
|
+
sourceLayoutSummary: null,
|
|
669
|
+
sourceLayoutById: null
|
|
289
670
|
};
|
|
290
671
|
|
|
291
672
|
function hashFNV1a32(input) {
|
|
@@ -332,6 +713,177 @@ function buildStartupGraphFingerprint(nodeList, linkList, graphPayload) {
|
|
|
332
713
|
return `nc-layout-v${STARTUP_LAYOUT_SNAPSHOT_VERSION}-${hashFNV1a32(signature)}`;
|
|
333
714
|
}
|
|
334
715
|
|
|
716
|
+
function summarizeLayoutPositions(positionItems) {
|
|
717
|
+
const safeItems = Array.isArray(positionItems) ? positionItems : [];
|
|
718
|
+
const finitePositions = safeItems.filter((item) => (
|
|
719
|
+
item &&
|
|
720
|
+
Number.isFinite(Number(item.x)) &&
|
|
721
|
+
Number.isFinite(Number(item.y))
|
|
722
|
+
));
|
|
723
|
+
|
|
724
|
+
if (finitePositions.length === 0) {
|
|
725
|
+
return {
|
|
726
|
+
finiteCount: 0,
|
|
727
|
+
minX: 0,
|
|
728
|
+
maxX: 0,
|
|
729
|
+
minY: 0,
|
|
730
|
+
maxY: 0,
|
|
731
|
+
spanX: 0,
|
|
732
|
+
spanY: 0,
|
|
733
|
+
uniqueRatio: 0
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
let minX = Infinity;
|
|
738
|
+
let maxX = -Infinity;
|
|
739
|
+
let minY = Infinity;
|
|
740
|
+
let maxY = -Infinity;
|
|
741
|
+
const uniqueBuckets = new Set();
|
|
742
|
+
|
|
743
|
+
for (let index = 0; index < finitePositions.length; index += 1) {
|
|
744
|
+
const item = finitePositions[index];
|
|
745
|
+
const x = Number(item.x);
|
|
746
|
+
const y = Number(item.y);
|
|
747
|
+
if (x < minX) minX = x;
|
|
748
|
+
if (x > maxX) maxX = x;
|
|
749
|
+
if (y < minY) minY = y;
|
|
750
|
+
if (y > maxY) maxY = y;
|
|
751
|
+
uniqueBuckets.add(`${Math.round(x)}:${Math.round(y)}`);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
return {
|
|
755
|
+
finiteCount: finitePositions.length,
|
|
756
|
+
minX,
|
|
757
|
+
maxX,
|
|
758
|
+
minY,
|
|
759
|
+
maxY,
|
|
760
|
+
spanX: maxX - minX,
|
|
761
|
+
spanY: maxY - minY,
|
|
762
|
+
uniqueRatio: uniqueBuckets.size / Math.max(1, finitePositions.length)
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function isDegenerateLayoutSummary(summary) {
|
|
767
|
+
return Boolean(
|
|
768
|
+
summary &&
|
|
769
|
+
summary.finiteCount > 0 &&
|
|
770
|
+
((summary.spanX < 48 && summary.spanY < 48) || summary.uniqueRatio < 0.12)
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function isSnapshotLayoutCollapsedVsSource(summary, sourceSummary) {
|
|
775
|
+
if (!summary || !sourceSummary || sourceSummary.finiteCount < 10) {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const sourceWideX = sourceSummary.spanX >= 320;
|
|
780
|
+
const sourceWideY = sourceSummary.spanY >= 320;
|
|
781
|
+
if (!sourceWideX && !sourceWideY) {
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const spanXTooSmall = sourceWideX && summary.spanX < Math.max(72, sourceSummary.spanX * 0.08);
|
|
786
|
+
const spanYTooSmall = sourceWideY && summary.spanY < Math.max(72, sourceSummary.spanY * 0.08);
|
|
787
|
+
return spanXTooSmall && spanYTooSmall;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function buildSourceLayoutById(nodeList) {
|
|
791
|
+
const map = new Map();
|
|
792
|
+
const safeNodes = Array.isArray(nodeList) ? nodeList : [];
|
|
793
|
+
for (let index = 0; index < safeNodes.length; index += 1) {
|
|
794
|
+
const node = safeNodes[index];
|
|
795
|
+
if (!node || node.id === undefined || node.id === null) {
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
const x = Number(node.x);
|
|
799
|
+
const y = Number(node.y);
|
|
800
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
map.set(node.id, { x, y });
|
|
804
|
+
}
|
|
805
|
+
return map;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function restoreSourceLayoutOrJitterNodes(nodeList, viewportWidth, viewportHeight, reason = '') {
|
|
809
|
+
const safeNodes = Array.isArray(nodeList) ? nodeList : [];
|
|
810
|
+
if (safeNodes.length === 0) {
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const currentSummary = summarizeLayoutPositions(safeNodes);
|
|
815
|
+
const sourceSummary = startupLayoutSnapshotState.sourceLayoutSummary;
|
|
816
|
+
const sourceLayoutById = startupLayoutSnapshotState.sourceLayoutById;
|
|
817
|
+
const shouldRestoreSource = Boolean(
|
|
818
|
+
sourceLayoutById instanceof Map &&
|
|
819
|
+
sourceLayoutById.size > 0 &&
|
|
820
|
+
isSnapshotLayoutCollapsedVsSource(currentSummary, sourceSummary)
|
|
821
|
+
);
|
|
822
|
+
|
|
823
|
+
if (!isDegenerateLayoutSummary(currentSummary) && !shouldRestoreSource) {
|
|
824
|
+
return false;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (shouldRestoreSource) {
|
|
828
|
+
let restoredCount = 0;
|
|
829
|
+
for (let index = 0; index < safeNodes.length; index += 1) {
|
|
830
|
+
const node = safeNodes[index];
|
|
831
|
+
const sourcePos = sourceLayoutById.get(node.id);
|
|
832
|
+
if (!sourcePos) {
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
node.x = sourcePos.x;
|
|
836
|
+
node.y = sourcePos.y;
|
|
837
|
+
node.fx = null;
|
|
838
|
+
node.fy = null;
|
|
839
|
+
if (currentPositions && typeof currentPositions.set === 'function') {
|
|
840
|
+
currentPositions.set(node.id, { x: node.x, y: node.y });
|
|
841
|
+
}
|
|
842
|
+
restoredCount += 1;
|
|
843
|
+
}
|
|
844
|
+
console.warn('[Startup Warm Snapshot] Restored source layout because active positions collapsed relative to source.', {
|
|
845
|
+
reason,
|
|
846
|
+
restoredCount,
|
|
847
|
+
currentSpanX: Number(currentSummary.spanX.toFixed(2)),
|
|
848
|
+
currentSpanY: Number(currentSummary.spanY.toFixed(2)),
|
|
849
|
+
sourceSpanX: sourceSummary ? Number(sourceSummary.spanX.toFixed(2)) : null,
|
|
850
|
+
sourceSpanY: sourceSummary ? Number(sourceSummary.spanY.toFixed(2)) : null
|
|
851
|
+
});
|
|
852
|
+
return restoredCount > 0;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const centerX = Number.isFinite(Number(viewportWidth)) && Number(viewportWidth) > 0
|
|
856
|
+
? Number(viewportWidth) / 2
|
|
857
|
+
: 400;
|
|
858
|
+
const centerY = Number.isFinite(Number(viewportHeight)) && Number(viewportHeight) > 0
|
|
859
|
+
? Number(viewportHeight) / 2
|
|
860
|
+
: 300;
|
|
861
|
+
const goldenAngle = Math.PI * (3 - Math.sqrt(5));
|
|
862
|
+
|
|
863
|
+
for (let index = 0; index < safeNodes.length; index += 1) {
|
|
864
|
+
const node = safeNodes[index];
|
|
865
|
+
const rankOffset = Number.isFinite(Number(node.rank)) ? Number(node.rank) * 4 : 0;
|
|
866
|
+
const radius = 28 + (Math.sqrt(index + 1) * 18) + rankOffset;
|
|
867
|
+
const angle = (index * goldenAngle) + (rankOffset * 0.017);
|
|
868
|
+
node.x = centerX + (Math.cos(angle) * radius);
|
|
869
|
+
node.y = centerY + (Math.sin(angle) * radius);
|
|
870
|
+
node.fx = null;
|
|
871
|
+
node.fy = null;
|
|
872
|
+
if (currentPositions && typeof currentPositions.set === 'function') {
|
|
873
|
+
currentPositions.set(node.id, { x: node.x, y: node.y });
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
console.warn('[Startup Warm Snapshot] Re-seeded degenerate initial layout before simulation bootstrap.', {
|
|
878
|
+
reason,
|
|
879
|
+
nodeCount: safeNodes.length,
|
|
880
|
+
spanX: Number(currentSummary.spanX.toFixed(2)),
|
|
881
|
+
spanY: Number(currentSummary.spanY.toFixed(2)),
|
|
882
|
+
uniqueRatio: Number(currentSummary.uniqueRatio.toFixed(4))
|
|
883
|
+
});
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
|
|
335
887
|
function openStartupLayoutSnapshotDb() {
|
|
336
888
|
return new Promise((resolve, reject) => {
|
|
337
889
|
if (typeof window === 'undefined' || !window.indexedDB) {
|
|
@@ -457,6 +1009,36 @@ async function persistStartupLayoutSnapshotRecord(record) {
|
|
|
457
1009
|
db.close();
|
|
458
1010
|
}
|
|
459
1011
|
|
|
1012
|
+
async function deleteStartupLayoutSnapshotRecord(fingerprint) {
|
|
1013
|
+
if (!fingerprint) {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
if (typeof localStorage !== 'undefined') {
|
|
1018
|
+
try {
|
|
1019
|
+
localStorage.removeItem(`${STARTUP_LAYOUT_SNAPSHOT_LS_PREFIX}${fingerprint}`);
|
|
1020
|
+
} catch (_err) {
|
|
1021
|
+
// Ignore localStorage cleanup failures.
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
const db = await openStartupLayoutSnapshotDb();
|
|
1026
|
+
if (!db) {
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
await new Promise((resolve, reject) => {
|
|
1031
|
+
const tx = db.transaction(STARTUP_LAYOUT_SNAPSHOT_STORE_NAME, 'readwrite');
|
|
1032
|
+
const store = tx.objectStore(STARTUP_LAYOUT_SNAPSHOT_STORE_NAME);
|
|
1033
|
+
store.delete(fingerprint);
|
|
1034
|
+
tx.oncomplete = () => resolve();
|
|
1035
|
+
tx.onerror = () => reject(tx.error || new Error('indexedDB delete failed'));
|
|
1036
|
+
tx.onabort = () => reject(tx.error || new Error('indexedDB delete aborted'));
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
db.close();
|
|
1040
|
+
}
|
|
1041
|
+
|
|
460
1042
|
function collectStartupLayoutSnapshotRecord(reason = '') {
|
|
461
1043
|
if (!startupLayoutSnapshotState.fingerprint || !Array.isArray(nodes) || nodes.length === 0) {
|
|
462
1044
|
return null;
|
|
@@ -469,8 +1051,8 @@ function collectStartupLayoutSnapshotRecord(reason = '') {
|
|
|
469
1051
|
id: n.id,
|
|
470
1052
|
x: Number.isFinite(Number(n.x)) ? Number(n.x) : 0,
|
|
471
1053
|
y: Number.isFinite(Number(n.y)) ? Number(n.y) : 0,
|
|
472
|
-
fx:
|
|
473
|
-
fy:
|
|
1054
|
+
fx: null,
|
|
1055
|
+
fy: null
|
|
474
1056
|
};
|
|
475
1057
|
}
|
|
476
1058
|
|
|
@@ -515,6 +1097,37 @@ function validateStartupLayoutSnapshotRecord(record) {
|
|
|
515
1097
|
return { ok: false, reason: 'position-coverage-low', coverage: Number(coverage.toFixed(4)) };
|
|
516
1098
|
}
|
|
517
1099
|
|
|
1100
|
+
const positionSummary = summarizeLayoutPositions(record.positions);
|
|
1101
|
+
if (expectedNodeCount >= 10 && positionSummary.finiteCount > 0) {
|
|
1102
|
+
if (isDegenerateLayoutSummary(positionSummary)) {
|
|
1103
|
+
return {
|
|
1104
|
+
ok: false,
|
|
1105
|
+
reason: 'degenerate-layout',
|
|
1106
|
+
purge: true,
|
|
1107
|
+
spanX: Number(positionSummary.spanX.toFixed(2)),
|
|
1108
|
+
spanY: Number(positionSummary.spanY.toFixed(2)),
|
|
1109
|
+
uniqueRatio: Number(positionSummary.uniqueRatio.toFixed(4)),
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
if (isSnapshotLayoutCollapsedVsSource(positionSummary, startupLayoutSnapshotState.sourceLayoutSummary)) {
|
|
1114
|
+
return {
|
|
1115
|
+
ok: false,
|
|
1116
|
+
reason: 'degenerate-layout-vs-source',
|
|
1117
|
+
purge: true,
|
|
1118
|
+
spanX: Number(positionSummary.spanX.toFixed(2)),
|
|
1119
|
+
spanY: Number(positionSummary.spanY.toFixed(2)),
|
|
1120
|
+
uniqueRatio: Number(positionSummary.uniqueRatio.toFixed(4)),
|
|
1121
|
+
sourceSpanX: startupLayoutSnapshotState.sourceLayoutSummary
|
|
1122
|
+
? Number(startupLayoutSnapshotState.sourceLayoutSummary.spanX.toFixed(2))
|
|
1123
|
+
: null,
|
|
1124
|
+
sourceSpanY: startupLayoutSnapshotState.sourceLayoutSummary
|
|
1125
|
+
? Number(startupLayoutSnapshotState.sourceLayoutSummary.spanY.toFixed(2))
|
|
1126
|
+
: null
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
518
1131
|
return {
|
|
519
1132
|
ok: true,
|
|
520
1133
|
coverage: Number(coverage.toFixed(4)),
|
|
@@ -539,8 +1152,8 @@ function applyStartupLayoutSnapshotRecord(record) {
|
|
|
539
1152
|
}
|
|
540
1153
|
n.x = Number.isFinite(saved.x) ? saved.x : n.x;
|
|
541
1154
|
n.y = Number.isFinite(saved.y) ? saved.y : n.y;
|
|
542
|
-
n.fx =
|
|
543
|
-
n.fy =
|
|
1155
|
+
n.fx = null;
|
|
1156
|
+
n.fy = null;
|
|
544
1157
|
if (currentPositions && typeof currentPositions.set === 'function') {
|
|
545
1158
|
currentPositions.set(n.id, { x: n.x, y: n.y });
|
|
546
1159
|
}
|
|
@@ -568,8 +1181,16 @@ function maybeApplyStartupWarmSnapshot(trigger = '') {
|
|
|
568
1181
|
recordFingerprint: record.fingerprint || null,
|
|
569
1182
|
recordNodeCount: record.nodeCount || 0,
|
|
570
1183
|
recordEdgeCount: record.edgeCount || 0,
|
|
571
|
-
positionCount: Array.isArray(record.positions) ? record.positions.length : 0
|
|
1184
|
+
positionCount: Array.isArray(record.positions) ? record.positions.length : 0,
|
|
1185
|
+
spanX: validation.spanX,
|
|
1186
|
+
spanY: validation.spanY,
|
|
1187
|
+
uniqueRatio: validation.uniqueRatio,
|
|
572
1188
|
});
|
|
1189
|
+
if (validation.purge === true && record.fingerprint) {
|
|
1190
|
+
deleteStartupLayoutSnapshotRecord(record.fingerprint).catch((error) => {
|
|
1191
|
+
console.warn('[Startup Warm Snapshot] Failed to purge invalid snapshot record:', error && error.message ? error.message : String(error));
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
573
1194
|
startupLayoutSnapshotState.pendingRecord = null;
|
|
574
1195
|
return false;
|
|
575
1196
|
}
|
|
@@ -1094,6 +1715,17 @@ markStartupCheckpoint('T1 graph_preprocessed', {
|
|
|
1094
1715
|
preprocessMs: Number(graphPreprocessElapsedMs.toFixed(2))
|
|
1095
1716
|
});
|
|
1096
1717
|
|
|
1718
|
+
startupLayoutSnapshotState.sourceLayoutById = buildSourceLayoutById(nodes);
|
|
1719
|
+
startupLayoutSnapshotState.sourceLayoutSummary = summarizeLayoutPositions(nodes);
|
|
1720
|
+
if (startupLayoutSnapshotState.sourceLayoutSummary.finiteCount > 0) {
|
|
1721
|
+
console.log('[Startup Warm Snapshot] Source layout summary prepared.', {
|
|
1722
|
+
finiteCount: startupLayoutSnapshotState.sourceLayoutSummary.finiteCount,
|
|
1723
|
+
spanX: Number(startupLayoutSnapshotState.sourceLayoutSummary.spanX.toFixed(2)),
|
|
1724
|
+
spanY: Number(startupLayoutSnapshotState.sourceLayoutSummary.spanY.toFixed(2)),
|
|
1725
|
+
uniqueRatio: Number(startupLayoutSnapshotState.sourceLayoutSummary.uniqueRatio.toFixed(4))
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1097
1729
|
startupLayoutSnapshotState.fingerprint = buildStartupGraphFingerprint(nodes, links, runtimeGraphData);
|
|
1098
1730
|
console.log('[Startup Warm Snapshot] Fingerprint prepared.', {
|
|
1099
1731
|
fingerprint: startupLayoutSnapshotState.fingerprint,
|
|
@@ -1491,6 +2123,7 @@ const simulation = {
|
|
|
1491
2123
|
const workerPayloadBuildStartTs = (typeof performance !== 'undefined' && typeof performance.now === 'function')
|
|
1492
2124
|
? performance.now()
|
|
1493
2125
|
: Date.now();
|
|
2126
|
+
restoreSourceLayoutOrJitterNodes(nodes, width, height, 'worker-init');
|
|
1494
2127
|
const workerNodes = new Array(nodes.length);
|
|
1495
2128
|
for (let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex += 1) {
|
|
1496
2129
|
const node = nodes[nodeIndex];
|
|
@@ -2480,11 +3113,23 @@ function handleDoubleClick(event, d) {
|
|
|
2480
3113
|
// v0.9.20 Enhancement: Auto-clear selection state when entering focus mode
|
|
2481
3114
|
// v0.9.20 增强:进入专注模式时自动清除选择状态
|
|
2482
3115
|
|
|
2483
|
-
|
|
3116
|
+
const focusInteractions = window.NoteConnectionFocusModeInteractions;
|
|
3117
|
+
const focusAction = focusInteractions && typeof focusInteractions.resolveDoubleClickAction === 'function'
|
|
3118
|
+
? focusInteractions.resolveDoubleClickAction({
|
|
3119
|
+
currentAnchorId: focusNode && focusNode.id,
|
|
3120
|
+
clickedNodeId: d && d.id,
|
|
3121
|
+
})
|
|
3122
|
+
: {
|
|
3123
|
+
action: focusNode && focusNode.id === d.id ? 'open-reader' : 'switch-focus',
|
|
3124
|
+
clickedNodeId: d && d.id,
|
|
3125
|
+
currentAnchorId: focusNode && focusNode.id,
|
|
3126
|
+
};
|
|
3127
|
+
|
|
3128
|
+
if (focusAction.action === 'open-reader') {
|
|
2484
3129
|
// Already focused on same node -> Open Reader
|
|
2485
3130
|
// 已经专注于同一节点 -> 打开阅读器
|
|
2486
3131
|
if (window.reader) window.reader.open(d);
|
|
2487
|
-
} else {
|
|
3132
|
+
} else if (focusAction.action === 'switch-focus') {
|
|
2488
3133
|
// Clear any existing selection/highlight state before entering focus mode
|
|
2489
3134
|
// 在进入专注模式前清除任何现有的选择/高亮状态
|
|
2490
3135
|
if (window.highlightManager) {
|
|
@@ -3727,43 +4372,595 @@ window.focusOnNode = function(id) {
|
|
|
3727
4372
|
}
|
|
3728
4373
|
};
|
|
3729
4374
|
|
|
4375
|
+
function normalizeGraphViewText(value) {
|
|
4376
|
+
return String(value || '').replace(/\s+/g, ' ').trim();
|
|
4377
|
+
}
|
|
3730
4378
|
|
|
4379
|
+
function normalizeGraphViewLookupKey(value) {
|
|
4380
|
+
return normalizeGraphViewText(value).toLowerCase();
|
|
4381
|
+
}
|
|
3731
4382
|
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
4383
|
+
function resolveGraphViewSourceBasename(sourcePath) {
|
|
4384
|
+
const normalized = String(sourcePath || '').replace(/\\/g, '/').trim();
|
|
4385
|
+
if (!normalized) {
|
|
4386
|
+
return '';
|
|
4387
|
+
}
|
|
4388
|
+
const fileName = normalized.split('/').filter(Boolean).pop() || normalized;
|
|
4389
|
+
return fileName.replace(/\.[^/.]+$/, '').trim();
|
|
4390
|
+
}
|
|
3735
4391
|
|
|
3736
|
-
function
|
|
3737
|
-
|
|
3738
|
-
// Requirement: "pin the corresponding node to the top"
|
|
3739
|
-
window.focusHistory = window.focusHistory.filter(n => n.id !== newNode.id);
|
|
3740
|
-
|
|
3741
|
-
// Add to specific history list
|
|
3742
|
-
window.focusHistory.unshift(newNode);
|
|
3743
|
-
if (window.focusHistory.length > MAX_HISTORY) window.focusHistory.pop();
|
|
3744
|
-
|
|
3745
|
-
renderFocusHistory();
|
|
4392
|
+
function getGraphViewNodeLabel(node) {
|
|
4393
|
+
return normalizeGraphViewText(node && (node.label || node.title || node.name || node.id));
|
|
3746
4394
|
}
|
|
3747
4395
|
|
|
3748
|
-
function
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
container.innerHTML = '';
|
|
3753
|
-
|
|
3754
|
-
if (window.focusHistory.length === 0) {
|
|
3755
|
-
container.innerHTML = '<div style="padding:5px; color:#aaa; font-style:italic">No history</div>';
|
|
3756
|
-
return;
|
|
4396
|
+
function getGraphViewLinkNodeId(endpoint) {
|
|
4397
|
+
if (endpoint && typeof endpoint === 'object') {
|
|
4398
|
+
return normalizeGraphViewText(endpoint.id || endpoint.nodeId || endpoint.key);
|
|
3757
4399
|
}
|
|
4400
|
+
return normalizeGraphViewText(endpoint);
|
|
4401
|
+
}
|
|
3758
4402
|
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
4403
|
+
function collectGraphViewKnowledgePointCandidates(payload) {
|
|
4404
|
+
const candidates = [];
|
|
4405
|
+
const seen = new Set();
|
|
4406
|
+
const appendCandidate = (value) => {
|
|
4407
|
+
const normalized = normalizeGraphViewText(value);
|
|
4408
|
+
if (!normalized || seen.has(normalized)) {
|
|
4409
|
+
return;
|
|
4410
|
+
}
|
|
4411
|
+
seen.add(normalized);
|
|
4412
|
+
candidates.push(normalized);
|
|
4413
|
+
};
|
|
4414
|
+
appendCandidate(payload && payload.graphNodeId);
|
|
4415
|
+
appendCandidate(payload && payload.graphTargetId);
|
|
4416
|
+
appendCandidate(payload && payload.nodeId);
|
|
4417
|
+
appendCandidate(payload && payload.documentId);
|
|
4418
|
+
appendCandidate(payload && payload.title);
|
|
4419
|
+
appendCandidate(payload && payload.label);
|
|
4420
|
+
appendCandidate(payload && payload.sourceBasename);
|
|
4421
|
+
appendCandidate(resolveGraphViewSourceBasename(payload && payload.sourcePath));
|
|
4422
|
+
appendCandidate(payload && payload.atomId);
|
|
4423
|
+
if (Array.isArray(payload && payload.atomIds)) {
|
|
4424
|
+
payload.atomIds.forEach(appendCandidate);
|
|
4425
|
+
}
|
|
4426
|
+
if (Array.isArray(payload && payload.nodeIds)) {
|
|
4427
|
+
payload.nodeIds.forEach(appendCandidate);
|
|
4428
|
+
}
|
|
4429
|
+
if (Array.isArray(payload && payload.matchedSpans)) {
|
|
4430
|
+
payload.matchedSpans.forEach((span) => {
|
|
4431
|
+
appendCandidate(span && span.title);
|
|
4432
|
+
appendCandidate(span && span.atomId);
|
|
4433
|
+
appendCandidate(resolveGraphViewSourceBasename(span && span.sourcePath));
|
|
4434
|
+
});
|
|
4435
|
+
}
|
|
4436
|
+
return candidates;
|
|
4437
|
+
}
|
|
4438
|
+
|
|
4439
|
+
function resolveGraphViewNodeByIdOrLabel(candidate) {
|
|
4440
|
+
const normalized = normalizeGraphViewText(candidate);
|
|
4441
|
+
if (!normalized || !Array.isArray(nodes)) {
|
|
4442
|
+
return null;
|
|
4443
|
+
}
|
|
4444
|
+
const exactId = nodes.find((node) => normalizeGraphViewText(node && node.id) === normalized);
|
|
4445
|
+
if (exactId) {
|
|
4446
|
+
return exactId;
|
|
4447
|
+
}
|
|
4448
|
+
const lookupKey = normalizeGraphViewLookupKey(normalized);
|
|
4449
|
+
const exactLabelMatches = nodes.filter((node) => normalizeGraphViewLookupKey(getGraphViewNodeLabel(node)) === lookupKey);
|
|
4450
|
+
if (exactLabelMatches.length === 1) {
|
|
4451
|
+
return exactLabelMatches[0];
|
|
4452
|
+
}
|
|
4453
|
+
const sourceMatches = nodes.filter((node) => {
|
|
4454
|
+
const metadata = node && typeof node.metadata === 'object' ? node.metadata : {};
|
|
4455
|
+
return normalizeGraphViewLookupKey(resolveGraphViewSourceBasename(node && (node.sourcePath || node.filepath || metadata.filepath || metadata.sourcePath))) === lookupKey;
|
|
4456
|
+
});
|
|
4457
|
+
return sourceMatches.length === 1 ? sourceMatches[0] : null;
|
|
4458
|
+
}
|
|
4459
|
+
|
|
4460
|
+
function resolveGraphViewNodeByKnowledgePoint(payload) {
|
|
4461
|
+
const candidates = collectGraphViewKnowledgePointCandidates(payload);
|
|
4462
|
+
for (const candidate of candidates) {
|
|
4463
|
+
const node = resolveGraphViewNodeByIdOrLabel(candidate);
|
|
4464
|
+
if (node) {
|
|
4465
|
+
return node;
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
return null;
|
|
4469
|
+
}
|
|
4470
|
+
|
|
4471
|
+
function buildGraphViewFocusModeSnapshot(nodeId) {
|
|
4472
|
+
const focusD = resolveGraphViewNodeByIdOrLabel(nodeId);
|
|
4473
|
+
if (!focusD) {
|
|
4474
|
+
return null;
|
|
4475
|
+
}
|
|
4476
|
+
const incoming = [];
|
|
4477
|
+
const outgoing = [];
|
|
4478
|
+
const linkList = Array.isArray(links) ? links : [];
|
|
4479
|
+
linkList.forEach((linkItem) => {
|
|
4480
|
+
const sourceId = getGraphViewLinkNodeId(linkItem && linkItem.source);
|
|
4481
|
+
const targetId = getGraphViewLinkNodeId(linkItem && linkItem.target);
|
|
4482
|
+
if (sourceId === focusD.id) {
|
|
4483
|
+
const targetNode = resolveGraphViewNodeByIdOrLabel(targetId);
|
|
4484
|
+
if (targetNode) {
|
|
4485
|
+
outgoing.push(targetNode);
|
|
4486
|
+
}
|
|
4487
|
+
} else if (targetId === focusD.id) {
|
|
4488
|
+
const sourceNode = resolveGraphViewNodeByIdOrLabel(sourceId);
|
|
4489
|
+
if (sourceNode) {
|
|
4490
|
+
incoming.push(sourceNode);
|
|
4491
|
+
}
|
|
4492
|
+
}
|
|
4493
|
+
});
|
|
4494
|
+
|
|
4495
|
+
const uniqueById = (items) => {
|
|
4496
|
+
const seen = new Set();
|
|
4497
|
+
const unique = [];
|
|
4498
|
+
items.forEach((item) => {
|
|
4499
|
+
const id = normalizeGraphViewText(item && item.id);
|
|
4500
|
+
if (!id || seen.has(id)) {
|
|
4501
|
+
return;
|
|
4502
|
+
}
|
|
4503
|
+
seen.add(id);
|
|
4504
|
+
unique.push(item);
|
|
4505
|
+
});
|
|
4506
|
+
return unique;
|
|
4507
|
+
};
|
|
4508
|
+
const incomingNodes = uniqueById(incoming).slice(0, 5);
|
|
4509
|
+
const outgoingNodes = uniqueById(outgoing).slice(0, 5);
|
|
4510
|
+
const snapshotNodes = [];
|
|
4511
|
+
const addSnapshotNode = (node, role, x, y) => {
|
|
4512
|
+
snapshotNodes.push({
|
|
4513
|
+
id: normalizeGraphViewText(node && node.id),
|
|
4514
|
+
label: getGraphViewNodeLabel(node),
|
|
4515
|
+
role,
|
|
4516
|
+
x,
|
|
4517
|
+
y,
|
|
4518
|
+
});
|
|
4519
|
+
};
|
|
4520
|
+
const placeLane = (nodeList, role, x) => {
|
|
4521
|
+
const step = 80 / (nodeList.length + 1);
|
|
4522
|
+
nodeList.forEach((node, index) => {
|
|
4523
|
+
addSnapshotNode(node, role, x, 10 + step * (index + 1));
|
|
4524
|
+
});
|
|
4525
|
+
};
|
|
4526
|
+
placeLane(incomingNodes, 'incoming', 22);
|
|
4527
|
+
addSnapshotNode(focusD, 'anchor', 50, 50);
|
|
4528
|
+
placeLane(outgoingNodes, 'outgoing', 78);
|
|
4529
|
+
|
|
4530
|
+
const visibleIds = new Set(snapshotNodes.map((node) => node.id));
|
|
4531
|
+
const snapshotEdges = linkList
|
|
4532
|
+
.map((linkItem) => {
|
|
4533
|
+
const sourceId = getGraphViewLinkNodeId(linkItem && linkItem.source);
|
|
4534
|
+
const targetId = getGraphViewLinkNodeId(linkItem && linkItem.target);
|
|
4535
|
+
if (!sourceId || !targetId || !visibleIds.has(sourceId) || !visibleIds.has(targetId)) {
|
|
4536
|
+
return null;
|
|
4537
|
+
}
|
|
4538
|
+
return {
|
|
4539
|
+
sourceId,
|
|
4540
|
+
targetId,
|
|
4541
|
+
relationKind: normalizeGraphViewText(linkItem && (linkItem.type || linkItem.relationKind || linkItem.kind)) || 'related',
|
|
4542
|
+
confidence: Number(linkItem && (linkItem.confidence || linkItem.weight)),
|
|
4543
|
+
};
|
|
4544
|
+
})
|
|
4545
|
+
.filter(Boolean);
|
|
4546
|
+
|
|
4547
|
+
return {
|
|
4548
|
+
anchorId: focusD.id,
|
|
4549
|
+
anchorLabel: getGraphViewNodeLabel(focusD),
|
|
4550
|
+
nodes: snapshotNodes,
|
|
4551
|
+
edges: snapshotEdges,
|
|
4552
|
+
};
|
|
4553
|
+
}
|
|
4554
|
+
|
|
4555
|
+
function buildGraphViewFocusModeProjection(nodeId, options = {}) {
|
|
4556
|
+
const focusD = resolveGraphViewNodeByIdOrLabel(nodeId);
|
|
4557
|
+
if (!focusD) {
|
|
4558
|
+
return null;
|
|
4559
|
+
}
|
|
4560
|
+
const linkList = Array.isArray(links) ? links : [];
|
|
4561
|
+
const uniqueById = (items) => {
|
|
4562
|
+
const seen = new Set();
|
|
4563
|
+
const unique = [];
|
|
4564
|
+
items.forEach((item) => {
|
|
4565
|
+
const id = normalizeGraphViewText(item && item.id);
|
|
4566
|
+
if (!id || seen.has(id)) {
|
|
4567
|
+
return;
|
|
4568
|
+
}
|
|
4569
|
+
seen.add(id);
|
|
4570
|
+
unique.push(item);
|
|
4571
|
+
});
|
|
4572
|
+
return unique;
|
|
4573
|
+
};
|
|
4574
|
+
const getEndpointNode = (endpoint) => {
|
|
4575
|
+
if (endpoint && typeof endpoint === 'object') {
|
|
4576
|
+
const endpointId = normalizeGraphViewText(endpoint.id || endpoint.nodeId || endpoint.key);
|
|
4577
|
+
return endpointId ? resolveGraphViewNodeByIdOrLabel(endpointId) || endpoint : null;
|
|
4578
|
+
}
|
|
4579
|
+
return resolveGraphViewNodeByIdOrLabel(endpoint);
|
|
4580
|
+
};
|
|
4581
|
+
const getLinkWeight = (linkItem) => {
|
|
4582
|
+
const numericWeight = Number(linkItem && (linkItem.weight || linkItem.confidence));
|
|
4583
|
+
return Number.isFinite(numericWeight) ? numericWeight : 0.5;
|
|
4584
|
+
};
|
|
4585
|
+
const getRelationKind = (linkItem) => (
|
|
4586
|
+
normalizeGraphViewText(linkItem && (linkItem.type || linkItem.relationKind || linkItem.kind)) || 'related'
|
|
4587
|
+
);
|
|
4588
|
+
const outgoing = [];
|
|
4589
|
+
const incoming = [];
|
|
4590
|
+
const focusEdgeMap = new Map();
|
|
4591
|
+
linkList.forEach((linkItem) => {
|
|
4592
|
+
const sourceId = getGraphViewLinkNodeId(linkItem && linkItem.source);
|
|
4593
|
+
const targetId = getGraphViewLinkNodeId(linkItem && linkItem.target);
|
|
4594
|
+
if (!sourceId || !targetId) {
|
|
4595
|
+
return;
|
|
4596
|
+
}
|
|
4597
|
+
if (sourceId === focusD.id || targetId === focusD.id) {
|
|
4598
|
+
focusEdgeMap.set(`${sourceId}-${targetId}`, linkItem);
|
|
4599
|
+
}
|
|
4600
|
+
if (sourceId === focusD.id) {
|
|
4601
|
+
const targetNode = getEndpointNode(linkItem && linkItem.target);
|
|
4602
|
+
if (targetNode) {
|
|
4603
|
+
outgoing.push(targetNode);
|
|
4604
|
+
}
|
|
4605
|
+
return;
|
|
4606
|
+
}
|
|
4607
|
+
if (targetId === focusD.id) {
|
|
4608
|
+
const sourceNode = getEndpointNode(linkItem && linkItem.source);
|
|
4609
|
+
if (sourceNode) {
|
|
4610
|
+
incoming.push(sourceNode);
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
});
|
|
4614
|
+
const scoreNode = (node) => {
|
|
4615
|
+
const key1 = `${focusD.id}-${node.id}`;
|
|
4616
|
+
const key2 = `${node.id}-${focusD.id}`;
|
|
4617
|
+
const edge = focusEdgeMap.get(key1) || focusEdgeMap.get(key2);
|
|
4618
|
+
const weight = getLinkWeight(edge);
|
|
4619
|
+
const degreeRatio = (Number(node && node.outDegree) || 0) / ((Number(node && node.inDegree) || 0) + 1);
|
|
4620
|
+
const normalizedDegreeRatio = Math.min(degreeRatio, 5) / 5;
|
|
4621
|
+
return (weight * 0.7) + (normalizedDegreeRatio * 0.3);
|
|
4622
|
+
};
|
|
4623
|
+
const sortByFocusScore = (left, right) => scoreNode(right) - scoreNode(left);
|
|
4624
|
+
const incomingNodes = uniqueById(incoming).sort(sortByFocusScore);
|
|
4625
|
+
const outgoingNodes = uniqueById(outgoing).sort(sortByFocusScore);
|
|
4626
|
+
const activeIdSet = new Set([focusD.id].concat(
|
|
4627
|
+
incomingNodes.map((node) => node.id),
|
|
4628
|
+
outgoingNodes.map((node) => node.id)
|
|
4629
|
+
));
|
|
4630
|
+
const associatedNodes = uniqueById(linkList
|
|
4631
|
+
.map((linkItem) => {
|
|
4632
|
+
const sourceId = getGraphViewLinkNodeId(linkItem && linkItem.source);
|
|
4633
|
+
const targetId = getGraphViewLinkNodeId(linkItem && linkItem.target);
|
|
4634
|
+
if ((sourceId !== focusD.id && targetId !== focusD.id) || getLinkWeight(linkItem) <= 0.6) {
|
|
4635
|
+
return null;
|
|
4636
|
+
}
|
|
4637
|
+
const otherId = sourceId === focusD.id ? targetId : sourceId;
|
|
4638
|
+
if (!otherId || activeIdSet.has(otherId)) {
|
|
4639
|
+
return null;
|
|
4640
|
+
}
|
|
4641
|
+
return getEndpointNode(sourceId === focusD.id ? linkItem.target : linkItem.source);
|
|
4642
|
+
})
|
|
4643
|
+
.filter(Boolean))
|
|
4644
|
+
.sort(sortByFocusScore);
|
|
4645
|
+
associatedNodes.forEach((node) => activeIdSet.add(node.id));
|
|
4646
|
+
|
|
4647
|
+
const currentWidth = Number.isFinite(Number(width)) ? Number(width) : 1200;
|
|
4648
|
+
const currentHeight = Number.isFinite(Number(height)) ? Number(height) : 800;
|
|
4649
|
+
const cx = Number.isFinite(Number(focusD.x)) ? Number(focusD.x) : currentWidth / 2;
|
|
4650
|
+
const cy = Number.isFinite(Number(focusD.y)) ? Number(focusD.y) : currentHeight / 2;
|
|
4651
|
+
const selectedLayoutType = normalizeGraphViewText(options.layoutType)
|
|
4652
|
+
|| (document.getElementById('focus-layout-select') ? document.getElementById('focus-layout-select').value : '')
|
|
4653
|
+
|| 'horizontal';
|
|
4654
|
+
const layoutType = selectedLayoutType === 'vertical' ? 'vertical' : 'horizontal';
|
|
4655
|
+
const defaultLayerGap = focusSpacingSettings && focusSpacingSettings[layoutType]
|
|
4656
|
+
? Number(focusSpacingSettings[layoutType].layer)
|
|
4657
|
+
: (layoutType === 'horizontal' ? 125 : 250);
|
|
4658
|
+
const defaultNodeGap = focusSpacingSettings && focusSpacingSettings[layoutType]
|
|
4659
|
+
? Number(focusSpacingSettings[layoutType].node)
|
|
4660
|
+
: (layoutType === 'horizontal' ? 80 : 20);
|
|
4661
|
+
const layerGap = Number.isFinite(Number(options.layerGap)) ? Number(options.layerGap) : defaultLayerGap;
|
|
4662
|
+
const nodeGap = Number.isFinite(Number(options.nodeGap)) ? Number(options.nodeGap) : defaultNodeGap;
|
|
4663
|
+
const projectionNodes = [];
|
|
4664
|
+
const projectionLabels = [];
|
|
4665
|
+
const points = new Map();
|
|
4666
|
+
const addProjectionNode = (node, role, x, y, labelDy, labelDx) => {
|
|
4667
|
+
const id = normalizeGraphViewText(node && node.id);
|
|
4668
|
+
if (!id || points.has(id)) {
|
|
4669
|
+
return;
|
|
4670
|
+
}
|
|
4671
|
+
points.set(id, { x, y });
|
|
4672
|
+
projectionNodes.push({
|
|
4673
|
+
id,
|
|
4674
|
+
label: getGraphViewNodeLabel(node) || id,
|
|
4675
|
+
role,
|
|
4676
|
+
x,
|
|
4677
|
+
y,
|
|
4678
|
+
score: Number(scoreNode(node).toFixed(4)),
|
|
4679
|
+
inDegree: Number(node && node.inDegree) || 0,
|
|
4680
|
+
outDegree: Number(node && node.outDegree) || 0,
|
|
4681
|
+
sourcePath: normalizeGraphViewText(node && (
|
|
4682
|
+
node.sourcePath
|
|
4683
|
+
|| node.filepath
|
|
4684
|
+
|| (node.metadata && (node.metadata.sourcePath || node.metadata.filepath))
|
|
4685
|
+
)),
|
|
4686
|
+
radius: role === 'anchor' ? 25 : 8,
|
|
4687
|
+
labelDy,
|
|
4688
|
+
labelDx,
|
|
4689
|
+
});
|
|
4690
|
+
};
|
|
4691
|
+
addProjectionNode(focusD, 'anchor', cx, cy, 35, layoutType === 'vertical' ? 25 : 29);
|
|
4692
|
+
if (layoutType === 'vertical') {
|
|
4693
|
+
const spreadVertical = (nodeList, baselineX, role) => {
|
|
4694
|
+
const totalHeight = (nodeList.length - 1) * nodeGap;
|
|
4695
|
+
const startY = cy - totalHeight / 2;
|
|
4696
|
+
nodeList.forEach((node, index) => {
|
|
4697
|
+
addProjectionNode(node, role, baselineX, startY + (index * nodeGap), 25, 25);
|
|
4698
|
+
});
|
|
4699
|
+
};
|
|
4700
|
+
spreadVertical(incomingNodes, cx - layerGap, 'incoming');
|
|
4701
|
+
spreadVertical(outgoingNodes, cx + layerGap, 'outgoing');
|
|
4702
|
+
projectionLabels.push({
|
|
4703
|
+
text: typeof t === 'function' ? t('focus_inbound') : 'Inbound',
|
|
4704
|
+
x: cx - layerGap,
|
|
4705
|
+
y: cy - (incomingNodes.length * nodeGap / 2) - 40,
|
|
4706
|
+
align: 'middle',
|
|
4707
|
+
role: 'incoming',
|
|
4708
|
+
});
|
|
4709
|
+
projectionLabels.push({
|
|
4710
|
+
text: typeof t === 'function' ? t('focus_outbound') : 'Outbound',
|
|
4711
|
+
x: cx + layerGap,
|
|
4712
|
+
y: cy - (outgoingNodes.length * nodeGap / 2) - 40,
|
|
4713
|
+
align: 'middle',
|
|
4714
|
+
role: 'outgoing',
|
|
4715
|
+
});
|
|
4716
|
+
} else {
|
|
4717
|
+
const spreadHorizontal = (nodeList, baselineY, role) => {
|
|
4718
|
+
const totalWidth = (nodeList.length - 1) * nodeGap;
|
|
4719
|
+
const startX = cx - totalWidth / 2;
|
|
4720
|
+
nodeList.forEach((node, index) => {
|
|
4721
|
+
const nodeScore = scoreNode(node);
|
|
4722
|
+
const stagger = (index % 2 === 0 ? -1 : 1) * 20;
|
|
4723
|
+
const nodeY = baselineY + stagger + (nodeScore * 20);
|
|
4724
|
+
addProjectionNode(node, role, startX + (index * nodeGap), nodeY, nodeY < baselineY ? -15 : 25, 12);
|
|
4725
|
+
});
|
|
4726
|
+
};
|
|
4727
|
+
spreadHorizontal(outgoingNodes, cy - layerGap, 'outgoing');
|
|
4728
|
+
spreadHorizontal(incomingNodes, cy + layerGap, 'incoming');
|
|
4729
|
+
projectionLabels.push({
|
|
4730
|
+
text: typeof t === 'function' ? t('focus_outbound') : 'Outbound',
|
|
4731
|
+
x: cx,
|
|
4732
|
+
y: cy - layerGap - 60,
|
|
4733
|
+
align: 'middle',
|
|
4734
|
+
role: 'outgoing',
|
|
4735
|
+
});
|
|
4736
|
+
projectionLabels.push({
|
|
4737
|
+
text: typeof t === 'function' ? t('focus_inbound') : 'Inbound',
|
|
4738
|
+
x: cx,
|
|
4739
|
+
y: cy + layerGap + 80,
|
|
4740
|
+
align: 'middle',
|
|
4741
|
+
role: 'incoming',
|
|
4742
|
+
});
|
|
4743
|
+
}
|
|
4744
|
+
if (associatedNodes.length > 0) {
|
|
4745
|
+
const left = [];
|
|
4746
|
+
const right = [];
|
|
4747
|
+
associatedNodes.forEach((node, index) => {
|
|
4748
|
+
if (index % 2 === 0) {
|
|
4749
|
+
left.push(node);
|
|
4750
|
+
} else {
|
|
4751
|
+
right.push(node);
|
|
4752
|
+
}
|
|
4753
|
+
});
|
|
4754
|
+
const sideGap = layerGap * 1.2;
|
|
4755
|
+
const placeSide = (nodeList, direction) => {
|
|
4756
|
+
nodeList.forEach((node, index) => {
|
|
4757
|
+
addProjectionNode(
|
|
4758
|
+
node,
|
|
4759
|
+
'associated',
|
|
4760
|
+
cx + (direction * sideGap),
|
|
4761
|
+
cy + (index * 60) - (nodeList.length * 30),
|
|
4762
|
+
25,
|
|
4763
|
+
12
|
|
4764
|
+
);
|
|
4765
|
+
});
|
|
4766
|
+
};
|
|
4767
|
+
placeSide(left, -1);
|
|
4768
|
+
placeSide(right, 1);
|
|
4769
|
+
}
|
|
4770
|
+
|
|
4771
|
+
const visibleIds = new Set(projectionNodes.map((node) => node.id));
|
|
4772
|
+
const projectionEdges = linkList
|
|
4773
|
+
.map((linkItem) => {
|
|
4774
|
+
const sourceId = getGraphViewLinkNodeId(linkItem && linkItem.source);
|
|
4775
|
+
const targetId = getGraphViewLinkNodeId(linkItem && linkItem.target);
|
|
4776
|
+
if (!sourceId || !targetId || !visibleIds.has(sourceId) || !visibleIds.has(targetId)) {
|
|
4777
|
+
return null;
|
|
4778
|
+
}
|
|
4779
|
+
return {
|
|
4780
|
+
sourceId,
|
|
4781
|
+
targetId,
|
|
4782
|
+
relationKind: getRelationKind(linkItem),
|
|
4783
|
+
confidence: getLinkWeight(linkItem),
|
|
4784
|
+
role: sourceId === focusD.id
|
|
4785
|
+
? 'outgoing'
|
|
4786
|
+
: targetId === focusD.id
|
|
4787
|
+
? 'incoming'
|
|
4788
|
+
: 'associated',
|
|
4789
|
+
};
|
|
4790
|
+
})
|
|
4791
|
+
.filter(Boolean);
|
|
4792
|
+
const maxContextNodes = Number.isFinite(Number(options.maxContextNodes))
|
|
4793
|
+
? Math.max(0, Math.min(500, Math.trunc(Number(options.maxContextNodes))))
|
|
4794
|
+
: 220;
|
|
4795
|
+
const contextNodes = maxContextNodes > 0
|
|
4796
|
+
? (Array.isArray(nodes) ? nodes : [])
|
|
4797
|
+
.map((node) => {
|
|
4798
|
+
const id = normalizeGraphViewText(node && node.id);
|
|
4799
|
+
const x = Number(node && node.x);
|
|
4800
|
+
const y = Number(node && node.y);
|
|
4801
|
+
if (!id || visibleIds.has(id) || !Number.isFinite(x) || !Number.isFinite(y)) {
|
|
4802
|
+
return null;
|
|
4803
|
+
}
|
|
4804
|
+
const dx = x - cx;
|
|
4805
|
+
const dy = y - cy;
|
|
4806
|
+
const distance = Math.sqrt((dx * dx) + (dy * dy));
|
|
4807
|
+
const degree = (Number(node && node.inDegree) || 0) + (Number(node && node.outDegree) || 0);
|
|
4808
|
+
const degreeScore = Math.min(degree, 40) / 40;
|
|
4809
|
+
const proximityScore = 1 / (1 + (distance / Math.max(layerGap, nodeGap, 1)));
|
|
4810
|
+
return {
|
|
4811
|
+
id,
|
|
4812
|
+
label: getGraphViewNodeLabel(node) || id,
|
|
4813
|
+
x,
|
|
4814
|
+
y,
|
|
4815
|
+
inDegree: Number(node && node.inDegree) || 0,
|
|
4816
|
+
outDegree: Number(node && node.outDegree) || 0,
|
|
4817
|
+
score: Number(((proximityScore * 0.72) + (degreeScore * 0.28)).toFixed(4)),
|
|
4818
|
+
};
|
|
4819
|
+
})
|
|
4820
|
+
.filter(Boolean)
|
|
4821
|
+
.sort((left, right) => right.score - left.score)
|
|
4822
|
+
.slice(0, maxContextNodes)
|
|
4823
|
+
: [];
|
|
4824
|
+
const boundsPoints = projectionNodes
|
|
4825
|
+
.map((node) => ({ x: node.x, y: node.y }))
|
|
4826
|
+
.concat(projectionLabels.map((label) => ({ x: label.x, y: label.y })));
|
|
4827
|
+
const contextBoundsPoints = contextNodes.length > 0
|
|
4828
|
+
? contextNodes.map((node) => ({ x: node.x, y: node.y }))
|
|
4829
|
+
: boundsPoints;
|
|
4830
|
+
const minX = Math.min(...boundsPoints.map((point) => point.x));
|
|
4831
|
+
const maxX = Math.max(...boundsPoints.map((point) => point.x));
|
|
4832
|
+
const minY = Math.min(...boundsPoints.map((point) => point.y));
|
|
4833
|
+
const maxY = Math.max(...boundsPoints.map((point) => point.y));
|
|
4834
|
+
const contextMinX = Math.min(...contextBoundsPoints.map((point) => point.x));
|
|
4835
|
+
const contextMaxX = Math.max(...contextBoundsPoints.map((point) => point.x));
|
|
4836
|
+
const contextMinY = Math.min(...contextBoundsPoints.map((point) => point.y));
|
|
4837
|
+
const contextMaxY = Math.max(...contextBoundsPoints.map((point) => point.y));
|
|
4838
|
+
return {
|
|
4839
|
+
anchorId: focusD.id,
|
|
4840
|
+
anchorLabel: getGraphViewNodeLabel(focusD) || focusD.id,
|
|
4841
|
+
layoutType,
|
|
4842
|
+
layerGap,
|
|
4843
|
+
nodeGap,
|
|
4844
|
+
stats: {
|
|
4845
|
+
inDegree: Number(focusD.inDegree) || incomingNodes.length,
|
|
4846
|
+
outDegree: Number(focusD.outDegree) || outgoingNodes.length,
|
|
4847
|
+
incomingCount: incomingNodes.length,
|
|
4848
|
+
outgoingCount: outgoingNodes.length,
|
|
4849
|
+
associatedCount: associatedNodes.length,
|
|
4850
|
+
contextCount: contextNodes.length,
|
|
4851
|
+
},
|
|
4852
|
+
controls: {
|
|
4853
|
+
layoutType,
|
|
4854
|
+
layerGap,
|
|
4855
|
+
nodeGap,
|
|
4856
|
+
rendererMode: document.querySelector('input[name="rendererMode"]:checked')
|
|
4857
|
+
? document.querySelector('input[name="rendererMode"]:checked').value
|
|
4858
|
+
: '',
|
|
4859
|
+
},
|
|
4860
|
+
labels: projectionLabels,
|
|
4861
|
+
contextNodes,
|
|
4862
|
+
nodes: projectionNodes,
|
|
4863
|
+
edges: projectionEdges,
|
|
4864
|
+
bounds: {
|
|
4865
|
+
minX,
|
|
4866
|
+
maxX,
|
|
4867
|
+
minY,
|
|
4868
|
+
maxY,
|
|
4869
|
+
width: Math.max(1, maxX - minX),
|
|
4870
|
+
height: Math.max(1, maxY - minY),
|
|
4871
|
+
},
|
|
4872
|
+
contextBounds: {
|
|
4873
|
+
minX: contextMinX,
|
|
4874
|
+
maxX: contextMaxX,
|
|
4875
|
+
minY: contextMinY,
|
|
4876
|
+
maxY: contextMaxY,
|
|
4877
|
+
width: Math.max(1, contextMaxX - contextMinX),
|
|
4878
|
+
height: Math.max(1, contextMaxY - contextMinY),
|
|
4879
|
+
},
|
|
4880
|
+
};
|
|
4881
|
+
}
|
|
4882
|
+
|
|
4883
|
+
// Compatibility API for agent workspace graph-focus capabilities.
|
|
4884
|
+
// Keeps agent runtime decoupled from historical global helper names.
|
|
4885
|
+
window.NoteConnectionGraphView = {
|
|
4886
|
+
resolveNodeById: function(nodeId) {
|
|
4887
|
+
const id = String(nodeId || '').trim();
|
|
4888
|
+
if (!id) {
|
|
4889
|
+
return null;
|
|
4890
|
+
}
|
|
4891
|
+
return resolveGraphViewNodeByIdOrLabel(id);
|
|
4892
|
+
},
|
|
4893
|
+
resolveNodeByKnowledgePoint: function(payload) {
|
|
4894
|
+
return resolveGraphViewNodeByKnowledgePoint(payload);
|
|
4895
|
+
},
|
|
4896
|
+
openFocusModeById: function(nodeId) {
|
|
4897
|
+
const node = this.resolveNodeById(nodeId);
|
|
4898
|
+
if (!node) {
|
|
4899
|
+
return false;
|
|
4900
|
+
}
|
|
4901
|
+
if (window.highlightManager && typeof window.highlightManager.unhighlight === 'function') {
|
|
4902
|
+
window.highlightManager.unhighlight({ force: true });
|
|
4903
|
+
}
|
|
4904
|
+
const popup = document.getElementById('node-stats-popup');
|
|
4905
|
+
if (popup) {
|
|
4906
|
+
popup.style.display = 'none';
|
|
4907
|
+
}
|
|
4908
|
+
enterFocusMode(node);
|
|
4909
|
+
return true;
|
|
4910
|
+
},
|
|
4911
|
+
getFocusNode: function() {
|
|
4912
|
+
return focusNode && typeof focusNode === 'object'
|
|
4913
|
+
? focusNode
|
|
4914
|
+
: null;
|
|
4915
|
+
},
|
|
4916
|
+
getFocusModeSnapshot: function(nodeId) {
|
|
4917
|
+
return buildGraphViewFocusModeSnapshot(nodeId || (focusNode && focusNode.id));
|
|
4918
|
+
},
|
|
4919
|
+
getFocusModeProjection: function(nodeId, options) {
|
|
4920
|
+
return buildGraphViewFocusModeProjection(nodeId || (focusNode && focusNode.id), options || {});
|
|
4921
|
+
},
|
|
4922
|
+
getNodeCount: function() {
|
|
4923
|
+
return Array.isArray(nodes) ? nodes.length : 0;
|
|
4924
|
+
},
|
|
4925
|
+
};
|
|
4926
|
+
|
|
4927
|
+
|
|
4928
|
+
|
|
4929
|
+
// --- Query History Implementation (v0.9.77) ---
|
|
4930
|
+
window.focusHistory = [];
|
|
4931
|
+
const MAX_HISTORY = 10;
|
|
4932
|
+
|
|
4933
|
+
function updateFocusHistory(newNode) {
|
|
4934
|
+
// Avoid duplicates: Remove if exists, then add to top
|
|
4935
|
+
// Requirement: "pin the corresponding node to the top"
|
|
4936
|
+
window.focusHistory = window.focusHistory.filter(n => n.id !== newNode.id);
|
|
4937
|
+
|
|
4938
|
+
// Add to specific history list
|
|
4939
|
+
window.focusHistory.unshift(newNode);
|
|
4940
|
+
if (window.focusHistory.length > MAX_HISTORY) window.focusHistory.pop();
|
|
4941
|
+
|
|
4942
|
+
renderFocusHistory();
|
|
4943
|
+
}
|
|
4944
|
+
|
|
4945
|
+
function renderFocusHistory() {
|
|
4946
|
+
const container = document.getElementById('focus-history-list');
|
|
4947
|
+
if (!container) return; // Should be created by init
|
|
4948
|
+
|
|
4949
|
+
container.innerHTML = '';
|
|
4950
|
+
|
|
4951
|
+
if (window.focusHistory.length === 0) {
|
|
4952
|
+
container.innerHTML = '<div style="padding:5px; color:#aaa; font-style:italic">No history</div>';
|
|
4953
|
+
return;
|
|
4954
|
+
}
|
|
4955
|
+
|
|
4956
|
+
window.focusHistory.forEach(node => {
|
|
4957
|
+
const item = document.createElement('div');
|
|
4958
|
+
item.style.padding = '4px 8px';
|
|
4959
|
+
item.style.cursor = 'pointer';
|
|
4960
|
+
item.style.borderBottom = '1px solid #444';
|
|
4961
|
+
item.style.fontSize = '0.8rem';
|
|
4962
|
+
item.style.color = '#eee';
|
|
4963
|
+
item.innerText = node.label;
|
|
3767
4964
|
item.title = `Cluster: ${node.clusterId || '-'}`;
|
|
3768
4965
|
|
|
3769
4966
|
item.addEventListener('mouseenter', () => item.style.background = '#444');
|
|
@@ -4366,12 +5563,18 @@ const workersSlider = document.getElementById('set-workers-slider');
|
|
|
4366
5563
|
// --- Settings Integration ---
|
|
4367
5564
|
function initSettingsUI() {
|
|
4368
5565
|
const modal = document.getElementById('settings-modal');
|
|
5566
|
+
const agentModal = document.getElementById('agent-settings-modal');
|
|
4369
5567
|
const openBtn = document.getElementById('btn-open-settings');
|
|
4370
|
-
const
|
|
5568
|
+
const mainCloseBtns = modal ? modal.querySelectorAll('[data-settings-close="main"]') : [];
|
|
5569
|
+
const agentCloseBtns = agentModal ? agentModal.querySelectorAll('[data-settings-close="agent"]') : [];
|
|
5570
|
+
const openAgentSettingsBtn = document.getElementById('btn-open-agent-settings');
|
|
5571
|
+
const agentSettingsBackBtn = document.getElementById('btn-agent-settings-back');
|
|
5572
|
+
const agentSettingsBackFooterBtn = document.getElementById('btn-agent-settings-back-footer');
|
|
4371
5573
|
const resetBtn = document.getElementById('btn-reset-settings');
|
|
4372
5574
|
|
|
4373
|
-
//
|
|
4374
|
-
|
|
5575
|
+
// Track settings page state so simulation only resumes after every settings
|
|
5576
|
+
// surface is actually closed.
|
|
5577
|
+
let activeSettingsPage = null;
|
|
4375
5578
|
|
|
4376
5579
|
// Controls
|
|
4377
5580
|
const inputs = {
|
|
@@ -4392,6 +5595,7 @@ function initSettingsUI() {
|
|
|
4392
5595
|
const workersSlider = document.getElementById('set-workers-slider');
|
|
4393
5596
|
const workersInput = document.getElementById('set-workers-input');
|
|
4394
5597
|
const gpuCheckbox = document.getElementById('set-gpu');
|
|
5598
|
+
const staticModeCheckbox = document.getElementById('set-static-mode');
|
|
4395
5599
|
const gpuRenderingCheckbox = document.getElementById('set-gpu-rendering');
|
|
4396
5600
|
const memorySavingCheckbox = document.getElementById('set-memory-saving');
|
|
4397
5601
|
const compactModeCheckbox = document.getElementById('set-compact-mode');
|
|
@@ -4400,6 +5604,665 @@ function initSettingsUI() {
|
|
|
4400
5604
|
// Reader Settings
|
|
4401
5605
|
const inputReadingMode = document.getElementById('set-reading-mode');
|
|
4402
5606
|
|
|
5607
|
+
// Simplified NoteMD Provider Settings
|
|
5608
|
+
const providerTemplateSelect = document.getElementById('set-notemd-provider-template');
|
|
5609
|
+
const providerTemplateHint = document.getElementById('set-notemd-provider-template-hint');
|
|
5610
|
+
const providerNameSelect = document.getElementById('set-notemd-provider-name');
|
|
5611
|
+
const providerBaseUrlInput = document.getElementById('set-notemd-provider-base-url');
|
|
5612
|
+
const providerModelInput = document.getElementById('set-notemd-provider-model');
|
|
5613
|
+
const providerApiKeyInput = document.getElementById('set-notemd-provider-api-key');
|
|
5614
|
+
const providerApiVersionInput = document.getElementById('set-notemd-provider-api-version');
|
|
5615
|
+
const providerApiVersionRow = document.getElementById('set-notemd-provider-api-version-row');
|
|
5616
|
+
const providerApiVersionHint = document.getElementById('set-notemd-provider-api-version-hint');
|
|
5617
|
+
const providerStatus = document.getElementById('set-notemd-provider-status');
|
|
5618
|
+
const providerConfigPath = document.getElementById('set-notemd-provider-config-path');
|
|
5619
|
+
const applyProviderTemplateBtn = document.getElementById('btn-apply-notemd-provider-template');
|
|
5620
|
+
const testProviderBtn = document.getElementById('btn-test-notemd-provider');
|
|
5621
|
+
const saveProviderBtn = document.getElementById('btn-save-notemd-provider');
|
|
5622
|
+
const materializeProviderTemplatesBtn = document.getElementById('btn-materialize-notemd-provider-templates');
|
|
5623
|
+
const NOTEMD_PROVIDER_DRAFT_STORAGE_KEY = 'nc.notemdProviderDraft.v1';
|
|
5624
|
+
const NOTEMD_PROVIDER_AUTOSAVE_DEBOUNCE_MS = 900;
|
|
5625
|
+
|
|
5626
|
+
const notemdProviderModalState = {
|
|
5627
|
+
settings: null,
|
|
5628
|
+
templates: [],
|
|
5629
|
+
configPath: '',
|
|
5630
|
+
loading: false,
|
|
5631
|
+
autosaveTimer: null,
|
|
5632
|
+
lastSavedFingerprint: '',
|
|
5633
|
+
applyingDraft: false,
|
|
5634
|
+
};
|
|
5635
|
+
|
|
5636
|
+
const t = (key, fallback, params) => {
|
|
5637
|
+
if (window.i18n && typeof window.i18n.t === 'function') {
|
|
5638
|
+
const translated = window.i18n.t(key, params);
|
|
5639
|
+
if (translated && translated !== key) {
|
|
5640
|
+
return translated;
|
|
5641
|
+
}
|
|
5642
|
+
}
|
|
5643
|
+
return fallback;
|
|
5644
|
+
};
|
|
5645
|
+
|
|
5646
|
+
const buildRuntimeUrl = (resourcePath) => {
|
|
5647
|
+
if (window.NoteConnectionRuntime && typeof window.NoteConnectionRuntime.buildUrl === 'function') {
|
|
5648
|
+
return window.NoteConnectionRuntime.buildUrl(resourcePath.replace(/^\/+/, ''));
|
|
5649
|
+
}
|
|
5650
|
+
return resourcePath;
|
|
5651
|
+
};
|
|
5652
|
+
|
|
5653
|
+
const buildRuntimeFetchOptions = (init) => {
|
|
5654
|
+
if (window.NoteConnectionRuntime && typeof window.NoteConnectionRuntime.buildFetchOptions === 'function') {
|
|
5655
|
+
return window.NoteConnectionRuntime.buildFetchOptions(init);
|
|
5656
|
+
}
|
|
5657
|
+
return init;
|
|
5658
|
+
};
|
|
5659
|
+
|
|
5660
|
+
const cloneJson = (value) => JSON.parse(JSON.stringify(value));
|
|
5661
|
+
|
|
5662
|
+
const clearProviderAutosaveTimer = () => {
|
|
5663
|
+
if (notemdProviderModalState.autosaveTimer) {
|
|
5664
|
+
clearTimeout(notemdProviderModalState.autosaveTimer);
|
|
5665
|
+
notemdProviderModalState.autosaveTimer = null;
|
|
5666
|
+
}
|
|
5667
|
+
};
|
|
5668
|
+
|
|
5669
|
+
const readProviderDraftStore = () => {
|
|
5670
|
+
try {
|
|
5671
|
+
const raw = localStorage.getItem(NOTEMD_PROVIDER_DRAFT_STORAGE_KEY);
|
|
5672
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
5673
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
5674
|
+
} catch (_error) {
|
|
5675
|
+
return {};
|
|
5676
|
+
}
|
|
5677
|
+
};
|
|
5678
|
+
|
|
5679
|
+
const writeProviderDraftStore = (draftStore) => {
|
|
5680
|
+
try {
|
|
5681
|
+
localStorage.setItem(NOTEMD_PROVIDER_DRAFT_STORAGE_KEY, JSON.stringify(draftStore || {}));
|
|
5682
|
+
} catch (_error) {
|
|
5683
|
+
// Best effort only.
|
|
5684
|
+
}
|
|
5685
|
+
};
|
|
5686
|
+
|
|
5687
|
+
const getProviderDraft = (providerName) => {
|
|
5688
|
+
const draftStore = readProviderDraftStore();
|
|
5689
|
+
const key = String(providerName || '').trim();
|
|
5690
|
+
if (!key || !draftStore[key] || typeof draftStore[key] !== 'object') {
|
|
5691
|
+
return null;
|
|
5692
|
+
}
|
|
5693
|
+
return draftStore[key];
|
|
5694
|
+
};
|
|
5695
|
+
|
|
5696
|
+
const updateProviderDraft = (providerName, draftPatch) => {
|
|
5697
|
+
const key = String(providerName || '').trim();
|
|
5698
|
+
if (!key) {
|
|
5699
|
+
return;
|
|
5700
|
+
}
|
|
5701
|
+
const draftStore = readProviderDraftStore();
|
|
5702
|
+
const current = draftStore[key] && typeof draftStore[key] === 'object' ? draftStore[key] : {};
|
|
5703
|
+
draftStore[key] = {
|
|
5704
|
+
...current,
|
|
5705
|
+
...draftPatch,
|
|
5706
|
+
updatedAt: new Date().toISOString(),
|
|
5707
|
+
};
|
|
5708
|
+
writeProviderDraftStore(draftStore);
|
|
5709
|
+
};
|
|
5710
|
+
|
|
5711
|
+
const removeProviderDraft = (providerName) => {
|
|
5712
|
+
const key = String(providerName || '').trim();
|
|
5713
|
+
if (!key) {
|
|
5714
|
+
return;
|
|
5715
|
+
}
|
|
5716
|
+
const draftStore = readProviderDraftStore();
|
|
5717
|
+
if (Object.prototype.hasOwnProperty.call(draftStore, key)) {
|
|
5718
|
+
delete draftStore[key];
|
|
5719
|
+
writeProviderDraftStore(draftStore);
|
|
5720
|
+
}
|
|
5721
|
+
};
|
|
5722
|
+
|
|
5723
|
+
const setProviderModalStatus = (message, tone = 'muted') => {
|
|
5724
|
+
if (!providerStatus) {
|
|
5725
|
+
return;
|
|
5726
|
+
}
|
|
5727
|
+
providerStatus.textContent = String(message || '');
|
|
5728
|
+
providerStatus.style.color =
|
|
5729
|
+
tone === 'error'
|
|
5730
|
+
? '#fca5a5'
|
|
5731
|
+
: tone === 'success'
|
|
5732
|
+
? '#93c5fd'
|
|
5733
|
+
: '#aeb7c3';
|
|
5734
|
+
};
|
|
5735
|
+
|
|
5736
|
+
const setProviderModalBusy = (busy) => {
|
|
5737
|
+
notemdProviderModalState.loading = busy === true;
|
|
5738
|
+
const disabled = notemdProviderModalState.loading;
|
|
5739
|
+
[
|
|
5740
|
+
providerTemplateSelect,
|
|
5741
|
+
providerNameSelect,
|
|
5742
|
+
providerBaseUrlInput,
|
|
5743
|
+
providerModelInput,
|
|
5744
|
+
providerApiKeyInput,
|
|
5745
|
+
providerApiVersionInput,
|
|
5746
|
+
applyProviderTemplateBtn,
|
|
5747
|
+
testProviderBtn,
|
|
5748
|
+
saveProviderBtn,
|
|
5749
|
+
materializeProviderTemplatesBtn,
|
|
5750
|
+
].forEach((element) => {
|
|
5751
|
+
if (element) {
|
|
5752
|
+
element.disabled = disabled;
|
|
5753
|
+
}
|
|
5754
|
+
});
|
|
5755
|
+
};
|
|
5756
|
+
|
|
5757
|
+
const ensureRuntimeBridgeReady = async () => {
|
|
5758
|
+
if (
|
|
5759
|
+
window.NoteConnectionRuntime
|
|
5760
|
+
&& typeof window.NoteConnectionRuntime.whenReady === 'function'
|
|
5761
|
+
) {
|
|
5762
|
+
await window.NoteConnectionRuntime.whenReady();
|
|
5763
|
+
}
|
|
5764
|
+
};
|
|
5765
|
+
|
|
5766
|
+
const refreshRuntimeBridgeFromTauri = async () => {
|
|
5767
|
+
if (
|
|
5768
|
+
!window.NoteConnectionRuntime
|
|
5769
|
+
|| typeof window.NoteConnectionRuntime.refreshFromTauri !== 'function'
|
|
5770
|
+
) {
|
|
5771
|
+
return false;
|
|
5772
|
+
}
|
|
5773
|
+
try {
|
|
5774
|
+
await window.NoteConnectionRuntime.refreshFromTauri();
|
|
5775
|
+
return true;
|
|
5776
|
+
} catch (error) {
|
|
5777
|
+
console.warn('[Settings] Runtime bridge refresh failed before retrying agent settings request.', error);
|
|
5778
|
+
return false;
|
|
5779
|
+
}
|
|
5780
|
+
};
|
|
5781
|
+
|
|
5782
|
+
const fetchRuntimeJsonOnce = async (resourcePath, init) => {
|
|
5783
|
+
await ensureRuntimeBridgeReady();
|
|
5784
|
+
const response = await fetch(buildRuntimeUrl(resourcePath), buildRuntimeFetchOptions(init));
|
|
5785
|
+
const payload = await response.json().catch(() => null);
|
|
5786
|
+
return { response, payload };
|
|
5787
|
+
};
|
|
5788
|
+
|
|
5789
|
+
const isSuccessfulRuntimeJsonResponse = ({ response, payload }) => (
|
|
5790
|
+
response.ok && payload && payload.success === true
|
|
5791
|
+
);
|
|
5792
|
+
|
|
5793
|
+
const buildRuntimeJsonError = (resourcePath, { response, payload }) => {
|
|
5794
|
+
const message = payload && payload.error
|
|
5795
|
+
? String(payload.error)
|
|
5796
|
+
: `Request failed (${resourcePath} ${response.status})`;
|
|
5797
|
+
return new Error(message);
|
|
5798
|
+
};
|
|
5799
|
+
|
|
5800
|
+
const requestRuntimeJson = async (resourcePath, init = {}) => {
|
|
5801
|
+
const firstAttempt = await fetchRuntimeJsonOnce(resourcePath, init);
|
|
5802
|
+
if (isSuccessfulRuntimeJsonResponse(firstAttempt)) {
|
|
5803
|
+
return firstAttempt.payload;
|
|
5804
|
+
}
|
|
5805
|
+
|
|
5806
|
+
if (firstAttempt.response.status === 401 && await refreshRuntimeBridgeFromTauri()) {
|
|
5807
|
+
const secondAttempt = await fetchRuntimeJsonOnce(resourcePath, init);
|
|
5808
|
+
if (isSuccessfulRuntimeJsonResponse(secondAttempt)) {
|
|
5809
|
+
return secondAttempt.payload;
|
|
5810
|
+
}
|
|
5811
|
+
throw buildRuntimeJsonError(resourcePath, secondAttempt);
|
|
5812
|
+
}
|
|
5813
|
+
|
|
5814
|
+
throw buildRuntimeJsonError(resourcePath, firstAttempt);
|
|
5815
|
+
};
|
|
5816
|
+
|
|
5817
|
+
const isAnySettingsPanelOpen = () => activeSettingsPage !== null;
|
|
5818
|
+
|
|
5819
|
+
const resumeSimulationIfAllowed = () => {
|
|
5820
|
+
const isFrozen = document.getElementById('freeze-layout')
|
|
5821
|
+
? document.getElementById('freeze-layout').checked
|
|
5822
|
+
: false;
|
|
5823
|
+
if (!isFrozen) {
|
|
5824
|
+
simulation.alpha(0.3).restart();
|
|
5825
|
+
}
|
|
5826
|
+
};
|
|
5827
|
+
|
|
5828
|
+
const showMainSettingsPage = () => {
|
|
5829
|
+
if (modal) {
|
|
5830
|
+
modal.style.display = 'flex';
|
|
5831
|
+
const body = modal.querySelector('.settings-modal-body');
|
|
5832
|
+
if (body) {
|
|
5833
|
+
body.scrollTop = 0;
|
|
5834
|
+
}
|
|
5835
|
+
}
|
|
5836
|
+
if (agentModal) {
|
|
5837
|
+
agentModal.style.display = 'none';
|
|
5838
|
+
}
|
|
5839
|
+
activeSettingsPage = 'main';
|
|
5840
|
+
simulation.stop();
|
|
5841
|
+
};
|
|
5842
|
+
|
|
5843
|
+
const showAgentSettingsPage = async () => {
|
|
5844
|
+
if (modal) {
|
|
5845
|
+
modal.style.display = 'none';
|
|
5846
|
+
}
|
|
5847
|
+
if (agentModal) {
|
|
5848
|
+
agentModal.style.display = 'flex';
|
|
5849
|
+
const body = agentModal.querySelector('.settings-modal-body');
|
|
5850
|
+
if (body) {
|
|
5851
|
+
body.scrollTop = 0;
|
|
5852
|
+
}
|
|
5853
|
+
}
|
|
5854
|
+
activeSettingsPage = 'agent';
|
|
5855
|
+
simulation.stop();
|
|
5856
|
+
await loadNotemdProviderModalState();
|
|
5857
|
+
};
|
|
5858
|
+
|
|
5859
|
+
const closeAllSettingsPages = () => {
|
|
5860
|
+
clearProviderAutosaveTimer();
|
|
5861
|
+
const nextFingerprint = getCurrentProviderFingerprint();
|
|
5862
|
+
if (
|
|
5863
|
+
activeSettingsPage === 'agent'
|
|
5864
|
+
&& nextFingerprint
|
|
5865
|
+
&& nextFingerprint !== notemdProviderModalState.lastSavedFingerprint
|
|
5866
|
+
&& !notemdProviderModalState.loading
|
|
5867
|
+
) {
|
|
5868
|
+
void saveNotemdProviderModalState({ auto: true }).catch((error) => {
|
|
5869
|
+
setProviderModalStatus(
|
|
5870
|
+
t('notemd_provider_autosave_failed', 'Auto-save failed: {error}', {
|
|
5871
|
+
error: error && error.message ? error.message : String(error),
|
|
5872
|
+
}),
|
|
5873
|
+
'error'
|
|
5874
|
+
);
|
|
5875
|
+
});
|
|
5876
|
+
}
|
|
5877
|
+
if (modal) {
|
|
5878
|
+
modal.style.display = 'none';
|
|
5879
|
+
}
|
|
5880
|
+
if (agentModal) {
|
|
5881
|
+
agentModal.style.display = 'none';
|
|
5882
|
+
}
|
|
5883
|
+
activeSettingsPage = null;
|
|
5884
|
+
resumeSimulationIfAllowed();
|
|
5885
|
+
};
|
|
5886
|
+
|
|
5887
|
+
const getCurrentProviderFromModalState = () => {
|
|
5888
|
+
if (!notemdProviderModalState.settings || !providerNameSelect) {
|
|
5889
|
+
return null;
|
|
5890
|
+
}
|
|
5891
|
+
const providerName = String(providerNameSelect.value || '').trim();
|
|
5892
|
+
return notemdProviderModalState.settings.providers.find((provider) => provider.name === providerName) || null;
|
|
5893
|
+
};
|
|
5894
|
+
|
|
5895
|
+
const getCurrentProviderFingerprint = () => {
|
|
5896
|
+
const providerName = String(providerNameSelect ? providerNameSelect.value : '').trim();
|
|
5897
|
+
if (!providerName) {
|
|
5898
|
+
return '';
|
|
5899
|
+
}
|
|
5900
|
+
return JSON.stringify({
|
|
5901
|
+
providerName,
|
|
5902
|
+
templateId: String(providerTemplateSelect ? providerTemplateSelect.value : '').trim(),
|
|
5903
|
+
baseUrl: String(providerBaseUrlInput ? providerBaseUrlInput.value : '').trim(),
|
|
5904
|
+
model: String(providerModelInput ? providerModelInput.value : '').trim(),
|
|
5905
|
+
apiKey: String(providerApiKeyInput ? providerApiKeyInput.value : ''),
|
|
5906
|
+
apiVersion: String(providerApiVersionInput ? providerApiVersionInput.value : '').trim(),
|
|
5907
|
+
});
|
|
5908
|
+
};
|
|
5909
|
+
|
|
5910
|
+
const buildCurrentProviderRequestPayload = () => {
|
|
5911
|
+
const providerName = String(providerNameSelect ? providerNameSelect.value : '').trim();
|
|
5912
|
+
const persistedProvider = getCurrentProviderFromModalState();
|
|
5913
|
+
return {
|
|
5914
|
+
provider: {
|
|
5915
|
+
name: providerName,
|
|
5916
|
+
baseUrl: String(providerBaseUrlInput ? providerBaseUrlInput.value : '').trim(),
|
|
5917
|
+
model: String(providerModelInput ? providerModelInput.value : '').trim(),
|
|
5918
|
+
apiKey: String(providerApiKeyInput ? providerApiKeyInput.value : ''),
|
|
5919
|
+
apiVersion: String(providerApiVersionInput ? providerApiVersionInput.value : '').trim(),
|
|
5920
|
+
temperature: Number.isFinite(Number(persistedProvider && persistedProvider.temperature))
|
|
5921
|
+
? Number(persistedProvider.temperature)
|
|
5922
|
+
: 0.5,
|
|
5923
|
+
},
|
|
5924
|
+
providerName,
|
|
5925
|
+
};
|
|
5926
|
+
};
|
|
5927
|
+
|
|
5928
|
+
const syncCurrentProviderDraft = () => {
|
|
5929
|
+
const providerName = String(providerNameSelect ? providerNameSelect.value : '').trim();
|
|
5930
|
+
if (!providerName || notemdProviderModalState.applyingDraft) {
|
|
5931
|
+
return;
|
|
5932
|
+
}
|
|
5933
|
+
updateProviderDraft(providerName, {
|
|
5934
|
+
templateId: String(providerTemplateSelect ? providerTemplateSelect.value : '').trim(),
|
|
5935
|
+
baseUrl: String(providerBaseUrlInput ? providerBaseUrlInput.value : '').trim(),
|
|
5936
|
+
model: String(providerModelInput ? providerModelInput.value : '').trim(),
|
|
5937
|
+
apiKey: String(providerApiKeyInput ? providerApiKeyInput.value : ''),
|
|
5938
|
+
apiVersion: String(providerApiVersionInput ? providerApiVersionInput.value : '').trim(),
|
|
5939
|
+
});
|
|
5940
|
+
};
|
|
5941
|
+
|
|
5942
|
+
const findTemplateForProvider = (providerName) => {
|
|
5943
|
+
const templates = Array.isArray(notemdProviderModalState.templates) ? notemdProviderModalState.templates : [];
|
|
5944
|
+
return templates.find((template) => template.providerName === providerName) || null;
|
|
5945
|
+
};
|
|
5946
|
+
|
|
5947
|
+
const updateProviderApiVersionVisibility = (provider) => {
|
|
5948
|
+
if (!providerApiVersionRow) {
|
|
5949
|
+
return;
|
|
5950
|
+
}
|
|
5951
|
+
const isAzure = provider && String(provider.name || '').trim() === 'Azure OpenAI';
|
|
5952
|
+
providerApiVersionRow.style.display = isAzure ? '' : 'none';
|
|
5953
|
+
if (providerApiVersionInput) {
|
|
5954
|
+
providerApiVersionInput.placeholder = isAzure ? '2025-01-01-preview' : '';
|
|
5955
|
+
}
|
|
5956
|
+
if (providerApiVersionHint) {
|
|
5957
|
+
providerApiVersionHint.textContent = isAzure
|
|
5958
|
+
? t('notemd_provider_api_version_required', 'Required for Azure OpenAI. Example: 2025-01-01-preview.')
|
|
5959
|
+
: t('notemd_provider_api_version_optional', 'Optional. Leave blank for standard OpenAI-compatible endpoints. This is mainly used by Azure OpenAI.');
|
|
5960
|
+
}
|
|
5961
|
+
};
|
|
5962
|
+
|
|
5963
|
+
const applyProviderDraftToFields = (providerName) => {
|
|
5964
|
+
const draft = getProviderDraft(providerName);
|
|
5965
|
+
if (!draft) {
|
|
5966
|
+
return;
|
|
5967
|
+
}
|
|
5968
|
+
notemdProviderModalState.applyingDraft = true;
|
|
5969
|
+
try {
|
|
5970
|
+
if (providerTemplateSelect && typeof draft.templateId === 'string' && draft.templateId) {
|
|
5971
|
+
providerTemplateSelect.value = draft.templateId;
|
|
5972
|
+
renderProviderTemplateHint();
|
|
5973
|
+
}
|
|
5974
|
+
if (providerBaseUrlInput && typeof draft.baseUrl === 'string') {
|
|
5975
|
+
providerBaseUrlInput.value = draft.baseUrl;
|
|
5976
|
+
}
|
|
5977
|
+
if (providerModelInput && typeof draft.model === 'string') {
|
|
5978
|
+
providerModelInput.value = draft.model;
|
|
5979
|
+
}
|
|
5980
|
+
if (providerApiKeyInput && typeof draft.apiKey === 'string') {
|
|
5981
|
+
providerApiKeyInput.value = draft.apiKey;
|
|
5982
|
+
}
|
|
5983
|
+
if (providerApiVersionInput && typeof draft.apiVersion === 'string') {
|
|
5984
|
+
providerApiVersionInput.value = draft.apiVersion;
|
|
5985
|
+
}
|
|
5986
|
+
} finally {
|
|
5987
|
+
notemdProviderModalState.applyingDraft = false;
|
|
5988
|
+
}
|
|
5989
|
+
};
|
|
5990
|
+
|
|
5991
|
+
const renderProviderTemplateHint = () => {
|
|
5992
|
+
if (!providerTemplateHint) {
|
|
5993
|
+
return;
|
|
5994
|
+
}
|
|
5995
|
+
const templateId = providerTemplateSelect ? String(providerTemplateSelect.value || '').trim() : '';
|
|
5996
|
+
const templates = Array.isArray(notemdProviderModalState.templates) ? notemdProviderModalState.templates : [];
|
|
5997
|
+
const template = templates.find((item) => item.id === templateId) || null;
|
|
5998
|
+
if (!template) {
|
|
5999
|
+
providerTemplateHint.textContent = '';
|
|
6000
|
+
return;
|
|
6001
|
+
}
|
|
6002
|
+
const noteSummary = Array.isArray(template.notes) ? template.notes.slice(0, 2).join(' ') : '';
|
|
6003
|
+
providerTemplateHint.textContent = `${template.label} · ${template.category} · ${template.transport}. ${template.recommendedFor} ${template.hostHint} ${noteSummary}`.trim();
|
|
6004
|
+
};
|
|
6005
|
+
|
|
6006
|
+
const renderSelectedProviderFields = () => {
|
|
6007
|
+
const provider = getCurrentProviderFromModalState();
|
|
6008
|
+
if (!provider) {
|
|
6009
|
+
if (providerBaseUrlInput) providerBaseUrlInput.value = '';
|
|
6010
|
+
if (providerModelInput) providerModelInput.value = '';
|
|
6011
|
+
if (providerApiKeyInput) providerApiKeyInput.value = '';
|
|
6012
|
+
if (providerApiVersionInput) providerApiVersionInput.value = '';
|
|
6013
|
+
updateProviderApiVersionVisibility(null);
|
|
6014
|
+
return;
|
|
6015
|
+
}
|
|
6016
|
+
if (providerBaseUrlInput) providerBaseUrlInput.value = String(provider.baseUrl || '');
|
|
6017
|
+
if (providerModelInput) providerModelInput.value = String(provider.model || '');
|
|
6018
|
+
if (providerApiKeyInput) providerApiKeyInput.value = String(provider.apiKey || '');
|
|
6019
|
+
if (providerApiVersionInput) providerApiVersionInput.value = String(provider.apiVersion || '');
|
|
6020
|
+
updateProviderApiVersionVisibility(provider);
|
|
6021
|
+
|
|
6022
|
+
const matchedTemplate = findTemplateForProvider(provider.name);
|
|
6023
|
+
if (providerTemplateSelect && matchedTemplate) {
|
|
6024
|
+
providerTemplateSelect.value = matchedTemplate.id;
|
|
6025
|
+
renderProviderTemplateHint();
|
|
6026
|
+
}
|
|
6027
|
+
applyProviderDraftToFields(provider.name);
|
|
6028
|
+
};
|
|
6029
|
+
|
|
6030
|
+
const populateProviderTemplateSelect = () => {
|
|
6031
|
+
if (!providerTemplateSelect) {
|
|
6032
|
+
return;
|
|
6033
|
+
}
|
|
6034
|
+
providerTemplateSelect.innerHTML = '';
|
|
6035
|
+
const placeholder = document.createElement('option');
|
|
6036
|
+
placeholder.value = '';
|
|
6037
|
+
placeholder.textContent = t('notemd_provider_template_select', 'Choose a preset template');
|
|
6038
|
+
providerTemplateSelect.appendChild(placeholder);
|
|
6039
|
+
const templates = Array.isArray(notemdProviderModalState.templates) ? notemdProviderModalState.templates : [];
|
|
6040
|
+
templates.forEach((template) => {
|
|
6041
|
+
const option = document.createElement('option');
|
|
6042
|
+
option.value = template.id;
|
|
6043
|
+
option.textContent = `${template.label} (${template.category})`;
|
|
6044
|
+
providerTemplateSelect.appendChild(option);
|
|
6045
|
+
});
|
|
6046
|
+
};
|
|
6047
|
+
|
|
6048
|
+
const populateProviderSelect = () => {
|
|
6049
|
+
if (!providerNameSelect) {
|
|
6050
|
+
return;
|
|
6051
|
+
}
|
|
6052
|
+
providerNameSelect.innerHTML = '';
|
|
6053
|
+
const settings = notemdProviderModalState.settings;
|
|
6054
|
+
const providers = settings && Array.isArray(settings.providers) ? settings.providers : [];
|
|
6055
|
+
providers.forEach((provider) => {
|
|
6056
|
+
const option = document.createElement('option');
|
|
6057
|
+
option.value = provider.name;
|
|
6058
|
+
option.textContent = provider.name;
|
|
6059
|
+
providerNameSelect.appendChild(option);
|
|
6060
|
+
});
|
|
6061
|
+
if (settings && settings.activeProvider) {
|
|
6062
|
+
providerNameSelect.value = settings.activeProvider;
|
|
6063
|
+
}
|
|
6064
|
+
renderSelectedProviderFields();
|
|
6065
|
+
};
|
|
6066
|
+
|
|
6067
|
+
const loadNotemdProviderModalState = async (options = {}) => {
|
|
6068
|
+
if (notemdProviderModalState.loading) {
|
|
6069
|
+
return;
|
|
6070
|
+
}
|
|
6071
|
+
setProviderModalBusy(true);
|
|
6072
|
+
setProviderModalStatus(t('notemd_provider_loading', 'Loading provider settings...'));
|
|
6073
|
+
try {
|
|
6074
|
+
const templatesPayload = await requestRuntimeJson('/api/notemd/provider-templates?persist=1');
|
|
6075
|
+
const settingsPayload = await requestRuntimeJson('/api/notemd/settings');
|
|
6076
|
+
notemdProviderModalState.templates = Array.isArray(templatesPayload.templates)
|
|
6077
|
+
? cloneJson(templatesPayload.templates)
|
|
6078
|
+
: [];
|
|
6079
|
+
notemdProviderModalState.configPath = String(templatesPayload.configPath || '');
|
|
6080
|
+
notemdProviderModalState.settings = cloneJson(settingsPayload.settings || {});
|
|
6081
|
+
populateProviderTemplateSelect();
|
|
6082
|
+
populateProviderSelect();
|
|
6083
|
+
if (providerConfigPath) {
|
|
6084
|
+
providerConfigPath.textContent = notemdProviderModalState.configPath
|
|
6085
|
+
? `${t('notemd_provider_config_path', 'TOML path')}: ${notemdProviderModalState.configPath}`
|
|
6086
|
+
: '';
|
|
6087
|
+
}
|
|
6088
|
+
notemdProviderModalState.lastSavedFingerprint = getCurrentProviderFingerprint();
|
|
6089
|
+
setProviderModalStatus(
|
|
6090
|
+
t('notemd_provider_loaded', 'Provider presets and TOML templates are ready.'),
|
|
6091
|
+
'success'
|
|
6092
|
+
);
|
|
6093
|
+
} catch (error) {
|
|
6094
|
+
setProviderModalStatus(
|
|
6095
|
+
t('notemd_provider_load_failed', 'Provider settings load failed: {error}', {
|
|
6096
|
+
error: error && error.message ? error.message : String(error),
|
|
6097
|
+
}),
|
|
6098
|
+
'error'
|
|
6099
|
+
);
|
|
6100
|
+
} finally {
|
|
6101
|
+
setProviderModalBusy(false);
|
|
6102
|
+
}
|
|
6103
|
+
};
|
|
6104
|
+
|
|
6105
|
+
const saveNotemdProviderModalState = async (options = {}) => {
|
|
6106
|
+
if (!notemdProviderModalState.settings || !providerNameSelect) {
|
|
6107
|
+
return;
|
|
6108
|
+
}
|
|
6109
|
+
const auto = options.auto === true;
|
|
6110
|
+
const nextSettings = cloneJson(notemdProviderModalState.settings);
|
|
6111
|
+
const providerName = String(providerNameSelect.value || '').trim();
|
|
6112
|
+
const nextProvider = nextSettings.providers.find((provider) => provider.name === providerName);
|
|
6113
|
+
if (!nextProvider) {
|
|
6114
|
+
throw new Error(`Unsupported provider: ${providerName}`);
|
|
6115
|
+
}
|
|
6116
|
+
nextSettings.activeProvider = providerName;
|
|
6117
|
+
nextProvider.baseUrl = String(providerBaseUrlInput ? providerBaseUrlInput.value : '').trim();
|
|
6118
|
+
nextProvider.model = String(providerModelInput ? providerModelInput.value : '').trim();
|
|
6119
|
+
nextProvider.apiKey = String(providerApiKeyInput ? providerApiKeyInput.value : '');
|
|
6120
|
+
nextProvider.apiVersion = String(providerApiVersionInput ? providerApiVersionInput.value : '').trim();
|
|
6121
|
+
setProviderModalBusy(true);
|
|
6122
|
+
setProviderModalStatus(
|
|
6123
|
+
auto
|
|
6124
|
+
? t('notemd_provider_autosaving', 'Auto-saving provider settings...')
|
|
6125
|
+
: t('notemd_provider_saving', 'Saving provider settings...')
|
|
6126
|
+
);
|
|
6127
|
+
try {
|
|
6128
|
+
const payload = await requestRuntimeJson('/api/notemd/settings', {
|
|
6129
|
+
method: 'POST',
|
|
6130
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6131
|
+
body: JSON.stringify({ settings: nextSettings }),
|
|
6132
|
+
});
|
|
6133
|
+
notemdProviderModalState.settings = cloneJson(payload.settings || nextSettings);
|
|
6134
|
+
populateProviderSelect();
|
|
6135
|
+
notemdProviderModalState.lastSavedFingerprint = getCurrentProviderFingerprint();
|
|
6136
|
+
removeProviderDraft(providerName);
|
|
6137
|
+
setProviderModalStatus(
|
|
6138
|
+
auto
|
|
6139
|
+
? t('notemd_provider_autosaved', 'Provider settings auto-saved.')
|
|
6140
|
+
: t('notemd_provider_saved', 'Provider settings saved to app_config.toml.'),
|
|
6141
|
+
'success'
|
|
6142
|
+
);
|
|
6143
|
+
} finally {
|
|
6144
|
+
setProviderModalBusy(false);
|
|
6145
|
+
}
|
|
6146
|
+
};
|
|
6147
|
+
|
|
6148
|
+
const scheduleProviderAutosave = () => {
|
|
6149
|
+
if (notemdProviderModalState.loading || notemdProviderModalState.applyingDraft) {
|
|
6150
|
+
return;
|
|
6151
|
+
}
|
|
6152
|
+
const providerName = String(providerNameSelect ? providerNameSelect.value : '').trim();
|
|
6153
|
+
if (!providerName || !notemdProviderModalState.settings) {
|
|
6154
|
+
return;
|
|
6155
|
+
}
|
|
6156
|
+
const nextFingerprint = getCurrentProviderFingerprint();
|
|
6157
|
+
if (!nextFingerprint || nextFingerprint === notemdProviderModalState.lastSavedFingerprint) {
|
|
6158
|
+
return;
|
|
6159
|
+
}
|
|
6160
|
+
clearProviderAutosaveTimer();
|
|
6161
|
+
notemdProviderModalState.autosaveTimer = setTimeout(() => {
|
|
6162
|
+
notemdProviderModalState.autosaveTimer = null;
|
|
6163
|
+
void saveNotemdProviderModalState({ auto: true }).catch((error) => {
|
|
6164
|
+
setProviderModalStatus(
|
|
6165
|
+
t('notemd_provider_autosave_failed', 'Auto-save failed: {error}', {
|
|
6166
|
+
error: error && error.message ? error.message : String(error),
|
|
6167
|
+
}),
|
|
6168
|
+
'error'
|
|
6169
|
+
);
|
|
6170
|
+
});
|
|
6171
|
+
}, NOTEMD_PROVIDER_AUTOSAVE_DEBOUNCE_MS);
|
|
6172
|
+
};
|
|
6173
|
+
|
|
6174
|
+
const applyNotemdProviderTemplate = async () => {
|
|
6175
|
+
if (!providerTemplateSelect) {
|
|
6176
|
+
return;
|
|
6177
|
+
}
|
|
6178
|
+
const templateId = String(providerTemplateSelect.value || '').trim();
|
|
6179
|
+
if (!templateId) {
|
|
6180
|
+
setProviderModalStatus(t('notemd_provider_template_required', 'Choose a preset template first.'), 'error');
|
|
6181
|
+
return;
|
|
6182
|
+
}
|
|
6183
|
+
setProviderModalBusy(true);
|
|
6184
|
+
setProviderModalStatus(t('notemd_provider_template_applying', 'Applying provider preset...'));
|
|
6185
|
+
try {
|
|
6186
|
+
const payload = await requestRuntimeJson('/api/notemd/provider-templates/apply', {
|
|
6187
|
+
method: 'POST',
|
|
6188
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6189
|
+
body: JSON.stringify({ templateId }),
|
|
6190
|
+
});
|
|
6191
|
+
notemdProviderModalState.settings = cloneJson(payload.settings || {});
|
|
6192
|
+
if (payload.configPath) {
|
|
6193
|
+
notemdProviderModalState.configPath = String(payload.configPath);
|
|
6194
|
+
}
|
|
6195
|
+
populateProviderSelect();
|
|
6196
|
+
if (providerConfigPath) {
|
|
6197
|
+
providerConfigPath.textContent = notemdProviderModalState.configPath
|
|
6198
|
+
? `${t('notemd_provider_config_path', 'TOML path')}: ${notemdProviderModalState.configPath}`
|
|
6199
|
+
: '';
|
|
6200
|
+
}
|
|
6201
|
+
notemdProviderModalState.lastSavedFingerprint = getCurrentProviderFingerprint();
|
|
6202
|
+
removeProviderDraft(String(providerNameSelect ? providerNameSelect.value : '').trim());
|
|
6203
|
+
setProviderModalStatus(
|
|
6204
|
+
t('notemd_provider_template_applied', 'Preset applied and saved to app_config.toml.'),
|
|
6205
|
+
'success'
|
|
6206
|
+
);
|
|
6207
|
+
} finally {
|
|
6208
|
+
setProviderModalBusy(false);
|
|
6209
|
+
}
|
|
6210
|
+
};
|
|
6211
|
+
|
|
6212
|
+
const materializeNotemdProviderTemplates = async () => {
|
|
6213
|
+
setProviderModalBusy(true);
|
|
6214
|
+
setProviderModalStatus(t('notemd_provider_templates_writing', 'Writing TOML provider templates...'));
|
|
6215
|
+
try {
|
|
6216
|
+
const payload = await requestRuntimeJson('/api/notemd/provider-templates?persist=1');
|
|
6217
|
+
notemdProviderModalState.templates = Array.isArray(payload.templates)
|
|
6218
|
+
? cloneJson(payload.templates)
|
|
6219
|
+
: [];
|
|
6220
|
+
notemdProviderModalState.configPath = String(payload.configPath || notemdProviderModalState.configPath || '');
|
|
6221
|
+
populateProviderTemplateSelect();
|
|
6222
|
+
renderProviderTemplateHint();
|
|
6223
|
+
if (providerConfigPath) {
|
|
6224
|
+
providerConfigPath.textContent = notemdProviderModalState.configPath
|
|
6225
|
+
? `${t('notemd_provider_config_path', 'TOML path')}: ${notemdProviderModalState.configPath}`
|
|
6226
|
+
: '';
|
|
6227
|
+
}
|
|
6228
|
+
setProviderModalStatus(
|
|
6229
|
+
payload.persisted
|
|
6230
|
+
? t('notemd_provider_templates_written', 'Provider templates were written to app_config.toml.')
|
|
6231
|
+
: t('notemd_provider_templates_present', 'Provider templates are already present in app_config.toml.'),
|
|
6232
|
+
'success'
|
|
6233
|
+
);
|
|
6234
|
+
} finally {
|
|
6235
|
+
setProviderModalBusy(false);
|
|
6236
|
+
}
|
|
6237
|
+
};
|
|
6238
|
+
|
|
6239
|
+
const testNotemdProviderConnection = async () => {
|
|
6240
|
+
const payload = buildCurrentProviderRequestPayload();
|
|
6241
|
+
if (!payload.providerName) {
|
|
6242
|
+
throw new Error('Provider name is required.');
|
|
6243
|
+
}
|
|
6244
|
+
|
|
6245
|
+
setProviderModalBusy(true);
|
|
6246
|
+
setProviderModalStatus(
|
|
6247
|
+
t('btn_test_provider_connection', 'Test Connection') + '...',
|
|
6248
|
+
'muted'
|
|
6249
|
+
);
|
|
6250
|
+
try {
|
|
6251
|
+
const response = await requestRuntimeJson('/api/notemd/test-llm', {
|
|
6252
|
+
method: 'POST',
|
|
6253
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6254
|
+
body: JSON.stringify(payload),
|
|
6255
|
+
});
|
|
6256
|
+
const result = response.result || {};
|
|
6257
|
+
setProviderModalStatus(
|
|
6258
|
+
String(result.message || `Connected to ${payload.providerName}.`),
|
|
6259
|
+
result.success === false ? 'error' : 'success'
|
|
6260
|
+
);
|
|
6261
|
+
} finally {
|
|
6262
|
+
setProviderModalBusy(false);
|
|
6263
|
+
}
|
|
6264
|
+
};
|
|
6265
|
+
|
|
4403
6266
|
// Load initial values
|
|
4404
6267
|
const updateUIFromSettings = (settings) => {
|
|
4405
6268
|
const mode = document.querySelector('input[name="layoutMode"]:checked') ? document.querySelector('input[name="layoutMode"]:checked').value : 'force';
|
|
@@ -4441,6 +6304,9 @@ function initSettingsUI() {
|
|
|
4441
6304
|
if (settings.performance.enableGPU !== undefined) {
|
|
4442
6305
|
if (gpuCheckbox) gpuCheckbox.checked = settings.performance.enableGPU;
|
|
4443
6306
|
}
|
|
6307
|
+
if (settings.performance.staticMode !== undefined) {
|
|
6308
|
+
if (staticModeCheckbox) staticModeCheckbox.checked = settings.performance.staticMode;
|
|
6309
|
+
}
|
|
4444
6310
|
if (settings.performance.gpuRendering !== undefined) {
|
|
4445
6311
|
if (gpuRenderingCheckbox) gpuRenderingCheckbox.checked = settings.performance.gpuRendering;
|
|
4446
6312
|
}
|
|
@@ -4503,6 +6369,12 @@ function initSettingsUI() {
|
|
|
4503
6369
|
});
|
|
4504
6370
|
}
|
|
4505
6371
|
|
|
6372
|
+
if (staticModeCheckbox) {
|
|
6373
|
+
staticModeCheckbox.addEventListener('change', (e) => {
|
|
6374
|
+
settingsManager.set('performance', 'staticMode', e.target.checked);
|
|
6375
|
+
});
|
|
6376
|
+
}
|
|
6377
|
+
|
|
4506
6378
|
if (gpuRenderingCheckbox) {
|
|
4507
6379
|
gpuRenderingCheckbox.addEventListener('change', (e) => {
|
|
4508
6380
|
settingsManager.set('performance', 'gpuRendering', e.target.checked);
|
|
@@ -4548,33 +6420,132 @@ function initSettingsUI() {
|
|
|
4548
6420
|
settingsManager.set('reading', 'mode', e.target.value);
|
|
4549
6421
|
});
|
|
4550
6422
|
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
6423
|
+
if (providerTemplateSelect) {
|
|
6424
|
+
providerTemplateSelect.addEventListener('change', () => {
|
|
6425
|
+
renderProviderTemplateHint();
|
|
6426
|
+
syncCurrentProviderDraft();
|
|
6427
|
+
scheduleProviderAutosave();
|
|
6428
|
+
});
|
|
6429
|
+
}
|
|
6430
|
+
|
|
6431
|
+
if (providerNameSelect) {
|
|
6432
|
+
providerNameSelect.addEventListener('change', () => {
|
|
6433
|
+
renderSelectedProviderFields();
|
|
6434
|
+
syncCurrentProviderDraft();
|
|
6435
|
+
scheduleProviderAutosave();
|
|
6436
|
+
});
|
|
6437
|
+
}
|
|
6438
|
+
|
|
6439
|
+
[
|
|
6440
|
+
providerBaseUrlInput,
|
|
6441
|
+
providerModelInput,
|
|
6442
|
+
providerApiKeyInput,
|
|
6443
|
+
providerApiVersionInput,
|
|
6444
|
+
].forEach((input) => {
|
|
6445
|
+
if (!input) {
|
|
6446
|
+
return;
|
|
4559
6447
|
}
|
|
4560
|
-
|
|
6448
|
+
input.addEventListener('input', () => {
|
|
6449
|
+
syncCurrentProviderDraft();
|
|
6450
|
+
scheduleProviderAutosave();
|
|
6451
|
+
});
|
|
6452
|
+
input.addEventListener('change', () => {
|
|
6453
|
+
syncCurrentProviderDraft();
|
|
6454
|
+
scheduleProviderAutosave();
|
|
6455
|
+
});
|
|
6456
|
+
});
|
|
6457
|
+
|
|
6458
|
+
if (applyProviderTemplateBtn) {
|
|
6459
|
+
applyProviderTemplateBtn.addEventListener('click', () => {
|
|
6460
|
+
void applyNotemdProviderTemplate().catch((error) => {
|
|
6461
|
+
setProviderModalStatus(
|
|
6462
|
+
t('notemd_provider_template_apply_failed', 'Provider preset apply failed: {error}', {
|
|
6463
|
+
error: error && error.message ? error.message : String(error),
|
|
6464
|
+
}),
|
|
6465
|
+
'error'
|
|
6466
|
+
);
|
|
6467
|
+
});
|
|
6468
|
+
});
|
|
6469
|
+
}
|
|
6470
|
+
|
|
6471
|
+
if (testProviderBtn) {
|
|
6472
|
+
testProviderBtn.addEventListener('click', () => {
|
|
6473
|
+
void testNotemdProviderConnection().catch((error) => {
|
|
6474
|
+
setProviderModalStatus(
|
|
6475
|
+
error && error.message ? error.message : String(error),
|
|
6476
|
+
'error'
|
|
6477
|
+
);
|
|
6478
|
+
});
|
|
6479
|
+
});
|
|
6480
|
+
}
|
|
6481
|
+
|
|
6482
|
+
if (saveProviderBtn) {
|
|
6483
|
+
saveProviderBtn.addEventListener('click', () => {
|
|
6484
|
+
void saveNotemdProviderModalState().catch((error) => {
|
|
6485
|
+
setProviderModalStatus(
|
|
6486
|
+
t('notemd_provider_save_failed', 'Provider save failed: {error}', {
|
|
6487
|
+
error: error && error.message ? error.message : String(error),
|
|
6488
|
+
}),
|
|
6489
|
+
'error'
|
|
6490
|
+
);
|
|
6491
|
+
});
|
|
6492
|
+
});
|
|
6493
|
+
}
|
|
6494
|
+
|
|
6495
|
+
if (materializeProviderTemplatesBtn) {
|
|
6496
|
+
materializeProviderTemplatesBtn.addEventListener('click', () => {
|
|
6497
|
+
void materializeNotemdProviderTemplates().catch((error) => {
|
|
6498
|
+
setProviderModalStatus(
|
|
6499
|
+
t('notemd_provider_templates_write_failed', 'Template write failed: {error}', {
|
|
6500
|
+
error: error && error.message ? error.message : String(error),
|
|
6501
|
+
}),
|
|
6502
|
+
'error'
|
|
6503
|
+
);
|
|
6504
|
+
});
|
|
6505
|
+
});
|
|
6506
|
+
}
|
|
4561
6507
|
|
|
4562
6508
|
// Modal Actions
|
|
4563
|
-
// v0.9.42: When opening settings, update UI to reflect current mode's values
|
|
4564
6509
|
openBtn.addEventListener('click', () => {
|
|
4565
6510
|
updateUIFromSettings(settingsManager.settings);
|
|
4566
|
-
|
|
4567
|
-
isSettingsModalOpen = true;
|
|
4568
|
-
simulation.stop(); // v0.9.41: Force freeze to save resources
|
|
4569
|
-
});
|
|
4570
|
-
|
|
4571
|
-
closeBtns.forEach(btn => btn.addEventListener('click', closeSettings));
|
|
4572
|
-
|
|
4573
|
-
// Close on click outside
|
|
4574
|
-
modal.addEventListener('click', (e) => {
|
|
4575
|
-
if (e.target === modal) closeSettings();
|
|
6511
|
+
showMainSettingsPage();
|
|
4576
6512
|
});
|
|
4577
6513
|
|
|
6514
|
+
if (openAgentSettingsBtn) {
|
|
6515
|
+
openAgentSettingsBtn.addEventListener('click', () => {
|
|
6516
|
+
void showAgentSettingsPage().catch((error) => {
|
|
6517
|
+
console.warn('[Settings] Failed to open agent settings.', error);
|
|
6518
|
+
});
|
|
6519
|
+
});
|
|
6520
|
+
}
|
|
6521
|
+
|
|
6522
|
+
if (agentSettingsBackBtn) {
|
|
6523
|
+
agentSettingsBackBtn.addEventListener('click', showMainSettingsPage);
|
|
6524
|
+
}
|
|
6525
|
+
|
|
6526
|
+
if (agentSettingsBackFooterBtn) {
|
|
6527
|
+
agentSettingsBackFooterBtn.addEventListener('click', showMainSettingsPage);
|
|
6528
|
+
}
|
|
6529
|
+
|
|
6530
|
+
mainCloseBtns.forEach((btn) => btn.addEventListener('click', closeAllSettingsPages));
|
|
6531
|
+
agentCloseBtns.forEach((btn) => btn.addEventListener('click', closeAllSettingsPages));
|
|
6532
|
+
|
|
6533
|
+
if (modal) {
|
|
6534
|
+
modal.addEventListener('click', (e) => {
|
|
6535
|
+
if (e.target === modal) {
|
|
6536
|
+
closeAllSettingsPages();
|
|
6537
|
+
}
|
|
6538
|
+
});
|
|
6539
|
+
}
|
|
6540
|
+
|
|
6541
|
+
if (agentModal) {
|
|
6542
|
+
agentModal.addEventListener('click', (e) => {
|
|
6543
|
+
if (e.target === agentModal) {
|
|
6544
|
+
closeAllSettingsPages();
|
|
6545
|
+
}
|
|
6546
|
+
});
|
|
6547
|
+
}
|
|
6548
|
+
|
|
4578
6549
|
resetBtn.addEventListener('click', () => {
|
|
4579
6550
|
settingsManager.reset();
|
|
4580
6551
|
updateUIFromSettings(settingsManager.settings);
|
|
@@ -4588,7 +6559,7 @@ function initSettingsUI() {
|
|
|
4588
6559
|
|
|
4589
6560
|
// v0.9.40: Check Freeze Layout State before restarting
|
|
4590
6561
|
const globalFreeze = document.getElementById('freeze-layout') ? document.getElementById('freeze-layout').checked : false;
|
|
4591
|
-
const isFrozen = globalFreeze ||
|
|
6562
|
+
const isFrozen = globalFreeze || isAnySettingsPanelOpen();
|
|
4592
6563
|
|
|
4593
6564
|
if (!isFrozen) {
|
|
4594
6565
|
simulation.alpha(0.3).restart();
|
|
@@ -4799,6 +6770,151 @@ function getTauriDialogApi() {
|
|
|
4799
6770
|
return null;
|
|
4800
6771
|
}
|
|
4801
6772
|
|
|
6773
|
+
function normalizePathModeRuntimeText(value) {
|
|
6774
|
+
return String(value || '').replace(/\s+/g, ' ').trim();
|
|
6775
|
+
}
|
|
6776
|
+
|
|
6777
|
+
function normalizePathModeLanguage(value) {
|
|
6778
|
+
const raw = normalizePathModeRuntimeText(value).toLowerCase();
|
|
6779
|
+
return raw.startsWith('zh') ? 'zh' : 'en';
|
|
6780
|
+
}
|
|
6781
|
+
|
|
6782
|
+
function resolvePathModeRuntimeTarget(targetId) {
|
|
6783
|
+
const normalizedTargetId = normalizePathModeRuntimeText(targetId);
|
|
6784
|
+
if (!normalizedTargetId) {
|
|
6785
|
+
return null;
|
|
6786
|
+
}
|
|
6787
|
+
const node = resolveGraphViewNodeByIdOrLabel(normalizedTargetId);
|
|
6788
|
+
if (!node) {
|
|
6789
|
+
return {
|
|
6790
|
+
id: normalizedTargetId,
|
|
6791
|
+
label: normalizedTargetId,
|
|
6792
|
+
resolved: false,
|
|
6793
|
+
};
|
|
6794
|
+
}
|
|
6795
|
+
return {
|
|
6796
|
+
id: node.id,
|
|
6797
|
+
label: getGraphViewNodeLabel(node) || node.id,
|
|
6798
|
+
resolved: true,
|
|
6799
|
+
};
|
|
6800
|
+
}
|
|
6801
|
+
|
|
6802
|
+
function buildGodotFuturePathRuntimeConfig(targetId, options = {}) {
|
|
6803
|
+
const runtimeTarget = resolvePathModeRuntimeTarget(targetId);
|
|
6804
|
+
if (!runtimeTarget || !runtimeTarget.id) {
|
|
6805
|
+
return null;
|
|
6806
|
+
}
|
|
6807
|
+
const existingConfig = options.config && typeof options.config === 'object'
|
|
6808
|
+
? options.config
|
|
6809
|
+
: {};
|
|
6810
|
+
const targetIds = [];
|
|
6811
|
+
const seen = new Set();
|
|
6812
|
+
const appendTargetId = function(value) {
|
|
6813
|
+
const normalized = normalizePathModeRuntimeText(value);
|
|
6814
|
+
if (!normalized || seen.has(normalized)) {
|
|
6815
|
+
return;
|
|
6816
|
+
}
|
|
6817
|
+
seen.add(normalized);
|
|
6818
|
+
targetIds.push(normalized);
|
|
6819
|
+
};
|
|
6820
|
+
appendTargetId(runtimeTarget.id);
|
|
6821
|
+
if (Array.isArray(existingConfig.targetIds)) {
|
|
6822
|
+
existingConfig.targetIds.forEach(appendTargetId);
|
|
6823
|
+
}
|
|
6824
|
+
return {
|
|
6825
|
+
...existingConfig,
|
|
6826
|
+
mode: 'diffusion',
|
|
6827
|
+
strategy: 'core',
|
|
6828
|
+
layout: 'orbital',
|
|
6829
|
+
targetId: runtimeTarget.id,
|
|
6830
|
+
target_id: runtimeTarget.id,
|
|
6831
|
+
targetIds,
|
|
6832
|
+
focus_mode: true,
|
|
6833
|
+
language: normalizePathModeLanguage(
|
|
6834
|
+
existingConfig.language
|
|
6835
|
+
|| window.i18n && window.i18n.currentLanguage
|
|
6836
|
+
|| document.documentElement && document.documentElement.lang
|
|
6837
|
+
|| 'en'
|
|
6838
|
+
),
|
|
6839
|
+
};
|
|
6840
|
+
}
|
|
6841
|
+
|
|
6842
|
+
async function openGodotFuturePathById(targetId, options = {}) {
|
|
6843
|
+
const config = buildGodotFuturePathRuntimeConfig(targetId, options);
|
|
6844
|
+
if (!config) {
|
|
6845
|
+
throw new Error('Missing Godot Future Path target node.');
|
|
6846
|
+
}
|
|
6847
|
+
window.__NC_LAST_GODOT_FUTURE_PATH_REQUEST = { ...config };
|
|
6848
|
+
|
|
6849
|
+
const pathApp = window.pathApp;
|
|
6850
|
+
if (pathApp && typeof pathApp === 'object') {
|
|
6851
|
+
if (!window.__NC_AGENT_GODOT_FUTURE_PATH_INITIALIZED && typeof pathApp.init === 'function') {
|
|
6852
|
+
pathApp.init(config.targetId);
|
|
6853
|
+
window.__NC_AGENT_GODOT_FUTURE_PATH_INITIALIZED = true;
|
|
6854
|
+
}
|
|
6855
|
+
if (pathApp.runtimeConfig && typeof pathApp.runtimeConfig === 'object') {
|
|
6856
|
+
pathApp.runtimeConfig.mode = 'diffusion';
|
|
6857
|
+
pathApp.runtimeConfig.strategy = 'core';
|
|
6858
|
+
pathApp.runtimeConfig.layout = 'orbital';
|
|
6859
|
+
pathApp.runtimeConfig.targetId = config.targetId;
|
|
6860
|
+
pathApp.runtimeConfig.targetIds = config.targetIds.slice();
|
|
6861
|
+
}
|
|
6862
|
+
pathApp.currentTargetId = config.targetId;
|
|
6863
|
+
pathApp.currentTargetIds = config.targetIds.slice();
|
|
6864
|
+
pathApp.centralNodeId = config.targetId;
|
|
6865
|
+
if (typeof pathApp.applyRemoteConfigure === 'function') {
|
|
6866
|
+
pathApp.applyRemoteConfigure(config);
|
|
6867
|
+
}
|
|
6868
|
+
if (typeof pathApp._sendBridgeMessage === 'function') {
|
|
6869
|
+
pathApp._sendBridgeMessage('configure', config);
|
|
6870
|
+
}
|
|
6871
|
+
if (typeof pathApp.triggerUpdate === 'function') {
|
|
6872
|
+
pathApp.triggerUpdate();
|
|
6873
|
+
}
|
|
6874
|
+
if (typeof pathApp.requestBridgeWindowVisibility === 'function') {
|
|
6875
|
+
const bridgeReady = await pathApp.requestBridgeWindowVisibility(true, {
|
|
6876
|
+
waitMs: 1800,
|
|
6877
|
+
reason: options.source || 'open-godot-future-path',
|
|
6878
|
+
});
|
|
6879
|
+
if (bridgeReady && typeof pathApp._sendBridgeMessage === 'function') {
|
|
6880
|
+
pathApp._sendBridgeMessage('configure', config);
|
|
6881
|
+
}
|
|
6882
|
+
}
|
|
6883
|
+
}
|
|
6884
|
+
|
|
6885
|
+
const invoke = getTauriCoreInvoke();
|
|
6886
|
+
if (invoke) {
|
|
6887
|
+
const caps = window.__NC_RUNTIME_CAPS || {};
|
|
6888
|
+
if (caps.platform === 'android' && caps.supports_native_pathmode === true) {
|
|
6889
|
+
await invoke('open_native_pathmode', {
|
|
6890
|
+
request: {
|
|
6891
|
+
mode: 'diffusion',
|
|
6892
|
+
strategy: 'core',
|
|
6893
|
+
targetId: config.targetId,
|
|
6894
|
+
},
|
|
6895
|
+
});
|
|
6896
|
+
} else {
|
|
6897
|
+
await invoke('toggle_pathmode_window', { showGodot: true });
|
|
6898
|
+
}
|
|
6899
|
+
}
|
|
6900
|
+
|
|
6901
|
+
return {
|
|
6902
|
+
opened: true,
|
|
6903
|
+
targetId: config.targetId,
|
|
6904
|
+
targetIds: config.targetIds.slice(),
|
|
6905
|
+
strategy: config.strategy,
|
|
6906
|
+
mode: config.mode,
|
|
6907
|
+
};
|
|
6908
|
+
}
|
|
6909
|
+
|
|
6910
|
+
window.NoteConnectionPathMode = {
|
|
6911
|
+
...(window.NoteConnectionPathMode && typeof window.NoteConnectionPathMode === 'object'
|
|
6912
|
+
? window.NoteConnectionPathMode
|
|
6913
|
+
: {}),
|
|
6914
|
+
buildGodotFuturePathRuntimeConfig,
|
|
6915
|
+
openGodotFuturePathById,
|
|
6916
|
+
};
|
|
6917
|
+
|
|
4802
6918
|
function normalizeNotemdPickerPayload(payload) {
|
|
4803
6919
|
const safePayload = payload && typeof payload === 'object' ? { ...payload } : {};
|
|
4804
6920
|
const initialPath = typeof safePayload.initialPath === 'string'
|