codemaxxing 0.4.11 → 0.4.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/index.js +105 -5
- package/package.json +1 -1
- package/src/index.tsx +140 -4
package/dist/index.js
CHANGED
|
@@ -152,6 +152,10 @@ function App() {
|
|
|
152
152
|
const [ollamaDeleteConfirm, setOllamaDeleteConfirm] = useState(null);
|
|
153
153
|
const [ollamaPulling, setOllamaPulling] = useState(null);
|
|
154
154
|
const [ollamaExitPrompt, setOllamaExitPrompt] = useState(false);
|
|
155
|
+
const [ollamaDeletePicker, setOllamaDeletePicker] = useState(null);
|
|
156
|
+
const [ollamaDeletePickerIndex, setOllamaDeletePickerIndex] = useState(0);
|
|
157
|
+
const [ollamaPullPicker, setOllamaPullPicker] = useState(false);
|
|
158
|
+
const [ollamaPullPickerIndex, setOllamaPullPickerIndex] = useState(0);
|
|
155
159
|
const [wizardScreen, setWizardScreen] = useState(null);
|
|
156
160
|
const [wizardIndex, setWizardIndex] = useState(0);
|
|
157
161
|
const [wizardHardware, setWizardHardware] = useState(null);
|
|
@@ -341,8 +345,7 @@ function App() {
|
|
|
341
345
|
// Commands that need args (like /commit, /model) — fill input instead of executing
|
|
342
346
|
if (selected.cmd === "/commit" || selected.cmd === "/model" || selected.cmd === "/session delete" ||
|
|
343
347
|
selected.cmd === "/skills install" || selected.cmd === "/skills remove" || selected.cmd === "/skills search" ||
|
|
344
|
-
selected.cmd === "/skills on" || selected.cmd === "/skills off" || selected.cmd === "/architect"
|
|
345
|
-
selected.cmd === "/ollama pull" || selected.cmd === "/ollama delete") {
|
|
348
|
+
selected.cmd === "/skills on" || selected.cmd === "/skills off" || selected.cmd === "/architect") {
|
|
346
349
|
setInput(selected.cmd + " ");
|
|
347
350
|
setCmdIndex(0);
|
|
348
351
|
setInputKey((k) => k + 1);
|
|
@@ -678,10 +681,17 @@ function App() {
|
|
|
678
681
|
addMsg(result.ok ? "info" : "error", result.ok ? `\u2705 ${result.message}` : `\u274C ${result.message}`);
|
|
679
682
|
return;
|
|
680
683
|
}
|
|
684
|
+
if (trimmed === "/ollama pull") {
|
|
685
|
+
// No model specified — show picker
|
|
686
|
+
setOllamaPullPicker(true);
|
|
687
|
+
setOllamaPullPickerIndex(0);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
681
690
|
if (trimmed.startsWith("/ollama pull ")) {
|
|
682
691
|
const modelId = trimmed.replace("/ollama pull ", "").trim();
|
|
683
692
|
if (!modelId) {
|
|
684
|
-
|
|
693
|
+
setOllamaPullPicker(true);
|
|
694
|
+
setOllamaPullPickerIndex(0);
|
|
685
695
|
return;
|
|
686
696
|
}
|
|
687
697
|
if (!isOllamaInstalled()) {
|
|
@@ -719,10 +729,27 @@ function App() {
|
|
|
719
729
|
}
|
|
720
730
|
return;
|
|
721
731
|
}
|
|
732
|
+
if (trimmed === "/ollama delete") {
|
|
733
|
+
// No model specified — show picker of installed models
|
|
734
|
+
const models = await listInstalledModelsDetailed();
|
|
735
|
+
if (models.length === 0) {
|
|
736
|
+
addMsg("info", "No models installed.");
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
setOllamaDeletePicker({ models: models.map(m => ({ name: m.name, size: m.size })) });
|
|
740
|
+
setOllamaDeletePickerIndex(0);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
722
743
|
if (trimmed.startsWith("/ollama delete ")) {
|
|
723
744
|
const modelId = trimmed.replace("/ollama delete ", "").trim();
|
|
724
745
|
if (!modelId) {
|
|
725
|
-
|
|
746
|
+
const models = await listInstalledModelsDetailed();
|
|
747
|
+
if (models.length === 0) {
|
|
748
|
+
addMsg("info", "No models installed.");
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
setOllamaDeletePicker({ models: models.map(m => ({ name: m.name, size: m.size })) });
|
|
752
|
+
setOllamaDeletePickerIndex(0);
|
|
726
753
|
return;
|
|
727
754
|
}
|
|
728
755
|
// Look up size for confirmation
|
|
@@ -1295,6 +1322,71 @@ function App() {
|
|
|
1295
1322
|
}
|
|
1296
1323
|
return;
|
|
1297
1324
|
}
|
|
1325
|
+
// ── Ollama delete picker ──
|
|
1326
|
+
if (ollamaDeletePicker) {
|
|
1327
|
+
if (key.upArrow) {
|
|
1328
|
+
setOllamaDeletePickerIndex((prev) => (prev - 1 + ollamaDeletePicker.models.length) % ollamaDeletePicker.models.length);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
if (key.downArrow) {
|
|
1332
|
+
setOllamaDeletePickerIndex((prev) => (prev + 1) % ollamaDeletePicker.models.length);
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
if (key.escape) {
|
|
1336
|
+
setOllamaDeletePicker(null);
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
if (key.return) {
|
|
1340
|
+
const selected = ollamaDeletePicker.models[ollamaDeletePickerIndex];
|
|
1341
|
+
if (selected) {
|
|
1342
|
+
setOllamaDeletePicker(null);
|
|
1343
|
+
setOllamaDeleteConfirm({ model: selected.name, size: selected.size });
|
|
1344
|
+
}
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
// ── Ollama pull picker ──
|
|
1350
|
+
if (ollamaPullPicker) {
|
|
1351
|
+
const pullModels = [
|
|
1352
|
+
{ id: "qwen2.5-coder:7b", name: "Qwen 2.5 Coder 7B", size: "5 GB", desc: "Best balance of speed & quality" },
|
|
1353
|
+
{ id: "qwen2.5-coder:14b", name: "Qwen 2.5 Coder 14B", size: "9 GB", desc: "Higher quality, needs 16GB+ RAM" },
|
|
1354
|
+
{ id: "qwen2.5-coder:3b", name: "Qwen 2.5 Coder 3B", size: "2 GB", desc: "Fast but limited quality" },
|
|
1355
|
+
{ id: "qwen2.5-coder:32b", name: "Qwen 2.5 Coder 32B", size: "20 GB", desc: "Premium quality, needs 48GB+" },
|
|
1356
|
+
{ id: "deepseek-coder-v2:16b", name: "DeepSeek Coder V2", size: "9 GB", desc: "Strong alternative" },
|
|
1357
|
+
{ id: "codellama:7b", name: "CodeLlama 7B", size: "4 GB", desc: "Meta's coding model" },
|
|
1358
|
+
{ id: "starcoder2:7b", name: "StarCoder2 7B", size: "4 GB", desc: "Code completion focused" },
|
|
1359
|
+
];
|
|
1360
|
+
if (key.upArrow) {
|
|
1361
|
+
setOllamaPullPickerIndex((prev) => (prev - 1 + pullModels.length) % pullModels.length);
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
if (key.downArrow) {
|
|
1365
|
+
setOllamaPullPickerIndex((prev) => (prev + 1) % pullModels.length);
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
if (key.escape) {
|
|
1369
|
+
setOllamaPullPicker(false);
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
if (key.return) {
|
|
1373
|
+
const selected = pullModels[ollamaPullPickerIndex];
|
|
1374
|
+
if (selected) {
|
|
1375
|
+
setOllamaPullPicker(false);
|
|
1376
|
+
// Trigger the pull
|
|
1377
|
+
setInput(`/ollama pull ${selected.id}`);
|
|
1378
|
+
setInputKey((k) => k + 1);
|
|
1379
|
+
// Submit it
|
|
1380
|
+
setTimeout(() => {
|
|
1381
|
+
const submitInput = `/ollama pull ${selected.id}`;
|
|
1382
|
+
setInput("");
|
|
1383
|
+
handleSubmit(submitInput);
|
|
1384
|
+
}, 50);
|
|
1385
|
+
}
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1298
1390
|
// ── Ollama delete confirmation ──
|
|
1299
1391
|
if (ollamaDeleteConfirm) {
|
|
1300
1392
|
if (inputChar === "y" || inputChar === "Y") {
|
|
@@ -1820,7 +1912,15 @@ function App() {
|
|
|
1820
1912
|
})(), skillsPicker === "remove" && (() => {
|
|
1821
1913
|
const installed = listInstalledSkills();
|
|
1822
1914
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.error, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.error, children: "Remove a skill:" }), installed.map((s, i) => (_jsxs(Text, { children: [i === skillsPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsxs(Text, { color: i === skillsPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: [s.name, " \u2014 ", s.description] })] }, s.name))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter remove · Esc back" })] }));
|
|
1823
|
-
})(), themePicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Choose a theme:" }), listThemes().map((key, i) => (_jsxs(Text, { children: [i === themePickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === themePickerIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: key }), _jsxs(Text, { color: theme.colors.muted, children: [" — ", THEMES[key].description] }), key === theme.name.toLowerCase() ? _jsx(Text, { color: theme.colors.muted, children: " (current)" }) : null] }, key))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), sessionPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.secondary, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Resume a session:" }), sessionPicker.map((s, i) => (_jsxs(Text, { children: [i === sessionPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === sessionPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: s.display })] }, s.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), deleteSessionPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.error, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.error, children: "Delete a session:" }), deleteSessionPicker.map((s, i) => (_jsxs(Text, { children: [i === deleteSessionPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === deleteSessionPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: s.display })] }, s.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), deleteSessionConfirm && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.warning, paddingX: 1, marginBottom: 0, children: [_jsxs(Text, { bold: true, color: theme.colors.warning, children: ["Delete session ", deleteSessionConfirm.id, "?"] }), _jsxs(Text, { color: theme.colors.muted, children: [" ", deleteSessionConfirm.display] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.error, bold: true, children: " [y]" }), _jsx(Text, { children: "es " }), _jsx(Text, { color: theme.colors.success, bold: true, children: "[n]" }), _jsx(Text, { children: "o" })] })] })),
|
|
1915
|
+
})(), themePicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Choose a theme:" }), listThemes().map((key, i) => (_jsxs(Text, { children: [i === themePickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === themePickerIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: key }), _jsxs(Text, { color: theme.colors.muted, children: [" — ", THEMES[key].description] }), key === theme.name.toLowerCase() ? _jsx(Text, { color: theme.colors.muted, children: " (current)" }) : null] }, key))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), sessionPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.secondary, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Resume a session:" }), sessionPicker.map((s, i) => (_jsxs(Text, { children: [i === sessionPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === sessionPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: s.display })] }, s.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), deleteSessionPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.error, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.error, children: "Delete a session:" }), deleteSessionPicker.map((s, i) => (_jsxs(Text, { children: [i === deleteSessionPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === deleteSessionPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: s.display })] }, s.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), deleteSessionConfirm && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.warning, paddingX: 1, marginBottom: 0, children: [_jsxs(Text, { bold: true, color: theme.colors.warning, children: ["Delete session ", deleteSessionConfirm.id, "?"] }), _jsxs(Text, { color: theme.colors.muted, children: [" ", deleteSessionConfirm.display] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.error, bold: true, children: " [y]" }), _jsx(Text, { children: "es " }), _jsx(Text, { color: theme.colors.success, bold: true, children: "[n]" }), _jsx(Text, { children: "o" })] })] })), ollamaDeletePicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Delete which model?" }), _jsx(Text, { children: "" }), ollamaDeletePicker.models.map((m, i) => (_jsxs(Text, { children: [" ", i === ollamaDeletePickerIndex ? _jsx(Text, { color: theme.colors.primary, bold: true, children: "▸ " }) : " ", _jsx(Text, { color: i === ollamaDeletePickerIndex ? theme.colors.primary : undefined, children: m.name }), _jsxs(Text, { color: theme.colors.muted, children: [" (", (m.size / (1024 * 1024 * 1024)).toFixed(1), " GB)"] })] }, m.name))), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter to delete · Esc cancel" })] })), ollamaPullPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Download which model?" }), _jsx(Text, { children: "" }), [
|
|
1916
|
+
{ id: "qwen2.5-coder:7b", name: "Qwen 2.5 Coder 7B", size: "5 GB", desc: "Best balance of speed & quality" },
|
|
1917
|
+
{ id: "qwen2.5-coder:14b", name: "Qwen 2.5 Coder 14B", size: "9 GB", desc: "Higher quality, needs 16GB+ RAM" },
|
|
1918
|
+
{ id: "qwen2.5-coder:3b", name: "Qwen 2.5 Coder 3B", size: "2 GB", desc: "Fast but limited quality" },
|
|
1919
|
+
{ id: "qwen2.5-coder:32b", name: "Qwen 2.5 Coder 32B", size: "20 GB", desc: "Premium, needs 48GB+" },
|
|
1920
|
+
{ id: "deepseek-coder-v2:16b", name: "DeepSeek Coder V2", size: "9 GB", desc: "Strong alternative" },
|
|
1921
|
+
{ id: "codellama:7b", name: "CodeLlama 7B", size: "4 GB", desc: "Meta's coding model" },
|
|
1922
|
+
{ id: "starcoder2:7b", name: "StarCoder2 7B", size: "4 GB", desc: "Code completion focused" },
|
|
1923
|
+
].map((m, i) => (_jsxs(Text, { children: [" ", i === ollamaPullPickerIndex ? _jsx(Text, { color: theme.colors.primary, bold: true, children: "▸ " }) : " ", _jsx(Text, { color: i === ollamaPullPickerIndex ? theme.colors.primary : undefined, bold: true, children: m.name }), _jsxs(Text, { color: theme.colors.muted, children: [" · ", m.size, " · ", m.desc] })] }, m.id))), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter to download · Esc cancel" })] })), ollamaDeleteConfirm && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.warning, paddingX: 1, marginBottom: 0, children: [_jsxs(Text, { bold: true, color: theme.colors.warning, children: ["Delete ", ollamaDeleteConfirm.model, " (", (ollamaDeleteConfirm.size / (1024 * 1024 * 1024)).toFixed(1), " GB)?"] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.error, bold: true, children: " [y]" }), _jsx(Text, { children: "es " }), _jsx(Text, { color: theme.colors.success, bold: true, children: "[n]" }), _jsx(Text, { children: "o" })] })] })), ollamaPulling && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsxs(Text, { bold: true, color: theme.colors.secondary, children: [" Downloading ", ollamaPulling.model, "..."] }), ollamaPulling.progress.status === "downloading" || ollamaPulling.progress.percent > 0 ? (_jsxs(Text, { children: [" ", _jsxs(Text, { color: theme.colors.primary, children: ["\u2588".repeat(Math.floor(ollamaPulling.progress.percent / 5)), "\u2591".repeat(20 - Math.floor(ollamaPulling.progress.percent / 5))] }), " ", _jsxs(Text, { bold: true, children: [ollamaPulling.progress.percent, "%"] }), ollamaPulling.progress.completed != null && ollamaPulling.progress.total != null ? (_jsxs(Text, { color: theme.colors.muted, children: [" \u00B7 ", formatBytes(ollamaPulling.progress.completed), " / ", formatBytes(ollamaPulling.progress.total)] })) : null] })) : (_jsxs(Text, { color: theme.colors.muted, children: [" ", ollamaPulling.progress.status, "..."] }))] })), ollamaExitPrompt && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.warning, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.warning, children: "Ollama is still running." }), _jsx(Text, { color: theme.colors.muted, children: "Stop it to free GPU memory?" }), _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.success, bold: true, children: " [y]" }), _jsx(Text, { children: "es " }), _jsx(Text, { color: theme.colors.error, bold: true, children: "[n]" }), _jsx(Text, { children: "o " }), _jsx(Text, { color: theme.colors.primary, bold: true, children: "[a]" }), _jsx(Text, { children: "lways" })] })] })), wizardScreen === "connection" && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "No LLM detected. How do you want to connect?" }), _jsx(Text, { children: "" }), [
|
|
1824
1924
|
{ key: "local", icon: "\uD83D\uDDA5\uFE0F", label: "Set up a local model", desc: "free, runs on your machine" },
|
|
1825
1925
|
{ key: "openrouter", icon: "\uD83C\uDF10", label: "OpenRouter", desc: "200+ cloud models, browser login" },
|
|
1826
1926
|
{ key: "apikey", icon: "\uD83D\uDD11", label: "Enter API key manually", desc: "" },
|
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -187,6 +187,10 @@ function App() {
|
|
|
187
187
|
const [ollamaDeleteConfirm, setOllamaDeleteConfirm] = useState<{ model: string; size: number } | null>(null);
|
|
188
188
|
const [ollamaPulling, setOllamaPulling] = useState<{ model: string; progress: PullProgress } | null>(null);
|
|
189
189
|
const [ollamaExitPrompt, setOllamaExitPrompt] = useState(false);
|
|
190
|
+
const [ollamaDeletePicker, setOllamaDeletePicker] = useState<{ models: { name: string; size: number }[] } | null>(null);
|
|
191
|
+
const [ollamaDeletePickerIndex, setOllamaDeletePickerIndex] = useState(0);
|
|
192
|
+
const [ollamaPullPicker, setOllamaPullPicker] = useState(false);
|
|
193
|
+
const [ollamaPullPickerIndex, setOllamaPullPickerIndex] = useState(0);
|
|
190
194
|
|
|
191
195
|
// ── Setup Wizard State ──
|
|
192
196
|
type WizardScreen = "connection" | "models" | "install-ollama" | "pulling" | null;
|
|
@@ -396,8 +400,7 @@ function App() {
|
|
|
396
400
|
// Commands that need args (like /commit, /model) — fill input instead of executing
|
|
397
401
|
if (selected.cmd === "/commit" || selected.cmd === "/model" || selected.cmd === "/session delete" ||
|
|
398
402
|
selected.cmd === "/skills install" || selected.cmd === "/skills remove" || selected.cmd === "/skills search" ||
|
|
399
|
-
selected.cmd === "/skills on" || selected.cmd === "/skills off" || selected.cmd === "/architect"
|
|
400
|
-
selected.cmd === "/ollama pull" || selected.cmd === "/ollama delete") {
|
|
403
|
+
selected.cmd === "/skills on" || selected.cmd === "/skills off" || selected.cmd === "/architect") {
|
|
401
404
|
setInput(selected.cmd + " ");
|
|
402
405
|
setCmdIndex(0);
|
|
403
406
|
setInputKey((k) => k + 1);
|
|
@@ -722,10 +725,17 @@ function App() {
|
|
|
722
725
|
addMsg(result.ok ? "info" : "error", result.ok ? `\u2705 ${result.message}` : `\u274C ${result.message}`);
|
|
723
726
|
return;
|
|
724
727
|
}
|
|
728
|
+
if (trimmed === "/ollama pull") {
|
|
729
|
+
// No model specified — show picker
|
|
730
|
+
setOllamaPullPicker(true);
|
|
731
|
+
setOllamaPullPickerIndex(0);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
725
734
|
if (trimmed.startsWith("/ollama pull ")) {
|
|
726
735
|
const modelId = trimmed.replace("/ollama pull ", "").trim();
|
|
727
736
|
if (!modelId) {
|
|
728
|
-
|
|
737
|
+
setOllamaPullPicker(true);
|
|
738
|
+
setOllamaPullPickerIndex(0);
|
|
729
739
|
return;
|
|
730
740
|
}
|
|
731
741
|
if (!isOllamaInstalled()) {
|
|
@@ -759,10 +769,27 @@ function App() {
|
|
|
759
769
|
}
|
|
760
770
|
return;
|
|
761
771
|
}
|
|
772
|
+
if (trimmed === "/ollama delete") {
|
|
773
|
+
// No model specified — show picker of installed models
|
|
774
|
+
const models = await listInstalledModelsDetailed();
|
|
775
|
+
if (models.length === 0) {
|
|
776
|
+
addMsg("info", "No models installed.");
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
setOllamaDeletePicker({ models: models.map(m => ({ name: m.name, size: m.size })) });
|
|
780
|
+
setOllamaDeletePickerIndex(0);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
762
783
|
if (trimmed.startsWith("/ollama delete ")) {
|
|
763
784
|
const modelId = trimmed.replace("/ollama delete ", "").trim();
|
|
764
785
|
if (!modelId) {
|
|
765
|
-
|
|
786
|
+
const models = await listInstalledModelsDetailed();
|
|
787
|
+
if (models.length === 0) {
|
|
788
|
+
addMsg("info", "No models installed.");
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
setOllamaDeletePicker({ models: models.map(m => ({ name: m.name, size: m.size })) });
|
|
792
|
+
setOllamaDeletePickerIndex(0);
|
|
766
793
|
return;
|
|
767
794
|
}
|
|
768
795
|
// Look up size for confirmation
|
|
@@ -1317,6 +1344,73 @@ function App() {
|
|
|
1317
1344
|
return;
|
|
1318
1345
|
}
|
|
1319
1346
|
|
|
1347
|
+
// ── Ollama delete picker ──
|
|
1348
|
+
if (ollamaDeletePicker) {
|
|
1349
|
+
if (key.upArrow) {
|
|
1350
|
+
setOllamaDeletePickerIndex((prev) => (prev - 1 + ollamaDeletePicker.models.length) % ollamaDeletePicker.models.length);
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
if (key.downArrow) {
|
|
1354
|
+
setOllamaDeletePickerIndex((prev) => (prev + 1) % ollamaDeletePicker.models.length);
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
if (key.escape) {
|
|
1358
|
+
setOllamaDeletePicker(null);
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
if (key.return) {
|
|
1362
|
+
const selected = ollamaDeletePicker.models[ollamaDeletePickerIndex];
|
|
1363
|
+
if (selected) {
|
|
1364
|
+
setOllamaDeletePicker(null);
|
|
1365
|
+
setOllamaDeleteConfirm({ model: selected.name, size: selected.size });
|
|
1366
|
+
}
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// ── Ollama pull picker ──
|
|
1373
|
+
if (ollamaPullPicker) {
|
|
1374
|
+
const pullModels = [
|
|
1375
|
+
{ id: "qwen2.5-coder:7b", name: "Qwen 2.5 Coder 7B", size: "5 GB", desc: "Best balance of speed & quality" },
|
|
1376
|
+
{ id: "qwen2.5-coder:14b", name: "Qwen 2.5 Coder 14B", size: "9 GB", desc: "Higher quality, needs 16GB+ RAM" },
|
|
1377
|
+
{ id: "qwen2.5-coder:3b", name: "Qwen 2.5 Coder 3B", size: "2 GB", desc: "Fast but limited quality" },
|
|
1378
|
+
{ id: "qwen2.5-coder:32b", name: "Qwen 2.5 Coder 32B", size: "20 GB", desc: "Premium quality, needs 48GB+" },
|
|
1379
|
+
{ id: "deepseek-coder-v2:16b", name: "DeepSeek Coder V2", size: "9 GB", desc: "Strong alternative" },
|
|
1380
|
+
{ id: "codellama:7b", name: "CodeLlama 7B", size: "4 GB", desc: "Meta's coding model" },
|
|
1381
|
+
{ id: "starcoder2:7b", name: "StarCoder2 7B", size: "4 GB", desc: "Code completion focused" },
|
|
1382
|
+
];
|
|
1383
|
+
if (key.upArrow) {
|
|
1384
|
+
setOllamaPullPickerIndex((prev) => (prev - 1 + pullModels.length) % pullModels.length);
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
if (key.downArrow) {
|
|
1388
|
+
setOllamaPullPickerIndex((prev) => (prev + 1) % pullModels.length);
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
if (key.escape) {
|
|
1392
|
+
setOllamaPullPicker(false);
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
if (key.return) {
|
|
1396
|
+
const selected = pullModels[ollamaPullPickerIndex];
|
|
1397
|
+
if (selected) {
|
|
1398
|
+
setOllamaPullPicker(false);
|
|
1399
|
+
// Trigger the pull
|
|
1400
|
+
setInput(`/ollama pull ${selected.id}`);
|
|
1401
|
+
setInputKey((k) => k + 1);
|
|
1402
|
+
// Submit it
|
|
1403
|
+
setTimeout(() => {
|
|
1404
|
+
const submitInput = `/ollama pull ${selected.id}`;
|
|
1405
|
+
setInput("");
|
|
1406
|
+
handleSubmit(submitInput);
|
|
1407
|
+
}, 50);
|
|
1408
|
+
}
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1320
1414
|
// ── Ollama delete confirmation ──
|
|
1321
1415
|
if (ollamaDeleteConfirm) {
|
|
1322
1416
|
if (inputChar === "y" || inputChar === "Y") {
|
|
@@ -2072,6 +2166,48 @@ function App() {
|
|
|
2072
2166
|
</Box>
|
|
2073
2167
|
)}
|
|
2074
2168
|
|
|
2169
|
+
{/* ═══ OLLAMA DELETE PICKER ═══ */}
|
|
2170
|
+
{ollamaDeletePicker && (
|
|
2171
|
+
<Box flexDirection="column" borderStyle="single" borderColor={theme.colors.border} paddingX={1} marginBottom={0}>
|
|
2172
|
+
<Text bold color={theme.colors.secondary}>Delete which model?</Text>
|
|
2173
|
+
<Text>{""}</Text>
|
|
2174
|
+
{ollamaDeletePicker.models.map((m, i) => (
|
|
2175
|
+
<Text key={m.name}>
|
|
2176
|
+
{" "}{i === ollamaDeletePickerIndex ? <Text color={theme.colors.primary} bold>{"▸ "}</Text> : " "}
|
|
2177
|
+
<Text color={i === ollamaDeletePickerIndex ? theme.colors.primary : undefined}>{m.name}</Text>
|
|
2178
|
+
<Text color={theme.colors.muted}>{" ("}{(m.size / (1024 * 1024 * 1024)).toFixed(1)}{" GB)"}</Text>
|
|
2179
|
+
</Text>
|
|
2180
|
+
))}
|
|
2181
|
+
<Text>{""}</Text>
|
|
2182
|
+
<Text dimColor>{" ↑↓ navigate · Enter to delete · Esc cancel"}</Text>
|
|
2183
|
+
</Box>
|
|
2184
|
+
)}
|
|
2185
|
+
|
|
2186
|
+
{/* ═══ OLLAMA PULL PICKER ═══ */}
|
|
2187
|
+
{ollamaPullPicker && (
|
|
2188
|
+
<Box flexDirection="column" borderStyle="single" borderColor={theme.colors.border} paddingX={1} marginBottom={0}>
|
|
2189
|
+
<Text bold color={theme.colors.secondary}>Download which model?</Text>
|
|
2190
|
+
<Text>{""}</Text>
|
|
2191
|
+
{[
|
|
2192
|
+
{ id: "qwen2.5-coder:7b", name: "Qwen 2.5 Coder 7B", size: "5 GB", desc: "Best balance of speed & quality" },
|
|
2193
|
+
{ id: "qwen2.5-coder:14b", name: "Qwen 2.5 Coder 14B", size: "9 GB", desc: "Higher quality, needs 16GB+ RAM" },
|
|
2194
|
+
{ id: "qwen2.5-coder:3b", name: "Qwen 2.5 Coder 3B", size: "2 GB", desc: "Fast but limited quality" },
|
|
2195
|
+
{ id: "qwen2.5-coder:32b", name: "Qwen 2.5 Coder 32B", size: "20 GB", desc: "Premium, needs 48GB+" },
|
|
2196
|
+
{ id: "deepseek-coder-v2:16b", name: "DeepSeek Coder V2", size: "9 GB", desc: "Strong alternative" },
|
|
2197
|
+
{ id: "codellama:7b", name: "CodeLlama 7B", size: "4 GB", desc: "Meta's coding model" },
|
|
2198
|
+
{ id: "starcoder2:7b", name: "StarCoder2 7B", size: "4 GB", desc: "Code completion focused" },
|
|
2199
|
+
].map((m, i) => (
|
|
2200
|
+
<Text key={m.id}>
|
|
2201
|
+
{" "}{i === ollamaPullPickerIndex ? <Text color={theme.colors.primary} bold>{"▸ "}</Text> : " "}
|
|
2202
|
+
<Text color={i === ollamaPullPickerIndex ? theme.colors.primary : undefined} bold>{m.name}</Text>
|
|
2203
|
+
<Text color={theme.colors.muted}>{" · "}{m.size}{" · "}{m.desc}</Text>
|
|
2204
|
+
</Text>
|
|
2205
|
+
))}
|
|
2206
|
+
<Text>{""}</Text>
|
|
2207
|
+
<Text dimColor>{" ↑↓ navigate · Enter to download · Esc cancel"}</Text>
|
|
2208
|
+
</Box>
|
|
2209
|
+
)}
|
|
2210
|
+
|
|
2075
2211
|
{/* ═══ OLLAMA DELETE CONFIRM ═══ */}
|
|
2076
2212
|
{ollamaDeleteConfirm && (
|
|
2077
2213
|
<Box flexDirection="column" borderStyle="single" borderColor={theme.colors.warning} paddingX={1} marginBottom={0}>
|