blyuecode 0.1.11 → 0.1.12
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/core/localCheck.js +14 -0
- package/dist/ui/Settings.js +28 -22
- package/package.json +1 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export async function detectOllama(url = 'http://localhost:11434') {
|
|
2
|
+
try {
|
|
3
|
+
const res = await fetch(`${url}/api/tags`, {
|
|
4
|
+
signal: AbortSignal.timeout(1500),
|
|
5
|
+
});
|
|
6
|
+
if (!res.ok)
|
|
7
|
+
return [];
|
|
8
|
+
const json = await res.json();
|
|
9
|
+
return (json.models || []).map((m) => m.name);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
}
|
package/dist/ui/Settings.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
3
|
import { Box, Text, useInput, useStdout } from 'ink';
|
|
4
4
|
import { KeyInput } from './KeyInput.js';
|
|
5
5
|
import { getConfig, saveConfig, applyConfig } from '../core/config.js';
|
|
6
|
+
import { detectOllama } from '../core/localCheck.js';
|
|
6
7
|
import { theme } from './theme.js';
|
|
7
8
|
export const Settings = ({ onClose, onModelChange }) => {
|
|
8
9
|
const { stdout } = useStdout();
|
|
@@ -12,6 +13,15 @@ export const Settings = ({ onClose, onModelChange }) => {
|
|
|
12
13
|
const [cfg, setCfg] = useState(() => getConfig());
|
|
13
14
|
const [draft, setDraft] = useState('');
|
|
14
15
|
const [testResult, setTestResult] = useState(null);
|
|
16
|
+
const [ollamaModels, setOllamaModels] = useState([]);
|
|
17
|
+
const [ollamaChecked, setOllamaChecked] = useState(false);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
(async () => {
|
|
20
|
+
const models = await detectOllama();
|
|
21
|
+
setOllamaModels(models);
|
|
22
|
+
setOllamaChecked(true);
|
|
23
|
+
})();
|
|
24
|
+
}, []);
|
|
15
25
|
const persist = (next) => {
|
|
16
26
|
saveConfig(next);
|
|
17
27
|
applyConfig(next);
|
|
@@ -28,7 +38,6 @@ export const Settings = ({ onClose, onModelChange }) => {
|
|
|
28
38
|
: 'not configured',
|
|
29
39
|
action: () => setScreen('n8n'),
|
|
30
40
|
},
|
|
31
|
-
{ label: 'Reset all config', hint: 'delete ~/.blyue/.env', action: () => setScreen('reset') },
|
|
32
41
|
{ label: 'Close', hint: 'esc', action: onClose },
|
|
33
42
|
];
|
|
34
43
|
// ---- Main menu navigation ----
|
|
@@ -42,7 +51,7 @@ export const Settings = ({ onClose, onModelChange }) => {
|
|
|
42
51
|
if (key.return)
|
|
43
52
|
mainItems[cursor].action();
|
|
44
53
|
}, { isActive: screen === 'main' });
|
|
45
|
-
// ---- n8n submenu
|
|
54
|
+
// ---- n8n submenu ----
|
|
46
55
|
const n8nItems = [
|
|
47
56
|
{ label: 'URL', hint: cfg.n8nUrl || 'not set', action: () => { setDraft(cfg.n8nUrl || ''); setScreen('n8n-url'); } },
|
|
48
57
|
{ label: 'API key', hint: cfg.n8nApiKey ? '•••' + cfg.n8nApiKey.slice(-4) : 'not set', action: () => { setDraft(cfg.n8nApiKey || ''); setScreen('n8n-key'); } },
|
|
@@ -77,18 +86,23 @@ export const Settings = ({ onClose, onModelChange }) => {
|
|
|
77
86
|
if (key.return)
|
|
78
87
|
n8nItems[n8nCursor].action();
|
|
79
88
|
}, { isActive: screen === 'n8n' });
|
|
89
|
+
// ---- n8n test ----
|
|
90
|
+
useInput(() => setScreen('n8n'), { isActive: screen === 'n8n-test' });
|
|
80
91
|
// ---- Frame helpers ----
|
|
81
92
|
const inner = width - 4;
|
|
82
93
|
const top = (label) => (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '╭─ ' }), _jsx(Text, { color: theme.accent, bold: true, children: label }), _jsx(Text, { color: theme.muted, children: ' ' + '─'.repeat(Math.max(1, inner - label.length - 3)) + '╮' })] }));
|
|
83
94
|
const bot = (_jsx(Text, { color: theme.muted, children: '╰' + '─'.repeat(inner) + '╯' }));
|
|
84
95
|
const row = (content) => (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), content] }));
|
|
85
|
-
// ----
|
|
96
|
+
// ---- Main screen ----
|
|
86
97
|
if (screen === 'main') {
|
|
87
|
-
|
|
98
|
+
const baseUrlIsLocal = (cfg.baseUrl || '').includes('localhost') ||
|
|
99
|
+
(cfg.baseUrl || '').includes('127.0.0.1');
|
|
100
|
+
return (_jsxs(Box, { flexDirection: "column", children: [top('settings'), ollamaChecked && ollamaModels.length > 0 && (row(_jsxs(Text, { children: [_jsx(Text, { color: theme.success, children: "\u25CF " }), _jsx(Text, { color: theme.muted, children: "Ollama detected \u00B7 " }), _jsxs(Text, { color: theme.text, children: [ollamaModels.length, " model(s)"] })] }))), ollamaChecked && ollamaModels.length === 0 && baseUrlIsLocal && (row(_jsxs(Text, { children: [_jsx(Text, { color: theme.error, children: "\u25CF " }), _jsx(Text, { color: theme.muted, children: "Ollama not running at localhost:11434" })] }))), mainItems.map((it, i) => {
|
|
88
101
|
const active = i === cursor;
|
|
89
102
|
return (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsx(Text, { color: active ? theme.accent : theme.muted, bold: active, children: active ? '❯ ' : ' ' }), _jsx(Text, { color: active ? theme.text : theme.muted, bold: active, children: it.label.padEnd(20) }), _jsx(Text, { color: theme.muted, children: it.hint })] }, it.label));
|
|
90
103
|
}), bot, _jsx(Text, { color: theme.muted, children: "\u2191\u2193 move \u00B7 \u21B5 select \u00B7 esc close" })] }));
|
|
91
104
|
}
|
|
105
|
+
// ---- API key / Base URL editors ----
|
|
92
106
|
if (screen === 'apikey' || screen === 'baseurl') {
|
|
93
107
|
const isKey = screen === 'apikey';
|
|
94
108
|
const title = isKey ? 'api key' : 'base url';
|
|
@@ -97,21 +111,26 @@ export const Settings = ({ onClose, onModelChange }) => {
|
|
|
97
111
|
const next = { ...cfg, [isKey ? 'apiKey' : 'baseUrl']: v };
|
|
98
112
|
persist(next);
|
|
99
113
|
};
|
|
100
|
-
return (_jsxs(Box, { flexDirection: "column", children: [top(title), row(_jsx(Text, { color: theme.muted, children: isKey ? 'Paste your new API key' : 'e.g.
|
|
114
|
+
return (_jsxs(Box, { flexDirection: "column", children: [top(title), row(_jsx(Text, { color: theme.muted, children: isKey ? 'Paste your new API key' : 'e.g. http://localhost:11434/v1' })), row(_jsxs(Box, { children: [_jsx(Text, { color: theme.accent, bold: true, children: '❯ ' }), _jsx(KeyInput, { value: value, onChange: setValue, placeholder: isKey ? 'sk-...' : 'https://...', masked: isKey, onSubmit: () => setScreen('main') })] })), bot, _jsx(Text, { color: theme.muted, children: "type \u00B7 backspace \u00B7 \u21B5 save \u00B7 esc back" })] }));
|
|
101
115
|
}
|
|
116
|
+
// ---- Model editor ----
|
|
102
117
|
if (screen === 'model') {
|
|
103
|
-
|
|
104
|
-
return (_jsxs(Box, { flexDirection: "column", children: [top('model'), row(_jsx(Text, { color: theme.muted, children:
|
|
118
|
+
const isLocal = (cfg.baseUrl || '').includes('localhost');
|
|
119
|
+
return (_jsxs(Box, { flexDirection: "column", children: [top('model'), row(_jsx(Text, { color: theme.muted, children: isLocal && ollamaModels.length > 0
|
|
120
|
+
? 'Available: ' + ollamaModels.slice(0, 3).join(', ')
|
|
121
|
+
: 'Type model name' })), row(_jsxs(Box, { children: [_jsx(Text, { color: theme.accent, bold: true, children: '❯ ' }), _jsx(KeyInput, { value: cfg.model, onChange: (v) => persist({ ...cfg, model: v }), placeholder: "qwen2.5-coder:1.5b", onSubmit: () => {
|
|
105
122
|
onModelChange?.(cfg.model);
|
|
106
123
|
setScreen('main');
|
|
107
124
|
} })] })), bot, _jsx(Text, { color: theme.muted, children: "type \u00B7 \u21B5 save \u00B7 esc back" })] }));
|
|
108
125
|
}
|
|
126
|
+
// ---- n8n submenu ----
|
|
109
127
|
if (screen === 'n8n') {
|
|
110
128
|
return (_jsxs(Box, { flexDirection: "column", children: [top('n8n integration'), n8nItems.map((it, i) => {
|
|
111
129
|
const active = i === n8nCursor;
|
|
112
130
|
return (_jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: '│ ' }), _jsx(Text, { color: active ? theme.accent : theme.muted, bold: active, children: active ? '❯ ' : ' ' }), _jsx(Text, { color: active ? theme.text : theme.muted, bold: active, children: it.label.padEnd(20) }), _jsx(Text, { color: theme.muted, children: it.hint })] }, it.label));
|
|
113
131
|
}), bot, _jsx(Text, { color: theme.muted, children: "\u2191\u2193 move \u00B7 \u21B5 select \u00B7 esc back" })] }));
|
|
114
132
|
}
|
|
133
|
+
// ---- n8n URL / key editors ----
|
|
115
134
|
if (screen === 'n8n-url' || screen === 'n8n-key') {
|
|
116
135
|
const isKey = screen === 'n8n-key';
|
|
117
136
|
const title = isKey ? 'n8n api key' : 'n8n url';
|
|
@@ -122,22 +141,9 @@ export const Settings = ({ onClose, onModelChange }) => {
|
|
|
122
141
|
};
|
|
123
142
|
return (_jsxs(Box, { flexDirection: "column", children: [top(title), row(_jsx(Text, { color: theme.muted, children: isKey ? 'Settings → n8n API → create key' : 'e.g. http://localhost:5678' })), row(_jsxs(Box, { children: [_jsx(Text, { color: theme.accent, bold: true, children: '❯ ' }), _jsx(KeyInput, { value: value, onChange: setValue, placeholder: isKey ? 'n8n_api_...' : 'http://localhost:5678', masked: isKey, onSubmit: () => setScreen('n8n') })] })), bot, _jsx(Text, { color: theme.muted, children: "type \u00B7 backspace \u00B7 \u21B5 save \u00B7 esc back" })] }));
|
|
124
143
|
}
|
|
144
|
+
// ---- n8n test result ----
|
|
125
145
|
if (screen === 'n8n-test') {
|
|
126
|
-
useInput(() => setScreen('n8n'), { isActive: screen === 'n8n-test' });
|
|
127
146
|
return (_jsxs(Box, { flexDirection: "column", children: [top('n8n test'), row(_jsx(Text, { color: testResult?.startsWith('✓') ? theme.success : theme.muted, children: testResult || 'testing…' })), bot, _jsx(Text, { color: theme.muted, children: "press any key to go back" })] }));
|
|
128
147
|
}
|
|
129
|
-
if (screen === 'reset') {
|
|
130
|
-
return (_jsxs(Box, { flexDirection: "column", children: [top('reset config'), row(_jsx(Text, { color: theme.error, children: "Delete ~/.blyue/.env and reconfigure?" })), row(_jsx(Text, { color: theme.muted, children: "Press Y to confirm, N to cancel." })), bot] }));
|
|
131
|
-
}
|
|
132
|
-
return null;
|
|
133
|
-
};
|
|
134
|
-
// Handle Y/N for reset outside component main body
|
|
135
|
-
export const SettingsResetHandler = ({ onConfirm, onCancel, }) => {
|
|
136
|
-
useInput((input) => {
|
|
137
|
-
if (input === 'y' || input === 'Y')
|
|
138
|
-
onConfirm();
|
|
139
|
-
if (input === 'n' || input === 'N')
|
|
140
|
-
onCancel();
|
|
141
|
-
});
|
|
142
148
|
return null;
|
|
143
149
|
};
|