snow-ai 0.2.24 → 0.2.26
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/api/chat.d.ts +0 -8
- package/dist/api/chat.js +1 -144
- package/dist/api/responses.d.ts +0 -11
- package/dist/api/responses.js +1 -189
- package/dist/api/systemPrompt.d.ts +1 -1
- package/dist/api/systemPrompt.js +90 -295
- package/dist/app.d.ts +2 -1
- package/dist/app.js +11 -13
- package/dist/cli.js +16 -3
- package/dist/hooks/useClipboard.js +4 -4
- package/dist/hooks/useGlobalNavigation.d.ts +1 -1
- package/dist/hooks/useKeyboardInput.d.ts +1 -0
- package/dist/hooks/useKeyboardInput.js +8 -4
- package/dist/hooks/useTerminalFocus.d.ts +5 -0
- package/dist/hooks/useTerminalFocus.js +22 -2
- package/dist/mcp/aceCodeSearch.d.ts +58 -4
- package/dist/mcp/aceCodeSearch.js +563 -20
- package/dist/mcp/filesystem.d.ts +59 -10
- package/dist/mcp/filesystem.js +431 -124
- package/dist/mcp/ideDiagnostics.d.ts +36 -0
- package/dist/mcp/ideDiagnostics.js +92 -0
- package/dist/ui/components/ChatInput.js +6 -3
- package/dist/ui/pages/ChatScreen.d.ts +4 -2
- package/dist/ui/pages/ChatScreen.js +31 -2
- package/dist/ui/pages/ConfigProfileScreen.d.ts +7 -0
- package/dist/ui/pages/ConfigProfileScreen.js +300 -0
- package/dist/ui/pages/{ApiConfigScreen.d.ts → ConfigScreen.d.ts} +1 -1
- package/dist/ui/pages/ConfigScreen.js +748 -0
- package/dist/ui/pages/WelcomeScreen.js +7 -18
- package/dist/utils/apiConfig.d.ts +0 -2
- package/dist/utils/apiConfig.js +12 -0
- package/dist/utils/configManager.d.ts +45 -0
- package/dist/utils/configManager.js +274 -0
- package/dist/utils/contextCompressor.js +355 -49
- package/dist/utils/escapeHandler.d.ts +79 -0
- package/dist/utils/escapeHandler.js +153 -0
- package/dist/utils/incrementalSnapshot.js +2 -1
- package/dist/utils/mcpToolsManager.js +44 -0
- package/dist/utils/retryUtils.js +6 -0
- package/dist/utils/textBuffer.js +13 -15
- package/dist/utils/vscodeConnection.js +26 -11
- package/dist/utils/workspaceSnapshot.js +2 -1
- package/package.json +2 -1
- package/dist/ui/pages/ApiConfigScreen.js +0 -161
- package/dist/ui/pages/ModelConfigScreen.d.ts +0 -8
- package/dist/ui/pages/ModelConfigScreen.js +0 -504
|
@@ -0,0 +1,748 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import Gradient from 'ink-gradient';
|
|
4
|
+
import { Select, Alert, Spinner } from '@inkjs/ui';
|
|
5
|
+
import TextInput from 'ink-text-input';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { getOpenAiConfig, updateOpenAiConfig, validateApiConfig, } from '../../utils/apiConfig.js';
|
|
8
|
+
import { fetchAvailableModels, filterModels, } from '../../api/models.js';
|
|
9
|
+
import { getActiveProfileName, getAllProfiles, switchProfile, createProfile, deleteProfile, saveProfile, } from '../../utils/configManager.js';
|
|
10
|
+
const focusEventTokenRegex = /(?:\x1b)?\[[0-9;]*[IO]/g;
|
|
11
|
+
const isFocusEventInput = (value) => {
|
|
12
|
+
if (!value) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (value === '\x1b[I' ||
|
|
16
|
+
value === '\x1b[O' ||
|
|
17
|
+
value === '[I' ||
|
|
18
|
+
value === '[O') {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
const trimmed = value.trim();
|
|
22
|
+
if (!trimmed) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const tokens = trimmed.match(focusEventTokenRegex);
|
|
26
|
+
if (!tokens) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const normalized = trimmed.replace(/\s+/g, '');
|
|
30
|
+
const tokensCombined = tokens.join('');
|
|
31
|
+
return tokensCombined === normalized;
|
|
32
|
+
};
|
|
33
|
+
const stripFocusArtifacts = (value) => {
|
|
34
|
+
if (!value) {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
return value
|
|
38
|
+
.replace(/\x1b\[[0-9;]*[IO]/g, '')
|
|
39
|
+
.replace(/\[[0-9;]*[IO]/g, '')
|
|
40
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
|
|
41
|
+
};
|
|
42
|
+
export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
43
|
+
// Profile management
|
|
44
|
+
const [profiles, setProfiles] = useState([]);
|
|
45
|
+
const [activeProfile, setActiveProfile] = useState('');
|
|
46
|
+
const [profileMode, setProfileMode] = useState('normal');
|
|
47
|
+
const [newProfileName, setNewProfileName] = useState('');
|
|
48
|
+
// API settings
|
|
49
|
+
const [baseUrl, setBaseUrl] = useState('');
|
|
50
|
+
const [apiKey, setApiKey] = useState('');
|
|
51
|
+
const [requestMethod, setRequestMethod] = useState('chat');
|
|
52
|
+
const [anthropicBeta, setAnthropicBeta] = useState(false);
|
|
53
|
+
// Model settings
|
|
54
|
+
const [advancedModel, setAdvancedModel] = useState('');
|
|
55
|
+
const [basicModel, setBasicModel] = useState('');
|
|
56
|
+
const [maxContextTokens, setMaxContextTokens] = useState(4000);
|
|
57
|
+
const [maxTokens, setMaxTokens] = useState(4096);
|
|
58
|
+
const [compactModelName, setCompactModelName] = useState('');
|
|
59
|
+
// UI state
|
|
60
|
+
const [currentField, setCurrentField] = useState('profile');
|
|
61
|
+
const [errors, setErrors] = useState([]);
|
|
62
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
63
|
+
const [models, setModels] = useState([]);
|
|
64
|
+
const [loading, setLoading] = useState(false);
|
|
65
|
+
const [loadError, setLoadError] = useState('');
|
|
66
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
67
|
+
const [manualInputMode, setManualInputMode] = useState(false);
|
|
68
|
+
const [manualInputValue, setManualInputValue] = useState('');
|
|
69
|
+
const requestMethodOptions = [
|
|
70
|
+
{
|
|
71
|
+
label: 'Chat Completions - Modern chat API (GPT-4, GPT-3.5-turbo)',
|
|
72
|
+
value: 'chat',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
label: 'Responses - New responses API (2025, with built-in tools)',
|
|
76
|
+
value: 'responses',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
label: 'Gemini - Google Gemini API',
|
|
80
|
+
value: 'gemini',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
label: 'Anthropic - Claude API',
|
|
84
|
+
value: 'anthropic',
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
loadProfilesAndConfig();
|
|
89
|
+
}, []);
|
|
90
|
+
const loadProfilesAndConfig = () => {
|
|
91
|
+
// Load profiles
|
|
92
|
+
const loadedProfiles = getAllProfiles();
|
|
93
|
+
setProfiles(loadedProfiles);
|
|
94
|
+
// Load current config
|
|
95
|
+
const config = getOpenAiConfig();
|
|
96
|
+
setBaseUrl(config.baseUrl);
|
|
97
|
+
setApiKey(config.apiKey);
|
|
98
|
+
setRequestMethod(config.requestMethod || 'chat');
|
|
99
|
+
setAnthropicBeta(config.anthropicBeta || false);
|
|
100
|
+
setAdvancedModel(config.advancedModel || '');
|
|
101
|
+
setBasicModel(config.basicModel || '');
|
|
102
|
+
setMaxContextTokens(config.maxContextTokens || 4000);
|
|
103
|
+
setMaxTokens(config.maxTokens || 4096);
|
|
104
|
+
setCompactModelName(config.compactModel?.modelName || '');
|
|
105
|
+
setActiveProfile(getActiveProfileName());
|
|
106
|
+
};
|
|
107
|
+
const loadModels = async () => {
|
|
108
|
+
setLoading(true);
|
|
109
|
+
setLoadError('');
|
|
110
|
+
// Temporarily save current config to use the latest baseUrl/apiKey
|
|
111
|
+
const tempConfig = {
|
|
112
|
+
baseUrl,
|
|
113
|
+
apiKey,
|
|
114
|
+
requestMethod,
|
|
115
|
+
};
|
|
116
|
+
updateOpenAiConfig(tempConfig);
|
|
117
|
+
try {
|
|
118
|
+
const fetchedModels = await fetchAvailableModels();
|
|
119
|
+
setModels(fetchedModels);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
|
|
123
|
+
setLoadError(errorMessage);
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
setLoading(false);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const getCurrentOptions = () => {
|
|
131
|
+
const filteredModels = filterModels(models, searchTerm);
|
|
132
|
+
const modelOptions = filteredModels.map(model => ({
|
|
133
|
+
label: model.id,
|
|
134
|
+
value: model.id,
|
|
135
|
+
}));
|
|
136
|
+
return [
|
|
137
|
+
{ label: 'Manual Input (Enter model name)', value: '__MANUAL_INPUT__' },
|
|
138
|
+
...modelOptions,
|
|
139
|
+
];
|
|
140
|
+
};
|
|
141
|
+
const getCurrentValue = () => {
|
|
142
|
+
if (currentField === 'profile')
|
|
143
|
+
return activeProfile;
|
|
144
|
+
if (currentField === 'baseUrl')
|
|
145
|
+
return baseUrl;
|
|
146
|
+
if (currentField === 'apiKey')
|
|
147
|
+
return apiKey;
|
|
148
|
+
if (currentField === 'advancedModel')
|
|
149
|
+
return advancedModel;
|
|
150
|
+
if (currentField === 'basicModel')
|
|
151
|
+
return basicModel;
|
|
152
|
+
if (currentField === 'maxContextTokens')
|
|
153
|
+
return maxContextTokens.toString();
|
|
154
|
+
if (currentField === 'maxTokens')
|
|
155
|
+
return maxTokens.toString();
|
|
156
|
+
if (currentField === 'compactModelName')
|
|
157
|
+
return compactModelName;
|
|
158
|
+
return '';
|
|
159
|
+
};
|
|
160
|
+
const handleProfileChange = (value) => {
|
|
161
|
+
if (value === '__CREATE_NEW__') {
|
|
162
|
+
setProfileMode('creating');
|
|
163
|
+
setNewProfileName('');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (value === '__DELETE__') {
|
|
167
|
+
if (activeProfile === 'default') {
|
|
168
|
+
setErrors(['Cannot delete the default profile']);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
setProfileMode('deleting');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// Switch profile
|
|
175
|
+
try {
|
|
176
|
+
switchProfile(value);
|
|
177
|
+
loadProfilesAndConfig();
|
|
178
|
+
setIsEditing(false);
|
|
179
|
+
setErrors([]);
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
setErrors([
|
|
183
|
+
err instanceof Error ? err.message : 'Failed to switch profile',
|
|
184
|
+
]);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const handleCreateProfile = () => {
|
|
188
|
+
const cleaned = stripFocusArtifacts(newProfileName).trim();
|
|
189
|
+
if (!cleaned) {
|
|
190
|
+
setErrors(['Profile name cannot be empty']);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
// Create new profile with current config
|
|
195
|
+
const currentConfig = {
|
|
196
|
+
snowcfg: {
|
|
197
|
+
baseUrl,
|
|
198
|
+
apiKey,
|
|
199
|
+
requestMethod,
|
|
200
|
+
anthropicBeta,
|
|
201
|
+
advancedModel,
|
|
202
|
+
basicModel,
|
|
203
|
+
maxContextTokens,
|
|
204
|
+
maxTokens,
|
|
205
|
+
compactModel: compactModelName ? { modelName: compactModelName } : undefined,
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
createProfile(cleaned, currentConfig);
|
|
209
|
+
switchProfile(cleaned);
|
|
210
|
+
loadProfilesAndConfig();
|
|
211
|
+
setProfileMode('normal');
|
|
212
|
+
setNewProfileName('');
|
|
213
|
+
setIsEditing(false);
|
|
214
|
+
setErrors([]);
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
setErrors([
|
|
218
|
+
err instanceof Error ? err.message : 'Failed to create profile',
|
|
219
|
+
]);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const handleDeleteProfile = () => {
|
|
223
|
+
try {
|
|
224
|
+
deleteProfile(activeProfile);
|
|
225
|
+
loadProfilesAndConfig();
|
|
226
|
+
setProfileMode('normal');
|
|
227
|
+
setIsEditing(false);
|
|
228
|
+
setErrors([]);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
setErrors([
|
|
232
|
+
err instanceof Error ? err.message : 'Failed to delete profile',
|
|
233
|
+
]);
|
|
234
|
+
setProfileMode('normal');
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
const handleModelChange = (value) => {
|
|
238
|
+
if (value === '__MANUAL_INPUT__') {
|
|
239
|
+
setManualInputMode(true);
|
|
240
|
+
setManualInputValue('');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (currentField === 'advancedModel') {
|
|
244
|
+
setAdvancedModel(value);
|
|
245
|
+
}
|
|
246
|
+
else if (currentField === 'basicModel') {
|
|
247
|
+
setBasicModel(value);
|
|
248
|
+
}
|
|
249
|
+
else if (currentField === 'compactModelName') {
|
|
250
|
+
setCompactModelName(value);
|
|
251
|
+
}
|
|
252
|
+
setIsEditing(false);
|
|
253
|
+
setSearchTerm('');
|
|
254
|
+
};
|
|
255
|
+
const saveConfiguration = () => {
|
|
256
|
+
const validationErrors = validateApiConfig({
|
|
257
|
+
baseUrl,
|
|
258
|
+
apiKey,
|
|
259
|
+
requestMethod,
|
|
260
|
+
});
|
|
261
|
+
if (validationErrors.length === 0) {
|
|
262
|
+
const config = {
|
|
263
|
+
baseUrl,
|
|
264
|
+
apiKey,
|
|
265
|
+
requestMethod,
|
|
266
|
+
anthropicBeta,
|
|
267
|
+
advancedModel,
|
|
268
|
+
basicModel,
|
|
269
|
+
maxContextTokens,
|
|
270
|
+
maxTokens,
|
|
271
|
+
};
|
|
272
|
+
// Only save compactModel if modelName is provided (uses same baseUrl/apiKey)
|
|
273
|
+
if (compactModelName) {
|
|
274
|
+
config.compactModel = {
|
|
275
|
+
modelName: compactModelName,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// Save to main config
|
|
279
|
+
updateOpenAiConfig(config);
|
|
280
|
+
// Also save to the current profile
|
|
281
|
+
try {
|
|
282
|
+
const fullConfig = {
|
|
283
|
+
snowcfg: {
|
|
284
|
+
baseUrl,
|
|
285
|
+
apiKey,
|
|
286
|
+
requestMethod,
|
|
287
|
+
anthropicBeta,
|
|
288
|
+
advancedModel,
|
|
289
|
+
basicModel,
|
|
290
|
+
maxContextTokens,
|
|
291
|
+
maxTokens,
|
|
292
|
+
compactModel: compactModelName
|
|
293
|
+
? { modelName: compactModelName }
|
|
294
|
+
: undefined,
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
saveProfile(activeProfile, fullConfig);
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
console.error('Failed to save profile:', err);
|
|
301
|
+
}
|
|
302
|
+
setErrors([]);
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
setErrors(validationErrors);
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
useInput((rawInput, key) => {
|
|
311
|
+
const input = stripFocusArtifacts(rawInput);
|
|
312
|
+
if (!input && isFocusEventInput(rawInput)) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (isFocusEventInput(rawInput)) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
// Handle profile creation mode
|
|
319
|
+
if (profileMode === 'creating') {
|
|
320
|
+
if (key.return) {
|
|
321
|
+
handleCreateProfile();
|
|
322
|
+
}
|
|
323
|
+
else if (key.escape) {
|
|
324
|
+
setProfileMode('normal');
|
|
325
|
+
setNewProfileName('');
|
|
326
|
+
setErrors([]);
|
|
327
|
+
}
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
// Handle profile deletion confirmation
|
|
331
|
+
if (profileMode === 'deleting') {
|
|
332
|
+
if (input === 'y' || input === 'Y') {
|
|
333
|
+
handleDeleteProfile();
|
|
334
|
+
}
|
|
335
|
+
else if (input === 'n' || input === 'N' || key.escape) {
|
|
336
|
+
setProfileMode('normal');
|
|
337
|
+
setErrors([]);
|
|
338
|
+
}
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
// Handle loading state
|
|
342
|
+
if (loading) {
|
|
343
|
+
if (key.escape) {
|
|
344
|
+
setLoading(false);
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
// Handle manual input mode
|
|
349
|
+
if (manualInputMode) {
|
|
350
|
+
if (key.return) {
|
|
351
|
+
const cleaned = stripFocusArtifacts(manualInputValue).trim();
|
|
352
|
+
if (cleaned) {
|
|
353
|
+
if (currentField === 'advancedModel') {
|
|
354
|
+
setAdvancedModel(cleaned);
|
|
355
|
+
}
|
|
356
|
+
else if (currentField === 'basicModel') {
|
|
357
|
+
setBasicModel(cleaned);
|
|
358
|
+
}
|
|
359
|
+
else if (currentField === 'compactModelName') {
|
|
360
|
+
setCompactModelName(cleaned);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
setManualInputMode(false);
|
|
364
|
+
setManualInputValue('');
|
|
365
|
+
setIsEditing(false);
|
|
366
|
+
setSearchTerm('');
|
|
367
|
+
}
|
|
368
|
+
else if (key.escape) {
|
|
369
|
+
setManualInputMode(false);
|
|
370
|
+
setManualInputValue('');
|
|
371
|
+
}
|
|
372
|
+
else if (key.backspace || key.delete) {
|
|
373
|
+
setManualInputValue(prev => prev.slice(0, -1));
|
|
374
|
+
}
|
|
375
|
+
else if (input && input.match(/[a-zA-Z0-9-_./:]/)) {
|
|
376
|
+
setManualInputValue(prev => prev + stripFocusArtifacts(input));
|
|
377
|
+
}
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
// Allow Escape key to exit Select component
|
|
381
|
+
if (isEditing &&
|
|
382
|
+
(currentField === 'profile' ||
|
|
383
|
+
currentField === 'requestMethod' ||
|
|
384
|
+
currentField === 'advancedModel' ||
|
|
385
|
+
currentField === 'basicModel' ||
|
|
386
|
+
currentField === 'compactModelName') &&
|
|
387
|
+
key.escape) {
|
|
388
|
+
setIsEditing(false);
|
|
389
|
+
setSearchTerm('');
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
// Handle editing mode
|
|
393
|
+
if (isEditing) {
|
|
394
|
+
// For baseUrl and apiKey, TextInput component handles all input, just handle Return to exit
|
|
395
|
+
if (currentField === 'baseUrl' || currentField === 'apiKey') {
|
|
396
|
+
if (key.return) {
|
|
397
|
+
setIsEditing(false);
|
|
398
|
+
}
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
// Handle numeric input for token fields
|
|
402
|
+
if (currentField === 'maxContextTokens' || currentField === 'maxTokens') {
|
|
403
|
+
if (input && input.match(/[0-9]/)) {
|
|
404
|
+
const currentValue = currentField === 'maxContextTokens' ? maxContextTokens : maxTokens;
|
|
405
|
+
const newValue = parseInt(currentValue.toString() + input, 10);
|
|
406
|
+
if (!isNaN(newValue)) {
|
|
407
|
+
if (currentField === 'maxContextTokens') {
|
|
408
|
+
setMaxContextTokens(newValue);
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
setMaxTokens(newValue);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
else if (key.backspace || key.delete) {
|
|
416
|
+
const currentValue = currentField === 'maxContextTokens' ? maxContextTokens : maxTokens;
|
|
417
|
+
const currentStr = currentValue.toString();
|
|
418
|
+
const newStr = currentStr.slice(0, -1);
|
|
419
|
+
const newValue = parseInt(newStr, 10);
|
|
420
|
+
if (currentField === 'maxContextTokens') {
|
|
421
|
+
setMaxContextTokens(!isNaN(newValue) ? newValue : 0);
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
setMaxTokens(!isNaN(newValue) ? newValue : 0);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
else if (key.return) {
|
|
428
|
+
const minValue = currentField === 'maxContextTokens' ? 4000 : 100;
|
|
429
|
+
const currentValue = currentField === 'maxContextTokens' ? maxContextTokens : maxTokens;
|
|
430
|
+
const finalValue = currentValue < minValue ? minValue : currentValue;
|
|
431
|
+
if (currentField === 'maxContextTokens') {
|
|
432
|
+
setMaxContextTokens(finalValue);
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
setMaxTokens(finalValue);
|
|
436
|
+
}
|
|
437
|
+
setIsEditing(false);
|
|
438
|
+
}
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
// Allow typing to filter for model selection
|
|
442
|
+
if (input && input.match(/[a-zA-Z0-9-_.]/)) {
|
|
443
|
+
setSearchTerm(prev => prev + input);
|
|
444
|
+
}
|
|
445
|
+
else if (key.backspace || key.delete) {
|
|
446
|
+
setSearchTerm(prev => prev.slice(0, -1));
|
|
447
|
+
}
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
// Handle save/exit globally
|
|
451
|
+
if (input === 's' && (key.ctrl || key.meta)) {
|
|
452
|
+
if (saveConfiguration()) {
|
|
453
|
+
onSave();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
else if (key.escape) {
|
|
457
|
+
saveConfiguration();
|
|
458
|
+
onBack();
|
|
459
|
+
}
|
|
460
|
+
else if (key.return) {
|
|
461
|
+
if (isEditing) {
|
|
462
|
+
setIsEditing(false);
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
// Enter edit mode
|
|
466
|
+
if (currentField === 'anthropicBeta') {
|
|
467
|
+
setAnthropicBeta(!anthropicBeta);
|
|
468
|
+
}
|
|
469
|
+
else if (currentField === 'maxContextTokens' ||
|
|
470
|
+
currentField === 'maxTokens') {
|
|
471
|
+
setIsEditing(true);
|
|
472
|
+
}
|
|
473
|
+
else if (currentField === 'advancedModel' ||
|
|
474
|
+
currentField === 'basicModel' ||
|
|
475
|
+
currentField === 'compactModelName') {
|
|
476
|
+
// Load models for model fields
|
|
477
|
+
setLoadError(''); // Clear previous error
|
|
478
|
+
loadModels()
|
|
479
|
+
.then(() => {
|
|
480
|
+
setIsEditing(true);
|
|
481
|
+
})
|
|
482
|
+
.catch(() => {
|
|
483
|
+
// Error is already set in loadModels, just enter manual input mode
|
|
484
|
+
setManualInputMode(true);
|
|
485
|
+
setManualInputValue(getCurrentValue());
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
setIsEditing(true);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
else if (input === 'm' && !isEditing) {
|
|
494
|
+
// Shortcut: press 'm' for manual input mode
|
|
495
|
+
if (currentField === 'advancedModel' ||
|
|
496
|
+
currentField === 'basicModel' ||
|
|
497
|
+
currentField === 'compactModelName') {
|
|
498
|
+
setManualInputMode(true);
|
|
499
|
+
setManualInputValue(getCurrentValue());
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
else if (!isEditing && key.upArrow) {
|
|
503
|
+
const fields = [
|
|
504
|
+
'profile',
|
|
505
|
+
'baseUrl',
|
|
506
|
+
'apiKey',
|
|
507
|
+
'requestMethod',
|
|
508
|
+
'anthropicBeta',
|
|
509
|
+
'advancedModel',
|
|
510
|
+
'basicModel',
|
|
511
|
+
'compactModelName',
|
|
512
|
+
'maxContextTokens',
|
|
513
|
+
'maxTokens',
|
|
514
|
+
];
|
|
515
|
+
const currentIndex = fields.indexOf(currentField);
|
|
516
|
+
if (currentIndex > 0) {
|
|
517
|
+
setCurrentField(fields[currentIndex - 1]);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
else if (!isEditing && key.downArrow) {
|
|
521
|
+
const fields = [
|
|
522
|
+
'profile',
|
|
523
|
+
'baseUrl',
|
|
524
|
+
'apiKey',
|
|
525
|
+
'requestMethod',
|
|
526
|
+
'anthropicBeta',
|
|
527
|
+
'advancedModel',
|
|
528
|
+
'basicModel',
|
|
529
|
+
'compactModelName',
|
|
530
|
+
'maxContextTokens',
|
|
531
|
+
'maxTokens',
|
|
532
|
+
];
|
|
533
|
+
const currentIndex = fields.indexOf(currentField);
|
|
534
|
+
if (currentIndex < fields.length - 1) {
|
|
535
|
+
setCurrentField(fields[currentIndex + 1]);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
// Render profile creation mode
|
|
540
|
+
if (profileMode === 'creating') {
|
|
541
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
542
|
+
!inlineMode && (React.createElement(Box, { marginBottom: 1, borderStyle: "double", borderColor: 'cyan', paddingX: 2 },
|
|
543
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
544
|
+
React.createElement(Gradient, { name: "rainbow" }, "Create New Profile"),
|
|
545
|
+
React.createElement(Text, { color: "gray", dimColor: true }, "Enter a name for the new configuration profile")))),
|
|
546
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
547
|
+
React.createElement(Text, { color: "cyan" }, "Profile Name:"),
|
|
548
|
+
React.createElement(Box, { marginLeft: 2 },
|
|
549
|
+
React.createElement(TextInput, { value: newProfileName, onChange: value => setNewProfileName(stripFocusArtifacts(value)), placeholder: "e.g., work, personal, test" }))),
|
|
550
|
+
errors.length > 0 && (React.createElement(Box, { marginTop: 1 },
|
|
551
|
+
React.createElement(Text, { color: "red" }, errors[0]))),
|
|
552
|
+
React.createElement(Box, { marginTop: 1 },
|
|
553
|
+
React.createElement(Alert, { variant: "info" }, "Press Enter to create, Esc to cancel"))));
|
|
554
|
+
}
|
|
555
|
+
// Render profile deletion confirmation
|
|
556
|
+
if (profileMode === 'deleting') {
|
|
557
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
558
|
+
!inlineMode && (React.createElement(Box, { marginBottom: 1, borderStyle: "double", borderColor: 'cyan', paddingX: 2 },
|
|
559
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
560
|
+
React.createElement(Gradient, { name: "rainbow" }, "Delete Profile"),
|
|
561
|
+
React.createElement(Text, { color: "gray", dimColor: true }, "Confirm profile deletion")))),
|
|
562
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
563
|
+
React.createElement(Text, { color: "yellow" },
|
|
564
|
+
"Are you sure you want to delete the profile \"",
|
|
565
|
+
activeProfile,
|
|
566
|
+
"\"?"),
|
|
567
|
+
React.createElement(Text, { color: "gray", dimColor: true }, "This action cannot be undone. You will be switched to the default profile.")),
|
|
568
|
+
errors.length > 0 && (React.createElement(Box, { marginTop: 1 },
|
|
569
|
+
React.createElement(Text, { color: "red" }, errors[0]))),
|
|
570
|
+
React.createElement(Box, { marginTop: 1 },
|
|
571
|
+
React.createElement(Alert, { variant: "warning" }, "Press Y to confirm, N or Esc to cancel"))));
|
|
572
|
+
}
|
|
573
|
+
if (loading) {
|
|
574
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
575
|
+
!inlineMode && (React.createElement(Box, { marginBottom: 1, borderStyle: "double", borderColor: 'cyan', paddingX: 2 },
|
|
576
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
577
|
+
React.createElement(Gradient, { name: "rainbow" }, "API & Model Configuration"),
|
|
578
|
+
React.createElement(Text, { color: "gray", dimColor: true }, "Loading available models...")))),
|
|
579
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
580
|
+
React.createElement(Box, null,
|
|
581
|
+
React.createElement(Spinner, { type: "dots" }),
|
|
582
|
+
React.createElement(Text, { color: "cyan" }, " Fetching models from API...")),
|
|
583
|
+
React.createElement(Box, { marginLeft: 2 },
|
|
584
|
+
React.createElement(Text, { color: "gray", dimColor: true }, "This may take a few seconds depending on your network connection"))),
|
|
585
|
+
React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
586
|
+
React.createElement(Alert, { variant: "info" }, "Press Esc to cancel and return to configuration"))));
|
|
587
|
+
}
|
|
588
|
+
if (manualInputMode) {
|
|
589
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
590
|
+
!inlineMode && (React.createElement(Box, { marginBottom: 1, borderStyle: "double", borderColor: 'cyan', paddingX: 2 },
|
|
591
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
592
|
+
React.createElement(Gradient, { name: "rainbow" }, "Manual Input Model"),
|
|
593
|
+
React.createElement(Text, { color: "gray", dimColor: true }, "Enter model name manually")))),
|
|
594
|
+
loadError && (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
595
|
+
React.createElement(Text, { color: "yellow" }, "\u26A0 Failed to load models from API"),
|
|
596
|
+
React.createElement(Text, { color: "gray", dimColor: true }, loadError))),
|
|
597
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
598
|
+
React.createElement(Text, { color: "cyan" },
|
|
599
|
+
currentField === 'advancedModel' && 'Advanced Model',
|
|
600
|
+
currentField === 'basicModel' && 'Basic Model',
|
|
601
|
+
currentField === 'compactModelName' && 'Compact Model',
|
|
602
|
+
":"),
|
|
603
|
+
React.createElement(Box, { marginLeft: 2 },
|
|
604
|
+
React.createElement(Text, { color: "green" },
|
|
605
|
+
`> ${manualInputValue}`,
|
|
606
|
+
React.createElement(Text, { color: "white" }, "_")))),
|
|
607
|
+
React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
608
|
+
React.createElement(Alert, { variant: "info" }, "Press Enter to confirm, Esc to cancel"))));
|
|
609
|
+
}
|
|
610
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
611
|
+
!inlineMode && (React.createElement(Box, { marginBottom: 1, borderStyle: "double", borderColor: 'cyan', paddingX: 2 },
|
|
612
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
613
|
+
React.createElement(Gradient, { name: "rainbow" }, "API & Model Configuration"),
|
|
614
|
+
React.createElement(Text, { color: "gray", dimColor: true }, "Configure your API settings and AI models"),
|
|
615
|
+
activeProfile && (React.createElement(Text, { color: "cyan", dimColor: true },
|
|
616
|
+
"Active Profile: ",
|
|
617
|
+
activeProfile))))),
|
|
618
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
619
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
620
|
+
React.createElement(Text, { color: currentField === 'profile' ? 'green' : 'white' },
|
|
621
|
+
currentField === 'profile' ? '❯ ' : ' ',
|
|
622
|
+
"Profile:"),
|
|
623
|
+
currentField === 'profile' && isEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
624
|
+
React.createElement(Select, { options: [
|
|
625
|
+
...profiles.map(p => ({
|
|
626
|
+
label: `${p.displayName}${p.isActive ? ' (Active)' : ''}`,
|
|
627
|
+
value: p.name,
|
|
628
|
+
})),
|
|
629
|
+
{
|
|
630
|
+
label: chalk.green('+ New Profile'),
|
|
631
|
+
value: '__CREATE_NEW__',
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
label: chalk.red('🆇 Delete Profile'),
|
|
635
|
+
value: '__DELETE__',
|
|
636
|
+
},
|
|
637
|
+
], defaultValue: activeProfile, onChange: handleProfileChange }))),
|
|
638
|
+
(!isEditing || currentField !== 'profile') && (React.createElement(Box, { marginLeft: 3 },
|
|
639
|
+
React.createElement(Text, { color: "gray" }, profiles.find(p => p.name === activeProfile)?.displayName ||
|
|
640
|
+
activeProfile)))),
|
|
641
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
642
|
+
React.createElement(Text, { color: currentField === 'baseUrl' ? 'green' : 'white' },
|
|
643
|
+
currentField === 'baseUrl' ? '❯ ' : ' ',
|
|
644
|
+
"Base URL:"),
|
|
645
|
+
currentField === 'baseUrl' && isEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
646
|
+
React.createElement(TextInput, { value: baseUrl, onChange: value => setBaseUrl(stripFocusArtifacts(value)), placeholder: "https://api.openai.com/v1" }))),
|
|
647
|
+
(!isEditing || currentField !== 'baseUrl') && (React.createElement(Box, { marginLeft: 3 },
|
|
648
|
+
React.createElement(Text, { color: "gray" }, baseUrl || 'Not set')))),
|
|
649
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
650
|
+
React.createElement(Text, { color: currentField === 'apiKey' ? 'green' : 'white' },
|
|
651
|
+
currentField === 'apiKey' ? '❯ ' : ' ',
|
|
652
|
+
"API Key:"),
|
|
653
|
+
currentField === 'apiKey' && isEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
654
|
+
React.createElement(TextInput, { value: apiKey, onChange: value => setApiKey(stripFocusArtifacts(value)), placeholder: "sk-...", mask: "*" }))),
|
|
655
|
+
(!isEditing || currentField !== 'apiKey') && (React.createElement(Box, { marginLeft: 3 },
|
|
656
|
+
React.createElement(Text, { color: "gray" }, apiKey ? '*'.repeat(Math.min(apiKey.length, 20)) : 'Not set')))),
|
|
657
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
658
|
+
React.createElement(Text, { color: currentField === 'requestMethod' ? 'green' : 'white' },
|
|
659
|
+
currentField === 'requestMethod' ? '❯ ' : ' ',
|
|
660
|
+
"Request Method:"),
|
|
661
|
+
currentField === 'requestMethod' && isEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
662
|
+
React.createElement(Select, { options: requestMethodOptions, defaultValue: requestMethod, onChange: value => {
|
|
663
|
+
setRequestMethod(value);
|
|
664
|
+
setIsEditing(false);
|
|
665
|
+
} }))),
|
|
666
|
+
(!isEditing || currentField !== 'requestMethod') && (React.createElement(Box, { marginLeft: 3 },
|
|
667
|
+
React.createElement(Text, { color: "gray" }, requestMethodOptions.find(opt => opt.value === requestMethod)
|
|
668
|
+
?.label || 'Not set')))),
|
|
669
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
670
|
+
React.createElement(Text, { color: currentField === 'anthropicBeta' ? 'green' : 'white' },
|
|
671
|
+
currentField === 'anthropicBeta' ? '❯ ' : ' ',
|
|
672
|
+
"Anthropic Beta:"),
|
|
673
|
+
React.createElement(Box, { marginLeft: 3 },
|
|
674
|
+
React.createElement(Text, { color: "gray" },
|
|
675
|
+
anthropicBeta ? '☑ Enabled' : '☐ Disabled',
|
|
676
|
+
" (Press Enter to toggle)"))),
|
|
677
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
678
|
+
React.createElement(Text, { color: currentField === 'advancedModel' ? 'green' : 'white' },
|
|
679
|
+
currentField === 'advancedModel' ? '❯ ' : ' ',
|
|
680
|
+
"Advanced Model:"),
|
|
681
|
+
currentField === 'advancedModel' && isEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
682
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
683
|
+
searchTerm && React.createElement(Text, { color: "cyan" },
|
|
684
|
+
"Filter: ",
|
|
685
|
+
searchTerm),
|
|
686
|
+
React.createElement(Select, { options: getCurrentOptions(), defaultValue: getCurrentValue(), onChange: handleModelChange })))),
|
|
687
|
+
(!isEditing || currentField !== 'advancedModel') && (React.createElement(Box, { marginLeft: 3 },
|
|
688
|
+
React.createElement(Text, { color: "gray" }, advancedModel || 'Not set')))),
|
|
689
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
690
|
+
React.createElement(Text, { color: currentField === 'basicModel' ? 'green' : 'white' },
|
|
691
|
+
currentField === 'basicModel' ? '❯ ' : ' ',
|
|
692
|
+
"Basic Model:"),
|
|
693
|
+
currentField === 'basicModel' && isEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
694
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
695
|
+
searchTerm && React.createElement(Text, { color: "cyan" },
|
|
696
|
+
"Filter: ",
|
|
697
|
+
searchTerm),
|
|
698
|
+
React.createElement(Select, { options: getCurrentOptions(), defaultValue: getCurrentValue(), onChange: handleModelChange })))),
|
|
699
|
+
(!isEditing || currentField !== 'basicModel') && (React.createElement(Box, { marginLeft: 3 },
|
|
700
|
+
React.createElement(Text, { color: "gray" }, basicModel || 'Not set')))),
|
|
701
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
702
|
+
React.createElement(Text, { color: currentField === 'compactModelName' ? 'green' : 'white' },
|
|
703
|
+
currentField === 'compactModelName' ? '❯ ' : ' ',
|
|
704
|
+
"Compact Model:"),
|
|
705
|
+
currentField === 'compactModelName' && isEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
706
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
707
|
+
searchTerm && React.createElement(Text, { color: "cyan" },
|
|
708
|
+
"Filter: ",
|
|
709
|
+
searchTerm),
|
|
710
|
+
React.createElement(Select, { options: getCurrentOptions(), defaultValue: getCurrentValue(), onChange: handleModelChange })))),
|
|
711
|
+
(!isEditing || currentField !== 'compactModelName') && (React.createElement(Box, { marginLeft: 3 },
|
|
712
|
+
React.createElement(Text, { color: "gray" }, compactModelName || 'Not set')))),
|
|
713
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
714
|
+
React.createElement(Text, { color: currentField === 'maxContextTokens' ? 'green' : 'white' },
|
|
715
|
+
currentField === 'maxContextTokens' ? '❯ ' : ' ',
|
|
716
|
+
"Max Context Tokens:"),
|
|
717
|
+
currentField === 'maxContextTokens' && isEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
718
|
+
React.createElement(Text, { color: "cyan" },
|
|
719
|
+
"Enter value: ",
|
|
720
|
+
maxContextTokens))),
|
|
721
|
+
(!isEditing || currentField !== 'maxContextTokens') && (React.createElement(Box, { marginLeft: 3 },
|
|
722
|
+
React.createElement(Text, { color: "gray" }, maxContextTokens)))),
|
|
723
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
724
|
+
React.createElement(Text, { color: currentField === 'maxTokens' ? 'green' : 'white' },
|
|
725
|
+
currentField === 'maxTokens' ? '❯ ' : ' ',
|
|
726
|
+
"Max Tokens:"),
|
|
727
|
+
currentField === 'maxTokens' && isEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
728
|
+
React.createElement(Text, { color: "cyan" },
|
|
729
|
+
"Enter value: ",
|
|
730
|
+
maxTokens))),
|
|
731
|
+
(!isEditing || currentField !== 'maxTokens') && (React.createElement(Box, { marginLeft: 3 },
|
|
732
|
+
React.createElement(Text, { color: "gray" }, maxTokens))))),
|
|
733
|
+
errors.length > 0 && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
734
|
+
React.createElement(Text, { color: "red", bold: true }, "Errors:"),
|
|
735
|
+
errors.map((error, index) => (React.createElement(Text, { key: index, color: "red" },
|
|
736
|
+
"\u2022 ",
|
|
737
|
+
error))))),
|
|
738
|
+
React.createElement(Box, { flexDirection: "column", marginTop: 1 }, isEditing ? (React.createElement(React.Fragment, null,
|
|
739
|
+
React.createElement(Alert, { variant: "info" },
|
|
740
|
+
"Editing mode:",
|
|
741
|
+
' ',
|
|
742
|
+
currentField === 'advancedModel' ||
|
|
743
|
+
currentField === 'basicModel' ||
|
|
744
|
+
currentField === 'compactModelName'
|
|
745
|
+
? 'Type to filter, ↑↓ to select, Enter to confirm'
|
|
746
|
+
: 'Press Enter to save and exit editing'))) : (React.createElement(React.Fragment, null,
|
|
747
|
+
React.createElement(Alert, { variant: "info" }, "Use \u2191\u2193 to navigate, Enter to edit, M for manual input, Ctrl+S or Esc to save"))))));
|
|
748
|
+
}
|