snow-ai 0.2.12 → 0.2.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/api/anthropic.js +15 -4
- package/dist/api/systemPrompt.d.ts +1 -1
- package/dist/api/systemPrompt.js +149 -40
- package/dist/hooks/useConversation.d.ts +1 -1
- package/dist/hooks/useConversation.js +5 -3
- package/dist/hooks/useGlobalNavigation.js +2 -0
- package/dist/hooks/useToolConfirmation.d.ts +2 -1
- package/dist/hooks/useToolConfirmation.js +2 -1
- package/dist/mcp/filesystem.d.ts +16 -1
- package/dist/mcp/filesystem.js +193 -89
- package/dist/mcp/multiLanguageASTParser.d.ts +67 -0
- package/dist/mcp/multiLanguageASTParser.js +360 -0
- package/dist/mcp/todo.d.ts +1 -1
- package/dist/mcp/todo.js +21 -26
- package/dist/ui/components/ChatInput.d.ts +1 -1
- package/dist/ui/components/ChatInput.js +84 -45
- package/dist/ui/components/DiffViewer.d.ts +1 -2
- package/dist/ui/components/DiffViewer.js +65 -65
- package/dist/ui/components/MCPInfoPanel.js +1 -2
- package/dist/ui/components/TodoTree.js +1 -1
- package/dist/ui/components/ToolConfirmation.d.ts +11 -1
- package/dist/ui/components/ToolConfirmation.js +86 -6
- package/dist/ui/pages/ChatScreen.js +223 -111
- package/dist/utils/commands/ide.js +18 -1
- package/dist/utils/mcpToolsManager.d.ts +1 -1
- package/dist/utils/mcpToolsManager.js +45 -36
- package/dist/utils/textBuffer.d.ts +5 -0
- package/dist/utils/textBuffer.js +23 -2
- package/dist/utils/vscodeConnection.js +10 -1
- package/package.json +13 -1
- package/readme.md +12 -2
|
@@ -18,7 +18,7 @@ import { sessionManager } from '../../utils/sessionManager.js';
|
|
|
18
18
|
import { useSessionSave } from '../../hooks/useSessionSave.js';
|
|
19
19
|
import { useToolConfirmation } from '../../hooks/useToolConfirmation.js';
|
|
20
20
|
import { handleConversationWithTools } from '../../hooks/useConversation.js';
|
|
21
|
-
import { parseAndValidateFileReferences, createMessageWithFileInstructions, getSystemInfo } from '../../utils/fileUtils.js';
|
|
21
|
+
import { parseAndValidateFileReferences, createMessageWithFileInstructions, getSystemInfo, } from '../../utils/fileUtils.js';
|
|
22
22
|
import { compressContext } from '../../utils/contextCompressor.js';
|
|
23
23
|
import { incrementalSnapshotManager } from '../../utils/incrementalSnapshot.js';
|
|
24
24
|
// Import commands to register them
|
|
@@ -30,7 +30,7 @@ import '../../utils/commands/init.js';
|
|
|
30
30
|
import '../../utils/commands/ide.js';
|
|
31
31
|
import '../../utils/commands/compact.js';
|
|
32
32
|
import { navigateTo } from '../../hooks/useGlobalNavigation.js';
|
|
33
|
-
import { vscodeConnection } from '../../utils/vscodeConnection.js';
|
|
33
|
+
import { vscodeConnection, } from '../../utils/vscodeConnection.js';
|
|
34
34
|
// Format elapsed time to human readable format
|
|
35
35
|
function formatElapsedTime(seconds) {
|
|
36
36
|
if (seconds < 60) {
|
|
@@ -61,7 +61,16 @@ export default function ChatScreen({}) {
|
|
|
61
61
|
const [showMcpInfo, setShowMcpInfo] = useState(false);
|
|
62
62
|
const [mcpPanelKey, setMcpPanelKey] = useState(0);
|
|
63
63
|
const [streamTokenCount, setStreamTokenCount] = useState(0);
|
|
64
|
-
const [yoloMode, setYoloMode] = useState(
|
|
64
|
+
const [yoloMode, setYoloMode] = useState(() => {
|
|
65
|
+
// Load yolo mode from localStorage on initialization
|
|
66
|
+
try {
|
|
67
|
+
const saved = localStorage.getItem('snow-yolo-mode');
|
|
68
|
+
return saved === 'true';
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
65
74
|
const [contextUsage, setContextUsage] = useState(null);
|
|
66
75
|
const [elapsedSeconds, setElapsedSeconds] = useState(0);
|
|
67
76
|
const [timerStartTime, setTimerStartTime] = useState(null);
|
|
@@ -85,8 +94,17 @@ export default function ChatScreen({}) {
|
|
|
85
94
|
useEffect(() => {
|
|
86
95
|
pendingMessagesRef.current = pendingMessages;
|
|
87
96
|
}, [pendingMessages]);
|
|
97
|
+
// Persist yolo mode to localStorage
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
try {
|
|
100
|
+
localStorage.setItem('snow-yolo-mode', String(yoloMode));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Ignore localStorage errors
|
|
104
|
+
}
|
|
105
|
+
}, [yoloMode]);
|
|
88
106
|
// Use tool confirmation hook
|
|
89
|
-
const { pendingToolConfirmation, requestToolConfirmation, isToolAutoApproved, addMultipleToAlwaysApproved } = useToolConfirmation();
|
|
107
|
+
const { pendingToolConfirmation, requestToolConfirmation, isToolAutoApproved, addMultipleToAlwaysApproved, } = useToolConfirmation();
|
|
90
108
|
// Animation for streaming/saving indicator
|
|
91
109
|
useEffect(() => {
|
|
92
110
|
if (!isStreaming && !isSaving)
|
|
@@ -123,58 +141,54 @@ export default function ChatScreen({}) {
|
|
|
123
141
|
}, [timerStartTime]);
|
|
124
142
|
// Monitor VSCode connection status and editor context
|
|
125
143
|
useEffect(() => {
|
|
126
|
-
|
|
127
|
-
const checkConnection = setInterval(() => {
|
|
144
|
+
const checkConnectionInterval = setInterval(() => {
|
|
128
145
|
const isConnected = vscodeConnection.isConnected();
|
|
129
|
-
const isServerRunning = vscodeConnection.isServerRunning();
|
|
130
146
|
setVscodeConnected(isConnected);
|
|
131
147
|
// Update connection status based on actual connection state
|
|
132
148
|
if (isConnected && vscodeConnectionStatus !== 'connected') {
|
|
133
149
|
setVscodeConnectionStatus('connected');
|
|
134
|
-
if (connectingTimeout) {
|
|
135
|
-
clearTimeout(connectingTimeout);
|
|
136
|
-
connectingTimeout = null;
|
|
137
|
-
}
|
|
138
150
|
}
|
|
139
151
|
else if (!isConnected && vscodeConnectionStatus === 'connected') {
|
|
140
152
|
setVscodeConnectionStatus('disconnected');
|
|
141
153
|
}
|
|
142
|
-
else if (vscodeConnectionStatus === 'connecting' && !isServerRunning) {
|
|
143
|
-
// Server failed to start
|
|
144
|
-
setVscodeConnectionStatus('error');
|
|
145
|
-
if (connectingTimeout) {
|
|
146
|
-
clearTimeout(connectingTimeout);
|
|
147
|
-
connectingTimeout = null;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
154
|
}, 1000);
|
|
151
|
-
|
|
152
|
-
if (vscodeConnectionStatus === 'connecting') {
|
|
153
|
-
connectingTimeout = setTimeout(() => {
|
|
154
|
-
if (vscodeConnectionStatus === 'connecting') {
|
|
155
|
-
setVscodeConnectionStatus('error');
|
|
156
|
-
}
|
|
157
|
-
}, 15000);
|
|
158
|
-
}
|
|
159
|
-
const unsubscribe = vscodeConnection.onContextUpdate((context) => {
|
|
155
|
+
const unsubscribe = vscodeConnection.onContextUpdate(context => {
|
|
160
156
|
setEditorContext(context);
|
|
161
157
|
// When we receive context, it means connection is successful
|
|
162
158
|
if (vscodeConnectionStatus !== 'connected') {
|
|
163
159
|
setVscodeConnectionStatus('connected');
|
|
164
|
-
if (connectingTimeout) {
|
|
165
|
-
clearTimeout(connectingTimeout);
|
|
166
|
-
connectingTimeout = null;
|
|
167
|
-
}
|
|
168
160
|
}
|
|
169
161
|
});
|
|
170
162
|
return () => {
|
|
171
|
-
clearInterval(
|
|
172
|
-
if (connectingTimeout) {
|
|
173
|
-
clearTimeout(connectingTimeout);
|
|
174
|
-
}
|
|
163
|
+
clearInterval(checkConnectionInterval);
|
|
175
164
|
unsubscribe();
|
|
176
165
|
};
|
|
177
166
|
}, [vscodeConnectionStatus]);
|
|
167
|
+
// Separate effect for handling connecting timeout
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (vscodeConnectionStatus !== 'connecting') {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// Set timeout for connecting state (30 seconds to allow for VSCode extension reconnection)
|
|
173
|
+
const connectingTimeout = setTimeout(() => {
|
|
174
|
+
const isConnected = vscodeConnection.isConnected();
|
|
175
|
+
const isServerRunning = vscodeConnection.isServerRunning();
|
|
176
|
+
// Only set error if still not connected after timeout
|
|
177
|
+
if (!isConnected) {
|
|
178
|
+
if (isServerRunning) {
|
|
179
|
+
// Server is running but no connection - show error with helpful message
|
|
180
|
+
setVscodeConnectionStatus('error');
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
// Server not running - go back to disconnected
|
|
184
|
+
setVscodeConnectionStatus('disconnected');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}, 30000); // Increased to 30 seconds
|
|
188
|
+
return () => {
|
|
189
|
+
clearTimeout(connectingTimeout);
|
|
190
|
+
};
|
|
191
|
+
}, [vscodeConnectionStatus]);
|
|
178
192
|
// Load snapshot file counts when session changes
|
|
179
193
|
useEffect(() => {
|
|
180
194
|
const loadSnapshotFileCounts = async () => {
|
|
@@ -231,12 +245,15 @@ export default function ChatScreen({}) {
|
|
|
231
245
|
// Abort the controller
|
|
232
246
|
abortController.abort();
|
|
233
247
|
// Add discontinued message
|
|
234
|
-
setMessages(prev => [
|
|
248
|
+
setMessages(prev => [
|
|
249
|
+
...prev,
|
|
250
|
+
{
|
|
235
251
|
role: 'assistant',
|
|
236
252
|
content: '',
|
|
237
253
|
streaming: false,
|
|
238
|
-
discontinued: true
|
|
239
|
-
}
|
|
254
|
+
discontinued: true,
|
|
255
|
+
},
|
|
256
|
+
]);
|
|
240
257
|
// Stop streaming state
|
|
241
258
|
setIsStreaming(false);
|
|
242
259
|
setAbortController(null);
|
|
@@ -245,7 +262,9 @@ export default function ChatScreen({}) {
|
|
|
245
262
|
});
|
|
246
263
|
const handleCommandExecution = async (commandName, result) => {
|
|
247
264
|
// Handle /compact command
|
|
248
|
-
if (commandName === 'compact' &&
|
|
265
|
+
if (commandName === 'compact' &&
|
|
266
|
+
result.success &&
|
|
267
|
+
result.action === 'compact') {
|
|
249
268
|
// Set compressing state (不添加命令面板消息)
|
|
250
269
|
setIsCompressing(true);
|
|
251
270
|
setCompressionError(null);
|
|
@@ -256,7 +275,7 @@ export default function ChatScreen({}) {
|
|
|
256
275
|
.map(msg => ({
|
|
257
276
|
role: msg.role,
|
|
258
277
|
content: msg.content,
|
|
259
|
-
tool_call_id: msg.toolCallId
|
|
278
|
+
tool_call_id: msg.toolCallId,
|
|
260
279
|
}));
|
|
261
280
|
// Compress the context
|
|
262
281
|
const result = await compressContext(chatMessages);
|
|
@@ -264,7 +283,7 @@ export default function ChatScreen({}) {
|
|
|
264
283
|
const summaryMessage = {
|
|
265
284
|
role: 'assistant',
|
|
266
285
|
content: result.summary,
|
|
267
|
-
streaming: false
|
|
286
|
+
streaming: false,
|
|
268
287
|
};
|
|
269
288
|
// Clear session and set new compressed state
|
|
270
289
|
sessionManager.clearCurrentSession();
|
|
@@ -275,7 +294,7 @@ export default function ChatScreen({}) {
|
|
|
275
294
|
setContextUsage({
|
|
276
295
|
prompt_tokens: result.usage.prompt_tokens,
|
|
277
296
|
completion_tokens: result.usage.completion_tokens,
|
|
278
|
-
total_tokens: result.usage.total_tokens
|
|
297
|
+
total_tokens: result.usage.total_tokens,
|
|
279
298
|
});
|
|
280
299
|
}
|
|
281
300
|
catch (error) {
|
|
@@ -285,7 +304,7 @@ export default function ChatScreen({}) {
|
|
|
285
304
|
const errorMessage = {
|
|
286
305
|
role: 'assistant',
|
|
287
306
|
content: `**Compression Failed**\n\n${errorMsg}`,
|
|
288
|
-
streaming: false
|
|
307
|
+
streaming: false,
|
|
289
308
|
};
|
|
290
309
|
setMessages(prev => [...prev, errorMessage]);
|
|
291
310
|
}
|
|
@@ -302,7 +321,7 @@ export default function ChatScreen({}) {
|
|
|
302
321
|
const commandMessage = {
|
|
303
322
|
role: 'command',
|
|
304
323
|
content: '',
|
|
305
|
-
commandName: commandName
|
|
324
|
+
commandName: commandName,
|
|
306
325
|
};
|
|
307
326
|
setMessages(prev => [...prev, commandMessage]);
|
|
308
327
|
}
|
|
@@ -322,11 +341,13 @@ export default function ChatScreen({}) {
|
|
|
322
341
|
setRemountKey(prev => prev + 1);
|
|
323
342
|
// Reset context usage (token statistics)
|
|
324
343
|
setContextUsage(null);
|
|
344
|
+
// Note: yoloMode is preserved via localStorage (lines 68-76, 104-111)
|
|
345
|
+
// Note: VSCode connection is preserved and managed by vscodeConnection utility
|
|
325
346
|
// Add command execution feedback
|
|
326
347
|
const commandMessage = {
|
|
327
348
|
role: 'command',
|
|
328
349
|
content: '',
|
|
329
|
-
commandName: commandName
|
|
350
|
+
commandName: commandName,
|
|
330
351
|
};
|
|
331
352
|
setMessages([commandMessage]);
|
|
332
353
|
}
|
|
@@ -335,7 +356,7 @@ export default function ChatScreen({}) {
|
|
|
335
356
|
const commandMessage = {
|
|
336
357
|
role: 'command',
|
|
337
358
|
content: '',
|
|
338
|
-
commandName: commandName
|
|
359
|
+
commandName: commandName,
|
|
339
360
|
};
|
|
340
361
|
setMessages(prev => [...prev, commandMessage]);
|
|
341
362
|
}
|
|
@@ -345,7 +366,7 @@ export default function ChatScreen({}) {
|
|
|
345
366
|
const commandMessage = {
|
|
346
367
|
role: 'command',
|
|
347
368
|
content: '',
|
|
348
|
-
commandName: commandName
|
|
369
|
+
commandName: commandName,
|
|
349
370
|
};
|
|
350
371
|
setMessages(prev => [...prev, commandMessage]);
|
|
351
372
|
}
|
|
@@ -354,7 +375,7 @@ export default function ChatScreen({}) {
|
|
|
354
375
|
const commandMessage = {
|
|
355
376
|
role: 'command',
|
|
356
377
|
content: '',
|
|
357
|
-
commandName: commandName
|
|
378
|
+
commandName: commandName,
|
|
358
379
|
};
|
|
359
380
|
setMessages(prev => [...prev, commandMessage]);
|
|
360
381
|
}
|
|
@@ -366,16 +387,18 @@ export default function ChatScreen({}) {
|
|
|
366
387
|
const commandMessage = {
|
|
367
388
|
role: 'command',
|
|
368
389
|
content: '',
|
|
369
|
-
commandName: commandName
|
|
390
|
+
commandName: commandName,
|
|
370
391
|
};
|
|
371
392
|
setMessages(prev => [...prev, commandMessage]);
|
|
372
393
|
}
|
|
373
|
-
else if (result.success &&
|
|
394
|
+
else if (result.success &&
|
|
395
|
+
result.action === 'initProject' &&
|
|
396
|
+
result.prompt) {
|
|
374
397
|
// Add command execution feedback
|
|
375
398
|
const commandMessage = {
|
|
376
399
|
role: 'command',
|
|
377
400
|
content: '',
|
|
378
|
-
commandName: commandName
|
|
401
|
+
commandName: commandName,
|
|
379
402
|
};
|
|
380
403
|
setMessages(prev => [...prev, commandMessage]);
|
|
381
404
|
// Auto-send the prompt using basicModel, hide the prompt from UI
|
|
@@ -456,8 +479,16 @@ export default function ChatScreen({}) {
|
|
|
456
479
|
const regularFiles = validFiles.filter(f => !f.isImage);
|
|
457
480
|
// Convert image files to image content format
|
|
458
481
|
const imageContents = [
|
|
459
|
-
...(images || []).map(img => ({
|
|
460
|
-
|
|
482
|
+
...(images || []).map(img => ({
|
|
483
|
+
type: 'image',
|
|
484
|
+
data: img.data,
|
|
485
|
+
mimeType: img.mimeType,
|
|
486
|
+
})),
|
|
487
|
+
...imageFiles.map(f => ({
|
|
488
|
+
type: 'image',
|
|
489
|
+
data: f.imageData,
|
|
490
|
+
mimeType: f.mimeType,
|
|
491
|
+
})),
|
|
461
492
|
];
|
|
462
493
|
// Get system information
|
|
463
494
|
const systemInfo = getSystemInfo();
|
|
@@ -468,7 +499,7 @@ export default function ChatScreen({}) {
|
|
|
468
499
|
content: cleanContent,
|
|
469
500
|
files: validFiles.length > 0 ? validFiles : undefined,
|
|
470
501
|
images: imageContents.length > 0 ? imageContents : undefined,
|
|
471
|
-
systemInfo
|
|
502
|
+
systemInfo,
|
|
472
503
|
};
|
|
473
504
|
setMessages(prev => [...prev, userMessage]);
|
|
474
505
|
}
|
|
@@ -497,7 +528,7 @@ export default function ChatScreen({}) {
|
|
|
497
528
|
useBasicModel,
|
|
498
529
|
getPendingMessages: () => pendingMessagesRef.current,
|
|
499
530
|
clearPendingMessages: () => setPendingMessages([]),
|
|
500
|
-
setIsStreaming
|
|
531
|
+
setIsStreaming,
|
|
501
532
|
});
|
|
502
533
|
}
|
|
503
534
|
catch (error) {
|
|
@@ -508,7 +539,7 @@ export default function ChatScreen({}) {
|
|
|
508
539
|
const finalMessage = {
|
|
509
540
|
role: 'assistant',
|
|
510
541
|
content: `Error: ${errorMessage}`,
|
|
511
|
-
streaming: false
|
|
542
|
+
streaming: false,
|
|
512
543
|
};
|
|
513
544
|
setMessages(prev => [...prev, finalMessage]);
|
|
514
545
|
}
|
|
@@ -538,7 +569,7 @@ export default function ChatScreen({}) {
|
|
|
538
569
|
// Save user message
|
|
539
570
|
saveMessage({
|
|
540
571
|
role: 'user',
|
|
541
|
-
content: combinedMessage
|
|
572
|
+
content: combinedMessage,
|
|
542
573
|
}).catch(error => {
|
|
543
574
|
console.error('Failed to save user message:', error);
|
|
544
575
|
});
|
|
@@ -560,7 +591,7 @@ export default function ChatScreen({}) {
|
|
|
560
591
|
setContextUsage,
|
|
561
592
|
getPendingMessages: () => pendingMessagesRef.current,
|
|
562
593
|
clearPendingMessages: () => setPendingMessages([]),
|
|
563
|
-
setIsStreaming
|
|
594
|
+
setIsStreaming,
|
|
564
595
|
});
|
|
565
596
|
}
|
|
566
597
|
catch (error) {
|
|
@@ -571,7 +602,7 @@ export default function ChatScreen({}) {
|
|
|
571
602
|
const finalMessage = {
|
|
572
603
|
role: 'assistant',
|
|
573
604
|
content: `Error: ${errorMessage}`,
|
|
574
|
-
streaming: false
|
|
605
|
+
streaming: false,
|
|
575
606
|
};
|
|
576
607
|
setMessages(prev => [...prev, finalMessage]);
|
|
577
608
|
}
|
|
@@ -589,12 +620,13 @@ export default function ChatScreen({}) {
|
|
|
589
620
|
if (terminalHeight < MIN_TERMINAL_HEIGHT) {
|
|
590
621
|
return (React.createElement(Box, { flexDirection: "column", padding: 2 },
|
|
591
622
|
React.createElement(Box, { borderStyle: "round", borderColor: "red", padding: 1 },
|
|
592
|
-
React.createElement(Text, { color: "red", bold: true }, "\u26A0
|
|
623
|
+
React.createElement(Text, { color: "red", bold: true }, "\u26A0 Terminal Too Small")),
|
|
593
624
|
React.createElement(Box, { marginTop: 1 },
|
|
594
625
|
React.createElement(Text, { color: "yellow" },
|
|
595
626
|
"Your terminal height is ",
|
|
596
627
|
terminalHeight,
|
|
597
|
-
" lines, but at least
|
|
628
|
+
" lines, but at least",
|
|
629
|
+
' ',
|
|
598
630
|
MIN_TERMINAL_HEIGHT,
|
|
599
631
|
" lines are required.")),
|
|
600
632
|
React.createElement(Box, { marginTop: 1 },
|
|
@@ -613,7 +645,9 @@ export default function ChatScreen({}) {
|
|
|
613
645
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
614
646
|
"\u2022 Working directory: ",
|
|
615
647
|
workingDirectory))),
|
|
616
|
-
...messages
|
|
648
|
+
...messages
|
|
649
|
+
.filter(m => !m.streaming && !m.toolPending)
|
|
650
|
+
.map((message, index) => {
|
|
617
651
|
// Determine tool message type and color
|
|
618
652
|
let toolStatusColor = 'cyan';
|
|
619
653
|
let isToolMessage = false;
|
|
@@ -636,42 +670,89 @@ export default function ChatScreen({}) {
|
|
|
636
670
|
}
|
|
637
671
|
return (React.createElement(Box, { key: `msg-${index}`, marginBottom: isToolMessage ? 0 : 1, marginX: 1, flexDirection: "column" },
|
|
638
672
|
React.createElement(Box, null,
|
|
639
|
-
React.createElement(Text, { color: message.role === 'user'
|
|
640
|
-
|
|
673
|
+
React.createElement(Text, { color: message.role === 'user'
|
|
674
|
+
? 'green'
|
|
675
|
+
: message.role === 'command'
|
|
676
|
+
? 'gray'
|
|
677
|
+
: toolStatusColor, bold: true }, message.role === 'user'
|
|
678
|
+
? '⛇'
|
|
679
|
+
: message.role === 'command'
|
|
680
|
+
? '⌘'
|
|
681
|
+
: '❆'),
|
|
641
682
|
React.createElement(Box, { marginLeft: 1, marginBottom: 1, flexDirection: "column" }, message.role === 'command' ? (React.createElement(Text, { color: "gray", dimColor: true },
|
|
642
683
|
"\u2514\u2500 ",
|
|
643
684
|
message.commandName)) : message.showTodoTree ? (React.createElement(TodoTree, { todos: currentTodos })) : (React.createElement(React.Fragment, null,
|
|
644
|
-
React.createElement(MarkdownRenderer, { content: message.content || ' ', color: message.role === 'user'
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
685
|
+
React.createElement(MarkdownRenderer, { content: message.content || ' ', color: message.role === 'user'
|
|
686
|
+
? 'gray'
|
|
687
|
+
: isToolMessage
|
|
688
|
+
? message.content.startsWith('⚡')
|
|
689
|
+
? 'yellow'
|
|
690
|
+
: message.content.startsWith('✓')
|
|
691
|
+
? 'green'
|
|
692
|
+
: 'red'
|
|
693
|
+
: undefined }),
|
|
694
|
+
message.toolDisplay &&
|
|
695
|
+
message.toolDisplay.args.length > 0 && (React.createElement(Box, { flexDirection: "column" }, message.toolDisplay.args.map((arg, argIndex) => (React.createElement(Text, { key: argIndex, color: "gray", dimColor: true },
|
|
648
696
|
arg.isLast ? '└─' : '├─',
|
|
649
697
|
" ",
|
|
650
698
|
arg.key,
|
|
651
|
-
":
|
|
699
|
+
":",
|
|
700
|
+
' ',
|
|
652
701
|
arg.value))))),
|
|
653
|
-
message.toolCall &&
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
702
|
+
message.toolCall &&
|
|
703
|
+
(message.toolCall.name === 'filesystem-create' ||
|
|
704
|
+
message.toolCall.name === 'filesystem-write') &&
|
|
705
|
+
message.toolCall.arguments.content && (React.createElement(Box, { marginTop: 1 },
|
|
706
|
+
React.createElement(DiffViewer, { newContent: message.toolCall.arguments.content, filename: message.toolCall.arguments.path }))),
|
|
707
|
+
message.toolCall &&
|
|
708
|
+
message.toolCall.name === 'filesystem-edit' &&
|
|
709
|
+
message.toolCall.arguments.oldContent &&
|
|
710
|
+
message.toolCall.arguments.newContent && (React.createElement(Box, { marginTop: 1 },
|
|
711
|
+
React.createElement(DiffViewer, { oldContent: message.toolCall.arguments.oldContent, newContent: message.toolCall.arguments.newContent, filename: message.toolCall.arguments.filename }))),
|
|
712
|
+
message.toolCall &&
|
|
713
|
+
message.toolCall.name === 'terminal-execute' &&
|
|
714
|
+
message.toolCall.arguments.command && (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
658
715
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
659
|
-
"\u2514\u2500 Command:
|
|
716
|
+
"\u2514\u2500 Command:",
|
|
717
|
+
' ',
|
|
660
718
|
React.createElement(Text, { color: "white" }, message.toolCall.arguments.command)),
|
|
661
719
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
662
|
-
"\u2514\u2500 Exit Code:
|
|
663
|
-
|
|
664
|
-
|
|
720
|
+
"\u2514\u2500 Exit Code:",
|
|
721
|
+
' ',
|
|
722
|
+
React.createElement(Text, { color: message.toolCall.arguments.exitCode === 0
|
|
723
|
+
? 'green'
|
|
724
|
+
: 'red' }, message.toolCall.arguments.exitCode)),
|
|
725
|
+
message.toolCall.arguments.stdout &&
|
|
726
|
+
message.toolCall.arguments.stdout.trim()
|
|
727
|
+
.length > 0 && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
665
728
|
React.createElement(Text, { color: "green", dimColor: true }, "\u2514\u2500 stdout:"),
|
|
666
729
|
React.createElement(Box, { paddingLeft: 2 },
|
|
667
|
-
React.createElement(Text, { color: "white" }, message.toolCall.arguments.stdout
|
|
668
|
-
|
|
669
|
-
|
|
730
|
+
React.createElement(Text, { color: "white" }, message.toolCall.arguments.stdout
|
|
731
|
+
.trim()
|
|
732
|
+
.split('\n')
|
|
733
|
+
.slice(0, 20)
|
|
734
|
+
.join('\n')),
|
|
735
|
+
message.toolCall.arguments.stdout
|
|
736
|
+
.trim()
|
|
737
|
+
.split('\n').length > 20 && (React.createElement(Text, { color: "gray", dimColor: true }, "... (output truncated)"))))),
|
|
738
|
+
message.toolCall.arguments.stderr &&
|
|
739
|
+
message.toolCall.arguments.stderr.trim()
|
|
740
|
+
.length > 0 && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
670
741
|
React.createElement(Text, { color: "red", dimColor: true }, "\u2514\u2500 stderr:"),
|
|
671
742
|
React.createElement(Box, { paddingLeft: 2 },
|
|
672
|
-
React.createElement(Text, { color: "red" }, message.toolCall.arguments.stderr
|
|
673
|
-
|
|
674
|
-
|
|
743
|
+
React.createElement(Text, { color: "red" }, message.toolCall.arguments.stderr
|
|
744
|
+
.trim()
|
|
745
|
+
.split('\n')
|
|
746
|
+
.slice(0, 10)
|
|
747
|
+
.join('\n')),
|
|
748
|
+
message.toolCall.arguments.stderr
|
|
749
|
+
.trim()
|
|
750
|
+
.split('\n').length > 10 && (React.createElement(Text, { color: "gray", dimColor: true }, "... (output truncated)"))))))),
|
|
751
|
+
message.content.startsWith('✓') &&
|
|
752
|
+
message.toolResult &&
|
|
753
|
+
!message.toolCall && (React.createElement(ToolResultPreview, { toolName: message.content
|
|
754
|
+
.replace('✓ ', '')
|
|
755
|
+
.split('\n')[0] || '', result: message.toolResult, maxLines: 5 })),
|
|
675
756
|
message.role === 'user' && message.systemInfo && (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
676
757
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
677
758
|
"\u2514\u2500 Platform: ",
|
|
@@ -680,20 +761,27 @@ export default function ChatScreen({}) {
|
|
|
680
761
|
"\u2514\u2500 Shell: ",
|
|
681
762
|
message.systemInfo.shell),
|
|
682
763
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
683
|
-
"\u2514\u2500 Working Directory:
|
|
764
|
+
"\u2514\u2500 Working Directory:",
|
|
765
|
+
' ',
|
|
684
766
|
message.systemInfo.workingDirectory))),
|
|
685
767
|
message.files && message.files.length > 0 && (React.createElement(Box, { flexDirection: "column" }, message.files.map((file, fileIndex) => (React.createElement(Text, { key: fileIndex, color: "gray", dimColor: true },
|
|
686
768
|
"\u2514\u2500 ",
|
|
687
769
|
file.path,
|
|
688
|
-
file.exists
|
|
689
|
-
|
|
770
|
+
file.exists
|
|
771
|
+
? ` (total line ${file.lineCount})`
|
|
772
|
+
: ' (file not found)'))))),
|
|
773
|
+
message.role === 'user' &&
|
|
774
|
+
message.images &&
|
|
775
|
+
message.images.length > 0 && (React.createElement(Box, { marginTop: 1, flexDirection: "column" }, message.images.map((_image, imageIndex) => (React.createElement(Text, { key: imageIndex, color: "gray", dimColor: true },
|
|
690
776
|
"\u2514\u2500 [image #",
|
|
691
777
|
imageIndex + 1,
|
|
692
778
|
"]"))))),
|
|
693
779
|
message.discontinued && (React.createElement(Text, { color: "red", bold: true }, "\u2514\u2500 user discontinue"))))))));
|
|
694
|
-
})
|
|
695
|
-
] },
|
|
696
|
-
messages
|
|
780
|
+
}),
|
|
781
|
+
] }, item => item),
|
|
782
|
+
messages
|
|
783
|
+
.filter(m => m.toolPending)
|
|
784
|
+
.map((message, index) => (React.createElement(Box, { key: `pending-tool-${index}`, marginBottom: 1, marginX: 1 },
|
|
697
785
|
React.createElement(Text, { color: "yellowBright", bold: true }, "\u2746"),
|
|
698
786
|
React.createElement(Box, { marginLeft: 1, marginBottom: 1, flexDirection: "row" },
|
|
699
787
|
React.createElement(MarkdownRenderer, { content: message.content || ' ', color: "yellow" }),
|
|
@@ -709,15 +797,20 @@ export default function ChatScreen({}) {
|
|
|
709
797
|
streamTokenCount > 0 && (React.createElement(React.Fragment, null,
|
|
710
798
|
' · ',
|
|
711
799
|
React.createElement(Text, { color: "cyan" },
|
|
712
|
-
"\u2193
|
|
800
|
+
"\u2193",
|
|
801
|
+
' ',
|
|
713
802
|
streamTokenCount >= 1000
|
|
714
803
|
? `${(streamTokenCount / 1000).toFixed(1)}k`
|
|
715
804
|
: streamTokenCount,
|
|
716
|
-
|
|
805
|
+
' ',
|
|
806
|
+
"tokens"))),
|
|
717
807
|
")")) : ('Create the first dialogue record file...'))))),
|
|
718
808
|
React.createElement(Box, { marginX: 1 },
|
|
719
809
|
React.createElement(PendingMessages, { pendingMessages: pendingMessages })),
|
|
720
|
-
pendingToolConfirmation && (React.createElement(ToolConfirmation, { toolName: pendingToolConfirmation.batchToolNames ||
|
|
810
|
+
pendingToolConfirmation && (React.createElement(ToolConfirmation, { toolName: pendingToolConfirmation.batchToolNames ||
|
|
811
|
+
pendingToolConfirmation.tool.function.name, toolArguments: !pendingToolConfirmation.allTools
|
|
812
|
+
? pendingToolConfirmation.tool.function.arguments
|
|
813
|
+
: undefined, allTools: pendingToolConfirmation.allTools, onConfirm: pendingToolConfirmation.resolve })),
|
|
721
814
|
showSessionPanel && (React.createElement(Box, { marginX: 1 },
|
|
722
815
|
React.createElement(SessionListPanel, { onSelectSession: handleSessionPanelSelect, onClose: () => setShowSessionPanel(false) }))),
|
|
723
816
|
showMcpPanel && (React.createElement(Box, { marginX: 1, flexDirection: "column" },
|
|
@@ -725,24 +818,43 @@ export default function ChatScreen({}) {
|
|
|
725
818
|
React.createElement(Box, { marginTop: 1 },
|
|
726
819
|
React.createElement(Text, { color: "gray", dimColor: true }, "Press ESC to close")))),
|
|
727
820
|
pendingRollback && (React.createElement(FileRollbackConfirmation, { fileCount: pendingRollback.fileCount, onConfirm: handleRollbackConfirm })),
|
|
728
|
-
!pendingToolConfirmation &&
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
821
|
+
!pendingToolConfirmation &&
|
|
822
|
+
!isCompressing &&
|
|
823
|
+
!showSessionPanel &&
|
|
824
|
+
!showMcpPanel &&
|
|
825
|
+
!pendingRollback && (React.createElement(React.Fragment, null,
|
|
826
|
+
React.createElement(ChatInput, { onSubmit: handleMessageSubmit, onCommand: handleCommandExecution, placeholder: "Ask me anything about coding...", disabled: !!pendingToolConfirmation, chatHistory: messages, onHistorySelect: handleHistorySelect, yoloMode: yoloMode, contextUsage: contextUsage
|
|
827
|
+
? {
|
|
828
|
+
inputTokens: contextUsage.prompt_tokens,
|
|
829
|
+
maxContextTokens: getOpenAiConfig().maxContextTokens || 4000,
|
|
830
|
+
cacheCreationTokens: contextUsage.cache_creation_input_tokens,
|
|
831
|
+
cacheReadTokens: contextUsage.cache_read_input_tokens,
|
|
832
|
+
cachedTokens: contextUsage.cached_tokens,
|
|
833
|
+
}
|
|
834
|
+
: undefined, snapshotFileCount: snapshotFileCount }),
|
|
736
835
|
vscodeConnectionStatus !== 'disconnected' && (React.createElement(Box, { marginTop: 1 },
|
|
737
|
-
React.createElement(Text, { color: vscodeConnectionStatus === 'connecting'
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
836
|
+
React.createElement(Text, { color: vscodeConnectionStatus === 'connecting'
|
|
837
|
+
? 'yellow'
|
|
838
|
+
: vscodeConnectionStatus === 'connected'
|
|
839
|
+
? 'green'
|
|
840
|
+
: vscodeConnectionStatus === 'error'
|
|
841
|
+
? 'red'
|
|
842
|
+
: 'gray', dimColor: vscodeConnectionStatus !== 'error' },
|
|
843
|
+
"\u25CF",
|
|
844
|
+
' ',
|
|
845
|
+
vscodeConnectionStatus === 'connecting'
|
|
846
|
+
? 'Waiting for VSCode extension to connect...'
|
|
847
|
+
: vscodeConnectionStatus === 'connected'
|
|
848
|
+
? 'VSCode Connected'
|
|
849
|
+
: vscodeConnectionStatus === 'error'
|
|
850
|
+
? 'Connection Failed - Make sure Snow CLI extension is installed and active in VSCode'
|
|
851
|
+
: 'VSCode',
|
|
852
|
+
vscodeConnectionStatus === 'connected' &&
|
|
853
|
+
editorContext.activeFile &&
|
|
854
|
+
` | ${editorContext.activeFile}`,
|
|
855
|
+
vscodeConnectionStatus === 'connected' &&
|
|
856
|
+
editorContext.selectedText &&
|
|
857
|
+
` | ${editorContext.selectedText.length} chars selected`))))),
|
|
746
858
|
isCompressing && (React.createElement(Box, { marginTop: 1 },
|
|
747
859
|
React.createElement(Text, { color: "cyan" },
|
|
748
860
|
React.createElement(Spinner, { type: "dots" }),
|