backpack-viewer 0.1.3 → 0.2.5
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/README.md +33 -72
- package/bin/serve.js +86 -9
- package/dist/assets/index-BwXh5IUT.js +1 -0
- package/dist/assets/index-DQfh3jIv.css +1 -0
- package/{index.html → dist/index.html} +2 -1
- package/package.json +6 -9
- package/src/api.ts +0 -13
- package/src/canvas.ts +0 -409
- package/src/colors.ts +0 -40
- package/src/info-panel.ts +0 -230
- package/src/layout.ts +0 -138
- package/src/main.ts +0 -68
- package/src/sidebar.ts +0 -80
- package/src/style.css +0 -337
- package/tsconfig.json +0 -15
- package/tsconfig.node.json +0 -13
- package/vite.config.ts +0 -75
package/src/layout.ts
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import type { OntologyData } from "backpack-ontology";
|
|
2
|
-
|
|
3
|
-
export interface LayoutNode {
|
|
4
|
-
id: string;
|
|
5
|
-
x: number;
|
|
6
|
-
y: number;
|
|
7
|
-
vx: number;
|
|
8
|
-
vy: number;
|
|
9
|
-
label: string;
|
|
10
|
-
type: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface LayoutEdge {
|
|
14
|
-
sourceId: string;
|
|
15
|
-
targetId: string;
|
|
16
|
-
type: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface LayoutState {
|
|
20
|
-
nodes: LayoutNode[];
|
|
21
|
-
edges: LayoutEdge[];
|
|
22
|
-
nodeMap: Map<string, LayoutNode>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const REPULSION = 5000;
|
|
26
|
-
const ATTRACTION = 0.005;
|
|
27
|
-
const REST_LENGTH = 150;
|
|
28
|
-
const DAMPING = 0.9;
|
|
29
|
-
const CENTER_GRAVITY = 0.01;
|
|
30
|
-
const MIN_DISTANCE = 30;
|
|
31
|
-
const MAX_VELOCITY = 50;
|
|
32
|
-
|
|
33
|
-
/** Extract a display label from a node — first string property value, fallback to id. */
|
|
34
|
-
function nodeLabel(properties: Record<string, unknown>, id: string): string {
|
|
35
|
-
for (const value of Object.values(properties)) {
|
|
36
|
-
if (typeof value === "string") return value;
|
|
37
|
-
}
|
|
38
|
-
return id;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Create a layout state from ontology data. Nodes start in a circle. */
|
|
42
|
-
export function createLayout(data: OntologyData): LayoutState {
|
|
43
|
-
const radius = Math.sqrt(data.nodes.length) * REST_LENGTH * 0.5;
|
|
44
|
-
const nodeMap = new Map<string, LayoutNode>();
|
|
45
|
-
|
|
46
|
-
const nodes: LayoutNode[] = data.nodes.map((n, i) => {
|
|
47
|
-
const angle = (2 * Math.PI * i) / data.nodes.length;
|
|
48
|
-
const node: LayoutNode = {
|
|
49
|
-
id: n.id,
|
|
50
|
-
x: Math.cos(angle) * radius,
|
|
51
|
-
y: Math.sin(angle) * radius,
|
|
52
|
-
vx: 0,
|
|
53
|
-
vy: 0,
|
|
54
|
-
label: nodeLabel(n.properties, n.id),
|
|
55
|
-
type: n.type,
|
|
56
|
-
};
|
|
57
|
-
nodeMap.set(n.id, node);
|
|
58
|
-
return node;
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const edges: LayoutEdge[] = data.edges.map((e) => ({
|
|
62
|
-
sourceId: e.sourceId,
|
|
63
|
-
targetId: e.targetId,
|
|
64
|
-
type: e.type,
|
|
65
|
-
}));
|
|
66
|
-
|
|
67
|
-
return { nodes, edges, nodeMap };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Run one tick of the force simulation. Returns new alpha. */
|
|
71
|
-
export function tick(state: LayoutState, alpha: number): number {
|
|
72
|
-
const { nodes, edges, nodeMap } = state;
|
|
73
|
-
|
|
74
|
-
// Repulsion — all pairs
|
|
75
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
76
|
-
for (let j = i + 1; j < nodes.length; j++) {
|
|
77
|
-
const a = nodes[i];
|
|
78
|
-
const b = nodes[j];
|
|
79
|
-
let dx = b.x - a.x;
|
|
80
|
-
let dy = b.y - a.y;
|
|
81
|
-
let dist = Math.sqrt(dx * dx + dy * dy);
|
|
82
|
-
if (dist < MIN_DISTANCE) dist = MIN_DISTANCE;
|
|
83
|
-
|
|
84
|
-
const force = (REPULSION * alpha) / (dist * dist);
|
|
85
|
-
const fx = (dx / dist) * force;
|
|
86
|
-
const fy = (dy / dist) * force;
|
|
87
|
-
|
|
88
|
-
a.vx -= fx;
|
|
89
|
-
a.vy -= fy;
|
|
90
|
-
b.vx += fx;
|
|
91
|
-
b.vy += fy;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Attraction — along edges
|
|
96
|
-
for (const edge of edges) {
|
|
97
|
-
const source = nodeMap.get(edge.sourceId);
|
|
98
|
-
const target = nodeMap.get(edge.targetId);
|
|
99
|
-
if (!source || !target) continue;
|
|
100
|
-
|
|
101
|
-
const dx = target.x - source.x;
|
|
102
|
-
const dy = target.y - source.y;
|
|
103
|
-
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
104
|
-
if (dist === 0) continue;
|
|
105
|
-
|
|
106
|
-
const force = ATTRACTION * (dist - REST_LENGTH) * alpha;
|
|
107
|
-
const fx = (dx / dist) * force;
|
|
108
|
-
const fy = (dy / dist) * force;
|
|
109
|
-
|
|
110
|
-
source.vx += fx;
|
|
111
|
-
source.vy += fy;
|
|
112
|
-
target.vx -= fx;
|
|
113
|
-
target.vy -= fy;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Centering gravity
|
|
117
|
-
for (const node of nodes) {
|
|
118
|
-
node.vx -= node.x * CENTER_GRAVITY * alpha;
|
|
119
|
-
node.vy -= node.y * CENTER_GRAVITY * alpha;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Integrate — update positions, apply damping, clamp velocity
|
|
123
|
-
for (const node of nodes) {
|
|
124
|
-
node.vx *= DAMPING;
|
|
125
|
-
node.vy *= DAMPING;
|
|
126
|
-
|
|
127
|
-
const speed = Math.sqrt(node.vx * node.vx + node.vy * node.vy);
|
|
128
|
-
if (speed > MAX_VELOCITY) {
|
|
129
|
-
node.vx = (node.vx / speed) * MAX_VELOCITY;
|
|
130
|
-
node.vy = (node.vy / speed) * MAX_VELOCITY;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
node.x += node.vx;
|
|
134
|
-
node.y += node.vy;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return alpha * 0.995;
|
|
138
|
-
}
|
package/src/main.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import type { OntologyData } from "backpack-ontology";
|
|
2
|
-
import { listOntologies, loadOntology } from "./api";
|
|
3
|
-
import { initSidebar } from "./sidebar";
|
|
4
|
-
import { initCanvas } from "./canvas";
|
|
5
|
-
import { initInfoPanel } from "./info-panel";
|
|
6
|
-
import "./style.css";
|
|
7
|
-
|
|
8
|
-
let activeOntology = "";
|
|
9
|
-
let currentData: OntologyData | null = null;
|
|
10
|
-
|
|
11
|
-
async function main() {
|
|
12
|
-
const infoPanel = initInfoPanel(
|
|
13
|
-
document.getElementById("canvas-container")!
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
const canvas = initCanvas(
|
|
17
|
-
document.getElementById("canvas-container")!,
|
|
18
|
-
(nodeId) => {
|
|
19
|
-
if (nodeId && currentData) {
|
|
20
|
-
infoPanel.show(nodeId, currentData);
|
|
21
|
-
} else {
|
|
22
|
-
infoPanel.hide();
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
const sidebar = initSidebar(
|
|
28
|
-
document.getElementById("sidebar")!,
|
|
29
|
-
async (name) => {
|
|
30
|
-
activeOntology = name;
|
|
31
|
-
sidebar.setActive(name);
|
|
32
|
-
infoPanel.hide();
|
|
33
|
-
currentData = await loadOntology(name);
|
|
34
|
-
canvas.loadGraph(currentData);
|
|
35
|
-
}
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
// Load ontology list
|
|
39
|
-
const summaries = await listOntologies();
|
|
40
|
-
sidebar.setSummaries(summaries);
|
|
41
|
-
|
|
42
|
-
// Auto-load first ontology
|
|
43
|
-
if (summaries.length > 0) {
|
|
44
|
-
activeOntology = summaries[0].name;
|
|
45
|
-
sidebar.setActive(activeOntology);
|
|
46
|
-
currentData = await loadOntology(activeOntology);
|
|
47
|
-
canvas.loadGraph(currentData);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Live reload — when Claude adds nodes via MCP, re-fetch and re-render
|
|
51
|
-
if (import.meta.hot) {
|
|
52
|
-
import.meta.hot.on("ontology-change", async () => {
|
|
53
|
-
const updated = await listOntologies();
|
|
54
|
-
sidebar.setSummaries(updated);
|
|
55
|
-
|
|
56
|
-
if (activeOntology) {
|
|
57
|
-
try {
|
|
58
|
-
currentData = await loadOntology(activeOntology);
|
|
59
|
-
canvas.loadGraph(currentData);
|
|
60
|
-
} catch {
|
|
61
|
-
// Ontology may have been deleted
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
main();
|
package/src/sidebar.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import type { OntologySummary } from "backpack-ontology";
|
|
2
|
-
|
|
3
|
-
export function initSidebar(
|
|
4
|
-
container: HTMLElement,
|
|
5
|
-
onSelect: (name: string) => void
|
|
6
|
-
) {
|
|
7
|
-
// Build DOM
|
|
8
|
-
const heading = document.createElement("h2");
|
|
9
|
-
heading.textContent = "Backpack Ontology Viewer";
|
|
10
|
-
|
|
11
|
-
const input = document.createElement("input");
|
|
12
|
-
input.type = "text";
|
|
13
|
-
input.placeholder = "Filter...";
|
|
14
|
-
input.id = "filter";
|
|
15
|
-
|
|
16
|
-
const list = document.createElement("ul");
|
|
17
|
-
list.id = "ontology-list";
|
|
18
|
-
|
|
19
|
-
const footer = document.createElement("div");
|
|
20
|
-
footer.className = "sidebar-footer";
|
|
21
|
-
footer.innerHTML =
|
|
22
|
-
'<a href="mailto:support@backpackontology.com">support@backpackontology.com</a>' +
|
|
23
|
-
"<span>Feedback, support & sponsorship</span>";
|
|
24
|
-
|
|
25
|
-
container.appendChild(heading);
|
|
26
|
-
container.appendChild(input);
|
|
27
|
-
container.appendChild(list);
|
|
28
|
-
container.appendChild(footer);
|
|
29
|
-
|
|
30
|
-
let items: HTMLLIElement[] = [];
|
|
31
|
-
let activeName = "";
|
|
32
|
-
|
|
33
|
-
// Filter
|
|
34
|
-
input.addEventListener("input", () => {
|
|
35
|
-
const query = input.value.toLowerCase();
|
|
36
|
-
for (const item of items) {
|
|
37
|
-
const name = item.dataset.name ?? "";
|
|
38
|
-
item.style.display = name.includes(query) ? "" : "none";
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
setSummaries(summaries: OntologySummary[]) {
|
|
44
|
-
list.innerHTML = "";
|
|
45
|
-
items = summaries.map((s) => {
|
|
46
|
-
const li = document.createElement("li");
|
|
47
|
-
li.className = "ontology-item";
|
|
48
|
-
li.dataset.name = s.name;
|
|
49
|
-
|
|
50
|
-
const nameSpan = document.createElement("span");
|
|
51
|
-
nameSpan.className = "name";
|
|
52
|
-
nameSpan.textContent = s.name;
|
|
53
|
-
|
|
54
|
-
const statsSpan = document.createElement("span");
|
|
55
|
-
statsSpan.className = "stats";
|
|
56
|
-
statsSpan.textContent = `${s.nodeCount} nodes, ${s.edgeCount} edges`;
|
|
57
|
-
|
|
58
|
-
li.appendChild(nameSpan);
|
|
59
|
-
li.appendChild(statsSpan);
|
|
60
|
-
|
|
61
|
-
li.addEventListener("click", () => onSelect(s.name));
|
|
62
|
-
|
|
63
|
-
list.appendChild(li);
|
|
64
|
-
return li;
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// Re-apply active state
|
|
68
|
-
if (activeName) {
|
|
69
|
-
this.setActive(activeName);
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
setActive(name: string) {
|
|
74
|
-
activeName = name;
|
|
75
|
-
for (const item of items) {
|
|
76
|
-
item.classList.toggle("active", item.dataset.name === name);
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
}
|
package/src/style.css
DELETED
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
* {
|
|
2
|
-
margin: 0;
|
|
3
|
-
padding: 0;
|
|
4
|
-
box-sizing: border-box;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
body {
|
|
8
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
9
|
-
background: #141414;
|
|
10
|
-
color: #d4d4d4;
|
|
11
|
-
overflow: hidden;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
#app {
|
|
15
|
-
display: flex;
|
|
16
|
-
height: 100vh;
|
|
17
|
-
width: 100vw;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/* --- Sidebar --- */
|
|
21
|
-
|
|
22
|
-
#sidebar {
|
|
23
|
-
width: 280px;
|
|
24
|
-
min-width: 280px;
|
|
25
|
-
background: #1a1a1a;
|
|
26
|
-
border-right: 1px solid #2a2a2a;
|
|
27
|
-
display: flex;
|
|
28
|
-
flex-direction: column;
|
|
29
|
-
padding: 16px;
|
|
30
|
-
overflow-y: auto;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
#sidebar h2 {
|
|
34
|
-
font-size: 13px;
|
|
35
|
-
font-weight: 600;
|
|
36
|
-
text-transform: uppercase;
|
|
37
|
-
letter-spacing: 0.05em;
|
|
38
|
-
color: #737373;
|
|
39
|
-
margin-bottom: 14px;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
#sidebar input {
|
|
43
|
-
width: 100%;
|
|
44
|
-
padding: 8px 12px;
|
|
45
|
-
border: 1px solid #2a2a2a;
|
|
46
|
-
border-radius: 6px;
|
|
47
|
-
background: #141414;
|
|
48
|
-
color: #d4d4d4;
|
|
49
|
-
font-size: 13px;
|
|
50
|
-
outline: none;
|
|
51
|
-
margin-bottom: 12px;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
#sidebar input:focus {
|
|
55
|
-
border-color: #d4a27f;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
#sidebar input::placeholder {
|
|
59
|
-
color: #525252;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
#ontology-list {
|
|
63
|
-
list-style: none;
|
|
64
|
-
display: flex;
|
|
65
|
-
flex-direction: column;
|
|
66
|
-
gap: 2px;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.ontology-item {
|
|
70
|
-
padding: 10px 12px;
|
|
71
|
-
border-radius: 6px;
|
|
72
|
-
cursor: pointer;
|
|
73
|
-
transition: background 0.15s;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.ontology-item:hover {
|
|
77
|
-
background: #222222;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.ontology-item.active {
|
|
81
|
-
background: #2a2a2a;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.ontology-item .name {
|
|
85
|
-
display: block;
|
|
86
|
-
font-size: 13px;
|
|
87
|
-
font-weight: 500;
|
|
88
|
-
color: #d4d4d4;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.ontology-item .stats {
|
|
92
|
-
display: block;
|
|
93
|
-
font-size: 11px;
|
|
94
|
-
color: #525252;
|
|
95
|
-
margin-top: 2px;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/* --- Sidebar Footer --- */
|
|
99
|
-
|
|
100
|
-
.sidebar-footer {
|
|
101
|
-
margin-top: auto;
|
|
102
|
-
padding-top: 16px;
|
|
103
|
-
border-top: 1px solid #2a2a2a;
|
|
104
|
-
text-align: center;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.sidebar-footer a {
|
|
108
|
-
display: block;
|
|
109
|
-
font-size: 12px;
|
|
110
|
-
font-weight: 500;
|
|
111
|
-
color: #d4a27f;
|
|
112
|
-
text-decoration: none;
|
|
113
|
-
margin-bottom: 4px;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.sidebar-footer a:hover {
|
|
117
|
-
color: #e8b898;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.sidebar-footer span {
|
|
121
|
-
display: block;
|
|
122
|
-
font-size: 10px;
|
|
123
|
-
color: #525252;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/* --- Canvas --- */
|
|
127
|
-
|
|
128
|
-
#canvas-container {
|
|
129
|
-
flex: 1;
|
|
130
|
-
position: relative;
|
|
131
|
-
overflow: hidden;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
#graph-canvas {
|
|
135
|
-
position: absolute;
|
|
136
|
-
top: 0;
|
|
137
|
-
left: 0;
|
|
138
|
-
width: 100%;
|
|
139
|
-
height: 100%;
|
|
140
|
-
cursor: grab;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
#graph-canvas:active {
|
|
144
|
-
cursor: grabbing;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/* --- Info Panel --- */
|
|
148
|
-
|
|
149
|
-
.info-panel {
|
|
150
|
-
position: absolute;
|
|
151
|
-
top: 16px;
|
|
152
|
-
right: 16px;
|
|
153
|
-
width: 360px;
|
|
154
|
-
max-height: calc(100vh - 32px);
|
|
155
|
-
background: #1a1a1a;
|
|
156
|
-
border: 1px solid #2a2a2a;
|
|
157
|
-
border-radius: 10px;
|
|
158
|
-
overflow-y: auto;
|
|
159
|
-
padding: 20px;
|
|
160
|
-
z-index: 10;
|
|
161
|
-
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.info-panel.hidden {
|
|
165
|
-
display: none;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.info-close {
|
|
169
|
-
position: absolute;
|
|
170
|
-
top: 12px;
|
|
171
|
-
right: 14px;
|
|
172
|
-
background: none;
|
|
173
|
-
border: none;
|
|
174
|
-
color: #737373;
|
|
175
|
-
font-size: 20px;
|
|
176
|
-
cursor: pointer;
|
|
177
|
-
line-height: 1;
|
|
178
|
-
padding: 4px;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.info-close:hover {
|
|
182
|
-
color: #d4d4d4;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/* Header */
|
|
186
|
-
|
|
187
|
-
.info-header {
|
|
188
|
-
margin-bottom: 16px;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.info-type-badge {
|
|
192
|
-
display: inline-block;
|
|
193
|
-
padding: 3px 10px;
|
|
194
|
-
border-radius: 12px;
|
|
195
|
-
font-size: 11px;
|
|
196
|
-
font-weight: 600;
|
|
197
|
-
color: #141414;
|
|
198
|
-
margin-bottom: 8px;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.info-label {
|
|
202
|
-
font-size: 18px;
|
|
203
|
-
font-weight: 600;
|
|
204
|
-
color: #e5e5e5;
|
|
205
|
-
margin-bottom: 4px;
|
|
206
|
-
word-break: break-word;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
.info-id {
|
|
210
|
-
display: block;
|
|
211
|
-
font-size: 11px;
|
|
212
|
-
color: #525252;
|
|
213
|
-
font-family: monospace;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/* Sections */
|
|
217
|
-
|
|
218
|
-
.info-section {
|
|
219
|
-
margin-bottom: 16px;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.info-section-title {
|
|
223
|
-
font-size: 11px;
|
|
224
|
-
font-weight: 600;
|
|
225
|
-
text-transform: uppercase;
|
|
226
|
-
letter-spacing: 0.05em;
|
|
227
|
-
color: #737373;
|
|
228
|
-
margin-bottom: 8px;
|
|
229
|
-
padding-bottom: 4px;
|
|
230
|
-
border-bottom: 1px solid #2a2a2a;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/* Properties */
|
|
234
|
-
|
|
235
|
-
.info-props {
|
|
236
|
-
display: grid;
|
|
237
|
-
grid-template-columns: auto 1fr;
|
|
238
|
-
gap: 4px 12px;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
.info-props dt {
|
|
242
|
-
font-size: 12px;
|
|
243
|
-
color: #737373;
|
|
244
|
-
padding-top: 2px;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.info-props dd {
|
|
248
|
-
font-size: 12px;
|
|
249
|
-
color: #d4d4d4;
|
|
250
|
-
word-break: break-word;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
.info-value {
|
|
254
|
-
white-space: pre-wrap;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
.info-array {
|
|
258
|
-
display: flex;
|
|
259
|
-
flex-wrap: wrap;
|
|
260
|
-
gap: 4px;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
.info-tag {
|
|
264
|
-
display: inline-block;
|
|
265
|
-
padding: 2px 8px;
|
|
266
|
-
background: #222222;
|
|
267
|
-
border-radius: 4px;
|
|
268
|
-
font-size: 11px;
|
|
269
|
-
color: #a3a3a3;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
.info-json {
|
|
273
|
-
font-size: 11px;
|
|
274
|
-
font-family: monospace;
|
|
275
|
-
color: #a3a3a3;
|
|
276
|
-
background: #111111;
|
|
277
|
-
padding: 6px 8px;
|
|
278
|
-
border-radius: 4px;
|
|
279
|
-
overflow-x: auto;
|
|
280
|
-
white-space: pre;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/* Connections */
|
|
284
|
-
|
|
285
|
-
.info-connections {
|
|
286
|
-
list-style: none;
|
|
287
|
-
display: flex;
|
|
288
|
-
flex-direction: column;
|
|
289
|
-
gap: 6px;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
.info-connection {
|
|
293
|
-
display: flex;
|
|
294
|
-
align-items: center;
|
|
295
|
-
gap: 6px;
|
|
296
|
-
padding: 6px 8px;
|
|
297
|
-
background: #1e1e1e;
|
|
298
|
-
border-radius: 6px;
|
|
299
|
-
font-size: 12px;
|
|
300
|
-
flex-wrap: wrap;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
.info-target-dot {
|
|
304
|
-
width: 8px;
|
|
305
|
-
height: 8px;
|
|
306
|
-
border-radius: 50%;
|
|
307
|
-
flex-shrink: 0;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
.info-arrow {
|
|
311
|
-
color: #525252;
|
|
312
|
-
font-size: 14px;
|
|
313
|
-
flex-shrink: 0;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
.info-edge-type {
|
|
317
|
-
color: #737373;
|
|
318
|
-
font-size: 11px;
|
|
319
|
-
font-weight: 500;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
.info-target {
|
|
323
|
-
color: #d4d4d4;
|
|
324
|
-
font-weight: 500;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
.info-edge-props {
|
|
328
|
-
width: 100%;
|
|
329
|
-
padding-top: 4px;
|
|
330
|
-
padding-left: 20px;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
.info-edge-prop {
|
|
334
|
-
display: block;
|
|
335
|
-
font-size: 11px;
|
|
336
|
-
color: #525252;
|
|
337
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"forceConsistentCasingInFileNames": true,
|
|
10
|
-
"outDir": "./dist",
|
|
11
|
-
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
12
|
-
"types": ["vite/client"]
|
|
13
|
-
},
|
|
14
|
-
"include": ["src/**/*.ts"]
|
|
15
|
-
}
|
package/tsconfig.node.json
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"forceConsistentCasingInFileNames": true,
|
|
10
|
-
"types": ["node"]
|
|
11
|
-
},
|
|
12
|
-
"include": ["vite.config.ts"]
|
|
13
|
-
}
|
package/vite.config.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { defineConfig, type Plugin } from "vite";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { JsonFileBackend, dataDir } from "backpack-ontology";
|
|
5
|
-
import type { StorageBackend } from "backpack-ontology";
|
|
6
|
-
|
|
7
|
-
function ontologyApiPlugin(): Plugin {
|
|
8
|
-
let storage: StorageBackend;
|
|
9
|
-
|
|
10
|
-
return {
|
|
11
|
-
name: "ontology-api",
|
|
12
|
-
|
|
13
|
-
configureServer(server) {
|
|
14
|
-
storage = new JsonFileBackend();
|
|
15
|
-
storage.initialize();
|
|
16
|
-
|
|
17
|
-
// Watch the ontologies directory for live updates from Claude/MCP
|
|
18
|
-
const ontologiesDir = path.join(dataDir(), "ontologies");
|
|
19
|
-
try {
|
|
20
|
-
fs.watch(ontologiesDir, { recursive: true }, () => {
|
|
21
|
-
server.ws.send({
|
|
22
|
-
type: "custom",
|
|
23
|
-
event: "ontology-change",
|
|
24
|
-
data: {},
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
} catch {
|
|
28
|
-
// Directory may not exist yet
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
server.middlewares.use((req, res, next) => {
|
|
32
|
-
if (!req.url?.startsWith("/api/ontologies")) return next();
|
|
33
|
-
|
|
34
|
-
const urlPath = req.url.replace(/\?.*$/, "");
|
|
35
|
-
|
|
36
|
-
// GET /api/ontologies
|
|
37
|
-
if (urlPath === "/api/ontologies") {
|
|
38
|
-
storage
|
|
39
|
-
.listOntologies()
|
|
40
|
-
.then((summaries) => {
|
|
41
|
-
res.setHeader("Content-Type", "application/json");
|
|
42
|
-
res.end(JSON.stringify(summaries));
|
|
43
|
-
})
|
|
44
|
-
.catch(() => {
|
|
45
|
-
res.setHeader("Content-Type", "application/json");
|
|
46
|
-
res.end("[]");
|
|
47
|
-
});
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// GET /api/ontologies/:name
|
|
52
|
-
const name = decodeURIComponent(
|
|
53
|
-
urlPath.replace("/api/ontologies/", "")
|
|
54
|
-
);
|
|
55
|
-
if (!name) return next();
|
|
56
|
-
|
|
57
|
-
storage
|
|
58
|
-
.loadOntology(name)
|
|
59
|
-
.then((data) => {
|
|
60
|
-
res.setHeader("Content-Type", "application/json");
|
|
61
|
-
res.end(JSON.stringify(data));
|
|
62
|
-
})
|
|
63
|
-
.catch(() => {
|
|
64
|
-
res.statusCode = 404;
|
|
65
|
-
res.setHeader("Content-Type", "application/json");
|
|
66
|
-
res.end(JSON.stringify({ error: "Ontology not found" }));
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export default defineConfig({
|
|
74
|
-
plugins: [ontologyApiPlugin()],
|
|
75
|
-
});
|