snow-ai 0.3.11 → 0.3.13
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/agents/compactAgent.js +6 -5
- package/dist/agents/reviewAgent.js +1 -0
- package/dist/agents/summaryAgent.js +1 -0
- package/dist/api/anthropic.d.ts +7 -1
- package/dist/api/anthropic.js +74 -7
- package/dist/api/chat.js +1 -1
- package/dist/api/responses.js +2 -2
- package/dist/api/systemPrompt.js +4 -5
- package/dist/api/types.d.ts +5 -0
- package/dist/hooks/useConversation.js +13 -3
- package/dist/hooks/useFilePicker.d.ts +4 -4
- package/dist/hooks/useFilePicker.js +88 -40
- package/dist/hooks/useHistoryNavigation.d.ts +5 -0
- package/dist/hooks/useHistoryNavigation.js +78 -1
- package/dist/hooks/useInputBuffer.js +1 -1
- package/dist/hooks/useKeyboardInput.d.ts +5 -0
- package/dist/hooks/useKeyboardInput.js +51 -9
- package/dist/hooks/useStreamingState.js +1 -1
- package/dist/mcp/aceCodeSearch.d.ts +4 -0
- package/dist/mcp/aceCodeSearch.js +17 -1
- package/dist/mcp/filesystem.js +2 -2
- package/dist/mcp/utils/filesystem/match-finder.utils.js +1 -1
- package/dist/ui/components/ChatInput.js +11 -4
- package/dist/ui/pages/ChatScreen.js +6 -2
- package/dist/ui/pages/ConfigScreen.js +245 -110
- package/dist/ui/pages/HeadlessModeScreen.js +3 -1
- package/dist/utils/apiConfig.d.ts +5 -0
- package/dist/utils/apiConfig.js +9 -3
- package/dist/utils/contextCompressor.js +7 -2
- package/dist/utils/historyManager.d.ts +45 -0
- package/dist/utils/historyManager.js +159 -0
- package/dist/utils/subAgentExecutor.js +2 -1
- package/dist/utils/textBuffer.js +24 -5
- package/package.json +1 -1
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
import { historyManager } from '../utils/historyManager.js';
|
|
2
3
|
export function useHistoryNavigation(buffer, triggerUpdate, chatHistory, onHistorySelect) {
|
|
3
4
|
const [showHistoryMenu, setShowHistoryMenu] = useState(false);
|
|
4
5
|
const [historySelectedIndex, setHistorySelectedIndex] = useState(0);
|
|
5
6
|
const [escapeKeyCount, setEscapeKeyCount] = useState(0);
|
|
6
7
|
const escapeKeyTimer = useRef(null);
|
|
8
|
+
// Terminal-style history navigation state
|
|
9
|
+
const [currentHistoryIndex, setCurrentHistoryIndex] = useState(-1); // -1 means not in history mode
|
|
10
|
+
const savedInput = useRef(''); // Save current input when entering history mode
|
|
11
|
+
const [persistentHistory, setPersistentHistory] = useState([]);
|
|
12
|
+
const persistentHistoryRef = useRef([]);
|
|
13
|
+
// Keep ref in sync with state
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
persistentHistoryRef.current = persistentHistory;
|
|
16
|
+
}, [persistentHistory]);
|
|
17
|
+
// Load persistent history on mount
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
historyManager.loadHistory().then(entries => {
|
|
20
|
+
setPersistentHistory(entries);
|
|
21
|
+
});
|
|
22
|
+
}, []);
|
|
7
23
|
// Cleanup timer on unmount
|
|
8
24
|
useEffect(() => {
|
|
9
25
|
return () => {
|
|
@@ -35,7 +51,62 @@ export function useHistoryNavigation(buffer, triggerUpdate, chatHistory, onHisto
|
|
|
35
51
|
triggerUpdate();
|
|
36
52
|
onHistorySelect(selectedIndex, selectedMessage.content);
|
|
37
53
|
}
|
|
38
|
-
}, [chatHistory, onHistorySelect, buffer
|
|
54
|
+
}, [chatHistory, onHistorySelect, buffer]);
|
|
55
|
+
// Terminal-style history navigation: navigate up (older)
|
|
56
|
+
const navigateHistoryUp = useCallback(() => {
|
|
57
|
+
const history = persistentHistoryRef.current;
|
|
58
|
+
if (history.length === 0)
|
|
59
|
+
return false;
|
|
60
|
+
// Save current input when first entering history mode
|
|
61
|
+
if (currentHistoryIndex === -1) {
|
|
62
|
+
savedInput.current = buffer.getFullText();
|
|
63
|
+
}
|
|
64
|
+
// Navigate to older message (persistentHistory is already newest first)
|
|
65
|
+
const newIndex = currentHistoryIndex === -1
|
|
66
|
+
? 0
|
|
67
|
+
: Math.min(history.length - 1, currentHistoryIndex + 1);
|
|
68
|
+
setCurrentHistoryIndex(newIndex);
|
|
69
|
+
const entry = history[newIndex];
|
|
70
|
+
if (entry) {
|
|
71
|
+
buffer.setText(entry.content);
|
|
72
|
+
triggerUpdate();
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}, [currentHistoryIndex, buffer]);
|
|
76
|
+
// Terminal-style history navigation: navigate down (newer)
|
|
77
|
+
const navigateHistoryDown = useCallback(() => {
|
|
78
|
+
if (currentHistoryIndex === -1)
|
|
79
|
+
return false;
|
|
80
|
+
const newIndex = currentHistoryIndex - 1;
|
|
81
|
+
const history = persistentHistoryRef.current;
|
|
82
|
+
if (newIndex < 0) {
|
|
83
|
+
// Restore original input
|
|
84
|
+
buffer.setText(savedInput.current);
|
|
85
|
+
setCurrentHistoryIndex(-1);
|
|
86
|
+
savedInput.current = '';
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
setCurrentHistoryIndex(newIndex);
|
|
90
|
+
const entry = history[newIndex];
|
|
91
|
+
if (entry) {
|
|
92
|
+
buffer.setText(entry.content);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
triggerUpdate();
|
|
96
|
+
return true;
|
|
97
|
+
}, [currentHistoryIndex, buffer]);
|
|
98
|
+
// Reset history navigation state
|
|
99
|
+
const resetHistoryNavigation = useCallback(() => {
|
|
100
|
+
setCurrentHistoryIndex(-1);
|
|
101
|
+
savedInput.current = '';
|
|
102
|
+
}, []);
|
|
103
|
+
// Save message to persistent history
|
|
104
|
+
const saveToHistory = useCallback(async (content) => {
|
|
105
|
+
await historyManager.addEntry(content);
|
|
106
|
+
// Reload history to update the list
|
|
107
|
+
const entries = await historyManager.getEntries();
|
|
108
|
+
setPersistentHistory(entries);
|
|
109
|
+
}, []);
|
|
39
110
|
return {
|
|
40
111
|
showHistoryMenu,
|
|
41
112
|
setShowHistoryMenu,
|
|
@@ -46,5 +117,11 @@ export function useHistoryNavigation(buffer, triggerUpdate, chatHistory, onHisto
|
|
|
46
117
|
escapeKeyTimer,
|
|
47
118
|
getUserMessages,
|
|
48
119
|
handleHistorySelect,
|
|
120
|
+
// Terminal-style history navigation
|
|
121
|
+
currentHistoryIndex,
|
|
122
|
+
navigateHistoryUp,
|
|
123
|
+
navigateHistoryDown,
|
|
124
|
+
resetHistoryNavigation,
|
|
125
|
+
saveToHistory,
|
|
49
126
|
};
|
|
50
127
|
}
|
|
@@ -14,7 +14,7 @@ export function useInputBuffer(viewport) {
|
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
buffer.updateViewport(viewport);
|
|
16
16
|
triggerUpdate();
|
|
17
|
-
}, [viewport.width, viewport.height, buffer
|
|
17
|
+
}, [viewport.width, viewport.height, buffer]);
|
|
18
18
|
// Cleanup buffer on unmount
|
|
19
19
|
useEffect(() => {
|
|
20
20
|
return () => {
|
|
@@ -41,6 +41,11 @@ type KeyboardInputOptions = {
|
|
|
41
41
|
infoText: string;
|
|
42
42
|
}>;
|
|
43
43
|
handleHistorySelect: (value: string) => void;
|
|
44
|
+
currentHistoryIndex: number;
|
|
45
|
+
navigateHistoryUp: () => boolean;
|
|
46
|
+
navigateHistoryDown: () => boolean;
|
|
47
|
+
resetHistoryNavigation: () => void;
|
|
48
|
+
saveToHistory: (content: string) => Promise<void>;
|
|
44
49
|
pasteFromClipboard: () => Promise<void>;
|
|
45
50
|
onSubmit: (message: string, images?: Array<{
|
|
46
51
|
data: string;
|
|
@@ -2,7 +2,7 @@ import { useRef, useEffect } from 'react';
|
|
|
2
2
|
import { useInput } from 'ink';
|
|
3
3
|
import { executeCommand } from '../utils/commandExecutor.js';
|
|
4
4
|
export function useKeyboardInput(options) {
|
|
5
|
-
const { buffer, disabled, triggerUpdate, forceUpdate, showCommands, setShowCommands, commandSelectedIndex, setCommandSelectedIndex, getFilteredCommands, updateCommandPanelState, onCommand, showFilePicker, setShowFilePicker, fileSelectedIndex, setFileSelectedIndex, setFileQuery, setAtSymbolPosition, filteredFileCount, updateFilePickerState, handleFileSelect, fileListRef, showHistoryMenu, setShowHistoryMenu, historySelectedIndex, setHistorySelectedIndex, escapeKeyCount, setEscapeKeyCount, escapeKeyTimer, getUserMessages, handleHistorySelect, pasteFromClipboard, onSubmit, ensureFocus, } = options;
|
|
5
|
+
const { buffer, disabled, triggerUpdate, forceUpdate, showCommands, setShowCommands, commandSelectedIndex, setCommandSelectedIndex, getFilteredCommands, updateCommandPanelState, onCommand, showFilePicker, setShowFilePicker, fileSelectedIndex, setFileSelectedIndex, setFileQuery, setAtSymbolPosition, filteredFileCount, updateFilePickerState, handleFileSelect, fileListRef, showHistoryMenu, setShowHistoryMenu, historySelectedIndex, setHistorySelectedIndex, escapeKeyCount, setEscapeKeyCount, escapeKeyTimer, getUserMessages, handleHistorySelect, currentHistoryIndex, navigateHistoryUp, navigateHistoryDown, resetHistoryNavigation, saveToHistory, pasteFromClipboard, onSubmit, ensureFocus, } = options;
|
|
6
6
|
// Track paste detection
|
|
7
7
|
const inputBuffer = useRef('');
|
|
8
8
|
const inputTimer = useRef(null);
|
|
@@ -129,18 +129,18 @@ export function useKeyboardInput(options) {
|
|
|
129
129
|
}
|
|
130
130
|
// Ctrl+L - Delete from cursor to beginning
|
|
131
131
|
if (key.ctrl && input === 'l') {
|
|
132
|
-
const
|
|
132
|
+
const displayText = buffer.text;
|
|
133
133
|
const cursorPos = buffer.getCursorPosition();
|
|
134
|
-
const afterCursor =
|
|
134
|
+
const afterCursor = displayText.slice(cursorPos);
|
|
135
135
|
buffer.setText(afterCursor);
|
|
136
136
|
forceStateUpdate();
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
139
|
// Ctrl+R - Delete from cursor to end
|
|
140
140
|
if (key.ctrl && input === 'r') {
|
|
141
|
-
const
|
|
141
|
+
const displayText = buffer.text;
|
|
142
142
|
const cursorPos = buffer.getCursorPosition();
|
|
143
|
-
const beforeCursor =
|
|
143
|
+
const beforeCursor = displayText.slice(0, cursorPos);
|
|
144
144
|
buffer.setText(beforeCursor);
|
|
145
145
|
forceStateUpdate();
|
|
146
146
|
return;
|
|
@@ -221,6 +221,10 @@ export function useKeyboardInput(options) {
|
|
|
221
221
|
}
|
|
222
222
|
// Enter - submit message
|
|
223
223
|
if (key.return) {
|
|
224
|
+
// Reset history navigation on submit
|
|
225
|
+
if (currentHistoryIndex !== -1) {
|
|
226
|
+
resetHistoryNavigation();
|
|
227
|
+
}
|
|
224
228
|
const message = buffer.getFullText().trim();
|
|
225
229
|
if (message) {
|
|
226
230
|
// Check if message is a command with arguments (e.g., /review [note])
|
|
@@ -253,6 +257,8 @@ export function useKeyboardInput(options) {
|
|
|
253
257
|
}));
|
|
254
258
|
buffer.setText('');
|
|
255
259
|
forceUpdate({});
|
|
260
|
+
// Save to persistent history
|
|
261
|
+
saveToHistory(message);
|
|
256
262
|
onSubmit(message, validImages.length > 0 ? validImages : undefined);
|
|
257
263
|
}
|
|
258
264
|
return;
|
|
@@ -275,23 +281,59 @@ export function useKeyboardInput(options) {
|
|
|
275
281
|
return;
|
|
276
282
|
}
|
|
277
283
|
if (key.upArrow && !showCommands && !showFilePicker) {
|
|
278
|
-
buffer.moveUp();
|
|
279
284
|
const text = buffer.getFullText();
|
|
280
285
|
const cursorPos = buffer.getCursorPosition();
|
|
281
|
-
|
|
286
|
+
const isEmpty = text.trim() === '';
|
|
287
|
+
const isAtStart = cursorPos === 0;
|
|
288
|
+
const hasNewline = text.includes('\n');
|
|
289
|
+
// Terminal-style history navigation:
|
|
290
|
+
// 1. Empty input box -> navigate history
|
|
291
|
+
// 2. Cursor at start of single line -> navigate history
|
|
292
|
+
// 3. Otherwise -> normal cursor movement
|
|
293
|
+
if (isEmpty || (!hasNewline && isAtStart)) {
|
|
294
|
+
const navigated = navigateHistoryUp();
|
|
295
|
+
if (navigated) {
|
|
296
|
+
updateFilePickerState(buffer.getFullText(), buffer.getCursorPosition());
|
|
297
|
+
triggerUpdate();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// Normal cursor movement
|
|
302
|
+
buffer.moveUp();
|
|
303
|
+
updateFilePickerState(buffer.getFullText(), buffer.getCursorPosition());
|
|
282
304
|
triggerUpdate();
|
|
283
305
|
return;
|
|
284
306
|
}
|
|
285
307
|
if (key.downArrow && !showCommands && !showFilePicker) {
|
|
286
|
-
buffer.moveDown();
|
|
287
308
|
const text = buffer.getFullText();
|
|
288
309
|
const cursorPos = buffer.getCursorPosition();
|
|
289
|
-
|
|
310
|
+
const isEmpty = text.trim() === '';
|
|
311
|
+
const isAtEnd = cursorPos === text.length;
|
|
312
|
+
const hasNewline = text.includes('\n');
|
|
313
|
+
// Terminal-style history navigation:
|
|
314
|
+
// 1. Empty input box -> navigate history (if in history mode)
|
|
315
|
+
// 2. Cursor at end of single line -> navigate history (if in history mode)
|
|
316
|
+
// 3. Otherwise -> normal cursor movement
|
|
317
|
+
if ((isEmpty || (!hasNewline && isAtEnd)) && currentHistoryIndex !== -1) {
|
|
318
|
+
const navigated = navigateHistoryDown();
|
|
319
|
+
if (navigated) {
|
|
320
|
+
updateFilePickerState(buffer.getFullText(), buffer.getCursorPosition());
|
|
321
|
+
triggerUpdate();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Normal cursor movement
|
|
326
|
+
buffer.moveDown();
|
|
327
|
+
updateFilePickerState(buffer.getFullText(), buffer.getCursorPosition());
|
|
290
328
|
triggerUpdate();
|
|
291
329
|
return;
|
|
292
330
|
}
|
|
293
331
|
// Regular character input
|
|
294
332
|
if (input && !key.ctrl && !key.meta && !key.escape) {
|
|
333
|
+
// Reset history navigation when user starts typing
|
|
334
|
+
if (currentHistoryIndex !== -1) {
|
|
335
|
+
resetHistoryNavigation();
|
|
336
|
+
}
|
|
295
337
|
// Ensure focus is active when user is typing (handles delayed focus events)
|
|
296
338
|
// This is especially important for drag-and-drop operations where focus
|
|
297
339
|
// events may arrive out of order or be filtered by sanitizeInput
|
|
@@ -44,6 +44,10 @@ export declare class ACECodeSearchService {
|
|
|
44
44
|
* Find symbol definition (go to definition)
|
|
45
45
|
*/
|
|
46
46
|
findDefinition(symbolName: string, contextFile?: string): Promise<CodeSymbol | null>;
|
|
47
|
+
/**
|
|
48
|
+
* Expand glob patterns with braces like "*.{ts,tsx}" into multiple patterns
|
|
49
|
+
*/
|
|
50
|
+
private expandGlobBraces;
|
|
47
51
|
/**
|
|
48
52
|
* Strategy 1: Use git grep for fast searching in Git repositories
|
|
49
53
|
*/
|
|
@@ -470,6 +470,20 @@ export class ACECodeSearchService {
|
|
|
470
470
|
}
|
|
471
471
|
return null;
|
|
472
472
|
}
|
|
473
|
+
/**
|
|
474
|
+
* Expand glob patterns with braces like "*.{ts,tsx}" into multiple patterns
|
|
475
|
+
*/
|
|
476
|
+
expandGlobBraces(glob) {
|
|
477
|
+
// Match {a,b,c} pattern
|
|
478
|
+
const braceMatch = glob.match(/^(.+)\{([^}]+)\}(.*)$/);
|
|
479
|
+
if (!braceMatch || !braceMatch[1] || !braceMatch[2] || braceMatch[3] === undefined) {
|
|
480
|
+
return [glob];
|
|
481
|
+
}
|
|
482
|
+
const prefix = braceMatch[1];
|
|
483
|
+
const alternatives = braceMatch[2].split(',');
|
|
484
|
+
const suffix = braceMatch[3];
|
|
485
|
+
return alternatives.map(alt => `${prefix}${alt}${suffix}`);
|
|
486
|
+
}
|
|
473
487
|
/**
|
|
474
488
|
* Strategy 1: Use git grep for fast searching in Git repositories
|
|
475
489
|
*/
|
|
@@ -484,7 +498,9 @@ export class ACECodeSearchService {
|
|
|
484
498
|
pattern,
|
|
485
499
|
];
|
|
486
500
|
if (fileGlob) {
|
|
487
|
-
|
|
501
|
+
// Expand glob patterns with braces (e.g., "source/**/*.{ts,tsx}" -> ["source/**/*.ts", "source/**/*.tsx"])
|
|
502
|
+
const expandedGlobs = this.expandGlobBraces(fileGlob);
|
|
503
|
+
args.push('--', ...expandedGlobs);
|
|
488
504
|
}
|
|
489
505
|
const child = spawn('git', args, {
|
|
490
506
|
cwd: this.basePath,
|
package/dist/mcp/filesystem.js
CHANGED
|
@@ -411,7 +411,7 @@ export class FilesystemMCPService {
|
|
|
411
411
|
for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
|
|
412
412
|
// Yield control periodically to prevent UI freeze
|
|
413
413
|
if (i % YIELD_INTERVAL === 0) {
|
|
414
|
-
await new Promise(resolve =>
|
|
414
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
415
415
|
}
|
|
416
416
|
// Quick pre-filter: check first line similarity (only for multi-line searches)
|
|
417
417
|
if (usePreFilter) {
|
|
@@ -456,7 +456,7 @@ export class FilesystemMCPService {
|
|
|
456
456
|
for (let i = 0; i <= contentLines.length - correctedSearchLines.length; i++) {
|
|
457
457
|
// Yield control periodically to prevent UI freeze
|
|
458
458
|
if (i % YIELD_INTERVAL === 0) {
|
|
459
|
-
await new Promise(resolve =>
|
|
459
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
460
460
|
}
|
|
461
461
|
const candidateLines = contentLines.slice(i, i + correctedSearchLines.length);
|
|
462
462
|
const candidateContent = candidateLines.join('\n');
|
|
@@ -22,7 +22,7 @@ export async function findClosestMatches(searchContent, fileLines, topN = 3) {
|
|
|
22
22
|
for (let i = 0; i <= fileLines.length - searchLines.length; i++) {
|
|
23
23
|
// Yield control periodically to prevent UI freeze
|
|
24
24
|
if (i % YIELD_INTERVAL === 0) {
|
|
25
|
-
await new Promise(resolve =>
|
|
25
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
26
26
|
}
|
|
27
27
|
// Quick pre-filter: check first line similarity (only for multi-line)
|
|
28
28
|
if (usePreFilter) {
|
|
@@ -48,7 +48,7 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
|
|
|
48
48
|
// Use file picker hook
|
|
49
49
|
const { showFilePicker, setShowFilePicker, fileSelectedIndex, setFileSelectedIndex, fileQuery, setFileQuery, atSymbolPosition, setAtSymbolPosition, filteredFileCount, updateFilePickerState, handleFileSelect, handleFilteredCountChange, fileListRef, } = useFilePicker(buffer, triggerUpdate);
|
|
50
50
|
// Use history navigation hook
|
|
51
|
-
const { showHistoryMenu, setShowHistoryMenu, historySelectedIndex, setHistorySelectedIndex, escapeKeyCount, setEscapeKeyCount, escapeKeyTimer, getUserMessages, handleHistorySelect, } = useHistoryNavigation(buffer, triggerUpdate, chatHistory, onHistorySelect);
|
|
51
|
+
const { showHistoryMenu, setShowHistoryMenu, historySelectedIndex, setHistorySelectedIndex, escapeKeyCount, setEscapeKeyCount, escapeKeyTimer, getUserMessages, handleHistorySelect, currentHistoryIndex, navigateHistoryUp, navigateHistoryDown, resetHistoryNavigation, saveToHistory, } = useHistoryNavigation(buffer, triggerUpdate, chatHistory, onHistorySelect);
|
|
52
52
|
// Use clipboard hook
|
|
53
53
|
const { pasteFromClipboard } = useClipboard(buffer, updateCommandPanelState, updateFilePickerState, triggerUpdate);
|
|
54
54
|
// Use keyboard input hook
|
|
@@ -85,6 +85,11 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
|
|
|
85
85
|
escapeKeyTimer,
|
|
86
86
|
getUserMessages,
|
|
87
87
|
handleHistorySelect,
|
|
88
|
+
currentHistoryIndex,
|
|
89
|
+
navigateHistoryUp,
|
|
90
|
+
navigateHistoryDown,
|
|
91
|
+
resetHistoryNavigation,
|
|
92
|
+
saveToHistory,
|
|
88
93
|
pasteFromClipboard,
|
|
89
94
|
onSubmit,
|
|
90
95
|
ensureFocus,
|
|
@@ -95,7 +100,9 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
|
|
|
95
100
|
buffer.setText(initialContent);
|
|
96
101
|
triggerUpdate();
|
|
97
102
|
}
|
|
98
|
-
|
|
103
|
+
// Only run when initialContent changes
|
|
104
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
105
|
+
}, [initialContent]);
|
|
99
106
|
// Force full re-render when file picker visibility changes to prevent artifacts
|
|
100
107
|
useEffect(() => {
|
|
101
108
|
// Use a small delay to ensure the component tree has updated
|
|
@@ -103,7 +110,7 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
|
|
|
103
110
|
forceUpdate({});
|
|
104
111
|
}, 10);
|
|
105
112
|
return () => clearTimeout(timer);
|
|
106
|
-
}, [showFilePicker
|
|
113
|
+
}, [showFilePicker]);
|
|
107
114
|
// Handle terminal width changes with debounce (like gemini-cli)
|
|
108
115
|
useEffect(() => {
|
|
109
116
|
// Skip on initial mount
|
|
@@ -117,7 +124,7 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
|
|
|
117
124
|
forceUpdate({});
|
|
118
125
|
}, 100);
|
|
119
126
|
return () => clearTimeout(timer);
|
|
120
|
-
}, [terminalWidth
|
|
127
|
+
}, [terminalWidth]);
|
|
121
128
|
// Notify parent of context percentage changes
|
|
122
129
|
useEffect(() => {
|
|
123
130
|
if (contextUsage && onContextPercentageChange) {
|
|
@@ -302,7 +302,9 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
302
302
|
// Check if all tool results exist after this assistant message
|
|
303
303
|
for (let j = i + 1; j < messages.length; j++) {
|
|
304
304
|
const followMsg = messages[j];
|
|
305
|
-
if (followMsg &&
|
|
305
|
+
if (followMsg &&
|
|
306
|
+
followMsg.role === 'tool' &&
|
|
307
|
+
followMsg.tool_call_id) {
|
|
306
308
|
toolCallIds.delete(followMsg.tool_call_id);
|
|
307
309
|
}
|
|
308
310
|
}
|
|
@@ -908,7 +910,9 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
908
910
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
909
911
|
React.createElement(ShimmerText, { text: streamingState.isReasoning
|
|
910
912
|
? 'Deep thinking...'
|
|
911
|
-
:
|
|
913
|
+
: streamingState.streamTokenCount > 0
|
|
914
|
+
? 'Writing...'
|
|
915
|
+
: 'Thinking...' }),
|
|
912
916
|
' ',
|
|
913
917
|
"(",
|
|
914
918
|
formatElapsedTime(streamingState.elapsedSeconds),
|