@projectservan8n/cnapse 0.4.0 → 0.5.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/ConfigUI-V5TM6KKS.js +306 -0
- package/dist/index.js +1031 -377
- package/package.json +1 -1
- package/src/components/App.tsx +172 -291
- package/src/components/ConfigUI.tsx +353 -0
- package/src/components/HelpMenu.tsx +2 -1
- package/src/components/ProviderSelector.tsx +378 -0
- package/src/hooks/index.ts +15 -0
- package/src/hooks/useChat.ts +149 -0
- package/src/hooks/useTasks.ts +63 -0
- package/src/hooks/useTelegram.ts +91 -0
- package/src/hooks/useVision.ts +47 -0
- package/src/index.tsx +95 -83
- package/src/lib/ollama.ts +140 -0
- package/src/lib/tasks.ts +249 -34
package/package.json
CHANGED
package/src/components/App.tsx
CHANGED
|
@@ -1,372 +1,253 @@
|
|
|
1
|
-
import React, { useState,
|
|
1
|
+
import React, { useState, useCallback } from 'react';
|
|
2
2
|
import { Box, Text, useApp, useInput } from 'ink';
|
|
3
3
|
import { Header } from './Header.js';
|
|
4
4
|
import { ChatMessage } from './ChatMessage.js';
|
|
5
5
|
import { ChatInput } from './ChatInput.js';
|
|
6
6
|
import { StatusBar } from './StatusBar.js';
|
|
7
7
|
import { HelpMenu } from './HelpMenu.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
interface ChatMsg {
|
|
15
|
-
id: string;
|
|
16
|
-
role: 'user' | 'assistant' | 'system';
|
|
17
|
-
content: string;
|
|
18
|
-
timestamp: Date;
|
|
19
|
-
isStreaming?: boolean;
|
|
20
|
-
}
|
|
8
|
+
import { ProviderSelector } from './ProviderSelector.js';
|
|
9
|
+
import { getConfig } from '../lib/config.js';
|
|
10
|
+
import { useChat, useVision, useTelegram, useTasks } from '../hooks/index.js';
|
|
11
|
+
|
|
12
|
+
type OverlayType = 'none' | 'help' | 'provider';
|
|
21
13
|
|
|
22
14
|
export function App() {
|
|
23
15
|
const { exit } = useApp();
|
|
24
|
-
const [messages, setMessages] = useState<ChatMsg[]>([
|
|
25
|
-
{
|
|
26
|
-
id: '0',
|
|
27
|
-
role: 'system',
|
|
28
|
-
content: 'Welcome to C-napse! Type your message and press Enter.\n\nShortcuts:\n Ctrl+C - Exit\n Ctrl+W - Toggle screen watch\n /clear - Clear chat\n /help - Show help',
|
|
29
|
-
timestamp: new Date(),
|
|
30
|
-
},
|
|
31
|
-
]);
|
|
32
|
-
const [input, setInput] = useState('');
|
|
33
|
-
const [isProcessing, setIsProcessing] = useState(false);
|
|
34
|
-
const [status, setStatus] = useState('Ready');
|
|
35
|
-
const [error, setError] = useState<string | null>(null);
|
|
36
|
-
const [screenWatch, setScreenWatch] = useState(false);
|
|
37
|
-
const [showHelpMenu, setShowHelpMenu] = useState(false);
|
|
38
|
-
const [telegramEnabled, setTelegramEnabled] = useState(false);
|
|
39
|
-
const screenContextRef = useRef<string | null>(null);
|
|
40
|
-
|
|
41
|
-
// Screen watching effect
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
if (!screenWatch) {
|
|
44
|
-
screenContextRef.current = null;
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const checkScreen = async () => {
|
|
49
|
-
const desc = await getScreenDescription();
|
|
50
|
-
if (desc) {
|
|
51
|
-
screenContextRef.current = desc;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
16
|
|
|
55
|
-
|
|
56
|
-
|
|
17
|
+
// UI State
|
|
18
|
+
const [overlay, setOverlay] = useState<OverlayType>('none');
|
|
19
|
+
const [screenWatch, setScreenWatch] = useState(false);
|
|
20
|
+
const [status, setStatus] = useState('Ready');
|
|
57
21
|
|
|
58
|
-
|
|
59
|
-
|
|
22
|
+
// Feature hooks
|
|
23
|
+
const chat = useChat(screenWatch);
|
|
24
|
+
const vision = useVision();
|
|
25
|
+
const telegram = useTelegram((msg) => {
|
|
26
|
+
chat.addSystemMessage(`📱 Telegram [${msg.from}]: ${msg.text}`);
|
|
27
|
+
});
|
|
28
|
+
const tasks = useTasks((task, step) => {
|
|
29
|
+
if (step.status === 'running') {
|
|
30
|
+
setStatus(`Running: ${step.description}`);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
60
33
|
|
|
34
|
+
// Keyboard shortcuts
|
|
61
35
|
useInput((inputChar, key) => {
|
|
62
|
-
|
|
63
|
-
if (showHelpMenu) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
36
|
+
if (overlay !== 'none') return;
|
|
66
37
|
|
|
67
|
-
if (key.ctrl && inputChar === 'c')
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (key.ctrl && inputChar === '
|
|
71
|
-
setMessages([messages[0]!]); // Keep welcome message
|
|
72
|
-
setError(null);
|
|
73
|
-
}
|
|
38
|
+
if (key.ctrl && inputChar === 'c') exit();
|
|
39
|
+
if (key.ctrl && inputChar === 'l') chat.clearMessages();
|
|
40
|
+
if (key.ctrl && inputChar === 'h') setOverlay('help');
|
|
41
|
+
if (key.ctrl && inputChar === 'p') setOverlay('provider');
|
|
74
42
|
if (key.ctrl && inputChar === 'w') {
|
|
75
|
-
setScreenWatch(
|
|
43
|
+
setScreenWatch(prev => {
|
|
76
44
|
const newState = !prev;
|
|
77
|
-
addSystemMessage(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
: '🖥️ Screen watching disabled.'
|
|
45
|
+
chat.addSystemMessage(newState
|
|
46
|
+
? '🖥️ Screen watching enabled.'
|
|
47
|
+
: '🖥️ Screen watching disabled.'
|
|
81
48
|
);
|
|
82
49
|
return newState;
|
|
83
50
|
});
|
|
84
51
|
}
|
|
85
|
-
if (key.ctrl && inputChar === 'h') {
|
|
86
|
-
setShowHelpMenu(true);
|
|
87
|
-
}
|
|
88
52
|
if (key.ctrl && inputChar === 't') {
|
|
89
|
-
|
|
90
|
-
const newState = !prev;
|
|
91
|
-
addSystemMessage(
|
|
92
|
-
newState
|
|
93
|
-
? '📱 Telegram bot enabled.'
|
|
94
|
-
: '📱 Telegram bot disabled.'
|
|
95
|
-
);
|
|
96
|
-
return newState;
|
|
97
|
-
});
|
|
53
|
+
handleTelegramToggle();
|
|
98
54
|
}
|
|
99
55
|
});
|
|
100
56
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const userInput = value.trim();
|
|
105
|
-
setInput('');
|
|
106
|
-
setError(null);
|
|
107
|
-
|
|
108
|
-
// Handle commands
|
|
109
|
-
if (userInput.startsWith('/')) {
|
|
110
|
-
handleCommand(userInput);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Add user message
|
|
115
|
-
const userMsg: ChatMsg = {
|
|
116
|
-
id: Date.now().toString(),
|
|
117
|
-
role: 'user',
|
|
118
|
-
content: userInput,
|
|
119
|
-
timestamp: new Date(),
|
|
120
|
-
};
|
|
121
|
-
setMessages((prev) => [...prev, userMsg]);
|
|
122
|
-
|
|
123
|
-
// Add placeholder for assistant
|
|
124
|
-
const assistantId = (Date.now() + 1).toString();
|
|
125
|
-
setMessages((prev) => [
|
|
126
|
-
...prev,
|
|
127
|
-
{
|
|
128
|
-
id: assistantId,
|
|
129
|
-
role: 'assistant',
|
|
130
|
-
content: '',
|
|
131
|
-
timestamp: new Date(),
|
|
132
|
-
isStreaming: true,
|
|
133
|
-
},
|
|
134
|
-
]);
|
|
135
|
-
|
|
136
|
-
setIsProcessing(true);
|
|
137
|
-
setStatus('Thinking...');
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
// Build message history for API
|
|
141
|
-
const apiMessages: Message[] = messages
|
|
142
|
-
.filter((m) => m.role === 'user' || m.role === 'assistant')
|
|
143
|
-
.slice(-10)
|
|
144
|
-
.map((m) => ({ role: m.role as 'user' | 'assistant', content: m.content }));
|
|
145
|
-
|
|
146
|
-
// Add screen context if watching
|
|
147
|
-
let finalInput = userInput;
|
|
148
|
-
if (screenWatch && screenContextRef.current) {
|
|
149
|
-
finalInput = `[Screen context: ${screenContextRef.current}]\n\n${userInput}`;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
apiMessages.push({ role: 'user', content: finalInput });
|
|
153
|
-
|
|
154
|
-
const response = await chat(apiMessages);
|
|
155
|
-
|
|
156
|
-
// Update assistant message with response
|
|
157
|
-
setMessages((prev) =>
|
|
158
|
-
prev.map((m) =>
|
|
159
|
-
m.id === assistantId
|
|
160
|
-
? { ...m, content: response.content || '(no response)', isStreaming: false }
|
|
161
|
-
: m
|
|
162
|
-
)
|
|
163
|
-
);
|
|
164
|
-
} catch (err) {
|
|
165
|
-
const errorMsg = err instanceof Error ? err.message : 'Unknown error';
|
|
166
|
-
setError(errorMsg);
|
|
167
|
-
// Update assistant message with error
|
|
168
|
-
setMessages((prev) =>
|
|
169
|
-
prev.map((m) =>
|
|
170
|
-
m.id === assistantId
|
|
171
|
-
? { ...m, content: `Error: ${errorMsg}`, isStreaming: false }
|
|
172
|
-
: m
|
|
173
|
-
)
|
|
174
|
-
);
|
|
175
|
-
} finally {
|
|
176
|
-
setIsProcessing(false);
|
|
177
|
-
setStatus('Ready');
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const handleCommand = (cmd: string) => {
|
|
57
|
+
// Command handlers
|
|
58
|
+
const handleCommand = useCallback(async (cmd: string) => {
|
|
182
59
|
const parts = cmd.slice(1).split(' ');
|
|
183
60
|
const command = parts[0];
|
|
184
61
|
const args = parts.slice(1).join(' ');
|
|
185
62
|
|
|
186
63
|
switch (command) {
|
|
187
64
|
case 'clear':
|
|
188
|
-
|
|
189
|
-
addSystemMessage('Chat cleared.');
|
|
65
|
+
chat.clearMessages();
|
|
66
|
+
chat.addSystemMessage('Chat cleared.');
|
|
190
67
|
break;
|
|
68
|
+
|
|
191
69
|
case 'help':
|
|
192
|
-
|
|
70
|
+
setOverlay('help');
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
case 'provider':
|
|
74
|
+
case 'model':
|
|
75
|
+
setOverlay('provider');
|
|
193
76
|
break;
|
|
77
|
+
|
|
78
|
+
case 'config': {
|
|
79
|
+
const config = getConfig();
|
|
80
|
+
chat.addSystemMessage(
|
|
81
|
+
`⚙️ Configuration:\n` +
|
|
82
|
+
` Provider: ${config.provider}\n` +
|
|
83
|
+
` Model: ${config.model}\n` +
|
|
84
|
+
` Ollama: ${config.ollamaHost}\n\n` +
|
|
85
|
+
`Use /provider to change`
|
|
86
|
+
);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
case 'screen':
|
|
91
|
+
await handleScreenCommand();
|
|
92
|
+
break;
|
|
93
|
+
|
|
194
94
|
case 'watch':
|
|
195
|
-
setScreenWatch(
|
|
95
|
+
setScreenWatch(prev => {
|
|
196
96
|
const newState = !prev;
|
|
197
|
-
addSystemMessage(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
: '🖥️ Screen watching disabled.'
|
|
97
|
+
chat.addSystemMessage(newState
|
|
98
|
+
? '🖥️ Screen watching enabled.'
|
|
99
|
+
: '🖥️ Screen watching disabled.'
|
|
201
100
|
);
|
|
202
101
|
return newState;
|
|
203
102
|
});
|
|
204
103
|
break;
|
|
104
|
+
|
|
205
105
|
case 'telegram':
|
|
206
|
-
handleTelegramToggle();
|
|
207
|
-
break;
|
|
208
|
-
case 'screen':
|
|
209
|
-
handleScreenCommand();
|
|
106
|
+
await handleTelegramToggle();
|
|
210
107
|
break;
|
|
108
|
+
|
|
211
109
|
case 'task':
|
|
212
110
|
if (args) {
|
|
213
|
-
handleTaskCommand(args);
|
|
111
|
+
await handleTaskCommand(args);
|
|
214
112
|
} else {
|
|
215
|
-
addSystemMessage('Usage: /task <description>\nExample: /task open notepad and type hello');
|
|
113
|
+
chat.addSystemMessage('Usage: /task <description>\nExample: /task open notepad and type hello');
|
|
216
114
|
}
|
|
217
115
|
break;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
116
|
+
|
|
117
|
+
case 'memory':
|
|
118
|
+
if (args === 'clear') {
|
|
119
|
+
tasks.clearMemory();
|
|
120
|
+
chat.addSystemMessage('🧠 Task memory cleared.');
|
|
121
|
+
} else {
|
|
122
|
+
const stats = tasks.getMemoryStats();
|
|
123
|
+
chat.addSystemMessage(
|
|
124
|
+
`🧠 Task Memory:\n\n` +
|
|
125
|
+
` Learned patterns: ${stats.patternCount}\n` +
|
|
126
|
+
` Total successful uses: ${stats.totalUses}\n\n` +
|
|
127
|
+
(stats.topPatterns.length > 0
|
|
128
|
+
? ` Top patterns:\n${stats.topPatterns.map(p => ` • ${p}`).join('\n')}\n\n`
|
|
129
|
+
: ' No patterns learned yet.\n\n') +
|
|
130
|
+
`The more you use /task, the smarter it gets!\n` +
|
|
131
|
+
`Use /memory clear to reset.`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
226
134
|
break;
|
|
135
|
+
|
|
227
136
|
case 'quit':
|
|
228
137
|
case 'exit':
|
|
229
138
|
exit();
|
|
230
139
|
break;
|
|
140
|
+
|
|
231
141
|
default:
|
|
232
|
-
addSystemMessage(`Unknown command: ${command}\nType /help
|
|
142
|
+
chat.addSystemMessage(`Unknown command: ${command}\nType /help for commands`);
|
|
233
143
|
}
|
|
234
|
-
};
|
|
144
|
+
}, [chat, exit]);
|
|
235
145
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const handleScreenCommand = async () => {
|
|
242
|
-
addSystemMessage('📸 Taking screenshot and analyzing...');
|
|
243
|
-
setStatus('Analyzing screen...');
|
|
244
|
-
setIsProcessing(true);
|
|
146
|
+
// Screen command
|
|
147
|
+
const handleScreenCommand = useCallback(async () => {
|
|
148
|
+
chat.addSystemMessage('📸 Analyzing screen...');
|
|
149
|
+
setStatus('Analyzing...');
|
|
245
150
|
|
|
246
151
|
try {
|
|
247
|
-
const
|
|
248
|
-
addSystemMessage(`🖥️ Screen
|
|
152
|
+
const description = await vision.analyze();
|
|
153
|
+
chat.addSystemMessage(`🖥️ Screen:\n\n${description}`);
|
|
249
154
|
} catch (err) {
|
|
250
|
-
|
|
251
|
-
addSystemMessage(`❌ Screen capture failed: ${errorMsg}`);
|
|
155
|
+
chat.addSystemMessage(`❌ ${vision.error || 'Vision failed'}`);
|
|
252
156
|
} finally {
|
|
253
|
-
setIsProcessing(false);
|
|
254
157
|
setStatus('Ready');
|
|
255
158
|
}
|
|
256
|
-
};
|
|
159
|
+
}, [chat, vision]);
|
|
257
160
|
|
|
258
|
-
|
|
259
|
-
|
|
161
|
+
// Telegram toggle
|
|
162
|
+
const handleTelegramToggle = useCallback(async () => {
|
|
163
|
+
if (telegram.isEnabled) {
|
|
164
|
+
await telegram.stop();
|
|
165
|
+
chat.addSystemMessage('📱 Telegram stopped.');
|
|
166
|
+
} else {
|
|
167
|
+
chat.addSystemMessage('📱 Starting Telegram...');
|
|
168
|
+
setStatus('Starting Telegram...');
|
|
169
|
+
try {
|
|
170
|
+
await telegram.start();
|
|
171
|
+
chat.addSystemMessage(
|
|
172
|
+
'📱 Telegram started!\n' +
|
|
173
|
+
'Send /start to your bot to connect.\n' +
|
|
174
|
+
'Commands: /screen, /describe, /run, /status'
|
|
175
|
+
);
|
|
176
|
+
} catch {
|
|
177
|
+
chat.addSystemMessage(`❌ ${telegram.error || 'Telegram failed'}`);
|
|
178
|
+
} finally {
|
|
179
|
+
setStatus('Ready');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}, [chat, telegram]);
|
|
183
|
+
|
|
184
|
+
// Task command
|
|
185
|
+
const handleTaskCommand = useCallback(async (description: string) => {
|
|
186
|
+
chat.addSystemMessage(`📋 Parsing: ${description}`);
|
|
260
187
|
setStatus('Parsing task...');
|
|
261
|
-
setIsProcessing(true);
|
|
262
188
|
|
|
263
189
|
try {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
addSystemMessage(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
await executeTask(task, (updatedTask, currentStep) => {
|
|
273
|
-
// Update progress
|
|
274
|
-
if (currentStep.status === 'running') {
|
|
275
|
-
setStatus(`Running: ${currentStep.description}`);
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
// Show final result
|
|
280
|
-
addSystemMessage(`\n${formatTask(task)}`);
|
|
281
|
-
|
|
282
|
-
if (task.status === 'completed') {
|
|
283
|
-
addSystemMessage('✅ Task completed successfully!');
|
|
284
|
-
} else {
|
|
285
|
-
addSystemMessage('❌ Task failed. Check the steps above for errors.');
|
|
286
|
-
}
|
|
287
|
-
} catch (err) {
|
|
288
|
-
const errorMsg = err instanceof Error ? err.message : 'Task failed';
|
|
289
|
-
addSystemMessage(`❌ Task error: ${errorMsg}`);
|
|
190
|
+
const task = await tasks.run(description);
|
|
191
|
+
chat.addSystemMessage(`\n${tasks.format(task)}`);
|
|
192
|
+
chat.addSystemMessage(task.status === 'completed'
|
|
193
|
+
? '✅ Task completed!'
|
|
194
|
+
: '❌ Task failed.'
|
|
195
|
+
);
|
|
196
|
+
} catch {
|
|
197
|
+
chat.addSystemMessage(`❌ ${tasks.error || 'Task failed'}`);
|
|
290
198
|
} finally {
|
|
291
|
-
setIsProcessing(false);
|
|
292
199
|
setStatus('Ready');
|
|
293
200
|
}
|
|
294
|
-
};
|
|
201
|
+
}, [chat, tasks]);
|
|
295
202
|
|
|
296
|
-
|
|
297
|
-
|
|
203
|
+
// Submit handler
|
|
204
|
+
const handleSubmit = useCallback(async (value: string) => {
|
|
205
|
+
if (!value.trim()) return;
|
|
298
206
|
|
|
299
|
-
if (
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
await bot.stop();
|
|
303
|
-
setTelegramEnabled(false);
|
|
304
|
-
addSystemMessage('📱 Telegram bot stopped.');
|
|
305
|
-
} catch (err) {
|
|
306
|
-
const errorMsg = err instanceof Error ? err.message : 'Failed to stop bot';
|
|
307
|
-
addSystemMessage(`❌ Error stopping bot: ${errorMsg}`);
|
|
308
|
-
}
|
|
207
|
+
if (value.startsWith('/')) {
|
|
208
|
+
await handleCommand(value);
|
|
309
209
|
} else {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
setStatus('
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
// Setup event handlers
|
|
316
|
-
bot.on('message', (msg: TelegramMessage) => {
|
|
317
|
-
addSystemMessage(`📱 Telegram [${msg.from}]: ${msg.text}`);
|
|
318
|
-
});
|
|
210
|
+
setStatus('Thinking...');
|
|
211
|
+
await chat.sendMessage(value);
|
|
212
|
+
setStatus('Ready');
|
|
213
|
+
}
|
|
214
|
+
}, [chat, handleCommand]);
|
|
319
215
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
216
|
+
// Provider selection callback
|
|
217
|
+
const handleProviderSelect = useCallback((provider: string, model: string) => {
|
|
218
|
+
chat.addSystemMessage(`✅ Updated: ${provider} / ${model}`);
|
|
219
|
+
}, [chat]);
|
|
323
220
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
addSystemMessage(
|
|
327
|
-
'📱 Telegram bot started!\n\n' +
|
|
328
|
-
'Open Telegram and send /start to your bot to connect.\n' +
|
|
329
|
-
'Commands: /screen, /describe, /run <cmd>, /status'
|
|
330
|
-
);
|
|
331
|
-
} catch (err) {
|
|
332
|
-
const errorMsg = err instanceof Error ? err.message : 'Failed to start bot';
|
|
333
|
-
addSystemMessage(`❌ Telegram error: ${errorMsg}`);
|
|
334
|
-
} finally {
|
|
335
|
-
setStatus('Ready');
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
const addSystemMessage = (content: string) => {
|
|
341
|
-
setMessages((prev) => [
|
|
342
|
-
...prev,
|
|
343
|
-
{
|
|
344
|
-
id: Date.now().toString(),
|
|
345
|
-
role: 'system',
|
|
346
|
-
content,
|
|
347
|
-
timestamp: new Date(),
|
|
348
|
-
},
|
|
349
|
-
]);
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
// Only show last N messages that fit
|
|
353
|
-
const visibleMessages = messages.slice(-20);
|
|
354
|
-
|
|
355
|
-
// If help menu is open, show it as overlay
|
|
356
|
-
if (showHelpMenu) {
|
|
221
|
+
// Render overlays
|
|
222
|
+
if (overlay === 'help') {
|
|
357
223
|
return (
|
|
358
224
|
<Box flexDirection="column" height="100%" alignItems="center" justifyContent="center">
|
|
359
225
|
<HelpMenu
|
|
360
|
-
onClose={() =>
|
|
361
|
-
onSelect={
|
|
226
|
+
onClose={() => setOverlay('none')}
|
|
227
|
+
onSelect={(cmd) => { setOverlay('none'); handleCommand(cmd); }}
|
|
362
228
|
/>
|
|
363
229
|
</Box>
|
|
364
230
|
);
|
|
365
231
|
}
|
|
366
232
|
|
|
233
|
+
if (overlay === 'provider') {
|
|
234
|
+
return (
|
|
235
|
+
<Box flexDirection="column" height="100%" alignItems="center" justifyContent="center">
|
|
236
|
+
<ProviderSelector
|
|
237
|
+
onClose={() => setOverlay('none')}
|
|
238
|
+
onSelect={handleProviderSelect}
|
|
239
|
+
/>
|
|
240
|
+
</Box>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Main UI
|
|
245
|
+
const visibleMessages = chat.messages.slice(-20);
|
|
246
|
+
const isProcessing = chat.isProcessing || vision.isAnalyzing || tasks.isRunning || telegram.isStarting;
|
|
247
|
+
|
|
367
248
|
return (
|
|
368
249
|
<Box flexDirection="column" height="100%">
|
|
369
|
-
<Header screenWatch={screenWatch} telegramEnabled={
|
|
250
|
+
<Header screenWatch={screenWatch} telegramEnabled={telegram.isEnabled} />
|
|
370
251
|
|
|
371
252
|
<Box flexDirection="column" flexGrow={1} borderStyle="round" borderColor="gray" padding={1}>
|
|
372
253
|
<Text bold color="gray"> Chat </Text>
|
|
@@ -381,15 +262,15 @@ export function App() {
|
|
|
381
262
|
))}
|
|
382
263
|
</Box>
|
|
383
264
|
|
|
384
|
-
{error && (
|
|
265
|
+
{chat.error && (
|
|
385
266
|
<Box marginY={1}>
|
|
386
|
-
<Text color="red">Error: {error}</Text>
|
|
267
|
+
<Text color="red">Error: {chat.error}</Text>
|
|
387
268
|
</Box>
|
|
388
269
|
)}
|
|
389
270
|
|
|
390
271
|
<ChatInput
|
|
391
|
-
value=
|
|
392
|
-
onChange={
|
|
272
|
+
value=""
|
|
273
|
+
onChange={() => {}}
|
|
393
274
|
onSubmit={handleSubmit}
|
|
394
275
|
isProcessing={isProcessing}
|
|
395
276
|
/>
|