@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.
Files changed (146) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1674 -0
  3. package/explorer/README.md +282 -0
  4. package/explorer/src/App.css +0 -0
  5. package/explorer/src/App.tsx +2035 -0
  6. package/explorer/src/ErrorBoundary.tsx +112 -0
  7. package/explorer/src/ExploreWorkspaceTabs.tsx +29 -0
  8. package/explorer/src/assets/hero.png +0 -0
  9. package/explorer/src/assets/react.svg +1 -0
  10. package/explorer/src/assets/vite.svg +1 -0
  11. package/explorer/src/explorerCapabilities.ts +24 -0
  12. package/explorer/src/index.css +71 -0
  13. package/explorer/src/main.tsx +7 -0
  14. package/explorer/src/store/edgePairKeys.d.ts +2 -0
  15. package/explorer/src/store/edgePairKeys.js +7 -0
  16. package/explorer/src/store/graphStore.ts +183 -0
  17. package/explorer/src/store/registryStore.ts +78 -0
  18. package/explorer/src/types.d.ts +16 -0
  19. package/explorer/src/ui/primitives.css +414 -0
  20. package/explorer/src/ui/primitives.tsx +170 -0
  21. package/explorer/src/workspaces/DecisionWorkspace/DecisionWorkspace.tsx +279 -0
  22. package/explorer/src/workspaces/DiffMergeWorkspace/DiffMergeWorkspace.tsx +121 -0
  23. package/explorer/src/workspaces/EnrichWorkspace/EntityResolutionTab.tsx +454 -0
  24. package/explorer/src/workspaces/EnrichWorkspace/RegistryTab.tsx +285 -0
  25. package/explorer/src/workspaces/GraphWorkspace/GraphCanvas.tsx +2673 -0
  26. package/explorer/src/workspaces/GraphWorkspace/GraphInspectorPanel.tsx +771 -0
  27. package/explorer/src/workspaces/GraphWorkspace/GraphLoadingOverlay.tsx +410 -0
  28. package/explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx +3584 -0
  29. package/explorer/src/workspaces/GraphWorkspace/MarkdownContentViewer.tsx +705 -0
  30. package/explorer/src/workspaces/GraphWorkspace/SigmaSceneAdapter.tsx +55 -0
  31. package/explorer/src/workspaces/GraphWorkspace/TimelinePanel.tsx +193 -0
  32. package/explorer/src/workspaces/GraphWorkspace/behaviors/clickSelectionBehavior.ts +29 -0
  33. package/explorer/src/workspaces/GraphWorkspace/behaviors/fitViewBehavior.ts +15 -0
  34. package/explorer/src/workspaces/GraphWorkspace/behaviors/focusCameraBehavior.ts +25 -0
  35. package/explorer/src/workspaces/GraphWorkspace/behaviors/hoverActivationBehavior.ts +15 -0
  36. package/explorer/src/workspaces/GraphWorkspace/behaviors/pathHighlightBehavior.ts +52 -0
  37. package/explorer/src/workspaces/GraphWorkspace/behaviors/searchFocusBehavior.ts +36 -0
  38. package/explorer/src/workspaces/GraphWorkspace/behaviors/types.ts +41 -0
  39. package/explorer/src/workspaces/GraphWorkspace/behaviors/viewModeSwitchBehavior.ts +29 -0
  40. package/explorer/src/workspaces/GraphWorkspace/graphAnalytics.ts +779 -0
  41. package/explorer/src/workspaces/GraphWorkspace/graphColorLegend.ts +36 -0
  42. package/explorer/src/workspaces/GraphWorkspace/graphConfig.ts +102 -0
  43. package/explorer/src/workspaces/GraphWorkspace/graphEntityShape.ts +34 -0
  44. package/explorer/src/workspaces/GraphWorkspace/graphLoading.ts +84 -0
  45. package/explorer/src/workspaces/GraphWorkspace/graphSceneLayers.ts +775 -0
  46. package/explorer/src/workspaces/GraphWorkspace/graphSceneState.ts +2753 -0
  47. package/explorer/src/workspaces/GraphWorkspace/graphStructureLayer.ts +335 -0
  48. package/explorer/src/workspaces/GraphWorkspace/graphTheme.ts +1078 -0
  49. package/explorer/src/workspaces/GraphWorkspace/graphViewCopy.ts +41 -0
  50. package/explorer/src/workspaces/GraphWorkspace/localGraphTransition.ts +9 -0
  51. package/explorer/src/workspaces/GraphWorkspace/markdownEditorState.ts +114 -0
  52. package/explorer/src/workspaces/GraphWorkspace/markdownResourceClient.ts +103 -0
  53. package/explorer/src/workspaces/GraphWorkspace/markdownUrlSafety.ts +29 -0
  54. package/explorer/src/workspaces/GraphWorkspace/nodeMarkdownSync.ts +81 -0
  55. package/explorer/src/workspaces/GraphWorkspace/pluginRegistryPredicates.ts +24 -0
  56. package/explorer/src/workspaces/GraphWorkspace/plugins/explorationEffectsPlugin.tsx +353 -0
  57. package/explorer/src/workspaces/GraphWorkspace/plugins/explorationEffectsPluginPhaseC.tsx +522 -0
  58. package/explorer/src/workspaces/GraphWorkspace/plugins/index.ts +16 -0
  59. package/explorer/src/workspaces/GraphWorkspace/plugins/legendPlugin.tsx +120 -0
  60. package/explorer/src/workspaces/GraphWorkspace/plugins/neighborhoodPanelPlugin.tsx +231 -0
  61. package/explorer/src/workspaces/GraphWorkspace/plugins/temporalOverlayPlugin.tsx +142 -0
  62. package/explorer/src/workspaces/GraphWorkspace/plugins/types.ts +106 -0
  63. package/explorer/src/workspaces/GraphWorkspace/realtimeGraphAttributes.ts +50 -0
  64. package/explorer/src/workspaces/GraphWorkspace/scene.ts +79 -0
  65. package/explorer/src/workspaces/GraphWorkspace/sigmaNativeRendering.ts +421 -0
  66. package/explorer/src/workspaces/GraphWorkspace/smallGraphLayout.ts +135 -0
  67. package/explorer/src/workspaces/GraphWorkspace/temporalLifecyclePredicates.ts +31 -0
  68. package/explorer/src/workspaces/GraphWorkspace/temporalScrubberBounds.ts +46 -0
  69. package/explorer/src/workspaces/GraphWorkspace/temporalSnapshotGuards.ts +113 -0
  70. package/explorer/src/workspaces/GraphWorkspace/types.ts +395 -0
  71. package/explorer/src/workspaces/GraphWorkspace/useLoadGraph.ts +746 -0
  72. package/explorer/src/workspaces/GraphWorkspace/useMarkdownEditor.ts +177 -0
  73. package/explorer/src/workspaces/ImportExportWorkspace/ImportExportWorkspace.tsx +196 -0
  74. package/explorer/src/workspaces/LineageWorkspace/LineageDiagram.tsx +177 -0
  75. package/explorer/src/workspaces/ManageWorkspace/KGOverviewTab.tsx +291 -0
  76. package/explorer/src/workspaces/ManageWorkspace/OntologySummaryTab.tsx +346 -0
  77. package/explorer/src/workspaces/MemoryWorkspace.tsx +464 -0
  78. package/explorer/src/workspaces/OntologyWorkspace/AlignmentsTab.tsx +437 -0
  79. package/explorer/src/workspaces/OntologyWorkspace/HealthTab.tsx +214 -0
  80. package/explorer/src/workspaces/OntologyWorkspace/OntologyEditor.tsx +770 -0
  81. package/explorer/src/workspaces/OntologyWorkspace/OntologyLoader.tsx +913 -0
  82. package/explorer/src/workspaces/OntologyWorkspace/OntologyManager.tsx +930 -0
  83. package/explorer/src/workspaces/OntologyWorkspace/OntologySearch.tsx +590 -0
  84. package/explorer/src/workspaces/OntologyWorkspace/ProposalReview.tsx +429 -0
  85. package/explorer/src/workspaces/OntologyWorkspace/SKOSVocabularyManager.tsx +696 -0
  86. package/explorer/src/workspaces/OntologyWorkspace/ShaclStudio.tsx +363 -0
  87. package/explorer/src/workspaces/OntologyWorkspace/VersionsTab.tsx +546 -0
  88. package/explorer/src/workspaces/OntologyWorkspace/api.ts +164 -0
  89. package/explorer/src/workspaces/OntologyWorkspace/index.tsx +101 -0
  90. package/explorer/src/workspaces/OntologyWorkspace/ontologyEditorModel.ts +87 -0
  91. package/explorer/src/workspaces/OntologyWorkspace/ontologyUrlState.ts +94 -0
  92. package/explorer/src/workspaces/OntologyWorkspace/types.ts +119 -0
  93. package/explorer/src/workspaces/ReasoningWorkspace.tsx +243 -0
  94. package/explorer/src/workspaces/SparqlWorkspace/SparqlWorkspace.tsx +271 -0
  95. package/explorer/src/workspaces/VocabularyWorkspace/ConceptTree.tsx +97 -0
  96. package/explorer/src/workspaces/VocabularyWorkspace/ImportDropzone.tsx +95 -0
  97. package/explorer/src/workspaces/VocabularyWorkspace/PropertyPanel.tsx +116 -0
  98. package/explorer/src/workspaces/VocabularyWorkspace/Sidebar.tsx +112 -0
  99. package/explorer/src/workspaces/VocabularyWorkspace/VocabularyWorkspace.tsx +70 -0
  100. package/explorer/src/workspaces/VocabularyWorkspace/queries.ts +52 -0
  101. package/explorer/src/workspaces/VocabularyWorkspace/types.ts +24 -0
  102. package/package.json +31 -0
  103. package/plugins/.claude-plugin/README.md +262 -0
  104. package/plugins/.claude-plugin/marketplace.json +20 -0
  105. package/plugins/.claude-plugin/plugin.json +33 -0
  106. package/plugins/.cline-plugin/README.md +32 -0
  107. package/plugins/.cline-plugin/marketplace.json +17 -0
  108. package/plugins/.cline-plugin/plugin.json +35 -0
  109. package/plugins/.codex-plugin/marketplace.json +21 -0
  110. package/plugins/.codex-plugin/plugin.json +35 -0
  111. package/plugins/.continue-plugin/README.md +34 -0
  112. package/plugins/.continue-plugin/marketplace.json +17 -0
  113. package/plugins/.continue-plugin/plugin.json +33 -0
  114. package/plugins/.cursor-plugin/marketplace.json +18 -0
  115. package/plugins/.cursor-plugin/plugin.json +32 -0
  116. package/plugins/.openclaw-plugin/README.md +62 -0
  117. package/plugins/.openclaw-plugin/marketplace.json +18 -0
  118. package/plugins/.openclaw-plugin/plugin.json +46 -0
  119. package/plugins/.pi-plugin/README.md +35 -0
  120. package/plugins/.vscode-plugin/README.md +48 -0
  121. package/plugins/.vscode-plugin/marketplace.json +17 -0
  122. package/plugins/.vscode-plugin/plugin.json +35 -0
  123. package/plugins/.windsurf-plugin/README.md +39 -0
  124. package/plugins/.windsurf-plugin/marketplace.json +17 -0
  125. package/plugins/.windsurf-plugin/plugin.json +35 -0
  126. package/plugins/agents/decision-advisor.md +126 -0
  127. package/plugins/agents/explainability.md +129 -0
  128. package/plugins/agents/kg-assistant.md +73 -0
  129. package/plugins/hooks/hooks.json +12 -0
  130. package/plugins/skills/causal/SKILL.md +74 -0
  131. package/plugins/skills/change/SKILL.md +80 -0
  132. package/plugins/skills/decision/SKILL.md +197 -0
  133. package/plugins/skills/deduplicate/SKILL.md +41 -0
  134. package/plugins/skills/embed/SKILL.md +230 -0
  135. package/plugins/skills/explain/SKILL.md +44 -0
  136. package/plugins/skills/export/SKILL.md +67 -0
  137. package/plugins/skills/extract/SKILL.md +93 -0
  138. package/plugins/skills/ingest/SKILL.md +38 -0
  139. package/plugins/skills/ontology/SKILL.md +89 -0
  140. package/plugins/skills/policy/SKILL.md +66 -0
  141. package/plugins/skills/provenance/SKILL.md +70 -0
  142. package/plugins/skills/query/SKILL.md +84 -0
  143. package/plugins/skills/reason/SKILL.md +201 -0
  144. package/plugins/skills/temporal/SKILL.md +164 -0
  145. package/plugins/skills/validate/SKILL.md +228 -0
  146. package/plugins/skills/visualize/SKILL.md +249 -0
@@ -0,0 +1,590 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import {
3
+ AlertCircle,
4
+ BookOpen,
5
+ ChevronDown,
6
+ ChevronRight,
7
+ ExternalLink,
8
+ Loader2,
9
+ Search,
10
+ X,
11
+ } from "lucide-react";
12
+
13
+ interface SearchResult {
14
+ uri: string;
15
+ label: string;
16
+ type: string;
17
+ entity_type: string;
18
+ definition?: string;
19
+ source_ontology?: string;
20
+ namespace_prefix?: string;
21
+ }
22
+
23
+ interface EntityDetail {
24
+ uri: string;
25
+ label: string;
26
+ type: string;
27
+ entity_type: string;
28
+ definition?: string;
29
+ source_ontology?: string;
30
+ superclasses: string[];
31
+ subclasses: string[];
32
+ domain: string[];
33
+ range: string[];
34
+ instance_count: number;
35
+ properties: Record<string, unknown>;
36
+ }
37
+
38
+ const ENTITY_TYPE_COLORS: Record<string, string> = {
39
+ class: "#d2a8ff",
40
+ property: "#ff7a45",
41
+ individual: "#9ee8d7",
42
+ concept: "#ff2d4b",
43
+ scheme: "#ff6b81",
44
+ unknown: "#9c7a82",
45
+ };
46
+
47
+ const ENTITY_TYPE_LABELS: Record<string, string> = {
48
+ class: "Class",
49
+ property: "Property",
50
+ individual: "Individual",
51
+ concept: "Concept",
52
+ scheme: "Scheme",
53
+ unknown: "Entity",
54
+ };
55
+
56
+ function TypeBadge({ entityType }: { entityType: string }) {
57
+ const color = ENTITY_TYPE_COLORS[entityType] || ENTITY_TYPE_COLORS.unknown;
58
+ return (
59
+ <span
60
+ style={{
61
+ padding: "1px 7px",
62
+ borderRadius: 999,
63
+ fontSize: 10,
64
+ fontWeight: 700,
65
+ letterSpacing: "0.06em",
66
+ textTransform: "uppercase" as const,
67
+ background: `${color}14`,
68
+ border: `1px solid ${color}28`,
69
+ color,
70
+ flexShrink: 0,
71
+ }}
72
+ >
73
+ {ENTITY_TYPE_LABELS[entityType] || entityType}
74
+ </span>
75
+ );
76
+ }
77
+
78
+ function UriRef({ uri }: { uri: string }) {
79
+ const short = uri.includes("#")
80
+ ? uri.split("#").pop() || uri
81
+ : uri.split("/").pop() || uri;
82
+ return (
83
+ <span
84
+ title={uri}
85
+ style={{ color: "#ff2d4b", fontSize: 11, fontFamily: "monospace", cursor: "help" }}
86
+ >
87
+ {short}
88
+ </span>
89
+ );
90
+ }
91
+
92
+ function ResultRow({
93
+ result,
94
+ selected,
95
+ onSelect,
96
+ }: {
97
+ result: SearchResult;
98
+ selected: boolean;
99
+ onSelect: () => void;
100
+ }) {
101
+ return (
102
+ <div
103
+ onClick={onSelect}
104
+ style={{
105
+ display: "flex",
106
+ flexDirection: "column",
107
+ gap: 4,
108
+ padding: "10px 14px",
109
+ borderRadius: 10,
110
+ border: "1px solid",
111
+ cursor: "pointer",
112
+ transition: "160ms ease",
113
+ background: selected ? "rgba(255,45,75,0.1)" : "rgba(255,255,255,0.02)",
114
+ borderColor: selected ? "rgba(255,107,129,0.24)" : "rgba(255,107,129,0.08)",
115
+ }}
116
+ >
117
+ <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
118
+ <span style={{ color: "#fff0f2", fontSize: 13, fontWeight: 700, flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
119
+ {result.label || result.uri}
120
+ </span>
121
+ <TypeBadge entityType={result.entity_type} />
122
+ </div>
123
+ <div style={{ color: "#9c7a82", fontSize: 10, fontFamily: "monospace", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
124
+ {result.uri}
125
+ </div>
126
+ {result.definition && (
127
+ <div style={{ color: "#c99aa3", fontSize: 12, lineHeight: 1.4, overflow: "hidden", display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical" as const }}>
128
+ {result.definition}
129
+ </div>
130
+ )}
131
+ {result.source_ontology && (
132
+ <div style={{ color: "#8f6d75", fontSize: 10 }}>
133
+ From: {result.source_ontology}
134
+ </div>
135
+ )}
136
+ </div>
137
+ );
138
+ }
139
+
140
+ function CollapsibleList({ label, items }: { label: string; items: string[] }) {
141
+ const [open, setOpen] = useState(false);
142
+ if (!items.length) return null;
143
+ return (
144
+ <div>
145
+ <button
146
+ onClick={() => setOpen((v) => !v)}
147
+ style={collapseHdrStyle}
148
+ >
149
+ {open ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
150
+ <span>{label}</span>
151
+ <span style={{ color: "#9c7a82", fontSize: 10 }}>({items.length})</span>
152
+ </button>
153
+ {open && (
154
+ <div style={{ marginLeft: 16, marginTop: 4, display: "flex", flexDirection: "column", gap: 3 }}>
155
+ {items.slice(0, 12).map((uri) => (
156
+ <div key={uri} style={{ display: "flex", alignItems: "center", gap: 6 }}>
157
+ <span style={{ color: "#9c7a82", fontSize: 10 }}>→</span>
158
+ <UriRef uri={uri} />
159
+ </div>
160
+ ))}
161
+ {items.length > 12 && (
162
+ <span style={{ color: "#8f6d75", fontSize: 10 }}>+{items.length - 12} more</span>
163
+ )}
164
+ </div>
165
+ )}
166
+ </div>
167
+ );
168
+ }
169
+
170
+ function DetailPanel({
171
+ uri,
172
+ onClose,
173
+ }: {
174
+ uri: string;
175
+ onClose: () => void;
176
+ }) {
177
+ const [detail, setDetail] = useState<EntityDetail | null>(null);
178
+ const [loading, setLoading] = useState(true);
179
+ const [error, setError] = useState("");
180
+
181
+ const [prevUri, setPrevUri] = useState(uri);
182
+ if (uri !== prevUri) {
183
+ setPrevUri(uri);
184
+ setLoading(true);
185
+ setError("");
186
+ setDetail(null);
187
+ }
188
+
189
+ useEffect(() => {
190
+ let ignore = false;
191
+ fetch(`/api/ontology/entity/${encodeURIComponent(uri)}`)
192
+ .then(async (r) => {
193
+ if (!r.ok) throw new Error("Not found");
194
+ const data = await r.json();
195
+ if (r.status === 207) setError(data.message || "Warning: Partial success loading entity.");
196
+ return data;
197
+ })
198
+ .then((data) => {
199
+ if (!ignore) setDetail(data);
200
+ })
201
+ .catch((e) => {
202
+ if (!ignore) setError(e.message);
203
+ })
204
+ .finally(() => {
205
+ if (!ignore) setLoading(false);
206
+ });
207
+ return () => { ignore = true; };
208
+ }, [uri]);
209
+
210
+ return (
211
+ <div style={detailPanelStyle}>
212
+ <div style={detailHeaderStyle}>
213
+ <div style={{ display: "flex", alignItems: "center", gap: 8, minWidth: 0 }}>
214
+ <BookOpen size={14} color="#d2a8ff" />
215
+ <span style={{ color: "#fff0f2", fontSize: 13, fontWeight: 700, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
216
+ Entity Detail
217
+ </span>
218
+ </div>
219
+ <button onClick={onClose} style={closeDetailBtnStyle}>
220
+ <X size={14} />
221
+ </button>
222
+ </div>
223
+
224
+ {loading && (
225
+ <div style={centerStyle}>
226
+ <Loader2 size={18} color="#ff2d4b" style={{ animation: "spin 1s linear infinite" }} />
227
+ </div>
228
+ )}
229
+
230
+ {error && (
231
+ <div style={centerStyle}>
232
+ <AlertCircle size={16} color="#ff9daf" />
233
+ <span style={{ color: "#ff9daf", fontSize: 12, marginTop: 6 }}>{error}</span>
234
+ </div>
235
+ )}
236
+
237
+ {detail && !loading && (
238
+ <div style={detailBodyStyle}>
239
+ <div style={{ marginBottom: 14 }}>
240
+ <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", marginBottom: 4 }}>
241
+ <h3 style={{ margin: 0, color: "#fff0f2", fontSize: 17, fontWeight: 800, letterSpacing: "-0.03em" }}>
242
+ {detail.label || detail.uri.split("/").pop()}
243
+ </h3>
244
+ <TypeBadge entityType={detail.entity_type} />
245
+ </div>
246
+ <div style={{ color: "#8f6d75", fontSize: 10, fontFamily: "monospace", wordBreak: "break-all" }}>
247
+ {detail.uri}
248
+ </div>
249
+ </div>
250
+
251
+ {detail.definition && (
252
+ <DetailSection label="Definition">
253
+ <p style={{ margin: 0, color: "#f0cdd3", fontSize: 13, lineHeight: 1.6 }}>
254
+ {detail.definition}
255
+ </p>
256
+ </DetailSection>
257
+ )}
258
+
259
+ {detail.instance_count > 0 && (
260
+ <DetailSection label="Instances">
261
+ <span style={{ color: "#9ee8d7", fontSize: 14, fontWeight: 800 }}>
262
+ {detail.instance_count.toLocaleString()}
263
+ </span>
264
+ </DetailSection>
265
+ )}
266
+
267
+ <CollapsibleList label="Superclasses / Broader" items={detail.superclasses} />
268
+ <CollapsibleList label="Subclasses / Narrower" items={detail.subclasses} />
269
+ <CollapsibleList label="Domain" items={detail.domain} />
270
+ <CollapsibleList label="Range" items={detail.range} />
271
+
272
+ {detail.source_ontology && (
273
+ <DetailSection label="Source Ontology">
274
+ <span style={{ color: "#f0cdd3", fontSize: 12, fontFamily: "monospace" }}>
275
+ {detail.source_ontology}
276
+ </span>
277
+ </DetailSection>
278
+ )}
279
+
280
+ <a
281
+ href={detail.uri}
282
+ target="_blank"
283
+ rel="noreferrer"
284
+ style={openUriStyle}
285
+ >
286
+ <ExternalLink size={11} />
287
+ Open URI
288
+ </a>
289
+ </div>
290
+ )}
291
+ </div>
292
+ );
293
+ }
294
+
295
+ function DetailSection({ label, children }: { label: string; children: React.ReactNode }) {
296
+ return (
297
+ <div style={{ paddingTop: 10, borderTop: "1px solid rgba(255,255,255,0.05)", marginTop: 10 }}>
298
+ <div style={{ color: "#9c7a82", fontSize: 10, fontWeight: 700, textTransform: "uppercase" as const, letterSpacing: "0.07em", marginBottom: 5 }}>
299
+ {label}
300
+ </div>
301
+ {children}
302
+ </div>
303
+ );
304
+ }
305
+
306
+ // ---------------------------------------------------------------------------
307
+ // Main OntologySearch component
308
+ // ---------------------------------------------------------------------------
309
+
310
+ export function OntologySearch() {
311
+ const [query, setQuery] = useState("");
312
+ const [entityType, setEntityType] = useState<string>("all");
313
+ const [results, setResults] = useState<SearchResult[]>([]);
314
+ const [searching, setSearching] = useState(false);
315
+ const [selectedUri, setSelectedUri] = useState<string | null>(null);
316
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
317
+
318
+ const runSearch = async (q: string, type: string) => {
319
+ if (!q.trim()) {
320
+ setResults([]);
321
+ return;
322
+ }
323
+ setSearching(true);
324
+ try {
325
+ const params = new URLSearchParams({ q: q.trim(), limit: "80" });
326
+ if (type !== "all") params.set("entity_type", type);
327
+ const res = await fetch(`/api/ontology/search?${params}`);
328
+ if (!res.ok) throw new Error("Search failed");
329
+ setResults(await res.json());
330
+ } catch {
331
+ setResults([]);
332
+ } finally {
333
+ setSearching(false);
334
+ }
335
+ };
336
+
337
+ useEffect(() => {
338
+ if (timerRef.current) clearTimeout(timerRef.current);
339
+ timerRef.current = setTimeout(() => runSearch(query, entityType), 320);
340
+ return () => { if (timerRef.current) clearTimeout(timerRef.current); };
341
+ }, [query, entityType]);
342
+
343
+ return (
344
+ <div style={searchShellStyle}>
345
+ {/* Search input */}
346
+ <div style={searchTopStyle}>
347
+ <div style={searchBarStyle}>
348
+ <Search size={14} color="#9c7a82" />
349
+ <input
350
+ autoFocus
351
+ value={query}
352
+ onChange={(e) => setQuery(e.target.value)}
353
+ placeholder="Search classes, properties, concepts…"
354
+ style={searchInputStyle}
355
+ />
356
+ {searching && <Loader2 size={13} color="#ff2d4b" style={{ animation: "spin 0.8s linear infinite", flexShrink: 0 }} />}
357
+ {query && !searching && (
358
+ <button onClick={() => { setQuery(""); setResults([]); }} style={clearBtnStyle}>
359
+ <X size={12} />
360
+ </button>
361
+ )}
362
+ </div>
363
+
364
+ <div style={typeFilterStyle}>
365
+ {(["all", "class", "property", "individual", "concept", "scheme"] as const).map((t) => (
366
+ <button
367
+ key={t}
368
+ onClick={() => setEntityType(t)}
369
+ style={{
370
+ ...typeFilterBtnBase,
371
+ ...(entityType === t ? typeFilterBtnActive : typeFilterBtnIdle),
372
+ }}
373
+ >
374
+ {t === "all" ? "All" : ENTITY_TYPE_LABELS[t] || t}
375
+ </button>
376
+ ))}
377
+ </div>
378
+ </div>
379
+
380
+ {/* Results + detail */}
381
+ <div style={searchBodyStyle}>
382
+ <div style={resultListStyle}>
383
+ {!query && (
384
+ <div style={hintStyle}>
385
+ <Search size={20} color="rgba(255,45,75,0.2)" />
386
+ <span style={{ color: "#9c7a82", fontSize: 12, marginTop: 8 }}>
387
+ Type to search across all loaded ontologies
388
+ </span>
389
+ </div>
390
+ )}
391
+
392
+ {query && results.length === 0 && !searching && (
393
+ <div style={hintStyle}>
394
+ <span style={{ color: "#9c7a82", fontSize: 12 }}>No results for "{query}"</span>
395
+ </div>
396
+ )}
397
+
398
+ {results.length > 0 && (
399
+ <div style={{ padding: "10px 12px", display: "flex", flexDirection: "column", gap: 6 }}>
400
+ <div style={{ color: "#9c7a82", fontSize: 11, fontWeight: 700, marginBottom: 2 }}>
401
+ {results.length} result{results.length !== 1 ? "s" : ""}
402
+ </div>
403
+ {results.map((r) => (
404
+ <ResultRow
405
+ key={r.uri}
406
+ result={r}
407
+ selected={selectedUri === r.uri}
408
+ onSelect={() => setSelectedUri((prev) => (prev === r.uri ? null : r.uri))}
409
+ />
410
+ ))}
411
+ </div>
412
+ )}
413
+ </div>
414
+
415
+ {selectedUri && (
416
+ <DetailPanel uri={selectedUri} onClose={() => setSelectedUri(null)} />
417
+ )}
418
+ </div>
419
+ </div>
420
+ );
421
+ }
422
+
423
+ /* ─── styles ─────────────────────────────────────────────────────────── */
424
+
425
+ const searchShellStyle: React.CSSProperties = {
426
+ display: "flex",
427
+ flexDirection: "column",
428
+ height: "100%",
429
+ overflow: "hidden",
430
+ };
431
+
432
+ const searchTopStyle: React.CSSProperties = {
433
+ padding: "12px 14px 10px",
434
+ borderBottom: "1px solid rgba(255,107,129,0.08)",
435
+ display: "flex",
436
+ flexDirection: "column",
437
+ gap: 8,
438
+ flexShrink: 0,
439
+ };
440
+
441
+ const searchBarStyle: React.CSSProperties = {
442
+ display: "flex",
443
+ alignItems: "center",
444
+ gap: 8,
445
+ padding: "7px 12px",
446
+ borderRadius: 10,
447
+ border: "1px solid rgba(255,107,129,0.14)",
448
+ background: "rgba(0,0,0,0.24)",
449
+ };
450
+
451
+ const searchInputStyle: React.CSSProperties = {
452
+ flex: 1,
453
+ background: "transparent",
454
+ border: "none",
455
+ outline: "none",
456
+ color: "#fff0f2",
457
+ fontSize: 13,
458
+ };
459
+
460
+ const clearBtnStyle: React.CSSProperties = {
461
+ background: "transparent",
462
+ border: "none",
463
+ color: "#9c7a82",
464
+ cursor: "pointer",
465
+ padding: 2,
466
+ display: "grid",
467
+ placeItems: "center",
468
+ };
469
+
470
+ const typeFilterStyle: React.CSSProperties = {
471
+ display: "flex",
472
+ gap: 5,
473
+ flexWrap: "wrap",
474
+ };
475
+
476
+ const typeFilterBtnBase: React.CSSProperties = {
477
+ padding: "4px 10px",
478
+ borderRadius: 999,
479
+ border: "1px solid transparent",
480
+ fontSize: 11,
481
+ fontWeight: 600,
482
+ cursor: "pointer",
483
+ transition: "160ms ease",
484
+ };
485
+
486
+ const typeFilterBtnIdle: React.CSSProperties = {
487
+ background: "transparent",
488
+ color: "#c99aa3",
489
+ borderColor: "rgba(255,107,129,0.1)",
490
+ };
491
+
492
+ const typeFilterBtnActive: React.CSSProperties = {
493
+ background: "rgba(255,45,75,0.14)",
494
+ color: "#fff0f2",
495
+ borderColor: "rgba(255,107,129,0.26)",
496
+ };
497
+
498
+ const searchBodyStyle: React.CSSProperties = {
499
+ flex: 1,
500
+ minHeight: 0,
501
+ display: "flex",
502
+ overflow: "hidden",
503
+ };
504
+
505
+ const resultListStyle: React.CSSProperties = {
506
+ flex: 1,
507
+ overflowY: "auto",
508
+ minWidth: 0,
509
+ };
510
+
511
+ const hintStyle: React.CSSProperties = {
512
+ display: "flex",
513
+ flexDirection: "column",
514
+ alignItems: "center",
515
+ justifyContent: "center",
516
+ height: "100%",
517
+ padding: 32,
518
+ };
519
+
520
+ const detailPanelStyle: React.CSSProperties = {
521
+ width: 320,
522
+ flexShrink: 0,
523
+ display: "flex",
524
+ flexDirection: "column",
525
+ borderLeft: "1px solid rgba(255,107,129,0.1)",
526
+ background: "rgba(6,3,5,0.5)",
527
+ overflow: "hidden",
528
+ };
529
+
530
+ const detailHeaderStyle: React.CSSProperties = {
531
+ display: "flex",
532
+ alignItems: "center",
533
+ justifyContent: "space-between",
534
+ padding: "12px 14px",
535
+ borderBottom: "1px solid rgba(255,107,129,0.08)",
536
+ flexShrink: 0,
537
+ };
538
+
539
+ const closeDetailBtnStyle: React.CSSProperties = {
540
+ background: "transparent",
541
+ border: "none",
542
+ color: "#c99aa3",
543
+ cursor: "pointer",
544
+ padding: 2,
545
+ display: "grid",
546
+ placeItems: "center",
547
+ };
548
+
549
+ const detailBodyStyle: React.CSSProperties = {
550
+ flex: 1,
551
+ overflowY: "auto",
552
+ padding: "14px",
553
+ display: "flex",
554
+ flexDirection: "column",
555
+ gap: 0,
556
+ };
557
+
558
+ const centerStyle: React.CSSProperties = {
559
+ display: "flex",
560
+ flexDirection: "column",
561
+ alignItems: "center",
562
+ justifyContent: "center",
563
+ height: "100%",
564
+ padding: 24,
565
+ };
566
+
567
+ const collapseHdrStyle: React.CSSProperties = {
568
+ display: "flex",
569
+ alignItems: "center",
570
+ gap: 5,
571
+ background: "transparent",
572
+ border: "none",
573
+ color: "#c99aa3",
574
+ fontSize: 12,
575
+ fontWeight: 600,
576
+ cursor: "pointer",
577
+ padding: "6px 0",
578
+ width: "100%",
579
+ textAlign: "left",
580
+ };
581
+
582
+ const openUriStyle: React.CSSProperties = {
583
+ display: "inline-flex",
584
+ alignItems: "center",
585
+ gap: 5,
586
+ marginTop: 14,
587
+ color: "#ff2d4b",
588
+ fontSize: 11,
589
+ textDecoration: "none",
590
+ };