wave-code 0.7.0 → 0.7.1
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/BackgroundTaskManager.d.ts.map +1 -1
- package/dist/components/BackgroundTaskManager.js +20 -12
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +28 -15
- package/dist/components/ConfirmationDetails.d.ts +1 -0
- package/dist/components/ConfirmationDetails.d.ts.map +1 -1
- package/dist/components/ConfirmationDetails.js +6 -9
- package/dist/components/ConfirmationSelector.d.ts +1 -0
- package/dist/components/ConfirmationSelector.d.ts.map +1 -1
- package/dist/components/ConfirmationSelector.js +164 -117
- package/dist/components/DiffDisplay.d.ts +1 -0
- package/dist/components/DiffDisplay.d.ts.map +1 -1
- package/dist/components/DiffDisplay.js +92 -29
- package/dist/components/HistorySearch.d.ts.map +1 -1
- package/dist/components/HistorySearch.js +26 -20
- package/dist/components/Markdown.d.ts.map +1 -1
- package/dist/components/Markdown.js +3 -1
- package/dist/components/McpManager.d.ts.map +1 -1
- package/dist/components/McpManager.js +49 -52
- package/dist/components/MessageBlockItem.d.ts +9 -0
- package/dist/components/MessageBlockItem.d.ts.map +1 -0
- package/dist/components/MessageBlockItem.js +11 -0
- package/dist/components/MessageList.d.ts +2 -4
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +28 -23
- package/dist/components/PluginDetail.d.ts.map +1 -1
- package/dist/components/PluginDetail.js +19 -22
- package/dist/components/SessionSelector.d.ts.map +1 -1
- package/dist/components/SessionSelector.js +8 -5
- package/dist/components/TaskList.d.ts.map +1 -1
- package/dist/components/TaskList.js +2 -5
- package/dist/components/ToolDisplay.d.ts.map +1 -1
- package/dist/components/ToolDisplay.js +1 -1
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +17 -2
- package/dist/utils/highlightUtils.d.ts +2 -0
- package/dist/utils/highlightUtils.d.ts.map +1 -0
- package/dist/utils/highlightUtils.js +69 -0
- package/dist/utils/toolParameterTransforms.d.ts +1 -1
- package/dist/utils/toolParameterTransforms.d.ts.map +1 -1
- package/dist/utils/toolParameterTransforms.js +10 -3
- package/package.json +4 -2
- package/src/components/BackgroundTaskManager.tsx +20 -12
- package/src/components/ChatInterface.tsx +35 -23
- package/src/components/ConfirmationDetails.tsx +13 -9
- package/src/components/ConfirmationSelector.tsx +207 -128
- package/src/components/DiffDisplay.tsx +162 -59
- package/src/components/HistorySearch.tsx +31 -25
- package/src/components/Markdown.tsx +3 -1
- package/src/components/McpManager.tsx +51 -59
- package/src/components/MessageBlockItem.tsx +83 -0
- package/src/components/MessageList.tsx +55 -52
- package/src/components/PluginDetail.tsx +30 -31
- package/src/components/SessionSelector.tsx +8 -5
- package/src/components/TaskList.tsx +2 -5
- package/src/components/ToolDisplay.tsx +5 -1
- package/src/contexts/useChat.tsx +18 -2
- package/src/utils/highlightUtils.ts +76 -0
- package/src/utils/toolParameterTransforms.ts +11 -2
- package/dist/components/MessageItem.d.ts +0 -8
- package/dist/components/MessageItem.d.ts.map +0 -1
- package/dist/components/MessageItem.js +0 -13
- package/src/components/MessageItem.tsx +0 -81
|
@@ -7,11 +7,13 @@ import { diffLines, diffWords } from "diff";
|
|
|
7
7
|
interface DiffDisplayProps {
|
|
8
8
|
toolName?: string;
|
|
9
9
|
parameters?: string;
|
|
10
|
+
startLineNumber?: number;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
13
14
|
toolName,
|
|
14
15
|
parameters,
|
|
16
|
+
startLineNumber,
|
|
15
17
|
}) => {
|
|
16
18
|
const showDiff =
|
|
17
19
|
toolName && [WRITE_TOOL_NAME, EDIT_TOOL_NAME].includes(toolName);
|
|
@@ -21,12 +23,12 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
21
23
|
if (!showDiff || !toolName || !parameters) return [];
|
|
22
24
|
try {
|
|
23
25
|
// Use local transformation with JSON parsing and type guards
|
|
24
|
-
return transformToolBlockToChanges(toolName, parameters);
|
|
26
|
+
return transformToolBlockToChanges(toolName, parameters, startLineNumber);
|
|
25
27
|
} catch (error) {
|
|
26
28
|
console.warn("Error transforming tool block to changes:", error);
|
|
27
29
|
return [];
|
|
28
30
|
}
|
|
29
|
-
}, [toolName, parameters, showDiff]);
|
|
31
|
+
}, [toolName, parameters, showDiff, startLineNumber]);
|
|
30
32
|
|
|
31
33
|
// Render word-level diff between two lines of text
|
|
32
34
|
const renderWordLevelDiff = (
|
|
@@ -100,6 +102,43 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
100
102
|
try {
|
|
101
103
|
if (changes.length === 0) return null;
|
|
102
104
|
|
|
105
|
+
const maxLineNum = changes.reduce((max, change) => {
|
|
106
|
+
const oldLines = (change.oldContent || "").split("\n").length;
|
|
107
|
+
const newLines = (change.newContent || "").split("\n").length;
|
|
108
|
+
const start = change.startLineNumber || 1;
|
|
109
|
+
// For Edit tool, the diff might show context lines before/after the change.
|
|
110
|
+
// The startLineNumber is the line where old_string starts.
|
|
111
|
+
// diffLines will include context lines if they are part of the change object.
|
|
112
|
+
// However, our transformEditParameters currently only puts old_string/new_string.
|
|
113
|
+
// If we ever support context lines in the Change object, we need to be careful.
|
|
114
|
+
return Math.max(max, start + oldLines, start + newLines);
|
|
115
|
+
}, 0);
|
|
116
|
+
const maxDigits = Math.max(2, maxLineNum.toString().length);
|
|
117
|
+
|
|
118
|
+
const renderLine = (
|
|
119
|
+
oldLineNum: number | null,
|
|
120
|
+
newLineNum: number | null,
|
|
121
|
+
prefix: string,
|
|
122
|
+
content: React.ReactNode,
|
|
123
|
+
color: string,
|
|
124
|
+
key: string,
|
|
125
|
+
) => {
|
|
126
|
+
const formatNum = (num: number | null) =>
|
|
127
|
+
num === null
|
|
128
|
+
? " ".repeat(maxDigits)
|
|
129
|
+
: num.toString().padStart(maxDigits);
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<Box key={key} flexDirection="row">
|
|
133
|
+
<Text color="gray">{formatNum(oldLineNum)} </Text>
|
|
134
|
+
<Text color="gray">{formatNum(newLineNum)} </Text>
|
|
135
|
+
<Text color="gray">| </Text>
|
|
136
|
+
<Text color={color}>{prefix}</Text>
|
|
137
|
+
<Text color={color}>{content}</Text>
|
|
138
|
+
</Box>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
103
142
|
const allElements: React.ReactNode[] = [];
|
|
104
143
|
|
|
105
144
|
changes.forEach((change, changeIndex) => {
|
|
@@ -110,45 +149,46 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
110
149
|
change.newContent || "",
|
|
111
150
|
);
|
|
112
151
|
|
|
152
|
+
let oldLineNum = change.startLineNumber || 1;
|
|
153
|
+
let newLineNum = change.startLineNumber || 1;
|
|
154
|
+
|
|
113
155
|
// Process line diffs
|
|
114
156
|
const diffElements: React.ReactNode[] = [];
|
|
115
157
|
lineDiffs.forEach((part, partIndex) => {
|
|
158
|
+
const lines = part.value.split("\n");
|
|
159
|
+
// diffLines might return a trailing empty string if the content ends with a newline
|
|
160
|
+
if (lines[lines.length - 1] === "") {
|
|
161
|
+
lines.pop();
|
|
162
|
+
}
|
|
163
|
+
|
|
116
164
|
if (part.added) {
|
|
117
|
-
const lines = part.value
|
|
118
|
-
.split("\n")
|
|
119
|
-
.filter((line) => line !== "");
|
|
120
165
|
lines.forEach((line, lineIndex) => {
|
|
121
166
|
diffElements.push(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
167
|
+
renderLine(
|
|
168
|
+
null,
|
|
169
|
+
newLineNum++,
|
|
170
|
+
"+",
|
|
171
|
+
line,
|
|
172
|
+
"green",
|
|
173
|
+
`add-${changeIndex}-${partIndex}-${lineIndex}`,
|
|
174
|
+
),
|
|
129
175
|
);
|
|
130
176
|
});
|
|
131
177
|
} else if (part.removed) {
|
|
132
|
-
const lines = part.value
|
|
133
|
-
.split("\n")
|
|
134
|
-
.filter((line) => line !== "");
|
|
135
178
|
lines.forEach((line, lineIndex) => {
|
|
136
179
|
diffElements.push(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
180
|
+
renderLine(
|
|
181
|
+
oldLineNum++,
|
|
182
|
+
null,
|
|
183
|
+
"-",
|
|
184
|
+
line,
|
|
185
|
+
"red",
|
|
186
|
+
`remove-${changeIndex}-${partIndex}-${lineIndex}`,
|
|
187
|
+
),
|
|
144
188
|
);
|
|
145
189
|
});
|
|
146
190
|
} else {
|
|
147
191
|
// Context lines - show unchanged content
|
|
148
|
-
const lines = part.value
|
|
149
|
-
.split("\n")
|
|
150
|
-
.filter((line) => line !== "");
|
|
151
|
-
|
|
152
192
|
const isFirstBlock = partIndex === 0;
|
|
153
193
|
const isLastBlock = partIndex === lineDiffs.length - 1;
|
|
154
194
|
|
|
@@ -159,6 +199,9 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
159
199
|
if (isFirstBlock && !isLastBlock) {
|
|
160
200
|
// First block: keep last 3
|
|
161
201
|
if (lines.length > 3) {
|
|
202
|
+
const skipCount = lines.length - 3;
|
|
203
|
+
oldLineNum += skipCount;
|
|
204
|
+
newLineNum += skipCount;
|
|
162
205
|
linesToDisplay = lines.slice(-3);
|
|
163
206
|
showEllipsisTop = true;
|
|
164
207
|
}
|
|
@@ -174,15 +217,12 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
174
217
|
linesToDisplay = [...lines.slice(0, 3), ...lines.slice(-3)];
|
|
175
218
|
showEllipsisTop = false; // We'll put ellipsis in the middle
|
|
176
219
|
}
|
|
177
|
-
} else if (isFirstBlock && isLastBlock) {
|
|
178
|
-
// Only one block (no changes?) - keep all or apply a general limit
|
|
179
|
-
// For now, let's keep all if it's the only block
|
|
180
220
|
}
|
|
181
221
|
|
|
182
222
|
if (showEllipsisTop) {
|
|
183
223
|
diffElements.push(
|
|
184
224
|
<Box key={`ellipsis-top-${changeIndex}-${partIndex}`}>
|
|
185
|
-
<Text color="gray"> ...</Text>
|
|
225
|
+
<Text color="gray">{" ".repeat(maxDigits * 2 + 2)}...</Text>
|
|
186
226
|
</Box>,
|
|
187
227
|
);
|
|
188
228
|
}
|
|
@@ -195,28 +235,39 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
195
235
|
lines.length > 6 &&
|
|
196
236
|
lineIndex === 3
|
|
197
237
|
) {
|
|
238
|
+
const skipCount = lines.length - 6;
|
|
239
|
+
oldLineNum += skipCount;
|
|
240
|
+
newLineNum += skipCount;
|
|
198
241
|
diffElements.push(
|
|
199
242
|
<Box key={`ellipsis-mid-${changeIndex}-${partIndex}`}>
|
|
200
|
-
<Text color="gray">
|
|
243
|
+
<Text color="gray">
|
|
244
|
+
{" ".repeat(maxDigits * 2 + 2)}...
|
|
245
|
+
</Text>
|
|
201
246
|
</Box>,
|
|
202
247
|
);
|
|
203
248
|
}
|
|
204
249
|
|
|
205
250
|
diffElements.push(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
251
|
+
renderLine(
|
|
252
|
+
oldLineNum++,
|
|
253
|
+
newLineNum++,
|
|
254
|
+
" ",
|
|
255
|
+
line,
|
|
256
|
+
"white",
|
|
257
|
+
`context-${changeIndex}-${partIndex}-${lineIndex}`,
|
|
258
|
+
),
|
|
213
259
|
);
|
|
214
260
|
});
|
|
215
261
|
|
|
216
262
|
if (showEllipsisBottom) {
|
|
263
|
+
const skipCount = lines.length - linesToDisplay.length;
|
|
264
|
+
// We don't increment oldLineNum/newLineNum here because they are already incremented in the loop
|
|
265
|
+
// But we need to account for the lines we skipped at the end of this block
|
|
266
|
+
oldLineNum += skipCount;
|
|
267
|
+
newLineNum += skipCount;
|
|
217
268
|
diffElements.push(
|
|
218
269
|
<Box key={`ellipsis-bottom-${changeIndex}-${partIndex}`}>
|
|
219
|
-
<Text color="gray"> ...</Text>
|
|
270
|
+
<Text color="gray">{" ".repeat(maxDigits * 2 + 2)}...</Text>
|
|
220
271
|
</Box>,
|
|
221
272
|
);
|
|
222
273
|
}
|
|
@@ -235,6 +286,8 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
235
286
|
) {
|
|
236
287
|
const removedText = extractTextFromElement(diffElements[0]);
|
|
237
288
|
const addedText = extractTextFromElement(diffElements[1]);
|
|
289
|
+
const oldLineNumVal = extractOldLineNumFromElement(diffElements[0]);
|
|
290
|
+
const newLineNumVal = extractNewLineNumFromElement(diffElements[1]);
|
|
238
291
|
|
|
239
292
|
if (removedText && addedText) {
|
|
240
293
|
const { removedParts, addedParts } = renderWordLevelDiff(
|
|
@@ -244,19 +297,24 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
244
297
|
);
|
|
245
298
|
|
|
246
299
|
allElements.push(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
300
|
+
renderLine(
|
|
301
|
+
oldLineNumVal,
|
|
302
|
+
null,
|
|
303
|
+
"-",
|
|
304
|
+
removedParts,
|
|
305
|
+
"red",
|
|
306
|
+
`word-diff-removed-${changeIndex}`,
|
|
307
|
+
),
|
|
254
308
|
);
|
|
255
309
|
allElements.push(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
310
|
+
renderLine(
|
|
311
|
+
null,
|
|
312
|
+
newLineNumVal,
|
|
313
|
+
"+",
|
|
314
|
+
addedParts,
|
|
315
|
+
"green",
|
|
316
|
+
`word-diff-added-${changeIndex}`,
|
|
317
|
+
),
|
|
260
318
|
);
|
|
261
319
|
} else {
|
|
262
320
|
allElements.push(...diffElements);
|
|
@@ -298,9 +356,6 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
298
356
|
return (
|
|
299
357
|
<Box flexDirection="column">
|
|
300
358
|
<Box paddingLeft={2} borderLeft borderColor="cyan" flexDirection="column">
|
|
301
|
-
<Text color="cyan" bold>
|
|
302
|
-
Diff:
|
|
303
|
-
</Text>
|
|
304
359
|
{renderExpandedDiff()}
|
|
305
360
|
</Box>
|
|
306
361
|
</Box>
|
|
@@ -312,16 +367,64 @@ const extractTextFromElement = (element: React.ReactNode): string | null => {
|
|
|
312
367
|
if (!React.isValidElement(element)) return null;
|
|
313
368
|
|
|
314
369
|
// Navigate through Box -> Text structure
|
|
370
|
+
// Our new structure is: Box -> Text (old), Text (new), Text (|), Text (prefix), Text (content)
|
|
371
|
+
const children = (
|
|
372
|
+
element.props as unknown as { children?: React.ReactNode[] }
|
|
373
|
+
).children;
|
|
374
|
+
if (Array.isArray(children) && children.length >= 5) {
|
|
375
|
+
const textElement = children[4]; // Fifth child should be the Text with content
|
|
376
|
+
if (React.isValidElement(textElement)) {
|
|
377
|
+
const textChildren = (textElement.props as Record<string, unknown>)
|
|
378
|
+
.children;
|
|
379
|
+
return Array.isArray(textChildren)
|
|
380
|
+
? textChildren.join("")
|
|
381
|
+
: String(textChildren || "");
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return null;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const extractOldLineNumFromElement = (
|
|
388
|
+
element: React.ReactNode,
|
|
389
|
+
): number | null => {
|
|
390
|
+
if (!React.isValidElement(element)) return null;
|
|
391
|
+
const children = (
|
|
392
|
+
element.props as unknown as { children?: React.ReactNode[] }
|
|
393
|
+
).children;
|
|
394
|
+
if (Array.isArray(children) && children.length >= 1) {
|
|
395
|
+
const textElement = children[0];
|
|
396
|
+
if (React.isValidElement(textElement)) {
|
|
397
|
+
const textChildren = (textElement.props as Record<string, unknown>)
|
|
398
|
+
.children;
|
|
399
|
+
const val = (
|
|
400
|
+
Array.isArray(textChildren)
|
|
401
|
+
? textChildren.join("")
|
|
402
|
+
: String(textChildren || "")
|
|
403
|
+
).trim();
|
|
404
|
+
return val ? parseInt(val, 10) : null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const extractNewLineNumFromElement = (
|
|
411
|
+
element: React.ReactNode,
|
|
412
|
+
): number | null => {
|
|
413
|
+
if (!React.isValidElement(element)) return null;
|
|
315
414
|
const children = (
|
|
316
415
|
element.props as unknown as { children?: React.ReactNode[] }
|
|
317
416
|
).children;
|
|
318
417
|
if (Array.isArray(children) && children.length >= 2) {
|
|
319
|
-
const textElement = children[1];
|
|
320
|
-
if (
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
418
|
+
const textElement = children[1];
|
|
419
|
+
if (React.isValidElement(textElement)) {
|
|
420
|
+
const textChildren = (textElement.props as Record<string, unknown>)
|
|
421
|
+
.children;
|
|
422
|
+
const val = (
|
|
423
|
+
Array.isArray(textChildren)
|
|
424
|
+
? textChildren.join("")
|
|
425
|
+
: String(textChildren || "")
|
|
426
|
+
).trim();
|
|
427
|
+
return val ? parseInt(val, 10) : null;
|
|
325
428
|
}
|
|
326
429
|
}
|
|
327
430
|
return null;
|
|
@@ -14,38 +14,34 @@ export const HistorySearch: React.FC<HistorySearchProps> = ({
|
|
|
14
14
|
onCancel,
|
|
15
15
|
}) => {
|
|
16
16
|
const MAX_VISIBLE_ITEMS = 5;
|
|
17
|
-
const [
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const selectedIndexRef = React.useRef(0);
|
|
22
|
-
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
entriesRef.current = entries;
|
|
25
|
-
}, [entries]);
|
|
26
|
-
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
selectedIndexRef.current = selectedIndex;
|
|
29
|
-
}, [selectedIndex]);
|
|
17
|
+
const [state, setState] = useState({
|
|
18
|
+
selectedIndex: 0,
|
|
19
|
+
entries: [] as PromptEntry[],
|
|
20
|
+
});
|
|
30
21
|
|
|
31
22
|
useEffect(() => {
|
|
32
23
|
const fetchHistory = async () => {
|
|
33
24
|
const results = await PromptHistoryManager.searchHistory(searchQuery);
|
|
34
25
|
const limitedResults = results.slice(0, 20);
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
setState({
|
|
27
|
+
entries: limitedResults,
|
|
28
|
+
selectedIndex: 0,
|
|
29
|
+
});
|
|
37
30
|
};
|
|
38
31
|
fetchHistory();
|
|
39
32
|
}, [searchQuery]);
|
|
40
33
|
|
|
41
34
|
useInput((input, key) => {
|
|
42
35
|
if (key.return) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
setState((prev) => {
|
|
37
|
+
if (
|
|
38
|
+
prev.entries.length > 0 &&
|
|
39
|
+
prev.selectedIndex < prev.entries.length
|
|
40
|
+
) {
|
|
41
|
+
onSelect(prev.entries[prev.selectedIndex].prompt);
|
|
42
|
+
}
|
|
43
|
+
return prev;
|
|
44
|
+
});
|
|
49
45
|
return;
|
|
50
46
|
}
|
|
51
47
|
|
|
@@ -55,18 +51,27 @@ export const HistorySearch: React.FC<HistorySearchProps> = ({
|
|
|
55
51
|
}
|
|
56
52
|
|
|
57
53
|
if (key.upArrow) {
|
|
58
|
-
|
|
54
|
+
setState((prev) => ({
|
|
55
|
+
...prev,
|
|
56
|
+
selectedIndex: Math.max(0, prev.selectedIndex - 1),
|
|
57
|
+
}));
|
|
59
58
|
return;
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
if (key.downArrow) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
setState((prev) => ({
|
|
63
|
+
...prev,
|
|
64
|
+
selectedIndex: Math.min(
|
|
65
|
+
prev.entries.length - 1,
|
|
66
|
+
prev.selectedIndex + 1,
|
|
67
|
+
),
|
|
68
|
+
}));
|
|
66
69
|
return;
|
|
67
70
|
}
|
|
68
71
|
});
|
|
69
72
|
|
|
73
|
+
const { entries, selectedIndex } = state;
|
|
74
|
+
|
|
70
75
|
if (entries.length === 0) {
|
|
71
76
|
return (
|
|
72
77
|
<Box
|
|
@@ -147,6 +152,7 @@ export const HistorySearch: React.FC<HistorySearchProps> = ({
|
|
|
147
152
|
backgroundColor={isSelected ? "blue" : undefined}
|
|
148
153
|
wrap="truncate-end"
|
|
149
154
|
>
|
|
155
|
+
{isSelected ? "> " : " "}
|
|
150
156
|
{entry.prompt.replace(/\n/g, " ")}
|
|
151
157
|
</Text>
|
|
152
158
|
</Box>
|
|
@@ -2,6 +2,7 @@ import React, { useMemo } from "react";
|
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { Renderer, marked, type Tokens } from "marked";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
+
import { highlightToAnsi } from "../utils/highlightUtils.js";
|
|
5
6
|
|
|
6
7
|
export interface MarkdownProps {
|
|
7
8
|
children: string;
|
|
@@ -21,7 +22,8 @@ class AnsiRenderer extends Renderer<string> {
|
|
|
21
22
|
override code({ text, lang }: Tokens.Code): string {
|
|
22
23
|
const prefix = lang ? `\`\`\`${lang}` : "```";
|
|
23
24
|
const suffix = "```";
|
|
24
|
-
|
|
25
|
+
const highlighted = highlightToAnsi(text, lang);
|
|
26
|
+
return `\n${chalk.gray(prefix)}\n${highlighted}\n${chalk.gray(suffix)}\n`;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
override blockquote({ tokens }: Tokens.Blockquote): string {
|
|
@@ -65,76 +65,68 @@ export const McpManager: React.FC<McpManagerProps> = ({
|
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
useInput((input, key) => {
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
if (key.return) {
|
|
69
|
+
setViewMode((prevMode) => {
|
|
70
|
+
if (prevMode === "list") {
|
|
71
|
+
setSelectedIndex((prevIndex) => {
|
|
72
|
+
if (servers.length > 0 && prevIndex < servers.length) {
|
|
73
|
+
// We can't call setViewMode here because we're already in a setViewMode call
|
|
74
|
+
// But we can return the new mode from the outer setViewMode
|
|
75
|
+
}
|
|
76
|
+
return prevIndex;
|
|
77
|
+
});
|
|
78
|
+
return "detail";
|
|
73
79
|
}
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
80
|
+
return prevMode;
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
76
84
|
|
|
77
|
-
|
|
85
|
+
if (key.escape) {
|
|
86
|
+
setViewMode((prev) => {
|
|
87
|
+
if (prev === "detail") {
|
|
88
|
+
return "list";
|
|
89
|
+
}
|
|
78
90
|
onCancel();
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
setSelectedIndex(Math.max(0, selectedIndex - 1));
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (key.downArrow) {
|
|
88
|
-
setSelectedIndex(Math.min(servers.length - 1, selectedIndex + 1));
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
+
return prev;
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
91
95
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
selectedIndex < servers.length
|
|
97
|
-
) {
|
|
98
|
-
const server = servers[selectedIndex];
|
|
99
|
-
if (server.status === "disconnected" || server.status === "error") {
|
|
100
|
-
handleConnect(server.name);
|
|
101
|
-
}
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
96
|
+
if (key.upArrow) {
|
|
97
|
+
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
104
100
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
) {
|
|
110
|
-
const server = servers[selectedIndex];
|
|
111
|
-
if (server.status === "connected") {
|
|
112
|
-
handleDisconnect(server.name);
|
|
113
|
-
}
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
} else if (viewMode === "detail") {
|
|
117
|
-
// Detail mode navigation
|
|
118
|
-
if (key.escape) {
|
|
119
|
-
setViewMode("list");
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
101
|
+
if (key.downArrow) {
|
|
102
|
+
setSelectedIndex((prev) => Math.min(servers.length - 1, prev + 1));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
122
105
|
|
|
123
|
-
|
|
106
|
+
// Hotkeys for server actions
|
|
107
|
+
if (input === "c") {
|
|
108
|
+
setSelectedIndex((prev) => {
|
|
109
|
+
const server = servers[prev];
|
|
124
110
|
if (
|
|
125
|
-
|
|
126
|
-
(
|
|
127
|
-
selectedServer.status === "error")
|
|
111
|
+
server &&
|
|
112
|
+
(server.status === "disconnected" || server.status === "error")
|
|
128
113
|
) {
|
|
129
|
-
handleConnect(
|
|
130
|
-
return;
|
|
114
|
+
handleConnect(server.name);
|
|
131
115
|
}
|
|
116
|
+
return prev;
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
132
120
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
121
|
+
if (input === "d") {
|
|
122
|
+
setSelectedIndex((prev) => {
|
|
123
|
+
const server = servers[prev];
|
|
124
|
+
if (server && server.status === "connected") {
|
|
125
|
+
handleDisconnect(server.name);
|
|
136
126
|
}
|
|
137
|
-
|
|
127
|
+
return prev;
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
138
130
|
}
|
|
139
131
|
});
|
|
140
132
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import type { Message, MessageBlock } from "wave-agent-sdk";
|
|
4
|
+
import { MessageSource } from "wave-agent-sdk";
|
|
5
|
+
import { CommandOutputDisplay } from "./CommandOutputDisplay.js";
|
|
6
|
+
import { ToolDisplay } from "./ToolDisplay.js";
|
|
7
|
+
import { CompressDisplay } from "./CompressDisplay.js";
|
|
8
|
+
import { ReasoningDisplay } from "./ReasoningDisplay.js";
|
|
9
|
+
import { Markdown } from "./Markdown.js";
|
|
10
|
+
|
|
11
|
+
export interface MessageBlockItemProps {
|
|
12
|
+
block: MessageBlock;
|
|
13
|
+
message: Message;
|
|
14
|
+
isExpanded: boolean;
|
|
15
|
+
paddingTop?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const MessageBlockItem = ({
|
|
19
|
+
block,
|
|
20
|
+
message,
|
|
21
|
+
isExpanded,
|
|
22
|
+
paddingTop = 0,
|
|
23
|
+
}: MessageBlockItemProps) => {
|
|
24
|
+
return (
|
|
25
|
+
<Box flexDirection="column" paddingTop={paddingTop}>
|
|
26
|
+
{block.type === "text" && block.content.trim() && (
|
|
27
|
+
<Box>
|
|
28
|
+
{block.customCommandContent && (
|
|
29
|
+
<Text color="cyan" bold>
|
|
30
|
+
${" "}
|
|
31
|
+
</Text>
|
|
32
|
+
)}
|
|
33
|
+
{block.source === MessageSource.HOOK && (
|
|
34
|
+
<Text color="magenta" bold>
|
|
35
|
+
~{" "}
|
|
36
|
+
</Text>
|
|
37
|
+
)}
|
|
38
|
+
{message.role === "user" ? (
|
|
39
|
+
<Text backgroundColor="gray" color="white">
|
|
40
|
+
{block.content}
|
|
41
|
+
</Text>
|
|
42
|
+
) : (
|
|
43
|
+
<Markdown>{block.content}</Markdown>
|
|
44
|
+
)}
|
|
45
|
+
</Box>
|
|
46
|
+
)}
|
|
47
|
+
|
|
48
|
+
{block.type === "error" && (
|
|
49
|
+
<Box>
|
|
50
|
+
<Text color="red">Error: {block.content}</Text>
|
|
51
|
+
</Box>
|
|
52
|
+
)}
|
|
53
|
+
|
|
54
|
+
{block.type === "command_output" && (
|
|
55
|
+
<CommandOutputDisplay block={block} isExpanded={isExpanded} />
|
|
56
|
+
)}
|
|
57
|
+
|
|
58
|
+
{block.type === "tool" && (
|
|
59
|
+
<ToolDisplay block={block} isExpanded={isExpanded} />
|
|
60
|
+
)}
|
|
61
|
+
|
|
62
|
+
{block.type === "image" && (
|
|
63
|
+
<Box>
|
|
64
|
+
<Text color="magenta" bold>
|
|
65
|
+
# Image
|
|
66
|
+
</Text>
|
|
67
|
+
{block.imageUrls && block.imageUrls.length > 0 && (
|
|
68
|
+
<Text color="gray" dimColor>
|
|
69
|
+
{" "}
|
|
70
|
+
({block.imageUrls.length})
|
|
71
|
+
</Text>
|
|
72
|
+
)}
|
|
73
|
+
</Box>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
{block.type === "compress" && (
|
|
77
|
+
<CompressDisplay block={block} isExpanded={isExpanded} />
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
{block.type === "reasoning" && <ReasoningDisplay block={block} />}
|
|
81
|
+
</Box>
|
|
82
|
+
);
|
|
83
|
+
};
|