wave-code 0.6.5 → 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/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/commands/plugin/disable.d.ts.map +1 -1
- package/dist/commands/plugin/disable.js +3 -10
- package/dist/commands/plugin/enable.d.ts.map +1 -1
- package/dist/commands/plugin/enable.js +3 -10
- package/dist/commands/plugin/install.d.ts.map +1 -1
- package/dist/commands/plugin/install.js +4 -11
- package/dist/commands/plugin/list.d.ts.map +1 -1
- package/dist/commands/plugin/list.js +5 -39
- package/dist/commands/plugin/marketplace.js +9 -9
- package/dist/commands/plugin/uninstall.d.ts.map +1 -1
- package/dist/commands/plugin/uninstall.js +4 -17
- package/dist/commands/plugin/update.js +3 -3
- package/dist/components/App.d.ts +1 -0
- package/dist/components/App.d.ts.map +1 -1
- package/dist/components/App.js +4 -4
- package/dist/components/BackgroundTaskManager.d.ts.map +1 -1
- package/dist/components/BackgroundTaskManager.js +34 -18
- 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 +7 -14
- 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 +94 -36
- 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 +1 -0
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +20 -3
- package/dist/hooks/usePluginManager.d.ts.map +1 -1
- package/dist/hooks/usePluginManager.js +20 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/print-cli.d.ts +1 -0
- package/dist/print-cli.d.ts.map +1 -1
- package/dist/print-cli.js +2 -1
- 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 +2 -6
- package/dist/utils/toolParameterTransforms.d.ts.map +1 -1
- package/dist/utils/toolParameterTransforms.js +10 -14
- package/package.json +4 -2
- package/src/cli.tsx +3 -0
- package/src/commands/plugin/disable.ts +3 -17
- package/src/commands/plugin/enable.ts +3 -17
- package/src/commands/plugin/install.ts +4 -18
- package/src/commands/plugin/list.ts +5 -55
- package/src/commands/plugin/marketplace.ts +9 -9
- package/src/commands/plugin/uninstall.ts +4 -26
- package/src/commands/plugin/update.ts +3 -3
- package/src/components/App.tsx +10 -2
- package/src/components/BackgroundTaskManager.tsx +69 -44
- package/src/components/ChatInterface.tsx +35 -23
- package/src/components/ConfirmationDetails.tsx +13 -15
- package/src/components/ConfirmationSelector.tsx +207 -128
- package/src/components/DiffDisplay.tsx +164 -75
- 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 +22 -2
- package/src/hooks/usePluginManager.ts +21 -57
- package/src/index.ts +17 -0
- package/src/print-cli.ts +3 -0
- package/src/utils/highlightUtils.ts +76 -0
- package/src/utils/toolParameterTransforms.ts +11 -20
- 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
|
@@ -1,37 +1,34 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import {
|
|
4
|
-
WRITE_TOOL_NAME,
|
|
5
|
-
EDIT_TOOL_NAME,
|
|
6
|
-
MULTI_EDIT_TOOL_NAME,
|
|
7
|
-
} from "wave-agent-sdk";
|
|
3
|
+
import { WRITE_TOOL_NAME, EDIT_TOOL_NAME } from "wave-agent-sdk";
|
|
8
4
|
import { transformToolBlockToChanges } from "../utils/toolParameterTransforms.js";
|
|
9
5
|
import { diffLines, diffWords } from "diff";
|
|
10
6
|
|
|
11
7
|
interface DiffDisplayProps {
|
|
12
8
|
toolName?: string;
|
|
13
9
|
parameters?: string;
|
|
10
|
+
startLineNumber?: number;
|
|
14
11
|
}
|
|
15
12
|
|
|
16
13
|
export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
17
14
|
toolName,
|
|
18
15
|
parameters,
|
|
16
|
+
startLineNumber,
|
|
19
17
|
}) => {
|
|
20
18
|
const showDiff =
|
|
21
|
-
toolName &&
|
|
22
|
-
[WRITE_TOOL_NAME, EDIT_TOOL_NAME, MULTI_EDIT_TOOL_NAME].includes(toolName);
|
|
19
|
+
toolName && [WRITE_TOOL_NAME, EDIT_TOOL_NAME].includes(toolName);
|
|
23
20
|
|
|
24
21
|
// Diff detection and transformation using typed parameters
|
|
25
22
|
const changes = useMemo(() => {
|
|
26
23
|
if (!showDiff || !toolName || !parameters) return [];
|
|
27
24
|
try {
|
|
28
25
|
// Use local transformation with JSON parsing and type guards
|
|
29
|
-
return transformToolBlockToChanges(toolName, parameters);
|
|
26
|
+
return transformToolBlockToChanges(toolName, parameters, startLineNumber);
|
|
30
27
|
} catch (error) {
|
|
31
28
|
console.warn("Error transforming tool block to changes:", error);
|
|
32
29
|
return [];
|
|
33
30
|
}
|
|
34
|
-
}, [toolName, parameters, showDiff]);
|
|
31
|
+
}, [toolName, parameters, showDiff, startLineNumber]);
|
|
35
32
|
|
|
36
33
|
// Render word-level diff between two lines of text
|
|
37
34
|
const renderWordLevelDiff = (
|
|
@@ -105,64 +102,93 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
105
102
|
try {
|
|
106
103
|
if (changes.length === 0) return null;
|
|
107
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
|
+
|
|
108
142
|
const allElements: React.ReactNode[] = [];
|
|
109
143
|
|
|
110
144
|
changes.forEach((change, changeIndex) => {
|
|
111
145
|
try {
|
|
112
|
-
// Add ellipsis between non-contiguous edits in MultiEdit
|
|
113
|
-
if (toolName === MULTI_EDIT_TOOL_NAME && changeIndex > 0) {
|
|
114
|
-
allElements.push(
|
|
115
|
-
<Box key={`multi-edit-separator-${changeIndex}`}>
|
|
116
|
-
<Text color="gray">...</Text>
|
|
117
|
-
</Box>,
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
146
|
// Get line-level diff to understand the structure
|
|
122
147
|
const lineDiffs = diffLines(
|
|
123
148
|
change.oldContent || "",
|
|
124
149
|
change.newContent || "",
|
|
125
150
|
);
|
|
126
151
|
|
|
152
|
+
let oldLineNum = change.startLineNumber || 1;
|
|
153
|
+
let newLineNum = change.startLineNumber || 1;
|
|
154
|
+
|
|
127
155
|
// Process line diffs
|
|
128
156
|
const diffElements: React.ReactNode[] = [];
|
|
129
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
|
+
|
|
130
164
|
if (part.added) {
|
|
131
|
-
const lines = part.value
|
|
132
|
-
.split("\n")
|
|
133
|
-
.filter((line) => line !== "");
|
|
134
165
|
lines.forEach((line, lineIndex) => {
|
|
135
166
|
diffElements.push(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
167
|
+
renderLine(
|
|
168
|
+
null,
|
|
169
|
+
newLineNum++,
|
|
170
|
+
"+",
|
|
171
|
+
line,
|
|
172
|
+
"green",
|
|
173
|
+
`add-${changeIndex}-${partIndex}-${lineIndex}`,
|
|
174
|
+
),
|
|
143
175
|
);
|
|
144
176
|
});
|
|
145
177
|
} else if (part.removed) {
|
|
146
|
-
const lines = part.value
|
|
147
|
-
.split("\n")
|
|
148
|
-
.filter((line) => line !== "");
|
|
149
178
|
lines.forEach((line, lineIndex) => {
|
|
150
179
|
diffElements.push(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
180
|
+
renderLine(
|
|
181
|
+
oldLineNum++,
|
|
182
|
+
null,
|
|
183
|
+
"-",
|
|
184
|
+
line,
|
|
185
|
+
"red",
|
|
186
|
+
`remove-${changeIndex}-${partIndex}-${lineIndex}`,
|
|
187
|
+
),
|
|
158
188
|
);
|
|
159
189
|
});
|
|
160
190
|
} else {
|
|
161
191
|
// Context lines - show unchanged content
|
|
162
|
-
const lines = part.value
|
|
163
|
-
.split("\n")
|
|
164
|
-
.filter((line) => line !== "");
|
|
165
|
-
|
|
166
192
|
const isFirstBlock = partIndex === 0;
|
|
167
193
|
const isLastBlock = partIndex === lineDiffs.length - 1;
|
|
168
194
|
|
|
@@ -173,6 +199,9 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
173
199
|
if (isFirstBlock && !isLastBlock) {
|
|
174
200
|
// First block: keep last 3
|
|
175
201
|
if (lines.length > 3) {
|
|
202
|
+
const skipCount = lines.length - 3;
|
|
203
|
+
oldLineNum += skipCount;
|
|
204
|
+
newLineNum += skipCount;
|
|
176
205
|
linesToDisplay = lines.slice(-3);
|
|
177
206
|
showEllipsisTop = true;
|
|
178
207
|
}
|
|
@@ -188,15 +217,12 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
188
217
|
linesToDisplay = [...lines.slice(0, 3), ...lines.slice(-3)];
|
|
189
218
|
showEllipsisTop = false; // We'll put ellipsis in the middle
|
|
190
219
|
}
|
|
191
|
-
} else if (isFirstBlock && isLastBlock) {
|
|
192
|
-
// Only one block (no changes?) - keep all or apply a general limit
|
|
193
|
-
// For now, let's keep all if it's the only block
|
|
194
220
|
}
|
|
195
221
|
|
|
196
222
|
if (showEllipsisTop) {
|
|
197
223
|
diffElements.push(
|
|
198
224
|
<Box key={`ellipsis-top-${changeIndex}-${partIndex}`}>
|
|
199
|
-
<Text color="gray"> ...</Text>
|
|
225
|
+
<Text color="gray">{" ".repeat(maxDigits * 2 + 2)}...</Text>
|
|
200
226
|
</Box>,
|
|
201
227
|
);
|
|
202
228
|
}
|
|
@@ -209,28 +235,39 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
209
235
|
lines.length > 6 &&
|
|
210
236
|
lineIndex === 3
|
|
211
237
|
) {
|
|
238
|
+
const skipCount = lines.length - 6;
|
|
239
|
+
oldLineNum += skipCount;
|
|
240
|
+
newLineNum += skipCount;
|
|
212
241
|
diffElements.push(
|
|
213
242
|
<Box key={`ellipsis-mid-${changeIndex}-${partIndex}`}>
|
|
214
|
-
<Text color="gray">
|
|
243
|
+
<Text color="gray">
|
|
244
|
+
{" ".repeat(maxDigits * 2 + 2)}...
|
|
245
|
+
</Text>
|
|
215
246
|
</Box>,
|
|
216
247
|
);
|
|
217
248
|
}
|
|
218
249
|
|
|
219
250
|
diffElements.push(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
251
|
+
renderLine(
|
|
252
|
+
oldLineNum++,
|
|
253
|
+
newLineNum++,
|
|
254
|
+
" ",
|
|
255
|
+
line,
|
|
256
|
+
"white",
|
|
257
|
+
`context-${changeIndex}-${partIndex}-${lineIndex}`,
|
|
258
|
+
),
|
|
227
259
|
);
|
|
228
260
|
});
|
|
229
261
|
|
|
230
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;
|
|
231
268
|
diffElements.push(
|
|
232
269
|
<Box key={`ellipsis-bottom-${changeIndex}-${partIndex}`}>
|
|
233
|
-
<Text color="gray"> ...</Text>
|
|
270
|
+
<Text color="gray">{" ".repeat(maxDigits * 2 + 2)}...</Text>
|
|
234
271
|
</Box>,
|
|
235
272
|
);
|
|
236
273
|
}
|
|
@@ -249,6 +286,8 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
249
286
|
) {
|
|
250
287
|
const removedText = extractTextFromElement(diffElements[0]);
|
|
251
288
|
const addedText = extractTextFromElement(diffElements[1]);
|
|
289
|
+
const oldLineNumVal = extractOldLineNumFromElement(diffElements[0]);
|
|
290
|
+
const newLineNumVal = extractNewLineNumFromElement(diffElements[1]);
|
|
252
291
|
|
|
253
292
|
if (removedText && addedText) {
|
|
254
293
|
const { removedParts, addedParts } = renderWordLevelDiff(
|
|
@@ -258,19 +297,24 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
258
297
|
);
|
|
259
298
|
|
|
260
299
|
allElements.push(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
300
|
+
renderLine(
|
|
301
|
+
oldLineNumVal,
|
|
302
|
+
null,
|
|
303
|
+
"-",
|
|
304
|
+
removedParts,
|
|
305
|
+
"red",
|
|
306
|
+
`word-diff-removed-${changeIndex}`,
|
|
307
|
+
),
|
|
268
308
|
);
|
|
269
309
|
allElements.push(
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
310
|
+
renderLine(
|
|
311
|
+
null,
|
|
312
|
+
newLineNumVal,
|
|
313
|
+
"+",
|
|
314
|
+
addedParts,
|
|
315
|
+
"green",
|
|
316
|
+
`word-diff-added-${changeIndex}`,
|
|
317
|
+
),
|
|
274
318
|
);
|
|
275
319
|
} else {
|
|
276
320
|
allElements.push(...diffElements);
|
|
@@ -312,9 +356,6 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
312
356
|
return (
|
|
313
357
|
<Box flexDirection="column">
|
|
314
358
|
<Box paddingLeft={2} borderLeft borderColor="cyan" flexDirection="column">
|
|
315
|
-
<Text color="cyan" bold>
|
|
316
|
-
Diff:
|
|
317
|
-
</Text>
|
|
318
359
|
{renderExpandedDiff()}
|
|
319
360
|
</Box>
|
|
320
361
|
</Box>
|
|
@@ -326,16 +367,64 @@ const extractTextFromElement = (element: React.ReactNode): string | null => {
|
|
|
326
367
|
if (!React.isValidElement(element)) return null;
|
|
327
368
|
|
|
328
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;
|
|
329
414
|
const children = (
|
|
330
415
|
element.props as unknown as { children?: React.ReactNode[] }
|
|
331
416
|
).children;
|
|
332
417
|
if (Array.isArray(children) && children.length >= 2) {
|
|
333
|
-
const textElement = children[1];
|
|
334
|
-
if (
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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;
|
|
339
428
|
}
|
|
340
429
|
}
|
|
341
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
|
|