snow-ai 0.3.31 → 0.3.32
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/agents/codebaseIndexAgent.d.ts +102 -0
- package/dist/agents/codebaseIndexAgent.js +640 -0
- package/dist/api/embedding.d.ts +34 -0
- package/dist/api/embedding.js +74 -0
- package/dist/api/systemPrompt.d.ts +5 -1
- package/dist/api/systemPrompt.js +86 -16
- package/dist/hooks/useConversation.js +1 -1
- package/dist/mcp/aceCodeSearch.d.ts +0 -33
- package/dist/mcp/aceCodeSearch.js +0 -46
- package/dist/mcp/codebaseSearch.d.ts +44 -0
- package/dist/mcp/codebaseSearch.js +146 -0
- package/dist/ui/pages/ChatScreen.js +175 -1
- package/dist/ui/pages/CodeBaseConfigScreen.d.ts +8 -0
- package/dist/ui/pages/CodeBaseConfigScreen.js +323 -0
- package/dist/ui/pages/SubAgentConfigScreen.js +4 -0
- package/dist/ui/pages/WelcomeScreen.js +12 -1
- package/dist/utils/codebaseConfig.d.ts +20 -0
- package/dist/utils/codebaseConfig.js +75 -0
- package/dist/utils/codebaseDatabase.d.ts +102 -0
- package/dist/utils/codebaseDatabase.js +333 -0
- package/dist/utils/commands/home.js +14 -1
- package/dist/utils/mcpToolsManager.js +74 -10
- package/dist/utils/toolDisplayConfig.js +2 -0
- package/package.json +4 -1
|
@@ -32,6 +32,9 @@ import { convertSessionMessagesToUI } from '../../utils/sessionConverter.js';
|
|
|
32
32
|
import { incrementalSnapshotManager } from '../../utils/incrementalSnapshot.js';
|
|
33
33
|
import { formatElapsedTime } from '../../utils/textUtils.js';
|
|
34
34
|
import { shouldAutoCompress, performAutoCompression, } from '../../utils/autoCompress.js';
|
|
35
|
+
import { CodebaseIndexAgent } from '../../agents/codebaseIndexAgent.js';
|
|
36
|
+
import { loadCodebaseConfig } from '../../utils/codebaseConfig.js';
|
|
37
|
+
import { logger } from '../../utils/logger.js';
|
|
35
38
|
// Import commands to register them
|
|
36
39
|
import '../../utils/commands/clear.js';
|
|
37
40
|
import '../../utils/commands/resume.js';
|
|
@@ -85,6 +88,12 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
85
88
|
const { stdout } = useStdout();
|
|
86
89
|
const workingDirectory = process.cwd();
|
|
87
90
|
const isInitialMount = useRef(true);
|
|
91
|
+
// Codebase indexing state
|
|
92
|
+
const [codebaseIndexing, setCodebaseIndexing] = useState(false);
|
|
93
|
+
const [codebaseProgress, setCodebaseProgress] = useState(null);
|
|
94
|
+
const [watcherEnabled, setWatcherEnabled] = useState(false);
|
|
95
|
+
const [fileUpdateNotification, setFileUpdateNotification] = useState(null);
|
|
96
|
+
const codebaseAgentRef = useRef(null);
|
|
88
97
|
// Use custom hooks
|
|
89
98
|
const streamingState = useStreamingState();
|
|
90
99
|
const vscodeState = useVSCodeState();
|
|
@@ -95,6 +104,154 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
95
104
|
useEffect(() => {
|
|
96
105
|
pendingMessagesRef.current = pendingMessages;
|
|
97
106
|
}, [pendingMessages]);
|
|
107
|
+
// Auto-start codebase indexing on mount if enabled
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
const startCodebaseIndexing = async () => {
|
|
110
|
+
try {
|
|
111
|
+
const config = loadCodebaseConfig();
|
|
112
|
+
// Only start if enabled and not already indexing
|
|
113
|
+
if (!config.enabled || codebaseIndexing) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Initialize agent
|
|
117
|
+
const agent = new CodebaseIndexAgent(workingDirectory);
|
|
118
|
+
codebaseAgentRef.current = agent;
|
|
119
|
+
// Check if indexing is needed
|
|
120
|
+
const progress = agent.getProgress();
|
|
121
|
+
// If indexing is already completed, start watcher and return early
|
|
122
|
+
if (progress.status === 'completed' && progress.totalChunks > 0) {
|
|
123
|
+
agent.startWatching(progressData => {
|
|
124
|
+
setCodebaseProgress({
|
|
125
|
+
totalFiles: progressData.totalFiles,
|
|
126
|
+
processedFiles: progressData.processedFiles,
|
|
127
|
+
totalChunks: progressData.totalChunks,
|
|
128
|
+
currentFile: progressData.currentFile,
|
|
129
|
+
status: progressData.status,
|
|
130
|
+
});
|
|
131
|
+
// Handle file update notifications
|
|
132
|
+
if (progressData.totalFiles === 0 && progressData.currentFile) {
|
|
133
|
+
setFileUpdateNotification({
|
|
134
|
+
file: progressData.currentFile,
|
|
135
|
+
timestamp: Date.now(),
|
|
136
|
+
});
|
|
137
|
+
// Clear notification after 3 seconds
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
setFileUpdateNotification(null);
|
|
140
|
+
}, 3000);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
setWatcherEnabled(true);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// If watcher was enabled before but indexing not completed, restore it
|
|
147
|
+
const wasWatcherEnabled = agent.isWatcherEnabled();
|
|
148
|
+
if (wasWatcherEnabled) {
|
|
149
|
+
logger.info('Restoring file watcher from previous session');
|
|
150
|
+
agent.startWatching(progressData => {
|
|
151
|
+
setCodebaseProgress({
|
|
152
|
+
totalFiles: progressData.totalFiles,
|
|
153
|
+
processedFiles: progressData.processedFiles,
|
|
154
|
+
totalChunks: progressData.totalChunks,
|
|
155
|
+
currentFile: progressData.currentFile,
|
|
156
|
+
status: progressData.status,
|
|
157
|
+
});
|
|
158
|
+
// Handle file update notifications
|
|
159
|
+
if (progressData.totalFiles === 0 && progressData.currentFile) {
|
|
160
|
+
setFileUpdateNotification({
|
|
161
|
+
file: progressData.currentFile,
|
|
162
|
+
timestamp: Date.now(),
|
|
163
|
+
});
|
|
164
|
+
// Clear notification after 3 seconds
|
|
165
|
+
setTimeout(() => {
|
|
166
|
+
setFileUpdateNotification(null);
|
|
167
|
+
}, 3000);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
setWatcherEnabled(true);
|
|
171
|
+
}
|
|
172
|
+
// Start or resume indexing in background
|
|
173
|
+
setCodebaseIndexing(true);
|
|
174
|
+
agent.start(progressData => {
|
|
175
|
+
setCodebaseProgress({
|
|
176
|
+
totalFiles: progressData.totalFiles,
|
|
177
|
+
processedFiles: progressData.processedFiles,
|
|
178
|
+
totalChunks: progressData.totalChunks,
|
|
179
|
+
currentFile: progressData.currentFile,
|
|
180
|
+
status: progressData.status,
|
|
181
|
+
});
|
|
182
|
+
// Handle file update notifications (when totalFiles is 0, it's a file update)
|
|
183
|
+
if (progressData.totalFiles === 0 && progressData.currentFile) {
|
|
184
|
+
setFileUpdateNotification({
|
|
185
|
+
file: progressData.currentFile,
|
|
186
|
+
timestamp: Date.now(),
|
|
187
|
+
});
|
|
188
|
+
// Clear notification after 3 seconds
|
|
189
|
+
setTimeout(() => {
|
|
190
|
+
setFileUpdateNotification(null);
|
|
191
|
+
}, 3000);
|
|
192
|
+
}
|
|
193
|
+
// Stop indexing when completed or error
|
|
194
|
+
if (progressData.status === 'completed' ||
|
|
195
|
+
progressData.status === 'error') {
|
|
196
|
+
setCodebaseIndexing(false);
|
|
197
|
+
// Start file watcher after initial indexing is completed
|
|
198
|
+
if (progressData.status === 'completed' && agent) {
|
|
199
|
+
agent.startWatching(watcherProgressData => {
|
|
200
|
+
setCodebaseProgress({
|
|
201
|
+
totalFiles: watcherProgressData.totalFiles,
|
|
202
|
+
processedFiles: watcherProgressData.processedFiles,
|
|
203
|
+
totalChunks: watcherProgressData.totalChunks,
|
|
204
|
+
currentFile: watcherProgressData.currentFile,
|
|
205
|
+
status: watcherProgressData.status,
|
|
206
|
+
});
|
|
207
|
+
// Handle file update notifications
|
|
208
|
+
if (watcherProgressData.totalFiles === 0 &&
|
|
209
|
+
watcherProgressData.currentFile) {
|
|
210
|
+
setFileUpdateNotification({
|
|
211
|
+
file: watcherProgressData.currentFile,
|
|
212
|
+
timestamp: Date.now(),
|
|
213
|
+
});
|
|
214
|
+
// Clear notification after 3 seconds
|
|
215
|
+
setTimeout(() => {
|
|
216
|
+
setFileUpdateNotification(null);
|
|
217
|
+
}, 3000);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
setWatcherEnabled(true);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
console.error('Failed to start codebase indexing:', error);
|
|
227
|
+
setCodebaseIndexing(false);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
startCodebaseIndexing();
|
|
231
|
+
// Cleanup on unmount - just stop indexing, don't close database
|
|
232
|
+
// This allows resuming when returning to chat screen
|
|
233
|
+
return () => {
|
|
234
|
+
if (codebaseAgentRef.current) {
|
|
235
|
+
codebaseAgentRef.current.stop();
|
|
236
|
+
codebaseAgentRef.current.stopWatching();
|
|
237
|
+
setWatcherEnabled(false);
|
|
238
|
+
// Don't call close() - let it resume when returning
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}, []); // Only run once on mount
|
|
242
|
+
// Export stop function for use in commands (like /home)
|
|
243
|
+
useEffect(() => {
|
|
244
|
+
// Store global reference to stop function for /home command
|
|
245
|
+
global.__stopCodebaseIndexing = async () => {
|
|
246
|
+
if (codebaseAgentRef.current) {
|
|
247
|
+
await codebaseAgentRef.current.stop();
|
|
248
|
+
setCodebaseIndexing(false);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
return () => {
|
|
252
|
+
delete global.__stopCodebaseIndexing;
|
|
253
|
+
};
|
|
254
|
+
}, []);
|
|
98
255
|
// Persist yolo mode to localStorage
|
|
99
256
|
useEffect(() => {
|
|
100
257
|
try {
|
|
@@ -1161,7 +1318,24 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
1161
1318
|
` | ${vscodeState.editorContext.activeFile}`,
|
|
1162
1319
|
vscodeState.vscodeConnectionStatus === 'connected' &&
|
|
1163
1320
|
vscodeState.editorContext.selectedText &&
|
|
1164
|
-
` | ${vscodeState.editorContext.selectedText.length} chars selected`)))
|
|
1321
|
+
` | ${vscodeState.editorContext.selectedText.length} chars selected`))),
|
|
1322
|
+
codebaseIndexing && codebaseProgress && (React.createElement(Box, { marginTop: 1, paddingX: 1 },
|
|
1323
|
+
React.createElement(Text, { color: "cyan", dimColor: true },
|
|
1324
|
+
React.createElement(Spinner, { type: "dots" }),
|
|
1325
|
+
" Indexing codebase...",
|
|
1326
|
+
' ',
|
|
1327
|
+
codebaseProgress.processedFiles,
|
|
1328
|
+
"/",
|
|
1329
|
+
codebaseProgress.totalFiles,
|
|
1330
|
+
" files",
|
|
1331
|
+
codebaseProgress.totalChunks > 0 &&
|
|
1332
|
+
` (${codebaseProgress.totalChunks} chunks)`))),
|
|
1333
|
+
!codebaseIndexing && watcherEnabled && (React.createElement(Box, { marginTop: 1, paddingX: 1 },
|
|
1334
|
+
React.createElement(Text, { color: "green", dimColor: true }, "\u2609 File watcher active - monitoring code changes"))),
|
|
1335
|
+
fileUpdateNotification && (React.createElement(Box, { marginTop: 1, paddingX: 1 },
|
|
1336
|
+
React.createElement(Text, { color: "yellow", dimColor: true },
|
|
1337
|
+
"\u26C1 Updated: ",
|
|
1338
|
+
fileUpdateNotification.file))))),
|
|
1165
1339
|
isCompressing && (React.createElement(Box, { marginTop: 1 },
|
|
1166
1340
|
React.createElement(Text, { color: "cyan" },
|
|
1167
1341
|
React.createElement(Spinner, { type: "dots" }),
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import Gradient from 'ink-gradient';
|
|
4
|
+
import { Alert } from '@inkjs/ui';
|
|
5
|
+
import TextInput from 'ink-text-input';
|
|
6
|
+
import { loadCodebaseConfig, saveCodebaseConfig, } from '../../utils/codebaseConfig.js';
|
|
7
|
+
const focusEventTokenRegex = /(?:\x1b)?\[[0-9;]*[IO]/g;
|
|
8
|
+
const isFocusEventInput = (value) => {
|
|
9
|
+
if (!value) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (value === '\x1b[I' ||
|
|
13
|
+
value === '\x1b[O' ||
|
|
14
|
+
value === '[I' ||
|
|
15
|
+
value === '[O') {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
const trimmed = value.trim();
|
|
19
|
+
if (!trimmed) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const tokens = trimmed.match(focusEventTokenRegex);
|
|
23
|
+
if (!tokens) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const normalized = trimmed.replace(/\s+/g, '');
|
|
27
|
+
const tokensCombined = tokens.join('');
|
|
28
|
+
return tokensCombined === normalized;
|
|
29
|
+
};
|
|
30
|
+
const stripFocusArtifacts = (value) => {
|
|
31
|
+
if (!value) {
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
return value
|
|
35
|
+
.replace(/\x1b\[[0-9;]*[IO]/g, '')
|
|
36
|
+
.replace(/\[[0-9;]*[IO]/g, '')
|
|
37
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
|
|
38
|
+
};
|
|
39
|
+
export default function CodeBaseConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
40
|
+
// Configuration state
|
|
41
|
+
const [enabled, setEnabled] = useState(false);
|
|
42
|
+
const [embeddingModelName, setEmbeddingModelName] = useState('');
|
|
43
|
+
const [embeddingBaseUrl, setEmbeddingBaseUrl] = useState('');
|
|
44
|
+
const [embeddingApiKey, setEmbeddingApiKey] = useState('');
|
|
45
|
+
const [embeddingDimensions, setEmbeddingDimensions] = useState(1536);
|
|
46
|
+
const [batchMaxLines, setBatchMaxLines] = useState(10);
|
|
47
|
+
const [batchConcurrency, setBatchConcurrency] = useState(1);
|
|
48
|
+
// UI state
|
|
49
|
+
const [currentField, setCurrentField] = useState('enabled');
|
|
50
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
51
|
+
const [errors, setErrors] = useState([]);
|
|
52
|
+
// Scrolling configuration
|
|
53
|
+
const MAX_VISIBLE_FIELDS = 8;
|
|
54
|
+
const allFields = [
|
|
55
|
+
'enabled',
|
|
56
|
+
'embeddingModelName',
|
|
57
|
+
'embeddingBaseUrl',
|
|
58
|
+
'embeddingApiKey',
|
|
59
|
+
'embeddingDimensions',
|
|
60
|
+
'batchMaxLines',
|
|
61
|
+
'batchConcurrency',
|
|
62
|
+
];
|
|
63
|
+
const currentFieldIndex = allFields.indexOf(currentField);
|
|
64
|
+
const totalFields = allFields.length;
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
loadConfiguration();
|
|
67
|
+
}, []);
|
|
68
|
+
const loadConfiguration = () => {
|
|
69
|
+
const config = loadCodebaseConfig();
|
|
70
|
+
setEnabled(config.enabled);
|
|
71
|
+
setEmbeddingModelName(config.embedding.modelName);
|
|
72
|
+
setEmbeddingBaseUrl(config.embedding.baseUrl);
|
|
73
|
+
setEmbeddingApiKey(config.embedding.apiKey);
|
|
74
|
+
setEmbeddingDimensions(config.embedding.dimensions);
|
|
75
|
+
setBatchMaxLines(config.batch.maxLines);
|
|
76
|
+
setBatchConcurrency(config.batch.concurrency);
|
|
77
|
+
};
|
|
78
|
+
const saveConfiguration = () => {
|
|
79
|
+
// Validation
|
|
80
|
+
const validationErrors = [];
|
|
81
|
+
if (enabled) {
|
|
82
|
+
// Embedding configuration is required
|
|
83
|
+
if (!embeddingModelName.trim()) {
|
|
84
|
+
validationErrors.push('Embedding model name is required when enabled');
|
|
85
|
+
}
|
|
86
|
+
if (!embeddingBaseUrl.trim()) {
|
|
87
|
+
validationErrors.push('Embedding base URL is required when enabled');
|
|
88
|
+
}
|
|
89
|
+
if (!embeddingApiKey.trim()) {
|
|
90
|
+
validationErrors.push('Embedding API key is required when enabled');
|
|
91
|
+
}
|
|
92
|
+
if (embeddingDimensions <= 0) {
|
|
93
|
+
validationErrors.push('Embedding dimensions must be greater than 0');
|
|
94
|
+
}
|
|
95
|
+
// Batch configuration validation
|
|
96
|
+
if (batchMaxLines <= 0) {
|
|
97
|
+
validationErrors.push('Batch max lines must be greater than 0');
|
|
98
|
+
}
|
|
99
|
+
if (batchConcurrency <= 0) {
|
|
100
|
+
validationErrors.push('Batch concurrency must be greater than 0');
|
|
101
|
+
}
|
|
102
|
+
// LLM is optional - no validation needed
|
|
103
|
+
}
|
|
104
|
+
if (validationErrors.length > 0) {
|
|
105
|
+
setErrors(validationErrors);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const config = {
|
|
110
|
+
enabled,
|
|
111
|
+
embedding: {
|
|
112
|
+
modelName: embeddingModelName,
|
|
113
|
+
baseUrl: embeddingBaseUrl,
|
|
114
|
+
apiKey: embeddingApiKey,
|
|
115
|
+
dimensions: embeddingDimensions,
|
|
116
|
+
},
|
|
117
|
+
llm: {
|
|
118
|
+
modelName: '',
|
|
119
|
+
baseUrl: '',
|
|
120
|
+
apiKey: '',
|
|
121
|
+
},
|
|
122
|
+
batch: {
|
|
123
|
+
maxLines: batchMaxLines,
|
|
124
|
+
concurrency: batchConcurrency,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
saveCodebaseConfig(config);
|
|
128
|
+
setErrors([]);
|
|
129
|
+
// Trigger codebase config reload in ChatScreen
|
|
130
|
+
if (global.__reloadCodebaseConfig) {
|
|
131
|
+
global.__reloadCodebaseConfig();
|
|
132
|
+
}
|
|
133
|
+
onSave?.();
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
setErrors([
|
|
137
|
+
error instanceof Error ? error.message : 'Failed to save configuration',
|
|
138
|
+
]);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const renderField = (field) => {
|
|
142
|
+
const isActive = field === currentField;
|
|
143
|
+
const isCurrentlyEditing = isActive && isEditing;
|
|
144
|
+
switch (field) {
|
|
145
|
+
case 'enabled':
|
|
146
|
+
return (React.createElement(Box, { key: field, flexDirection: "column" },
|
|
147
|
+
React.createElement(Text, { color: isActive ? 'green' : 'white' },
|
|
148
|
+
isActive ? '❯ ' : ' ',
|
|
149
|
+
"CodeBase Enabled:"),
|
|
150
|
+
React.createElement(Box, { marginLeft: 3 },
|
|
151
|
+
React.createElement(Text, { color: "gray" },
|
|
152
|
+
enabled ? '[✓] Enabled' : '[ ] Disabled',
|
|
153
|
+
" (Press Enter to toggle)"))));
|
|
154
|
+
case 'embeddingModelName':
|
|
155
|
+
return (React.createElement(Box, { key: field, flexDirection: "column" },
|
|
156
|
+
React.createElement(Text, { color: isActive ? 'green' : 'white' },
|
|
157
|
+
isActive ? '❯ ' : ' ',
|
|
158
|
+
"Embedding Model Name:"),
|
|
159
|
+
isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
160
|
+
React.createElement(Text, { color: "cyan" },
|
|
161
|
+
React.createElement(TextInput, { value: embeddingModelName, onChange: value => setEmbeddingModelName(stripFocusArtifacts(value)), onSubmit: () => setIsEditing(false) })))),
|
|
162
|
+
!isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
163
|
+
React.createElement(Text, { color: "gray" }, embeddingModelName || 'Not set')))));
|
|
164
|
+
case 'embeddingBaseUrl':
|
|
165
|
+
return (React.createElement(Box, { key: field, flexDirection: "column" },
|
|
166
|
+
React.createElement(Text, { color: isActive ? 'green' : 'white' },
|
|
167
|
+
isActive ? '❯ ' : ' ',
|
|
168
|
+
"Embedding Base URL:"),
|
|
169
|
+
isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
170
|
+
React.createElement(Text, { color: "cyan" },
|
|
171
|
+
React.createElement(TextInput, { value: embeddingBaseUrl, onChange: value => setEmbeddingBaseUrl(stripFocusArtifacts(value)), onSubmit: () => setIsEditing(false) })))),
|
|
172
|
+
!isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
173
|
+
React.createElement(Text, { color: "gray" }, embeddingBaseUrl || 'Not set')))));
|
|
174
|
+
case 'embeddingApiKey':
|
|
175
|
+
return (React.createElement(Box, { key: field, flexDirection: "column" },
|
|
176
|
+
React.createElement(Text, { color: isActive ? 'green' : 'white' },
|
|
177
|
+
isActive ? '❯ ' : ' ',
|
|
178
|
+
"Embedding API Key:"),
|
|
179
|
+
isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
180
|
+
React.createElement(Text, { color: "cyan" },
|
|
181
|
+
React.createElement(TextInput, { value: embeddingApiKey, onChange: value => setEmbeddingApiKey(stripFocusArtifacts(value)), onSubmit: () => setIsEditing(false), mask: "*" })))),
|
|
182
|
+
!isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
183
|
+
React.createElement(Text, { color: "gray" }, embeddingApiKey ? '••••••••' : 'Not set')))));
|
|
184
|
+
case 'embeddingDimensions':
|
|
185
|
+
return (React.createElement(Box, { key: field, flexDirection: "column" },
|
|
186
|
+
React.createElement(Text, { color: isActive ? 'green' : 'white' },
|
|
187
|
+
isActive ? '❯ ' : ' ',
|
|
188
|
+
"Embedding Dimensions:"),
|
|
189
|
+
isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
190
|
+
React.createElement(Text, { color: "cyan" },
|
|
191
|
+
React.createElement(TextInput, { value: embeddingDimensions.toString(), onChange: value => {
|
|
192
|
+
const num = parseInt(stripFocusArtifacts(value) || '0');
|
|
193
|
+
if (!isNaN(num)) {
|
|
194
|
+
setEmbeddingDimensions(num);
|
|
195
|
+
}
|
|
196
|
+
}, onSubmit: () => setIsEditing(false) })))),
|
|
197
|
+
!isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
198
|
+
React.createElement(Text, { color: "gray" }, embeddingDimensions)))));
|
|
199
|
+
case 'batchMaxLines':
|
|
200
|
+
return (React.createElement(Box, { key: field, flexDirection: "column" },
|
|
201
|
+
React.createElement(Text, { color: isActive ? 'green' : 'white' },
|
|
202
|
+
isActive ? '❯ ' : ' ',
|
|
203
|
+
"Batch Max Lines:"),
|
|
204
|
+
isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
205
|
+
React.createElement(Text, { color: "cyan" },
|
|
206
|
+
React.createElement(TextInput, { value: batchMaxLines.toString(), onChange: value => {
|
|
207
|
+
const num = parseInt(stripFocusArtifacts(value) || '0');
|
|
208
|
+
if (!isNaN(num)) {
|
|
209
|
+
setBatchMaxLines(num);
|
|
210
|
+
}
|
|
211
|
+
}, onSubmit: () => setIsEditing(false) })))),
|
|
212
|
+
!isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
213
|
+
React.createElement(Text, { color: "gray" }, batchMaxLines)))));
|
|
214
|
+
case 'batchConcurrency':
|
|
215
|
+
return (React.createElement(Box, { key: field, flexDirection: "column" },
|
|
216
|
+
React.createElement(Text, { color: isActive ? 'green' : 'white' },
|
|
217
|
+
isActive ? '❯ ' : ' ',
|
|
218
|
+
"Batch Concurrency:"),
|
|
219
|
+
isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
220
|
+
React.createElement(Text, { color: "cyan" },
|
|
221
|
+
React.createElement(TextInput, { value: batchConcurrency.toString(), onChange: value => {
|
|
222
|
+
const num = parseInt(stripFocusArtifacts(value) || '0');
|
|
223
|
+
if (!isNaN(num)) {
|
|
224
|
+
setBatchConcurrency(num);
|
|
225
|
+
}
|
|
226
|
+
}, onSubmit: () => setIsEditing(false) })))),
|
|
227
|
+
!isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
228
|
+
React.createElement(Text, { color: "gray" }, batchConcurrency)))));
|
|
229
|
+
default:
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
useInput((rawInput, key) => {
|
|
234
|
+
const input = stripFocusArtifacts(rawInput);
|
|
235
|
+
if (!input && isFocusEventInput(rawInput)) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (isFocusEventInput(rawInput)) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// When editing, only handle submission
|
|
242
|
+
if (isEditing) {
|
|
243
|
+
// TextInput handles the actual editing
|
|
244
|
+
// Escape to cancel editing
|
|
245
|
+
if (key.escape) {
|
|
246
|
+
setIsEditing(false);
|
|
247
|
+
loadConfiguration(); // Reset to saved values
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// Navigation
|
|
252
|
+
if (key.upArrow) {
|
|
253
|
+
const currentIndex = allFields.indexOf(currentField);
|
|
254
|
+
if (currentIndex > 0) {
|
|
255
|
+
setCurrentField(allFields[currentIndex - 1]);
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (key.downArrow) {
|
|
260
|
+
const currentIndex = allFields.indexOf(currentField);
|
|
261
|
+
if (currentIndex < allFields.length - 1) {
|
|
262
|
+
setCurrentField(allFields[currentIndex + 1]);
|
|
263
|
+
}
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// Toggle enabled field
|
|
267
|
+
if (key.return && currentField === 'enabled') {
|
|
268
|
+
setEnabled(!enabled);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
// Enter editing mode for text fields
|
|
272
|
+
if (key.return && currentField !== 'enabled') {
|
|
273
|
+
setIsEditing(true);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
// Save configuration (Ctrl+S or Escape when not editing)
|
|
277
|
+
if ((key.ctrl && input === 's') || key.escape) {
|
|
278
|
+
saveConfiguration();
|
|
279
|
+
if (!errors.length) {
|
|
280
|
+
onBack();
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
286
|
+
!inlineMode && (React.createElement(Box, { marginBottom: 1, borderStyle: "double", borderColor: 'cyan', paddingX: 2 },
|
|
287
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
288
|
+
React.createElement(Gradient, { name: "rainbow" }, "CodeBase Configuration"),
|
|
289
|
+
React.createElement(Text, { color: "gray", dimColor: true }, "Configure codebase indexing and search settings")))),
|
|
290
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
291
|
+
React.createElement(Text, { color: "yellow", bold: true },
|
|
292
|
+
"Settings (",
|
|
293
|
+
currentFieldIndex + 1,
|
|
294
|
+
"/",
|
|
295
|
+
totalFields,
|
|
296
|
+
")"),
|
|
297
|
+
totalFields > MAX_VISIBLE_FIELDS && (React.createElement(Text, { color: "gray", dimColor: true },
|
|
298
|
+
' ',
|
|
299
|
+
"\u00B7 \u2191\u2193 to scroll"))),
|
|
300
|
+
React.createElement(Box, { flexDirection: "column" }, (() => {
|
|
301
|
+
// Calculate visible window
|
|
302
|
+
if (allFields.length <= MAX_VISIBLE_FIELDS) {
|
|
303
|
+
// Show all fields if less than max
|
|
304
|
+
return allFields.map(field => renderField(field));
|
|
305
|
+
}
|
|
306
|
+
// Calculate scroll window
|
|
307
|
+
const halfWindow = Math.floor(MAX_VISIBLE_FIELDS / 2);
|
|
308
|
+
let startIndex = Math.max(0, currentFieldIndex - halfWindow);
|
|
309
|
+
let endIndex = Math.min(allFields.length, startIndex + MAX_VISIBLE_FIELDS);
|
|
310
|
+
// Adjust if we're near the end
|
|
311
|
+
if (endIndex - startIndex < MAX_VISIBLE_FIELDS) {
|
|
312
|
+
startIndex = Math.max(0, endIndex - MAX_VISIBLE_FIELDS);
|
|
313
|
+
}
|
|
314
|
+
const visibleFields = allFields.slice(startIndex, endIndex);
|
|
315
|
+
return visibleFields.map(field => renderField(field));
|
|
316
|
+
})()),
|
|
317
|
+
errors.length > 0 && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
318
|
+
React.createElement(Text, { color: "red", bold: true }, "Errors:"),
|
|
319
|
+
errors.map((error, index) => (React.createElement(Text, { key: index, color: "red" },
|
|
320
|
+
"\u2022 ",
|
|
321
|
+
error))))),
|
|
322
|
+
React.createElement(Box, { flexDirection: "column", marginTop: 1 }, isEditing ? (React.createElement(Alert, { variant: "info" }, "Editing mode: Type to edit, Enter to save, Esc to cancel")) : (React.createElement(Alert, { variant: "info" }, "Use \u2191\u2193 to navigate, Enter to edit/toggle, Ctrl+S or Esc to save")))));
|
|
323
|
+
}
|
|
@@ -10,6 +10,7 @@ import ProxyConfigScreen from './ProxyConfigScreen.js';
|
|
|
10
10
|
import SubAgentConfigScreen from './SubAgentConfigScreen.js';
|
|
11
11
|
import SubAgentListScreen from './SubAgentListScreen.js';
|
|
12
12
|
import SensitiveCommandConfigScreen from './SensitiveCommandConfigScreen.js';
|
|
13
|
+
import CodeBaseConfigScreen from './CodeBaseConfigScreen.js';
|
|
13
14
|
export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
|
|
14
15
|
const [infoText, setInfoText] = useState('Start a new chat conversation');
|
|
15
16
|
const [inlineView, setInlineView] = useState('menu');
|
|
@@ -35,6 +36,11 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
|
|
|
35
36
|
value: 'proxy',
|
|
36
37
|
infoText: 'Configure system proxy and browser for web search and fetch',
|
|
37
38
|
},
|
|
39
|
+
{
|
|
40
|
+
label: 'CodeBase Settings',
|
|
41
|
+
value: 'codebase',
|
|
42
|
+
infoText: 'Configure codebase indexing with embedding and LLM models',
|
|
43
|
+
},
|
|
38
44
|
{
|
|
39
45
|
label: 'System Prompt Settings',
|
|
40
46
|
value: 'systemprompt',
|
|
@@ -71,13 +77,16 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
|
|
|
71
77
|
setInfoText(newInfoText);
|
|
72
78
|
}, []);
|
|
73
79
|
const handleInlineMenuSelect = useCallback((value) => {
|
|
74
|
-
// Handle inline views (config, proxy, subagent) or pass through to parent
|
|
80
|
+
// Handle inline views (config, proxy, codebase, subagent) or pass through to parent
|
|
75
81
|
if (value === 'config') {
|
|
76
82
|
setInlineView('config');
|
|
77
83
|
}
|
|
78
84
|
else if (value === 'proxy') {
|
|
79
85
|
setInlineView('proxy-config');
|
|
80
86
|
}
|
|
87
|
+
else if (value === 'codebase') {
|
|
88
|
+
setInlineView('codebase-config');
|
|
89
|
+
}
|
|
81
90
|
else if (value === 'subagent') {
|
|
82
91
|
setInlineView('subagent-list');
|
|
83
92
|
}
|
|
@@ -141,6 +150,8 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
|
|
|
141
150
|
React.createElement(ConfigScreen, { onBack: handleBackToMenu, onSave: handleConfigSave, inlineMode: true }))),
|
|
142
151
|
inlineView === 'proxy-config' && (React.createElement(Box, { paddingX: 1 },
|
|
143
152
|
React.createElement(ProxyConfigScreen, { onBack: handleBackToMenu, onSave: handleConfigSave, inlineMode: true }))),
|
|
153
|
+
inlineView === 'codebase-config' && (React.createElement(Box, { paddingX: 1 },
|
|
154
|
+
React.createElement(CodeBaseConfigScreen, { onBack: handleBackToMenu, onSave: handleConfigSave, inlineMode: true }))),
|
|
144
155
|
inlineView === 'subagent-list' && (React.createElement(Box, { paddingX: 1 },
|
|
145
156
|
React.createElement(SubAgentListScreen, { onBack: handleBackToMenu, onAdd: handleSubAgentAdd, onEdit: handleSubAgentEdit, inlineMode: true }))),
|
|
146
157
|
inlineView === 'subagent-add' && (React.createElement(Box, { paddingX: 1 },
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface CodebaseConfig {
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
embedding: {
|
|
4
|
+
modelName: string;
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
apiKey: string;
|
|
7
|
+
dimensions: number;
|
|
8
|
+
};
|
|
9
|
+
llm: {
|
|
10
|
+
modelName: string;
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
apiKey: string;
|
|
13
|
+
};
|
|
14
|
+
batch: {
|
|
15
|
+
maxLines: number;
|
|
16
|
+
concurrency: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export declare const loadCodebaseConfig: () => CodebaseConfig;
|
|
20
|
+
export declare const saveCodebaseConfig: (config: CodebaseConfig) => void;
|