@projectservan8n/cnapse 0.4.0 → 0.5.0
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/index.js +706 -268
- package/package.json +1 -1
- package/src/components/App.tsx +172 -291
- package/src/components/HelpMenu.tsx +2 -1
- package/src/components/ProviderSelector.tsx +176 -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/lib/tasks.ts +249 -34
package/dist/index.js
CHANGED
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
import { render } from "ink";
|
|
12
12
|
|
|
13
13
|
// src/components/App.tsx
|
|
14
|
-
import { useState as
|
|
15
|
-
import { Box as
|
|
14
|
+
import { useState as useState7, useCallback as useCallback5 } from "react";
|
|
15
|
+
import { Box as Box7, Text as Text7, useApp, useInput as useInput3 } from "ink";
|
|
16
16
|
|
|
17
17
|
// src/components/Header.tsx
|
|
18
18
|
import { Box, Text } from "ink";
|
|
@@ -111,11 +111,12 @@ var MENU_ITEMS = [
|
|
|
111
111
|
{ command: "/screen", shortcut: "Ctrl+S", description: "Take screenshot and describe", category: "actions" },
|
|
112
112
|
{ command: "/task", description: "Run multi-step task", category: "actions" },
|
|
113
113
|
{ command: "/telegram", shortcut: "Ctrl+T", description: "Toggle Telegram bot", category: "actions" },
|
|
114
|
+
{ command: "/memory", description: "View/clear learned task patterns", category: "actions" },
|
|
114
115
|
// Settings
|
|
115
116
|
{ command: "/config", description: "Show/edit configuration", category: "settings" },
|
|
116
117
|
{ command: "/watch", shortcut: "Ctrl+W", description: "Toggle screen watching", category: "settings" },
|
|
117
118
|
{ command: "/model", description: "Change AI model", category: "settings" },
|
|
118
|
-
{ command: "/provider", description: "Change AI provider", category: "settings" }
|
|
119
|
+
{ command: "/provider", shortcut: "Ctrl+P", description: "Change AI provider", category: "settings" }
|
|
119
120
|
];
|
|
120
121
|
function HelpMenu({ onClose, onSelect }) {
|
|
121
122
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
@@ -190,6 +191,143 @@ function HelpMenu({ onClose, onSelect }) {
|
|
|
190
191
|
);
|
|
191
192
|
}
|
|
192
193
|
|
|
194
|
+
// src/components/ProviderSelector.tsx
|
|
195
|
+
import { useState as useState2 } from "react";
|
|
196
|
+
import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
|
|
197
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
198
|
+
var PROVIDERS = [
|
|
199
|
+
{
|
|
200
|
+
id: "ollama",
|
|
201
|
+
name: "Ollama",
|
|
202
|
+
description: "Local AI - Free, private, no API key",
|
|
203
|
+
defaultModel: "qwen2.5:0.5b",
|
|
204
|
+
models: ["qwen2.5:0.5b", "qwen2.5:1.5b", "qwen2.5:7b", "llama3.2:1b", "llama3.2:3b", "mistral:7b", "codellama:7b", "llava:7b"]
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: "openrouter",
|
|
208
|
+
name: "OpenRouter",
|
|
209
|
+
description: "Many models, pay-per-use",
|
|
210
|
+
defaultModel: "qwen/qwen-2.5-coder-32b-instruct",
|
|
211
|
+
models: [
|
|
212
|
+
"qwen/qwen-2.5-coder-32b-instruct",
|
|
213
|
+
"anthropic/claude-3.5-sonnet",
|
|
214
|
+
"openai/gpt-4o",
|
|
215
|
+
"openai/gpt-4o-mini",
|
|
216
|
+
"google/gemini-pro-1.5",
|
|
217
|
+
"meta-llama/llama-3.1-70b-instruct"
|
|
218
|
+
]
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: "anthropic",
|
|
222
|
+
name: "Anthropic",
|
|
223
|
+
description: "Claude models - Best for coding",
|
|
224
|
+
defaultModel: "claude-3-5-sonnet-20241022",
|
|
225
|
+
models: ["claude-3-5-sonnet-20241022", "claude-3-opus-20240229", "claude-3-haiku-20240307"]
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: "openai",
|
|
229
|
+
name: "OpenAI",
|
|
230
|
+
description: "GPT models",
|
|
231
|
+
defaultModel: "gpt-4o",
|
|
232
|
+
models: ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-3.5-turbo"]
|
|
233
|
+
}
|
|
234
|
+
];
|
|
235
|
+
function ProviderSelector({ onClose, onSelect }) {
|
|
236
|
+
const config = getConfig();
|
|
237
|
+
const [mode, setMode] = useState2("provider");
|
|
238
|
+
const [providerIndex, setProviderIndex] = useState2(() => {
|
|
239
|
+
const idx = PROVIDERS.findIndex((p) => p.id === config.provider);
|
|
240
|
+
return idx >= 0 ? idx : 0;
|
|
241
|
+
});
|
|
242
|
+
const [modelIndex, setModelIndex] = useState2(0);
|
|
243
|
+
const [selectedProvider, setSelectedProvider] = useState2(null);
|
|
244
|
+
useInput2((input, key) => {
|
|
245
|
+
if (key.escape) {
|
|
246
|
+
onClose();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (mode === "provider") {
|
|
250
|
+
if (key.upArrow) {
|
|
251
|
+
setProviderIndex((prev) => prev > 0 ? prev - 1 : PROVIDERS.length - 1);
|
|
252
|
+
} else if (key.downArrow) {
|
|
253
|
+
setProviderIndex((prev) => prev < PROVIDERS.length - 1 ? prev + 1 : 0);
|
|
254
|
+
} else if (key.return) {
|
|
255
|
+
const provider = PROVIDERS[providerIndex];
|
|
256
|
+
setSelectedProvider(provider);
|
|
257
|
+
const currentModelIdx = provider.models.findIndex((m) => m === config.model);
|
|
258
|
+
setModelIndex(currentModelIdx >= 0 ? currentModelIdx : 0);
|
|
259
|
+
setMode("model");
|
|
260
|
+
}
|
|
261
|
+
} else if (mode === "model" && selectedProvider) {
|
|
262
|
+
if (key.upArrow) {
|
|
263
|
+
setModelIndex((prev) => prev > 0 ? prev - 1 : selectedProvider.models.length - 1);
|
|
264
|
+
} else if (key.downArrow) {
|
|
265
|
+
setModelIndex((prev) => prev < selectedProvider.models.length - 1 ? prev + 1 : 0);
|
|
266
|
+
} else if (key.return) {
|
|
267
|
+
const model = selectedProvider.models[modelIndex];
|
|
268
|
+
setProvider(selectedProvider.id);
|
|
269
|
+
setModel(model);
|
|
270
|
+
onSelect(selectedProvider.id, model);
|
|
271
|
+
onClose();
|
|
272
|
+
} else if (key.leftArrow || input === "b") {
|
|
273
|
+
setMode("provider");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
if (mode === "provider") {
|
|
278
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, children: [
|
|
279
|
+
/* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "Select Provider" }) }),
|
|
280
|
+
/* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", children: "Use arrows to navigate, Enter to select, Esc to cancel" }) }),
|
|
281
|
+
PROVIDERS.map((provider, index) => {
|
|
282
|
+
const isSelected = index === providerIndex;
|
|
283
|
+
const isCurrent = provider.id === config.provider;
|
|
284
|
+
return /* @__PURE__ */ jsxs5(Box6, { marginY: 0, children: [
|
|
285
|
+
/* @__PURE__ */ jsxs5(Text6, { color: isSelected ? "cyan" : "white", children: [
|
|
286
|
+
isSelected ? "\u276F " : " ",
|
|
287
|
+
provider.name,
|
|
288
|
+
isCurrent && /* @__PURE__ */ jsx6(Text6, { color: "green", children: " (current)" })
|
|
289
|
+
] }),
|
|
290
|
+
isSelected && /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
|
|
291
|
+
" - ",
|
|
292
|
+
provider.description
|
|
293
|
+
] })
|
|
294
|
+
] }, provider.id);
|
|
295
|
+
}),
|
|
296
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
|
|
297
|
+
"Current: ",
|
|
298
|
+
config.provider,
|
|
299
|
+
" / ",
|
|
300
|
+
config.model
|
|
301
|
+
] }) })
|
|
302
|
+
] });
|
|
303
|
+
}
|
|
304
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, children: [
|
|
305
|
+
/* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsxs5(Text6, { bold: true, color: "cyan", children: [
|
|
306
|
+
"Select Model for ",
|
|
307
|
+
selectedProvider?.name
|
|
308
|
+
] }) }),
|
|
309
|
+
/* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", children: "Arrows to navigate, Enter to select, B/Left to go back" }) }),
|
|
310
|
+
selectedProvider?.models.map((model, index) => {
|
|
311
|
+
const isSelected = index === modelIndex;
|
|
312
|
+
const isCurrent = model === config.model && selectedProvider.id === config.provider;
|
|
313
|
+
const isDefault = model === selectedProvider.defaultModel;
|
|
314
|
+
return /* @__PURE__ */ jsx6(Box6, { marginY: 0, children: /* @__PURE__ */ jsxs5(Text6, { color: isSelected ? "cyan" : "white", children: [
|
|
315
|
+
isSelected ? "\u276F " : " ",
|
|
316
|
+
model,
|
|
317
|
+
isCurrent && /* @__PURE__ */ jsx6(Text6, { color: "green", children: " (current)" }),
|
|
318
|
+
isDefault && !isCurrent && /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: " (default)" })
|
|
319
|
+
] }) }, model);
|
|
320
|
+
}),
|
|
321
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
|
|
322
|
+
"Provider: ",
|
|
323
|
+
selectedProvider?.name
|
|
324
|
+
] }) })
|
|
325
|
+
] });
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/hooks/useChat.ts
|
|
329
|
+
import { useState as useState3, useCallback, useRef, useEffect } from "react";
|
|
330
|
+
|
|
193
331
|
// src/lib/api.ts
|
|
194
332
|
var SYSTEM_PROMPT = `You are C-napse, a helpful AI assistant for PC automation running on the user's desktop.
|
|
195
333
|
You can help with coding, file management, shell commands, and more. Be concise and helpful.
|
|
@@ -347,6 +485,107 @@ async function getScreenDescription() {
|
|
|
347
485
|
}
|
|
348
486
|
}
|
|
349
487
|
|
|
488
|
+
// src/hooks/useChat.ts
|
|
489
|
+
var WELCOME_MESSAGE = {
|
|
490
|
+
id: "0",
|
|
491
|
+
role: "system",
|
|
492
|
+
content: "Welcome to C-napse! Type your message and press Enter.\n\nShortcuts: Ctrl+H for help, Ctrl+P for provider",
|
|
493
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
494
|
+
};
|
|
495
|
+
function useChat(screenWatch = false) {
|
|
496
|
+
const [messages, setMessages] = useState3([WELCOME_MESSAGE]);
|
|
497
|
+
const [isProcessing, setIsProcessing] = useState3(false);
|
|
498
|
+
const [error, setError] = useState3(null);
|
|
499
|
+
const screenContextRef = useRef(null);
|
|
500
|
+
useEffect(() => {
|
|
501
|
+
if (!screenWatch) {
|
|
502
|
+
screenContextRef.current = null;
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const checkScreen = async () => {
|
|
506
|
+
const desc = await getScreenDescription();
|
|
507
|
+
if (desc) {
|
|
508
|
+
screenContextRef.current = desc;
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
checkScreen();
|
|
512
|
+
const interval = setInterval(checkScreen, 5e3);
|
|
513
|
+
return () => clearInterval(interval);
|
|
514
|
+
}, [screenWatch]);
|
|
515
|
+
const addSystemMessage = useCallback((content) => {
|
|
516
|
+
setMessages((prev) => [
|
|
517
|
+
...prev,
|
|
518
|
+
{
|
|
519
|
+
id: Date.now().toString(),
|
|
520
|
+
role: "system",
|
|
521
|
+
content,
|
|
522
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
523
|
+
}
|
|
524
|
+
]);
|
|
525
|
+
}, []);
|
|
526
|
+
const sendMessage = useCallback(async (content) => {
|
|
527
|
+
if (!content.trim() || isProcessing) return;
|
|
528
|
+
setError(null);
|
|
529
|
+
const userMsg = {
|
|
530
|
+
id: Date.now().toString(),
|
|
531
|
+
role: "user",
|
|
532
|
+
content,
|
|
533
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
534
|
+
};
|
|
535
|
+
const assistantId = (Date.now() + 1).toString();
|
|
536
|
+
const assistantMsg = {
|
|
537
|
+
id: assistantId,
|
|
538
|
+
role: "assistant",
|
|
539
|
+
content: "",
|
|
540
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
541
|
+
isStreaming: true
|
|
542
|
+
};
|
|
543
|
+
setMessages((prev) => [...prev, userMsg, assistantMsg]);
|
|
544
|
+
setIsProcessing(true);
|
|
545
|
+
try {
|
|
546
|
+
const apiMessages = messages.filter((m) => m.role === "user" || m.role === "assistant").slice(-10).map((m) => ({ role: m.role, content: m.content }));
|
|
547
|
+
let finalContent = content;
|
|
548
|
+
if (screenWatch && screenContextRef.current) {
|
|
549
|
+
finalContent = `[Screen context: ${screenContextRef.current}]
|
|
550
|
+
|
|
551
|
+
${content}`;
|
|
552
|
+
}
|
|
553
|
+
apiMessages.push({ role: "user", content: finalContent });
|
|
554
|
+
const response = await chat(apiMessages);
|
|
555
|
+
setMessages(
|
|
556
|
+
(prev) => prev.map(
|
|
557
|
+
(m) => m.id === assistantId ? { ...m, content: response.content || "(no response)", isStreaming: false } : m
|
|
558
|
+
)
|
|
559
|
+
);
|
|
560
|
+
} catch (err2) {
|
|
561
|
+
const errorMsg = err2 instanceof Error ? err2.message : "Unknown error";
|
|
562
|
+
setError(errorMsg);
|
|
563
|
+
setMessages(
|
|
564
|
+
(prev) => prev.map(
|
|
565
|
+
(m) => m.id === assistantId ? { ...m, content: `Error: ${errorMsg}`, isStreaming: false } : m
|
|
566
|
+
)
|
|
567
|
+
);
|
|
568
|
+
} finally {
|
|
569
|
+
setIsProcessing(false);
|
|
570
|
+
}
|
|
571
|
+
}, [messages, isProcessing, screenWatch]);
|
|
572
|
+
const clearMessages = useCallback(() => {
|
|
573
|
+
setMessages([WELCOME_MESSAGE]);
|
|
574
|
+
setError(null);
|
|
575
|
+
}, []);
|
|
576
|
+
return {
|
|
577
|
+
messages,
|
|
578
|
+
isProcessing,
|
|
579
|
+
error,
|
|
580
|
+
sendMessage,
|
|
581
|
+
addSystemMessage,
|
|
582
|
+
clearMessages
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/hooks/useVision.ts
|
|
587
|
+
import { useState as useState4, useCallback as useCallback2 } from "react";
|
|
588
|
+
|
|
350
589
|
// src/lib/vision.ts
|
|
351
590
|
async function describeScreen() {
|
|
352
591
|
const screenshot = await captureScreenshot();
|
|
@@ -370,10 +609,10 @@ async function captureScreenFallback() {
|
|
|
370
609
|
const { exec: exec5 } = await import("child_process");
|
|
371
610
|
const { promisify: promisify5 } = await import("util");
|
|
372
611
|
const { tmpdir } = await import("os");
|
|
373
|
-
const { join } = await import("path");
|
|
612
|
+
const { join: join2 } = await import("path");
|
|
374
613
|
const { readFile, unlink } = await import("fs/promises");
|
|
375
614
|
const execAsync5 = promisify5(exec5);
|
|
376
|
-
const tempFile =
|
|
615
|
+
const tempFile = join2(tmpdir(), `cnapse-screen-${Date.now()}.png`);
|
|
377
616
|
try {
|
|
378
617
|
const platform = process.platform;
|
|
379
618
|
if (platform === "win32") {
|
|
@@ -551,6 +790,40 @@ async function analyzeWithOpenAI(base64Image, prompt) {
|
|
|
551
790
|
return data.choices?.[0]?.message?.content || "Unable to analyze image";
|
|
552
791
|
}
|
|
553
792
|
|
|
793
|
+
// src/hooks/useVision.ts
|
|
794
|
+
function useVision() {
|
|
795
|
+
const [isAnalyzing, setIsAnalyzing] = useState4(false);
|
|
796
|
+
const [lastDescription, setLastDescription] = useState4(null);
|
|
797
|
+
const [lastScreenshot, setLastScreenshot] = useState4(null);
|
|
798
|
+
const [error, setError] = useState4(null);
|
|
799
|
+
const analyze = useCallback2(async () => {
|
|
800
|
+
setIsAnalyzing(true);
|
|
801
|
+
setError(null);
|
|
802
|
+
try {
|
|
803
|
+
const result = await describeScreen();
|
|
804
|
+
setLastDescription(result.description);
|
|
805
|
+
setLastScreenshot(result.screenshot);
|
|
806
|
+
return result.description;
|
|
807
|
+
} catch (err2) {
|
|
808
|
+
const errorMsg = err2 instanceof Error ? err2.message : "Vision analysis failed";
|
|
809
|
+
setError(errorMsg);
|
|
810
|
+
throw err2;
|
|
811
|
+
} finally {
|
|
812
|
+
setIsAnalyzing(false);
|
|
813
|
+
}
|
|
814
|
+
}, []);
|
|
815
|
+
return {
|
|
816
|
+
isAnalyzing,
|
|
817
|
+
lastDescription,
|
|
818
|
+
lastScreenshot,
|
|
819
|
+
error,
|
|
820
|
+
analyze
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/hooks/useTelegram.ts
|
|
825
|
+
import { useState as useState5, useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
826
|
+
|
|
554
827
|
// src/services/telegram.ts
|
|
555
828
|
import { EventEmitter } from "events";
|
|
556
829
|
|
|
@@ -991,48 +1264,231 @@ function getTelegramBot() {
|
|
|
991
1264
|
return instance;
|
|
992
1265
|
}
|
|
993
1266
|
|
|
1267
|
+
// src/hooks/useTelegram.ts
|
|
1268
|
+
function useTelegram(onMessage) {
|
|
1269
|
+
const [isEnabled, setIsEnabled] = useState5(false);
|
|
1270
|
+
const [isStarting, setIsStarting] = useState5(false);
|
|
1271
|
+
const [error, setError] = useState5(null);
|
|
1272
|
+
const [lastMessage, setLastMessage] = useState5(null);
|
|
1273
|
+
const onMessageRef = useRef2(onMessage);
|
|
1274
|
+
useEffect2(() => {
|
|
1275
|
+
onMessageRef.current = onMessage;
|
|
1276
|
+
}, [onMessage]);
|
|
1277
|
+
const start = useCallback3(async () => {
|
|
1278
|
+
if (isEnabled) return;
|
|
1279
|
+
setIsStarting(true);
|
|
1280
|
+
setError(null);
|
|
1281
|
+
try {
|
|
1282
|
+
const bot = getTelegramBot();
|
|
1283
|
+
bot.on("message", (msg) => {
|
|
1284
|
+
setLastMessage(msg);
|
|
1285
|
+
onMessageRef.current?.(msg);
|
|
1286
|
+
});
|
|
1287
|
+
bot.on("error", (err2) => {
|
|
1288
|
+
setError(err2.message);
|
|
1289
|
+
});
|
|
1290
|
+
await bot.start();
|
|
1291
|
+
setIsEnabled(true);
|
|
1292
|
+
} catch (err2) {
|
|
1293
|
+
const errorMsg = err2 instanceof Error ? err2.message : "Failed to start Telegram bot";
|
|
1294
|
+
setError(errorMsg);
|
|
1295
|
+
throw err2;
|
|
1296
|
+
} finally {
|
|
1297
|
+
setIsStarting(false);
|
|
1298
|
+
}
|
|
1299
|
+
}, [isEnabled]);
|
|
1300
|
+
const stop = useCallback3(async () => {
|
|
1301
|
+
if (!isEnabled) return;
|
|
1302
|
+
try {
|
|
1303
|
+
const bot = getTelegramBot();
|
|
1304
|
+
await bot.stop();
|
|
1305
|
+
setIsEnabled(false);
|
|
1306
|
+
} catch (err2) {
|
|
1307
|
+
const errorMsg = err2 instanceof Error ? err2.message : "Failed to stop Telegram bot";
|
|
1308
|
+
setError(errorMsg);
|
|
1309
|
+
throw err2;
|
|
1310
|
+
}
|
|
1311
|
+
}, [isEnabled]);
|
|
1312
|
+
const toggle = useCallback3(async () => {
|
|
1313
|
+
if (isEnabled) {
|
|
1314
|
+
await stop();
|
|
1315
|
+
} else {
|
|
1316
|
+
await start();
|
|
1317
|
+
}
|
|
1318
|
+
}, [isEnabled, start, stop]);
|
|
1319
|
+
return {
|
|
1320
|
+
isEnabled,
|
|
1321
|
+
isStarting,
|
|
1322
|
+
error,
|
|
1323
|
+
lastMessage,
|
|
1324
|
+
toggle,
|
|
1325
|
+
start,
|
|
1326
|
+
stop
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// src/hooks/useTasks.ts
|
|
1331
|
+
import { useState as useState6, useCallback as useCallback4 } from "react";
|
|
1332
|
+
|
|
994
1333
|
// src/lib/tasks.ts
|
|
995
|
-
|
|
996
|
-
|
|
1334
|
+
import * as fs from "fs";
|
|
1335
|
+
import * as path from "path";
|
|
1336
|
+
import * as os from "os";
|
|
1337
|
+
var TASK_MEMORY_FILE = path.join(os.homedir(), ".cnapse", "task-memory.json");
|
|
1338
|
+
function loadTaskMemory() {
|
|
1339
|
+
try {
|
|
1340
|
+
if (fs.existsSync(TASK_MEMORY_FILE)) {
|
|
1341
|
+
const data = fs.readFileSync(TASK_MEMORY_FILE, "utf-8");
|
|
1342
|
+
return JSON.parse(data);
|
|
1343
|
+
}
|
|
1344
|
+
} catch {
|
|
1345
|
+
}
|
|
1346
|
+
return { patterns: [], version: 1 };
|
|
1347
|
+
}
|
|
1348
|
+
function saveTaskPattern(input, steps) {
|
|
1349
|
+
try {
|
|
1350
|
+
const memory = loadTaskMemory();
|
|
1351
|
+
const normalized = normalizeInput(input);
|
|
1352
|
+
const existing = memory.patterns.find((p) => p.normalizedInput === normalized);
|
|
1353
|
+
if (existing) {
|
|
1354
|
+
existing.steps = steps;
|
|
1355
|
+
existing.successCount++;
|
|
1356
|
+
existing.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
|
|
1357
|
+
} else {
|
|
1358
|
+
memory.patterns.push({
|
|
1359
|
+
input,
|
|
1360
|
+
normalizedInput: normalized,
|
|
1361
|
+
steps,
|
|
1362
|
+
successCount: 1,
|
|
1363
|
+
lastUsed: (/* @__PURE__ */ new Date()).toISOString()
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
memory.patterns = memory.patterns.sort((a, b) => b.successCount - a.successCount).slice(0, 100);
|
|
1367
|
+
const dir = path.dirname(TASK_MEMORY_FILE);
|
|
1368
|
+
if (!fs.existsSync(dir)) {
|
|
1369
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1370
|
+
}
|
|
1371
|
+
fs.writeFileSync(TASK_MEMORY_FILE, JSON.stringify(memory, null, 2));
|
|
1372
|
+
} catch {
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
function normalizeInput(input) {
|
|
1376
|
+
return input.toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
1377
|
+
}
|
|
1378
|
+
function findSimilarPatterns(input) {
|
|
1379
|
+
const memory = loadTaskMemory();
|
|
1380
|
+
const normalized = normalizeInput(input);
|
|
1381
|
+
const words = normalized.split(" ").filter((w) => w.length > 2);
|
|
1382
|
+
return memory.patterns.filter((pattern) => {
|
|
1383
|
+
const patternWords = pattern.normalizedInput.split(" ");
|
|
1384
|
+
const matches = words.filter((w) => patternWords.includes(w));
|
|
1385
|
+
return matches.length >= Math.min(2, words.length * 0.5);
|
|
1386
|
+
}).sort((a, b) => b.successCount - a.successCount).slice(0, 3);
|
|
1387
|
+
}
|
|
1388
|
+
function buildChainOfThoughtPrompt(input) {
|
|
1389
|
+
const similarPatterns = findSimilarPatterns(input);
|
|
1390
|
+
let learnedExamples = "";
|
|
1391
|
+
if (similarPatterns.length > 0) {
|
|
1392
|
+
learnedExamples = `
|
|
1393
|
+
## LEARNED PATTERNS (from successful past tasks)
|
|
1394
|
+
These patterns worked before - use them as reference:
|
|
1395
|
+
|
|
1396
|
+
${similarPatterns.map((p, i) => `
|
|
1397
|
+
Pattern ${i + 1} (used ${p.successCount} times):
|
|
1398
|
+
Input: "${p.input}"
|
|
1399
|
+
Steps: ${JSON.stringify(p.steps, null, 2)}
|
|
1400
|
+
`).join("\n")}
|
|
1401
|
+
`;
|
|
1402
|
+
}
|
|
1403
|
+
return `You are a task parser for Windows PC automation. Your job is to convert natural language into precise, executable steps.
|
|
1404
|
+
|
|
1405
|
+
## THINKING PROCESS
|
|
1406
|
+
Before outputting steps, THINK through these questions:
|
|
1407
|
+
|
|
1408
|
+
1. **WHAT** is the main goal?
|
|
1409
|
+
- What application needs to open?
|
|
1410
|
+
- What action needs to happen inside it?
|
|
1411
|
+
- What is the expected end result?
|
|
1412
|
+
|
|
1413
|
+
2. **HOW** to achieve it on Windows?
|
|
1414
|
+
- Use Win+R (meta+r) to open Run dialog for apps
|
|
1415
|
+
- Wait 1-3 seconds after opening apps for them to load
|
|
1416
|
+
- Use keyboard shortcuts when possible (faster, more reliable)
|
|
1417
|
+
- Common shortcuts: Ctrl+S (save), Ctrl+O (open), Ctrl+N (new), Alt+F4 (close)
|
|
1418
|
+
|
|
1419
|
+
3. **SEQUENCE** - what order makes sense?
|
|
1420
|
+
- Open app FIRST
|
|
1421
|
+
- WAIT for it to load
|
|
1422
|
+
- THEN interact with it
|
|
1423
|
+
- Add waits between actions that need time
|
|
997
1424
|
|
|
998
|
-
|
|
999
|
-
-
|
|
1000
|
-
-
|
|
1001
|
-
-
|
|
1002
|
-
- key_combo: Key combination (e.g., "key_combo:control+s", "key_combo:alt+f4")
|
|
1003
|
-
- click: Click mouse (e.g., "click:left", "click:right")
|
|
1004
|
-
- wait: Wait seconds (e.g., "wait:2")
|
|
1005
|
-
- focus_window: Focus window by title (e.g., "focus_window:Notepad")
|
|
1006
|
-
- screenshot: Take screenshot and describe
|
|
1425
|
+
4. **EDGE CASES** - what could go wrong?
|
|
1426
|
+
- App might already be open -> focus_window first
|
|
1427
|
+
- Dialogs might appear -> handle or dismiss them
|
|
1428
|
+
- Typing too fast -> add small waits
|
|
1007
1429
|
|
|
1008
|
-
|
|
1430
|
+
## AVAILABLE ACTIONS
|
|
1431
|
+
- open_app: Open app via Run dialog (e.g., "open_app:notepad", "open_app:code", "open_app:chrome")
|
|
1432
|
+
- type_text: Type text string (e.g., "type_text:Hello World")
|
|
1433
|
+
- press_key: Single key (e.g., "press_key:enter", "press_key:escape", "press_key:tab")
|
|
1434
|
+
- key_combo: Key combination (e.g., "key_combo:control+s", "key_combo:alt+f4", "key_combo:meta+r")
|
|
1435
|
+
- click: Mouse click (e.g., "click:left", "click:right")
|
|
1436
|
+
- wait: Wait N seconds (e.g., "wait:2" - use 1-3s for app loads)
|
|
1437
|
+
- focus_window: Focus by title (e.g., "focus_window:Notepad")
|
|
1438
|
+
- screenshot: Capture and describe screen
|
|
1439
|
+
${learnedExamples}
|
|
1440
|
+
## EXAMPLES WITH REASONING
|
|
1441
|
+
|
|
1442
|
+
### Example 1: "open notepad and type hello"
|
|
1443
|
+
Thinking:
|
|
1444
|
+
- Goal: Open Notepad, then type text into it
|
|
1445
|
+
- How: Win+R -> notepad -> Enter to open, then type
|
|
1446
|
+
- Sequence: Open -> Wait for load -> Type
|
|
1447
|
+
- Edge case: Need wait time for Notepad window to be ready
|
|
1448
|
+
|
|
1449
|
+
Output:
|
|
1009
1450
|
[
|
|
1010
|
-
{ "description": "
|
|
1011
|
-
|
|
1451
|
+
{ "description": "Open Notepad via Run dialog", "action": "open_app:notepad" },
|
|
1452
|
+
{ "description": "Wait for Notepad to fully load", "action": "wait:2" },
|
|
1453
|
+
{ "description": "Type the greeting text", "action": "type_text:hello" }
|
|
1454
|
+
]
|
|
1455
|
+
|
|
1456
|
+
### Example 2: "save the current document"
|
|
1457
|
+
Thinking:
|
|
1458
|
+
- Goal: Save whatever is in the current app
|
|
1459
|
+
- How: Ctrl+S is universal save shortcut
|
|
1460
|
+
- Sequence: Just the key combo, maybe wait for save
|
|
1461
|
+
- Edge case: If file is new, Save As dialog might appear
|
|
1462
|
+
|
|
1463
|
+
Output:
|
|
1464
|
+
[
|
|
1465
|
+
{ "description": "Press Ctrl+S to save", "action": "key_combo:control+s" },
|
|
1466
|
+
{ "description": "Wait for save to complete", "action": "wait:1" }
|
|
1012
1467
|
]
|
|
1013
1468
|
|
|
1014
|
-
Example
|
|
1015
|
-
|
|
1469
|
+
### Example 3: "close this window"
|
|
1470
|
+
Thinking:
|
|
1471
|
+
- Goal: Close the current active window
|
|
1472
|
+
- How: Alt+F4 closes active window on Windows
|
|
1473
|
+
- Sequence: Single action
|
|
1474
|
+
- Edge case: Might prompt to save - user handles that
|
|
1475
|
+
|
|
1476
|
+
Output:
|
|
1016
1477
|
[
|
|
1017
|
-
{ "description": "
|
|
1018
|
-
{ "description": "Wait for Notepad to open", "action": "wait:2" },
|
|
1019
|
-
{ "description": "Type hello world", "action": "type_text:Hello World" }
|
|
1478
|
+
{ "description": "Close active window with Alt+F4", "action": "key_combo:alt+f4" }
|
|
1020
1479
|
]
|
|
1021
1480
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1481
|
+
## YOUR TASK
|
|
1482
|
+
Now parse this request: "${input}"
|
|
1483
|
+
|
|
1484
|
+
First, briefly think through the 4 questions above, then output ONLY a JSON array:
|
|
1024
1485
|
[
|
|
1025
|
-
{ "description": "
|
|
1026
|
-
|
|
1027
|
-
{ "description": "Open folder with Ctrl+K Ctrl+O", "action": "key_combo:control+k" },
|
|
1028
|
-
{ "description": "Wait for dialog", "action": "wait:1" },
|
|
1029
|
-
{ "description": "Continue folder open", "action": "key_combo:control+o" },
|
|
1030
|
-
{ "description": "Wait for folder dialog", "action": "wait:1" },
|
|
1031
|
-
{ "description": "Type folder path", "action": "type_text:E:\\\\Projects" },
|
|
1032
|
-
{ "description": "Press Enter to open folder", "action": "press_key:enter" },
|
|
1033
|
-
{ "description": "Wait for folder to load", "action": "wait:2" },
|
|
1034
|
-
{ "description": "Open terminal with Ctrl+\`", "action": "key_combo:control+\`" }
|
|
1486
|
+
{ "description": "Human readable step", "action": "action_type:params" },
|
|
1487
|
+
...
|
|
1035
1488
|
]`;
|
|
1489
|
+
}
|
|
1490
|
+
async function parseTask(input) {
|
|
1491
|
+
const systemPrompt = buildChainOfThoughtPrompt(input);
|
|
1036
1492
|
const messages = [
|
|
1037
1493
|
{ role: "user", content: input }
|
|
1038
1494
|
];
|
|
@@ -1143,6 +1599,11 @@ async function executeTask(task, onProgress) {
|
|
|
1143
1599
|
}
|
|
1144
1600
|
if (task.status !== "failed") {
|
|
1145
1601
|
task.status = "completed";
|
|
1602
|
+
const steps = task.steps.map((s) => ({
|
|
1603
|
+
description: s.description,
|
|
1604
|
+
action: s.action
|
|
1605
|
+
}));
|
|
1606
|
+
saveTaskPattern(task.description, steps);
|
|
1146
1607
|
}
|
|
1147
1608
|
task.completedAt = /* @__PURE__ */ new Date();
|
|
1148
1609
|
return task;
|
|
@@ -1150,6 +1611,24 @@ async function executeTask(task, onProgress) {
|
|
|
1150
1611
|
function sleep(ms) {
|
|
1151
1612
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1152
1613
|
}
|
|
1614
|
+
function getTaskMemoryStats() {
|
|
1615
|
+
const memory = loadTaskMemory();
|
|
1616
|
+
const totalUses = memory.patterns.reduce((sum, p) => sum + p.successCount, 0);
|
|
1617
|
+
const topPatterns = memory.patterns.sort((a, b) => b.successCount - a.successCount).slice(0, 5).map((p) => `"${p.input}" (${p.successCount}x)`);
|
|
1618
|
+
return {
|
|
1619
|
+
patternCount: memory.patterns.length,
|
|
1620
|
+
totalUses,
|
|
1621
|
+
topPatterns
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
function clearTaskMemory() {
|
|
1625
|
+
try {
|
|
1626
|
+
if (fs.existsSync(TASK_MEMORY_FILE)) {
|
|
1627
|
+
fs.unlinkSync(TASK_MEMORY_FILE);
|
|
1628
|
+
}
|
|
1629
|
+
} catch {
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1153
1632
|
function formatTask(task) {
|
|
1154
1633
|
const statusEmoji = {
|
|
1155
1634
|
pending: "\u23F3",
|
|
@@ -1180,293 +1659,251 @@ function formatTask(task) {
|
|
|
1180
1659
|
return output;
|
|
1181
1660
|
}
|
|
1182
1661
|
|
|
1662
|
+
// src/hooks/useTasks.ts
|
|
1663
|
+
function useTasks(onProgress) {
|
|
1664
|
+
const [isRunning, setIsRunning] = useState6(false);
|
|
1665
|
+
const [currentTask, setCurrentTask] = useState6(null);
|
|
1666
|
+
const [currentStep, setCurrentStep] = useState6(null);
|
|
1667
|
+
const [error, setError] = useState6(null);
|
|
1668
|
+
const run = useCallback4(async (description) => {
|
|
1669
|
+
setIsRunning(true);
|
|
1670
|
+
setError(null);
|
|
1671
|
+
try {
|
|
1672
|
+
const task = await parseTask(description);
|
|
1673
|
+
setCurrentTask(task);
|
|
1674
|
+
const result = await executeTask(task, (updatedTask, step) => {
|
|
1675
|
+
setCurrentTask({ ...updatedTask });
|
|
1676
|
+
setCurrentStep(step);
|
|
1677
|
+
onProgress?.(updatedTask, step);
|
|
1678
|
+
});
|
|
1679
|
+
setCurrentTask(result);
|
|
1680
|
+
return result;
|
|
1681
|
+
} catch (err2) {
|
|
1682
|
+
const errorMsg = err2 instanceof Error ? err2.message : "Task failed";
|
|
1683
|
+
setError(errorMsg);
|
|
1684
|
+
throw err2;
|
|
1685
|
+
} finally {
|
|
1686
|
+
setIsRunning(false);
|
|
1687
|
+
setCurrentStep(null);
|
|
1688
|
+
}
|
|
1689
|
+
}, [onProgress]);
|
|
1690
|
+
return {
|
|
1691
|
+
isRunning,
|
|
1692
|
+
currentTask,
|
|
1693
|
+
currentStep,
|
|
1694
|
+
error,
|
|
1695
|
+
run,
|
|
1696
|
+
format: formatTask,
|
|
1697
|
+
getMemoryStats: getTaskMemoryStats,
|
|
1698
|
+
clearMemory: clearTaskMemory
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1183
1702
|
// src/components/App.tsx
|
|
1184
|
-
import { jsx as
|
|
1703
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1185
1704
|
function App() {
|
|
1186
1705
|
const { exit } = useApp();
|
|
1187
|
-
const [
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
const
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
const [error, setError] = useState2(null);
|
|
1199
|
-
const [screenWatch, setScreenWatch] = useState2(false);
|
|
1200
|
-
const [showHelpMenu, setShowHelpMenu] = useState2(false);
|
|
1201
|
-
const [telegramEnabled, setTelegramEnabled] = useState2(false);
|
|
1202
|
-
const screenContextRef = useRef(null);
|
|
1203
|
-
useEffect(() => {
|
|
1204
|
-
if (!screenWatch) {
|
|
1205
|
-
screenContextRef.current = null;
|
|
1206
|
-
return;
|
|
1207
|
-
}
|
|
1208
|
-
const checkScreen = async () => {
|
|
1209
|
-
const desc = await getScreenDescription();
|
|
1210
|
-
if (desc) {
|
|
1211
|
-
screenContextRef.current = desc;
|
|
1212
|
-
}
|
|
1213
|
-
};
|
|
1214
|
-
checkScreen();
|
|
1215
|
-
const interval = setInterval(checkScreen, 5e3);
|
|
1216
|
-
return () => clearInterval(interval);
|
|
1217
|
-
}, [screenWatch]);
|
|
1218
|
-
useInput2((inputChar, key) => {
|
|
1219
|
-
if (showHelpMenu) {
|
|
1220
|
-
return;
|
|
1221
|
-
}
|
|
1222
|
-
if (key.ctrl && inputChar === "c") {
|
|
1223
|
-
exit();
|
|
1224
|
-
}
|
|
1225
|
-
if (key.ctrl && inputChar === "l") {
|
|
1226
|
-
setMessages([messages[0]]);
|
|
1227
|
-
setError(null);
|
|
1706
|
+
const [overlay, setOverlay] = useState7("none");
|
|
1707
|
+
const [screenWatch, setScreenWatch] = useState7(false);
|
|
1708
|
+
const [status, setStatus] = useState7("Ready");
|
|
1709
|
+
const chat2 = useChat(screenWatch);
|
|
1710
|
+
const vision = useVision();
|
|
1711
|
+
const telegram = useTelegram((msg) => {
|
|
1712
|
+
chat2.addSystemMessage(`\u{1F4F1} Telegram [${msg.from}]: ${msg.text}`);
|
|
1713
|
+
});
|
|
1714
|
+
const tasks = useTasks((task, step) => {
|
|
1715
|
+
if (step.status === "running") {
|
|
1716
|
+
setStatus(`Running: ${step.description}`);
|
|
1228
1717
|
}
|
|
1718
|
+
});
|
|
1719
|
+
useInput3((inputChar, key) => {
|
|
1720
|
+
if (overlay !== "none") return;
|
|
1721
|
+
if (key.ctrl && inputChar === "c") exit();
|
|
1722
|
+
if (key.ctrl && inputChar === "l") chat2.clearMessages();
|
|
1723
|
+
if (key.ctrl && inputChar === "h") setOverlay("help");
|
|
1724
|
+
if (key.ctrl && inputChar === "p") setOverlay("provider");
|
|
1229
1725
|
if (key.ctrl && inputChar === "w") {
|
|
1230
1726
|
setScreenWatch((prev) => {
|
|
1231
1727
|
const newState = !prev;
|
|
1232
|
-
addSystemMessage(
|
|
1233
|
-
newState ? "\u{1F5A5}\uFE0F Screen watching enabled.
|
|
1728
|
+
chat2.addSystemMessage(
|
|
1729
|
+
newState ? "\u{1F5A5}\uFE0F Screen watching enabled." : "\u{1F5A5}\uFE0F Screen watching disabled."
|
|
1234
1730
|
);
|
|
1235
1731
|
return newState;
|
|
1236
1732
|
});
|
|
1237
1733
|
}
|
|
1238
|
-
if (key.ctrl && inputChar === "h") {
|
|
1239
|
-
setShowHelpMenu(true);
|
|
1240
|
-
}
|
|
1241
1734
|
if (key.ctrl && inputChar === "t") {
|
|
1242
|
-
|
|
1243
|
-
const newState = !prev;
|
|
1244
|
-
addSystemMessage(
|
|
1245
|
-
newState ? "\u{1F4F1} Telegram bot enabled." : "\u{1F4F1} Telegram bot disabled."
|
|
1246
|
-
);
|
|
1247
|
-
return newState;
|
|
1248
|
-
});
|
|
1735
|
+
handleTelegramToggle();
|
|
1249
1736
|
}
|
|
1250
1737
|
});
|
|
1251
|
-
const
|
|
1252
|
-
if (!value.trim() || isProcessing) return;
|
|
1253
|
-
const userInput = value.trim();
|
|
1254
|
-
setInput("");
|
|
1255
|
-
setError(null);
|
|
1256
|
-
if (userInput.startsWith("/")) {
|
|
1257
|
-
handleCommand(userInput);
|
|
1258
|
-
return;
|
|
1259
|
-
}
|
|
1260
|
-
const userMsg = {
|
|
1261
|
-
id: Date.now().toString(),
|
|
1262
|
-
role: "user",
|
|
1263
|
-
content: userInput,
|
|
1264
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
1265
|
-
};
|
|
1266
|
-
setMessages((prev) => [...prev, userMsg]);
|
|
1267
|
-
const assistantId = (Date.now() + 1).toString();
|
|
1268
|
-
setMessages((prev) => [
|
|
1269
|
-
...prev,
|
|
1270
|
-
{
|
|
1271
|
-
id: assistantId,
|
|
1272
|
-
role: "assistant",
|
|
1273
|
-
content: "",
|
|
1274
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
1275
|
-
isStreaming: true
|
|
1276
|
-
}
|
|
1277
|
-
]);
|
|
1278
|
-
setIsProcessing(true);
|
|
1279
|
-
setStatus("Thinking...");
|
|
1280
|
-
try {
|
|
1281
|
-
const apiMessages = messages.filter((m) => m.role === "user" || m.role === "assistant").slice(-10).map((m) => ({ role: m.role, content: m.content }));
|
|
1282
|
-
let finalInput = userInput;
|
|
1283
|
-
if (screenWatch && screenContextRef.current) {
|
|
1284
|
-
finalInput = `[Screen context: ${screenContextRef.current}]
|
|
1285
|
-
|
|
1286
|
-
${userInput}`;
|
|
1287
|
-
}
|
|
1288
|
-
apiMessages.push({ role: "user", content: finalInput });
|
|
1289
|
-
const response = await chat(apiMessages);
|
|
1290
|
-
setMessages(
|
|
1291
|
-
(prev) => prev.map(
|
|
1292
|
-
(m) => m.id === assistantId ? { ...m, content: response.content || "(no response)", isStreaming: false } : m
|
|
1293
|
-
)
|
|
1294
|
-
);
|
|
1295
|
-
} catch (err2) {
|
|
1296
|
-
const errorMsg = err2 instanceof Error ? err2.message : "Unknown error";
|
|
1297
|
-
setError(errorMsg);
|
|
1298
|
-
setMessages(
|
|
1299
|
-
(prev) => prev.map(
|
|
1300
|
-
(m) => m.id === assistantId ? { ...m, content: `Error: ${errorMsg}`, isStreaming: false } : m
|
|
1301
|
-
)
|
|
1302
|
-
);
|
|
1303
|
-
} finally {
|
|
1304
|
-
setIsProcessing(false);
|
|
1305
|
-
setStatus("Ready");
|
|
1306
|
-
}
|
|
1307
|
-
};
|
|
1308
|
-
const handleCommand = (cmd) => {
|
|
1738
|
+
const handleCommand = useCallback5(async (cmd) => {
|
|
1309
1739
|
const parts = cmd.slice(1).split(" ");
|
|
1310
1740
|
const command = parts[0];
|
|
1311
1741
|
const args2 = parts.slice(1).join(" ");
|
|
1312
1742
|
switch (command) {
|
|
1313
1743
|
case "clear":
|
|
1314
|
-
|
|
1315
|
-
addSystemMessage("Chat cleared.");
|
|
1744
|
+
chat2.clearMessages();
|
|
1745
|
+
chat2.addSystemMessage("Chat cleared.");
|
|
1316
1746
|
break;
|
|
1317
1747
|
case "help":
|
|
1318
|
-
|
|
1748
|
+
setOverlay("help");
|
|
1749
|
+
break;
|
|
1750
|
+
case "provider":
|
|
1751
|
+
case "model":
|
|
1752
|
+
setOverlay("provider");
|
|
1753
|
+
break;
|
|
1754
|
+
case "config": {
|
|
1755
|
+
const config = getConfig();
|
|
1756
|
+
chat2.addSystemMessage(
|
|
1757
|
+
`\u2699\uFE0F Configuration:
|
|
1758
|
+
Provider: ${config.provider}
|
|
1759
|
+
Model: ${config.model}
|
|
1760
|
+
Ollama: ${config.ollamaHost}
|
|
1761
|
+
|
|
1762
|
+
Use /provider to change`
|
|
1763
|
+
);
|
|
1764
|
+
break;
|
|
1765
|
+
}
|
|
1766
|
+
case "screen":
|
|
1767
|
+
await handleScreenCommand();
|
|
1319
1768
|
break;
|
|
1320
1769
|
case "watch":
|
|
1321
1770
|
setScreenWatch((prev) => {
|
|
1322
1771
|
const newState = !prev;
|
|
1323
|
-
addSystemMessage(
|
|
1772
|
+
chat2.addSystemMessage(
|
|
1324
1773
|
newState ? "\u{1F5A5}\uFE0F Screen watching enabled." : "\u{1F5A5}\uFE0F Screen watching disabled."
|
|
1325
1774
|
);
|
|
1326
1775
|
return newState;
|
|
1327
1776
|
});
|
|
1328
1777
|
break;
|
|
1329
1778
|
case "telegram":
|
|
1330
|
-
handleTelegramToggle();
|
|
1331
|
-
break;
|
|
1332
|
-
case "screen":
|
|
1333
|
-
handleScreenCommand();
|
|
1779
|
+
await handleTelegramToggle();
|
|
1334
1780
|
break;
|
|
1335
1781
|
case "task":
|
|
1336
1782
|
if (args2) {
|
|
1337
|
-
handleTaskCommand(args2);
|
|
1783
|
+
await handleTaskCommand(args2);
|
|
1338
1784
|
} else {
|
|
1339
|
-
addSystemMessage("Usage: /task <description>\nExample: /task open notepad and type hello");
|
|
1785
|
+
chat2.addSystemMessage("Usage: /task <description>\nExample: /task open notepad and type hello");
|
|
1340
1786
|
}
|
|
1341
1787
|
break;
|
|
1342
|
-
case "
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1788
|
+
case "memory":
|
|
1789
|
+
if (args2 === "clear") {
|
|
1790
|
+
tasks.clearMemory();
|
|
1791
|
+
chat2.addSystemMessage("\u{1F9E0} Task memory cleared.");
|
|
1792
|
+
} else {
|
|
1793
|
+
const stats = tasks.getMemoryStats();
|
|
1794
|
+
chat2.addSystemMessage(
|
|
1795
|
+
`\u{1F9E0} Task Memory:
|
|
1796
|
+
|
|
1797
|
+
Learned patterns: ${stats.patternCount}
|
|
1798
|
+
Total successful uses: ${stats.totalUses}
|
|
1799
|
+
|
|
1800
|
+
` + (stats.topPatterns.length > 0 ? ` Top patterns:
|
|
1801
|
+
${stats.topPatterns.map((p) => ` \u2022 ${p}`).join("\n")}
|
|
1802
|
+
|
|
1803
|
+
` : " No patterns learned yet.\n\n") + `The more you use /task, the smarter it gets!
|
|
1804
|
+
Use /memory clear to reset.`
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1350
1807
|
break;
|
|
1351
1808
|
case "quit":
|
|
1352
1809
|
case "exit":
|
|
1353
1810
|
exit();
|
|
1354
1811
|
break;
|
|
1355
1812
|
default:
|
|
1356
|
-
addSystemMessage(`Unknown command: ${command}
|
|
1357
|
-
Type /help
|
|
1813
|
+
chat2.addSystemMessage(`Unknown command: ${command}
|
|
1814
|
+
Type /help for commands`);
|
|
1358
1815
|
}
|
|
1359
|
-
};
|
|
1360
|
-
const
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
const handleScreenCommand = async () => {
|
|
1364
|
-
addSystemMessage("\u{1F4F8} Taking screenshot and analyzing...");
|
|
1365
|
-
setStatus("Analyzing screen...");
|
|
1366
|
-
setIsProcessing(true);
|
|
1816
|
+
}, [chat2, exit]);
|
|
1817
|
+
const handleScreenCommand = useCallback5(async () => {
|
|
1818
|
+
chat2.addSystemMessage("\u{1F4F8} Analyzing screen...");
|
|
1819
|
+
setStatus("Analyzing...");
|
|
1367
1820
|
try {
|
|
1368
|
-
const
|
|
1369
|
-
addSystemMessage(`\u{1F5A5}\uFE0F Screen
|
|
1821
|
+
const description = await vision.analyze();
|
|
1822
|
+
chat2.addSystemMessage(`\u{1F5A5}\uFE0F Screen:
|
|
1370
1823
|
|
|
1371
|
-
${
|
|
1824
|
+
${description}`);
|
|
1372
1825
|
} catch (err2) {
|
|
1373
|
-
|
|
1374
|
-
addSystemMessage(`\u274C Screen capture failed: ${errorMsg}`);
|
|
1826
|
+
chat2.addSystemMessage(`\u274C ${vision.error || "Vision failed"}`);
|
|
1375
1827
|
} finally {
|
|
1376
|
-
setIsProcessing(false);
|
|
1377
1828
|
setStatus("Ready");
|
|
1378
1829
|
}
|
|
1379
|
-
};
|
|
1380
|
-
const
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
try {
|
|
1385
|
-
const task = await parseTask(taskDescription);
|
|
1386
|
-
addSystemMessage(`\u{1F4CB} Task planned (${task.steps.length} steps):
|
|
1387
|
-
${formatTask(task)}`);
|
|
1388
|
-
addSystemMessage("\u{1F680} Executing task...");
|
|
1389
|
-
setStatus("Executing task...");
|
|
1390
|
-
await executeTask(task, (updatedTask, currentStep) => {
|
|
1391
|
-
if (currentStep.status === "running") {
|
|
1392
|
-
setStatus(`Running: ${currentStep.description}`);
|
|
1393
|
-
}
|
|
1394
|
-
});
|
|
1395
|
-
addSystemMessage(`
|
|
1396
|
-
${formatTask(task)}`);
|
|
1397
|
-
if (task.status === "completed") {
|
|
1398
|
-
addSystemMessage("\u2705 Task completed successfully!");
|
|
1399
|
-
} else {
|
|
1400
|
-
addSystemMessage("\u274C Task failed. Check the steps above for errors.");
|
|
1401
|
-
}
|
|
1402
|
-
} catch (err2) {
|
|
1403
|
-
const errorMsg = err2 instanceof Error ? err2.message : "Task failed";
|
|
1404
|
-
addSystemMessage(`\u274C Task error: ${errorMsg}`);
|
|
1405
|
-
} finally {
|
|
1406
|
-
setIsProcessing(false);
|
|
1407
|
-
setStatus("Ready");
|
|
1408
|
-
}
|
|
1409
|
-
};
|
|
1410
|
-
const handleTelegramToggle = async () => {
|
|
1411
|
-
const bot = getTelegramBot();
|
|
1412
|
-
if (telegramEnabled) {
|
|
1413
|
-
try {
|
|
1414
|
-
await bot.stop();
|
|
1415
|
-
setTelegramEnabled(false);
|
|
1416
|
-
addSystemMessage("\u{1F4F1} Telegram bot stopped.");
|
|
1417
|
-
} catch (err2) {
|
|
1418
|
-
const errorMsg = err2 instanceof Error ? err2.message : "Failed to stop bot";
|
|
1419
|
-
addSystemMessage(`\u274C Error stopping bot: ${errorMsg}`);
|
|
1420
|
-
}
|
|
1830
|
+
}, [chat2, vision]);
|
|
1831
|
+
const handleTelegramToggle = useCallback5(async () => {
|
|
1832
|
+
if (telegram.isEnabled) {
|
|
1833
|
+
await telegram.stop();
|
|
1834
|
+
chat2.addSystemMessage("\u{1F4F1} Telegram stopped.");
|
|
1421
1835
|
} else {
|
|
1422
|
-
addSystemMessage("\u{1F4F1} Starting Telegram
|
|
1836
|
+
chat2.addSystemMessage("\u{1F4F1} Starting Telegram...");
|
|
1423
1837
|
setStatus("Starting Telegram...");
|
|
1424
1838
|
try {
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
bot.on("error", (error2) => {
|
|
1429
|
-
addSystemMessage(`\u{1F4F1} Telegram error: ${error2.message}`);
|
|
1430
|
-
});
|
|
1431
|
-
await bot.start();
|
|
1432
|
-
setTelegramEnabled(true);
|
|
1433
|
-
addSystemMessage(
|
|
1434
|
-
"\u{1F4F1} Telegram bot started!\n\nOpen Telegram and send /start to your bot to connect.\nCommands: /screen, /describe, /run <cmd>, /status"
|
|
1839
|
+
await telegram.start();
|
|
1840
|
+
chat2.addSystemMessage(
|
|
1841
|
+
"\u{1F4F1} Telegram started!\nSend /start to your bot to connect.\nCommands: /screen, /describe, /run, /status"
|
|
1435
1842
|
);
|
|
1436
|
-
} catch
|
|
1437
|
-
|
|
1438
|
-
addSystemMessage(`\u274C Telegram error: ${errorMsg}`);
|
|
1843
|
+
} catch {
|
|
1844
|
+
chat2.addSystemMessage(`\u274C ${telegram.error || "Telegram failed"}`);
|
|
1439
1845
|
} finally {
|
|
1440
1846
|
setStatus("Ready");
|
|
1441
1847
|
}
|
|
1442
1848
|
}
|
|
1443
|
-
};
|
|
1444
|
-
const
|
|
1445
|
-
|
|
1446
|
-
|
|
1849
|
+
}, [chat2, telegram]);
|
|
1850
|
+
const handleTaskCommand = useCallback5(async (description) => {
|
|
1851
|
+
chat2.addSystemMessage(`\u{1F4CB} Parsing: ${description}`);
|
|
1852
|
+
setStatus("Parsing task...");
|
|
1853
|
+
try {
|
|
1854
|
+
const task = await tasks.run(description);
|
|
1855
|
+
chat2.addSystemMessage(`
|
|
1856
|
+
${tasks.format(task)}`);
|
|
1857
|
+
chat2.addSystemMessage(
|
|
1858
|
+
task.status === "completed" ? "\u2705 Task completed!" : "\u274C Task failed."
|
|
1859
|
+
);
|
|
1860
|
+
} catch {
|
|
1861
|
+
chat2.addSystemMessage(`\u274C ${tasks.error || "Task failed"}`);
|
|
1862
|
+
} finally {
|
|
1863
|
+
setStatus("Ready");
|
|
1864
|
+
}
|
|
1865
|
+
}, [chat2, tasks]);
|
|
1866
|
+
const handleSubmit = useCallback5(async (value) => {
|
|
1867
|
+
if (!value.trim()) return;
|
|
1868
|
+
if (value.startsWith("/")) {
|
|
1869
|
+
await handleCommand(value);
|
|
1870
|
+
} else {
|
|
1871
|
+
setStatus("Thinking...");
|
|
1872
|
+
await chat2.sendMessage(value);
|
|
1873
|
+
setStatus("Ready");
|
|
1874
|
+
}
|
|
1875
|
+
}, [chat2, handleCommand]);
|
|
1876
|
+
const handleProviderSelect = useCallback5((provider, model) => {
|
|
1877
|
+
chat2.addSystemMessage(`\u2705 Updated: ${provider} / ${model}`);
|
|
1878
|
+
}, [chat2]);
|
|
1879
|
+
if (overlay === "help") {
|
|
1880
|
+
return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: "100%", alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsx7(
|
|
1881
|
+
HelpMenu,
|
|
1447
1882
|
{
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1883
|
+
onClose: () => setOverlay("none"),
|
|
1884
|
+
onSelect: (cmd) => {
|
|
1885
|
+
setOverlay("none");
|
|
1886
|
+
handleCommand(cmd);
|
|
1887
|
+
}
|
|
1452
1888
|
}
|
|
1453
|
-
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
HelpMenu,
|
|
1889
|
+
) });
|
|
1890
|
+
}
|
|
1891
|
+
if (overlay === "provider") {
|
|
1892
|
+
return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: "100%", alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsx7(
|
|
1893
|
+
ProviderSelector,
|
|
1459
1894
|
{
|
|
1460
|
-
onClose: () =>
|
|
1461
|
-
onSelect:
|
|
1895
|
+
onClose: () => setOverlay("none"),
|
|
1896
|
+
onSelect: handleProviderSelect
|
|
1462
1897
|
}
|
|
1463
1898
|
) });
|
|
1464
1899
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1900
|
+
const visibleMessages = chat2.messages.slice(-20);
|
|
1901
|
+
const isProcessing = chat2.isProcessing || vision.isAnalyzing || tasks.isRunning || telegram.isStarting;
|
|
1902
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", height: "100%", children: [
|
|
1903
|
+
/* @__PURE__ */ jsx7(Header, { screenWatch, telegramEnabled: telegram.isEnabled }),
|
|
1904
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "gray", padding: 1, children: [
|
|
1905
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, color: "gray", children: " Chat " }),
|
|
1906
|
+
visibleMessages.map((msg) => /* @__PURE__ */ jsx7(
|
|
1470
1907
|
ChatMessage,
|
|
1471
1908
|
{
|
|
1472
1909
|
role: msg.role,
|
|
@@ -1477,25 +1914,26 @@ ${formatTask(task)}`);
|
|
|
1477
1914
|
msg.id
|
|
1478
1915
|
))
|
|
1479
1916
|
] }),
|
|
1480
|
-
error && /* @__PURE__ */
|
|
1917
|
+
chat2.error && /* @__PURE__ */ jsx7(Box7, { marginY: 1, children: /* @__PURE__ */ jsxs6(Text7, { color: "red", children: [
|
|
1481
1918
|
"Error: ",
|
|
1482
|
-
error
|
|
1919
|
+
chat2.error
|
|
1483
1920
|
] }) }),
|
|
1484
|
-
/* @__PURE__ */
|
|
1921
|
+
/* @__PURE__ */ jsx7(
|
|
1485
1922
|
ChatInput,
|
|
1486
1923
|
{
|
|
1487
|
-
value:
|
|
1488
|
-
onChange:
|
|
1924
|
+
value: "",
|
|
1925
|
+
onChange: () => {
|
|
1926
|
+
},
|
|
1489
1927
|
onSubmit: handleSubmit,
|
|
1490
1928
|
isProcessing
|
|
1491
1929
|
}
|
|
1492
1930
|
),
|
|
1493
|
-
/* @__PURE__ */
|
|
1931
|
+
/* @__PURE__ */ jsx7(StatusBar, { status })
|
|
1494
1932
|
] });
|
|
1495
1933
|
}
|
|
1496
1934
|
|
|
1497
1935
|
// src/index.tsx
|
|
1498
|
-
import { jsx as
|
|
1936
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1499
1937
|
var args = process.argv.slice(2);
|
|
1500
1938
|
if (args.length > 0) {
|
|
1501
1939
|
const command = args[0];
|
|
@@ -1591,7 +2029,7 @@ Manual Setup:
|
|
|
1591
2029
|
}
|
|
1592
2030
|
case "init": {
|
|
1593
2031
|
const { Setup } = await import("./Setup-Q32JPHGP.js");
|
|
1594
|
-
render(/* @__PURE__ */
|
|
2032
|
+
render(/* @__PURE__ */ jsx8(Setup, {}));
|
|
1595
2033
|
process.exit(0);
|
|
1596
2034
|
}
|
|
1597
2035
|
default: {
|
|
@@ -1599,4 +2037,4 @@ Manual Setup:
|
|
|
1599
2037
|
}
|
|
1600
2038
|
}
|
|
1601
2039
|
}
|
|
1602
|
-
render(/* @__PURE__ */
|
|
2040
|
+
render(/* @__PURE__ */ jsx8(App, {}));
|