@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,913 @@
1
+ import { useRef, useState } from "react";
2
+ import {
3
+ AlertCircle,
4
+ CheckCircle2,
5
+ ChevronDown,
6
+ FileUp,
7
+ Globe,
8
+ Loader2,
9
+ Plus,
10
+ X,
11
+ } from "lucide-react";
12
+
13
+ type LoaderMode = "url" | "file" | "create";
14
+ type CreateMode = "scratch" | "data" | "text";
15
+
16
+ interface OntologyPreview {
17
+ uri: string;
18
+ name: string;
19
+ description?: string;
20
+ namespace?: string;
21
+ version?: string;
22
+ license?: string;
23
+ format: string;
24
+ estimated_triples: number;
25
+ source_url?: string;
26
+ }
27
+
28
+ interface LoaderProps {
29
+ onLoaded: () => void;
30
+ onClose: () => void;
31
+ }
32
+
33
+ function Badge({ label, color }: { label: string; color: string }) {
34
+ return (
35
+ <span
36
+ style={{
37
+ padding: "2px 8px",
38
+ borderRadius: 999,
39
+ fontSize: 10,
40
+ fontWeight: 700,
41
+ letterSpacing: "0.07em",
42
+ textTransform: "uppercase" as const,
43
+ background: `${color}18`,
44
+ border: `1px solid ${color}33`,
45
+ color,
46
+ }}
47
+ >
48
+ {label}
49
+ </span>
50
+ );
51
+ }
52
+
53
+ function FieldGroup({
54
+ label,
55
+ children,
56
+ }: {
57
+ label: string;
58
+ children: React.ReactNode;
59
+ }) {
60
+ return (
61
+ <div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
62
+ <label style={fieldLabelStyle}>{label}</label>
63
+ {children}
64
+ </div>
65
+ );
66
+ }
67
+
68
+ function Input({
69
+ value,
70
+ onChange,
71
+ placeholder,
72
+ type = "text",
73
+ }: {
74
+ value: string;
75
+ onChange: (v: string) => void;
76
+ placeholder?: string;
77
+ type?: string;
78
+ }) {
79
+ return (
80
+ <input
81
+ type={type}
82
+ value={value}
83
+ onChange={(e) => onChange(e.target.value)}
84
+ placeholder={placeholder}
85
+ style={inputStyle}
86
+ />
87
+ );
88
+ }
89
+
90
+ function Textarea({
91
+ value,
92
+ onChange,
93
+ placeholder,
94
+ rows = 5,
95
+ }: {
96
+ value: string;
97
+ onChange: (v: string) => void;
98
+ placeholder?: string;
99
+ rows?: number;
100
+ }) {
101
+ return (
102
+ <textarea
103
+ value={value}
104
+ onChange={(e) => onChange(e.target.value)}
105
+ placeholder={placeholder}
106
+ rows={rows}
107
+ style={{ ...inputStyle, resize: "vertical", fontFamily: "monospace" }}
108
+ />
109
+ );
110
+ }
111
+
112
+ function PreviewCard({ preview }: { preview: OntologyPreview }) {
113
+ return (
114
+ <div style={previewCardStyle}>
115
+ <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
116
+ <CheckCircle2 size={16} color="#4cc38a" />
117
+ <span style={{ color: "#4cc38a", fontSize: 12, fontWeight: 700 }}>
118
+ Preview ready
119
+ </span>
120
+ <Badge label={preview.format} color="#ff2d4b" />
121
+ </div>
122
+
123
+ <div style={previewTitleStyle}>{preview.name}</div>
124
+ {preview.description && (
125
+ <div style={{ color: "#c99aa3", fontSize: 12, marginTop: 6, lineHeight: 1.5 }}>
126
+ {preview.description}
127
+ </div>
128
+ )}
129
+
130
+ <div style={previewGridStyle}>
131
+ <PreviewRow label="Namespace" value={preview.namespace || preview.uri} mono />
132
+ {preview.version && <PreviewRow label="Version" value={preview.version} />}
133
+ {preview.license && <PreviewRow label="License" value={preview.license} />}
134
+ <PreviewRow
135
+ label="Estimated triples"
136
+ value={preview.estimated_triples.toLocaleString()}
137
+ />
138
+ </div>
139
+ </div>
140
+ );
141
+ }
142
+
143
+ function PreviewRow({
144
+ label,
145
+ value,
146
+ mono = false,
147
+ }: {
148
+ label: string;
149
+ value: string;
150
+ mono?: boolean;
151
+ }) {
152
+ return (
153
+ <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
154
+ <span style={{ color: "#9c7a82", fontSize: 10, fontWeight: 700, textTransform: "uppercase" as const, letterSpacing: "0.07em" }}>
155
+ {label}
156
+ </span>
157
+ <span
158
+ style={{
159
+ color: "#f0cdd3",
160
+ fontSize: 11,
161
+ fontFamily: mono ? "monospace" : undefined,
162
+ wordBreak: "break-all",
163
+ }}
164
+ >
165
+ {value}
166
+ </span>
167
+ </div>
168
+ );
169
+ }
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // URL Import panel
173
+ // ---------------------------------------------------------------------------
174
+
175
+ function URLImportPanel({ onLoaded }: { onLoaded: () => void }) {
176
+ const [url, setUrl] = useState("");
177
+ const [format, setFormat] = useState("");
178
+ const [customName, setCustomName] = useState("");
179
+ const [description, setDescription] = useState("");
180
+ const [tags, setTags] = useState("");
181
+ const [preview, setPreview] = useState<OntologyPreview | null>(null);
182
+ const [previewState, setPreviewState] = useState<"idle" | "loading" | "error">("idle");
183
+ const [loadState, setLoadState] = useState<"idle" | "loading" | "success" | "error">("idle");
184
+ const [errorMsg, setErrorMsg] = useState("");
185
+ const [showAdvanced, setShowAdvanced] = useState(false);
186
+
187
+ const handlePreview = async () => {
188
+ if (!url.trim()) return;
189
+ setPreviewState("loading");
190
+ setPreview(null);
191
+ setErrorMsg("");
192
+ try {
193
+ const res = await fetch("/api/ontology/preview", {
194
+ method: "POST",
195
+ headers: { "Content-Type": "application/json" },
196
+ body: JSON.stringify({ url: url.trim(), format: format || undefined }),
197
+ });
198
+ if (!res.ok) {
199
+ const err = await res.json().catch(() => ({ detail: "Unknown error" }));
200
+ throw new Error(err.detail || "Preview failed");
201
+ }
202
+ setPreview(await res.json());
203
+ setPreviewState("idle");
204
+ } catch (e) {
205
+ setPreviewState("error");
206
+ setErrorMsg(e instanceof Error ? e.message : "Could not fetch preview");
207
+ }
208
+ };
209
+
210
+ const handleLoad = async () => {
211
+ if (!url.trim()) return;
212
+ setLoadState("loading");
213
+ try {
214
+ const res = await fetch("/api/ontology/load", {
215
+ method: "POST",
216
+ headers: { "Content-Type": "application/json" },
217
+ body: JSON.stringify({
218
+ url: url.trim(),
219
+ format: format || undefined,
220
+ name: customName || undefined,
221
+ description: description || undefined,
222
+ tags: tags ? tags.split(",").map((t) => t.trim()).filter(Boolean) : [],
223
+ }),
224
+ });
225
+ if (!res.ok) {
226
+ const err = await res.json().catch(() => ({ detail: "Load failed" }));
227
+ throw new Error(err.detail || "Load failed");
228
+ }
229
+ setLoadState("success");
230
+ setTimeout(() => {
231
+ setLoadState("idle");
232
+ onLoaded();
233
+ }, 1200);
234
+ } catch (e) {
235
+ setLoadState("error");
236
+ setErrorMsg(e instanceof Error ? e.message : "Load failed");
237
+ }
238
+ };
239
+
240
+ return (
241
+ <div style={panelBodyStyle}>
242
+ <FieldGroup label="Ontology URL">
243
+ <div style={{ display: "flex", gap: 8 }}>
244
+ <input
245
+ type="url"
246
+ value={url}
247
+ onChange={(e) => {
248
+ setUrl(e.target.value);
249
+ setPreview(null);
250
+ setPreviewState("idle");
251
+ }}
252
+ placeholder="https://schema.org/version/latest/schema.ttl"
253
+ style={{ ...inputStyle, flex: 1 }}
254
+ />
255
+ <button
256
+ onClick={handlePreview}
257
+ disabled={!url.trim() || previewState === "loading"}
258
+ style={previewBtnStyle}
259
+ >
260
+ {previewState === "loading" ? (
261
+ <Loader2 size={13} style={{ animation: "spin 1s linear infinite" }} />
262
+ ) : (
263
+ "Fetch Preview"
264
+ )}
265
+ </button>
266
+ </div>
267
+ </FieldGroup>
268
+
269
+ {previewState === "error" && (
270
+ <div style={errorBoxStyle}>
271
+ <AlertCircle size={13} />
272
+ <span>{errorMsg}</span>
273
+ </div>
274
+ )}
275
+
276
+ {preview && <PreviewCard preview={preview} />}
277
+
278
+ <button
279
+ onClick={() => setShowAdvanced((v) => !v)}
280
+ style={advancedToggleStyle}
281
+ >
282
+ <ChevronDown
283
+ size={13}
284
+ style={{ transform: showAdvanced ? "rotate(180deg)" : undefined, transition: "200ms" }}
285
+ />
286
+ Advanced options
287
+ </button>
288
+
289
+ {showAdvanced && (
290
+ <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
291
+ <FieldGroup label="Format override">
292
+ <select
293
+ value={format}
294
+ onChange={(e) => setFormat(e.target.value)}
295
+ style={selectStyle}
296
+ >
297
+ <option value="">Auto-detect</option>
298
+ <option value="turtle">Turtle (.ttl)</option>
299
+ <option value="xml">RDF/XML (.rdf, .owl)</option>
300
+ <option value="nt">N-Triples (.nt)</option>
301
+ <option value="json-ld">JSON-LD (.jsonld)</option>
302
+ </select>
303
+ </FieldGroup>
304
+ <FieldGroup label="Custom display name">
305
+ <Input value={customName} onChange={setCustomName} placeholder="Leave blank to use ontology title" />
306
+ </FieldGroup>
307
+ <FieldGroup label="Description">
308
+ <Input value={description} onChange={setDescription} placeholder="Optional description" />
309
+ </FieldGroup>
310
+ <FieldGroup label="Tags (comma-separated)">
311
+ <Input value={tags} onChange={setTags} placeholder="e.g. biology, upper-ontology" />
312
+ </FieldGroup>
313
+ </div>
314
+ )}
315
+
316
+ {loadState === "success" && (
317
+ <div style={successBoxStyle}>
318
+ <CheckCircle2 size={13} />
319
+ <span>Ontology loaded successfully</span>
320
+ </div>
321
+ )}
322
+
323
+ {loadState === "error" && (
324
+ <div style={errorBoxStyle}>
325
+ <AlertCircle size={13} />
326
+ <span>{errorMsg}</span>
327
+ </div>
328
+ )}
329
+
330
+ <div style={{ display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 4 }}>
331
+ <button
332
+ onClick={handleLoad}
333
+ disabled={!url.trim() || loadState === "loading"}
334
+ style={primaryBtnStyle}
335
+ >
336
+ {loadState === "loading" ? (
337
+ <>
338
+ <Loader2 size={13} style={{ animation: "spin 1s linear infinite" }} />
339
+ Loading…
340
+ </>
341
+ ) : (
342
+ <>
343
+ <Globe size={13} />
344
+ Load Ontology
345
+ </>
346
+ )}
347
+ </button>
348
+ </div>
349
+ </div>
350
+ );
351
+ }
352
+
353
+ // ---------------------------------------------------------------------------
354
+ // File Upload panel
355
+ // ---------------------------------------------------------------------------
356
+
357
+ function FileUploadPanel({ onLoaded }: { onLoaded: () => void }) {
358
+ const fileRef = useRef<HTMLInputElement>(null);
359
+ const [fileName, setFileName] = useState("");
360
+ const [content, setContent] = useState("");
361
+ const [format, setFormat] = useState("");
362
+ const [loadState, setLoadState] = useState<"idle" | "loading" | "success" | "error">("idle");
363
+ const [errorMsg, setErrorMsg] = useState("");
364
+ const [dragging, setDragging] = useState(false);
365
+
366
+ const handleFile = (file: File) => {
367
+ setFileName(file.name);
368
+ const ext = file.name.split(".").pop()?.toLowerCase() || "";
369
+ const fmtMap: Record<string, string> = {
370
+ ttl: "turtle", rdf: "xml", owl: "xml", xml: "xml",
371
+ nt: "nt", jsonld: "json-ld", json: "json-ld",
372
+ };
373
+ // Leave format empty for unknown extensions so the backend auto-detects
374
+ setFormat(fmtMap[ext] ?? "");
375
+ const reader = new FileReader();
376
+ reader.onload = (e) => setContent(e.target?.result as string || "");
377
+ reader.readAsText(file);
378
+ };
379
+
380
+ const handleDrop = (e: React.DragEvent) => {
381
+ e.preventDefault();
382
+ setDragging(false);
383
+ const file = e.dataTransfer.files[0];
384
+ if (file) handleFile(file);
385
+ };
386
+
387
+ const handleLoad = async () => {
388
+ if (!content) return;
389
+ setLoadState("loading");
390
+ try {
391
+ const res = await fetch("/api/ontology/load", {
392
+ method: "POST",
393
+ headers: { "Content-Type": "application/json" },
394
+ // Omit format when empty so the backend _detect_format() runs
395
+ body: JSON.stringify({ content, ...(format ? { format } : {}) }),
396
+ });
397
+ if (!res.ok) {
398
+ const err = await res.json().catch(() => ({ detail: "Load failed" }));
399
+ throw new Error(err.detail || "Load failed");
400
+ }
401
+ setLoadState("success");
402
+ setTimeout(() => {
403
+ setLoadState("idle");
404
+ onLoaded();
405
+ }, 1200);
406
+ } catch (e) {
407
+ setLoadState("error");
408
+ setErrorMsg(e instanceof Error ? e.message : "Load failed");
409
+ }
410
+ };
411
+
412
+ return (
413
+ <div style={panelBodyStyle}>
414
+ <div
415
+ style={{
416
+ ...dropzoneStyle,
417
+ borderColor: dragging
418
+ ? "rgba(255,45,75,0.5)"
419
+ : "rgba(255,107,129,0.18)",
420
+ background: dragging ? "rgba(255,45,75,0.06)" : undefined,
421
+ }}
422
+ onDragOver={(e) => { e.preventDefault(); setDragging(true); }}
423
+ onDragLeave={() => setDragging(false)}
424
+ onDrop={handleDrop}
425
+ onClick={() => fileRef.current?.click()}
426
+ >
427
+ <FileUp size={24} color="#ff2d4b" />
428
+ {fileName ? (
429
+ <div style={{ color: "#fff0f2", fontSize: 13, fontWeight: 600 }}>{fileName}</div>
430
+ ) : (
431
+ <>
432
+ <div style={{ color: "#c99aa3", fontSize: 13 }}>
433
+ Drop a file here or <span style={{ color: "#ff2d4b" }}>browse</span>
434
+ </div>
435
+ <div style={{ color: "#8f6d75", fontSize: 11 }}>
436
+ .ttl · .rdf · .owl · .xml · .nt · .jsonld · .json · .n3
437
+ </div>
438
+ </>
439
+ )}
440
+ <input
441
+ ref={fileRef}
442
+ type="file"
443
+ accept=".ttl,.rdf,.owl,.nt,.jsonld,.json,.xml,.n3"
444
+ style={{ display: "none" }}
445
+ onChange={(e) => { const f = e.target.files?.[0]; if (f) handleFile(f); }}
446
+ />
447
+ </div>
448
+
449
+ {content && (
450
+ <FieldGroup label="Format">
451
+ <select
452
+ value={format}
453
+ onChange={(e) => setFormat(e.target.value)}
454
+ style={selectStyle}
455
+ >
456
+ <option value="turtle">Turtle</option>
457
+ <option value="xml">RDF/XML</option>
458
+ <option value="nt">N-Triples</option>
459
+ <option value="json-ld">JSON-LD</option>
460
+ </select>
461
+ </FieldGroup>
462
+ )}
463
+
464
+ {loadState === "success" && (
465
+ <div style={successBoxStyle}>
466
+ <CheckCircle2 size={13} />
467
+ <span>Ontology loaded successfully — {fileName}</span>
468
+ </div>
469
+ )}
470
+
471
+ {loadState === "error" && (
472
+ <div style={errorBoxStyle}>
473
+ <AlertCircle size={13} />
474
+ <span>{errorMsg}</span>
475
+ </div>
476
+ )}
477
+
478
+ <div style={{ display: "flex", justifyContent: "flex-end" }}>
479
+ <button
480
+ onClick={handleLoad}
481
+ disabled={!content || loadState === "loading"}
482
+ style={primaryBtnStyle}
483
+ >
484
+ {loadState === "loading" ? (
485
+ <>
486
+ <Loader2 size={13} style={{ animation: "spin 1s linear infinite" }} />
487
+ Loading…
488
+ </>
489
+ ) : (
490
+ <>
491
+ <FileUp size={13} />
492
+ Load File
493
+ </>
494
+ )}
495
+ </button>
496
+ </div>
497
+ </div>
498
+ );
499
+ }
500
+
501
+ // ---------------------------------------------------------------------------
502
+ // Create New panel
503
+ // ---------------------------------------------------------------------------
504
+
505
+ function CreateNewPanel({ onLoaded }: { onLoaded: () => void }) {
506
+ const [createMode, setCreateMode] = useState<CreateMode>("scratch");
507
+ const [namespace, setNamespace] = useState("https://example.org/ontology/");
508
+ const [name, setName] = useState("");
509
+ const [description, setDescription] = useState("");
510
+ const [tags, setTags] = useState("");
511
+ const [sampleData, setSampleData] = useState("");
512
+ const [schemaText, setSchemaText] = useState("");
513
+ const [createState, setCreateState] = useState<"idle" | "loading" | "success" | "error">("idle");
514
+ const [errorMsg, setErrorMsg] = useState("");
515
+
516
+ const handleCreate = async () => {
517
+ if (!name.trim() || !namespace.trim()) return;
518
+ setCreateState("loading");
519
+ try {
520
+ const res = await fetch("/api/ontology/create", {
521
+ method: "POST",
522
+ headers: { "Content-Type": "application/json" },
523
+ body: JSON.stringify({
524
+ mode: createMode,
525
+ namespace: namespace.trim(),
526
+ name: name.trim(),
527
+ description: description || undefined,
528
+ tags: tags ? tags.split(",").map((t) => t.trim()).filter(Boolean) : [],
529
+ sample_data: createMode === "data" ? sampleData : undefined,
530
+ schema_text: createMode === "text" ? schemaText : undefined,
531
+ }),
532
+ });
533
+ if (!res.ok) {
534
+ const err = await res.json().catch(() => ({ detail: "Create failed" }));
535
+ throw new Error(err.detail || "Create failed");
536
+ }
537
+ setCreateState("success");
538
+ setTimeout(() => {
539
+ setCreateState("idle");
540
+ onLoaded();
541
+ }, 1200);
542
+ } catch (e) {
543
+ setCreateState("error");
544
+ setErrorMsg(e instanceof Error ? e.message : "Create failed");
545
+ }
546
+ };
547
+
548
+ return (
549
+ <div style={panelBodyStyle}>
550
+ <div style={{ display: "flex", gap: 6 }}>
551
+ {(["scratch", "data", "text"] as CreateMode[]).map((m) => (
552
+ <button
553
+ key={m}
554
+ onClick={() => setCreateMode(m)}
555
+ style={{
556
+ ...modeTabBase,
557
+ ...(createMode === m ? modeTabActive : modeTabIdle),
558
+ }}
559
+ >
560
+ {m === "scratch" ? "From Scratch" : m === "data" ? "From Data" : "From Text"}
561
+ </button>
562
+ ))}
563
+ </div>
564
+
565
+ <FieldGroup label="Display Name *">
566
+ <Input value={name} onChange={setName} placeholder="My Ontology" />
567
+ </FieldGroup>
568
+
569
+ <FieldGroup label="Namespace URI *">
570
+ <Input value={namespace} onChange={setNamespace} placeholder="https://example.org/onto/" />
571
+ </FieldGroup>
572
+
573
+ <FieldGroup label="Description">
574
+ <Input value={description} onChange={setDescription} placeholder="Optional description" />
575
+ </FieldGroup>
576
+
577
+ <FieldGroup label="Tags (comma-separated)">
578
+ <Input value={tags} onChange={setTags} placeholder="e.g. internal, draft" />
579
+ </FieldGroup>
580
+
581
+ {createMode === "data" && (
582
+ <FieldGroup label="Sample Data (JSON or CSV)">
583
+ <Textarea
584
+ value={sampleData}
585
+ onChange={setSampleData}
586
+ placeholder={'[{"name": "Alice", "age": 30, "city": "Berlin"}]'}
587
+ rows={6}
588
+ />
589
+ </FieldGroup>
590
+ )}
591
+
592
+ {createMode === "text" && (
593
+ <FieldGroup label="Schema Requirements (natural language)">
594
+ <Textarea
595
+ value={schemaText}
596
+ onChange={setSchemaText}
597
+ placeholder="Describe the ontology you need. E.g.: I need an ontology for a hospital domain with patients, doctors, appointments, and medications."
598
+ rows={6}
599
+ />
600
+ </FieldGroup>
601
+ )}
602
+
603
+ {createState === "success" && (
604
+ <div style={successBoxStyle}>
605
+ <CheckCircle2 size={13} />
606
+ <span>Ontology created and opened in the Registry</span>
607
+ </div>
608
+ )}
609
+
610
+ {createState === "error" && (
611
+ <div style={errorBoxStyle}>
612
+ <AlertCircle size={13} />
613
+ <span>{errorMsg}</span>
614
+ </div>
615
+ )}
616
+
617
+ <div style={{ display: "flex", justifyContent: "flex-end" }}>
618
+ <button
619
+ onClick={handleCreate}
620
+ disabled={!name.trim() || !namespace.trim() || createState === "loading"}
621
+ style={primaryBtnStyle}
622
+ >
623
+ {createState === "loading" ? (
624
+ <>
625
+ <Loader2 size={13} style={{ animation: "spin 1s linear infinite" }} />
626
+ Creating…
627
+ </>
628
+ ) : (
629
+ <>
630
+ <Plus size={13} />
631
+ Create Ontology
632
+ </>
633
+ )}
634
+ </button>
635
+ </div>
636
+ </div>
637
+ );
638
+ }
639
+
640
+ // ---------------------------------------------------------------------------
641
+ // Main OntologyLoader modal
642
+ // ---------------------------------------------------------------------------
643
+
644
+ export function OntologyLoader({ onLoaded, onClose }: LoaderProps) {
645
+ const [mode, setMode] = useState<LoaderMode>("url");
646
+
647
+ return (
648
+ <div style={overlayStyle} onClick={(e) => e.target === e.currentTarget && onClose()}>
649
+ <div style={modalStyle}>
650
+ <div style={modalHeaderStyle}>
651
+ <div>
652
+ <div style={{ color: "#fff0f2", fontSize: 16, fontWeight: 800 }}>Load Ontology</div>
653
+ <div style={{ color: "#c99aa3", fontSize: 12, marginTop: 2 }}>
654
+ Import from URL, upload a file, or create a new ontology
655
+ </div>
656
+ </div>
657
+ <button onClick={onClose} style={closeIconBtnStyle}>
658
+ <X size={16} />
659
+ </button>
660
+ </div>
661
+
662
+ <div style={{ display: "flex", gap: 2, padding: "0 20px", borderBottom: "1px solid rgba(255,107,129,0.1)" }}>
663
+ {(["url", "file", "create"] as LoaderMode[]).map((m) => (
664
+ <button
665
+ key={m}
666
+ onClick={() => setMode(m)}
667
+ style={{
668
+ ...modalTabBase,
669
+ ...(mode === m ? modalTabActive : modalTabIdle),
670
+ }}
671
+ >
672
+ {m === "url" ? (
673
+ <><Globe size={12} /> URL Import</>
674
+ ) : m === "file" ? (
675
+ <><FileUp size={12} /> File Upload</>
676
+ ) : (
677
+ <><Plus size={12} /> Create New</>
678
+ )}
679
+ </button>
680
+ ))}
681
+ </div>
682
+
683
+ <div style={modalBodyStyle}>
684
+ {mode === "url" && <URLImportPanel onLoaded={onLoaded} />}
685
+ {mode === "file" && <FileUploadPanel onLoaded={onLoaded} />}
686
+ {mode === "create" && <CreateNewPanel onLoaded={onLoaded} />}
687
+ </div>
688
+ </div>
689
+ </div>
690
+ );
691
+ }
692
+
693
+ /* ─── styles ─────────────────────────────────────────────────────────── */
694
+
695
+ const overlayStyle: React.CSSProperties = {
696
+ position: "fixed",
697
+ inset: 0,
698
+ background: "rgba(6,3,5,0.78)",
699
+ backdropFilter: "blur(6px)",
700
+ display: "flex",
701
+ alignItems: "center",
702
+ justifyContent: "center",
703
+ zIndex: 1000,
704
+ };
705
+
706
+ const modalStyle: React.CSSProperties = {
707
+ width: "min(620px, 96vw)",
708
+ maxHeight: "88vh",
709
+ display: "flex",
710
+ flexDirection: "column",
711
+ borderRadius: 20,
712
+ border: "1px solid rgba(255,107,129,0.16)",
713
+ background: "linear-gradient(180deg, rgba(11,21,34,0.98), rgba(6,13,22,0.96))",
714
+ boxShadow: "0 32px 80px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.06)",
715
+ overflow: "hidden",
716
+ };
717
+
718
+ const modalHeaderStyle: React.CSSProperties = {
719
+ display: "flex",
720
+ alignItems: "flex-start",
721
+ justifyContent: "space-between",
722
+ padding: "20px 20px 16px",
723
+ };
724
+
725
+ const modalBodyStyle: React.CSSProperties = {
726
+ flex: 1,
727
+ overflowY: "auto",
728
+ };
729
+
730
+ const panelBodyStyle: React.CSSProperties = {
731
+ padding: "16px 20px 20px",
732
+ display: "flex",
733
+ flexDirection: "column",
734
+ gap: 14,
735
+ };
736
+
737
+ const modalTabBase: React.CSSProperties = {
738
+ display: "inline-flex",
739
+ alignItems: "center",
740
+ gap: 6,
741
+ padding: "8px 14px",
742
+ border: "none",
743
+ borderBottom: "2px solid transparent",
744
+ background: "transparent",
745
+ cursor: "pointer",
746
+ fontSize: 12,
747
+ fontWeight: 600,
748
+ transition: "160ms ease",
749
+ };
750
+
751
+ const modalTabIdle: React.CSSProperties = {
752
+ color: "#c99aa3",
753
+ };
754
+
755
+ const modalTabActive: React.CSSProperties = {
756
+ color: "#ff2d4b",
757
+ borderBottomColor: "#ff2d4b",
758
+ };
759
+
760
+ const closeIconBtnStyle: React.CSSProperties = {
761
+ background: "transparent",
762
+ border: "none",
763
+ color: "#c99aa3",
764
+ cursor: "pointer",
765
+ padding: 4,
766
+ borderRadius: 8,
767
+ display: "grid",
768
+ placeItems: "center",
769
+ };
770
+
771
+ const fieldLabelStyle: React.CSSProperties = {
772
+ color: "#c99aa3",
773
+ fontSize: 11,
774
+ fontWeight: 700,
775
+ letterSpacing: "0.05em",
776
+ };
777
+
778
+ const inputStyle: React.CSSProperties = {
779
+ width: "100%",
780
+ padding: "8px 12px",
781
+ borderRadius: 8,
782
+ border: "1px solid rgba(255,107,129,0.16)",
783
+ background: "rgba(0,0,0,0.24)",
784
+ color: "#fff0f2",
785
+ fontSize: 13,
786
+ outline: "none",
787
+ boxSizing: "border-box",
788
+ };
789
+
790
+ const selectStyle: React.CSSProperties = {
791
+ ...inputStyle,
792
+ appearance: "none" as const,
793
+ cursor: "pointer",
794
+ };
795
+
796
+ const previewBtnStyle: React.CSSProperties = {
797
+ padding: "8px 14px",
798
+ borderRadius: 8,
799
+ border: "1px solid rgba(255,107,129,0.2)",
800
+ background: "rgba(255,45,75,0.08)",
801
+ color: "#ff6b81",
802
+ fontSize: 12,
803
+ fontWeight: 600,
804
+ cursor: "pointer",
805
+ whiteSpace: "nowrap",
806
+ display: "inline-flex",
807
+ alignItems: "center",
808
+ gap: 6,
809
+ };
810
+
811
+ const primaryBtnStyle: React.CSSProperties = {
812
+ display: "inline-flex",
813
+ alignItems: "center",
814
+ gap: 7,
815
+ padding: "9px 18px",
816
+ borderRadius: 10,
817
+ border: "1px solid rgba(255,45,75,0.3)",
818
+ background: "linear-gradient(135deg, rgba(255,45,75,0.2), rgba(255,45,75,0.1))",
819
+ color: "#ff6b81",
820
+ fontSize: 13,
821
+ fontWeight: 700,
822
+ cursor: "pointer",
823
+ };
824
+
825
+ const advancedToggleStyle: React.CSSProperties = {
826
+ display: "inline-flex",
827
+ alignItems: "center",
828
+ gap: 6,
829
+ background: "transparent",
830
+ border: "none",
831
+ color: "#9c7a82",
832
+ fontSize: 12,
833
+ cursor: "pointer",
834
+ padding: 0,
835
+ };
836
+
837
+ const previewCardStyle: React.CSSProperties = {
838
+ padding: 14,
839
+ borderRadius: 10,
840
+ border: "1px solid rgba(76,195,138,0.18)",
841
+ background: "rgba(76,195,138,0.04)",
842
+ };
843
+
844
+ const previewTitleStyle: React.CSSProperties = {
845
+ color: "#fff0f2",
846
+ fontSize: 15,
847
+ fontWeight: 800,
848
+ letterSpacing: "-0.03em",
849
+ };
850
+
851
+ const previewGridStyle: React.CSSProperties = {
852
+ display: "grid",
853
+ gridTemplateColumns: "1fr 1fr",
854
+ gap: 10,
855
+ marginTop: 12,
856
+ };
857
+
858
+ const dropzoneStyle: React.CSSProperties = {
859
+ border: "2px dashed",
860
+ borderRadius: 12,
861
+ padding: "32px 20px",
862
+ display: "flex",
863
+ flexDirection: "column",
864
+ alignItems: "center",
865
+ gap: 10,
866
+ cursor: "pointer",
867
+ transition: "160ms ease",
868
+ };
869
+
870
+ const successBoxStyle: React.CSSProperties = {
871
+ display: "flex",
872
+ alignItems: "center",
873
+ gap: 8,
874
+ padding: "8px 12px",
875
+ borderRadius: 8,
876
+ border: "1px solid rgba(76,195,138,0.22)",
877
+ background: "rgba(76,195,138,0.06)",
878
+ color: "#4cc38a",
879
+ fontSize: 12,
880
+ };
881
+
882
+ const errorBoxStyle: React.CSSProperties = {
883
+ display: "flex",
884
+ alignItems: "center",
885
+ gap: 8,
886
+ padding: "8px 12px",
887
+ borderRadius: 8,
888
+ border: "1px solid rgba(255,157,175,0.22)",
889
+ background: "rgba(255,157,175,0.06)",
890
+ color: "#ff9daf",
891
+ fontSize: 12,
892
+ };
893
+
894
+ const modeTabBase: React.CSSProperties = {
895
+ padding: "6px 12px",
896
+ borderRadius: 8,
897
+ border: "1px solid transparent",
898
+ cursor: "pointer",
899
+ fontSize: 12,
900
+ fontWeight: 600,
901
+ };
902
+
903
+ const modeTabIdle: React.CSSProperties = {
904
+ background: "transparent",
905
+ color: "#c99aa3",
906
+ borderColor: "rgba(255,107,129,0.1)",
907
+ };
908
+
909
+ const modeTabActive: React.CSSProperties = {
910
+ background: "rgba(255,45,75,0.14)",
911
+ color: "#fff0f2",
912
+ borderColor: "rgba(255,107,129,0.24)",
913
+ };