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>
103 lines
2.5 KiB
TypeScript
103 lines
2.5 KiB
TypeScript
export const GRAPH_THEME = {
|
|
background: {
|
|
canvas: "#050816",
|
|
shell: "#0A1021",
|
|
panel: "#10182C",
|
|
grid: "rgba(93, 124, 168, 0.08)",
|
|
vignette: "rgba(2, 4, 10, 0.88)",
|
|
},
|
|
nodes: {
|
|
palette: [
|
|
"#3CE7FF",
|
|
"#23D7C8",
|
|
"#5DA9FF",
|
|
"#9B6BFF",
|
|
"#FF4FD8",
|
|
"#FF9A3C",
|
|
"#B9FF3B",
|
|
],
|
|
selected: "#FFC857",
|
|
selectedGlow: "rgba(255, 200, 87, 0.36)",
|
|
hoverGlow: "rgba(60, 231, 255, 0.32)",
|
|
border: "#07111C",
|
|
subduedAlpha: 0.1,
|
|
},
|
|
edges: {
|
|
baseColor: "rgba(126, 162, 214, 0.08)",
|
|
subduedColor: "rgba(126, 162, 214, 0.03)",
|
|
hoverColor: "rgba(94, 198, 255, 0.9)",
|
|
pathColor: "rgba(255, 192, 92, 0.95)",
|
|
focusColor: "rgba(175, 191, 255, 0.26)",
|
|
},
|
|
motion: {
|
|
hoverMs: 160,
|
|
cameraMs: 480,
|
|
},
|
|
thresholds: {
|
|
interactiveLayoutMaxNodes: 8000,
|
|
stagedLayoutMaxNodes: 25000,
|
|
focusNeighborCap: 18,
|
|
focusPrimaryLabels: 8,
|
|
particleEdgeCap: 32,
|
|
},
|
|
} as const;
|
|
|
|
export const FORCE_ATLAS_SETTINGS = {
|
|
getEdgeWeight: "weight",
|
|
settings: {
|
|
barnesHutOptimize: true,
|
|
barnesHutTheta: 0.6,
|
|
linLogMode: true,
|
|
outboundAttractionDistribution: true,
|
|
strongGravityMode: false,
|
|
gravity: 0.14,
|
|
scalingRatio: 4.8,
|
|
slowDown: 6,
|
|
edgeWeightInfluence: 1,
|
|
adjustSizes: true,
|
|
},
|
|
} as const;
|
|
|
|
export function clamp(min: number, value: number, max: number): number {
|
|
return Math.min(max, Math.max(min, value));
|
|
}
|
|
|
|
export function hashString(value: string): number {
|
|
let hash = 0;
|
|
for (let index = 0; index < value.length; index += 1) {
|
|
hash = (hash << 5) - hash + value.charCodeAt(index);
|
|
hash |= 0;
|
|
}
|
|
return Math.abs(hash);
|
|
}
|
|
|
|
export function withAlpha(color: string | undefined, alpha: number): string {
|
|
if (!color) {
|
|
return `rgba(130, 145, 165, ${alpha})`;
|
|
}
|
|
|
|
if (color.startsWith("#")) {
|
|
const hex = color.slice(1);
|
|
const normalized = hex.length === 3
|
|
? hex.split("").map((char) => `${char}${char}`).join("")
|
|
: hex;
|
|
|
|
if (normalized.length === 6) {
|
|
const red = Number.parseInt(normalized.slice(0, 2), 16);
|
|
const green = Number.parseInt(normalized.slice(2, 4), 16);
|
|
const blue = Number.parseInt(normalized.slice(4, 6), 16);
|
|
return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
|
|
}
|
|
}
|
|
|
|
if (color.startsWith("rgba(")) {
|
|
return color.replace(/rgba\(([^)]+),\s*[\d.]+\)/, `rgba($1, ${alpha})`);
|
|
}
|
|
|
|
if (color.startsWith("rgb(")) {
|
|
return color.replace("rgb(", "rgba(").replace(")", `, ${alpha})`);
|
|
}
|
|
|
|
return `rgba(130, 145, 165, ${alpha})`;
|
|
}
|