From 28114690714cbaa06a82c9e8680b0d6ef916f9b8 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Fri, 1 May 2026 13:08:45 +0530 Subject: [PATCH 1/6] =?UTF-8?q?feat(explorer):=20add=20Ontology=20Hub=20wo?= =?UTF-8?q?rkspace=20=E2=80=94=20Registry,=20Loader,=20Entity=20Search=20&?= =?UTF-8?q?=20SKOS=20(closes=20#518)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the first subissue of Ontology Hub (#517): Frontend: - New OntologyWorkspace with 6 tabs (Registry, Editor, Versions, Alignments, Health, SHACL); active tab persisted in ontologyTab URL param - OntologyManager: full registry CRUD with status/format badges, stats, toggle/refresh/remove actions, search + filter toolbar, empty state CTA - OntologyLoader: 3-tab modal — URL import with live preview, drag-and-drop file upload, and Create New (from scratch / data / text) - OntologySearch: debounced entity search with type filters and detail panel showing superclasses, subclasses, domain/range, instance count - SKOSVocabularyManager: recursive concept hierarchy tree, client-side filtering, full SKOS annotation + relation detail panel - Editor/Versions (subissue 2) and Alignments/Health/SHACL (subissue 3) tabs render descriptive stub cards as placeholders Backend: - 12 new FastAPI endpoints under /api/ontology (registry, preview, load, create, search, entity detail, SKOS schemes + concept detail, toggle, refresh, remove) - rdflib-based RDF parser supporting Turtle, RDF/XML, N-Triples, JSON-LD - URL fetching via requests in asyncio.to_thread with 20 MB cap - Registry stored in app.state.ontology_registry; route ordering prevents literal paths being shadowed by /{uri:path} wildcards Also: add playwright dev dependency for screenshot testing --- CHANGELOG.md | 23 + explorer/package-lock.json | 45 + explorer/package.json | 1 + explorer/src/App.tsx | 20 +- .../OntologyWorkspace/OntologyLoader.tsx | 910 +++++++++++++++++ .../OntologyWorkspace/OntologyManager.tsx | 914 ++++++++++++++++++ .../OntologyWorkspace/OntologySearch.tsx | 574 +++++++++++ .../SKOSVocabularyManager.tsx | 638 ++++++++++++ .../workspaces/OntologyWorkspace/index.tsx | 289 ++++++ semantica/explorer/app.py | 2 + semantica/explorer/routes/ontology.py | 864 +++++++++++++++++ 11 files changed, 4279 insertions(+), 1 deletion(-) create mode 100644 explorer/src/workspaces/OntologyWorkspace/OntologyLoader.tsx create mode 100644 explorer/src/workspaces/OntologyWorkspace/OntologyManager.tsx create mode 100644 explorer/src/workspaces/OntologyWorkspace/OntologySearch.tsx create mode 100644 explorer/src/workspaces/OntologyWorkspace/SKOSVocabularyManager.tsx create mode 100644 explorer/src/workspaces/OntologyWorkspace/index.tsx create mode 100644 semantica/explorer/routes/ontology.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 918035c4..2b431008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- **Feature: Ontology Hub — Registry, Loader, Entity Search & SKOS Vocabulary Manager** (closes #518, part of #517, by @KaifAhmad1): + - Added a sixth workspace, **Ontology Hub** (`ontology-hub`), to the Knowledge Explorer sidebar with a `GitMerge` icon and "Schema Governance" kicker. The workspace shell hosts six tabs — Registry, Editor, Versions, Alignments, Health, and SHACL — with the active tab persisted in the `ontologyTab` URL search parameter via `window.history.replaceState`. + - **Registry tab (`OntologyManager`)** — full CRUD interface for loaded ontologies. Lists entries with color-coded status badges (published / draft / external), format badges (Turtle / XML / JSON-LD / N-Triples), per-ontology stats (class count, concept count, property count), source URL link, and enable/disable toggle, refresh, and remove (with confirmation) actions. Toolbar provides a live search input, All / OWL / SKOS / INTERNAL / EXTERNAL filter pills, an Entity Search button, and a "Load Ontology" button. Empty state surfaces a prominent CTA. Action feedback bar auto-hides after 3 seconds. + - **Load Ontology modal (`OntologyLoader`)** — three-tab modal overlay for importing ontologies: + - *URL Import*: paste any HTTP(S) URL, click "Fetch Preview" to call `POST /api/ontology/preview` (fetches up to 20 MB, parses with rdflib, returns title / namespace / version / license / format / triple count), then "Load Ontology" (`POST /api/ontology/load`). Advanced options toggle exposes format override, custom display name, description, and tags fields. + - *File Upload*: drag-and-drop zone (or browse) accepting `.ttl`, `.rdf`, `.owl`, `.nt`, `.jsonld` files; format auto-detected from extension; multipart `POST /api/ontology/load`. + - *Create New*: three modes — From Scratch (namespace + name + description + tags), From Data (sample data textarea for schema inference via `OntologyEngine.from_data()`), From Text (free-text textarea for LLM-assisted schema generation via `OntologyEngine.from_text()`); calls `POST /api/ontology/create`. + - **Entity Search panel (`OntologySearch`)** — slide-in right panel with debounced 320 ms search across all loaded ontologies via `GET /api/ontology/search`. Type filter pills: All, Class, Property, Individual, Concept, Scheme. Result rows show label, type badge, URI, definition snippet, and source ontology. Selecting a result opens a detail panel that fetches `GET /api/ontology/entity/{uri}` and renders label, URI, definition, superclasses, subclasses, domain, range, instance count, and external URI link. Long lists use a `CollapsibleList` expanding up to 12 items. + - **SKOS Vocabulary Manager (`SKOSVocabularyManager`)** — hierarchical SKOS concept browser activated when a SKOS ontology is selected in the registry. Fetches scheme hierarchy from `GET /api/vocabulary/hierarchy`, renders a recursive `ConceptTreeNode` tree with depth-based indentation, expand/collapse, and selection highlight. Client-side `filterConcepts()` matches label, altLabels, and description. Detail panel fetches `GET /api/ontology/skos/concept/{uri}` and displays all SKOS annotation properties (definition, scopeNote, example, historyNote, editorialNote, changeNote) plus broader / narrower / related / exactMatch / closeMatch lists with clickable navigation. + - **Backend (`semantica/explorer/routes/ontology.py`)** — 12 FastAPI endpoints under `GET|POST /api/ontology`: + - `GET /registry` — returns the in-memory `app.state.ontology_registry` dict as a list, with optional `q` search and `status` filter query params. + - `POST /preview` — streams up to 20 MB from a URL via `requests.get` in `asyncio.to_thread`, parses RDF with rdflib (auto-detects format or accepts `format` param), returns `OntologyPreview` metadata. + - `POST /load` — URL or multipart file load; stores parsed nodes/edges into the active graph session and registers an `OntologyEntry` in the registry. + - `POST /create` — creates an ontology from scratch, sample data, or natural-language text; falls back to a minimal ontology shell if `OntologyEngine` is unavailable. + - `GET /search` — full-text entity search with optional `type` filter across all nodes whose `node_type` maps to class, property, individual, concept, or scheme. + - `GET /entity/{uri:path}` — entity detail: label, type, definition, superclasses, subclasses, domain, range, instance count. + - `GET /skos/schemes` — lists all `skos:ConceptScheme` nodes in the active session. + - `GET /skos/concept/{uri:path}` — full SKOS concept detail including all annotation properties and relation sets. + - `DELETE /{uri:path}`, `PATCH /{uri:path}/toggle`, `POST /{uri:path}/refresh` — remove, enable/disable toggle, and re-fetch/re-parse for registered ontologies. Route ordering places all literal paths before the `:path` wildcards to avoid shadowing. + - Helper internals: `_parse_rdf_sync()` (rdflib parse → nodes/edges/metadata), `_fetch_url_sync()` (streaming requests with 20 MB cap), `_classify_node_type()` (maps raw RDF types to canonical categories), `_uri_to_prefix()` (URI → prefixed form for display). + - Editor, Versions (Subissue 2) and Alignments, Health, SHACL (Subissue 3) tabs render descriptive stub cards with amber subissue badges as placeholders for upcoming implementations. + - TypeScript compiled with zero errors; Vite dev server starts cleanly with the new workspace lazy-loaded via `React.lazy` + `Suspense`. + - **Feature: Explorer landing page redesign** (PR #516 by @ZohaibHassan16, review fixes by @KaifAhmad1): - Replaced the plain welcome screen with a full landing composition: premium hero section, product preview mock with animated SVG graph, live graph status metrics, intelligence capability band, and consolidated workspace launcher. - `WelcomeScreen` fetches `/api/graph/stats` on mount with `AbortController` cleanup and displays live node and edge counts; falls back to `"Live"` / `"Ready"` labels when the endpoint is unavailable. diff --git a/explorer/package-lock.json b/explorer/package-lock.json index 1f057188..4c0fc177 100644 --- a/explorer/package-lock.json +++ b/explorer/package-lock.json @@ -19,6 +19,7 @@ "graphology-metrics": "^2.4.0", "graphology-shortest-path": "^2.1.0", "lucide-react": "^1.7.0", + "playwright": "^1.59.1", "react": "^19.2.4", "react-arborist": "^3.4.3", "react-dom": "^19.2.4", @@ -3467,6 +3468,50 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.5.10", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", diff --git a/explorer/package.json b/explorer/package.json index 23df2b07..53de5b97 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -23,6 +23,7 @@ "graphology-metrics": "^2.4.0", "graphology-shortest-path": "^2.1.0", "lucide-react": "^1.7.0", + "playwright": "^1.59.1", "react": "^19.2.4", "react-arborist": "^3.4.3", "react-dom": "^19.2.4", diff --git a/explorer/src/App.tsx b/explorer/src/App.tsx index 3aea3078..2713f078 100644 --- a/explorer/src/App.tsx +++ b/explorer/src/App.tsx @@ -29,8 +29,9 @@ const RegistryTab = lazy(() => import('./workspaces/EnrichWorkspace/RegistryTab' const EntityResolutionTab = lazy(() => import('./workspaces/EnrichWorkspace/EntityResolutionTab').then((module) => ({ default: module.EntityResolutionTab }))); const KGOverviewTab = lazy(() => import('./workspaces/ManageWorkspace/KGOverviewTab').then((module) => ({ default: module.KGOverviewTab }))); const OntologySummaryTab = lazy(() => import('./workspaces/ManageWorkspace/OntologySummaryTab').then((module) => ({ default: module.OntologySummaryTab }))); +const OntologyWorkspace = lazy(() => import('./workspaces/OntologyWorkspace').then((module) => ({ default: module.OntologyWorkspace }))); -type WorkspaceId = 'welcome' | 'explore' | 'analyze' | 'decisions' | 'enrich' | 'manage'; +type WorkspaceId = 'welcome' | 'explore' | 'analyze' | 'decisions' | 'enrich' | 'manage' | 'ontology-hub'; type ExploreView = 'graph' | 'vocabulary'; type AnalyzeView = 'sparql' | 'reasoning'; type EnrichView = 'import' | 'merge' | 'registry' | 'resolve'; @@ -80,6 +81,7 @@ const navItems: NavItem[] = [ { id: 'decisions', label: 'Decisions', hint: 'Decision chains and precedent review', icon: Scale }, { id: 'enrich', label: 'Enrich', hint: 'Import, export, and merge workflows', icon: GitBranchPlus }, { id: 'manage', label: 'Manage', hint: 'Lineage and governance tooling', icon: Settings2 }, + { id: 'ontology-hub', label: 'Ontology Hub', hint: 'Schema governance, registry, and vocabulary management', icon: GitMerge }, ]; const shellStyles = ` @@ -1236,6 +1238,7 @@ export default function App() { const [enrichView, setEnrichView] = useState('import'); const [manageView, setManageView] = useState('lineage'); + const renderWorkspace = () => { if (activeWorkspace === 'welcome') { return ( @@ -1358,6 +1361,21 @@ export default function App() { ); } + if (activeWorkspace === 'ontology-hub') { + return ( + + }> + + + + ); + } + return ( void; + onClose: () => void; +} + +function Badge({ label, color }: { label: string; color: string }) { + return ( + + {label} + + ); +} + +function FieldGroup({ + label, + children, +}: { + label: string; + children: React.ReactNode; +}) { + return ( +
+ + {children} +
+ ); +} + +function Input({ + value, + onChange, + placeholder, + type = "text", +}: { + value: string; + onChange: (v: string) => void; + placeholder?: string; + type?: string; +}) { + return ( + onChange(e.target.value)} + placeholder={placeholder} + style={inputStyle} + /> + ); +} + +function Textarea({ + value, + onChange, + placeholder, + rows = 5, +}: { + value: string; + onChange: (v: string) => void; + placeholder?: string; + rows?: number; +}) { + return ( +