@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,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
|
+
};
|