@schlessera/brain-ui-react 0.16.0 → 0.17.0
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/dist/components/chat/risk-hints.js +1 -1
- package/dist/components/chat/risk-hints.js.map +1 -1
- package/dist/components/chat/tool-views.d.ts +2 -19
- package/dist/components/chat/tool-views.d.ts.map +1 -1
- package/dist/components/chat/tool-views.js +3 -125
- package/dist/components/chat/tool-views.js.map +1 -1
- package/dist/lib/diff.d.ts +19 -0
- package/dist/lib/diff.d.ts.map +1 -0
- package/dist/lib/diff.js +129 -0
- package/dist/lib/diff.js.map +1 -0
- package/dist/stores/chat-store.js +1 -1
- package/dist/stores/chat-store.js.map +1 -1
- package/dist/stores/graph-store.d.ts.map +1 -1
- package/dist/stores/graph-store.js +14 -1
- package/dist/stores/graph-store.js.map +1 -1
- package/dist/theme.css +435 -0
- package/package.json +14 -8
- package/src/components/chat/risk-hints.ts +1 -1
- package/src/components/chat/tool-views.tsx +4 -137
- package/src/lib/diff.ts +140 -0
- package/src/stores/chat-store.ts +1 -1
- package/src/stores/graph-store.ts +14 -1
package/src/lib/diff.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Standalone text-diff engine behind the Edit tool's diff view: line-level
|
|
2
|
+
// LCS with word-level refinement for single-line change pairs. Pure data —
|
|
3
|
+
// no React; rendering stays in components/chat/tool-views.tsx.
|
|
4
|
+
|
|
5
|
+
// ------------------------------------------------------------
|
|
6
|
+
// Diff helpers — line-level LCS with word-level refinement
|
|
7
|
+
// ------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
type DiffOp<T> = { kind: "same" | "del" | "ins"; value: T };
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Hand-rolled LCS diff (standard DP + forward walk) so we avoid pulling in the
|
|
13
|
+
* `diff` package. Returns ops in source order: `same` where the sequences
|
|
14
|
+
* agree, `del` for items only in `a`, `ins` for items only in `b`.
|
|
15
|
+
*/
|
|
16
|
+
function diffSeq<T>(
|
|
17
|
+
a: T[],
|
|
18
|
+
b: T[],
|
|
19
|
+
eq: (x: T, y: T) => boolean = (x, y) => x === y
|
|
20
|
+
): DiffOp<T>[] {
|
|
21
|
+
const m = a.length;
|
|
22
|
+
const n = b.length;
|
|
23
|
+
// dp[i][j] = length of the LCS of a[i:] and b[j:]
|
|
24
|
+
const dp: number[][] = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
25
|
+
for (let i = m - 1; i >= 0; i--) {
|
|
26
|
+
for (let j = n - 1; j >= 0; j--) {
|
|
27
|
+
dp[i][j] = eq(a[i], b[j])
|
|
28
|
+
? dp[i + 1][j + 1] + 1
|
|
29
|
+
: Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const ops: DiffOp<T>[] = [];
|
|
33
|
+
let i = 0;
|
|
34
|
+
let j = 0;
|
|
35
|
+
while (i < m && j < n) {
|
|
36
|
+
if (eq(a[i], b[j])) {
|
|
37
|
+
ops.push({ kind: "same", value: a[i] });
|
|
38
|
+
i++;
|
|
39
|
+
j++;
|
|
40
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
41
|
+
ops.push({ kind: "del", value: a[i] });
|
|
42
|
+
i++;
|
|
43
|
+
} else {
|
|
44
|
+
ops.push({ kind: "ins", value: b[j] });
|
|
45
|
+
j++;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
while (i < m) ops.push({ kind: "del", value: a[i++] });
|
|
49
|
+
while (j < n) ops.push({ kind: "ins", value: b[j++] });
|
|
50
|
+
return ops;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A word token, flagged when it differs from its counterpart line. */
|
|
54
|
+
export type WordToken = { text: string; changed: boolean };
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Token-level LCS for a single deleted/inserted line pair. Splitting on
|
|
58
|
+
* `/(\s+)/` keeps the whitespace as its own tokens so we can reassemble the
|
|
59
|
+
* line exactly. Unchanged tokens are shared between both sides.
|
|
60
|
+
*/
|
|
61
|
+
function wordDiff(oldLine: string, newLine: string): { del: WordToken[]; ins: WordToken[] } {
|
|
62
|
+
const a = oldLine.split(/(\s+)/);
|
|
63
|
+
const b = newLine.split(/(\s+)/);
|
|
64
|
+
// Token-level LCS is O(words²); a pathological minified line would be slow.
|
|
65
|
+
// Above the cap, skip word refinement and flag the whole line as changed.
|
|
66
|
+
if (a.length * b.length > 10_000) {
|
|
67
|
+
return {
|
|
68
|
+
del: [{ text: oldLine, changed: true }],
|
|
69
|
+
ins: [{ text: newLine, changed: true }],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const ops = diffSeq(a, b);
|
|
73
|
+
const del: WordToken[] = [];
|
|
74
|
+
const ins: WordToken[] = [];
|
|
75
|
+
for (const op of ops) {
|
|
76
|
+
if (op.kind === "same") {
|
|
77
|
+
del.push({ text: op.value, changed: false });
|
|
78
|
+
ins.push({ text: op.value, changed: false });
|
|
79
|
+
} else if (op.kind === "del") {
|
|
80
|
+
del.push({ text: op.value, changed: true });
|
|
81
|
+
} else {
|
|
82
|
+
ins.push({ text: op.value, changed: true });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { del, ins };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A rendered diff row. `tokens` is set only for word-refined single-line edits. */
|
|
89
|
+
export type DiffRow = {
|
|
90
|
+
kind: "same" | "del" | "ins";
|
|
91
|
+
line: string;
|
|
92
|
+
tokens: WordToken[] | null;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Merge `old_string`/`new_string` into a single diff: unchanged lines appear
|
|
97
|
+
* once as neutral, deletions/insertions keep the red/green treatment. A change
|
|
98
|
+
* block that is exactly one deleted line against one inserted line gets
|
|
99
|
+
* word-level highlighting.
|
|
100
|
+
*/
|
|
101
|
+
export function computeDiffRows(oldStr: string, newStr: string): DiffRow[] {
|
|
102
|
+
const oldLines = oldStr.split("\n");
|
|
103
|
+
const newLines = newStr.split("\n");
|
|
104
|
+
// Line-level LCS is O(m×n) in time and memory; a huge Edit (thousands of
|
|
105
|
+
// lines) would build a massive DP matrix and hang the UI. Above the cap, fall
|
|
106
|
+
// back to a naive "all old removed, all new added" diff.
|
|
107
|
+
if (oldLines.length * newLines.length > 250_000) {
|
|
108
|
+
return [
|
|
109
|
+
...oldLines.map((line): DiffRow => ({ kind: "del", line, tokens: null })),
|
|
110
|
+
...newLines.map((line): DiffRow => ({ kind: "ins", line, tokens: null })),
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
const ops = diffSeq(oldLines, newLines);
|
|
114
|
+
const rows: DiffRow[] = [];
|
|
115
|
+
let idx = 0;
|
|
116
|
+
while (idx < ops.length) {
|
|
117
|
+
if (ops[idx].kind === "same") {
|
|
118
|
+
rows.push({ kind: "same", line: ops[idx].value, tokens: null });
|
|
119
|
+
idx++;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
// Collect a maximal run of changes, grouping deletes before inserts.
|
|
123
|
+
const dels: string[] = [];
|
|
124
|
+
const inss: string[] = [];
|
|
125
|
+
while (idx < ops.length && ops[idx].kind !== "same") {
|
|
126
|
+
if (ops[idx].kind === "del") dels.push(ops[idx].value);
|
|
127
|
+
else inss.push(ops[idx].value);
|
|
128
|
+
idx++;
|
|
129
|
+
}
|
|
130
|
+
if (dels.length === 1 && inss.length === 1) {
|
|
131
|
+
const { del, ins } = wordDiff(dels[0], inss[0]);
|
|
132
|
+
rows.push({ kind: "del", line: dels[0], tokens: del });
|
|
133
|
+
rows.push({ kind: "ins", line: inss[0], tokens: ins });
|
|
134
|
+
} else {
|
|
135
|
+
for (const d of dels) rows.push({ kind: "del", line: d, tokens: null });
|
|
136
|
+
for (const s of inss) rows.push({ kind: "ins", line: s, tokens: null });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return rows;
|
|
140
|
+
}
|
package/src/stores/chat-store.ts
CHANGED
|
@@ -456,7 +456,7 @@ export const useChatStore = create<ChatState>((set, get) => {
|
|
|
456
456
|
),
|
|
457
457
|
})),
|
|
458
458
|
|
|
459
|
-
requestToolApproval: (key, toolUseId, toolName, input,
|
|
459
|
+
requestToolApproval: (key, toolUseId, toolName, input, _description) =>
|
|
460
460
|
mutateLastAssistant(key, (last) => {
|
|
461
461
|
// Check if tool call already exists (from streaming)
|
|
462
462
|
const existingIdx = last.toolCalls.findIndex(
|
|
@@ -204,7 +204,20 @@ function sceneRequest(state: GraphState): { endpoint: string; query: string } |
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
export const useGraphStore = create<GraphState>((set, get) => ({
|
|
207
|
-
|
|
207
|
+
/**
|
|
208
|
+
* Clusters, not Local.
|
|
209
|
+
*
|
|
210
|
+
* Local is centred on ONE node and has no center until the user picks one,
|
|
211
|
+
* so opening the graph landed on an empty canvas with a picker — which reads
|
|
212
|
+
* as "the graph is broken", not as "choose a starting point". Clusters is
|
|
213
|
+
* the only mode that answers the question someone opening a graph view is
|
|
214
|
+
* actually asking: what is in here, and what clumps together.
|
|
215
|
+
*
|
|
216
|
+
* Local stays one click away, and clicking any node still switches to it
|
|
217
|
+
* (node-popover.tsx) — that is the natural way in, rather than the landing
|
|
218
|
+
* state.
|
|
219
|
+
*/
|
|
220
|
+
mode: "clusters",
|
|
208
221
|
meta: null,
|
|
209
222
|
metaState: "idle",
|
|
210
223
|
|