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
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Path Mode Application Controller
|
|
3
3
|
* Handles interaction, rendering, and worker communication.
|
|
4
|
+
*
|
|
5
|
+
* Phase 4 P5: Canonical utility implementations now live in
|
|
6
|
+
* path_mermaid_utils.mjs and path_state.mjs, exposed via
|
|
7
|
+
* window.pathModules (loaded by path_modules_bridge.js).
|
|
8
|
+
* The _methodName functions below delegate to window.pathModules
|
|
9
|
+
* when available, falling back to inline implementations.
|
|
4
10
|
*/
|
|
5
11
|
|
|
12
|
+
(function () {
|
|
13
|
+
'use strict';
|
|
14
|
+
var _pm = window.pathModules;
|
|
15
|
+
if (_pm && _pm.utils) {
|
|
16
|
+
// Pre-bind canonical implementations before pathApp definition.
|
|
17
|
+
// These will be used by the methods defined below.
|
|
18
|
+
window._pathUtils = _pm.utils;
|
|
19
|
+
window._pathState = _pm.state;
|
|
20
|
+
}
|
|
21
|
+
})();
|
|
22
|
+
|
|
6
23
|
window.pathApp = {
|
|
7
24
|
canvas: null,
|
|
8
25
|
ctx: null,
|
|
@@ -39,6 +56,29 @@ window.pathApp = {
|
|
|
39
56
|
pendingWindowVisibility: null,
|
|
40
57
|
semanticA11yLastSummaryKey: '',
|
|
41
58
|
semanticA11yLastAnnouncementAt: 0,
|
|
59
|
+
semanticA11yDeferredRefreshTimer: null,
|
|
60
|
+
learningWorkbench: {
|
|
61
|
+
userId: 'path_user_default',
|
|
62
|
+
loading: false,
|
|
63
|
+
lastError: '',
|
|
64
|
+
lastUpdatedAt: '',
|
|
65
|
+
sessionPlan: null,
|
|
66
|
+
qualitySnapshot: null,
|
|
67
|
+
qualityGateEvaluation: null,
|
|
68
|
+
qualityBaselineSnapshot: null,
|
|
69
|
+
misconceptions: null,
|
|
70
|
+
runtimeState: null,
|
|
71
|
+
tutorFeedback: null,
|
|
72
|
+
sessionExecution: null,
|
|
73
|
+
sessionHistory: null,
|
|
74
|
+
sessionHistoryQuery: {
|
|
75
|
+
limit: 8,
|
|
76
|
+
offset: 0,
|
|
77
|
+
executionKind: 'all',
|
|
78
|
+
fromDate: '',
|
|
79
|
+
toDate: '',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
42
82
|
|
|
43
83
|
// Animation State
|
|
44
84
|
animationId: null,
|
|
@@ -323,6 +363,31 @@ window.pathApp = {
|
|
|
323
363
|
return true;
|
|
324
364
|
},
|
|
325
365
|
|
|
366
|
+
_isDetachedPreviewWithoutRuntimeBootstrap: function() {
|
|
367
|
+
if (typeof window === 'undefined' || window.__TAURI__) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
const hasDetachedPreviewScript = Array.from(document.scripts || []).some((script) =>
|
|
371
|
+
/__tauri_cli/.test(String(script && (script.text || script.textContent) || ''))
|
|
372
|
+
);
|
|
373
|
+
if (!hasDetachedPreviewScript) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
if (!window.NoteConnectionRuntime || typeof window.NoteConnectionRuntime.getBaseUrl !== 'function') {
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
const currentOrigin = String((window.location && window.location.origin) || '').trim().replace(/\/+$/, '');
|
|
380
|
+
const baseUrl = String(window.NoteConnectionRuntime.getBaseUrl() || '').trim();
|
|
381
|
+
if (!currentOrigin || !baseUrl) {
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
try {
|
|
385
|
+
return currentOrigin !== new URL(baseUrl).origin;
|
|
386
|
+
} catch (_error) {
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
|
|
326
391
|
_getBridgeWsUrl: function() {
|
|
327
392
|
if (typeof window !== 'undefined' && window.NoteConnectionRuntime && typeof window.NoteConnectionRuntime.getBridgeWsUrl === 'function') {
|
|
328
393
|
return window.NoteConnectionRuntime.getBridgeWsUrl('frontend');
|
|
@@ -598,6 +663,313 @@ window.pathApp = {
|
|
|
598
663
|
};
|
|
599
664
|
},
|
|
600
665
|
|
|
666
|
+
_getReaderLikeMermaidConfig: function(theme = 'dark') {
|
|
667
|
+
return {
|
|
668
|
+
startOnLoad: false,
|
|
669
|
+
theme,
|
|
670
|
+
securityLevel: 'loose',
|
|
671
|
+
htmlLabels: true,
|
|
672
|
+
};
|
|
673
|
+
},
|
|
674
|
+
|
|
675
|
+
_getBridgeTextMeasureContext: function() {
|
|
676
|
+
if (!this._bridgeTextMeasureCanvas) {
|
|
677
|
+
this._bridgeTextMeasureCanvas = document.createElement('canvas');
|
|
678
|
+
}
|
|
679
|
+
if (!this._bridgeTextMeasureContext) {
|
|
680
|
+
this._bridgeTextMeasureContext = this._bridgeTextMeasureCanvas.getContext('2d');
|
|
681
|
+
}
|
|
682
|
+
return this._bridgeTextMeasureContext;
|
|
683
|
+
},
|
|
684
|
+
|
|
685
|
+
_measureBridgeCanvasTextWidth: function(text, fontDescriptor) {
|
|
686
|
+
const context = this._getBridgeTextMeasureContext();
|
|
687
|
+
if (!context) {
|
|
688
|
+
return this._estimateBridgeTextLineWidth(text, fontDescriptor.fontSize || 16);
|
|
689
|
+
}
|
|
690
|
+
context.font = [
|
|
691
|
+
fontDescriptor.fontStyle || 'normal',
|
|
692
|
+
fontDescriptor.fontWeight || '400',
|
|
693
|
+
`${fontDescriptor.fontSize || 16}px`,
|
|
694
|
+
fontDescriptor.fontFamily || '"trebuchet ms", verdana, arial, sans-serif',
|
|
695
|
+
].join(' ');
|
|
696
|
+
return context.measureText(String(text || '')).width;
|
|
697
|
+
},
|
|
698
|
+
|
|
699
|
+
_extractBridgeForeignObjectSourceLines: function(foreignObject) {
|
|
700
|
+
const htmlRoot = foreignObject && (
|
|
701
|
+
foreignObject.querySelector('div, span, p')
|
|
702
|
+
|| foreignObject.firstElementChild
|
|
703
|
+
|| foreignObject
|
|
704
|
+
);
|
|
705
|
+
if (!htmlRoot) {
|
|
706
|
+
return { lines: [], fromRenderedLayout: false };
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const collectNormalizedLines = (value) => String(value || '')
|
|
710
|
+
.split(/\r?\n/)
|
|
711
|
+
.map((line) => this._normalizeBridgeInlineText(line))
|
|
712
|
+
.filter(Boolean);
|
|
713
|
+
|
|
714
|
+
// Prefer the browser's actual rendered line layout so the bridge mirrors
|
|
715
|
+
// the reader's foreignObject wrapping instead of guessing it again.
|
|
716
|
+
const renderedText = typeof htmlRoot.innerText === 'string' ? htmlRoot.innerText : '';
|
|
717
|
+
const renderedLines = collectNormalizedLines(renderedText);
|
|
718
|
+
if (renderedLines.length > 0) {
|
|
719
|
+
return {
|
|
720
|
+
lines: renderedLines,
|
|
721
|
+
fromRenderedLayout: true,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const paragraphs = Array.from(htmlRoot.querySelectorAll('p'));
|
|
726
|
+
const htmlSegments = paragraphs.length > 0
|
|
727
|
+
? paragraphs.map((paragraph) => String(paragraph.innerHTML || ''))
|
|
728
|
+
: [String(htmlRoot.innerHTML || htmlRoot.textContent || '')];
|
|
729
|
+
const scratch = document.createElement('div');
|
|
730
|
+
const lines = [];
|
|
731
|
+
htmlSegments.forEach((segment) => {
|
|
732
|
+
scratch.innerHTML = String(segment || '').replace(/<br\s*\/?>/gi, '\n');
|
|
733
|
+
collectNormalizedLines(scratch.innerText || scratch.textContent || '')
|
|
734
|
+
.forEach((line) => lines.push(line));
|
|
735
|
+
});
|
|
736
|
+
return {
|
|
737
|
+
lines,
|
|
738
|
+
fromRenderedLayout: false,
|
|
739
|
+
};
|
|
740
|
+
},
|
|
741
|
+
|
|
742
|
+
_wrapBridgeForeignObjectLines: function(lines, fontDescriptor, maxWidth, whiteSpaceMode) {
|
|
743
|
+
const safeLines = Array.isArray(lines) ? lines.filter((line) => String(line || '').trim()) : [];
|
|
744
|
+
if (safeLines.length === 0) {
|
|
745
|
+
return [];
|
|
746
|
+
}
|
|
747
|
+
if (String(whiteSpaceMode || '').toLowerCase() === 'nowrap') {
|
|
748
|
+
return safeLines;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const wrapped = [];
|
|
752
|
+
const hardMaxLines = 24;
|
|
753
|
+
const measure = (text) => this._measureBridgeCanvasTextWidth(text, fontDescriptor);
|
|
754
|
+
|
|
755
|
+
safeLines.forEach((line) => {
|
|
756
|
+
const normalized = this._normalizeBridgeInlineText(line);
|
|
757
|
+
if (!normalized) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
if (measure(normalized) <= maxWidth) {
|
|
761
|
+
wrapped.push(normalized);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const tokens = /\s/.test(normalized)
|
|
766
|
+
? normalized.split(/\s+/).filter(Boolean)
|
|
767
|
+
: Array.from(normalized);
|
|
768
|
+
let current = '';
|
|
769
|
+
const pushCurrent = () => {
|
|
770
|
+
if (current) {
|
|
771
|
+
wrapped.push(current);
|
|
772
|
+
current = '';
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
for (const token of tokens) {
|
|
777
|
+
const candidate = !current
|
|
778
|
+
? token
|
|
779
|
+
: (/\s/.test(normalized) ? `${current} ${token}` : `${current}${token}`);
|
|
780
|
+
if (!current || measure(candidate) <= maxWidth) {
|
|
781
|
+
current = candidate;
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
pushCurrent();
|
|
785
|
+
if (measure(token) <= maxWidth) {
|
|
786
|
+
current = token;
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
let fragment = '';
|
|
790
|
+
for (const char of Array.from(token)) {
|
|
791
|
+
const fragmentCandidate = fragment + char;
|
|
792
|
+
if (!fragment || measure(fragmentCandidate) <= maxWidth) {
|
|
793
|
+
fragment = fragmentCandidate;
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
wrapped.push(fragment);
|
|
797
|
+
fragment = char;
|
|
798
|
+
if (wrapped.length >= hardMaxLines) {
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
if (fragment && wrapped.length < hardMaxLines) {
|
|
803
|
+
current = fragment;
|
|
804
|
+
}
|
|
805
|
+
if (wrapped.length >= hardMaxLines) {
|
|
806
|
+
break;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
pushCurrent();
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
return wrapped.slice(0, hardMaxLines);
|
|
813
|
+
},
|
|
814
|
+
|
|
815
|
+
_convertBridgeForeignObjectToSvgText: function(svgElement, foreignObject) {
|
|
816
|
+
if (!foreignObject || !foreignObject.parentNode) {
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const extracted = this._extractBridgeForeignObjectSourceLines(foreignObject);
|
|
821
|
+
if (!extracted || extracted.lines.length === 0) {
|
|
822
|
+
foreignObject.remove();
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const styleNode = foreignObject.querySelector('.nodeLabel, .edgeLabel, span, div, p') || foreignObject.firstElementChild || foreignObject;
|
|
827
|
+
const computedStyle = window.getComputedStyle(styleNode);
|
|
828
|
+
const fontSize = Number.parseFloat(computedStyle.fontSize || '') || 16;
|
|
829
|
+
const fontFamily = computedStyle.fontFamily || '"trebuchet ms", verdana, arial, sans-serif';
|
|
830
|
+
const fontWeight = computedStyle.fontWeight || '400';
|
|
831
|
+
const fontStyle = computedStyle.fontStyle || 'normal';
|
|
832
|
+
const lineHeight = Number.parseFloat(computedStyle.lineHeight || '') || (fontSize * 1.5);
|
|
833
|
+
const textAlign = String(computedStyle.textAlign || '').trim().toLowerCase();
|
|
834
|
+
const whiteSpace = String(computedStyle.whiteSpace || '').trim().toLowerCase();
|
|
835
|
+
const fill = computedStyle.color || '#ccc';
|
|
836
|
+
|
|
837
|
+
const x = this._parseBridgeNumericAttribute(foreignObject, 'x', 0);
|
|
838
|
+
const y = this._parseBridgeNumericAttribute(foreignObject, 'y', 0);
|
|
839
|
+
const width = this._parseBridgeNumericAttribute(foreignObject, 'width', 0);
|
|
840
|
+
const height = this._parseBridgeNumericAttribute(foreignObject, 'height', 0);
|
|
841
|
+
const horizontalPadding = 6;
|
|
842
|
+
const verticalPadding = 4;
|
|
843
|
+
const usableWidth = Math.max(12, width - horizontalPadding * 2);
|
|
844
|
+
const fontDescriptor = { fontSize, fontFamily, fontWeight, fontStyle };
|
|
845
|
+
const wrappedLines = extracted.fromRenderedLayout
|
|
846
|
+
? extracted.lines.slice(0, 24)
|
|
847
|
+
: this._wrapBridgeForeignObjectLines(extracted.lines, fontDescriptor, usableWidth, whiteSpace);
|
|
848
|
+
if (wrappedLines.length === 0) {
|
|
849
|
+
foreignObject.remove();
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const totalHeight = (wrappedLines.length - 1) * lineHeight + fontSize;
|
|
854
|
+
const startY = y + Math.max(verticalPadding + fontSize * 0.85, (height - totalHeight) * 0.5 + fontSize * 0.85);
|
|
855
|
+
let anchor = 'middle';
|
|
856
|
+
let textX = x + (width * 0.5);
|
|
857
|
+
if (textAlign === 'left' || textAlign === 'start') {
|
|
858
|
+
anchor = 'start';
|
|
859
|
+
textX = x + horizontalPadding;
|
|
860
|
+
} else if (textAlign === 'right' || textAlign === 'end') {
|
|
861
|
+
anchor = 'end';
|
|
862
|
+
textX = x + width - horizontalPadding;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
866
|
+
textElement.setAttribute('fill', fill);
|
|
867
|
+
textElement.setAttribute('font-size', `${fontSize}`);
|
|
868
|
+
textElement.setAttribute('font-family', fontFamily);
|
|
869
|
+
textElement.setAttribute('font-weight', fontWeight);
|
|
870
|
+
textElement.setAttribute('font-style', fontStyle);
|
|
871
|
+
textElement.setAttribute('text-anchor', anchor);
|
|
872
|
+
textElement.setAttribute('dominant-baseline', 'alphabetic');
|
|
873
|
+
|
|
874
|
+
wrappedLines.forEach((line, index) => {
|
|
875
|
+
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
|
876
|
+
tspan.textContent = line;
|
|
877
|
+
tspan.setAttribute('x', `${textX}`);
|
|
878
|
+
if (index === 0) {
|
|
879
|
+
tspan.setAttribute('y', `${startY}`);
|
|
880
|
+
} else {
|
|
881
|
+
tspan.setAttribute('dy', `${lineHeight}`);
|
|
882
|
+
}
|
|
883
|
+
textElement.appendChild(tspan);
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
foreignObject.parentNode.insertBefore(textElement, foreignObject);
|
|
887
|
+
foreignObject.remove();
|
|
888
|
+
},
|
|
889
|
+
|
|
890
|
+
_convertBridgeForeignObjectsToSvgText: function(svgElement) {
|
|
891
|
+
const foreignObjects = Array.from(svgElement.querySelectorAll('foreignObject'));
|
|
892
|
+
foreignObjects.forEach((foreignObject) => this._convertBridgeForeignObjectToSvgText(svgElement, foreignObject));
|
|
893
|
+
},
|
|
894
|
+
|
|
895
|
+
_renderParityMermaidSvg: async function(sourceText, options = {}) {
|
|
896
|
+
if (!window.mermaid) {
|
|
897
|
+
throw new Error('Mermaid runtime is unavailable in the frontend renderer.');
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
const source = this._normalizeBridgeMermaidDefinition(sourceText || '');
|
|
901
|
+
if (!source) {
|
|
902
|
+
throw new Error('Mermaid render request is missing source.');
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
const theme = String(options.theme || 'dark') === 'default' ? 'default' : 'dark';
|
|
906
|
+
window.mermaid.initialize(this._getReaderLikeMermaidConfig(theme));
|
|
907
|
+
|
|
908
|
+
const requestedWidth = Number.isFinite(Number(options.maxWidth)) && Number(options.maxWidth) > 0
|
|
909
|
+
? Math.floor(Number(options.maxWidth))
|
|
910
|
+
: 620;
|
|
911
|
+
const requestedHeight = Number.isFinite(Number(options.maxHeight)) && Number(options.maxHeight) > 0
|
|
912
|
+
? Math.floor(Number(options.maxHeight))
|
|
913
|
+
: 860;
|
|
914
|
+
const hostWidth = Math.max(240, requestedWidth);
|
|
915
|
+
const host = document.createElement('div');
|
|
916
|
+
host.className = 'mermaid-render-host-offscreen';
|
|
917
|
+
host.style.position = 'fixed';
|
|
918
|
+
host.style.left = '-20000px';
|
|
919
|
+
host.style.top = '0';
|
|
920
|
+
host.style.width = String(hostWidth) + 'px';
|
|
921
|
+
host.style.minWidth = String(hostWidth) + 'px';
|
|
922
|
+
host.style.height = 'auto';
|
|
923
|
+
host.style.overflow = 'visible';
|
|
924
|
+
host.style.opacity = '0';
|
|
925
|
+
host.style.pointerEvents = 'none';
|
|
926
|
+
host.style.background = 'transparent';
|
|
927
|
+
host.style.fontFamily = '"Microsoft YaHei UI", "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", "Segoe UI", sans-serif';
|
|
928
|
+
document.body.appendChild(host);
|
|
929
|
+
|
|
930
|
+
try {
|
|
931
|
+
const renderId = 'parity-mermaid-' + Date.now() + '-' + Math.random().toString(16).slice(2);
|
|
932
|
+
const result = await window.mermaid.render(renderId, source, host);
|
|
933
|
+
if (result && typeof result.svg === 'string' && this._isBridgeMermaidErrorSvgMarkup(result.svg)) {
|
|
934
|
+
throw new Error('Frontend Mermaid parity renderer returned an error SVG instead of a diagram.');
|
|
935
|
+
}
|
|
936
|
+
let svgElement = host.querySelector('svg');
|
|
937
|
+
if (!svgElement) {
|
|
938
|
+
const parser = new DOMParser();
|
|
939
|
+
const documentSvg = parser.parseFromString(result.svg, 'image/svg+xml');
|
|
940
|
+
const parsedSvg = documentSvg.querySelector('svg');
|
|
941
|
+
if (!parsedSvg) {
|
|
942
|
+
throw new Error('Frontend Mermaid parity renderer did not produce an SVG root.');
|
|
943
|
+
}
|
|
944
|
+
host.replaceChildren(parsedSvg);
|
|
945
|
+
svgElement = host.querySelector('svg');
|
|
946
|
+
}
|
|
947
|
+
if (!svgElement) {
|
|
948
|
+
throw new Error('Frontend Mermaid parity renderer did not produce an SVG root.');
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
952
|
+
svgElement.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
|
|
953
|
+
this._convertBridgeForeignObjectsToSvgText(svgElement);
|
|
954
|
+
const naturalSize = this._tightenBridgeMermaidSvgBounds(svgElement);
|
|
955
|
+
const clampedSize = this._clampBridgeMermaidSize(naturalSize.width, naturalSize.height, requestedWidth, requestedHeight);
|
|
956
|
+
svgElement.setAttribute('width', String(clampedSize.width));
|
|
957
|
+
svgElement.setAttribute('height', String(clampedSize.height));
|
|
958
|
+
svgElement.style.maxWidth = '100%';
|
|
959
|
+
svgElement.style.height = 'auto';
|
|
960
|
+
svgElement.style.background = 'transparent';
|
|
961
|
+
|
|
962
|
+
return {
|
|
963
|
+
source,
|
|
964
|
+
svg: this._serializeBridgeMermaidSvg(svgElement),
|
|
965
|
+
width: Math.max(1, Math.round(clampedSize.width)),
|
|
966
|
+
height: Math.max(1, Math.round(clampedSize.height))
|
|
967
|
+
};
|
|
968
|
+
} finally {
|
|
969
|
+
host.remove();
|
|
970
|
+
}
|
|
971
|
+
},
|
|
972
|
+
|
|
601
973
|
_upsertBridgeMermaidOverrideStyle: function(svgElement) {
|
|
602
974
|
const styleId = 'noteconnection-mermaid-overrides';
|
|
603
975
|
let styleNode = svgElement.querySelector('#' + styleId);
|
|
@@ -725,15 +1097,40 @@ window.pathApp = {
|
|
|
725
1097
|
},
|
|
726
1098
|
|
|
727
1099
|
_normalizeBridgeInlineText: function(text) {
|
|
1100
|
+
if (window._pathUtils) return window._pathUtils.normalizeBridgeInlineText(text);
|
|
728
1101
|
return String(text || '').replace(/\s+/g, ' ').trim();
|
|
729
1102
|
},
|
|
730
1103
|
|
|
1104
|
+
_normalizeBridgeMermaidDefinition: function(source) {
|
|
1105
|
+
if (window._pathUtils && typeof window._pathUtils.normalizeBridgeMermaidDefinition === 'function') {
|
|
1106
|
+
return window._pathUtils.normalizeBridgeMermaidDefinition(source);
|
|
1107
|
+
}
|
|
1108
|
+
return String(source || '').trim();
|
|
1109
|
+
},
|
|
1110
|
+
|
|
1111
|
+
_isBridgeMermaidErrorSvgMarkup: function(markup) {
|
|
1112
|
+
const text = String(markup || '').toLowerCase();
|
|
1113
|
+
if (!text) {
|
|
1114
|
+
return false;
|
|
1115
|
+
}
|
|
1116
|
+
return (
|
|
1117
|
+
text.includes('syntax error in text') ||
|
|
1118
|
+
text.includes('lexical error on line') ||
|
|
1119
|
+
text.includes('parse error on line') ||
|
|
1120
|
+
text.includes('mermaid version') ||
|
|
1121
|
+
text.includes('class="error-icon"') ||
|
|
1122
|
+
text.includes('id="error-icon"')
|
|
1123
|
+
);
|
|
1124
|
+
},
|
|
1125
|
+
|
|
731
1126
|
_parseBridgeNumericAttribute: function(element, name, fallback = 0) {
|
|
1127
|
+
if (window._pathUtils) return window._pathUtils.parseBridgeNumericAttribute(element, name, fallback);
|
|
732
1128
|
const numeric = Number.parseFloat(String(element?.getAttribute?.(name) || ''));
|
|
733
1129
|
return Number.isFinite(numeric) ? numeric : fallback;
|
|
734
1130
|
},
|
|
735
1131
|
|
|
736
1132
|
_extractBridgeInlineStyleValue: function(styleValue, propertyName) {
|
|
1133
|
+
if (window._pathUtils) return window._pathUtils.extractBridgeInlineStyleValue(styleValue, propertyName);
|
|
737
1134
|
if (!styleValue) {
|
|
738
1135
|
return null;
|
|
739
1136
|
}
|
|
@@ -743,6 +1140,7 @@ window.pathApp = {
|
|
|
743
1140
|
},
|
|
744
1141
|
|
|
745
1142
|
_resolveBridgeTextProperty: function(element, propertyName) {
|
|
1143
|
+
if (window._pathUtils) return window._pathUtils.resolveBridgeTextProperty(element, propertyName);
|
|
746
1144
|
let current = element;
|
|
747
1145
|
while (current) {
|
|
748
1146
|
const attributeValue = current.getAttribute && current.getAttribute(propertyName);
|
|
@@ -759,6 +1157,7 @@ window.pathApp = {
|
|
|
759
1157
|
},
|
|
760
1158
|
|
|
761
1159
|
_parseBridgeCssLength: function(lengthValue, baseFontSize) {
|
|
1160
|
+
if (window._pathUtils) return window._pathUtils.parseBridgeCssLength(lengthValue, baseFontSize);
|
|
762
1161
|
if (!lengthValue) {
|
|
763
1162
|
return 0;
|
|
764
1163
|
}
|
|
@@ -780,12 +1179,14 @@ window.pathApp = {
|
|
|
780
1179
|
},
|
|
781
1180
|
|
|
782
1181
|
_resolveBridgeSvgFontSize: function(element) {
|
|
1182
|
+
if (window._pathUtils) return window._pathUtils.resolveBridgeSvgFontSize(element);
|
|
783
1183
|
const resolvedValue = this._resolveBridgeTextProperty(element, 'font-size');
|
|
784
1184
|
const parsed = this._parseBridgeCssLength(resolvedValue, 16);
|
|
785
1185
|
return parsed > 0 ? parsed : 16;
|
|
786
1186
|
},
|
|
787
1187
|
|
|
788
1188
|
_resolveBridgeSvgLineHeight: function(element, fontSize) {
|
|
1189
|
+
if (window._pathUtils) return window._pathUtils.resolveBridgeSvgLineHeight(element, fontSize);
|
|
789
1190
|
const resolvedValue = this._resolveBridgeTextProperty(element, 'line-height');
|
|
790
1191
|
const parsed = this._parseBridgeCssLength(resolvedValue, fontSize);
|
|
791
1192
|
return parsed > 0 ? parsed : Math.max(fontSize * 1.18, fontSize + 4);
|
|
@@ -796,6 +1197,7 @@ window.pathApp = {
|
|
|
796
1197
|
},
|
|
797
1198
|
|
|
798
1199
|
_estimateBridgeGlyphWidthUnits: function(char) {
|
|
1200
|
+
if (window._pathUtils) return window._pathUtils.estimateBridgeGlyphWidthUnits(char);
|
|
799
1201
|
if (!char) {
|
|
800
1202
|
return 0;
|
|
801
1203
|
}
|
|
@@ -827,6 +1229,7 @@ window.pathApp = {
|
|
|
827
1229
|
},
|
|
828
1230
|
|
|
829
1231
|
_estimateBridgeTextLineWidth: function(text, fontSize) {
|
|
1232
|
+
if (window._pathUtils) return window._pathUtils.estimateBridgeTextLineWidth(text, fontSize);
|
|
830
1233
|
let units = 0;
|
|
831
1234
|
for (const char of Array.from(String(text || ''))) {
|
|
832
1235
|
units += this._estimateBridgeGlyphWidthUnits(char);
|
|
@@ -835,6 +1238,7 @@ window.pathApp = {
|
|
|
835
1238
|
},
|
|
836
1239
|
|
|
837
1240
|
_splitBridgeTokenForWrap: function(token, fontSize, maxLineWidth) {
|
|
1241
|
+
if (window._pathUtils) return window._pathUtils.splitBridgeTokenForWrap(token, fontSize, maxLineWidth);
|
|
838
1242
|
const wrapped = [];
|
|
839
1243
|
let segment = '';
|
|
840
1244
|
for (const char of Array.from(token)) {
|
|
@@ -853,6 +1257,7 @@ window.pathApp = {
|
|
|
853
1257
|
},
|
|
854
1258
|
|
|
855
1259
|
_wrapBridgeMeasurementLine: function(line, fontSize, maxLineWidth) {
|
|
1260
|
+
if (window._pathUtils) return window._pathUtils.wrapBridgeMeasurementLine(line, fontSize, maxLineWidth);
|
|
856
1261
|
const normalizedLine = this._normalizeBridgeInlineText(line);
|
|
857
1262
|
if (!normalizedLine) {
|
|
858
1263
|
return [];
|
|
@@ -1308,79 +1713,38 @@ window.pathApp = {
|
|
|
1308
1713
|
},
|
|
1309
1714
|
|
|
1310
1715
|
_renderMermaidForBridge: async function(payload) {
|
|
1311
|
-
if (!window.mermaid) {
|
|
1312
|
-
throw new Error('Mermaid runtime is unavailable in the frontend renderer.');
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
1716
|
const requestId = String(payload?.requestId || '').trim();
|
|
1316
|
-
|
|
1317
|
-
if (!requestId || !source) {
|
|
1717
|
+
if (!requestId) {
|
|
1318
1718
|
throw new Error('Mermaid render request is missing a request id or source.');
|
|
1319
1719
|
}
|
|
1320
1720
|
|
|
1321
1721
|
const includeStages = payload?.includeStages === true;
|
|
1322
1722
|
const includeSvg = includeStages || payload?.includeSvg === true;
|
|
1323
1723
|
const stageSnapshots = [];
|
|
1324
|
-
const theme = String(payload?.theme || 'dark') === 'default' ? 'default' : 'dark';
|
|
1325
|
-
window.mermaid.initialize(this._getBridgeMermaidConfig(theme));
|
|
1326
|
-
|
|
1327
|
-
const requestedWidth = Number.isFinite(Number(payload?.maxWidth)) && Number(payload.maxWidth) > 0 ? Math.floor(Number(payload.maxWidth)) : 1600;
|
|
1328
|
-
const hostWidth = Math.max(480, requestedWidth);
|
|
1329
|
-
const host = document.createElement('div');
|
|
1330
|
-
host.style.position = 'fixed';
|
|
1331
|
-
host.style.left = '-20000px';
|
|
1332
|
-
host.style.top = '0';
|
|
1333
|
-
host.style.width = String(hostWidth) + 'px';
|
|
1334
|
-
host.style.minWidth = String(hostWidth) + 'px';
|
|
1335
|
-
host.style.height = 'auto';
|
|
1336
|
-
host.style.overflow = 'visible';
|
|
1337
|
-
host.style.opacity = '0';
|
|
1338
|
-
host.style.pointerEvents = 'none';
|
|
1339
|
-
host.style.background = 'transparent';
|
|
1340
|
-
host.style.fontFamily = '"Microsoft YaHei UI", "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", "Segoe UI", sans-serif';
|
|
1341
|
-
document.body.appendChild(host);
|
|
1342
1724
|
|
|
1343
1725
|
let objectUrl = null;
|
|
1344
1726
|
try {
|
|
1345
|
-
const
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
throw new Error('Frontend Mermaid renderer did not produce an SVG root.');
|
|
1354
|
-
}
|
|
1355
|
-
host.replaceChildren(parsedSvg);
|
|
1356
|
-
svgElement = host.querySelector('svg');
|
|
1357
|
-
}
|
|
1727
|
+
const parity = await this._renderParityMermaidSvg(payload?.source || '', {
|
|
1728
|
+
theme: payload?.theme || 'dark',
|
|
1729
|
+
maxWidth: payload?.maxWidth,
|
|
1730
|
+
maxHeight: payload?.maxHeight
|
|
1731
|
+
});
|
|
1732
|
+
const parser = new DOMParser();
|
|
1733
|
+
const documentSvg = parser.parseFromString(parity.svg, 'image/svg+xml');
|
|
1734
|
+
const svgElement = documentSvg.querySelector('svg');
|
|
1358
1735
|
if (!svgElement) {
|
|
1359
|
-
throw new Error('Frontend Mermaid renderer did not produce an SVG root.');
|
|
1736
|
+
throw new Error('Frontend Mermaid parity renderer did not produce an SVG root.');
|
|
1360
1737
|
}
|
|
1361
|
-
|
|
1362
|
-
svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
1363
|
-
svgElement.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
|
|
1364
1738
|
if (includeStages) {
|
|
1365
1739
|
stageSnapshots.push(this._captureBridgeMermaidStage('raw', svgElement));
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
this._normalizeBridgeMermaidSvg(svgElement);
|
|
1369
|
-
if (includeStages) {
|
|
1370
1740
|
stageSnapshots.push(this._captureBridgeMermaidStage('visual_normalized', svgElement));
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
this._fitBridgeMermaidLabelShapes(svgElement);
|
|
1374
|
-
if (includeStages) {
|
|
1375
1741
|
stageSnapshots.push(this._captureBridgeMermaidStage('labels_fitted', svgElement));
|
|
1376
1742
|
}
|
|
1377
1743
|
|
|
1378
|
-
const naturalSize = this._tightenBridgeMermaidSvgBounds(svgElement);
|
|
1379
|
-
const clampedSize = this._clampBridgeMermaidSize(naturalSize.width, naturalSize.height, payload?.maxWidth, payload?.maxHeight);
|
|
1380
1744
|
const requestedRenderScale = Number.isFinite(Number(payload?.renderScale)) && Number(payload.renderScale) > 0 ? Number(payload.renderScale) : 1;
|
|
1381
1745
|
const rasterScale = Math.min(4, Math.max(1, requestedRenderScale));
|
|
1382
|
-
const rasterWidth = Math.max(1, Math.round(
|
|
1383
|
-
const rasterHeight = Math.max(1, Math.round(
|
|
1746
|
+
const rasterWidth = Math.max(1, Math.round(parity.width * rasterScale));
|
|
1747
|
+
const rasterHeight = Math.max(1, Math.round(parity.height * rasterScale));
|
|
1384
1748
|
svgElement.setAttribute('width', String(rasterWidth));
|
|
1385
1749
|
svgElement.setAttribute('height', String(rasterHeight));
|
|
1386
1750
|
svgElement.style.maxWidth = String(rasterWidth) + 'px';
|
|
@@ -1431,7 +1795,6 @@ window.pathApp = {
|
|
|
1431
1795
|
if (objectUrl) {
|
|
1432
1796
|
URL.revokeObjectURL(objectUrl);
|
|
1433
1797
|
}
|
|
1434
|
-
host.remove();
|
|
1435
1798
|
}
|
|
1436
1799
|
},
|
|
1437
1800
|
|
|
@@ -1546,98 +1909,1520 @@ window.pathApp = {
|
|
|
1546
1909
|
});
|
|
1547
1910
|
},
|
|
1548
1911
|
|
|
1549
|
-
_sanitizeTargetIds: function(candidateIds) {
|
|
1550
|
-
if (!Array.isArray(candidateIds)) {
|
|
1551
|
-
return [];
|
|
1912
|
+
_sanitizeTargetIds: function(candidateIds) {
|
|
1913
|
+
if (!Array.isArray(candidateIds)) {
|
|
1914
|
+
return [];
|
|
1915
|
+
}
|
|
1916
|
+
const uniqueIds = [];
|
|
1917
|
+
const seen = new Set();
|
|
1918
|
+
candidateIds.forEach((id) => {
|
|
1919
|
+
if (typeof id !== 'string') return;
|
|
1920
|
+
const normalized = id.trim();
|
|
1921
|
+
if (!normalized || seen.has(normalized)) return;
|
|
1922
|
+
seen.add(normalized);
|
|
1923
|
+
uniqueIds.push(normalized);
|
|
1924
|
+
});
|
|
1925
|
+
return uniqueIds;
|
|
1926
|
+
},
|
|
1927
|
+
|
|
1928
|
+
_getDefaultTargetIds: function(limit = 1) {
|
|
1929
|
+
const maxCount = Math.max(0, Number.isFinite(limit) ? Math.floor(limit) : 0);
|
|
1930
|
+
if (maxCount <= 0) return [];
|
|
1931
|
+
const result = [];
|
|
1932
|
+
const seen = new Set();
|
|
1933
|
+
const appendCandidate = (rawId) => {
|
|
1934
|
+
if (typeof rawId !== 'string') return;
|
|
1935
|
+
const normalized = rawId.trim();
|
|
1936
|
+
if (!normalized || seen.has(normalized)) return;
|
|
1937
|
+
seen.add(normalized);
|
|
1938
|
+
result.push(normalized);
|
|
1939
|
+
};
|
|
1940
|
+
|
|
1941
|
+
const sourceData = this._getSourceGraphData();
|
|
1942
|
+
if (sourceData && Array.isArray(sourceData.nodes)) {
|
|
1943
|
+
sourceData.nodes.forEach((node) => appendCandidate(node?.id));
|
|
1944
|
+
}
|
|
1945
|
+
if (result.length < maxCount && Array.isArray(this.nodes)) {
|
|
1946
|
+
this.nodes.forEach((node) => appendCandidate(node?.id));
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
return result.slice(0, maxCount);
|
|
1950
|
+
},
|
|
1951
|
+
|
|
1952
|
+
_isTauriMode: function() {
|
|
1953
|
+
const hasTauriGlobal = typeof window !== 'undefined' && !!window.__TAURI__;
|
|
1954
|
+
const runtimeCaps = (typeof window !== 'undefined' && window.__NC_RUNTIME_CAPS) ? window.__NC_RUNTIME_CAPS : null;
|
|
1955
|
+
const hasDesktopRuntimeCaps = !!(runtimeCaps && runtimeCaps.supports_sidecar === true);
|
|
1956
|
+
const userAgent = typeof navigator !== 'undefined' ? String(navigator.userAgent || '') : '';
|
|
1957
|
+
return hasTauriGlobal || hasDesktopRuntimeCaps || userAgent.includes('Tauri');
|
|
1958
|
+
},
|
|
1959
|
+
|
|
1960
|
+
_getModeValue: function() {
|
|
1961
|
+
if (this._isTauriMode()) {
|
|
1962
|
+
return this.runtimeConfig.mode || (this.currentTargetId ? 'diffusion' : 'domain');
|
|
1963
|
+
}
|
|
1964
|
+
return document.getElementById('learning-mode')?.value || 'domain';
|
|
1965
|
+
},
|
|
1966
|
+
|
|
1967
|
+
_getStrategyValue: function() {
|
|
1968
|
+
if (this._isTauriMode()) {
|
|
1969
|
+
return this.runtimeConfig.strategy || 'foundational';
|
|
1970
|
+
}
|
|
1971
|
+
return document.getElementById('strategy')?.value || 'foundational';
|
|
1972
|
+
},
|
|
1973
|
+
|
|
1974
|
+
_getLayoutValue: function() {
|
|
1975
|
+
if (this._isTauriMode()) {
|
|
1976
|
+
// Layout is backend-defaulted to Track(Focus) in Tauri flow.
|
|
1977
|
+
return 'orbital';
|
|
1978
|
+
}
|
|
1979
|
+
return document.getElementById('layout-style')?.value || 'orbital';
|
|
1980
|
+
},
|
|
1981
|
+
|
|
1982
|
+
_getRetainHistoryEnabled: function() {
|
|
1983
|
+
if (this._isTauriMode()) {
|
|
1984
|
+
return this.runtimeConfig.retainHistory !== false;
|
|
1985
|
+
}
|
|
1986
|
+
return document.getElementById('set-retain-history')?.checked ?? true;
|
|
1987
|
+
},
|
|
1988
|
+
|
|
1989
|
+
_toggleHistorySidebar: function() {
|
|
1990
|
+
const sidebar = document.getElementById('learning-history-sidebar');
|
|
1991
|
+
if (!sidebar) return;
|
|
1992
|
+
sidebar.style.zIndex = '3000';
|
|
1993
|
+
if (sidebar.style.display === 'none' || sidebar.style.display === '') {
|
|
1994
|
+
sidebar.style.display = 'flex';
|
|
1995
|
+
sidebar.offsetHeight;
|
|
1996
|
+
setTimeout(() => {
|
|
1997
|
+
sidebar.style.transform = 'translateX(0)';
|
|
1998
|
+
}, 10);
|
|
1999
|
+
} else {
|
|
2000
|
+
sidebar.style.transform = 'translateX(100%)';
|
|
2001
|
+
setTimeout(() => {
|
|
2002
|
+
sidebar.style.display = 'none';
|
|
2003
|
+
}, 300);
|
|
2004
|
+
}
|
|
2005
|
+
},
|
|
2006
|
+
|
|
2007
|
+
_normalizeLearningWorkbenchUserId: function(rawValue) {
|
|
2008
|
+
const candidate = String(rawValue || '')
|
|
2009
|
+
.trim()
|
|
2010
|
+
.toLowerCase()
|
|
2011
|
+
.replace(/[^\p{L}\p{N}_-]+/gu, '_')
|
|
2012
|
+
.replace(/_+/g, '_');
|
|
2013
|
+
if (!candidate) {
|
|
2014
|
+
return 'path_user_default';
|
|
2015
|
+
}
|
|
2016
|
+
return candidate.slice(0, 64);
|
|
2017
|
+
},
|
|
2018
|
+
|
|
2019
|
+
_getLearningQualityBaselineStorageKey: function(userId) {
|
|
2020
|
+
const normalizedUserId = this._normalizeLearningWorkbenchUserId(userId);
|
|
2021
|
+
return `nc_learning_quality_baseline::${normalizedUserId}`;
|
|
2022
|
+
},
|
|
2023
|
+
|
|
2024
|
+
_normalizeLearningQualitySnapshotShape: function(snapshot) {
|
|
2025
|
+
if (!snapshot || typeof snapshot !== 'object') {
|
|
2026
|
+
return null;
|
|
2027
|
+
}
|
|
2028
|
+
const requiredKeys = [
|
|
2029
|
+
'retestPassRatePct',
|
|
2030
|
+
'misconceptionRecurrenceRatePct',
|
|
2031
|
+
'evidenceBackedSuggestionRatioPct',
|
|
2032
|
+
'averagePathMasteryGainPct',
|
|
2033
|
+
'randomPathMasteryGainPct',
|
|
2034
|
+
];
|
|
2035
|
+
const hasRequiredKeys = requiredKeys.every((key) => Number.isFinite(Number(snapshot[key])));
|
|
2036
|
+
if (!hasRequiredKeys) {
|
|
2037
|
+
return null;
|
|
2038
|
+
}
|
|
2039
|
+
const clamp = (value, minValue, maxValue, digits = 4) => {
|
|
2040
|
+
const numericValue = Number(value || 0);
|
|
2041
|
+
const bounded = Math.min(maxValue, Math.max(minValue, numericValue));
|
|
2042
|
+
return Number(bounded.toFixed(digits));
|
|
2043
|
+
};
|
|
2044
|
+
return {
|
|
2045
|
+
retestPassRatePct: clamp(snapshot.retestPassRatePct, 0, 100),
|
|
2046
|
+
misconceptionRecurrenceRatePct: clamp(snapshot.misconceptionRecurrenceRatePct, 0, 100),
|
|
2047
|
+
evidenceBackedSuggestionRatioPct: clamp(snapshot.evidenceBackedSuggestionRatioPct, 0, 100),
|
|
2048
|
+
averagePathMasteryGainPct: clamp(snapshot.averagePathMasteryGainPct, 0, 100),
|
|
2049
|
+
randomPathMasteryGainPct: clamp(snapshot.randomPathMasteryGainPct, 0, 100),
|
|
2050
|
+
historyWindowDays: Math.max(1, Math.min(180, Math.floor(Number(snapshot.historyWindowDays || 14)))),
|
|
2051
|
+
historyWindowRecords: Math.max(0, Math.floor(Number(snapshot.historyWindowRecords || 0))),
|
|
2052
|
+
historyWindowAverageMasteryDelta: clamp(snapshot.historyWindowAverageMasteryDelta, -1, 1, 6),
|
|
2053
|
+
historyWindowRetestPositiveDeltaRatePct: clamp(snapshot.historyWindowRetestPositiveDeltaRatePct, 0, 100),
|
|
2054
|
+
queryP95Ms: clamp(snapshot.queryP95Ms, 0, 60000),
|
|
2055
|
+
};
|
|
2056
|
+
},
|
|
2057
|
+
|
|
2058
|
+
_loadLearningQualityBaseline: function(userId) {
|
|
2059
|
+
if (typeof localStorage === 'undefined') {
|
|
2060
|
+
return null;
|
|
2061
|
+
}
|
|
2062
|
+
try {
|
|
2063
|
+
const storageKey = this._getLearningQualityBaselineStorageKey(userId);
|
|
2064
|
+
const raw = localStorage.getItem(storageKey);
|
|
2065
|
+
if (!raw) {
|
|
2066
|
+
return null;
|
|
2067
|
+
}
|
|
2068
|
+
const parsed = JSON.parse(raw);
|
|
2069
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2070
|
+
return null;
|
|
2071
|
+
}
|
|
2072
|
+
return this._normalizeLearningQualitySnapshotShape(parsed.snapshot || null);
|
|
2073
|
+
} catch (error) {
|
|
2074
|
+
console.warn('[PathApp] Failed to load quality baseline:', error);
|
|
2075
|
+
return null;
|
|
2076
|
+
}
|
|
2077
|
+
},
|
|
2078
|
+
|
|
2079
|
+
_fetchLearningQualityBaselineFromServer: async function(userId) {
|
|
2080
|
+
const normalizedUserId = this._normalizeLearningWorkbenchUserId(userId);
|
|
2081
|
+
try {
|
|
2082
|
+
const result = await this._requestLearningApiGet('/api/knowledge/quality/baseline', {
|
|
2083
|
+
userId: normalizedUserId,
|
|
2084
|
+
});
|
|
2085
|
+
if (!result || result.found !== true) {
|
|
2086
|
+
return null;
|
|
2087
|
+
}
|
|
2088
|
+
return this._normalizeLearningQualitySnapshotShape(result.snapshot || null);
|
|
2089
|
+
} catch (error) {
|
|
2090
|
+
console.warn('[PathApp] Failed to fetch quality baseline from server:', error);
|
|
2091
|
+
return null;
|
|
2092
|
+
}
|
|
2093
|
+
},
|
|
2094
|
+
|
|
2095
|
+
_saveLearningQualityBaseline: function(userId, snapshot) {
|
|
2096
|
+
if (typeof localStorage === 'undefined') {
|
|
2097
|
+
return false;
|
|
2098
|
+
}
|
|
2099
|
+
const normalizedSnapshot = this._normalizeLearningQualitySnapshotShape(snapshot);
|
|
2100
|
+
if (!normalizedSnapshot) {
|
|
2101
|
+
return false;
|
|
2102
|
+
}
|
|
2103
|
+
try {
|
|
2104
|
+
const storageKey = this._getLearningQualityBaselineStorageKey(userId);
|
|
2105
|
+
localStorage.setItem(storageKey, JSON.stringify({
|
|
2106
|
+
userId: this._normalizeLearningWorkbenchUserId(userId),
|
|
2107
|
+
storedAt: new Date().toISOString(),
|
|
2108
|
+
snapshot: normalizedSnapshot,
|
|
2109
|
+
}));
|
|
2110
|
+
return true;
|
|
2111
|
+
} catch (error) {
|
|
2112
|
+
console.warn('[PathApp] Failed to save quality baseline:', error);
|
|
2113
|
+
return false;
|
|
2114
|
+
}
|
|
2115
|
+
},
|
|
2116
|
+
|
|
2117
|
+
_saveLearningQualityBaselineToServer: async function(userId, snapshot) {
|
|
2118
|
+
const normalizedUserId = this._normalizeLearningWorkbenchUserId(userId);
|
|
2119
|
+
const normalizedSnapshot = this._normalizeLearningQualitySnapshotShape(snapshot);
|
|
2120
|
+
if (!normalizedSnapshot) {
|
|
2121
|
+
return false;
|
|
2122
|
+
}
|
|
2123
|
+
try {
|
|
2124
|
+
const result = await this._requestLearningApi('/api/knowledge/quality/baseline', {
|
|
2125
|
+
userId: normalizedUserId,
|
|
2126
|
+
snapshot: normalizedSnapshot,
|
|
2127
|
+
});
|
|
2128
|
+
return Boolean(result && result.found === true);
|
|
2129
|
+
} catch (error) {
|
|
2130
|
+
console.warn('[PathApp] Failed to persist quality baseline to server:', error);
|
|
2131
|
+
return false;
|
|
2132
|
+
}
|
|
2133
|
+
},
|
|
2134
|
+
|
|
2135
|
+
_syncLearningQualityBaselineFromServer: async function(userId) {
|
|
2136
|
+
const normalizedUserId = this._normalizeLearningWorkbenchUserId(userId);
|
|
2137
|
+
const serverSnapshot = await this._fetchLearningQualityBaselineFromServer(normalizedUserId);
|
|
2138
|
+
if (!serverSnapshot) {
|
|
2139
|
+
this.learningWorkbench.qualityBaselineSnapshot = this._loadLearningQualityBaseline(normalizedUserId);
|
|
2140
|
+
return this.learningWorkbench.qualityBaselineSnapshot;
|
|
2141
|
+
}
|
|
2142
|
+
this.learningWorkbench.qualityBaselineSnapshot = serverSnapshot;
|
|
2143
|
+
this._saveLearningQualityBaseline(normalizedUserId, serverSnapshot);
|
|
2144
|
+
return serverSnapshot;
|
|
2145
|
+
},
|
|
2146
|
+
|
|
2147
|
+
evaluateLearningWorkbenchQualityGates: async function() {
|
|
2148
|
+
if (this.learningWorkbench.loading) {
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
const userId = this._normalizeLearningWorkbenchUserId(this.learningWorkbench.userId);
|
|
2152
|
+
const currentSnapshot = this.learningWorkbench.qualitySnapshot?.snapshot || null;
|
|
2153
|
+
const baselineSnapshot = await this._syncLearningQualityBaselineFromServer(userId);
|
|
2154
|
+
if (!currentSnapshot) {
|
|
2155
|
+
this._setLearningWorkbenchStatus('No current quality snapshot available. Refresh first.', true);
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
if (!baselineSnapshot) {
|
|
2159
|
+
this._setLearningWorkbenchStatus('No quality baseline found. Set baseline first.', true);
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
this.learningWorkbench.loading = true;
|
|
2163
|
+
this.learningWorkbench.qualityBaselineSnapshot = baselineSnapshot;
|
|
2164
|
+
this._setLearningWorkbenchStatus('Evaluating learning quality gates...');
|
|
2165
|
+
this._renderLearningWorkbenchState();
|
|
2166
|
+
try {
|
|
2167
|
+
let evaluation = null;
|
|
2168
|
+
let usedLegacyFallback = false;
|
|
2169
|
+
try {
|
|
2170
|
+
const evaluationEnvelope = await this._requestLearningApi('/api/knowledge/quality/baseline/evaluate', {
|
|
2171
|
+
userId,
|
|
2172
|
+
current: currentSnapshot,
|
|
2173
|
+
sampledAt: this.learningWorkbench.qualitySnapshot?.sampledAt || undefined,
|
|
2174
|
+
historyWindowDays: this.learningWorkbench.qualitySnapshot?.snapshot?.historyWindowDays || undefined,
|
|
2175
|
+
});
|
|
2176
|
+
evaluation = evaluationEnvelope && typeof evaluationEnvelope === 'object'
|
|
2177
|
+
? evaluationEnvelope.evaluation
|
|
2178
|
+
: null;
|
|
2179
|
+
} catch (_baselineEvaluateError) {
|
|
2180
|
+
evaluation = await this._requestLearningApi('/api/knowledge/quality/evaluate', {
|
|
2181
|
+
baseline: baselineSnapshot,
|
|
2182
|
+
current: currentSnapshot,
|
|
2183
|
+
});
|
|
2184
|
+
usedLegacyFallback = true;
|
|
2185
|
+
}
|
|
2186
|
+
this.learningWorkbench.qualityGateEvaluation = evaluation || null;
|
|
2187
|
+
this._setLearningWorkbenchStatus(
|
|
2188
|
+
`Quality gate evaluation finished: ${evaluation?.overallPassed === true ? 'PASS' : 'FAIL'}`
|
|
2189
|
+
+ `${usedLegacyFallback ? ' (legacy fallback route).' : '.'}`
|
|
2190
|
+
);
|
|
2191
|
+
} catch (error) {
|
|
2192
|
+
const message = String(error?.message || error || 'Unknown quality gate evaluation error');
|
|
2193
|
+
this.learningWorkbench.lastError = message;
|
|
2194
|
+
this._setLearningWorkbenchStatus(`Quality gate evaluation failed: ${message}`, true);
|
|
2195
|
+
} finally {
|
|
2196
|
+
this.learningWorkbench.loading = false;
|
|
2197
|
+
this._renderLearningWorkbenchState();
|
|
2198
|
+
}
|
|
2199
|
+
},
|
|
2200
|
+
|
|
2201
|
+
setLearningWorkbenchQualityBaselineFromCurrent: async function() {
|
|
2202
|
+
const userId = this._normalizeLearningWorkbenchUserId(this.learningWorkbench.userId);
|
|
2203
|
+
const snapshot = this.learningWorkbench.qualitySnapshot?.snapshot || null;
|
|
2204
|
+
if (!snapshot) {
|
|
2205
|
+
this._setLearningWorkbenchStatus('No quality snapshot available. Refresh first.', true);
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
this.learningWorkbench.loading = true;
|
|
2209
|
+
this._renderLearningWorkbenchState();
|
|
2210
|
+
const persistedServer = await this._saveLearningQualityBaselineToServer(userId, snapshot);
|
|
2211
|
+
const persistedLocal = this._saveLearningQualityBaseline(userId, snapshot);
|
|
2212
|
+
if (!persistedServer && !persistedLocal) {
|
|
2213
|
+
this.learningWorkbench.loading = false;
|
|
2214
|
+
this._renderLearningWorkbenchState();
|
|
2215
|
+
this._setLearningWorkbenchStatus('Failed to persist quality baseline snapshot.', true);
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
this.learningWorkbench.qualityBaselineSnapshot = await this._syncLearningQualityBaselineFromServer(userId);
|
|
2219
|
+
this.learningWorkbench.qualityGateEvaluation = null;
|
|
2220
|
+
this.learningWorkbench.loading = false;
|
|
2221
|
+
const storageHint = persistedServer
|
|
2222
|
+
? 'server + local cache'
|
|
2223
|
+
: 'local cache';
|
|
2224
|
+
this._setLearningWorkbenchStatus(`Quality baseline updated from current snapshot (${storageHint}).`);
|
|
2225
|
+
this._renderLearningWorkbenchState();
|
|
2226
|
+
},
|
|
2227
|
+
|
|
2228
|
+
_normalizeLearningHistoryExecutionKind: function(rawValue) {
|
|
2229
|
+
const value = String(rawValue || 'all').trim().toLowerCase();
|
|
2230
|
+
if (value === 'session' || value === 'retest' || value === 'custom') {
|
|
2231
|
+
return value;
|
|
2232
|
+
}
|
|
2233
|
+
return 'all';
|
|
2234
|
+
},
|
|
2235
|
+
|
|
2236
|
+
_normalizeLearningHistoryDateValue: function(rawValue) {
|
|
2237
|
+
const value = String(rawValue || '').trim();
|
|
2238
|
+
if (!value) {
|
|
2239
|
+
return '';
|
|
2240
|
+
}
|
|
2241
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
2242
|
+
return value;
|
|
2243
|
+
}
|
|
2244
|
+
const parsed = new Date(value);
|
|
2245
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
2246
|
+
return '';
|
|
2247
|
+
}
|
|
2248
|
+
return parsed.toISOString().slice(0, 10);
|
|
2249
|
+
},
|
|
2250
|
+
|
|
2251
|
+
_getLearningWorkbenchSessionHistoryQuery: function() {
|
|
2252
|
+
const existing = this.learningWorkbench.sessionHistoryQuery && typeof this.learningWorkbench.sessionHistoryQuery === 'object'
|
|
2253
|
+
? this.learningWorkbench.sessionHistoryQuery
|
|
2254
|
+
: {};
|
|
2255
|
+
const limit = Math.max(1, Math.min(20, Math.floor(Number(existing.limit) || 8)));
|
|
2256
|
+
const offset = Math.max(0, Math.floor(Number(existing.offset) || 0));
|
|
2257
|
+
const executionKind = this._normalizeLearningHistoryExecutionKind(existing.executionKind);
|
|
2258
|
+
const fromDate = this._normalizeLearningHistoryDateValue(existing.fromDate);
|
|
2259
|
+
const toDate = this._normalizeLearningHistoryDateValue(existing.toDate);
|
|
2260
|
+
const normalized = {
|
|
2261
|
+
limit,
|
|
2262
|
+
offset,
|
|
2263
|
+
executionKind,
|
|
2264
|
+
fromDate,
|
|
2265
|
+
toDate,
|
|
2266
|
+
};
|
|
2267
|
+
this.learningWorkbench.sessionHistoryQuery = normalized;
|
|
2268
|
+
return normalized;
|
|
2269
|
+
},
|
|
2270
|
+
|
|
2271
|
+
_buildLearningWorkbenchSessionHistoryRequest: function(userId) {
|
|
2272
|
+
const query = this._getLearningWorkbenchSessionHistoryQuery();
|
|
2273
|
+
const fromExecutedAt = query.fromDate
|
|
2274
|
+
? `${query.fromDate}T00:00:00.000Z`
|
|
2275
|
+
: undefined;
|
|
2276
|
+
const toExecutedAt = query.toDate
|
|
2277
|
+
? `${query.toDate}T23:59:59.999Z`
|
|
2278
|
+
: undefined;
|
|
2279
|
+
return {
|
|
2280
|
+
userId,
|
|
2281
|
+
limit: query.limit,
|
|
2282
|
+
offset: query.offset,
|
|
2283
|
+
executionKinds: query.executionKind === 'all' ? undefined : [query.executionKind],
|
|
2284
|
+
fromExecutedAt,
|
|
2285
|
+
toExecutedAt,
|
|
2286
|
+
};
|
|
2287
|
+
},
|
|
2288
|
+
|
|
2289
|
+
_setLearningWorkbenchHistoryExecutionKind: function(rawValue, options = {}) {
|
|
2290
|
+
const nextKind = this._normalizeLearningHistoryExecutionKind(rawValue);
|
|
2291
|
+
const query = this._getLearningWorkbenchSessionHistoryQuery();
|
|
2292
|
+
const changed = query.executionKind !== nextKind;
|
|
2293
|
+
this.learningWorkbench.sessionHistoryQuery = {
|
|
2294
|
+
...query,
|
|
2295
|
+
executionKind: nextKind,
|
|
2296
|
+
offset: 0,
|
|
2297
|
+
};
|
|
2298
|
+
this._persistLearningWorkbenchPreferences();
|
|
2299
|
+
if (changed && options.refresh !== false) {
|
|
2300
|
+
void this.refreshLearningWorkbench({ force: false });
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
this._renderLearningWorkbenchState();
|
|
2304
|
+
},
|
|
2305
|
+
|
|
2306
|
+
_applyLearningWorkbenchHistoryDateRange: function(rawFromDate, rawToDate, options = {}) {
|
|
2307
|
+
const fromDate = this._normalizeLearningHistoryDateValue(rawFromDate);
|
|
2308
|
+
const toDate = this._normalizeLearningHistoryDateValue(rawToDate);
|
|
2309
|
+
const query = this._getLearningWorkbenchSessionHistoryQuery();
|
|
2310
|
+
const changed = query.fromDate !== fromDate || query.toDate !== toDate;
|
|
2311
|
+
this.learningWorkbench.sessionHistoryQuery = {
|
|
2312
|
+
...query,
|
|
2313
|
+
fromDate,
|
|
2314
|
+
toDate,
|
|
2315
|
+
offset: 0,
|
|
2316
|
+
};
|
|
2317
|
+
this._persistLearningWorkbenchPreferences();
|
|
2318
|
+
if (changed && options.refresh !== false) {
|
|
2319
|
+
void this.refreshLearningWorkbench({ force: false });
|
|
2320
|
+
return;
|
|
2321
|
+
}
|
|
2322
|
+
this._renderLearningWorkbenchState();
|
|
2323
|
+
},
|
|
2324
|
+
|
|
2325
|
+
_changeLearningWorkbenchHistoryPage: function(direction) {
|
|
2326
|
+
const step = Math.sign(Number(direction) || 0);
|
|
2327
|
+
if (step === 0) {
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
const query = this._getLearningWorkbenchSessionHistoryQuery();
|
|
2331
|
+
const page = this.learningWorkbench.sessionHistory && this.learningWorkbench.sessionHistory.page
|
|
2332
|
+
? this.learningWorkbench.sessionHistory.page
|
|
2333
|
+
: null;
|
|
2334
|
+
if (step > 0 && page && page.hasMore !== true) {
|
|
2335
|
+
return;
|
|
2336
|
+
}
|
|
2337
|
+
const nextOffset = step > 0
|
|
2338
|
+
? query.offset + query.limit
|
|
2339
|
+
: Math.max(0, query.offset - query.limit);
|
|
2340
|
+
if (nextOffset === query.offset) {
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2343
|
+
this.learningWorkbench.sessionHistoryQuery = {
|
|
2344
|
+
...query,
|
|
2345
|
+
offset: nextOffset,
|
|
2346
|
+
};
|
|
2347
|
+
this._persistLearningWorkbenchPreferences();
|
|
2348
|
+
void this.refreshLearningWorkbench({ force: false });
|
|
2349
|
+
},
|
|
2350
|
+
|
|
2351
|
+
_restoreLearningWorkbenchPreferences: function() {
|
|
2352
|
+
try {
|
|
2353
|
+
const stored = localStorage.getItem('nc_learning_workbench_prefs');
|
|
2354
|
+
if (!stored) {
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2357
|
+
const parsed = JSON.parse(stored);
|
|
2358
|
+
if (parsed && typeof parsed === 'object' && typeof parsed.userId === 'string') {
|
|
2359
|
+
this.learningWorkbench.userId = this._normalizeLearningWorkbenchUserId(parsed.userId);
|
|
2360
|
+
}
|
|
2361
|
+
if (parsed && typeof parsed === 'object') {
|
|
2362
|
+
const query = this._getLearningWorkbenchSessionHistoryQuery();
|
|
2363
|
+
this.learningWorkbench.sessionHistoryQuery = {
|
|
2364
|
+
...query,
|
|
2365
|
+
executionKind: this._normalizeLearningHistoryExecutionKind(parsed.historyExecutionKind),
|
|
2366
|
+
fromDate: this._normalizeLearningHistoryDateValue(parsed.historyFromDate),
|
|
2367
|
+
toDate: this._normalizeLearningHistoryDateValue(parsed.historyToDate),
|
|
2368
|
+
offset: 0,
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
this.learningWorkbench.qualityBaselineSnapshot = this._loadLearningQualityBaseline(this.learningWorkbench.userId);
|
|
2372
|
+
this.learningWorkbench.qualityGateEvaluation = null;
|
|
2373
|
+
} catch (error) {
|
|
2374
|
+
console.warn('[PathApp] Failed to restore learning workbench prefs:', error);
|
|
2375
|
+
}
|
|
2376
|
+
},
|
|
2377
|
+
|
|
2378
|
+
_persistLearningWorkbenchPreferences: function() {
|
|
2379
|
+
try {
|
|
2380
|
+
const historyQuery = this._getLearningWorkbenchSessionHistoryQuery();
|
|
2381
|
+
const payload = {
|
|
2382
|
+
userId: this._normalizeLearningWorkbenchUserId(this.learningWorkbench.userId),
|
|
2383
|
+
historyExecutionKind: historyQuery.executionKind,
|
|
2384
|
+
historyFromDate: historyQuery.fromDate,
|
|
2385
|
+
historyToDate: historyQuery.toDate,
|
|
2386
|
+
};
|
|
2387
|
+
localStorage.setItem('nc_learning_workbench_prefs', JSON.stringify(payload));
|
|
2388
|
+
} catch (error) {
|
|
2389
|
+
console.warn('[PathApp] Failed to persist learning workbench prefs:', error);
|
|
2390
|
+
}
|
|
2391
|
+
},
|
|
2392
|
+
|
|
2393
|
+
_toggleLearningWorkbenchSidebar: function() {
|
|
2394
|
+
const sidebar = document.getElementById('learning-workbench-sidebar');
|
|
2395
|
+
if (!sidebar) return;
|
|
2396
|
+
sidebar.style.zIndex = '3001';
|
|
2397
|
+
if (sidebar.style.display === 'none' || sidebar.style.display === '') {
|
|
2398
|
+
sidebar.style.display = 'flex';
|
|
2399
|
+
sidebar.offsetHeight;
|
|
2400
|
+
setTimeout(() => {
|
|
2401
|
+
sidebar.style.transform = 'translateX(0)';
|
|
2402
|
+
}, 10);
|
|
2403
|
+
void this.refreshLearningWorkbench({ force: false });
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
this._closeLearningWorkbenchSidebar();
|
|
2407
|
+
},
|
|
2408
|
+
|
|
2409
|
+
_closeLearningWorkbenchSidebar: function() {
|
|
2410
|
+
const sidebar = document.getElementById('learning-workbench-sidebar');
|
|
2411
|
+
if (!sidebar) return;
|
|
2412
|
+
sidebar.style.transform = 'translateX(-100%)';
|
|
2413
|
+
setTimeout(() => {
|
|
2414
|
+
sidebar.style.display = 'none';
|
|
2415
|
+
}, 300);
|
|
2416
|
+
},
|
|
2417
|
+
|
|
2418
|
+
_setLearningWorkbenchStatus: function(message, isError = false) {
|
|
2419
|
+
const statusEl = document.getElementById('learning-workbench-status');
|
|
2420
|
+
if (!statusEl) return;
|
|
2421
|
+
statusEl.textContent = String(message || '');
|
|
2422
|
+
statusEl.classList.toggle('is-error', !!isError);
|
|
2423
|
+
statusEl.classList.toggle('is-loading', this.learningWorkbench.loading === true);
|
|
2424
|
+
},
|
|
2425
|
+
|
|
2426
|
+
_requestLearningApi: async function(endpoint, payload = {}) {
|
|
2427
|
+
const response = await fetch(endpoint, {
|
|
2428
|
+
method: 'POST',
|
|
2429
|
+
headers: {
|
|
2430
|
+
'Content-Type': 'application/json',
|
|
2431
|
+
},
|
|
2432
|
+
body: JSON.stringify(payload || {}),
|
|
2433
|
+
});
|
|
2434
|
+
|
|
2435
|
+
let body = null;
|
|
2436
|
+
try {
|
|
2437
|
+
body = await response.json();
|
|
2438
|
+
} catch (_error) {
|
|
2439
|
+
body = null;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
if (!response.ok) {
|
|
2443
|
+
const message = body && body.error
|
|
2444
|
+
? body.error
|
|
2445
|
+
: `Request failed (${response.status})`;
|
|
2446
|
+
throw new Error(String(message));
|
|
2447
|
+
}
|
|
2448
|
+
if (!body || body.success !== true) {
|
|
2449
|
+
const message = body && body.error
|
|
2450
|
+
? body.error
|
|
2451
|
+
: `API ${endpoint} returned an unexpected response`;
|
|
2452
|
+
throw new Error(String(message));
|
|
2453
|
+
}
|
|
2454
|
+
return body.result;
|
|
2455
|
+
},
|
|
2456
|
+
|
|
2457
|
+
_requestLearningApiGet: async function(endpoint, params = {}) {
|
|
2458
|
+
const query = new URLSearchParams();
|
|
2459
|
+
Object.keys(params || {}).forEach((key) => {
|
|
2460
|
+
const value = params[key];
|
|
2461
|
+
if (value === undefined || value === null || value === '') {
|
|
2462
|
+
return;
|
|
2463
|
+
}
|
|
2464
|
+
query.set(key, String(value));
|
|
2465
|
+
});
|
|
2466
|
+
const url = query.toString()
|
|
2467
|
+
? `${endpoint}?${query.toString()}`
|
|
2468
|
+
: endpoint;
|
|
2469
|
+
const response = await fetch(url, {
|
|
2470
|
+
method: 'GET',
|
|
2471
|
+
});
|
|
2472
|
+
|
|
2473
|
+
let body = null;
|
|
2474
|
+
try {
|
|
2475
|
+
body = await response.json();
|
|
2476
|
+
} catch (_error) {
|
|
2477
|
+
body = null;
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
if (!response.ok) {
|
|
2481
|
+
const message = body && body.error
|
|
2482
|
+
? body.error
|
|
2483
|
+
: `Request failed (${response.status})`;
|
|
2484
|
+
throw new Error(String(message));
|
|
2485
|
+
}
|
|
2486
|
+
if (!body || body.success !== true) {
|
|
2487
|
+
const message = body && body.error
|
|
2488
|
+
? body.error
|
|
2489
|
+
: `API ${url} returned an unexpected response`;
|
|
2490
|
+
throw new Error(String(message));
|
|
2491
|
+
}
|
|
2492
|
+
return body.result;
|
|
2493
|
+
},
|
|
2494
|
+
|
|
2495
|
+
_getFocusAtomIdsForLearning: function() {
|
|
2496
|
+
const ids = [];
|
|
2497
|
+
if (typeof this.currentTargetId === 'string' && this.currentTargetId.trim()) {
|
|
2498
|
+
ids.push(this.currentTargetId.trim());
|
|
2499
|
+
}
|
|
2500
|
+
if (Array.isArray(this.currentTargetIds)) {
|
|
2501
|
+
this.currentTargetIds.forEach((id) => {
|
|
2502
|
+
if (typeof id === 'string' && id.trim()) {
|
|
2503
|
+
ids.push(id.trim());
|
|
2504
|
+
}
|
|
2505
|
+
});
|
|
2506
|
+
}
|
|
2507
|
+
if (typeof this.centralNodeId === 'string' && this.centralNodeId.trim()) {
|
|
2508
|
+
ids.push(this.centralNodeId.trim());
|
|
2509
|
+
}
|
|
2510
|
+
return Array.from(new Set(ids));
|
|
2511
|
+
},
|
|
2512
|
+
|
|
2513
|
+
_getCurrentFocusNode: function() {
|
|
2514
|
+
const sourceData = this._getSourceGraphData();
|
|
2515
|
+
if (!sourceData || !Array.isArray(sourceData.nodes)) {
|
|
2516
|
+
return null;
|
|
2517
|
+
}
|
|
2518
|
+
const preferredIds = this._getFocusAtomIdsForLearning();
|
|
2519
|
+
for (const candidateId of preferredIds) {
|
|
2520
|
+
const matched = sourceData.nodes.find((node) => node && node.id === candidateId);
|
|
2521
|
+
if (matched) {
|
|
2522
|
+
return matched;
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
if (typeof this.centralNodeId === 'string' && this.centralNodeId.trim()) {
|
|
2526
|
+
const matched = sourceData.nodes.find((node) => node && node.id === this.centralNodeId);
|
|
2527
|
+
if (matched) {
|
|
2528
|
+
return matched;
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
return this.nodes && this.nodes.length > 0 ? this.nodes[0] : null;
|
|
2532
|
+
},
|
|
2533
|
+
|
|
2534
|
+
_buildLearningDocumentFromNode: function(node) {
|
|
2535
|
+
if (!node || typeof node !== 'object') {
|
|
2536
|
+
return null;
|
|
2537
|
+
}
|
|
2538
|
+
const nodeId = typeof node.id === 'string' ? node.id.trim() : '';
|
|
2539
|
+
if (!nodeId) {
|
|
2540
|
+
return null;
|
|
2541
|
+
}
|
|
2542
|
+
const normalizedDocId = `path_${nodeId.toLowerCase().replace(/[^\p{L}\p{N}_-]+/gu, '_').slice(0, 96)}`;
|
|
2543
|
+
const sourcePathRaw = node?.metadata?.filepath
|
|
2544
|
+
|| node?.filepath
|
|
2545
|
+
|| `Knowledge_Base/path_mode/${normalizedDocId}.md`;
|
|
2546
|
+
const sourcePath = String(sourcePathRaw || '').replace(/\\/g, '/');
|
|
2547
|
+
const title = typeof node.label === 'string' && node.label.trim()
|
|
2548
|
+
? node.label.trim()
|
|
2549
|
+
: nodeId;
|
|
2550
|
+
const contentRaw = typeof node.content === 'string'
|
|
2551
|
+
? node.content
|
|
2552
|
+
: (typeof node.summary === 'string' ? node.summary : '');
|
|
2553
|
+
const content = String(contentRaw || '').trim();
|
|
2554
|
+
const finalContent = content.length > 0
|
|
2555
|
+
? `# ${title}\n\n${content}`
|
|
2556
|
+
: `# ${title}\n\nNode ${nodeId} was captured from Path Mode for learning orchestration.`;
|
|
2557
|
+
return {
|
|
2558
|
+
documentId: normalizedDocId,
|
|
2559
|
+
sourcePath,
|
|
2560
|
+
language: 'en',
|
|
2561
|
+
content: finalContent,
|
|
2562
|
+
};
|
|
2563
|
+
},
|
|
2564
|
+
|
|
2565
|
+
ingestFocusNodeForLearningWorkbench: async function() {
|
|
2566
|
+
const focusNode = this._getCurrentFocusNode();
|
|
2567
|
+
if (!focusNode) {
|
|
2568
|
+
this._setLearningWorkbenchStatus('No focus node available to ingest.', true);
|
|
2569
|
+
return;
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
const document = this._buildLearningDocumentFromNode(focusNode);
|
|
2573
|
+
if (!document) {
|
|
2574
|
+
this._setLearningWorkbenchStatus('Failed to build ingest document from focus node.', true);
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
this.learningWorkbench.loading = true;
|
|
2579
|
+
this.learningWorkbench.lastError = '';
|
|
2580
|
+
this._setLearningWorkbenchStatus('Ingesting focus node into learning graph...');
|
|
2581
|
+
try {
|
|
2582
|
+
const result = await this._requestLearningApi('/api/knowledge/ingest', {
|
|
2583
|
+
incremental: true,
|
|
2584
|
+
relationRecomputeMode: 'incremental',
|
|
2585
|
+
documents: [document],
|
|
2586
|
+
});
|
|
2587
|
+
const changedDocs = Number(result?.summary?.changedDocuments || 0);
|
|
2588
|
+
this._setLearningWorkbenchStatus(`Ingest succeeded. Changed documents: ${changedDocs}.`);
|
|
2589
|
+
await this.refreshLearningWorkbench({ force: true });
|
|
2590
|
+
} catch (error) {
|
|
2591
|
+
const message = String(error?.message || error || 'Unknown ingest error');
|
|
2592
|
+
this.learningWorkbench.lastError = message;
|
|
2593
|
+
this._setLearningWorkbenchStatus(`Ingest failed: ${message}`, true);
|
|
2594
|
+
} finally {
|
|
2595
|
+
this.learningWorkbench.loading = false;
|
|
2596
|
+
this._renderLearningWorkbenchState();
|
|
2597
|
+
}
|
|
2598
|
+
},
|
|
2599
|
+
|
|
2600
|
+
refreshLearningWorkbench: async function(options = {}) {
|
|
2601
|
+
if (this.learningWorkbench.loading) {
|
|
2602
|
+
return;
|
|
2603
|
+
}
|
|
2604
|
+
const userId = this._normalizeLearningWorkbenchUserId(this.learningWorkbench.userId);
|
|
2605
|
+
this.learningWorkbench.userId = userId;
|
|
2606
|
+
this.learningWorkbench.qualityBaselineSnapshot = await this._syncLearningQualityBaselineFromServer(userId);
|
|
2607
|
+
this.learningWorkbench.qualityGateEvaluation = null;
|
|
2608
|
+
this._persistLearningWorkbenchPreferences();
|
|
2609
|
+
this.learningWorkbench.loading = true;
|
|
2610
|
+
this.learningWorkbench.lastError = '';
|
|
2611
|
+
this._setLearningWorkbenchStatus('Refreshing learning workbench...');
|
|
2612
|
+
this._renderLearningWorkbenchState();
|
|
2613
|
+
|
|
2614
|
+
const focusAtomIds = this._getFocusAtomIdsForLearning();
|
|
2615
|
+
const includeDivergence = true;
|
|
2616
|
+
const includeRetrain = true;
|
|
2617
|
+
const sessionHistoryRequest = this._buildLearningWorkbenchSessionHistoryRequest(userId);
|
|
2618
|
+
try {
|
|
2619
|
+
const [sessionPlan, qualitySnapshot, misconceptions, runtimeState, sessionHistory] = await Promise.all([
|
|
2620
|
+
this._requestLearningApi('/api/knowledge/session/plan', {
|
|
2621
|
+
userId,
|
|
2622
|
+
focusAtomIds,
|
|
2623
|
+
maxActions: 14,
|
|
2624
|
+
includeDivergence,
|
|
2625
|
+
includeRetrain,
|
|
2626
|
+
}),
|
|
2627
|
+
this._requestLearningApi('/api/knowledge/quality/snapshot', {
|
|
2628
|
+
userId,
|
|
2629
|
+
}),
|
|
2630
|
+
this._requestLearningApi('/api/knowledge/mastery/misconceptions', {
|
|
2631
|
+
userId,
|
|
2632
|
+
atomIds: focusAtomIds,
|
|
2633
|
+
topK: 6,
|
|
2634
|
+
}),
|
|
2635
|
+
fetch('/api/knowledge/state', { method: 'GET' })
|
|
2636
|
+
.then((response) => response.json())
|
|
2637
|
+
.catch(() => null),
|
|
2638
|
+
this._requestLearningApi('/api/knowledge/session/history', sessionHistoryRequest),
|
|
2639
|
+
]);
|
|
2640
|
+
this.learningWorkbench.sessionPlan = sessionPlan || null;
|
|
2641
|
+
this.learningWorkbench.qualitySnapshot = qualitySnapshot || null;
|
|
2642
|
+
this.learningWorkbench.misconceptions = misconceptions || null;
|
|
2643
|
+
this.learningWorkbench.runtimeState = runtimeState && runtimeState.success === true
|
|
2644
|
+
? runtimeState
|
|
2645
|
+
: null;
|
|
2646
|
+
this.learningWorkbench.sessionHistory = sessionHistory || null;
|
|
2647
|
+
this.learningWorkbench.lastUpdatedAt = new Date().toISOString();
|
|
2648
|
+
const currentSnapshot = qualitySnapshot && qualitySnapshot.snapshot
|
|
2649
|
+
? this._normalizeLearningQualitySnapshotShape(qualitySnapshot.snapshot)
|
|
2650
|
+
: null;
|
|
2651
|
+
if (currentSnapshot && this.learningWorkbench.qualityBaselineSnapshot) {
|
|
2652
|
+
try {
|
|
2653
|
+
const qualityGateEvaluation = await this._requestLearningApi('/api/knowledge/quality/evaluate', {
|
|
2654
|
+
baseline: this.learningWorkbench.qualityBaselineSnapshot,
|
|
2655
|
+
current: currentSnapshot,
|
|
2656
|
+
});
|
|
2657
|
+
this.learningWorkbench.qualityGateEvaluation = qualityGateEvaluation || null;
|
|
2658
|
+
} catch (qualityGateError) {
|
|
2659
|
+
console.warn('[PathApp] Quality gate evaluation during refresh failed:', qualityGateError);
|
|
2660
|
+
this.learningWorkbench.qualityGateEvaluation = null;
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
const actionCount = Number(sessionPlan?.summary?.totalActions || sessionPlan?.actions?.length || 0);
|
|
2665
|
+
this._setLearningWorkbenchStatus(`Learning workbench updated. Planned actions: ${actionCount}.`);
|
|
2666
|
+
} catch (error) {
|
|
2667
|
+
const message = String(error?.message || error || 'Unknown refresh error');
|
|
2668
|
+
this.learningWorkbench.lastError = message;
|
|
2669
|
+
this._setLearningWorkbenchStatus(`Refresh failed: ${message}`, true);
|
|
2670
|
+
} finally {
|
|
2671
|
+
this.learningWorkbench.loading = false;
|
|
2672
|
+
this._renderLearningWorkbenchState();
|
|
2673
|
+
}
|
|
2674
|
+
},
|
|
2675
|
+
|
|
2676
|
+
_escapeHtml: function(value) {
|
|
2677
|
+
return String(value || '')
|
|
2678
|
+
.replace(/&/g, '&')
|
|
2679
|
+
.replace(/</g, '<')
|
|
2680
|
+
.replace(/>/g, '>')
|
|
2681
|
+
.replace(/"/g, '"')
|
|
2682
|
+
.replace(/'/g, ''');
|
|
2683
|
+
},
|
|
2684
|
+
|
|
2685
|
+
_collectOptionalAnswerForLearningAction: function(actionKind, atomId) {
|
|
2686
|
+
const normalized = String(actionKind || '').trim().toLowerCase();
|
|
2687
|
+
if (!['quiz', 'review', 'explain'].includes(normalized)) {
|
|
2688
|
+
return '';
|
|
2689
|
+
}
|
|
2690
|
+
if (typeof window === 'undefined' || typeof window.prompt !== 'function') {
|
|
2691
|
+
return '';
|
|
2692
|
+
}
|
|
2693
|
+
const promptMessage = `Optional: enter learner answer for ${actionKind} on ${atomId}.\nLeave empty to skip mastery diagnostics.`;
|
|
2694
|
+
const answer = window.prompt(promptMessage, '');
|
|
2695
|
+
if (typeof answer !== 'string') {
|
|
2696
|
+
return '';
|
|
2697
|
+
}
|
|
2698
|
+
return answer.trim();
|
|
2699
|
+
},
|
|
2700
|
+
|
|
2701
|
+
_collectOptionalAnswersForSessionActions: function(actions = []) {
|
|
2702
|
+
if (typeof window === 'undefined' || typeof window.prompt !== 'function') {
|
|
2703
|
+
return {};
|
|
2704
|
+
}
|
|
2705
|
+
if (!Array.isArray(actions) || actions.length === 0) {
|
|
2706
|
+
return {};
|
|
2707
|
+
}
|
|
2708
|
+
const confirmation = window.prompt(
|
|
2709
|
+
'Provide learner answers for this session run? Type "yes" to enable, leave empty to skip.',
|
|
2710
|
+
''
|
|
2711
|
+
);
|
|
2712
|
+
if (!confirmation || !/^y(es)?$/i.test(String(confirmation).trim())) {
|
|
2713
|
+
return {};
|
|
2714
|
+
}
|
|
2715
|
+
const answerMap = {};
|
|
2716
|
+
const promptableActions = actions
|
|
2717
|
+
.filter((action) => ['quiz', 'review', 'explain'].includes(String(action?.kind || '').trim().toLowerCase()))
|
|
2718
|
+
.slice(0, 6);
|
|
2719
|
+
promptableActions.forEach((action) => {
|
|
2720
|
+
const actionId = String(action?.id || '').trim();
|
|
2721
|
+
if (!actionId) {
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
const atomId = String(action?.atomId || 'unknown_atom').trim();
|
|
2725
|
+
const actionKind = String(action?.kind || 'action').trim();
|
|
2726
|
+
const promptText = `Optional answer for ${actionKind} on ${atomId} (leave empty to skip):`;
|
|
2727
|
+
const answer = window.prompt(promptText, '');
|
|
2728
|
+
if (typeof answer !== 'string') {
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
const normalized = answer.trim();
|
|
2732
|
+
if (normalized.length > 0) {
|
|
2733
|
+
answerMap[actionId] = normalized;
|
|
2734
|
+
}
|
|
2735
|
+
});
|
|
2736
|
+
return answerMap;
|
|
2737
|
+
},
|
|
2738
|
+
|
|
2739
|
+
_appendLearningWorkbenchSessionRecord: function(record) {
|
|
2740
|
+
if (!record || typeof record !== 'object') {
|
|
2741
|
+
return;
|
|
1552
2742
|
}
|
|
1553
|
-
const
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
2743
|
+
const recordId = String(record.id || '').trim();
|
|
2744
|
+
if (!recordId) {
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2747
|
+
const historyQuery = this._getLearningWorkbenchSessionHistoryQuery();
|
|
2748
|
+
if (historyQuery.offset > 0) {
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
const normalizedRecordKind = this._normalizeLearningHistoryExecutionKind(record.executionKind);
|
|
2752
|
+
const recordExecutionKind = normalizedRecordKind === 'all' ? 'session' : normalizedRecordKind;
|
|
2753
|
+
if (historyQuery.executionKind !== 'all' && historyQuery.executionKind !== recordExecutionKind) {
|
|
2754
|
+
return;
|
|
2755
|
+
}
|
|
2756
|
+
const base = this.learningWorkbench.sessionHistory && typeof this.learningWorkbench.sessionHistory === 'object'
|
|
2757
|
+
? this.learningWorkbench.sessionHistory
|
|
2758
|
+
: {
|
|
2759
|
+
userId: this.learningWorkbench.userId,
|
|
2760
|
+
generatedAt: new Date().toISOString(),
|
|
2761
|
+
records: [],
|
|
2762
|
+
page: {
|
|
2763
|
+
limit: historyQuery.limit,
|
|
2764
|
+
offset: 0,
|
|
2765
|
+
returnedRecords: 0,
|
|
2766
|
+
totalFilteredRecords: 0,
|
|
2767
|
+
hasMore: false,
|
|
2768
|
+
nextOffset: null,
|
|
2769
|
+
},
|
|
2770
|
+
summary: {
|
|
2771
|
+
totalRecords: 0,
|
|
2772
|
+
totalExecutedActions: 0,
|
|
2773
|
+
totalUpdatedMasteryCount: 0,
|
|
2774
|
+
averageMasteryDelta: 0,
|
|
2775
|
+
averageTutorConfidence: 0,
|
|
2776
|
+
executionKindBreakdown: [],
|
|
2777
|
+
},
|
|
2778
|
+
};
|
|
2779
|
+
const existingRecords = Array.isArray(base.records) ? base.records.slice() : [];
|
|
2780
|
+
const deduped = [record, ...existingRecords.filter((item) => String(item?.id || '').trim() !== recordId)]
|
|
2781
|
+
.slice(0, historyQuery.limit);
|
|
2782
|
+
const executionKindBreakdown = ['session', 'retest', 'custom'].map((executionKind) => {
|
|
2783
|
+
const kindRecords = deduped.filter((item) => {
|
|
2784
|
+
const normalizedKind = this._normalizeLearningHistoryExecutionKind(item?.executionKind);
|
|
2785
|
+
const resolvedKind = normalizedKind === 'all' ? 'session' : normalizedKind;
|
|
2786
|
+
return resolvedKind === executionKind;
|
|
2787
|
+
});
|
|
2788
|
+
const totalExecutedActions = kindRecords.reduce((sum, item) => sum + Number(item?.executedCount || 0), 0);
|
|
2789
|
+
const averageMasteryDelta = kindRecords.length > 0
|
|
2790
|
+
? kindRecords.reduce((sum, item) => sum + Number(item?.averageMasteryDelta || 0), 0) / kindRecords.length
|
|
2791
|
+
: 0;
|
|
2792
|
+
return {
|
|
2793
|
+
executionKind,
|
|
2794
|
+
recordCount: kindRecords.length,
|
|
2795
|
+
totalExecutedActions,
|
|
2796
|
+
averageMasteryDelta,
|
|
2797
|
+
};
|
|
1561
2798
|
});
|
|
1562
|
-
|
|
2799
|
+
this.learningWorkbench.sessionHistory = {
|
|
2800
|
+
...base,
|
|
2801
|
+
generatedAt: new Date().toISOString(),
|
|
2802
|
+
records: deduped,
|
|
2803
|
+
page: {
|
|
2804
|
+
limit: historyQuery.limit,
|
|
2805
|
+
offset: 0,
|
|
2806
|
+
returnedRecords: deduped.length,
|
|
2807
|
+
totalFilteredRecords: deduped.length,
|
|
2808
|
+
hasMore: false,
|
|
2809
|
+
nextOffset: null,
|
|
2810
|
+
},
|
|
2811
|
+
summary: {
|
|
2812
|
+
...(base.summary || {}),
|
|
2813
|
+
totalRecords: deduped.length,
|
|
2814
|
+
totalExecutedActions: deduped.reduce((sum, item) => sum + Number(item?.executedCount || 0), 0),
|
|
2815
|
+
totalUpdatedMasteryCount: deduped.reduce((sum, item) => sum + Number(item?.updatedMasteryCount || 0), 0),
|
|
2816
|
+
averageMasteryDelta: deduped.length > 0
|
|
2817
|
+
? deduped.reduce((sum, item) => sum + Number(item?.averageMasteryDelta || 0), 0) / deduped.length
|
|
2818
|
+
: 0,
|
|
2819
|
+
averageTutorConfidence: deduped.length > 0
|
|
2820
|
+
? deduped.reduce((sum, item) => sum + Number(item?.averageTutorConfidence || 0), 0) / deduped.length
|
|
2821
|
+
: 0,
|
|
2822
|
+
executionKindBreakdown,
|
|
2823
|
+
},
|
|
2824
|
+
};
|
|
1563
2825
|
},
|
|
1564
2826
|
|
|
1565
|
-
|
|
1566
|
-
const
|
|
1567
|
-
|
|
1568
|
-
const
|
|
1569
|
-
const
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
};
|
|
2827
|
+
executeLearningWorkbenchAction: async function(params = {}) {
|
|
2828
|
+
const userId = this._normalizeLearningWorkbenchUserId(this.learningWorkbench.userId);
|
|
2829
|
+
const atomId = String(params.atomId || '').trim();
|
|
2830
|
+
const actionKind = String(params.actionKind || '').trim();
|
|
2831
|
+
const source = String(params.source || '').trim();
|
|
2832
|
+
if (!atomId || !actionKind) {
|
|
2833
|
+
this._setLearningWorkbenchStatus('Invalid action payload for tutor execution.', true);
|
|
2834
|
+
return;
|
|
2835
|
+
}
|
|
2836
|
+
const answer = this._collectOptionalAnswerForLearningAction(actionKind, atomId);
|
|
2837
|
+
const shouldAnalyzeAnswer = answer.length > 0;
|
|
1577
2838
|
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
2839
|
+
this.learningWorkbench.loading = true;
|
|
2840
|
+
this._setLearningWorkbenchStatus(`Running session action (${actionKind}) for ${atomId}...`);
|
|
2841
|
+
this._renderLearningWorkbenchState();
|
|
2842
|
+
try {
|
|
2843
|
+
const result = await this._requestLearningApi('/api/knowledge/session/action', {
|
|
2844
|
+
userId,
|
|
2845
|
+
action: {
|
|
2846
|
+
atomId,
|
|
2847
|
+
kind: actionKind,
|
|
2848
|
+
source: source || 'session_plan',
|
|
2849
|
+
prompt: `Learning workbench action: ${actionKind} from ${source || 'session_plan'}`,
|
|
2850
|
+
answer: shouldAnalyzeAnswer ? answer : undefined,
|
|
2851
|
+
},
|
|
2852
|
+
autoAnalyzeAnswer: shouldAnalyzeAnswer,
|
|
2853
|
+
autoUpdateMasteryFromAnswer: shouldAnalyzeAnswer,
|
|
2854
|
+
persistMemory: true,
|
|
2855
|
+
memoryLayer: 'session',
|
|
2856
|
+
});
|
|
2857
|
+
const tutorResult = result && result.tutor ? result.tutor : null;
|
|
2858
|
+
this.learningWorkbench.tutorFeedback = {
|
|
2859
|
+
atomId,
|
|
2860
|
+
actionKind,
|
|
2861
|
+
source,
|
|
2862
|
+
result,
|
|
2863
|
+
tutorResult,
|
|
2864
|
+
receivedAt: new Date().toISOString(),
|
|
2865
|
+
};
|
|
2866
|
+
const tutorActionKind = result?.trace?.tutorActionKind || 'unknown';
|
|
2867
|
+
const persistedMemory = result?.trace?.persistedMemory === true;
|
|
2868
|
+
const updatedMastery = result?.trace?.updatedMastery === true;
|
|
2869
|
+
const effectiveOutcome = result?.trace?.effectiveOutcome || 'n/a';
|
|
2870
|
+
const masterySource = result?.trace?.masterySource || 'none';
|
|
2871
|
+
this._setLearningWorkbenchStatus(
|
|
2872
|
+
`Session action finished (${tutorActionKind}); memory ${persistedMemory ? 'persisted' : 'skipped'}; mastery ${updatedMastery ? `updated (${effectiveOutcome}, ${masterySource})` : 'unchanged'}.`
|
|
2873
|
+
);
|
|
2874
|
+
} catch (error) {
|
|
2875
|
+
const message = String(error?.message || error || 'Unknown tutor execution error');
|
|
2876
|
+
this.learningWorkbench.lastError = message;
|
|
2877
|
+
this._setLearningWorkbenchStatus(`Tutor action failed: ${message}`, true);
|
|
2878
|
+
} finally {
|
|
2879
|
+
this.learningWorkbench.loading = false;
|
|
2880
|
+
this._renderLearningWorkbenchState();
|
|
1581
2881
|
}
|
|
1582
|
-
|
|
1583
|
-
|
|
2882
|
+
},
|
|
2883
|
+
|
|
2884
|
+
runLearningWorkbenchSession: async function(options = {}) {
|
|
2885
|
+
if (this.learningWorkbench.loading) {
|
|
2886
|
+
return;
|
|
2887
|
+
}
|
|
2888
|
+
const userId = this._normalizeLearningWorkbenchUserId(this.learningWorkbench.userId);
|
|
2889
|
+
const sessionPlan = this.learningWorkbench.sessionPlan;
|
|
2890
|
+
if (!sessionPlan || !Array.isArray(sessionPlan.actions) || sessionPlan.actions.length === 0) {
|
|
2891
|
+
this._setLearningWorkbenchStatus('No session plan actions to execute. Refresh first.', true);
|
|
2892
|
+
return;
|
|
2893
|
+
}
|
|
2894
|
+
let actionLimit = Number(options.actionLimit);
|
|
2895
|
+
if (!Number.isFinite(actionLimit) || actionLimit <= 0) {
|
|
2896
|
+
if (typeof window !== 'undefined' && typeof window.prompt === 'function') {
|
|
2897
|
+
const suggestedLimit = Math.min(3, sessionPlan.actions.length);
|
|
2898
|
+
const raw = window.prompt(
|
|
2899
|
+
`Run how many planned actions? (1-${Math.min(20, sessionPlan.actions.length)})`,
|
|
2900
|
+
String(suggestedLimit)
|
|
2901
|
+
);
|
|
2902
|
+
if (raw === null) {
|
|
2903
|
+
this._setLearningWorkbenchStatus('Session execution canceled.');
|
|
2904
|
+
return;
|
|
2905
|
+
}
|
|
2906
|
+
actionLimit = Number(raw);
|
|
2907
|
+
} else {
|
|
2908
|
+
actionLimit = Math.min(3, sessionPlan.actions.length);
|
|
2909
|
+
}
|
|
1584
2910
|
}
|
|
2911
|
+
actionLimit = Math.max(1, Math.min(20, Math.floor(Number(actionLimit) || 1), sessionPlan.actions.length));
|
|
2912
|
+
const selectedActions = sessionPlan.actions.slice(0, actionLimit);
|
|
2913
|
+
const answersByActionId = this._collectOptionalAnswersForSessionActions(selectedActions);
|
|
2914
|
+
const answerCount = Object.keys(answersByActionId).length;
|
|
2915
|
+
const autoAnalyzeAnswer = answerCount > 0;
|
|
1585
2916
|
|
|
1586
|
-
|
|
2917
|
+
this.learningWorkbench.loading = true;
|
|
2918
|
+
this._setLearningWorkbenchStatus(`Running session plan execution for top ${actionLimit} actions...`);
|
|
2919
|
+
this._renderLearningWorkbenchState();
|
|
2920
|
+
try {
|
|
2921
|
+
const result = await this._requestLearningApi('/api/knowledge/session/execute', {
|
|
2922
|
+
userId,
|
|
2923
|
+
executionKind: 'session',
|
|
2924
|
+
sessionPlan,
|
|
2925
|
+
actionLimit,
|
|
2926
|
+
answersByActionId,
|
|
2927
|
+
autoAnalyzeAnswer,
|
|
2928
|
+
autoUpdateMasteryFromAnswer: autoAnalyzeAnswer,
|
|
2929
|
+
persistMemory: true,
|
|
2930
|
+
memoryLayer: 'session',
|
|
2931
|
+
});
|
|
2932
|
+
this.learningWorkbench.sessionExecution = {
|
|
2933
|
+
...result,
|
|
2934
|
+
receivedAt: new Date().toISOString(),
|
|
2935
|
+
};
|
|
2936
|
+
this._appendLearningWorkbenchSessionRecord(result?.record || null);
|
|
2937
|
+
const firstExecuted = Array.isArray(result?.items)
|
|
2938
|
+
? result.items.find((item) => item && item.status === 'executed' && item.result)
|
|
2939
|
+
: null;
|
|
2940
|
+
if (firstExecuted && firstExecuted.result) {
|
|
2941
|
+
this.learningWorkbench.tutorFeedback = {
|
|
2942
|
+
atomId: firstExecuted.action?.atomId || '',
|
|
2943
|
+
actionKind: firstExecuted.action?.kind || '',
|
|
2944
|
+
source: firstExecuted.action?.source || 'session_plan',
|
|
2945
|
+
result: firstExecuted.result,
|
|
2946
|
+
tutorResult: firstExecuted.result?.tutor || null,
|
|
2947
|
+
receivedAt: new Date().toISOString(),
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
const summary = result?.summary || {};
|
|
2951
|
+
const retestCount = Number(result?.retestPlan?.summary?.totalActions || 0);
|
|
2952
|
+
this._setLearningWorkbenchStatus(
|
|
2953
|
+
`Session execution finished: executed ${Number(summary.executedCount || 0)}/${Number(summary.attemptedActions || 0)}, mastery updates ${Number(summary.updatedMasteryCount || 0)}, analyzed answers ${Number(summary.analyzedAnswerCount || 0)} (input=${answerCount}), retest actions ${retestCount}.`
|
|
2954
|
+
);
|
|
2955
|
+
} catch (error) {
|
|
2956
|
+
const message = String(error?.message || error || 'Unknown session execution error');
|
|
2957
|
+
this.learningWorkbench.lastError = message;
|
|
2958
|
+
this._setLearningWorkbenchStatus(`Session execution failed: ${message}`, true);
|
|
2959
|
+
} finally {
|
|
2960
|
+
this.learningWorkbench.loading = false;
|
|
2961
|
+
this._renderLearningWorkbenchState();
|
|
2962
|
+
}
|
|
1587
2963
|
},
|
|
1588
2964
|
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
const
|
|
1594
|
-
|
|
1595
|
-
|
|
2965
|
+
runLearningWorkbenchRetestSession: async function() {
|
|
2966
|
+
if (this.learningWorkbench.loading) {
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2969
|
+
const userId = this._normalizeLearningWorkbenchUserId(this.learningWorkbench.userId);
|
|
2970
|
+
const existingExecution = this.learningWorkbench.sessionExecution || null;
|
|
2971
|
+
const retestActions = Array.isArray(existingExecution?.retestPlan?.actions)
|
|
2972
|
+
? existingExecution.retestPlan.actions
|
|
2973
|
+
: [];
|
|
2974
|
+
if (retestActions.length === 0) {
|
|
2975
|
+
this._setLearningWorkbenchStatus('No immediate retest actions available. Run a session first.', true);
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
const answersByActionId = this._collectOptionalAnswersForSessionActions(retestActions);
|
|
2979
|
+
const answerCount = Object.keys(answersByActionId).length;
|
|
2980
|
+
const autoAnalyzeAnswer = answerCount > 0;
|
|
2981
|
+
const generatedAt = new Date().toISOString();
|
|
2982
|
+
const totalEstimatedMinutes = retestActions.reduce(
|
|
2983
|
+
(sum, action) => sum + Number(action?.estimatedMinutes || 0),
|
|
2984
|
+
0
|
|
2985
|
+
);
|
|
2986
|
+
const retestSessionPlan = {
|
|
2987
|
+
userId,
|
|
2988
|
+
generatedAt,
|
|
2989
|
+
actions: retestActions,
|
|
2990
|
+
signals: {
|
|
2991
|
+
misconceptions: [],
|
|
2992
|
+
dueRetrainAtoms: Array.from(new Set(retestActions.map((action) => String(action.atomId || '').trim()))),
|
|
2993
|
+
masteryPathTargets: [],
|
|
2994
|
+
divergenceTargets: [],
|
|
2995
|
+
},
|
|
2996
|
+
summary: {
|
|
2997
|
+
totalActions: retestActions.length,
|
|
2998
|
+
totalEstimatedMinutes,
|
|
2999
|
+
evidenceCoverageRatio: 1,
|
|
3000
|
+
},
|
|
3001
|
+
};
|
|
1596
3002
|
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
3003
|
+
this.learningWorkbench.loading = true;
|
|
3004
|
+
this._setLearningWorkbenchStatus(`Running immediate retest for ${retestActions.length} actions...`);
|
|
3005
|
+
this._renderLearningWorkbenchState();
|
|
3006
|
+
try {
|
|
3007
|
+
const result = await this._requestLearningApi('/api/knowledge/session/execute', {
|
|
3008
|
+
userId,
|
|
3009
|
+
executionKind: 'retest',
|
|
3010
|
+
sessionPlan: retestSessionPlan,
|
|
3011
|
+
actionLimit: retestActions.length,
|
|
3012
|
+
answersByActionId,
|
|
3013
|
+
autoAnalyzeAnswer,
|
|
3014
|
+
autoUpdateMasteryFromAnswer: autoAnalyzeAnswer,
|
|
3015
|
+
persistMemory: true,
|
|
3016
|
+
memoryLayer: 'session',
|
|
3017
|
+
includeRetestPlan: false,
|
|
3018
|
+
});
|
|
3019
|
+
this.learningWorkbench.sessionExecution = {
|
|
3020
|
+
...result,
|
|
3021
|
+
receivedAt: new Date().toISOString(),
|
|
3022
|
+
};
|
|
3023
|
+
this._appendLearningWorkbenchSessionRecord(result?.record || null);
|
|
3024
|
+
const summary = result?.summary || {};
|
|
3025
|
+
this._setLearningWorkbenchStatus(
|
|
3026
|
+
`Retest execution finished: executed ${Number(summary.executedCount || 0)}/${Number(summary.attemptedActions || 0)}, mastery delta ${Number(summary.averageMasteryDelta || 0).toFixed(3)}.`
|
|
3027
|
+
);
|
|
3028
|
+
} catch (error) {
|
|
3029
|
+
const message = String(error?.message || error || 'Unknown retest execution error');
|
|
3030
|
+
this.learningWorkbench.lastError = message;
|
|
3031
|
+
this._setLearningWorkbenchStatus(`Retest execution failed: ${message}`, true);
|
|
3032
|
+
} finally {
|
|
3033
|
+
this.learningWorkbench.loading = false;
|
|
3034
|
+
this._renderLearningWorkbenchState();
|
|
1600
3035
|
}
|
|
1601
|
-
return document.getElementById('learning-mode')?.value || 'domain';
|
|
1602
3036
|
},
|
|
1603
3037
|
|
|
1604
|
-
|
|
1605
|
-
if (this.
|
|
1606
|
-
return
|
|
3038
|
+
runLearningWorkbenchHistoryRetest: async function(recordId) {
|
|
3039
|
+
if (this.learningWorkbench.loading) {
|
|
3040
|
+
return;
|
|
3041
|
+
}
|
|
3042
|
+
const normalizedRecordId = String(recordId || '').trim();
|
|
3043
|
+
if (!normalizedRecordId) {
|
|
3044
|
+
this._setLearningWorkbenchStatus('Invalid history record id for replay.', true);
|
|
3045
|
+
return;
|
|
3046
|
+
}
|
|
3047
|
+
const userId = this._normalizeLearningWorkbenchUserId(this.learningWorkbench.userId);
|
|
3048
|
+
const records = Array.isArray(this.learningWorkbench.sessionHistory?.records)
|
|
3049
|
+
? this.learningWorkbench.sessionHistory.records
|
|
3050
|
+
: [];
|
|
3051
|
+
const targetRecord = records.find((item) => String(item?.id || '').trim() === normalizedRecordId) || null;
|
|
3052
|
+
if (!targetRecord) {
|
|
3053
|
+
this._setLearningWorkbenchStatus('Target history record was not found in current page.', true);
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
3056
|
+
const focusAtomIds = Array.from(
|
|
3057
|
+
new Set(
|
|
3058
|
+
(Array.isArray(targetRecord.focusAtomIds) ? targetRecord.focusAtomIds : [])
|
|
3059
|
+
.map((atomId) => String(atomId || '').trim())
|
|
3060
|
+
.filter((atomId) => atomId.length > 0)
|
|
3061
|
+
)
|
|
3062
|
+
);
|
|
3063
|
+
if (focusAtomIds.length === 0) {
|
|
3064
|
+
this._setLearningWorkbenchStatus('Selected history record has no focus atoms for replay.', true);
|
|
3065
|
+
return;
|
|
1607
3066
|
}
|
|
1608
|
-
return document.getElementById('strategy')?.value || 'foundational';
|
|
1609
|
-
},
|
|
1610
3067
|
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
3068
|
+
this.learningWorkbench.loading = true;
|
|
3069
|
+
this._setLearningWorkbenchStatus(`Replaying retest from history record ${normalizedRecordId}...`);
|
|
3070
|
+
this._renderLearningWorkbenchState();
|
|
3071
|
+
try {
|
|
3072
|
+
const sessionPlan = await this._requestLearningApi('/api/knowledge/session/plan', {
|
|
3073
|
+
userId,
|
|
3074
|
+
focusAtomIds,
|
|
3075
|
+
maxActions: Math.min(14, Math.max(4, focusAtomIds.length * 2)),
|
|
3076
|
+
includeDivergence: false,
|
|
3077
|
+
includeRetrain: true,
|
|
3078
|
+
});
|
|
3079
|
+
if (!sessionPlan || !Array.isArray(sessionPlan.actions) || sessionPlan.actions.length === 0) {
|
|
3080
|
+
throw new Error('Replay retest failed because no runnable actions were generated.');
|
|
3081
|
+
}
|
|
3082
|
+
const actionLimit = Math.max(1, Math.min(8, focusAtomIds.length, sessionPlan.actions.length));
|
|
3083
|
+
const selectedActions = sessionPlan.actions.slice(0, actionLimit);
|
|
3084
|
+
const answersByActionId = this._collectOptionalAnswersForSessionActions(selectedActions);
|
|
3085
|
+
const answerCount = Object.keys(answersByActionId).length;
|
|
3086
|
+
const autoAnalyzeAnswer = answerCount > 0;
|
|
3087
|
+
const result = await this._requestLearningApi('/api/knowledge/session/execute', {
|
|
3088
|
+
userId,
|
|
3089
|
+
executionKind: 'retest',
|
|
3090
|
+
sessionPlan,
|
|
3091
|
+
actionLimit,
|
|
3092
|
+
answersByActionId,
|
|
3093
|
+
autoAnalyzeAnswer,
|
|
3094
|
+
autoUpdateMasteryFromAnswer: autoAnalyzeAnswer,
|
|
3095
|
+
persistMemory: true,
|
|
3096
|
+
memoryLayer: 'session',
|
|
3097
|
+
includeRetestPlan: false,
|
|
3098
|
+
});
|
|
3099
|
+
this.learningWorkbench.sessionExecution = {
|
|
3100
|
+
...result,
|
|
3101
|
+
receivedAt: new Date().toISOString(),
|
|
3102
|
+
};
|
|
3103
|
+
this._appendLearningWorkbenchSessionRecord(result?.record || null);
|
|
3104
|
+
const summary = result?.summary || {};
|
|
3105
|
+
this._setLearningWorkbenchStatus(
|
|
3106
|
+
`History replay finished: executed ${Number(summary.executedCount || 0)}/${Number(summary.attemptedActions || 0)}, mastery delta ${Number(summary.averageMasteryDelta || 0).toFixed(3)}.`
|
|
3107
|
+
);
|
|
3108
|
+
} catch (error) {
|
|
3109
|
+
const message = String(error?.message || error || 'Unknown history replay error');
|
|
3110
|
+
this.learningWorkbench.lastError = message;
|
|
3111
|
+
this._setLearningWorkbenchStatus(`History replay failed: ${message}`, true);
|
|
3112
|
+
} finally {
|
|
3113
|
+
this.learningWorkbench.loading = false;
|
|
3114
|
+
this._renderLearningWorkbenchState();
|
|
1615
3115
|
}
|
|
1616
|
-
return document.getElementById('layout-style')?.value || 'orbital';
|
|
1617
3116
|
},
|
|
1618
3117
|
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
3118
|
+
_renderLearningWorkbenchState: function() {
|
|
3119
|
+
const userIdInput = document.getElementById('learning-user-id');
|
|
3120
|
+
if (userIdInput && userIdInput.value !== this.learningWorkbench.userId) {
|
|
3121
|
+
userIdInput.value = this.learningWorkbench.userId;
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
const qualityEl = document.getElementById('learning-quality-summary');
|
|
3125
|
+
const qualityGatesEl = document.getElementById('learning-quality-gates');
|
|
3126
|
+
const setQualityBaselineBtn = document.getElementById('btn-set-quality-baseline');
|
|
3127
|
+
const evaluateQualityGatesBtn = document.getElementById('btn-evaluate-quality-gates');
|
|
3128
|
+
const misconceptionEl = document.getElementById('learning-misconception-list');
|
|
3129
|
+
const actionsEl = document.getElementById('learning-session-actions');
|
|
3130
|
+
const runtimeEl = document.getElementById('learning-runtime-summary');
|
|
3131
|
+
const updatedEl = document.getElementById('learning-workbench-updated-at');
|
|
3132
|
+
const tutorFeedbackEl = document.getElementById('learning-tutor-feedback');
|
|
3133
|
+
const sessionExecutionEl = document.getElementById('learning-session-execution');
|
|
3134
|
+
const sessionHistoryEl = document.getElementById('learning-session-history');
|
|
3135
|
+
const sessionHistorySummaryEl = document.getElementById('learning-session-history-summary');
|
|
3136
|
+
const sessionHistoryFilterEl = document.getElementById('learning-history-kind-filter');
|
|
3137
|
+
const sessionHistoryFromDateEl = document.getElementById('learning-history-from-date');
|
|
3138
|
+
const sessionHistoryToDateEl = document.getElementById('learning-history-to-date');
|
|
3139
|
+
const sessionHistoryApplyRangeBtn = document.getElementById('btn-learning-history-apply-range');
|
|
3140
|
+
const sessionHistoryClearRangeBtn = document.getElementById('btn-learning-history-clear-range');
|
|
3141
|
+
const sessionHistoryPrevBtn = document.getElementById('btn-learning-history-prev');
|
|
3142
|
+
const sessionHistoryNextBtn = document.getElementById('btn-learning-history-next');
|
|
3143
|
+
const sessionHistoryPageEl = document.getElementById('learning-history-page');
|
|
3144
|
+
|
|
3145
|
+
if (updatedEl) {
|
|
3146
|
+
updatedEl.textContent = this.learningWorkbench.lastUpdatedAt
|
|
3147
|
+
? `Last updated: ${new Date(this.learningWorkbench.lastUpdatedAt).toLocaleString()}`
|
|
3148
|
+
: 'Last updated: -';
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
if (qualityEl) {
|
|
3152
|
+
const snapshot = this.learningWorkbench.qualitySnapshot?.snapshot || null;
|
|
3153
|
+
const baseline = this.learningWorkbench.qualityBaselineSnapshot || null;
|
|
3154
|
+
if (setQualityBaselineBtn) {
|
|
3155
|
+
setQualityBaselineBtn.disabled = this.learningWorkbench.loading === true || !snapshot;
|
|
3156
|
+
}
|
|
3157
|
+
if (evaluateQualityGatesBtn) {
|
|
3158
|
+
evaluateQualityGatesBtn.disabled = this.learningWorkbench.loading === true || !snapshot || !baseline;
|
|
3159
|
+
}
|
|
3160
|
+
if (!snapshot) {
|
|
3161
|
+
qualityEl.innerHTML = '<li class="muted">No quality snapshot yet.</li>';
|
|
3162
|
+
} else {
|
|
3163
|
+
qualityEl.innerHTML = [
|
|
3164
|
+
`<li>Retest pass rate: <strong>${Number(snapshot.retestPassRatePct || 0).toFixed(2)}%</strong></li>`,
|
|
3165
|
+
`<li>Misconception recurrence: <strong>${Number(snapshot.misconceptionRecurrenceRatePct || 0).toFixed(2)}%</strong></li>`,
|
|
3166
|
+
`<li>Evidence-backed suggestions: <strong>${Number(snapshot.evidenceBackedSuggestionRatioPct || 0).toFixed(2)}%</strong></li>`,
|
|
3167
|
+
`<li>Path gain vs random: <strong>${Number(snapshot.averagePathMasteryGainPct || 0).toFixed(2)}% / ${Number(snapshot.randomPathMasteryGainPct || 0).toFixed(2)}%</strong></li>`,
|
|
3168
|
+
`<li>History window: <strong>${Math.max(1, Math.floor(Number(snapshot.historyWindowDays || 14)))}d, records ${Math.max(0, Math.floor(Number(snapshot.historyWindowRecords || 0)))}</strong></li>`,
|
|
3169
|
+
`<li>History mastery delta avg: <strong>${Number(snapshot.historyWindowAverageMasteryDelta || 0).toFixed(3)}</strong></li>`,
|
|
3170
|
+
`<li>History retest positive delta rate: <strong>${Number(snapshot.historyWindowRetestPositiveDeltaRatePct || 0).toFixed(2)}%</strong></li>`,
|
|
3171
|
+
`<li>Query p95: <strong>${Number(snapshot.queryP95Ms || 0).toFixed(2)} ms</strong></li>`,
|
|
3172
|
+
`<li>Baseline status: <strong>${baseline ? 'configured' : 'missing'}</strong></li>`,
|
|
3173
|
+
].join('');
|
|
3174
|
+
}
|
|
1622
3175
|
}
|
|
1623
|
-
return document.getElementById('set-retain-history')?.checked ?? true;
|
|
1624
|
-
},
|
|
1625
3176
|
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
3177
|
+
if (qualityGatesEl) {
|
|
3178
|
+
const evaluation = this.learningWorkbench.qualityGateEvaluation || null;
|
|
3179
|
+
const baseline = this.learningWorkbench.qualityBaselineSnapshot || null;
|
|
3180
|
+
if (!baseline) {
|
|
3181
|
+
qualityGatesEl.textContent = 'No quality baseline yet. Use "Set Baseline" after refreshing quality snapshot.';
|
|
3182
|
+
} else if (!evaluation) {
|
|
3183
|
+
qualityGatesEl.textContent = 'No quality gate evaluation yet.';
|
|
3184
|
+
} else {
|
|
3185
|
+
const gateLines = Array.isArray(evaluation.gates)
|
|
3186
|
+
? evaluation.gates.map((gate) => {
|
|
3187
|
+
const status = gate.passed === true ? 'PASS' : 'FAIL';
|
|
3188
|
+
const comparator = String(gate.comparator || '>=');
|
|
3189
|
+
const observedValue = Number(gate.observedValue || 0).toFixed(gate.unit === 'ms' ? 2 : 4);
|
|
3190
|
+
const threshold = Number(gate.threshold || 0).toFixed(gate.unit === 'ms' ? 2 : 4);
|
|
3191
|
+
return `- [${status}] ${gate.gateId}: ${observedValue} ${comparator} ${threshold} ${gate.unit}`;
|
|
3192
|
+
})
|
|
3193
|
+
: [];
|
|
3194
|
+
const deltas = evaluation.deltas || {};
|
|
3195
|
+
qualityGatesEl.textContent = [
|
|
3196
|
+
`Evaluated at: ${evaluation.evaluatedAt || '-'}`,
|
|
3197
|
+
`Overall: ${evaluation.overallPassed === true ? 'PASS' : 'FAIL'}`,
|
|
3198
|
+
`Delta retest pass-rate uplift: ${Number(deltas.retestPassRateUpliftPct || 0).toFixed(4)}`,
|
|
3199
|
+
`Delta misconception reduction: ${Number(deltas.misconceptionRecurrenceReductionPct || 0).toFixed(4)}`,
|
|
3200
|
+
`Delta path effectiveness lift: ${Number(deltas.pathEffectivenessLiftPct || 0).toFixed(4)}`,
|
|
3201
|
+
`Delta history mastery uplift: ${Number(deltas.historyWindowAverageMasteryDeltaUplift || 0).toFixed(6)}`,
|
|
3202
|
+
'Gates:',
|
|
3203
|
+
...(gateLines.length > 0 ? gateLines : ['- n/a']),
|
|
3204
|
+
].join('\n');
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
if (misconceptionEl) {
|
|
3209
|
+
const items = this.learningWorkbench.misconceptions?.items || [];
|
|
3210
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
3211
|
+
misconceptionEl.innerHTML = '<li class="muted">No recurring misconceptions detected.</li>';
|
|
3212
|
+
} else {
|
|
3213
|
+
misconceptionEl.innerHTML = items.map((item) => {
|
|
3214
|
+
const tag = String(item.errorTag || 'unknown');
|
|
3215
|
+
const count = Number(item.count || 0);
|
|
3216
|
+
const severity = Number(item.severityScore || 0).toFixed(3);
|
|
3217
|
+
return `<li><span class="chip">${tag}</span> count=${count}, severity=${severity}</li>`;
|
|
3218
|
+
}).join('');
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
if (actionsEl) {
|
|
3223
|
+
const actions = this.learningWorkbench.sessionPlan?.actions || [];
|
|
3224
|
+
if (!Array.isArray(actions) || actions.length === 0) {
|
|
3225
|
+
actionsEl.innerHTML = '<li class="muted">No session actions yet.</li>';
|
|
3226
|
+
} else {
|
|
3227
|
+
actionsEl.innerHTML = actions.slice(0, 10).map((action) => {
|
|
3228
|
+
const source = String(action.source || 'unknown');
|
|
3229
|
+
const kind = String(action.kind || 'action');
|
|
3230
|
+
const atomId = String(action.atomId || '');
|
|
3231
|
+
const minutes = Number(action.estimatedMinutes || 0);
|
|
3232
|
+
const safeKind = this._escapeHtml(kind);
|
|
3233
|
+
const safeSource = this._escapeHtml(source);
|
|
3234
|
+
const safeAtomId = this._escapeHtml(atomId);
|
|
3235
|
+
const encodedKind = encodeURIComponent(kind);
|
|
3236
|
+
const encodedAtomId = encodeURIComponent(atomId);
|
|
3237
|
+
const encodedSource = encodeURIComponent(source);
|
|
3238
|
+
return `
|
|
3239
|
+
<li>
|
|
3240
|
+
<div><strong>${safeKind}</strong> <span class="chip">${safeSource}</span> <code>${safeAtomId}</code> (${minutes}m)</div>
|
|
3241
|
+
<button
|
|
3242
|
+
class="btn-small workbench-run-action"
|
|
3243
|
+
data-action-kind="${encodedKind}"
|
|
3244
|
+
data-atom-id="${encodedAtomId}"
|
|
3245
|
+
data-action-source="${encodedSource}"
|
|
3246
|
+
type="button"
|
|
3247
|
+
>
|
|
3248
|
+
Ask Tutor
|
|
3249
|
+
</button>
|
|
3250
|
+
</li>
|
|
3251
|
+
`;
|
|
3252
|
+
}).join('');
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
if (tutorFeedbackEl) {
|
|
3257
|
+
const tutorFeedback = this.learningWorkbench.tutorFeedback;
|
|
3258
|
+
if (!tutorFeedback || !tutorFeedback.result) {
|
|
3259
|
+
tutorFeedbackEl.textContent = 'No tutor feedback yet.';
|
|
3260
|
+
} else {
|
|
3261
|
+
const trace = tutorFeedback.result?.trace || {};
|
|
3262
|
+
const tutorResult = tutorFeedback.tutorResult || tutorFeedback.result?.tutor || {};
|
|
3263
|
+
const message = String(tutorResult.message || '').trim();
|
|
3264
|
+
const safeMessage = message.length > 0 ? message : 'Tutor returned an empty message.';
|
|
3265
|
+
const answerAnalysis = tutorFeedback.result?.answerAnalysis || null;
|
|
3266
|
+
const answerAnalysisMessage = String(answerAnalysis?.message || '').trim();
|
|
3267
|
+
const safeAnswerAnalysis = answerAnalysisMessage.length > 0
|
|
3268
|
+
? answerAnalysisMessage
|
|
3269
|
+
: 'No answer analysis was generated.';
|
|
3270
|
+
tutorFeedbackEl.textContent = [
|
|
3271
|
+
`Action: ${tutorFeedback.actionKind} (${trace.tutorActionKind || 'unknown'})`,
|
|
3272
|
+
`Atom: ${tutorFeedback.atomId}`,
|
|
3273
|
+
`Memory persisted: ${trace.persistedMemory === true ? 'yes' : 'no'}`,
|
|
3274
|
+
`Mastery updated: ${trace.updatedMastery === true ? 'yes' : 'no'} (${trace.masterySource || 'none'})`,
|
|
3275
|
+
`Outcome/Error: ${trace.effectiveOutcome || 'n/a'} / ${trace.effectiveErrorTag || 'n/a'}`,
|
|
3276
|
+
safeMessage,
|
|
3277
|
+
'',
|
|
3278
|
+
'Answer Analysis:',
|
|
3279
|
+
safeAnswerAnalysis,
|
|
3280
|
+
].join('\n');
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
if (sessionExecutionEl) {
|
|
3285
|
+
const execution = this.learningWorkbench.sessionExecution;
|
|
3286
|
+
if (!execution || !execution.summary) {
|
|
3287
|
+
sessionExecutionEl.textContent = 'No session execution yet.';
|
|
3288
|
+
} else {
|
|
3289
|
+
const summary = execution.summary || {};
|
|
3290
|
+
const masteryDelta = execution.masteryDelta || null;
|
|
3291
|
+
const retestPlan = execution.retestPlan || null;
|
|
3292
|
+
const topDeltaLines = Array.isArray(masteryDelta?.items)
|
|
3293
|
+
? masteryDelta.items.slice(0, 3).map((item) => {
|
|
3294
|
+
const before = Number(item.beforeMastery || 0).toFixed(3);
|
|
3295
|
+
const after = Number(item.afterMastery || 0).toFixed(3);
|
|
3296
|
+
const delta = Number(item.deltaMastery || 0);
|
|
3297
|
+
const signedDelta = `${delta >= 0 ? '+' : ''}${delta.toFixed(3)}`;
|
|
3298
|
+
const atomId = String(item.atomId || 'unknown_atom');
|
|
3299
|
+
return `- ${atomId}: ${before} -> ${after} (${signedDelta})`;
|
|
3300
|
+
})
|
|
3301
|
+
: [];
|
|
3302
|
+
const retestPreviewLines = Array.isArray(retestPlan?.actions)
|
|
3303
|
+
? retestPlan.actions.slice(0, 3).map((action) => {
|
|
3304
|
+
const atomId = String(action.atomId || 'unknown_atom');
|
|
3305
|
+
const kind = String(action.kind || 'action');
|
|
3306
|
+
const source = String(action.source || 'retrain_plan');
|
|
3307
|
+
return `- ${kind} @ ${atomId} (${source})`;
|
|
3308
|
+
})
|
|
3309
|
+
: [];
|
|
3310
|
+
sessionExecutionEl.textContent = [
|
|
3311
|
+
`Executed at: ${execution.executedAt || '-'}`,
|
|
3312
|
+
`Planned/Attempted/Executed: ${Number(summary.plannedActions || 0)}/${Number(summary.attemptedActions || 0)}/${Number(summary.executedCount || 0)}`,
|
|
3313
|
+
`Skipped/Failed: ${Number(summary.skippedCount || 0)}/${Number(summary.failedCount || 0)}`,
|
|
3314
|
+
`Mastery updates (inferred/explicit): ${Number(summary.updatedMasteryCount || 0)} (${Number(summary.inferredMasteryCount || 0)}/${Number(summary.explicitMasteryCount || 0)})`,
|
|
3315
|
+
`Answer analyzed: ${Number(summary.analyzedAnswerCount || 0)}, memory persisted: ${Number(summary.memoryPersistedCount || 0)}`,
|
|
3316
|
+
`Avg tutor confidence: ${Number(summary.averageTutorConfidence || 0).toFixed(3)}`,
|
|
3317
|
+
`Mastery avg before/after/delta: ${Number(summary.averageMasteryBefore || 0).toFixed(3)} / ${Number(summary.averageMasteryAfter || 0).toFixed(3)} / ${Number(summary.averageMasteryDelta || 0).toFixed(3)}`,
|
|
3318
|
+
`Mastery movement improved/regressed/flat: ${Number(summary.improvedAtomCount || 0)}/${Number(summary.regressedAtomCount || 0)}/${Number(summary.unchangedAtomCount || 0)}`,
|
|
3319
|
+
`Stopped early: ${summary.stoppedEarly === true ? 'yes' : 'no'}`,
|
|
3320
|
+
...(masteryDelta
|
|
3321
|
+
? [
|
|
3322
|
+
`Compared atoms: ${Number(masteryDelta.comparedAtoms || 0)}, avg delta: ${Number(masteryDelta.averageDelta || 0).toFixed(3)}`,
|
|
3323
|
+
'Top mastery delta atoms:',
|
|
3324
|
+
...(topDeltaLines.length > 0 ? topDeltaLines : ['- n/a']),
|
|
3325
|
+
]
|
|
3326
|
+
: []),
|
|
3327
|
+
...(retestPlan
|
|
3328
|
+
? [
|
|
3329
|
+
`Immediate retest actions: ${Number(retestPlan.summary?.totalActions || 0)}`,
|
|
3330
|
+
'Retest preview:',
|
|
3331
|
+
...(retestPreviewLines.length > 0 ? retestPreviewLines : ['- n/a']),
|
|
3332
|
+
]
|
|
3333
|
+
: []),
|
|
3334
|
+
].join('\n');
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
if (sessionHistoryEl) {
|
|
3339
|
+
const records = this.learningWorkbench.sessionHistory?.records || [];
|
|
3340
|
+
const summary = this.learningWorkbench.sessionHistory?.summary || null;
|
|
3341
|
+
const page = this.learningWorkbench.sessionHistory?.page || null;
|
|
3342
|
+
const historyQuery = this._getLearningWorkbenchSessionHistoryQuery();
|
|
3343
|
+
if (sessionHistoryFilterEl) {
|
|
3344
|
+
sessionHistoryFilterEl.value = historyQuery.executionKind;
|
|
3345
|
+
}
|
|
3346
|
+
if (sessionHistoryFromDateEl) {
|
|
3347
|
+
sessionHistoryFromDateEl.value = historyQuery.fromDate;
|
|
3348
|
+
}
|
|
3349
|
+
if (sessionHistoryToDateEl) {
|
|
3350
|
+
sessionHistoryToDateEl.value = historyQuery.toDate;
|
|
3351
|
+
}
|
|
3352
|
+
if (sessionHistoryApplyRangeBtn) {
|
|
3353
|
+
sessionHistoryApplyRangeBtn.disabled = this.learningWorkbench.loading === true;
|
|
3354
|
+
}
|
|
3355
|
+
if (sessionHistoryClearRangeBtn) {
|
|
3356
|
+
const hasDateRange = historyQuery.fromDate.length > 0 || historyQuery.toDate.length > 0;
|
|
3357
|
+
sessionHistoryClearRangeBtn.disabled = this.learningWorkbench.loading === true || !hasDateRange;
|
|
3358
|
+
}
|
|
3359
|
+
if (sessionHistoryPrevBtn) {
|
|
3360
|
+
sessionHistoryPrevBtn.disabled = this.learningWorkbench.loading === true || historyQuery.offset <= 0;
|
|
3361
|
+
}
|
|
3362
|
+
if (sessionHistoryNextBtn) {
|
|
3363
|
+
const canGoNext = page ? page.hasMore === true : false;
|
|
3364
|
+
sessionHistoryNextBtn.disabled = this.learningWorkbench.loading === true || !canGoNext;
|
|
3365
|
+
}
|
|
3366
|
+
if (sessionHistoryPageEl) {
|
|
3367
|
+
const totalFilteredRecords = Math.max(0, Number(page?.totalFilteredRecords || 0));
|
|
3368
|
+
const totalPages = Math.max(1, Math.ceil(totalFilteredRecords / Math.max(1, historyQuery.limit)));
|
|
3369
|
+
const pageIndex = Math.floor(historyQuery.offset / Math.max(1, historyQuery.limit)) + 1;
|
|
3370
|
+
sessionHistoryPageEl.textContent = `Page ${Math.min(pageIndex, totalPages)}/${totalPages}`;
|
|
3371
|
+
}
|
|
3372
|
+
if (sessionHistorySummaryEl) {
|
|
3373
|
+
const totalRecords = Number(summary?.totalRecords || 0);
|
|
3374
|
+
const totalExecuted = Number(summary?.totalExecutedActions || 0);
|
|
3375
|
+
const totalUpdated = Number(summary?.totalUpdatedMasteryCount || 0);
|
|
3376
|
+
const avgDelta = Number(summary?.averageMasteryDelta || 0).toFixed(3);
|
|
3377
|
+
const avgConfidence = Number(summary?.averageTutorConfidence || 0).toFixed(3);
|
|
3378
|
+
const rangeSummary = historyQuery.fromDate || historyQuery.toDate
|
|
3379
|
+
? `, range=${historyQuery.fromDate || '-'}..${historyQuery.toDate || '-'}`
|
|
3380
|
+
: '';
|
|
3381
|
+
sessionHistorySummaryEl.textContent = `History summary: records=${totalRecords}, executed=${totalExecuted}, updated=${totalUpdated}, avgDelta=${avgDelta}, avgConfidence=${avgConfidence}${rangeSummary}`;
|
|
3382
|
+
}
|
|
3383
|
+
if (!Array.isArray(records) || records.length === 0) {
|
|
3384
|
+
sessionHistoryEl.innerHTML = '<li class="muted">No session history yet.</li>';
|
|
3385
|
+
} else {
|
|
3386
|
+
sessionHistoryEl.innerHTML = records.slice(0, Math.max(1, historyQuery.limit)).map((record) => {
|
|
3387
|
+
const executedAt = new Date(record.executedAt || Date.now()).toLocaleString();
|
|
3388
|
+
const kind = this._escapeHtml(String(record.executionKind || 'session'));
|
|
3389
|
+
const executedCount = Number(record.executedCount || 0);
|
|
3390
|
+
const attempted = Number(record.attemptedActions || 0);
|
|
3391
|
+
const delta = Number(record.averageMasteryDelta || 0);
|
|
3392
|
+
const signedDelta = `${delta >= 0 ? '+' : ''}${delta.toFixed(3)}`;
|
|
3393
|
+
const recordId = String(record.id || '').trim();
|
|
3394
|
+
const encodedRecordId = encodeURIComponent(recordId);
|
|
3395
|
+
const focusAtomIds = Array.isArray(record.focusAtomIds) ? record.focusAtomIds : [];
|
|
3396
|
+
const focusAtomCount = focusAtomIds.length;
|
|
3397
|
+
const focusPreview = focusAtomIds
|
|
3398
|
+
.slice(0, 3)
|
|
3399
|
+
.map((atomId) => this._escapeHtml(String(atomId || '')))
|
|
3400
|
+
.filter((atomId) => atomId.length > 0)
|
|
3401
|
+
.join(', ');
|
|
3402
|
+
const replayDisabledAttr = focusAtomCount > 0 ? '' : ' disabled';
|
|
3403
|
+
return `
|
|
3404
|
+
<li>
|
|
3405
|
+
<div><span class="chip">${kind}</span> ${executedAt} - ${executedCount}/${attempted} - mastery delta ${signedDelta}</div>
|
|
3406
|
+
<div class="muted">Focus atoms: ${focusAtomCount}${focusPreview ? ` (${focusPreview})` : ''}</div>
|
|
3407
|
+
<button class="btn-small workbench-run-history-retest" data-history-record-id="${encodedRecordId}" type="button"${replayDisabledAttr}>Replay Retest</button>
|
|
3408
|
+
</li>
|
|
3409
|
+
`;
|
|
3410
|
+
}).join('');
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
if (runtimeEl) {
|
|
3415
|
+
const runtimeState = this.learningWorkbench.runtimeState?.state || null;
|
|
3416
|
+
if (!runtimeState) {
|
|
3417
|
+
runtimeEl.textContent = 'Runtime: unavailable';
|
|
3418
|
+
} else {
|
|
3419
|
+
const sessionTelemetry = runtimeState.sessionActionTelemetry || null;
|
|
3420
|
+
const historyCount = Number(runtimeState.sessionExecutionHistoryRecords || 0);
|
|
3421
|
+
const sessionSummary = sessionTelemetry
|
|
3422
|
+
? `, sessionActions=${Number(sessionTelemetry.executionCount || 0)}, inferred=${Number(sessionTelemetry.inferredMasteryUpdateCount || 0)}, explicit=${Number(sessionTelemetry.explicitMasteryUpdateCount || 0)}, history=${historyCount}`
|
|
3423
|
+
: `, history=${historyCount}`;
|
|
3424
|
+
runtimeEl.textContent = `Runtime: docs=${runtimeState.documents}, atoms=${runtimeState.activeAtoms}, relations=${runtimeState.activeRelationEdges}, ingestP95=${Number(runtimeState.ingestTelemetry?.ingestP95Ms || 0).toFixed(2)}ms${sessionSummary}`;
|
|
3425
|
+
}
|
|
1641
3426
|
}
|
|
1642
3427
|
},
|
|
1643
3428
|
|
|
@@ -1708,6 +3493,7 @@ window.pathApp = {
|
|
|
1708
3493
|
const pathContainer = document.getElementById('path-container');
|
|
1709
3494
|
const graphWrapper = document.getElementById('graph-wrapper');
|
|
1710
3495
|
const sidebar = document.getElementById('learning-history-sidebar');
|
|
3496
|
+
const workbenchSidebar = document.getElementById('learning-workbench-sidebar');
|
|
1711
3497
|
|
|
1712
3498
|
if (pathContainer) pathContainer.style.display = 'none';
|
|
1713
3499
|
if (graphWrapper) graphWrapper.style.display = 'block';
|
|
@@ -1715,6 +3501,10 @@ window.pathApp = {
|
|
|
1715
3501
|
sidebar.style.transform = 'translateX(100%)';
|
|
1716
3502
|
sidebar.style.display = 'none';
|
|
1717
3503
|
}
|
|
3504
|
+
if (workbenchSidebar) {
|
|
3505
|
+
workbenchSidebar.style.transform = 'translateX(-100%)';
|
|
3506
|
+
workbenchSidebar.style.display = 'none';
|
|
3507
|
+
}
|
|
1718
3508
|
const multiWindowOptions = this._resolveMultiWindowOptions();
|
|
1719
3509
|
|
|
1720
3510
|
// Single-window toggle: hide Godot, show Tauri.
|
|
@@ -2237,6 +4027,8 @@ window.pathApp = {
|
|
|
2237
4027
|
const tauriMode = this._isTauriMode();
|
|
2238
4028
|
const toolbar = document.getElementById('path-toolbar');
|
|
2239
4029
|
|
|
4030
|
+
this._restoreLearningWorkbenchPreferences();
|
|
4031
|
+
|
|
2240
4032
|
if (tauriMode && toolbar) {
|
|
2241
4033
|
// In Tauri, controls are migrated to Godot. Keep canvas view only.
|
|
2242
4034
|
toolbar.style.display = 'none';
|
|
@@ -2266,6 +4058,24 @@ window.pathApp = {
|
|
|
2266
4058
|
const markBtn = document.getElementById('btn-mark-complete');
|
|
2267
4059
|
const historyBtn = document.getElementById('btn-toggle-history');
|
|
2268
4060
|
const closeHistoryBtn = document.getElementById('btn-close-history');
|
|
4061
|
+
const workbenchToggleBtn = document.getElementById('btn-toggle-learning-workbench');
|
|
4062
|
+
const workbenchCloseBtn = document.getElementById('btn-close-learning-workbench');
|
|
4063
|
+
const workbenchRefreshBtn = document.getElementById('btn-refresh-learning-workbench');
|
|
4064
|
+
const workbenchSetQualityBaselineBtn = document.getElementById('btn-set-quality-baseline');
|
|
4065
|
+
const workbenchEvaluateQualityGatesBtn = document.getElementById('btn-evaluate-quality-gates');
|
|
4066
|
+
const workbenchIngestBtn = document.getElementById('btn-ingest-focus-node');
|
|
4067
|
+
const workbenchRunSessionBtn = document.getElementById('btn-run-learning-session');
|
|
4068
|
+
const workbenchRunRetestBtn = document.getElementById('btn-run-retest-session');
|
|
4069
|
+
const workbenchUserIdInput = document.getElementById('learning-user-id');
|
|
4070
|
+
const workbenchActionsList = document.getElementById('learning-session-actions');
|
|
4071
|
+
const workbenchHistoryList = document.getElementById('learning-session-history');
|
|
4072
|
+
const workbenchHistoryKindFilter = document.getElementById('learning-history-kind-filter');
|
|
4073
|
+
const workbenchHistoryFromDate = document.getElementById('learning-history-from-date');
|
|
4074
|
+
const workbenchHistoryToDate = document.getElementById('learning-history-to-date');
|
|
4075
|
+
const workbenchHistoryApplyRangeBtn = document.getElementById('btn-learning-history-apply-range');
|
|
4076
|
+
const workbenchHistoryClearRangeBtn = document.getElementById('btn-learning-history-clear-range');
|
|
4077
|
+
const workbenchHistoryPrevBtn = document.getElementById('btn-learning-history-prev');
|
|
4078
|
+
const workbenchHistoryNextBtn = document.getElementById('btn-learning-history-next');
|
|
2269
4079
|
|
|
2270
4080
|
if (!tauriMode) {
|
|
2271
4081
|
if (learningModeEl) {
|
|
@@ -2286,6 +4096,11 @@ window.pathApp = {
|
|
|
2286
4096
|
if (layoutEl) layoutEl.addEventListener('change', () => this.triggerUpdate());
|
|
2287
4097
|
if (markBtn) markBtn.addEventListener('click', () => this.markComplete());
|
|
2288
4098
|
if (historyBtn) historyBtn.addEventListener('click', () => this._toggleHistorySidebar());
|
|
4099
|
+
if (workbenchToggleBtn) {
|
|
4100
|
+
workbenchToggleBtn.addEventListener('click', () => {
|
|
4101
|
+
this._toggleLearningWorkbenchSidebar();
|
|
4102
|
+
});
|
|
4103
|
+
}
|
|
2289
4104
|
}
|
|
2290
4105
|
|
|
2291
4106
|
if (closeHistoryBtn) {
|
|
@@ -2299,6 +4114,150 @@ window.pathApp = {
|
|
|
2299
4114
|
});
|
|
2300
4115
|
}
|
|
2301
4116
|
|
|
4117
|
+
if (workbenchCloseBtn) {
|
|
4118
|
+
workbenchCloseBtn.addEventListener('click', () => this._closeLearningWorkbenchSidebar());
|
|
4119
|
+
}
|
|
4120
|
+
|
|
4121
|
+
if (workbenchRefreshBtn) {
|
|
4122
|
+
workbenchRefreshBtn.addEventListener('click', () => {
|
|
4123
|
+
void this.refreshLearningWorkbench({ force: true });
|
|
4124
|
+
});
|
|
4125
|
+
}
|
|
4126
|
+
|
|
4127
|
+
if (workbenchSetQualityBaselineBtn) {
|
|
4128
|
+
workbenchSetQualityBaselineBtn.addEventListener('click', () => {
|
|
4129
|
+
void this.setLearningWorkbenchQualityBaselineFromCurrent();
|
|
4130
|
+
});
|
|
4131
|
+
}
|
|
4132
|
+
|
|
4133
|
+
if (workbenchEvaluateQualityGatesBtn) {
|
|
4134
|
+
workbenchEvaluateQualityGatesBtn.addEventListener('click', () => {
|
|
4135
|
+
void this.evaluateLearningWorkbenchQualityGates();
|
|
4136
|
+
});
|
|
4137
|
+
}
|
|
4138
|
+
|
|
4139
|
+
if (workbenchIngestBtn) {
|
|
4140
|
+
workbenchIngestBtn.addEventListener('click', () => {
|
|
4141
|
+
void this.ingestFocusNodeForLearningWorkbench();
|
|
4142
|
+
});
|
|
4143
|
+
}
|
|
4144
|
+
|
|
4145
|
+
if (workbenchRunSessionBtn) {
|
|
4146
|
+
workbenchRunSessionBtn.addEventListener('click', () => {
|
|
4147
|
+
void this.runLearningWorkbenchSession();
|
|
4148
|
+
});
|
|
4149
|
+
}
|
|
4150
|
+
|
|
4151
|
+
if (workbenchRunRetestBtn) {
|
|
4152
|
+
workbenchRunRetestBtn.addEventListener('click', () => {
|
|
4153
|
+
void this.runLearningWorkbenchRetestSession();
|
|
4154
|
+
});
|
|
4155
|
+
}
|
|
4156
|
+
|
|
4157
|
+
if (workbenchUserIdInput) {
|
|
4158
|
+
workbenchUserIdInput.value = this.learningWorkbench.userId || 'path_user_default';
|
|
4159
|
+
workbenchUserIdInput.addEventListener('change', (event) => {
|
|
4160
|
+
const candidate = typeof event?.target?.value === 'string'
|
|
4161
|
+
? event.target.value
|
|
4162
|
+
: '';
|
|
4163
|
+
this.learningWorkbench.userId = this._normalizeLearningWorkbenchUserId(candidate);
|
|
4164
|
+
const historyQuery = this._getLearningWorkbenchSessionHistoryQuery();
|
|
4165
|
+
this.learningWorkbench.sessionHistoryQuery = {
|
|
4166
|
+
...historyQuery,
|
|
4167
|
+
offset: 0,
|
|
4168
|
+
};
|
|
4169
|
+
this.learningWorkbench.qualityBaselineSnapshot = this._loadLearningQualityBaseline(this.learningWorkbench.userId);
|
|
4170
|
+
this.learningWorkbench.qualityGateEvaluation = null;
|
|
4171
|
+
workbenchUserIdInput.value = this.learningWorkbench.userId;
|
|
4172
|
+
this._persistLearningWorkbenchPreferences();
|
|
4173
|
+
this._renderLearningWorkbenchState();
|
|
4174
|
+
void this._syncLearningQualityBaselineFromServer(this.learningWorkbench.userId)
|
|
4175
|
+
.then(() => {
|
|
4176
|
+
this._renderLearningWorkbenchState();
|
|
4177
|
+
});
|
|
4178
|
+
});
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4181
|
+
if (workbenchHistoryKindFilter) {
|
|
4182
|
+
workbenchHistoryKindFilter.value = this._getLearningWorkbenchSessionHistoryQuery().executionKind;
|
|
4183
|
+
workbenchHistoryKindFilter.addEventListener('change', (event) => {
|
|
4184
|
+
const nextValue = typeof event?.target?.value === 'string'
|
|
4185
|
+
? event.target.value
|
|
4186
|
+
: 'all';
|
|
4187
|
+
this._setLearningWorkbenchHistoryExecutionKind(nextValue, { refresh: true });
|
|
4188
|
+
});
|
|
4189
|
+
}
|
|
4190
|
+
|
|
4191
|
+
if (workbenchHistoryFromDate) {
|
|
4192
|
+
workbenchHistoryFromDate.value = this._getLearningWorkbenchSessionHistoryQuery().fromDate;
|
|
4193
|
+
}
|
|
4194
|
+
|
|
4195
|
+
if (workbenchHistoryToDate) {
|
|
4196
|
+
workbenchHistoryToDate.value = this._getLearningWorkbenchSessionHistoryQuery().toDate;
|
|
4197
|
+
}
|
|
4198
|
+
|
|
4199
|
+
if (workbenchHistoryApplyRangeBtn) {
|
|
4200
|
+
workbenchHistoryApplyRangeBtn.addEventListener('click', () => {
|
|
4201
|
+
const fromDate = workbenchHistoryFromDate && typeof workbenchHistoryFromDate.value === 'string'
|
|
4202
|
+
? workbenchHistoryFromDate.value
|
|
4203
|
+
: '';
|
|
4204
|
+
const toDate = workbenchHistoryToDate && typeof workbenchHistoryToDate.value === 'string'
|
|
4205
|
+
? workbenchHistoryToDate.value
|
|
4206
|
+
: '';
|
|
4207
|
+
this._applyLearningWorkbenchHistoryDateRange(fromDate, toDate, { refresh: true });
|
|
4208
|
+
});
|
|
4209
|
+
}
|
|
4210
|
+
|
|
4211
|
+
if (workbenchHistoryClearRangeBtn) {
|
|
4212
|
+
workbenchHistoryClearRangeBtn.addEventListener('click', () => {
|
|
4213
|
+
this._applyLearningWorkbenchHistoryDateRange('', '', { refresh: true });
|
|
4214
|
+
});
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4217
|
+
if (workbenchHistoryPrevBtn) {
|
|
4218
|
+
workbenchHistoryPrevBtn.addEventListener('click', () => {
|
|
4219
|
+
this._changeLearningWorkbenchHistoryPage(-1);
|
|
4220
|
+
});
|
|
4221
|
+
}
|
|
4222
|
+
|
|
4223
|
+
if (workbenchHistoryNextBtn) {
|
|
4224
|
+
workbenchHistoryNextBtn.addEventListener('click', () => {
|
|
4225
|
+
this._changeLearningWorkbenchHistoryPage(1);
|
|
4226
|
+
});
|
|
4227
|
+
}
|
|
4228
|
+
|
|
4229
|
+
if (workbenchActionsList) {
|
|
4230
|
+
workbenchActionsList.addEventListener('click', (event) => {
|
|
4231
|
+
const runBtn = event.target && typeof event.target.closest === 'function'
|
|
4232
|
+
? event.target.closest('.workbench-run-action')
|
|
4233
|
+
: null;
|
|
4234
|
+
if (!runBtn) {
|
|
4235
|
+
return;
|
|
4236
|
+
}
|
|
4237
|
+
const actionKind = decodeURIComponent(runBtn.getAttribute('data-action-kind') || '');
|
|
4238
|
+
const atomId = decodeURIComponent(runBtn.getAttribute('data-atom-id') || '');
|
|
4239
|
+
const source = decodeURIComponent(runBtn.getAttribute('data-action-source') || '');
|
|
4240
|
+
void this.executeLearningWorkbenchAction({
|
|
4241
|
+
actionKind,
|
|
4242
|
+
atomId,
|
|
4243
|
+
source,
|
|
4244
|
+
});
|
|
4245
|
+
});
|
|
4246
|
+
}
|
|
4247
|
+
|
|
4248
|
+
if (workbenchHistoryList) {
|
|
4249
|
+
workbenchHistoryList.addEventListener('click', (event) => {
|
|
4250
|
+
const replayBtn = event.target && typeof event.target.closest === 'function'
|
|
4251
|
+
? event.target.closest('.workbench-run-history-retest')
|
|
4252
|
+
: null;
|
|
4253
|
+
if (!replayBtn) {
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4256
|
+
const recordId = decodeURIComponent(replayBtn.getAttribute('data-history-record-id') || '');
|
|
4257
|
+
void this.runLearningWorkbenchHistoryRetest(recordId);
|
|
4258
|
+
});
|
|
4259
|
+
}
|
|
4260
|
+
|
|
2302
4261
|
// Add Target Display UI if missing
|
|
2303
4262
|
if (!tauriMode && !document.getElementById('target-display') && toolbar && learningModeEl) {
|
|
2304
4263
|
const targetDiv = document.createElement('div');
|
|
@@ -2334,6 +4293,7 @@ window.pathApp = {
|
|
|
2334
4293
|
|
|
2335
4294
|
this._ensurePathSemanticA11y();
|
|
2336
4295
|
this._refreshPathSemanticA11y('Path mode initialized');
|
|
4296
|
+
this._renderLearningWorkbenchState();
|
|
2337
4297
|
},
|
|
2338
4298
|
|
|
2339
4299
|
_ensurePathSemanticA11y: function() {
|
|
@@ -2453,6 +4413,14 @@ window.pathApp = {
|
|
|
2453
4413
|
|
|
2454
4414
|
const now = Date.now();
|
|
2455
4415
|
if ((now - this.semanticA11yLastAnnouncementAt) < 250) {
|
|
4416
|
+
if (this.semanticA11yDeferredRefreshTimer) {
|
|
4417
|
+
clearTimeout(this.semanticA11yDeferredRefreshTimer);
|
|
4418
|
+
}
|
|
4419
|
+
const delayMs = Math.max(25, 260 - (now - this.semanticA11yLastAnnouncementAt));
|
|
4420
|
+
this.semanticA11yDeferredRefreshTimer = setTimeout(() => {
|
|
4421
|
+
this.semanticA11yDeferredRefreshTimer = null;
|
|
4422
|
+
this._refreshPathSemanticA11y(reason);
|
|
4423
|
+
}, delayMs);
|
|
2456
4424
|
return;
|
|
2457
4425
|
}
|
|
2458
4426
|
|
|
@@ -2932,6 +4900,10 @@ window.pathApp = {
|
|
|
2932
4900
|
}
|
|
2933
4901
|
this.saveHistory();
|
|
2934
4902
|
this.updateHistorySidebar();
|
|
4903
|
+
const workbenchSidebar = document.getElementById('learning-workbench-sidebar');
|
|
4904
|
+
if (workbenchSidebar && workbenchSidebar.style.display && workbenchSidebar.style.display !== 'none') {
|
|
4905
|
+
void this.refreshLearningWorkbench({ force: false });
|
|
4906
|
+
}
|
|
2935
4907
|
|
|
2936
4908
|
const next = this.nodes.find(n => !this.completedNodes.has(n.id) && n.id !== node.id);
|
|
2937
4909
|
if (next) setTimeout(() => this.switchCentral(next.id), 500);
|
|
@@ -3219,6 +5191,11 @@ window.pathApp = {
|
|
|
3219
5191
|
const bridge = (typeof window !== 'undefined') ? window.NoteConnectionRuntime : null;
|
|
3220
5192
|
const waitForRuntime = this._isTauriMode() && bridge && typeof bridge.whenReady === 'function';
|
|
3221
5193
|
|
|
5194
|
+
if (this._isDetachedPreviewWithoutRuntimeBootstrap() && !forceDesktop) {
|
|
5195
|
+
console.log('[PathApp] Detached preview runtime detected; skipping early WebSocket bootstrap.');
|
|
5196
|
+
return;
|
|
5197
|
+
}
|
|
5198
|
+
|
|
3222
5199
|
if (this._isTauriMode() && !forceDesktop) {
|
|
3223
5200
|
// In normal Tauri flow, avoid idle early bridge sockets unless the desktop shell explicitly asks for one.
|
|
3224
5201
|
return;
|
|
@@ -3346,7 +5323,12 @@ window.pathApp = {
|
|
|
3346
5323
|
// This runs as soon as path_app.js is loaded, before init() is called.
|
|
3347
5324
|
(function() {
|
|
3348
5325
|
const isTauri = !!(window.pathApp && typeof window.pathApp._isTauriMode === 'function' && window.pathApp._isTauriMode());
|
|
3349
|
-
|
|
5326
|
+
const isDetachedPreview = !!(
|
|
5327
|
+
window.pathApp &&
|
|
5328
|
+
typeof window.pathApp._isDetachedPreviewWithoutRuntimeBootstrap === 'function' &&
|
|
5329
|
+
window.pathApp._isDetachedPreviewWithoutRuntimeBootstrap()
|
|
5330
|
+
);
|
|
5331
|
+
if (isTauri || isDetachedPreview) {
|
|
3350
5332
|
// Bridge-first Tauri: only connect when Path Mode is explicitly initialized.
|
|
3351
5333
|
return;
|
|
3352
5334
|
}
|
|
@@ -3358,7 +5340,3 @@ window.pathApp = {
|
|
|
3358
5340
|
}
|
|
3359
5341
|
}, 500);
|
|
3360
5342
|
})();
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|