snow-ai 0.2.15 → 0.2.16
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/api/anthropic.d.ts +1 -1
- package/dist/api/anthropic.js +52 -76
- package/dist/api/chat.d.ts +4 -4
- package/dist/api/chat.js +32 -17
- package/dist/api/gemini.d.ts +1 -1
- package/dist/api/gemini.js +20 -13
- package/dist/api/responses.d.ts +5 -5
- package/dist/api/responses.js +29 -27
- package/dist/app.js +4 -1
- package/dist/hooks/useClipboard.d.ts +4 -0
- package/dist/hooks/useClipboard.js +120 -0
- package/dist/hooks/useCommandHandler.d.ts +26 -0
- package/dist/hooks/useCommandHandler.js +158 -0
- package/dist/hooks/useCommandPanel.d.ts +16 -0
- package/dist/hooks/useCommandPanel.js +53 -0
- package/dist/hooks/useConversation.d.ts +9 -1
- package/dist/hooks/useConversation.js +152 -58
- package/dist/hooks/useFilePicker.d.ts +17 -0
- package/dist/hooks/useFilePicker.js +91 -0
- package/dist/hooks/useHistoryNavigation.d.ts +21 -0
- package/dist/hooks/useHistoryNavigation.js +50 -0
- package/dist/hooks/useInputBuffer.d.ts +6 -0
- package/dist/hooks/useInputBuffer.js +29 -0
- package/dist/hooks/useKeyboardInput.d.ts +51 -0
- package/dist/hooks/useKeyboardInput.js +272 -0
- package/dist/hooks/useSnapshotState.d.ts +12 -0
- package/dist/hooks/useSnapshotState.js +28 -0
- package/dist/hooks/useStreamingState.d.ts +24 -0
- package/dist/hooks/useStreamingState.js +96 -0
- package/dist/hooks/useVSCodeState.d.ts +8 -0
- package/dist/hooks/useVSCodeState.js +63 -0
- package/dist/mcp/filesystem.d.ts +24 -5
- package/dist/mcp/filesystem.js +52 -17
- package/dist/mcp/todo.js +4 -8
- package/dist/ui/components/ChatInput.js +68 -557
- package/dist/ui/components/DiffViewer.js +57 -30
- package/dist/ui/components/FileList.js +70 -26
- package/dist/ui/components/MessageList.d.ts +6 -0
- package/dist/ui/components/MessageList.js +47 -15
- package/dist/ui/components/ShimmerText.d.ts +9 -0
- package/dist/ui/components/ShimmerText.js +30 -0
- package/dist/ui/components/TodoTree.d.ts +1 -1
- package/dist/ui/components/TodoTree.js +0 -4
- package/dist/ui/components/ToolConfirmation.js +14 -6
- package/dist/ui/pages/ChatScreen.js +159 -359
- package/dist/ui/pages/CustomHeadersScreen.d.ts +6 -0
- package/dist/ui/pages/CustomHeadersScreen.js +104 -0
- package/dist/ui/pages/WelcomeScreen.js +5 -0
- package/dist/utils/apiConfig.d.ts +10 -0
- package/dist/utils/apiConfig.js +51 -0
- package/dist/utils/incrementalSnapshot.d.ts +8 -0
- package/dist/utils/incrementalSnapshot.js +63 -0
- package/dist/utils/mcpToolsManager.js +6 -1
- package/dist/utils/retryUtils.d.ts +22 -0
- package/dist/utils/retryUtils.js +180 -0
- package/dist/utils/sessionConverter.js +80 -17
- package/dist/utils/sessionManager.js +35 -4
- package/dist/utils/textUtils.d.ts +4 -0
- package/dist/utils/textUtils.js +19 -0
- package/dist/utils/todoPreprocessor.d.ts +1 -1
- package/dist/utils/todoPreprocessor.js +0 -1
- package/dist/utils/vscodeConnection.d.ts +8 -0
- package/dist/utils/vscodeConnection.js +44 -0
- package/package.json +1 -1
- package/readme.md +3 -1
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { useRef, useEffect } from 'react';
|
|
2
|
+
import { useInput } from 'ink';
|
|
3
|
+
import { executeCommand } from '../utils/commandExecutor.js';
|
|
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, } = options;
|
|
6
|
+
// Track paste detection
|
|
7
|
+
const inputBuffer = useRef('');
|
|
8
|
+
const inputTimer = useRef(null);
|
|
9
|
+
// Cleanup timer on unmount
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
return () => {
|
|
12
|
+
if (inputTimer.current) {
|
|
13
|
+
clearTimeout(inputTimer.current);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}, []);
|
|
17
|
+
// Force immediate state update for critical operations like backspace
|
|
18
|
+
const forceStateUpdate = () => {
|
|
19
|
+
const text = buffer.getFullText();
|
|
20
|
+
const cursorPos = buffer.getCursorPosition();
|
|
21
|
+
updateFilePickerState(text, cursorPos);
|
|
22
|
+
updateCommandPanelState(text);
|
|
23
|
+
forceUpdate({});
|
|
24
|
+
};
|
|
25
|
+
// Handle input using useInput hook
|
|
26
|
+
useInput((input, key) => {
|
|
27
|
+
if (disabled)
|
|
28
|
+
return;
|
|
29
|
+
// Handle escape key for double-ESC history navigation
|
|
30
|
+
if (key.escape) {
|
|
31
|
+
// Close file picker if open
|
|
32
|
+
if (showFilePicker) {
|
|
33
|
+
setShowFilePicker(false);
|
|
34
|
+
setFileSelectedIndex(0);
|
|
35
|
+
setFileQuery('');
|
|
36
|
+
setAtSymbolPosition(-1);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Don't interfere with existing ESC behavior if in command panel
|
|
40
|
+
if (showCommands) {
|
|
41
|
+
setShowCommands(false);
|
|
42
|
+
setCommandSelectedIndex(0);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Handle history navigation
|
|
46
|
+
if (showHistoryMenu) {
|
|
47
|
+
setShowHistoryMenu(false);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Count escape key presses for double-ESC detection
|
|
51
|
+
setEscapeKeyCount(prev => prev + 1);
|
|
52
|
+
// Clear any existing timer
|
|
53
|
+
if (escapeKeyTimer.current) {
|
|
54
|
+
clearTimeout(escapeKeyTimer.current);
|
|
55
|
+
}
|
|
56
|
+
// Set timer to reset count after 500ms
|
|
57
|
+
escapeKeyTimer.current = setTimeout(() => {
|
|
58
|
+
setEscapeKeyCount(0);
|
|
59
|
+
}, 500);
|
|
60
|
+
// Check for double escape
|
|
61
|
+
if (escapeKeyCount >= 1) {
|
|
62
|
+
// This will be 2 after increment
|
|
63
|
+
const userMessages = getUserMessages();
|
|
64
|
+
if (userMessages.length > 0) {
|
|
65
|
+
setShowHistoryMenu(true);
|
|
66
|
+
setHistorySelectedIndex(0); // Reset selection to first item
|
|
67
|
+
setEscapeKeyCount(0);
|
|
68
|
+
if (escapeKeyTimer.current) {
|
|
69
|
+
clearTimeout(escapeKeyTimer.current);
|
|
70
|
+
escapeKeyTimer.current = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// Handle history menu navigation
|
|
77
|
+
if (showHistoryMenu) {
|
|
78
|
+
const userMessages = getUserMessages();
|
|
79
|
+
// Up arrow in history menu
|
|
80
|
+
if (key.upArrow) {
|
|
81
|
+
setHistorySelectedIndex(prev => Math.max(0, prev - 1));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// Down arrow in history menu
|
|
85
|
+
if (key.downArrow) {
|
|
86
|
+
const maxIndex = Math.max(0, userMessages.length - 1);
|
|
87
|
+
setHistorySelectedIndex(prev => Math.min(maxIndex, prev + 1));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Enter - select history item
|
|
91
|
+
if (key.return) {
|
|
92
|
+
if (userMessages.length > 0 &&
|
|
93
|
+
historySelectedIndex < userMessages.length) {
|
|
94
|
+
const selectedMessage = userMessages[historySelectedIndex];
|
|
95
|
+
if (selectedMessage) {
|
|
96
|
+
handleHistorySelect(selectedMessage.value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// For any other key in history menu, just return to prevent interference
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Ctrl+L - Delete from cursor to beginning
|
|
105
|
+
if (key.ctrl && input === 'l') {
|
|
106
|
+
const fullText = buffer.getFullText();
|
|
107
|
+
const cursorPos = buffer.getCursorPosition();
|
|
108
|
+
const afterCursor = fullText.slice(cursorPos);
|
|
109
|
+
buffer.setText(afterCursor);
|
|
110
|
+
forceStateUpdate();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Ctrl+R - Delete from cursor to end
|
|
114
|
+
if (key.ctrl && input === 'r') {
|
|
115
|
+
const fullText = buffer.getFullText();
|
|
116
|
+
const cursorPos = buffer.getCursorPosition();
|
|
117
|
+
const beforeCursor = fullText.slice(0, cursorPos);
|
|
118
|
+
buffer.setText(beforeCursor);
|
|
119
|
+
forceStateUpdate();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Windows: Alt+V, macOS: Ctrl+V - Paste from clipboard (including images)
|
|
123
|
+
const isPasteShortcut = process.platform === 'darwin'
|
|
124
|
+
? key.ctrl && input === 'v'
|
|
125
|
+
: key.meta && input === 'v';
|
|
126
|
+
if (isPasteShortcut) {
|
|
127
|
+
pasteFromClipboard();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Backspace
|
|
131
|
+
if (key.backspace || key.delete) {
|
|
132
|
+
buffer.backspace();
|
|
133
|
+
forceStateUpdate();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// Handle file picker navigation
|
|
137
|
+
if (showFilePicker) {
|
|
138
|
+
// Up arrow in file picker
|
|
139
|
+
if (key.upArrow) {
|
|
140
|
+
setFileSelectedIndex(prev => Math.max(0, prev - 1));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Down arrow in file picker
|
|
144
|
+
if (key.downArrow) {
|
|
145
|
+
const maxIndex = Math.max(0, filteredFileCount - 1);
|
|
146
|
+
setFileSelectedIndex(prev => Math.min(maxIndex, prev + 1));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// Tab or Enter - select file
|
|
150
|
+
if (key.tab || key.return) {
|
|
151
|
+
if (filteredFileCount > 0 && fileSelectedIndex < filteredFileCount) {
|
|
152
|
+
const selectedFile = fileListRef.current?.getSelectedFile();
|
|
153
|
+
if (selectedFile) {
|
|
154
|
+
handleFileSelect(selectedFile);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Handle command panel navigation
|
|
161
|
+
if (showCommands) {
|
|
162
|
+
const filteredCommands = getFilteredCommands();
|
|
163
|
+
// Up arrow in command panel
|
|
164
|
+
if (key.upArrow) {
|
|
165
|
+
setCommandSelectedIndex(prev => Math.max(0, prev - 1));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Down arrow in command panel
|
|
169
|
+
if (key.downArrow) {
|
|
170
|
+
const maxIndex = Math.max(0, filteredCommands.length - 1);
|
|
171
|
+
setCommandSelectedIndex(prev => Math.min(maxIndex, prev + 1));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// Enter - select command
|
|
175
|
+
if (key.return) {
|
|
176
|
+
if (filteredCommands.length > 0 &&
|
|
177
|
+
commandSelectedIndex < filteredCommands.length) {
|
|
178
|
+
const selectedCommand = filteredCommands[commandSelectedIndex];
|
|
179
|
+
if (selectedCommand) {
|
|
180
|
+
// Execute command instead of inserting text
|
|
181
|
+
executeCommand(selectedCommand.name).then(result => {
|
|
182
|
+
if (onCommand) {
|
|
183
|
+
onCommand(selectedCommand.name, result);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
buffer.setText('');
|
|
187
|
+
setShowCommands(false);
|
|
188
|
+
setCommandSelectedIndex(0);
|
|
189
|
+
triggerUpdate();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// If no commands available, fall through to normal Enter handling
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Enter - submit message
|
|
197
|
+
if (key.return) {
|
|
198
|
+
const message = buffer.getFullText().trim();
|
|
199
|
+
if (message) {
|
|
200
|
+
// Get images data, but only include images whose placeholders still exist
|
|
201
|
+
const currentText = buffer.text; // Use internal text (includes placeholders)
|
|
202
|
+
const allImages = buffer.getImages();
|
|
203
|
+
const validImages = allImages
|
|
204
|
+
.filter(img => currentText.includes(img.placeholder))
|
|
205
|
+
.map(img => ({
|
|
206
|
+
data: img.data,
|
|
207
|
+
mimeType: img.mimeType,
|
|
208
|
+
}));
|
|
209
|
+
buffer.setText('');
|
|
210
|
+
forceUpdate({});
|
|
211
|
+
onSubmit(message, validImages.length > 0 ? validImages : undefined);
|
|
212
|
+
}
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
// Arrow keys for cursor movement
|
|
216
|
+
if (key.leftArrow) {
|
|
217
|
+
buffer.moveLeft();
|
|
218
|
+
const text = buffer.getFullText();
|
|
219
|
+
const cursorPos = buffer.getCursorPosition();
|
|
220
|
+
updateFilePickerState(text, cursorPos);
|
|
221
|
+
triggerUpdate();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (key.rightArrow) {
|
|
225
|
+
buffer.moveRight();
|
|
226
|
+
const text = buffer.getFullText();
|
|
227
|
+
const cursorPos = buffer.getCursorPosition();
|
|
228
|
+
updateFilePickerState(text, cursorPos);
|
|
229
|
+
triggerUpdate();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (key.upArrow && !showCommands && !showFilePicker) {
|
|
233
|
+
buffer.moveUp();
|
|
234
|
+
const text = buffer.getFullText();
|
|
235
|
+
const cursorPos = buffer.getCursorPosition();
|
|
236
|
+
updateFilePickerState(text, cursorPos);
|
|
237
|
+
triggerUpdate();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (key.downArrow && !showCommands && !showFilePicker) {
|
|
241
|
+
buffer.moveDown();
|
|
242
|
+
const text = buffer.getFullText();
|
|
243
|
+
const cursorPos = buffer.getCursorPosition();
|
|
244
|
+
updateFilePickerState(text, cursorPos);
|
|
245
|
+
triggerUpdate();
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
// Regular character input
|
|
249
|
+
if (input && !key.ctrl && !key.meta && !key.escape) {
|
|
250
|
+
// Accumulate input for paste detection
|
|
251
|
+
inputBuffer.current += input;
|
|
252
|
+
// Clear existing timer
|
|
253
|
+
if (inputTimer.current) {
|
|
254
|
+
clearTimeout(inputTimer.current);
|
|
255
|
+
}
|
|
256
|
+
// Set timer to process accumulated input
|
|
257
|
+
inputTimer.current = setTimeout(() => {
|
|
258
|
+
const accumulated = inputBuffer.current;
|
|
259
|
+
inputBuffer.current = '';
|
|
260
|
+
// If we accumulated input, it's likely a paste
|
|
261
|
+
if (accumulated) {
|
|
262
|
+
buffer.insert(accumulated);
|
|
263
|
+
const text = buffer.getFullText();
|
|
264
|
+
const cursorPos = buffer.getCursorPosition();
|
|
265
|
+
updateCommandPanelState(text);
|
|
266
|
+
updateFilePickerState(text, cursorPos);
|
|
267
|
+
triggerUpdate();
|
|
268
|
+
}
|
|
269
|
+
}, 10); // Short delay to accumulate rapid input
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function useSnapshotState(messagesLength: number): {
|
|
2
|
+
snapshotFileCount: Map<number, number>;
|
|
3
|
+
setSnapshotFileCount: import("react").Dispatch<import("react").SetStateAction<Map<number, number>>>;
|
|
4
|
+
pendingRollback: {
|
|
5
|
+
messageIndex: number;
|
|
6
|
+
fileCount: number;
|
|
7
|
+
} | null;
|
|
8
|
+
setPendingRollback: import("react").Dispatch<import("react").SetStateAction<{
|
|
9
|
+
messageIndex: number;
|
|
10
|
+
fileCount: number;
|
|
11
|
+
} | null>>;
|
|
12
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { sessionManager } from '../utils/sessionManager.js';
|
|
3
|
+
import { incrementalSnapshotManager } from '../utils/incrementalSnapshot.js';
|
|
4
|
+
export function useSnapshotState(messagesLength) {
|
|
5
|
+
const [snapshotFileCount, setSnapshotFileCount] = useState(new Map());
|
|
6
|
+
const [pendingRollback, setPendingRollback] = useState(null);
|
|
7
|
+
// Load snapshot file counts when session changes
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const loadSnapshotFileCounts = async () => {
|
|
10
|
+
const currentSession = sessionManager.getCurrentSession();
|
|
11
|
+
if (!currentSession)
|
|
12
|
+
return;
|
|
13
|
+
const snapshots = await incrementalSnapshotManager.listSnapshots(currentSession.id);
|
|
14
|
+
const counts = new Map();
|
|
15
|
+
for (const snapshot of snapshots) {
|
|
16
|
+
counts.set(snapshot.messageIndex, snapshot.fileCount);
|
|
17
|
+
}
|
|
18
|
+
setSnapshotFileCount(counts);
|
|
19
|
+
};
|
|
20
|
+
loadSnapshotFileCounts();
|
|
21
|
+
}, [messagesLength]); // Reload when messages change
|
|
22
|
+
return {
|
|
23
|
+
snapshotFileCount,
|
|
24
|
+
setSnapshotFileCount,
|
|
25
|
+
pendingRollback,
|
|
26
|
+
setPendingRollback,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { UsageInfo } from '../api/chat.js';
|
|
2
|
+
export type RetryStatus = {
|
|
3
|
+
isRetrying: boolean;
|
|
4
|
+
attempt: number;
|
|
5
|
+
nextDelay: number;
|
|
6
|
+
remainingSeconds?: number;
|
|
7
|
+
errorMessage?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function useStreamingState(): {
|
|
10
|
+
isStreaming: boolean;
|
|
11
|
+
setIsStreaming: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
12
|
+
streamTokenCount: number;
|
|
13
|
+
setStreamTokenCount: import("react").Dispatch<import("react").SetStateAction<number>>;
|
|
14
|
+
isReasoning: boolean;
|
|
15
|
+
setIsReasoning: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
16
|
+
abortController: AbortController | null;
|
|
17
|
+
setAbortController: import("react").Dispatch<import("react").SetStateAction<AbortController | null>>;
|
|
18
|
+
contextUsage: UsageInfo | null;
|
|
19
|
+
setContextUsage: import("react").Dispatch<import("react").SetStateAction<UsageInfo | null>>;
|
|
20
|
+
elapsedSeconds: number;
|
|
21
|
+
retryStatus: RetryStatus | null;
|
|
22
|
+
setRetryStatus: import("react").Dispatch<import("react").SetStateAction<RetryStatus | null>>;
|
|
23
|
+
animationFrame: number;
|
|
24
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
export function useStreamingState() {
|
|
3
|
+
const [isStreaming, setIsStreaming] = useState(false);
|
|
4
|
+
const [streamTokenCount, setStreamTokenCount] = useState(0);
|
|
5
|
+
const [isReasoning, setIsReasoning] = useState(false);
|
|
6
|
+
const [abortController, setAbortController] = useState(null);
|
|
7
|
+
const [contextUsage, setContextUsage] = useState(null);
|
|
8
|
+
const [elapsedSeconds, setElapsedSeconds] = useState(0);
|
|
9
|
+
const [timerStartTime, setTimerStartTime] = useState(null);
|
|
10
|
+
const [retryStatus, setRetryStatus] = useState(null);
|
|
11
|
+
const [animationFrame, setAnimationFrame] = useState(0);
|
|
12
|
+
// Animation for streaming/saving indicator
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!isStreaming)
|
|
15
|
+
return;
|
|
16
|
+
const interval = setInterval(() => {
|
|
17
|
+
setAnimationFrame(prev => (prev + 1) % 5);
|
|
18
|
+
}, 300);
|
|
19
|
+
return () => {
|
|
20
|
+
clearInterval(interval);
|
|
21
|
+
setAnimationFrame(0);
|
|
22
|
+
};
|
|
23
|
+
}, [isStreaming]);
|
|
24
|
+
// Timer for tracking request duration
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (isStreaming && timerStartTime === null) {
|
|
27
|
+
// Start timer when streaming begins
|
|
28
|
+
setTimerStartTime(Date.now());
|
|
29
|
+
setElapsedSeconds(0);
|
|
30
|
+
}
|
|
31
|
+
else if (!isStreaming && timerStartTime !== null) {
|
|
32
|
+
// Stop timer when streaming ends
|
|
33
|
+
setTimerStartTime(null);
|
|
34
|
+
}
|
|
35
|
+
}, [isStreaming, timerStartTime]);
|
|
36
|
+
// Update elapsed time every second
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (timerStartTime === null)
|
|
39
|
+
return;
|
|
40
|
+
const interval = setInterval(() => {
|
|
41
|
+
const elapsed = Math.floor((Date.now() - timerStartTime) / 1000);
|
|
42
|
+
setElapsedSeconds(elapsed);
|
|
43
|
+
}, 1000);
|
|
44
|
+
return () => clearInterval(interval);
|
|
45
|
+
}, [timerStartTime]);
|
|
46
|
+
// Countdown timer for retry delays
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!retryStatus || !retryStatus.isRetrying)
|
|
49
|
+
return;
|
|
50
|
+
// Initialize remaining seconds from nextDelay
|
|
51
|
+
if (retryStatus.remainingSeconds === undefined) {
|
|
52
|
+
setRetryStatus(prev => prev
|
|
53
|
+
? {
|
|
54
|
+
...prev,
|
|
55
|
+
remainingSeconds: Math.ceil(prev.nextDelay / 1000),
|
|
56
|
+
}
|
|
57
|
+
: null);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Countdown every second
|
|
61
|
+
const interval = setInterval(() => {
|
|
62
|
+
setRetryStatus(prev => {
|
|
63
|
+
if (!prev || prev.remainingSeconds === undefined)
|
|
64
|
+
return prev;
|
|
65
|
+
const newRemaining = prev.remainingSeconds - 1;
|
|
66
|
+
if (newRemaining <= 0) {
|
|
67
|
+
return {
|
|
68
|
+
...prev,
|
|
69
|
+
remainingSeconds: 0,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
...prev,
|
|
74
|
+
remainingSeconds: newRemaining,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}, 1000);
|
|
78
|
+
return () => clearInterval(interval);
|
|
79
|
+
}, [retryStatus]);
|
|
80
|
+
return {
|
|
81
|
+
isStreaming,
|
|
82
|
+
setIsStreaming,
|
|
83
|
+
streamTokenCount,
|
|
84
|
+
setStreamTokenCount,
|
|
85
|
+
isReasoning,
|
|
86
|
+
setIsReasoning,
|
|
87
|
+
abortController,
|
|
88
|
+
setAbortController,
|
|
89
|
+
contextUsage,
|
|
90
|
+
setContextUsage,
|
|
91
|
+
elapsedSeconds,
|
|
92
|
+
retryStatus,
|
|
93
|
+
setRetryStatus,
|
|
94
|
+
animationFrame,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type EditorContext } from '../utils/vscodeConnection.js';
|
|
2
|
+
export type VSCodeConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
|
|
3
|
+
export declare function useVSCodeState(): {
|
|
4
|
+
vscodeConnected: boolean;
|
|
5
|
+
vscodeConnectionStatus: VSCodeConnectionStatus;
|
|
6
|
+
setVscodeConnectionStatus: import("react").Dispatch<import("react").SetStateAction<VSCodeConnectionStatus>>;
|
|
7
|
+
editorContext: EditorContext;
|
|
8
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { vscodeConnection } from '../utils/vscodeConnection.js';
|
|
3
|
+
export function useVSCodeState() {
|
|
4
|
+
const [vscodeConnected, setVscodeConnected] = useState(false);
|
|
5
|
+
const [vscodeConnectionStatus, setVscodeConnectionStatus] = useState('disconnected');
|
|
6
|
+
const [editorContext, setEditorContext] = useState({});
|
|
7
|
+
// Monitor VSCode connection status and editor context
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const checkConnectionInterval = setInterval(() => {
|
|
10
|
+
const isConnected = vscodeConnection.isConnected();
|
|
11
|
+
setVscodeConnected(isConnected);
|
|
12
|
+
// Update connection status based on actual connection state
|
|
13
|
+
if (isConnected && vscodeConnectionStatus !== 'connected') {
|
|
14
|
+
setVscodeConnectionStatus('connected');
|
|
15
|
+
}
|
|
16
|
+
else if (!isConnected && vscodeConnectionStatus === 'connected') {
|
|
17
|
+
setVscodeConnectionStatus('disconnected');
|
|
18
|
+
}
|
|
19
|
+
}, 1000);
|
|
20
|
+
const unsubscribe = vscodeConnection.onContextUpdate(context => {
|
|
21
|
+
setEditorContext(context);
|
|
22
|
+
// When we receive context, it means connection is successful
|
|
23
|
+
if (vscodeConnectionStatus !== 'connected') {
|
|
24
|
+
setVscodeConnectionStatus('connected');
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return () => {
|
|
28
|
+
clearInterval(checkConnectionInterval);
|
|
29
|
+
unsubscribe();
|
|
30
|
+
};
|
|
31
|
+
}, [vscodeConnectionStatus]);
|
|
32
|
+
// Separate effect for handling connecting timeout
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (vscodeConnectionStatus !== 'connecting') {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// Set timeout for connecting state (30 seconds to allow for VSCode extension reconnection)
|
|
38
|
+
const connectingTimeout = setTimeout(() => {
|
|
39
|
+
const isConnected = vscodeConnection.isConnected();
|
|
40
|
+
const isServerRunning = vscodeConnection.isServerRunning();
|
|
41
|
+
// Only set error if still not connected after timeout
|
|
42
|
+
if (!isConnected) {
|
|
43
|
+
if (isServerRunning) {
|
|
44
|
+
// Server is running but no connection - show error with helpful message
|
|
45
|
+
setVscodeConnectionStatus('error');
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Server not running - go back to disconnected
|
|
49
|
+
setVscodeConnectionStatus('disconnected');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}, 30000); // Increased to 30 seconds
|
|
53
|
+
return () => {
|
|
54
|
+
clearTimeout(connectingTimeout);
|
|
55
|
+
};
|
|
56
|
+
}, [vscodeConnectionStatus]);
|
|
57
|
+
return {
|
|
58
|
+
vscodeConnected,
|
|
59
|
+
vscodeConnectionStatus,
|
|
60
|
+
setVscodeConnectionStatus,
|
|
61
|
+
editorContext,
|
|
62
|
+
};
|
|
63
|
+
}
|
package/dist/mcp/filesystem.d.ts
CHANGED
|
@@ -82,12 +82,12 @@ export declare class FilesystemMCPService {
|
|
|
82
82
|
*/
|
|
83
83
|
createFile(filePath: string, content: string, createDirectories?: boolean): Promise<string>;
|
|
84
84
|
/**
|
|
85
|
-
* Delete
|
|
86
|
-
* @param
|
|
87
|
-
* @returns Success message
|
|
85
|
+
* Delete one or multiple files
|
|
86
|
+
* @param filePaths - Single file path or array of file paths to delete
|
|
87
|
+
* @returns Success message with details
|
|
88
88
|
* @throws Error if file deletion fails
|
|
89
89
|
*/
|
|
90
|
-
deleteFile(
|
|
90
|
+
deleteFile(filePaths: string | string[]): Promise<string>;
|
|
91
91
|
/**
|
|
92
92
|
* List files in a directory
|
|
93
93
|
* @param dirPath - Directory path relative to base path or absolute path
|
|
@@ -181,6 +181,7 @@ export declare const mcpTools: ({
|
|
|
181
181
|
};
|
|
182
182
|
content?: undefined;
|
|
183
183
|
createDirectories?: undefined;
|
|
184
|
+
filePaths?: undefined;
|
|
184
185
|
dirPath?: undefined;
|
|
185
186
|
newContent?: undefined;
|
|
186
187
|
contextLines?: undefined;
|
|
@@ -213,6 +214,7 @@ export declare const mcpTools: ({
|
|
|
213
214
|
};
|
|
214
215
|
startLine?: undefined;
|
|
215
216
|
endLine?: undefined;
|
|
217
|
+
filePaths?: undefined;
|
|
216
218
|
dirPath?: undefined;
|
|
217
219
|
newContent?: undefined;
|
|
218
220
|
contextLines?: undefined;
|
|
@@ -234,6 +236,20 @@ export declare const mcpTools: ({
|
|
|
234
236
|
type: string;
|
|
235
237
|
description: string;
|
|
236
238
|
};
|
|
239
|
+
filePaths: {
|
|
240
|
+
oneOf: ({
|
|
241
|
+
type: string;
|
|
242
|
+
description: string;
|
|
243
|
+
items?: undefined;
|
|
244
|
+
} | {
|
|
245
|
+
type: string;
|
|
246
|
+
items: {
|
|
247
|
+
type: string;
|
|
248
|
+
};
|
|
249
|
+
description: string;
|
|
250
|
+
})[];
|
|
251
|
+
description: string;
|
|
252
|
+
};
|
|
237
253
|
startLine?: undefined;
|
|
238
254
|
endLine?: undefined;
|
|
239
255
|
content?: undefined;
|
|
@@ -247,7 +263,7 @@ export declare const mcpTools: ({
|
|
|
247
263
|
maxResults?: undefined;
|
|
248
264
|
searchMode?: undefined;
|
|
249
265
|
};
|
|
250
|
-
required
|
|
266
|
+
required?: undefined;
|
|
251
267
|
};
|
|
252
268
|
} | {
|
|
253
269
|
name: string;
|
|
@@ -265,6 +281,7 @@ export declare const mcpTools: ({
|
|
|
265
281
|
endLine?: undefined;
|
|
266
282
|
content?: undefined;
|
|
267
283
|
createDirectories?: undefined;
|
|
284
|
+
filePaths?: undefined;
|
|
268
285
|
newContent?: undefined;
|
|
269
286
|
contextLines?: undefined;
|
|
270
287
|
query?: undefined;
|
|
@@ -304,6 +321,7 @@ export declare const mcpTools: ({
|
|
|
304
321
|
};
|
|
305
322
|
content?: undefined;
|
|
306
323
|
createDirectories?: undefined;
|
|
324
|
+
filePaths?: undefined;
|
|
307
325
|
dirPath?: undefined;
|
|
308
326
|
query?: undefined;
|
|
309
327
|
fileExtensions?: undefined;
|
|
@@ -357,6 +375,7 @@ export declare const mcpTools: ({
|
|
|
357
375
|
endLine?: undefined;
|
|
358
376
|
content?: undefined;
|
|
359
377
|
createDirectories?: undefined;
|
|
378
|
+
filePaths?: undefined;
|
|
360
379
|
newContent?: undefined;
|
|
361
380
|
contextLines?: undefined;
|
|
362
381
|
};
|