@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.
@@ -0,0 +1,306 @@
1
+ import {
2
+ getConfig,
3
+ setApiKey,
4
+ setModel,
5
+ setProvider
6
+ } from "./chunk-COKO6V5J.js";
7
+
8
+ // src/components/ConfigUI.tsx
9
+ import { useState, useEffect } from "react";
10
+ import { Box, Text, useInput, useApp } from "ink";
11
+ import TextInput from "ink-text-input";
12
+ import Spinner from "ink-spinner";
13
+ import { exec } from "child_process";
14
+ import { promisify } from "util";
15
+ import { jsx, jsxs } from "react/jsx-runtime";
16
+ var execAsync = promisify(exec);
17
+ var PROVIDERS = [
18
+ {
19
+ id: "ollama",
20
+ name: "Ollama",
21
+ description: "Local AI - Free, private, runs on your PC",
22
+ needsApiKey: false,
23
+ models: [
24
+ { id: "qwen2.5:0.5b", name: "Qwen 2.5 0.5B", description: "Ultra fast, good for tasks", recommended: true },
25
+ { id: "qwen2.5:1.5b", name: "Qwen 2.5 1.5B", description: "Fast, better quality" },
26
+ { id: "qwen2.5:7b", name: "Qwen 2.5 7B", description: "Best quality, needs 8GB+ RAM" },
27
+ { id: "llama3.2:1b", name: "Llama 3.2 1B", description: "Fast, good general use" },
28
+ { id: "llama3.2:3b", name: "Llama 3.2 3B", description: "Balanced speed/quality" },
29
+ { id: "codellama:7b", name: "Code Llama 7B", description: "Best for coding tasks" },
30
+ { id: "llava:7b", name: "LLaVA 7B", description: "Vision model - can see images" }
31
+ ]
32
+ },
33
+ {
34
+ id: "openrouter",
35
+ name: "OpenRouter",
36
+ description: "Many models, pay-per-use, great value",
37
+ needsApiKey: true,
38
+ models: [
39
+ { id: "qwen/qwen-2.5-coder-32b-instruct", name: "Qwen 2.5 Coder 32B", description: "Best for coding, very cheap", recommended: true },
40
+ { id: "anthropic/claude-3.5-sonnet", name: "Claude 3.5 Sonnet", description: "Best overall quality" },
41
+ { id: "openai/gpt-4o", name: "GPT-4o", description: "Fast, multimodal" },
42
+ { id: "openai/gpt-4o-mini", name: "GPT-4o Mini", description: "Cheap, fast, good quality" },
43
+ { id: "google/gemini-pro-1.5", name: "Gemini Pro 1.5", description: "Long context, fast" },
44
+ { id: "meta-llama/llama-3.1-70b-instruct", name: "Llama 3.1 70B", description: "Open source, powerful" }
45
+ ]
46
+ },
47
+ {
48
+ id: "anthropic",
49
+ name: "Anthropic",
50
+ description: "Claude models - Best for complex reasoning",
51
+ needsApiKey: true,
52
+ models: [
53
+ { id: "claude-3-5-sonnet-20241022", name: "Claude 3.5 Sonnet", description: "Best balance of speed/quality", recommended: true },
54
+ { id: "claude-3-opus-20240229", name: "Claude 3 Opus", description: "Most capable, slower" },
55
+ { id: "claude-3-haiku-20240307", name: "Claude 3 Haiku", description: "Fastest, cheapest" }
56
+ ]
57
+ },
58
+ {
59
+ id: "openai",
60
+ name: "OpenAI",
61
+ description: "GPT models - Well-known, reliable",
62
+ needsApiKey: true,
63
+ models: [
64
+ { id: "gpt-4o", name: "GPT-4o", description: "Latest, multimodal", recommended: true },
65
+ { id: "gpt-4o-mini", name: "GPT-4o Mini", description: "Fast and cheap" },
66
+ { id: "gpt-4-turbo", name: "GPT-4 Turbo", description: "Previous best" },
67
+ { id: "gpt-3.5-turbo", name: "GPT-3.5 Turbo", description: "Legacy, very cheap" }
68
+ ]
69
+ }
70
+ ];
71
+ function ConfigUI() {
72
+ const { exit } = useApp();
73
+ const config = getConfig();
74
+ const [step, setStep] = useState("provider");
75
+ const [providerIndex, setProviderIndex] = useState(() => {
76
+ const idx = PROVIDERS.findIndex((p) => p.id === config.provider);
77
+ return idx >= 0 ? idx : 0;
78
+ });
79
+ const [modelIndex, setModelIndex] = useState(0);
80
+ const [apiKeyInput, setApiKeyInput] = useState("");
81
+ const [selectedProvider, setSelectedProvider] = useState(null);
82
+ const [ollamaStatus, setOllamaStatus] = useState("checking");
83
+ const [ollamaMessage, setOllamaMessage] = useState("");
84
+ useInput((input, key) => {
85
+ if (key.escape) {
86
+ exit();
87
+ return;
88
+ }
89
+ if (step === "provider") {
90
+ if (key.upArrow) {
91
+ setProviderIndex((prev) => prev > 0 ? prev - 1 : PROVIDERS.length - 1);
92
+ } else if (key.downArrow) {
93
+ setProviderIndex((prev) => prev < PROVIDERS.length - 1 ? prev + 1 : 0);
94
+ } else if (key.return) {
95
+ const provider = PROVIDERS[providerIndex];
96
+ setSelectedProvider(provider);
97
+ setProvider(provider.id);
98
+ const recommendedIdx = provider.models.findIndex((m) => m.recommended);
99
+ setModelIndex(recommendedIdx >= 0 ? recommendedIdx : 0);
100
+ if (provider.needsApiKey) {
101
+ const apiKeyProvider = provider.id;
102
+ if (!config.apiKeys[apiKeyProvider]) {
103
+ setStep("apiKey");
104
+ } else {
105
+ setStep("model");
106
+ }
107
+ } else {
108
+ setStep("model");
109
+ }
110
+ }
111
+ } else if (step === "model" && selectedProvider) {
112
+ if (key.upArrow) {
113
+ setModelIndex((prev) => prev > 0 ? prev - 1 : selectedProvider.models.length - 1);
114
+ } else if (key.downArrow) {
115
+ setModelIndex((prev) => prev < selectedProvider.models.length - 1 ? prev + 1 : 0);
116
+ } else if (key.return) {
117
+ const model = selectedProvider.models[modelIndex];
118
+ setModel(model.id);
119
+ if (selectedProvider.id === "ollama") {
120
+ setStep("ollamaCheck");
121
+ } else {
122
+ setStep("done");
123
+ setTimeout(() => exit(), 2e3);
124
+ }
125
+ } else if (key.leftArrow || input === "b") {
126
+ setStep("provider");
127
+ }
128
+ }
129
+ });
130
+ const handleApiKeySubmit = (value) => {
131
+ if (value.trim() && selectedProvider) {
132
+ setApiKey(selectedProvider.id, value.trim());
133
+ setStep("model");
134
+ }
135
+ };
136
+ useEffect(() => {
137
+ if (step !== "ollamaCheck" || !selectedProvider) return;
138
+ const modelId = selectedProvider.models[modelIndex].id;
139
+ async function checkAndRunOllama() {
140
+ try {
141
+ setOllamaStatus("checking");
142
+ setOllamaMessage("Checking Ollama...");
143
+ try {
144
+ await execAsync("ollama list", { timeout: 5e3 });
145
+ } catch {
146
+ setOllamaStatus("error");
147
+ setOllamaMessage("Ollama not found. Install from https://ollama.ai");
148
+ setTimeout(() => exit(), 3e3);
149
+ return;
150
+ }
151
+ const { stdout } = await execAsync("ollama list");
152
+ const modelName = modelId.split(":")[0];
153
+ const hasModel = stdout.toLowerCase().includes(modelName.toLowerCase());
154
+ if (!hasModel) {
155
+ setOllamaStatus("pulling");
156
+ setOllamaMessage(`Downloading ${modelId}... (this may take a few minutes)`);
157
+ await execAsync(`ollama pull ${modelId}`, { timeout: 6e5 });
158
+ }
159
+ setOllamaStatus("running");
160
+ setOllamaMessage(`Starting ${modelId}...`);
161
+ await execAsync(`ollama run ${modelId} "Hello" --nowordwrap`, { timeout: 12e4 });
162
+ setOllamaStatus("ready");
163
+ setOllamaMessage(`${modelId} is ready!`);
164
+ setStep("done");
165
+ setTimeout(() => exit(), 2e3);
166
+ } catch (err) {
167
+ setOllamaStatus("error");
168
+ setOllamaMessage(`Error: ${err instanceof Error ? err.message : "Unknown error"}`);
169
+ setTimeout(() => exit(), 3e3);
170
+ }
171
+ }
172
+ checkAndRunOllama();
173
+ }, [step, selectedProvider, modelIndex, exit]);
174
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
175
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "C-napse Configuration" }) }),
176
+ step === "provider" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
177
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Select AI Provider:" }),
178
+ /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: "(Use arrows, Enter to select, Esc to cancel)" }),
179
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: PROVIDERS.map((p, i) => {
180
+ const isSelected = i === providerIndex;
181
+ const isCurrent = p.id === config.provider;
182
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
183
+ /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "white", children: [
184
+ isSelected ? "\u276F " : " ",
185
+ /* @__PURE__ */ jsx(Text, { bold: isSelected, children: p.name }),
186
+ isCurrent && /* @__PURE__ */ jsx(Text, { color: "green", children: " (current)" }),
187
+ p.needsApiKey && p.id !== "ollama" && config.apiKeys[p.id] && /* @__PURE__ */ jsx(Text, { color: "yellow", children: " (key saved)" })
188
+ ] }),
189
+ isSelected && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
190
+ " ",
191
+ p.description
192
+ ] })
193
+ ] }, p.id);
194
+ }) })
195
+ ] }),
196
+ step === "apiKey" && selectedProvider && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
197
+ /* @__PURE__ */ jsxs(Text, { children: [
198
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" }),
199
+ " Provider: ",
200
+ /* @__PURE__ */ jsx(Text, { bold: true, children: selectedProvider.name })
201
+ ] }),
202
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
203
+ /* @__PURE__ */ jsxs(Text, { bold: true, children: [
204
+ "Enter your ",
205
+ selectedProvider.name,
206
+ " API key:"
207
+ ] }),
208
+ /* @__PURE__ */ jsxs(Text, { color: "gray", dimColor: true, children: [
209
+ selectedProvider.id === "openrouter" && "Get key at: https://openrouter.ai/keys",
210
+ selectedProvider.id === "anthropic" && "Get key at: https://console.anthropic.com",
211
+ selectedProvider.id === "openai" && "Get key at: https://platform.openai.com/api-keys"
212
+ ] }),
213
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
214
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u276F " }),
215
+ /* @__PURE__ */ jsx(
216
+ TextInput,
217
+ {
218
+ value: apiKeyInput,
219
+ onChange: setApiKeyInput,
220
+ onSubmit: handleApiKeySubmit,
221
+ mask: "*"
222
+ }
223
+ )
224
+ ] })
225
+ ] })
226
+ ] }),
227
+ step === "model" && selectedProvider && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
228
+ /* @__PURE__ */ jsxs(Text, { children: [
229
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" }),
230
+ " Provider: ",
231
+ /* @__PURE__ */ jsx(Text, { bold: true, children: selectedProvider.name })
232
+ ] }),
233
+ selectedProvider.needsApiKey && /* @__PURE__ */ jsxs(Text, { children: [
234
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" }),
235
+ " API Key: ",
236
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "configured" })
237
+ ] }),
238
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
239
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Select Model:" }),
240
+ /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: "(Arrows to navigate, Enter to select, B to go back)" }),
241
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: selectedProvider.models.map((model, i) => {
242
+ const isSelected = i === modelIndex;
243
+ const isCurrent = model.id === config.model && selectedProvider.id === config.provider;
244
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
245
+ /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "white", children: [
246
+ isSelected ? "\u276F " : " ",
247
+ /* @__PURE__ */ jsx(Text, { bold: isSelected, children: model.name }),
248
+ model.recommended && /* @__PURE__ */ jsx(Text, { color: "yellow", children: " *" }),
249
+ isCurrent && /* @__PURE__ */ jsx(Text, { color: "green", children: " (current)" })
250
+ ] }),
251
+ isSelected && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
252
+ " ",
253
+ model.description
254
+ ] })
255
+ ] }, model.id);
256
+ }) }),
257
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: "* = Recommended for C-napse" }) })
258
+ ] })
259
+ ] }),
260
+ step === "ollamaCheck" && selectedProvider && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
261
+ /* @__PURE__ */ jsxs(Text, { children: [
262
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" }),
263
+ " Provider: ",
264
+ /* @__PURE__ */ jsx(Text, { bold: true, children: selectedProvider.name })
265
+ ] }),
266
+ /* @__PURE__ */ jsxs(Text, { children: [
267
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" }),
268
+ " Model: ",
269
+ /* @__PURE__ */ jsx(Text, { bold: true, children: selectedProvider.models[modelIndex]?.name })
270
+ ] }),
271
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: ollamaStatus === "error" ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
272
+ "\u2717 ",
273
+ ollamaMessage
274
+ ] }) : ollamaStatus === "ready" ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
275
+ "\u2713 ",
276
+ ollamaMessage
277
+ ] }) : /* @__PURE__ */ jsxs(Text, { children: [
278
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
279
+ " ",
280
+ ollamaMessage
281
+ ] }) })
282
+ ] }),
283
+ step === "done" && selectedProvider && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
284
+ /* @__PURE__ */ jsxs(Text, { children: [
285
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" }),
286
+ " Provider: ",
287
+ /* @__PURE__ */ jsx(Text, { bold: true, children: selectedProvider.name })
288
+ ] }),
289
+ selectedProvider.needsApiKey && /* @__PURE__ */ jsxs(Text, { children: [
290
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" }),
291
+ " API Key: ",
292
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "configured" })
293
+ ] }),
294
+ /* @__PURE__ */ jsxs(Text, { children: [
295
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" }),
296
+ " Model: ",
297
+ /* @__PURE__ */ jsx(Text, { bold: true, children: selectedProvider.models[modelIndex]?.name })
298
+ ] }),
299
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "green", bold: true, children: "Configuration saved! Run `cnapse` to start." }) })
300
+ ] }),
301
+ /* @__PURE__ */ jsx(Box, { marginTop: 2, children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: "Press Esc to cancel" }) })
302
+ ] });
303
+ }
304
+ export {
305
+ ConfigUI
306
+ };