gcsdk 1.0.6 → 1.0.9
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 +1 -0
- package/dist/index.d.ts +20 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -9
- package/dist/lib/utils.d.ts +44 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +288 -0
- package/dist/react/ConversationGraph.d.ts +9 -0
- package/dist/react/ConversationGraph.d.ts.map +1 -0
- package/dist/react/ConversationGraph.js +457 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/native/index.node +0 -0
- package/native/package.json +2 -2
- package/native/src/lib.rs +222 -18
- package/package.json +21 -1
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState, useCallback, useEffect, useMemo, } from "react";
|
|
3
|
+
import { flattenTree, assignLanes, BRANCH_COLORS } from "../lib/utils";
|
|
4
|
+
// Layout constants
|
|
5
|
+
const ROW_HEIGHT = 70;
|
|
6
|
+
const LANE_WIDTH = 40;
|
|
7
|
+
const NODE_RADIUS = 6;
|
|
8
|
+
const START_X = 50;
|
|
9
|
+
const START_Y = 60;
|
|
10
|
+
const TEXT_OFFSET_X = 30;
|
|
11
|
+
export function ConversationGraph({ history, onNodeClick, selectedNodeId, onBranch, }) {
|
|
12
|
+
const canvasRef = useRef(null);
|
|
13
|
+
const containerRef = useRef(null);
|
|
14
|
+
const inputRef = useRef(null);
|
|
15
|
+
const [viewport, setViewport] = useState({ x: 0, y: 0, zoom: 1 });
|
|
16
|
+
const [isPanning, setIsPanning] = useState(false);
|
|
17
|
+
const [panStart, setPanStart] = useState({ x: 0, y: 0 });
|
|
18
|
+
const [hoveredNode, setHoveredNode] = useState(null);
|
|
19
|
+
const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 });
|
|
20
|
+
const [inputValue, setInputValue] = useState("");
|
|
21
|
+
// Transform tree to layout nodes
|
|
22
|
+
const layoutNodes = useMemo(() => {
|
|
23
|
+
const flatHistory = flattenTree(history);
|
|
24
|
+
return assignLanes(flatHistory);
|
|
25
|
+
}, [history]);
|
|
26
|
+
// Build lookup maps for edges
|
|
27
|
+
const nodeMap = useMemo(() => {
|
|
28
|
+
const map = new Map();
|
|
29
|
+
layoutNodes.forEach((node) => map.set(node.id, node));
|
|
30
|
+
return map;
|
|
31
|
+
}, [layoutNodes]);
|
|
32
|
+
// Get unique branches for legend
|
|
33
|
+
const branches = useMemo(() => {
|
|
34
|
+
const seen = new Map();
|
|
35
|
+
layoutNodes.forEach((node) => {
|
|
36
|
+
if (!seen.has(node.branch_name)) {
|
|
37
|
+
seen.set(node.branch_name, node.color);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return Array.from(seen.entries()).map(([name, color]) => ({ name, color }));
|
|
41
|
+
}, [layoutNodes]);
|
|
42
|
+
// Calculate input position when a node is selected
|
|
43
|
+
const selectedNode = useMemo(() => {
|
|
44
|
+
if (!selectedNodeId)
|
|
45
|
+
return null;
|
|
46
|
+
return layoutNodes.find((n) => n.id === selectedNodeId) || null;
|
|
47
|
+
}, [selectedNodeId, layoutNodes]);
|
|
48
|
+
const inputPosition = useMemo(() => {
|
|
49
|
+
if (!selectedNode)
|
|
50
|
+
return null;
|
|
51
|
+
const canvasX = START_X + selectedNode.lane * LANE_WIDTH;
|
|
52
|
+
const canvasY = START_Y + selectedNode.row * ROW_HEIGHT + 25;
|
|
53
|
+
return {
|
|
54
|
+
x: canvasX * viewport.zoom + viewport.x,
|
|
55
|
+
y: canvasY * viewport.zoom + viewport.y,
|
|
56
|
+
};
|
|
57
|
+
}, [selectedNode, viewport]);
|
|
58
|
+
// Focus input when a node is selected
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (selectedNodeId && inputRef.current) {
|
|
61
|
+
inputRef.current.focus();
|
|
62
|
+
}
|
|
63
|
+
// Clear input when selection changes
|
|
64
|
+
setInputValue("");
|
|
65
|
+
}, [selectedNodeId]);
|
|
66
|
+
// Handle branch submission
|
|
67
|
+
const handleBranchSubmit = useCallback(() => {
|
|
68
|
+
if (!selectedNodeId || !inputValue.trim() || !onBranch)
|
|
69
|
+
return;
|
|
70
|
+
// Generate branch name
|
|
71
|
+
const branchCount = branches.length;
|
|
72
|
+
const branchName = `branch-${branchCount + 1}`;
|
|
73
|
+
onBranch(selectedNodeId, inputValue.trim(), branchName);
|
|
74
|
+
setInputValue("");
|
|
75
|
+
}, [selectedNodeId, inputValue, onBranch, branches.length]);
|
|
76
|
+
const handleInputKeyDown = useCallback((e) => {
|
|
77
|
+
if (e.key === "Enter") {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
handleBranchSubmit();
|
|
80
|
+
}
|
|
81
|
+
else if (e.key === "Escape") {
|
|
82
|
+
setInputValue("");
|
|
83
|
+
// Optionally deselect - parent should handle this via onNodeClick
|
|
84
|
+
}
|
|
85
|
+
}, [handleBranchSubmit]);
|
|
86
|
+
// Handle canvas resize
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const updateSize = () => {
|
|
89
|
+
if (containerRef.current && canvasRef.current) {
|
|
90
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
91
|
+
setCanvasSize({ width: rect.width, height: rect.height });
|
|
92
|
+
canvasRef.current.width = rect.width;
|
|
93
|
+
canvasRef.current.height = rect.height;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
updateSize();
|
|
97
|
+
window.addEventListener("resize", updateSize);
|
|
98
|
+
return () => window.removeEventListener("resize", updateSize);
|
|
99
|
+
}, []);
|
|
100
|
+
// Draw grid background
|
|
101
|
+
const drawGrid = useCallback((ctx, width, height) => {
|
|
102
|
+
const gridSize = 20 * viewport.zoom;
|
|
103
|
+
const offsetX = viewport.x % gridSize;
|
|
104
|
+
const offsetY = viewport.y % gridSize;
|
|
105
|
+
ctx.fillStyle = "rgb(15, 15, 15)";
|
|
106
|
+
ctx.fillRect(0, 0, width, height);
|
|
107
|
+
ctx.fillStyle = "rgba(255, 255, 255, 0.08)";
|
|
108
|
+
for (let x = offsetX; x < width; x += gridSize) {
|
|
109
|
+
for (let y = offsetY; y < height; y += gridSize) {
|
|
110
|
+
ctx.fillRect(x - 1, y - 1, 2, 2);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}, [viewport]);
|
|
114
|
+
// Main render effect
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
const canvas = canvasRef.current;
|
|
117
|
+
if (!canvas)
|
|
118
|
+
return;
|
|
119
|
+
const ctx = canvas.getContext("2d");
|
|
120
|
+
if (!ctx)
|
|
121
|
+
return;
|
|
122
|
+
const dpr = window.devicePixelRatio || 1;
|
|
123
|
+
const rect = canvas.getBoundingClientRect();
|
|
124
|
+
canvas.width = rect.width * dpr;
|
|
125
|
+
canvas.height = rect.height * dpr;
|
|
126
|
+
ctx.scale(dpr, dpr);
|
|
127
|
+
// Draw background grid
|
|
128
|
+
drawGrid(ctx, rect.width, rect.height);
|
|
129
|
+
ctx.save();
|
|
130
|
+
ctx.translate(viewport.x, viewport.y);
|
|
131
|
+
ctx.scale(viewport.zoom, viewport.zoom);
|
|
132
|
+
// Calculate max lane for text positioning
|
|
133
|
+
const maxLane = Math.max(...layoutNodes.map((n) => n.lane), 0);
|
|
134
|
+
// Draw lane guide lines
|
|
135
|
+
for (let lane = 0; lane <= maxLane; lane++) {
|
|
136
|
+
const x = START_X + lane * LANE_WIDTH;
|
|
137
|
+
const nodesInLane = layoutNodes.filter((n) => n.lane === lane);
|
|
138
|
+
if (nodesInLane.length === 0)
|
|
139
|
+
continue;
|
|
140
|
+
const minRow = Math.min(...nodesInLane.map((n) => n.row));
|
|
141
|
+
const maxRow = Math.max(...nodesInLane.map((n) => n.row));
|
|
142
|
+
const color = nodesInLane[0].color;
|
|
143
|
+
ctx.beginPath();
|
|
144
|
+
ctx.strokeStyle = color + "20";
|
|
145
|
+
ctx.lineWidth = 1;
|
|
146
|
+
ctx.setLineDash([3, 3]);
|
|
147
|
+
ctx.moveTo(x, START_Y + minRow * ROW_HEIGHT);
|
|
148
|
+
ctx.lineTo(x, START_Y + (maxRow + 0.5) * ROW_HEIGHT);
|
|
149
|
+
ctx.stroke();
|
|
150
|
+
ctx.setLineDash([]);
|
|
151
|
+
}
|
|
152
|
+
// Draw edges
|
|
153
|
+
layoutNodes.forEach((node) => {
|
|
154
|
+
if (!node.parent_id)
|
|
155
|
+
return;
|
|
156
|
+
const parent = nodeMap.get(node.parent_id);
|
|
157
|
+
if (!parent)
|
|
158
|
+
return;
|
|
159
|
+
const fromX = START_X + parent.lane * LANE_WIDTH;
|
|
160
|
+
const fromY = START_Y + parent.row * ROW_HEIGHT;
|
|
161
|
+
const toX = START_X + node.lane * LANE_WIDTH;
|
|
162
|
+
const toY = START_Y + node.row * ROW_HEIGHT;
|
|
163
|
+
ctx.beginPath();
|
|
164
|
+
ctx.strokeStyle = node.color;
|
|
165
|
+
ctx.lineWidth = 2;
|
|
166
|
+
if (parent.lane === node.lane) {
|
|
167
|
+
// Same lane - straight vertical line
|
|
168
|
+
ctx.moveTo(fromX, fromY);
|
|
169
|
+
ctx.lineTo(toX, toY);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// Different lanes - smooth bezier curve
|
|
173
|
+
const controlPointOffset = Math.abs(toY - fromY) * 0.5;
|
|
174
|
+
ctx.moveTo(fromX, fromY);
|
|
175
|
+
ctx.bezierCurveTo(fromX, fromY + controlPointOffset, toX, toY - controlPointOffset, toX, toY);
|
|
176
|
+
}
|
|
177
|
+
ctx.stroke();
|
|
178
|
+
});
|
|
179
|
+
// Draw nodes
|
|
180
|
+
layoutNodes.forEach((node) => {
|
|
181
|
+
const x = START_X + node.lane * LANE_WIDTH;
|
|
182
|
+
const y = START_Y + node.row * ROW_HEIGHT;
|
|
183
|
+
const isSelected = node.id === selectedNodeId;
|
|
184
|
+
const isHovered = node === hoveredNode;
|
|
185
|
+
// Draw commit node
|
|
186
|
+
ctx.beginPath();
|
|
187
|
+
ctx.arc(x, y, NODE_RADIUS, 0, Math.PI * 2);
|
|
188
|
+
ctx.fillStyle = node.color;
|
|
189
|
+
ctx.fill();
|
|
190
|
+
if (isSelected || isHovered) {
|
|
191
|
+
ctx.strokeStyle = "#ffffff";
|
|
192
|
+
ctx.lineWidth = 2;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
ctx.strokeStyle = "rgba(0, 0, 0, 0.3)";
|
|
196
|
+
ctx.lineWidth = 1;
|
|
197
|
+
}
|
|
198
|
+
ctx.stroke();
|
|
199
|
+
// Draw text - position after all lanes
|
|
200
|
+
const textX = START_X + maxLane * LANE_WIDTH + TEXT_OFFSET_X + 20;
|
|
201
|
+
const textY = y;
|
|
202
|
+
// Commit message
|
|
203
|
+
ctx.fillStyle = "#ffffff";
|
|
204
|
+
ctx.font = "13px system-ui, -apple-system, sans-serif";
|
|
205
|
+
ctx.textBaseline = "middle";
|
|
206
|
+
const truncatedContent = node.content.length > 50
|
|
207
|
+
? node.content.slice(0, 50) + "..."
|
|
208
|
+
: node.content;
|
|
209
|
+
ctx.fillText(truncatedContent, textX, textY - 8);
|
|
210
|
+
// Role and metadata
|
|
211
|
+
ctx.fillStyle = node.role === "user" ? "#58a6ff" : "#8b949e";
|
|
212
|
+
ctx.font = "11px system-ui, -apple-system, sans-serif";
|
|
213
|
+
const roleText = node.role.toUpperCase();
|
|
214
|
+
ctx.fillText(roleText, textX, textY + 8);
|
|
215
|
+
// Branch label for HEAD nodes
|
|
216
|
+
if (node.isHead) {
|
|
217
|
+
const roleWidth = ctx.measureText(roleText).width;
|
|
218
|
+
const labelX = textX + roleWidth + 10;
|
|
219
|
+
// Background pill
|
|
220
|
+
ctx.fillStyle = node.color;
|
|
221
|
+
const labelText = node.branch_name;
|
|
222
|
+
const labelWidth = ctx.measureText(labelText).width + 12;
|
|
223
|
+
ctx.beginPath();
|
|
224
|
+
ctx.roundRect(labelX, textY + 1, labelWidth, 14, 7);
|
|
225
|
+
ctx.fill();
|
|
226
|
+
// Label text
|
|
227
|
+
ctx.fillStyle = "#ffffff";
|
|
228
|
+
ctx.font = "10px system-ui, -apple-system, sans-serif";
|
|
229
|
+
ctx.fillText(labelText, labelX + 6, textY + 8);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
ctx.restore();
|
|
233
|
+
}, [
|
|
234
|
+
layoutNodes,
|
|
235
|
+
nodeMap,
|
|
236
|
+
hoveredNode,
|
|
237
|
+
selectedNodeId,
|
|
238
|
+
canvasSize,
|
|
239
|
+
viewport,
|
|
240
|
+
drawGrid,
|
|
241
|
+
]);
|
|
242
|
+
// Convert screen coords to canvas coords
|
|
243
|
+
const screenToCanvas = useCallback((screenX, screenY) => {
|
|
244
|
+
return {
|
|
245
|
+
x: (screenX - viewport.x) / viewport.zoom,
|
|
246
|
+
y: (screenY - viewport.y) / viewport.zoom,
|
|
247
|
+
};
|
|
248
|
+
}, [viewport]);
|
|
249
|
+
// Find node at position
|
|
250
|
+
const findNodeAtPosition = useCallback((canvasX, canvasY) => {
|
|
251
|
+
for (const node of layoutNodes) {
|
|
252
|
+
const cx = START_X + node.lane * LANE_WIDTH;
|
|
253
|
+
const cy = START_Y + node.row * ROW_HEIGHT;
|
|
254
|
+
const distance = Math.sqrt((canvasX - cx) ** 2 + (canvasY - cy) ** 2);
|
|
255
|
+
if (distance <= NODE_RADIUS + 5) {
|
|
256
|
+
return node;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}, [layoutNodes]);
|
|
261
|
+
const handleMouseDown = (e) => {
|
|
262
|
+
if (e.button !== 0)
|
|
263
|
+
return;
|
|
264
|
+
const canvas = canvasRef.current;
|
|
265
|
+
if (!canvas)
|
|
266
|
+
return;
|
|
267
|
+
const rect = canvas.getBoundingClientRect();
|
|
268
|
+
const x = e.clientX - rect.left;
|
|
269
|
+
const y = e.clientY - rect.top;
|
|
270
|
+
const canvasPos = screenToCanvas(x, y);
|
|
271
|
+
const clickedNode = findNodeAtPosition(canvasPos.x, canvasPos.y);
|
|
272
|
+
if (clickedNode) {
|
|
273
|
+
onNodeClick(clickedNode);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
setIsPanning(true);
|
|
277
|
+
setPanStart({ x: e.clientX - viewport.x, y: e.clientY - viewport.y });
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const handleMouseMove = (e) => {
|
|
281
|
+
const canvas = canvasRef.current;
|
|
282
|
+
if (!canvas)
|
|
283
|
+
return;
|
|
284
|
+
if (isPanning) {
|
|
285
|
+
setViewport((prev) => ({
|
|
286
|
+
...prev,
|
|
287
|
+
x: e.clientX - panStart.x,
|
|
288
|
+
y: e.clientY - panStart.y,
|
|
289
|
+
}));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const rect = canvas.getBoundingClientRect();
|
|
293
|
+
const x = e.clientX - rect.left;
|
|
294
|
+
const y = e.clientY - rect.top;
|
|
295
|
+
const canvasPos = screenToCanvas(x, y);
|
|
296
|
+
const node = findNodeAtPosition(canvasPos.x, canvasPos.y);
|
|
297
|
+
setHoveredNode(node);
|
|
298
|
+
canvas.style.cursor = node ? "pointer" : isPanning ? "grabbing" : "grab";
|
|
299
|
+
};
|
|
300
|
+
const handleMouseUp = () => {
|
|
301
|
+
setIsPanning(false);
|
|
302
|
+
};
|
|
303
|
+
const handleMouseLeave = () => {
|
|
304
|
+
setIsPanning(false);
|
|
305
|
+
setHoveredNode(null);
|
|
306
|
+
};
|
|
307
|
+
const handleWheel = (e) => {
|
|
308
|
+
e.preventDefault();
|
|
309
|
+
const delta = e.deltaY > 0 ? 0.9 : 1.1;
|
|
310
|
+
setViewport((prev) => ({
|
|
311
|
+
...prev,
|
|
312
|
+
zoom: Math.max(0.3, Math.min(3, prev.zoom * delta)),
|
|
313
|
+
}));
|
|
314
|
+
};
|
|
315
|
+
const handleZoomIn = () => {
|
|
316
|
+
setViewport((prev) => ({ ...prev, zoom: Math.min(prev.zoom * 1.2, 3) }));
|
|
317
|
+
};
|
|
318
|
+
const handleZoomOut = () => {
|
|
319
|
+
setViewport((prev) => ({ ...prev, zoom: Math.max(prev.zoom / 1.2, 0.3) }));
|
|
320
|
+
};
|
|
321
|
+
const handleResetView = () => {
|
|
322
|
+
setViewport({ x: 0, y: 0, zoom: 1 });
|
|
323
|
+
};
|
|
324
|
+
return (_jsxs("div", { style: {
|
|
325
|
+
display: "flex",
|
|
326
|
+
flexDirection: "column",
|
|
327
|
+
width: "100%",
|
|
328
|
+
height: "100%",
|
|
329
|
+
background: "#0f0f0f",
|
|
330
|
+
borderRadius: "10px",
|
|
331
|
+
overflow: "hidden",
|
|
332
|
+
}, children: [_jsxs("div", { style: {
|
|
333
|
+
display: "flex",
|
|
334
|
+
justifyContent: "space-between",
|
|
335
|
+
alignItems: "center",
|
|
336
|
+
padding: "12px 16px",
|
|
337
|
+
borderBottom: "1px solid #333",
|
|
338
|
+
background: "#1a1a1a",
|
|
339
|
+
}, children: [_jsx("div", { style: { display: "flex", gap: "16px", flexWrap: "wrap" }, children: branches.map((branch) => (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: "6px" }, children: [_jsx("div", { style: {
|
|
340
|
+
width: "10px",
|
|
341
|
+
height: "10px",
|
|
342
|
+
borderRadius: "50%",
|
|
343
|
+
background: branch.color,
|
|
344
|
+
} }), _jsx("span", { style: {
|
|
345
|
+
fontSize: "12px",
|
|
346
|
+
color: "#c9d1d9",
|
|
347
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
348
|
+
}, children: branch.name })] }, branch.name))) }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [_jsx("button", { onClick: handleZoomOut, style: {
|
|
349
|
+
width: "28px",
|
|
350
|
+
height: "28px",
|
|
351
|
+
display: "flex",
|
|
352
|
+
alignItems: "center",
|
|
353
|
+
justifyContent: "center",
|
|
354
|
+
background: "#2d2d2d",
|
|
355
|
+
border: "1px solid #444",
|
|
356
|
+
borderRadius: "4px",
|
|
357
|
+
cursor: "pointer",
|
|
358
|
+
color: "#c9d1d9",
|
|
359
|
+
}, title: "Zoom out", children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("circle", { cx: "11", cy: "11", r: "8" }), _jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" }), _jsx("line", { x1: "8", y1: "11", x2: "14", y2: "11" })] }) }), _jsx("button", { onClick: handleResetView, style: {
|
|
360
|
+
width: "28px",
|
|
361
|
+
height: "28px",
|
|
362
|
+
display: "flex",
|
|
363
|
+
alignItems: "center",
|
|
364
|
+
justifyContent: "center",
|
|
365
|
+
background: "#2d2d2d",
|
|
366
|
+
border: "1px solid #444",
|
|
367
|
+
borderRadius: "4px",
|
|
368
|
+
cursor: "pointer",
|
|
369
|
+
color: "#c9d1d9",
|
|
370
|
+
}, title: "Reset view", children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("polyline", { points: "15 3 21 3 21 9" }), _jsx("polyline", { points: "9 21 3 21 3 15" }), _jsx("line", { x1: "21", y1: "3", x2: "14", y2: "10" }), _jsx("line", { x1: "3", y1: "21", x2: "10", y2: "14" })] }) }), _jsx("button", { onClick: handleZoomIn, style: {
|
|
371
|
+
width: "28px",
|
|
372
|
+
height: "28px",
|
|
373
|
+
display: "flex",
|
|
374
|
+
alignItems: "center",
|
|
375
|
+
justifyContent: "center",
|
|
376
|
+
background: "#2d2d2d",
|
|
377
|
+
border: "1px solid #444",
|
|
378
|
+
borderRadius: "4px",
|
|
379
|
+
cursor: "pointer",
|
|
380
|
+
color: "#c9d1d9",
|
|
381
|
+
}, title: "Zoom in", children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("circle", { cx: "11", cy: "11", r: "8" }), _jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" }), _jsx("line", { x1: "11", y1: "8", x2: "11", y2: "14" }), _jsx("line", { x1: "8", y1: "11", x2: "14", y2: "11" })] }) }), _jsxs("span", { style: {
|
|
382
|
+
fontSize: "12px",
|
|
383
|
+
color: "#8b949e",
|
|
384
|
+
minWidth: "40px",
|
|
385
|
+
textAlign: "center",
|
|
386
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
387
|
+
}, children: [Math.round(viewport.zoom * 100), "%"] }), _jsxs("div", { style: {
|
|
388
|
+
marginLeft: "8px",
|
|
389
|
+
paddingLeft: "12px",
|
|
390
|
+
borderLeft: "1px solid #444",
|
|
391
|
+
display: "flex",
|
|
392
|
+
alignItems: "center",
|
|
393
|
+
gap: "8px",
|
|
394
|
+
}, children: [_jsxs("span", { style: {
|
|
395
|
+
fontSize: "12px",
|
|
396
|
+
color: "#8b949e",
|
|
397
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
398
|
+
}, children: [branches.length, " branches"] }), _jsxs("span", { style: {
|
|
399
|
+
fontSize: "12px",
|
|
400
|
+
color: "#8b949e",
|
|
401
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
402
|
+
}, children: [layoutNodes.length, " commits"] })] })] })] }), _jsxs("div", { ref: containerRef, style: {
|
|
403
|
+
flex: 1,
|
|
404
|
+
position: "relative",
|
|
405
|
+
overflow: "hidden",
|
|
406
|
+
}, children: [_jsx("canvas", { ref: canvasRef, style: {
|
|
407
|
+
width: "100%",
|
|
408
|
+
height: "100%",
|
|
409
|
+
cursor: "grab",
|
|
410
|
+
}, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseLeave, onWheel: handleWheel }), selectedNodeId && inputPosition && onBranch && (_jsxs("div", { style: {
|
|
411
|
+
position: "absolute",
|
|
412
|
+
left: inputPosition.x,
|
|
413
|
+
top: inputPosition.y,
|
|
414
|
+
transform: "translateX(-6px)",
|
|
415
|
+
display: "flex",
|
|
416
|
+
alignItems: "center",
|
|
417
|
+
gap: "6px",
|
|
418
|
+
zIndex: 10,
|
|
419
|
+
}, children: [_jsx("div", { style: {
|
|
420
|
+
width: "12px",
|
|
421
|
+
height: "2px",
|
|
422
|
+
background: selectedNode?.color || BRANCH_COLORS[0],
|
|
423
|
+
borderRadius: "1px",
|
|
424
|
+
} }), _jsxs("div", { style: {
|
|
425
|
+
display: "flex",
|
|
426
|
+
alignItems: "center",
|
|
427
|
+
background: "#1a1a2e",
|
|
428
|
+
border: `1px solid ${selectedNode?.color || BRANCH_COLORS[0]}`,
|
|
429
|
+
borderRadius: "6px",
|
|
430
|
+
padding: "4px 8px",
|
|
431
|
+
boxShadow: `0 0 12px ${selectedNode?.color || BRANCH_COLORS[0]}40`,
|
|
432
|
+
}, children: [_jsx("input", { ref: inputRef, type: "text", value: inputValue, onChange: (e) => setInputValue(e.target.value), onKeyDown: handleInputKeyDown, placeholder: "Type message to branch...", style: {
|
|
433
|
+
width: "200px",
|
|
434
|
+
background: "transparent",
|
|
435
|
+
border: "none",
|
|
436
|
+
outline: "none",
|
|
437
|
+
color: "#c9d1d9",
|
|
438
|
+
fontSize: "12px",
|
|
439
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
440
|
+
} }), _jsx("button", { onClick: handleBranchSubmit, disabled: !inputValue.trim(), style: {
|
|
441
|
+
width: "24px",
|
|
442
|
+
height: "24px",
|
|
443
|
+
display: "flex",
|
|
444
|
+
alignItems: "center",
|
|
445
|
+
justifyContent: "center",
|
|
446
|
+
background: inputValue.trim()
|
|
447
|
+
? selectedNode?.color || BRANCH_COLORS[0]
|
|
448
|
+
: "#333",
|
|
449
|
+
border: "none",
|
|
450
|
+
borderRadius: "4px",
|
|
451
|
+
cursor: inputValue.trim() ? "pointer" : "default",
|
|
452
|
+
color: "#fff",
|
|
453
|
+
marginLeft: "6px",
|
|
454
|
+
opacity: inputValue.trim() ? 1 : 0.5,
|
|
455
|
+
transition: "all 0.15s ease",
|
|
456
|
+
}, title: "Create branch", children: _jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }), _jsx("polyline", { points: "19 12 12 19 5 12" })] }) })] })] }))] })] }));
|
|
457
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface Message {
|
|
2
|
+
role: "user" | "assistant" | "system";
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
export interface HistoryNode {
|
|
6
|
+
id: string;
|
|
7
|
+
parent_id: string | null;
|
|
8
|
+
role: "user" | "assistant" | "system";
|
|
9
|
+
content: string;
|
|
10
|
+
branch_name: string;
|
|
11
|
+
}
|
|
12
|
+
export interface TreeNode {
|
|
13
|
+
id: string;
|
|
14
|
+
parent_id: string | null;
|
|
15
|
+
role: "user" | "assistant" | "system";
|
|
16
|
+
content: string;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
branches: string[];
|
|
19
|
+
children: TreeNode[];
|
|
20
|
+
}
|
|
21
|
+
export interface IGitChat {
|
|
22
|
+
reset(): void;
|
|
23
|
+
commit(message: Message, files: Record<string, string>): Promise<string>;
|
|
24
|
+
merge(branchName: string, baseBranchName: string): void;
|
|
25
|
+
createBranch(branchName: string, baseBranchName: string): void;
|
|
26
|
+
getHistory(): HistoryNode[];
|
|
27
|
+
checkout(branchName: string): void;
|
|
28
|
+
createBranchFromCommit(branchName: string, commitId: string): boolean;
|
|
29
|
+
getHistoryToCommit(commitId: string): HistoryNode[];
|
|
30
|
+
branchFromCommit(branchName: string, commitId: string): boolean;
|
|
31
|
+
renderTree(): Promise<string>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,QAAQ,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzE,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACxD,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/D,UAAU,IAAI,WAAW,EAAE,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACtE,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,EAAE,CAAC;IACpD,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAChE,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/native/index.node
CHANGED
|
Binary file
|
package/native/package.json
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"test": "cargo test",
|
|
8
8
|
"cargo-build": "cargo build --message-format=json-render-diagnostics > cargo.log",
|
|
9
9
|
"cross-build": "cross build --message-format=json-render-diagnostics > cross.log",
|
|
10
|
-
"postcargo-build": "
|
|
11
|
-
"postcross-build": "
|
|
10
|
+
"postcargo-build": "node node_modules/.bin/neon dist -n native < cargo.log",
|
|
11
|
+
"postcross-build": "node node_modules/.bin/neon dist -n native -m /target < cross.log",
|
|
12
12
|
"debug": "npm run cargo-build --",
|
|
13
13
|
"build": "npm run cargo-build -- --release",
|
|
14
14
|
"cross": "npm run cross-build -- --release"
|