@ltm-blueverse/alpha-semantic-hub 0.7.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 +21 -0
- package/README.md +1674 -0
- package/explorer/README.md +282 -0
- package/explorer/src/App.css +0 -0
- package/explorer/src/App.tsx +2035 -0
- package/explorer/src/ErrorBoundary.tsx +112 -0
- package/explorer/src/ExploreWorkspaceTabs.tsx +29 -0
- package/explorer/src/assets/hero.png +0 -0
- package/explorer/src/assets/react.svg +1 -0
- package/explorer/src/assets/vite.svg +1 -0
- package/explorer/src/explorerCapabilities.ts +24 -0
- package/explorer/src/index.css +71 -0
- package/explorer/src/main.tsx +7 -0
- package/explorer/src/store/edgePairKeys.d.ts +2 -0
- package/explorer/src/store/edgePairKeys.js +7 -0
- package/explorer/src/store/graphStore.ts +183 -0
- package/explorer/src/store/registryStore.ts +78 -0
- package/explorer/src/types.d.ts +16 -0
- package/explorer/src/ui/primitives.css +414 -0
- package/explorer/src/ui/primitives.tsx +170 -0
- package/explorer/src/workspaces/DecisionWorkspace/DecisionWorkspace.tsx +279 -0
- package/explorer/src/workspaces/DiffMergeWorkspace/DiffMergeWorkspace.tsx +121 -0
- package/explorer/src/workspaces/EnrichWorkspace/EntityResolutionTab.tsx +454 -0
- package/explorer/src/workspaces/EnrichWorkspace/RegistryTab.tsx +285 -0
- package/explorer/src/workspaces/GraphWorkspace/GraphCanvas.tsx +2673 -0
- package/explorer/src/workspaces/GraphWorkspace/GraphInspectorPanel.tsx +771 -0
- package/explorer/src/workspaces/GraphWorkspace/GraphLoadingOverlay.tsx +410 -0
- package/explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx +3584 -0
- package/explorer/src/workspaces/GraphWorkspace/MarkdownContentViewer.tsx +705 -0
- package/explorer/src/workspaces/GraphWorkspace/SigmaSceneAdapter.tsx +55 -0
- package/explorer/src/workspaces/GraphWorkspace/TimelinePanel.tsx +193 -0
- package/explorer/src/workspaces/GraphWorkspace/behaviors/clickSelectionBehavior.ts +29 -0
- package/explorer/src/workspaces/GraphWorkspace/behaviors/fitViewBehavior.ts +15 -0
- package/explorer/src/workspaces/GraphWorkspace/behaviors/focusCameraBehavior.ts +25 -0
- package/explorer/src/workspaces/GraphWorkspace/behaviors/hoverActivationBehavior.ts +15 -0
- package/explorer/src/workspaces/GraphWorkspace/behaviors/pathHighlightBehavior.ts +52 -0
- package/explorer/src/workspaces/GraphWorkspace/behaviors/searchFocusBehavior.ts +36 -0
- package/explorer/src/workspaces/GraphWorkspace/behaviors/types.ts +41 -0
- package/explorer/src/workspaces/GraphWorkspace/behaviors/viewModeSwitchBehavior.ts +29 -0
- package/explorer/src/workspaces/GraphWorkspace/graphAnalytics.ts +779 -0
- package/explorer/src/workspaces/GraphWorkspace/graphColorLegend.ts +36 -0
- package/explorer/src/workspaces/GraphWorkspace/graphConfig.ts +102 -0
- package/explorer/src/workspaces/GraphWorkspace/graphEntityShape.ts +34 -0
- package/explorer/src/workspaces/GraphWorkspace/graphLoading.ts +84 -0
- package/explorer/src/workspaces/GraphWorkspace/graphSceneLayers.ts +775 -0
- package/explorer/src/workspaces/GraphWorkspace/graphSceneState.ts +2753 -0
- package/explorer/src/workspaces/GraphWorkspace/graphStructureLayer.ts +335 -0
- package/explorer/src/workspaces/GraphWorkspace/graphTheme.ts +1078 -0
- package/explorer/src/workspaces/GraphWorkspace/graphViewCopy.ts +41 -0
- package/explorer/src/workspaces/GraphWorkspace/localGraphTransition.ts +9 -0
- package/explorer/src/workspaces/GraphWorkspace/markdownEditorState.ts +114 -0
- package/explorer/src/workspaces/GraphWorkspace/markdownResourceClient.ts +103 -0
- package/explorer/src/workspaces/GraphWorkspace/markdownUrlSafety.ts +29 -0
- package/explorer/src/workspaces/GraphWorkspace/nodeMarkdownSync.ts +81 -0
- package/explorer/src/workspaces/GraphWorkspace/pluginRegistryPredicates.ts +24 -0
- package/explorer/src/workspaces/GraphWorkspace/plugins/explorationEffectsPlugin.tsx +353 -0
- package/explorer/src/workspaces/GraphWorkspace/plugins/explorationEffectsPluginPhaseC.tsx +522 -0
- package/explorer/src/workspaces/GraphWorkspace/plugins/index.ts +16 -0
- package/explorer/src/workspaces/GraphWorkspace/plugins/legendPlugin.tsx +120 -0
- package/explorer/src/workspaces/GraphWorkspace/plugins/neighborhoodPanelPlugin.tsx +231 -0
- package/explorer/src/workspaces/GraphWorkspace/plugins/temporalOverlayPlugin.tsx +142 -0
- package/explorer/src/workspaces/GraphWorkspace/plugins/types.ts +106 -0
- package/explorer/src/workspaces/GraphWorkspace/realtimeGraphAttributes.ts +50 -0
- package/explorer/src/workspaces/GraphWorkspace/scene.ts +79 -0
- package/explorer/src/workspaces/GraphWorkspace/sigmaNativeRendering.ts +421 -0
- package/explorer/src/workspaces/GraphWorkspace/smallGraphLayout.ts +135 -0
- package/explorer/src/workspaces/GraphWorkspace/temporalLifecyclePredicates.ts +31 -0
- package/explorer/src/workspaces/GraphWorkspace/temporalScrubberBounds.ts +46 -0
- package/explorer/src/workspaces/GraphWorkspace/temporalSnapshotGuards.ts +113 -0
- package/explorer/src/workspaces/GraphWorkspace/types.ts +395 -0
- package/explorer/src/workspaces/GraphWorkspace/useLoadGraph.ts +746 -0
- package/explorer/src/workspaces/GraphWorkspace/useMarkdownEditor.ts +177 -0
- package/explorer/src/workspaces/ImportExportWorkspace/ImportExportWorkspace.tsx +196 -0
- package/explorer/src/workspaces/LineageWorkspace/LineageDiagram.tsx +177 -0
- package/explorer/src/workspaces/ManageWorkspace/KGOverviewTab.tsx +291 -0
- package/explorer/src/workspaces/ManageWorkspace/OntologySummaryTab.tsx +346 -0
- package/explorer/src/workspaces/MemoryWorkspace.tsx +464 -0
- package/explorer/src/workspaces/OntologyWorkspace/AlignmentsTab.tsx +437 -0
- package/explorer/src/workspaces/OntologyWorkspace/HealthTab.tsx +214 -0
- package/explorer/src/workspaces/OntologyWorkspace/OntologyEditor.tsx +770 -0
- package/explorer/src/workspaces/OntologyWorkspace/OntologyLoader.tsx +913 -0
- package/explorer/src/workspaces/OntologyWorkspace/OntologyManager.tsx +930 -0
- package/explorer/src/workspaces/OntologyWorkspace/OntologySearch.tsx +590 -0
- package/explorer/src/workspaces/OntologyWorkspace/ProposalReview.tsx +429 -0
- package/explorer/src/workspaces/OntologyWorkspace/SKOSVocabularyManager.tsx +696 -0
- package/explorer/src/workspaces/OntologyWorkspace/ShaclStudio.tsx +363 -0
- package/explorer/src/workspaces/OntologyWorkspace/VersionsTab.tsx +546 -0
- package/explorer/src/workspaces/OntologyWorkspace/api.ts +164 -0
- package/explorer/src/workspaces/OntologyWorkspace/index.tsx +101 -0
- package/explorer/src/workspaces/OntologyWorkspace/ontologyEditorModel.ts +87 -0
- package/explorer/src/workspaces/OntologyWorkspace/ontologyUrlState.ts +94 -0
- package/explorer/src/workspaces/OntologyWorkspace/types.ts +119 -0
- package/explorer/src/workspaces/ReasoningWorkspace.tsx +243 -0
- package/explorer/src/workspaces/SparqlWorkspace/SparqlWorkspace.tsx +271 -0
- package/explorer/src/workspaces/VocabularyWorkspace/ConceptTree.tsx +97 -0
- package/explorer/src/workspaces/VocabularyWorkspace/ImportDropzone.tsx +95 -0
- package/explorer/src/workspaces/VocabularyWorkspace/PropertyPanel.tsx +116 -0
- package/explorer/src/workspaces/VocabularyWorkspace/Sidebar.tsx +112 -0
- package/explorer/src/workspaces/VocabularyWorkspace/VocabularyWorkspace.tsx +70 -0
- package/explorer/src/workspaces/VocabularyWorkspace/queries.ts +52 -0
- package/explorer/src/workspaces/VocabularyWorkspace/types.ts +24 -0
- package/package.json +31 -0
- package/plugins/.claude-plugin/README.md +262 -0
- package/plugins/.claude-plugin/marketplace.json +20 -0
- package/plugins/.claude-plugin/plugin.json +33 -0
- package/plugins/.cline-plugin/README.md +32 -0
- package/plugins/.cline-plugin/marketplace.json +17 -0
- package/plugins/.cline-plugin/plugin.json +35 -0
- package/plugins/.codex-plugin/marketplace.json +21 -0
- package/plugins/.codex-plugin/plugin.json +35 -0
- package/plugins/.continue-plugin/README.md +34 -0
- package/plugins/.continue-plugin/marketplace.json +17 -0
- package/plugins/.continue-plugin/plugin.json +33 -0
- package/plugins/.cursor-plugin/marketplace.json +18 -0
- package/plugins/.cursor-plugin/plugin.json +32 -0
- package/plugins/.openclaw-plugin/README.md +62 -0
- package/plugins/.openclaw-plugin/marketplace.json +18 -0
- package/plugins/.openclaw-plugin/plugin.json +46 -0
- package/plugins/.pi-plugin/README.md +35 -0
- package/plugins/.vscode-plugin/README.md +48 -0
- package/plugins/.vscode-plugin/marketplace.json +17 -0
- package/plugins/.vscode-plugin/plugin.json +35 -0
- package/plugins/.windsurf-plugin/README.md +39 -0
- package/plugins/.windsurf-plugin/marketplace.json +17 -0
- package/plugins/.windsurf-plugin/plugin.json +35 -0
- package/plugins/agents/decision-advisor.md +126 -0
- package/plugins/agents/explainability.md +129 -0
- package/plugins/agents/kg-assistant.md +73 -0
- package/plugins/hooks/hooks.json +12 -0
- package/plugins/skills/causal/SKILL.md +74 -0
- package/plugins/skills/change/SKILL.md +80 -0
- package/plugins/skills/decision/SKILL.md +197 -0
- package/plugins/skills/deduplicate/SKILL.md +41 -0
- package/plugins/skills/embed/SKILL.md +230 -0
- package/plugins/skills/explain/SKILL.md +44 -0
- package/plugins/skills/export/SKILL.md +67 -0
- package/plugins/skills/extract/SKILL.md +93 -0
- package/plugins/skills/ingest/SKILL.md +38 -0
- package/plugins/skills/ontology/SKILL.md +89 -0
- package/plugins/skills/policy/SKILL.md +66 -0
- package/plugins/skills/provenance/SKILL.md +70 -0
- package/plugins/skills/query/SKILL.md +84 -0
- package/plugins/skills/reason/SKILL.md +201 -0
- package/plugins/skills/temporal/SKILL.md +164 -0
- package/plugins/skills/validate/SKILL.md +228 -0
- package/plugins/skills/visualize/SKILL.md +249 -0
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
import { Loader2 } from "lucide-react";
|
|
3
|
+
import { graph } from "../../store/graphStore";
|
|
4
|
+
import { GRAPH_THEME, withAlpha } from "./graphTheme";
|
|
5
|
+
import type { FocusedUnavailableReason, GraphSelectedNodeKind } from "./types";
|
|
6
|
+
import { focusedUnavailableReasonText } from "./graphViewCopy";
|
|
7
|
+
import { MarkdownContentViewer } from "./MarkdownContentViewer";
|
|
8
|
+
import type { MarkdownApplyResult } from "./markdownResourceClient";
|
|
9
|
+
|
|
10
|
+
export type LinkPrediction = {
|
|
11
|
+
target: string;
|
|
12
|
+
type: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
score: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type PathResponse = {
|
|
18
|
+
path: string[];
|
|
19
|
+
edge_ids?: string[];
|
|
20
|
+
total_weight: number;
|
|
21
|
+
hop_count: number;
|
|
22
|
+
distance_band: "direct" | "near" | "mid-range" | "distant";
|
|
23
|
+
// FR-1 distance intelligence enrichment
|
|
24
|
+
semantic_similarity?: number | null;
|
|
25
|
+
path_coherence_score?: number | null;
|
|
26
|
+
confidence_decay?: number | null;
|
|
27
|
+
bottleneck_node?: string | null;
|
|
28
|
+
alternative_path_count?: number;
|
|
29
|
+
interpretation?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export interface GraphInspectorPanelProps {
|
|
33
|
+
nodeId: string;
|
|
34
|
+
inspectableNodeId?: string | null;
|
|
35
|
+
selectedNodeKind?: GraphSelectedNodeKind;
|
|
36
|
+
canActivateFocused?: boolean;
|
|
37
|
+
focusedUnavailableReason?: FocusedUnavailableReason | null;
|
|
38
|
+
predictions: LinkPrediction[];
|
|
39
|
+
predictionType: string;
|
|
40
|
+
onPredictionTypeChange: (value: string) => void;
|
|
41
|
+
onRunPredictions: () => void;
|
|
42
|
+
isRunningPredictions?: boolean;
|
|
43
|
+
pathTargetId: string;
|
|
44
|
+
onPathTargetChange: (value: string) => void;
|
|
45
|
+
onTracePath: () => void;
|
|
46
|
+
pathResult: PathResponse | null;
|
|
47
|
+
onDownloadProvenance: (format: "json" | "markdown") => void;
|
|
48
|
+
onFocusNode?: (nodeId: string) => void;
|
|
49
|
+
onMarkdownApplied?: (result: MarkdownApplyResult) => void;
|
|
50
|
+
onMarkdownDirtyChange?: (dirty: boolean) => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const PROVENANCE_KEYS = ["source", "source_url", "pmid", "pmids", "evidence", "provenance", "confidence"] as const;
|
|
54
|
+
|
|
55
|
+
function sourceAttribution(properties: Record<string, unknown>) {
|
|
56
|
+
return PROVENANCE_KEYS
|
|
57
|
+
.filter((key) => key in properties)
|
|
58
|
+
.map((key) => ({ key, value: properties[key] }));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* ─── Path Distance Intelligence Panel ──────────────────────────── */
|
|
62
|
+
|
|
63
|
+
const BAND_COLORS: Record<string, string> = {
|
|
64
|
+
direct: "#3fb950",
|
|
65
|
+
near: "#ff8fa0",
|
|
66
|
+
"mid-range": "#e3b341",
|
|
67
|
+
distant: "#ff7b72",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function PathDistanceIntelPanel({ result }: { result: PathResponse }) {
|
|
71
|
+
const bandColor = BAND_COLORS[result.distance_band] ?? "#b08b93";
|
|
72
|
+
const hasMetrics =
|
|
73
|
+
result.confidence_decay != null ||
|
|
74
|
+
result.semantic_similarity != null ||
|
|
75
|
+
result.path_coherence_score != null ||
|
|
76
|
+
result.bottleneck_node != null;
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 8 }}>
|
|
80
|
+
{/* distance band + alt paths */}
|
|
81
|
+
<div style={{ display: "flex", gap: 6, flexWrap: "wrap", alignItems: "center" }}>
|
|
82
|
+
<span
|
|
83
|
+
style={{
|
|
84
|
+
padding: "3px 8px",
|
|
85
|
+
borderRadius: 999,
|
|
86
|
+
background: withAlpha(bandColor, 0.14),
|
|
87
|
+
border: `1px solid ${withAlpha(bandColor, 0.3)}`,
|
|
88
|
+
color: bandColor,
|
|
89
|
+
fontSize: 11,
|
|
90
|
+
fontWeight: 700,
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
{result.distance_band} · {result.hop_count} hop{result.hop_count !== 1 ? "s" : ""}
|
|
94
|
+
</span>
|
|
95
|
+
{(result.alternative_path_count ?? 0) > 0 && (
|
|
96
|
+
<span style={subtleChipStyle}>{result.alternative_path_count} alt path{result.alternative_path_count !== 1 ? "s" : ""}</span>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
{/* metric grid */}
|
|
101
|
+
{hasMetrics && <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
|
|
102
|
+
{result.confidence_decay != null && (
|
|
103
|
+
<div style={metricCardStyle}>
|
|
104
|
+
<div style={metricLabelStyle}>Confidence Decay</div>
|
|
105
|
+
<div
|
|
106
|
+
style={{
|
|
107
|
+
...metricValueStyle,
|
|
108
|
+
color: result.confidence_decay > 0.6 ? "#3fb950" : result.confidence_decay > 0.3 ? "#e3b341" : "#ff7b72",
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
{(result.confidence_decay * 100).toFixed(1)}%
|
|
112
|
+
</div>
|
|
113
|
+
<div style={metricBarTrackStyle}>
|
|
114
|
+
<div
|
|
115
|
+
style={{
|
|
116
|
+
...metricBarFillStyle,
|
|
117
|
+
width: `${result.confidence_decay * 100}%`,
|
|
118
|
+
background:
|
|
119
|
+
result.confidence_decay > 0.6 ? "#3fb950" : result.confidence_decay > 0.3 ? "#e3b341" : "#ff7b72",
|
|
120
|
+
}}
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
{result.semantic_similarity != null && (
|
|
126
|
+
<div style={metricCardStyle}>
|
|
127
|
+
<div style={metricLabelStyle}>Semantic Sim.</div>
|
|
128
|
+
<div style={{ ...metricValueStyle, color: "#ff8fa0" }}>
|
|
129
|
+
{(result.semantic_similarity * 100).toFixed(1)}%
|
|
130
|
+
</div>
|
|
131
|
+
<div style={metricBarTrackStyle}>
|
|
132
|
+
<div style={{ ...metricBarFillStyle, width: `${result.semantic_similarity * 100}%`, background: "#ff8fa0" }} />
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
{result.path_coherence_score != null && (
|
|
137
|
+
<div style={metricCardStyle}>
|
|
138
|
+
<div style={metricLabelStyle}>Path Coherence</div>
|
|
139
|
+
<div style={{ ...metricValueStyle, color: "#a5d6a7" }}>
|
|
140
|
+
{(result.path_coherence_score * 100).toFixed(1)}%
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
{result.bottleneck_node && (
|
|
145
|
+
<div style={metricCardStyle}>
|
|
146
|
+
<div style={metricLabelStyle}>Bottleneck</div>
|
|
147
|
+
<div
|
|
148
|
+
style={{
|
|
149
|
+
...metricValueStyle,
|
|
150
|
+
color: "#e3b341",
|
|
151
|
+
fontSize: 11,
|
|
152
|
+
overflow: "hidden",
|
|
153
|
+
textOverflow: "ellipsis",
|
|
154
|
+
whiteSpace: "nowrap",
|
|
155
|
+
}}
|
|
156
|
+
title={result.bottleneck_node}
|
|
157
|
+
>
|
|
158
|
+
{getNodeLabel(result.bottleneck_node)}
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
</div>}
|
|
163
|
+
|
|
164
|
+
{/* interpretation */}
|
|
165
|
+
{result.interpretation && (
|
|
166
|
+
<div
|
|
167
|
+
style={{
|
|
168
|
+
padding: "8px 10px",
|
|
169
|
+
background: "rgba(255,45,75,0.06)",
|
|
170
|
+
borderRadius: 8,
|
|
171
|
+
border: "1px solid rgba(255,45,75,0.14)",
|
|
172
|
+
color: "#a0b4cc",
|
|
173
|
+
fontSize: 12,
|
|
174
|
+
lineHeight: 1.5,
|
|
175
|
+
}}
|
|
176
|
+
>
|
|
177
|
+
{result.interpretation}
|
|
178
|
+
</div>
|
|
179
|
+
)}
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* ─── Path Flow Visualizer ──────────────────────────────────────── */
|
|
185
|
+
|
|
186
|
+
function getNodeLabel(nodeId: string): string {
|
|
187
|
+
if (!graph.hasNode(nodeId)) return nodeId;
|
|
188
|
+
const attrs = graph.getNodeAttributes(nodeId) as { label?: string; content?: string };
|
|
189
|
+
return String(attrs.label ?? attrs.content ?? nodeId);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function getEdgeLabelBetween(sourceId: string, targetId: string, edgeIds?: string[]): string {
|
|
193
|
+
// Try to find the specific edge from edgeIds first
|
|
194
|
+
if (edgeIds) {
|
|
195
|
+
for (const edgeId of edgeIds) {
|
|
196
|
+
if (graph.hasEdge(edgeId)) {
|
|
197
|
+
const [src, tgt] = graph.extremities(edgeId);
|
|
198
|
+
if ((src === sourceId && tgt === targetId) || (src === targetId && tgt === sourceId)) {
|
|
199
|
+
const attrs = graph.getEdgeAttributes(edgeId) as { edgeType?: string };
|
|
200
|
+
return attrs.edgeType ?? "→";
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Fallback: find any edge between the pair
|
|
206
|
+
if (graph.hasNode(sourceId) && graph.hasNode(targetId)) {
|
|
207
|
+
let label = "→";
|
|
208
|
+
graph.forEachEdge(sourceId, targetId, (_edgeId, attrs) => {
|
|
209
|
+
const edgeAttrs = attrs as { edgeType?: string };
|
|
210
|
+
if (edgeAttrs.edgeType) label = edgeAttrs.edgeType;
|
|
211
|
+
});
|
|
212
|
+
return label;
|
|
213
|
+
}
|
|
214
|
+
return "→";
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function PathFlowViz({
|
|
218
|
+
path,
|
|
219
|
+
edgeIds,
|
|
220
|
+
totalWeight,
|
|
221
|
+
bottleneckNodeId,
|
|
222
|
+
onFocusNode,
|
|
223
|
+
}: {
|
|
224
|
+
path: string[];
|
|
225
|
+
edgeIds?: string[];
|
|
226
|
+
totalWeight: number;
|
|
227
|
+
bottleneckNodeId?: string | null;
|
|
228
|
+
onFocusNode?: (nodeId: string) => void;
|
|
229
|
+
}) {
|
|
230
|
+
if (path.length === 0) {
|
|
231
|
+
return <div style={emptyTextStyle}>No path found between the selected nodes.</div>;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<div>
|
|
236
|
+
{/* Horizontal scrollable chip flow */}
|
|
237
|
+
<div style={pathFlowContainerStyle}>
|
|
238
|
+
{path.map((nodeId, index) => {
|
|
239
|
+
const label = getNodeLabel(nodeId);
|
|
240
|
+
const edgeLabel =
|
|
241
|
+
index < path.length - 1
|
|
242
|
+
? getEdgeLabelBetween(nodeId, path[index + 1], edgeIds)
|
|
243
|
+
: null;
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<div key={`${nodeId}-${index}`} style={{ display: "contents" }}>
|
|
247
|
+
{/* Node chip */}
|
|
248
|
+
<button
|
|
249
|
+
onClick={() => onFocusNode?.(nodeId)}
|
|
250
|
+
title={nodeId === bottleneckNodeId ? `Bottleneck: ${nodeId}` : `Focus: ${nodeId}`}
|
|
251
|
+
style={{
|
|
252
|
+
...pathNodeChipStyle,
|
|
253
|
+
cursor: onFocusNode ? "pointer" : "default",
|
|
254
|
+
...(nodeId === bottleneckNodeId
|
|
255
|
+
? { border: "1px solid rgba(227,179,65,0.5)", background: "rgba(227,179,65,0.12)" }
|
|
256
|
+
: {}),
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
<span style={pathNodeIndexStyle}>{index + 1}</span>
|
|
260
|
+
<span style={{ maxWidth: 120, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
|
261
|
+
{label}
|
|
262
|
+
</span>
|
|
263
|
+
</button>
|
|
264
|
+
|
|
265
|
+
{/* Edge connector */}
|
|
266
|
+
{edgeLabel !== null ? (
|
|
267
|
+
<div style={pathEdgeConnectorStyle}>
|
|
268
|
+
<div style={{ width: 16, height: 1, background: "rgba(255,45,75,0.3)" }} />
|
|
269
|
+
<span style={pathEdgeLabelStyle}>{edgeLabel}</span>
|
|
270
|
+
<div style={{ display: "flex", alignItems: "center" }}>
|
|
271
|
+
<div style={{ width: 12, height: 1, background: "rgba(255,45,75,0.3)" }} />
|
|
272
|
+
<div style={{ width: 0, height: 0, borderTop: "4px solid transparent", borderBottom: "4px solid transparent", borderLeft: "5px solid rgba(255,45,75,0.4)" }} />
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
) : null}
|
|
276
|
+
</div>
|
|
277
|
+
);
|
|
278
|
+
})}
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
{/* Weight badge */}
|
|
282
|
+
<div style={{ marginTop: 8, display: "flex", alignItems: "center", gap: 8 }}>
|
|
283
|
+
<span style={{ color: "#9c7a82", fontSize: 11 }}>Total weight:</span>
|
|
284
|
+
<span style={{ color: "#ff8fa0", fontSize: 12, fontWeight: 700 }}>{totalWeight.toFixed(3)}</span>
|
|
285
|
+
<span style={{ color: "#9c7a82", fontSize: 11 }}>·</span>
|
|
286
|
+
<span style={{ color: "#9c7a82", fontSize: 11 }}>{path.length} hops</span>
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* ─── Main Panel ─────────────────────────────────────────────────── */
|
|
293
|
+
|
|
294
|
+
export function GraphInspectorPanel({
|
|
295
|
+
nodeId,
|
|
296
|
+
inspectableNodeId,
|
|
297
|
+
selectedNodeKind = "none",
|
|
298
|
+
canActivateFocused = false,
|
|
299
|
+
focusedUnavailableReason = null,
|
|
300
|
+
predictions,
|
|
301
|
+
predictionType,
|
|
302
|
+
onPredictionTypeChange,
|
|
303
|
+
onRunPredictions,
|
|
304
|
+
isRunningPredictions = false,
|
|
305
|
+
pathTargetId,
|
|
306
|
+
onPathTargetChange,
|
|
307
|
+
onTracePath,
|
|
308
|
+
pathResult,
|
|
309
|
+
onDownloadProvenance,
|
|
310
|
+
onFocusNode,
|
|
311
|
+
onMarkdownApplied,
|
|
312
|
+
onMarkdownDirtyChange,
|
|
313
|
+
}: GraphInspectorPanelProps) {
|
|
314
|
+
if (!nodeId) {
|
|
315
|
+
return (
|
|
316
|
+
<div style={{ padding: 32, textAlign: "center", display: "flex", flexDirection: "column", alignItems: "center", gap: 12, marginTop: 32 }}>
|
|
317
|
+
<div style={{ width: 40, height: 40, borderRadius: "50%", background: "rgba(98, 226, 205, 0.07)", border: `1px solid ${GRAPH_THEME.ui.surface.panelBorder}`, display: "flex", alignItems: "center", justifyContent: "center" }}>
|
|
318
|
+
<div style={{ width: 14, height: 14, borderRadius: "50%", background: GRAPH_THEME.ui.timeline.playheadSoft }} />
|
|
319
|
+
</div>
|
|
320
|
+
<p style={{ color: GRAPH_THEME.ui.text.muted, fontSize: 14, margin: 0, lineHeight: 1.6 }}>
|
|
321
|
+
Search for a node or click one in the canvas to inspect its properties.
|
|
322
|
+
</p>
|
|
323
|
+
</div>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const resolvedNodeId = inspectableNodeId && graph.hasNode(inspectableNodeId) ? inspectableNodeId : null;
|
|
328
|
+
const directlyInspectable = graph.hasNode(nodeId);
|
|
329
|
+
const effectiveNodeId = directlyInspectable ? nodeId : resolvedNodeId;
|
|
330
|
+
const actionNodeId = directlyInspectable ? nodeId : resolvedNodeId;
|
|
331
|
+
const groupedDisplaySelection = selectedNodeKind === "grouped" && !directlyInspectable;
|
|
332
|
+
|
|
333
|
+
if (!effectiveNodeId) {
|
|
334
|
+
return (
|
|
335
|
+
<aside style={{ padding: 24, display: "flex", flexDirection: "column", gap: 16 }}>
|
|
336
|
+
<div style={{ borderBottom: `1px solid ${GRAPH_THEME.ui.surface.divider}`, paddingBottom: 16 }}>
|
|
337
|
+
<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 8 }}>
|
|
338
|
+
<span style={{ background: GRAPH_THEME.ui.timeline.playhead, boxShadow: "0 0 10px rgba(98, 226, 205, 0.34)", width: 8, height: 8, borderRadius: "50%" }} />
|
|
339
|
+
<span style={{ color: GRAPH_THEME.ui.timeline.playhead, fontSize: 12, fontWeight: 700 }}>Selection</span>
|
|
340
|
+
</div>
|
|
341
|
+
<h3 style={{ margin: 0, color: GRAPH_THEME.ui.text.strong, fontSize: 20, fontWeight: 700, wordBreak: "break-word" }}>
|
|
342
|
+
{nodeId}
|
|
343
|
+
</h3>
|
|
344
|
+
<div style={{ color: GRAPH_THEME.ui.text.muted, fontSize: 12, marginTop: 6, fontFamily: "monospace", wordBreak: "break-all" }}>{nodeId}</div>
|
|
345
|
+
</div>
|
|
346
|
+
<div style={groupedSelectionNoticeStyle}>
|
|
347
|
+
<div style={{ color: GRAPH_THEME.ui.text.strong, fontWeight: 600, marginBottom: 6 }}>Selected item is not directly inspectable in the current graph.</div>
|
|
348
|
+
<div style={{ color: GRAPH_THEME.ui.text.body, fontSize: 13, lineHeight: 1.6 }}>
|
|
349
|
+
{canActivateFocused
|
|
350
|
+
? "Use Focus to resolve this grouped selection to its canonical node."
|
|
351
|
+
: focusedUnavailableReasonText(focusedUnavailableReason)}
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
</aside>
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const attributes = graph.getNodeAttributes(effectiveNodeId) as {
|
|
359
|
+
color?: string;
|
|
360
|
+
content?: string;
|
|
361
|
+
label?: string;
|
|
362
|
+
nodeType?: string;
|
|
363
|
+
valid_from?: string | null;
|
|
364
|
+
valid_until?: string | null;
|
|
365
|
+
properties?: Record<string, unknown>;
|
|
366
|
+
};
|
|
367
|
+
const properties = attributes?.properties ?? {};
|
|
368
|
+
const attribution = sourceAttribution(properties);
|
|
369
|
+
const accentColor = attributes?.color || "#ff2d4b";
|
|
370
|
+
const propertyEntries = Object.entries(properties).filter(
|
|
371
|
+
([key]) =>
|
|
372
|
+
!["x","y","valid_from","valid_until","content","source","source_url","pmid","pmids","evidence","provenance","confidence"].includes(key),
|
|
373
|
+
);
|
|
374
|
+
const nodeContent = (typeof attributes?.content === "string" && attributes.content)
|
|
375
|
+
? attributes.content
|
|
376
|
+
: (typeof properties.content === "string" && properties.content)
|
|
377
|
+
? properties.content
|
|
378
|
+
: "";
|
|
379
|
+
|
|
380
|
+
return (
|
|
381
|
+
<aside style={{ padding: 24, display: "flex", flexDirection: "column", gap: 18 }}>
|
|
382
|
+
{/* Node identity */}
|
|
383
|
+
<div style={{ borderBottom: `1px solid ${GRAPH_THEME.ui.surface.divider}`, paddingBottom: 16 }}>
|
|
384
|
+
<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 8 }}>
|
|
385
|
+
<span style={{ background: accentColor, boxShadow: `0 0 10px ${accentColor}`, width: 8, height: 8, borderRadius: "50%" }} />
|
|
386
|
+
<span style={{ color: accentColor, fontSize: 12, fontWeight: 700 }}>
|
|
387
|
+
{groupedDisplaySelection ? "Grouped Selection" : (attributes?.nodeType || "Entity")}
|
|
388
|
+
</span>
|
|
389
|
+
</div>
|
|
390
|
+
<h3 style={{ margin: 0, color: GRAPH_THEME.ui.text.strong, fontSize: 20, fontWeight: 700, wordBreak: "break-word" }}>
|
|
391
|
+
{String(attributes?.label ?? effectiveNodeId)}
|
|
392
|
+
</h3>
|
|
393
|
+
<div style={{ color: GRAPH_THEME.ui.text.muted, fontSize: 12, marginTop: 6, fontFamily: "monospace", wordBreak: "break-all" }}>
|
|
394
|
+
{groupedDisplaySelection ? nodeId : effectiveNodeId}
|
|
395
|
+
</div>
|
|
396
|
+
{groupedDisplaySelection ? (
|
|
397
|
+
<div style={groupedSelectionNoticeStyle}>
|
|
398
|
+
<div style={{ color: GRAPH_THEME.ui.text.strong, fontWeight: 600, marginBottom: 6 }}>This grouped item stays display-level until you explicitly enter Focused mode.</div>
|
|
399
|
+
<div style={{ color: GRAPH_THEME.ui.text.body, fontSize: 13, lineHeight: 1.6 }}>
|
|
400
|
+
{canActivateFocused
|
|
401
|
+
? `Canonical node available: ${effectiveNodeId}`
|
|
402
|
+
: focusedUnavailableReasonText(focusedUnavailableReason)}
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
) : null}
|
|
406
|
+
<div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginTop: 12 }}>
|
|
407
|
+
{attributes?.valid_from || attributes?.valid_until ? (
|
|
408
|
+
<span style={subtleChipStyle}>temporal</span>
|
|
409
|
+
) : null}
|
|
410
|
+
{attribution.length ? <span style={subtleChipStyle}>{attribution.length} source fields</span> : null}
|
|
411
|
+
{predictions.length ? <span style={subtleChipStyle}>{predictions.length} candidate links</span> : null}
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
{/* Temporal bounds */}
|
|
416
|
+
{(attributes?.valid_from || attributes?.valid_until) ? (
|
|
417
|
+
<div style={{ padding: "10px 12px", background: "rgba(233, 196, 122, 0.075)", border: "1px solid rgba(233, 196, 122, 0.22)", borderRadius: 8, fontSize: 12, color: GRAPH_THEME.palette.accent.selected, fontFamily: "monospace" }}>
|
|
418
|
+
{attributes?.valid_from ? <div>from: {attributes.valid_from}</div> : null}
|
|
419
|
+
{attributes?.valid_until ? <div>until: {attributes.valid_until}</div> : null}
|
|
420
|
+
</div>
|
|
421
|
+
) : null}
|
|
422
|
+
|
|
423
|
+
{/* Canonical nodes remain editable even when their current body is empty. */}
|
|
424
|
+
<details className="node-panel-collapse" open>
|
|
425
|
+
<summary className="node-panel-summary">Content</summary>
|
|
426
|
+
<div className="node-panel-body" style={{ marginTop: 8 }}>
|
|
427
|
+
<MarkdownContentViewer
|
|
428
|
+
content={nodeContent}
|
|
429
|
+
resource={{ kind: "context-node", id: effectiveNodeId }}
|
|
430
|
+
onApplied={onMarkdownApplied}
|
|
431
|
+
onDirtyChange={onMarkdownDirtyChange}
|
|
432
|
+
/>
|
|
433
|
+
</div>
|
|
434
|
+
</details>
|
|
435
|
+
|
|
436
|
+
{/* Actions */}
|
|
437
|
+
<section style={sectionStyle}>
|
|
438
|
+
<div style={sectionTitleStyle}>Actions</div>
|
|
439
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
|
|
440
|
+
<button
|
|
441
|
+
style={{ ...actionButtonStyle, width: "100%", justifyContent: "center", opacity: isRunningPredictions ? 0.7 : 1 }}
|
|
442
|
+
onClick={onRunPredictions}
|
|
443
|
+
disabled={isRunningPredictions || !actionNodeId}
|
|
444
|
+
>
|
|
445
|
+
{isRunningPredictions ? (
|
|
446
|
+
<Loader2 size={14} className="animate-spin" style={{ marginRight: 6 }} />
|
|
447
|
+
) : null}
|
|
448
|
+
{isRunningPredictions ? "Running…" : "Run Link Prediction"}
|
|
449
|
+
</button>
|
|
450
|
+
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
|
451
|
+
<button style={secondaryActionButtonStyle} onClick={() => onDownloadProvenance("json")} disabled={!actionNodeId}>
|
|
452
|
+
Provenance JSON
|
|
453
|
+
</button>
|
|
454
|
+
<button style={secondaryActionButtonStyle} onClick={() => onDownloadProvenance("markdown")} disabled={!actionNodeId}>
|
|
455
|
+
Provenance MD
|
|
456
|
+
</button>
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
<input
|
|
460
|
+
value={predictionType}
|
|
461
|
+
onChange={(event) => onPredictionTypeChange(event.target.value)}
|
|
462
|
+
placeholder="Optional candidate type filter, e.g. disease"
|
|
463
|
+
style={inputStyle}
|
|
464
|
+
/>
|
|
465
|
+
</section>
|
|
466
|
+
|
|
467
|
+
{/* Trace Path */}
|
|
468
|
+
<section style={sectionStyle}>
|
|
469
|
+
<div style={sectionTitleStyle}>Trace Path</div>
|
|
470
|
+
<input
|
|
471
|
+
value={pathTargetId}
|
|
472
|
+
onChange={(event) => onPathTargetChange(event.target.value)}
|
|
473
|
+
placeholder="Target node ID"
|
|
474
|
+
style={inputStyle}
|
|
475
|
+
/>
|
|
476
|
+
<button style={actionButtonStyle} onClick={onTracePath} disabled={!actionNodeId}>Trace Causal Path</button>
|
|
477
|
+
|
|
478
|
+
{pathResult?.path?.length ? (
|
|
479
|
+
<>
|
|
480
|
+
<PathFlowViz
|
|
481
|
+
path={pathResult.path}
|
|
482
|
+
edgeIds={pathResult.edge_ids}
|
|
483
|
+
totalWeight={pathResult.total_weight}
|
|
484
|
+
bottleneckNodeId={pathResult.bottleneck_node}
|
|
485
|
+
onFocusNode={onFocusNode}
|
|
486
|
+
/>
|
|
487
|
+
<PathDistanceIntelPanel result={pathResult} />
|
|
488
|
+
</>
|
|
489
|
+
) : (
|
|
490
|
+
<div style={emptyTextStyle}>
|
|
491
|
+
Choose a target or click a candidate prediction to prepare a path trace.
|
|
492
|
+
</div>
|
|
493
|
+
)}
|
|
494
|
+
</section>
|
|
495
|
+
|
|
496
|
+
{/* Candidate Links */}
|
|
497
|
+
<details className="node-panel-collapse" open={predictions.length > 0}>
|
|
498
|
+
<summary className="node-panel-summary">Candidate Links</summary>
|
|
499
|
+
<div className="node-panel-body">
|
|
500
|
+
{predictions.length > 0 ? (
|
|
501
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
502
|
+
{predictions.map((prediction) => (
|
|
503
|
+
<button
|
|
504
|
+
key={`${prediction.target}-${prediction.type}`}
|
|
505
|
+
style={predictionCardStyle}
|
|
506
|
+
onClick={() => onPathTargetChange(prediction.target)}
|
|
507
|
+
>
|
|
508
|
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 8 }}>
|
|
509
|
+
<div>
|
|
510
|
+
<div style={{ color: GRAPH_THEME.ui.text.strong, fontWeight: 600 }}>{prediction.label || prediction.target}</div>
|
|
511
|
+
<div style={{ color: GRAPH_THEME.ui.text.muted, fontSize: 12 }}>{prediction.type}</div>
|
|
512
|
+
</div>
|
|
513
|
+
<div style={{ flexShrink: 0 }}>
|
|
514
|
+
<div style={{
|
|
515
|
+
padding: "2px 7px",
|
|
516
|
+
borderRadius: 999,
|
|
517
|
+
fontSize: 10,
|
|
518
|
+
fontWeight: 700,
|
|
519
|
+
background: GRAPH_THEME.ui.timeline.playheadSoft,
|
|
520
|
+
border: `1px solid ${GRAPH_THEME.ui.control.activeBorder}`,
|
|
521
|
+
color: GRAPH_THEME.ui.timeline.playhead,
|
|
522
|
+
}}>
|
|
523
|
+
{(prediction.score * 100).toFixed(1)}%
|
|
524
|
+
</div>
|
|
525
|
+
</div>
|
|
526
|
+
</div>
|
|
527
|
+
</button>
|
|
528
|
+
))}
|
|
529
|
+
</div>
|
|
530
|
+
) : isRunningPredictions ? (
|
|
531
|
+
<div style={{ display: "flex", alignItems: "center", gap: 8, padding: 8, color: "#b08b93", fontSize: 12 }}>
|
|
532
|
+
<Loader2 size={13} className="animate-spin" />
|
|
533
|
+
<span>Computing candidate links…</span>
|
|
534
|
+
</div>
|
|
535
|
+
) : (
|
|
536
|
+
<div style={emptyTextStyle}>Run link prediction to surface likely next-hop relationships.</div>
|
|
537
|
+
)}
|
|
538
|
+
</div>
|
|
539
|
+
</details>
|
|
540
|
+
|
|
541
|
+
{/* Source Attribution */}
|
|
542
|
+
<details className="node-panel-collapse">
|
|
543
|
+
<summary className="node-panel-summary">Source Attribution</summary>
|
|
544
|
+
<div className="node-panel-body">
|
|
545
|
+
{attribution.length ? (
|
|
546
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
547
|
+
{attribution.map(({ key, value }) => (
|
|
548
|
+
<div key={key} style={propertyCardStyle}>
|
|
549
|
+
<div style={{ color: GRAPH_THEME.ui.timeline.playhead, fontSize: 11, marginBottom: 4 }}>{key}</div>
|
|
550
|
+
<div style={{ color: GRAPH_THEME.ui.text.body, fontSize: 13, wordBreak: "break-word" }}>
|
|
551
|
+
{typeof value === "object" ? JSON.stringify(value) : String(value)}
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
))}
|
|
555
|
+
</div>
|
|
556
|
+
) : (
|
|
557
|
+
<div style={emptyTextStyle}>No explicit attribution metadata was found on this node.</div>
|
|
558
|
+
)}
|
|
559
|
+
</div>
|
|
560
|
+
</details>
|
|
561
|
+
|
|
562
|
+
{/* Properties */}
|
|
563
|
+
<details className="node-panel-collapse">
|
|
564
|
+
<summary className="node-panel-summary">Properties</summary>
|
|
565
|
+
<div className="node-panel-body">
|
|
566
|
+
{propertyEntries.length ? (
|
|
567
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
568
|
+
{propertyEntries.map(([key, value]) => (
|
|
569
|
+
<div key={key} style={propertyCardStyle}>
|
|
570
|
+
<div style={{ color: GRAPH_THEME.ui.timeline.playhead, fontSize: 11, marginBottom: 4 }}>{key}</div>
|
|
571
|
+
<div style={{ color: GRAPH_THEME.ui.text.body, fontSize: 13, wordBreak: "break-word" }}>
|
|
572
|
+
{typeof value === "object" ? JSON.stringify(value) : String(value)}
|
|
573
|
+
</div>
|
|
574
|
+
</div>
|
|
575
|
+
))}
|
|
576
|
+
</div>
|
|
577
|
+
) : (
|
|
578
|
+
<div style={emptyTextStyle}>No additional properties are attached to this node.</div>
|
|
579
|
+
)}
|
|
580
|
+
</div>
|
|
581
|
+
</details>
|
|
582
|
+
</aside>
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/* ─── styles ─────────────────────────────────────────────────────── */
|
|
587
|
+
|
|
588
|
+
const inputStyle: CSSProperties = {
|
|
589
|
+
width: "100%",
|
|
590
|
+
background: GRAPH_THEME.ui.control.inputBg,
|
|
591
|
+
border: `1px solid ${GRAPH_THEME.ui.control.inputBorder}`,
|
|
592
|
+
color: GRAPH_THEME.ui.text.strong,
|
|
593
|
+
borderRadius: 12,
|
|
594
|
+
padding: "11px 13px",
|
|
595
|
+
fontSize: 13,
|
|
596
|
+
boxShadow: "inset 0 1px 0 rgba(255,255,255,0.035)",
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const groupedSelectionNoticeStyle: CSSProperties = {
|
|
600
|
+
marginTop: 12,
|
|
601
|
+
padding: "10px 12px",
|
|
602
|
+
background: "rgba(98, 226, 205, 0.07)",
|
|
603
|
+
border: `1px solid ${GRAPH_THEME.ui.control.activeBorder}`,
|
|
604
|
+
borderRadius: 12,
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
const actionButtonStyle: CSSProperties = {
|
|
608
|
+
background: GRAPH_THEME.ui.control.primaryBg,
|
|
609
|
+
color: GRAPH_THEME.ui.control.primaryText,
|
|
610
|
+
border: `1px solid ${GRAPH_THEME.ui.control.primaryBorder}`,
|
|
611
|
+
borderRadius: 12,
|
|
612
|
+
padding: "9px 12px",
|
|
613
|
+
cursor: "pointer",
|
|
614
|
+
fontWeight: 700,
|
|
615
|
+
fontSize: 12,
|
|
616
|
+
display: "inline-flex",
|
|
617
|
+
alignItems: "center",
|
|
618
|
+
justifyContent: "center",
|
|
619
|
+
boxShadow: "inset 0 1px 0 rgba(255,255,255,0.07), 0 10px 24px rgba(0,0,0,0.18)",
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
const secondaryActionButtonStyle: CSSProperties = {
|
|
623
|
+
...actionButtonStyle,
|
|
624
|
+
background: GRAPH_THEME.ui.control.defaultBg,
|
|
625
|
+
border: `1px solid ${GRAPH_THEME.ui.control.defaultBorder}`,
|
|
626
|
+
color: GRAPH_THEME.ui.control.defaultText,
|
|
627
|
+
fontWeight: 600,
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
const predictionCardStyle: CSSProperties = {
|
|
631
|
+
textAlign: "left",
|
|
632
|
+
padding: "10px 12px",
|
|
633
|
+
background: "rgba(255, 255, 255, 0.035)",
|
|
634
|
+
border: `1px solid ${GRAPH_THEME.ui.surface.panelBorder}`,
|
|
635
|
+
borderRadius: 10,
|
|
636
|
+
cursor: "pointer",
|
|
637
|
+
width: "100%",
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
const propertyCardStyle: CSSProperties = {
|
|
641
|
+
background: "rgba(255, 255, 255, 0.028)",
|
|
642
|
+
padding: "10px 12px",
|
|
643
|
+
borderRadius: 10,
|
|
644
|
+
border: `1px solid ${GRAPH_THEME.ui.surface.panelBorder}`,
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
const emptyTextStyle: CSSProperties = {
|
|
648
|
+
color: GRAPH_THEME.ui.text.muted,
|
|
649
|
+
fontSize: 12,
|
|
650
|
+
lineHeight: 1.5,
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
const subtleChipStyle: CSSProperties = {
|
|
654
|
+
background: "rgba(255, 255, 255, 0.04)",
|
|
655
|
+
color: GRAPH_THEME.ui.text.body,
|
|
656
|
+
padding: "4px 8px",
|
|
657
|
+
borderRadius: 999,
|
|
658
|
+
fontSize: 11,
|
|
659
|
+
border: `1px solid ${GRAPH_THEME.ui.surface.panelBorder}`,
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
const sectionStyle: CSSProperties = {
|
|
663
|
+
display: "flex",
|
|
664
|
+
flexDirection: "column",
|
|
665
|
+
gap: 10,
|
|
666
|
+
padding: 14,
|
|
667
|
+
background: GRAPH_THEME.ui.surface.cardSubtle,
|
|
668
|
+
border: `1px solid ${GRAPH_THEME.ui.surface.panelBorder}`,
|
|
669
|
+
borderRadius: 14,
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
const sectionTitleStyle: CSSProperties = {
|
|
673
|
+
color: GRAPH_THEME.ui.text.muted,
|
|
674
|
+
fontSize: 11,
|
|
675
|
+
fontWeight: 700,
|
|
676
|
+
textTransform: "uppercase",
|
|
677
|
+
letterSpacing: "0.08em",
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
const pathFlowContainerStyle: CSSProperties = {
|
|
681
|
+
display: "flex",
|
|
682
|
+
alignItems: "center",
|
|
683
|
+
gap: 0,
|
|
684
|
+
flexWrap: "wrap",
|
|
685
|
+
rowGap: 8,
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
const pathNodeChipStyle: CSSProperties = {
|
|
689
|
+
display: "inline-flex",
|
|
690
|
+
alignItems: "center",
|
|
691
|
+
gap: 6,
|
|
692
|
+
padding: "5px 10px",
|
|
693
|
+
borderRadius: 999,
|
|
694
|
+
background: "rgba(98, 226, 205, 0.08)",
|
|
695
|
+
border: `1px solid ${GRAPH_THEME.ui.control.activeBorder}`,
|
|
696
|
+
color: GRAPH_THEME.ui.text.strong,
|
|
697
|
+
fontSize: 12,
|
|
698
|
+
fontWeight: 600,
|
|
699
|
+
maxWidth: 160,
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
const pathNodeIndexStyle: CSSProperties = {
|
|
703
|
+
display: "inline-flex",
|
|
704
|
+
alignItems: "center",
|
|
705
|
+
justifyContent: "center",
|
|
706
|
+
width: 16,
|
|
707
|
+
height: 16,
|
|
708
|
+
borderRadius: "50%",
|
|
709
|
+
background: GRAPH_THEME.ui.timeline.playheadSoft,
|
|
710
|
+
color: GRAPH_THEME.ui.timeline.playhead,
|
|
711
|
+
fontSize: 9,
|
|
712
|
+
fontWeight: 800,
|
|
713
|
+
flexShrink: 0,
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
const pathEdgeConnectorStyle: CSSProperties = {
|
|
717
|
+
display: "inline-flex",
|
|
718
|
+
alignItems: "center",
|
|
719
|
+
gap: 2,
|
|
720
|
+
flexShrink: 0,
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
const pathEdgeLabelStyle: CSSProperties = {
|
|
724
|
+
fontSize: 9,
|
|
725
|
+
fontWeight: 700,
|
|
726
|
+
color: GRAPH_THEME.ui.text.subtle,
|
|
727
|
+
letterSpacing: "0.04em",
|
|
728
|
+
textTransform: "uppercase",
|
|
729
|
+
maxWidth: 70,
|
|
730
|
+
overflow: "hidden",
|
|
731
|
+
textOverflow: "ellipsis",
|
|
732
|
+
whiteSpace: "nowrap",
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
const metricCardStyle: CSSProperties = {
|
|
736
|
+
background: "rgba(0,0,0,0.18)",
|
|
737
|
+
borderRadius: 8,
|
|
738
|
+
padding: "8px 10px",
|
|
739
|
+
border: "1px solid rgba(255,255,255,0.05)",
|
|
740
|
+
display: "flex",
|
|
741
|
+
flexDirection: "column",
|
|
742
|
+
gap: 3,
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
const metricLabelStyle: CSSProperties = {
|
|
746
|
+
color: "rgba(255,45,75,0.65)",
|
|
747
|
+
fontSize: 10,
|
|
748
|
+
fontWeight: 700,
|
|
749
|
+
letterSpacing: "0.06em",
|
|
750
|
+
textTransform: "uppercase",
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
const metricValueStyle: CSSProperties = {
|
|
754
|
+
fontSize: 14,
|
|
755
|
+
fontWeight: 700,
|
|
756
|
+
color: "#ffeaed",
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
const metricBarTrackStyle: CSSProperties = {
|
|
760
|
+
height: 3,
|
|
761
|
+
borderRadius: 999,
|
|
762
|
+
background: "rgba(255,255,255,0.07)",
|
|
763
|
+
overflow: "hidden",
|
|
764
|
+
marginTop: 4,
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
const metricBarFillStyle: CSSProperties = {
|
|
768
|
+
height: "100%",
|
|
769
|
+
borderRadius: 999,
|
|
770
|
+
transition: "width 300ms ease",
|
|
771
|
+
};
|