mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-30 04:40:16 +00:00
## Folder & Project
- Renamed `semantica-explorer/` → `explorer/` (cleaner path)
- Browser tab title: `Semantica Knowledge Explorer`
- Brand pill: `SEM` → `SKE` (tooltip: Semantica Knowledge Explorer)
- Nav rail label: `Explore` → `Knowledge Explorer`
- package.json name: `semantica-knowledge-explorer`
- Downgraded Vite 8 → Vite 5 for Node v20.17.0 compatibility
## App Shell
- Dynamic per-workspace kicker labels replacing static "Workspace" pill:
Graph Studio · Vocabulary Browser · Reasoning Engine · SPARQL Query ·
Decision Intelligence · Knowledge Audit · Graph Governance
## Enrich Workspace — 2 new tabs
### Entity Resolution tab
- Similarity threshold slider (0.50–0.99)
- Run Dedup Scan → POST /api/enrich/dedup
- Flagged pairs list with colour-coded score bars (red/amber/green)
- Expandable inline diff: primary vs duplicate side-by-side
- One-click Merge → POST /api/enrich/merge with logEvent dispatch
- Dismiss per pair; Clear all button
- Merge history sidebar pulled live from Registry store
### Registry tab (Document Registry)
- Live chronological audit log of all KG mutations in-session
- Colour-coded op-type badges: IMPORT · MERGE · ADD NODE · ADD EDGE ·
INFER · DELETE · EXPORT · VOCAB
- Filter pills to narrow by operation type
- Expandable JSON detail rows per entry
- Clear log button
- Entirely client-side via registryStore (no backend needed)
## Manage Workspace — 2 new tabs
### KG Overview tab
- Stats chips: total nodes, edges, graph density
- Node type breakdown bar chart (up to 8 types, colour-coded)
- Edge type breakdown bar chart from /api/graph/stats
- Top-10 most connected nodes ranked by degree
- Skeleton loading states + Refresh button
### Ontology Summary tab
- Read-only SKOS scheme tree (scheme → top concepts → narrower)
- Concept detail panel: labels, notation, description, narrower nav
- "Open Full Browser" button deep-links to Vocabulary Browser tab
## Decision Workspace polish
- CausalFlowDiagram: vertical node cards connected by relationship pills
- Outcome badges: colour-coded (green=approved, red=rejected, amber=deferred)
- Live filter input across decision ID, category, and outcome
- Animated skeleton loading while list fetches
## Graph Inspector polish
- PathFlowViz: clickable node chips connected by edge-type labels;
clicking a chip focuses that node in the canvas
- Link Prediction button shows spinner while computing
- Empty states for path trace and candidate links sections
## Registry dispatch — WebSocket
- ADD_NODE events → logEvent("add-node", …) in GraphWorkspace WS handler
- ADD_EDGE events → logEvent("add-edge", …) in GraphWorkspace WS handler
- Import, Export, Merge already dispatched logEvent on API response
## Graph visibility overhaul
### Edge colours (were nearly transparent, now clearly visible)
- edgeBackbone: rgba(…, 0.04) → rgba(…, 0.38)
- edgeStructure: rgba(…, 0.009) → rgba(…, 0.28)
- edgeInspection: rgba(…, 0.026) → rgba(…, 0.48)
- Muted edges: 0.009–0.02 → 0.12–0.26
- Focus edges: 0.16 → 0.42
### Edge sizes
- default minSize: 0.18 → 0.9 (always at least 1 pixel wide)
- path minSize: 1.8 → 2.4
- inactive/muted: hide:true → hide:false (dimmed not hidden)
### Node sizes
- default sizeMultiplier: 0.72 → 0.92
- default minSize: 0.68 → 3.5 (visible at all zoom levels)
- overview nodeScale: 0.66 → 0.88
- nodeTintMix (colour): 0.03 → 0.14
- nodeCoreMix (brightness): 0.52 → 0.72
### Label budget
- overview: 10 → 28 labels
- structure: 36 → 60 labels
- inspection: 80 → 120 labels
### Sigma settings
- renderEdgeLabels: false → true (relationship type on every edge)
- edgeLabelSize: — → 10
- labelRenderedSizeThreshold: 4 → 2
- labelDensity: 0.86 → 1.1
- hideLabelsOnMove: true → false (labels stay visible while panning)
- hideEdgesOnMove: true → false (edges stay visible while panning)
- minCameraRatio: — → 0.04 (prevents zooming inside a node)
- maxCameraRatio: — → 8 (graph stays visible when zoomed out)
### Zoom controls
- Added Zoom In (+) and Zoom Out (−) buttons to graph toolbar
- Smooth animated zoom via camera.animatedZoom / animatedUnzoom (200ms)
- Mouse scroll wheel clamped between minCameraRatio and maxCameraRatio
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
106 lines
4.8 KiB
TypeScript
106 lines
4.8 KiB
TypeScript
/**
|
|
* src/workspaces/DiffMergeWorkspace/DiffMergeWorkspace.tsx
|
|
*/
|
|
import { useState } from "react";
|
|
import { logEvent } from "../../store/registryStore";
|
|
|
|
const THEME_CSS = `
|
|
.glass-panel {
|
|
background: linear-gradient(135deg, rgba(13,17,23,0.75), rgba(22,27,34,0.6));
|
|
backdrop-filter: blur(16px) saturate(1.2);
|
|
-webkit-backdrop-filter: blur(16px) saturate(1.2);
|
|
border: 1px solid rgba(88,166,255,0.2);
|
|
box-shadow: 0 8px 32px rgba(0,0,0,0.5), inset 1px 1px 0 rgba(255,255,255,0.05);
|
|
}
|
|
`;
|
|
|
|
export function DiffMergeWorkspace() {
|
|
const [primaryId, setPrimaryId] = useState("n-primary-1");
|
|
const [duplicateId, setDuplicateId] = useState("n-dup-2");
|
|
|
|
const [msg, setMsg] = useState("");
|
|
|
|
const handleMerge = async () => {
|
|
try {
|
|
const res = await fetch("/api/enrich/merge", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ primary_id: primaryId, duplicate_ids: [duplicateId] })
|
|
});
|
|
const data = await res.json();
|
|
if (data.merged_into) {
|
|
setMsg(`Merge success: redirected ${data.edges_updated} edges to ${data.merged_into}`);
|
|
logEvent("merge", `Merged ${duplicateId} → ${data.merged_into} · ${data.edges_updated} edges redirected`, {
|
|
primary: data.merged_into,
|
|
duplicate: duplicateId,
|
|
edgesUpdated: data.edges_updated,
|
|
});
|
|
} else {
|
|
setMsg("Merge failed...");
|
|
}
|
|
} catch (err) {
|
|
setMsg("Error calling merge endpoint.");
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div style={{ display: "flex", flexDirection: "column", width: "100%", height: "100%", background: "#0d1117", padding: 32, gap: 24, boxSizing: "border-box" }}>
|
|
<style>{THEME_CSS}</style>
|
|
<div>
|
|
<h1 style={{ margin: "0 0 8px 0", color: "#fff" }}>Entity Diff & Merge</h1>
|
|
<p style={{ margin: 0, color: "#8b949e" }}>Compare suspected duplicate entities and reconcile them.</p>
|
|
</div>
|
|
|
|
<div style={{ display: "flex", gap: 24, flex: 1 }}>
|
|
{/* Primary View */}
|
|
<div className="glass-panel" style={{ flex: 1, borderRadius: 12, padding: 24 }}>
|
|
<h3 style={{ color: "#58a6ff", margin: "0 0 16px 0", borderBottom: "1px solid rgba(88,166,255,0.2)", paddingBottom: 8 }}>Primary Entity</h3>
|
|
<label style={{ display: "block", color: "#c9d1d9", marginBottom: 8, fontSize: 13 }}>Primary Node ID</label>
|
|
<input
|
|
value={primaryId} onChange={e => setPrimaryId(e.target.value)}
|
|
style={{ width: "100%", background: "rgba(0,0,0,0.3)", border: "1px solid rgba(255,255,255,0.1)", color: "#fff", padding: "8px 12px", borderRadius: 6, marginBottom: 24 }}
|
|
/>
|
|
|
|
<div style={{ background: "rgba(0,0,0,0.2)", padding: 16, borderRadius: 6 }}>
|
|
<div style={{ color: "#8b949e", fontSize: 12, marginBottom: 4 }}>Name</div>
|
|
<div style={{ color: "#fff", fontSize: 14 }}>Sample Company Inc.</div>
|
|
|
|
<div style={{ color: "#8b949e", fontSize: 12, marginTop: 16, marginBottom: 4 }}>Founded</div>
|
|
<div style={{ color: "#fff", fontSize: 14 }}>2004-05-12</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Duplicate View */}
|
|
<div className="glass-panel" style={{ flex: 1, borderRadius: 12, padding: 24 }}>
|
|
<h3 style={{ color: "#ff7b72", margin: "0 0 16px 0", borderBottom: "1px solid rgba(255,123,114,0.2)", paddingBottom: 8 }}>Duplicate Entity</h3>
|
|
<label style={{ display: "block", color: "#c9d1d9", marginBottom: 8, fontSize: 13 }}>Duplicate Node ID</label>
|
|
<input
|
|
value={duplicateId} onChange={e => setDuplicateId(e.target.value)}
|
|
style={{ width: "100%", background: "rgba(0,0,0,0.3)", border: "1px solid rgba(255,255,255,0.1)", color: "#fff", padding: "8px 12px", borderRadius: 6, marginBottom: 24 }}
|
|
/>
|
|
|
|
<div style={{ background: "rgba(0,0,0,0.2)", padding: 16, borderRadius: 6 }}>
|
|
<div style={{ color: "#d2a8ff", fontSize: 12, marginBottom: 4 }}>Name</div>
|
|
{/* Amber highlight for differing values */}
|
|
<div style={{ color: "#d29922", fontSize: 14, fontWeight: "bold" }}>Sample Company</div>
|
|
|
|
<div style={{ color: "#8b949e", fontSize: 12, marginTop: 16, marginBottom: 4 }}>Founded</div>
|
|
<div style={{ color: "#fff", fontSize: 14 }}>2004-05-12</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
|
<div style={{ color: "#58a6ff" }}>{msg}</div>
|
|
<button
|
|
onClick={handleMerge}
|
|
style={{ background: "#238636", color: "#fff", border: "none", padding: "10px 24px", borderRadius: 6, fontWeight: 600, cursor: "pointer", fontSize: 16 }}
|
|
>
|
|
Confirm Merge
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
);
|
|
}
|