@syntero/orca-cli 1.2.17-next.3 → 1.2.17-next.5
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/components/ChatApp.js +1 -1
- package/dist/components/ChatApp.js.map +1 -1
- package/dist/components/InputFooter.d.ts.map +1 -1
- package/dist/components/InputFooter.js +34 -695
- package/dist/components/InputFooter.js.map +1 -1
- package/dist/components/hooks/useCommandPalette.d.ts +13 -0
- package/dist/components/hooks/useCommandPalette.d.ts.map +1 -0
- package/dist/components/hooks/useCommandPalette.js +38 -0
- package/dist/components/hooks/useCommandPalette.js.map +1 -0
- package/dist/components/hooks/useInputKeyHandler.d.ts +14 -0
- package/dist/components/hooks/useInputKeyHandler.d.ts.map +1 -0
- package/dist/components/hooks/useInputKeyHandler.js +584 -0
- package/dist/components/hooks/useInputKeyHandler.js.map +1 -0
- package/dist/components/hooks/useInputLines.d.ts +18 -0
- package/dist/components/hooks/useInputLines.d.ts.map +1 -0
- package/dist/components/hooks/useInputLines.js +46 -0
- package/dist/components/hooks/useInputLines.js.map +1 -0
- package/dist/components/hooks/useMenus.d.ts +60 -0
- package/dist/components/hooks/useMenus.d.ts.map +1 -0
- package/dist/components/hooks/useMenus.js +266 -0
- package/dist/components/hooks/useMenus.js.map +1 -0
- package/dist/components/hooks/usePasteBlocks.d.ts +14 -0
- package/dist/components/hooks/usePasteBlocks.d.ts.map +1 -0
- package/dist/components/hooks/usePasteBlocks.js +32 -0
- package/dist/components/hooks/usePasteBlocks.js.map +1 -0
- package/dist/models.js +4 -4
- package/dist/settings.js +1 -1
- package/package.json +4 -4
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Box, Text, useInput, useApp } from 'ink';
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
4
3
|
import { createRequire } from 'module';
|
|
5
4
|
import { CommandPalette } from './CommandPalette.js';
|
|
6
5
|
import { ModelMenu } from './ModelMenu.js';
|
|
@@ -10,10 +9,11 @@ import { TokenInput } from './TokenInput.js';
|
|
|
10
9
|
import { SyncMenu } from './SyncMenu.js';
|
|
11
10
|
import { UploadMenu } from './UploadMenu.js';
|
|
12
11
|
import { ResumeMenu } from './ResumeMenu.js';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
12
|
+
import { parsePasteMarker, usePasteBlocks } from './hooks/usePasteBlocks.js';
|
|
13
|
+
import { useInputLines } from './hooks/useInputLines.js';
|
|
14
|
+
import { useCommandPalette } from './hooks/useCommandPalette.js';
|
|
15
|
+
import { useMenus } from './hooks/useMenus.js';
|
|
16
|
+
import { useInputKeyHandler } from './hooks/useInputKeyHandler.js';
|
|
17
17
|
// Get package version
|
|
18
18
|
const require = createRequire(import.meta.url);
|
|
19
19
|
const pkg = require('../../package.json');
|
|
@@ -40,701 +40,40 @@ const pkg = require('../../package.json');
|
|
|
40
40
|
* - Ctrl+C: Exit
|
|
41
41
|
*/
|
|
42
42
|
export function InputFooter({ onSubmit, history = [], isDisabled = false, settings, onResumeConversation, fetchUsers, fetchOrganizations, userRole, fetchTree, listLocalChildren, onSettingsChange, }) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
currentLine: 0,
|
|
48
|
-
cursorPos: 0,
|
|
49
|
-
});
|
|
50
|
-
// State for command history navigation
|
|
51
|
-
// historyIndex: -1 means not navigating, 0 = most recent, length-1 = oldest
|
|
52
|
-
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
53
|
-
// savedInput: saves the current input when user starts navigating history
|
|
54
|
-
const [savedInput, setSavedInput] = useState('');
|
|
55
|
-
// Destructure for easier access in render
|
|
43
|
+
// --- Compose hooks ---
|
|
44
|
+
const pasteBlocks = usePasteBlocks();
|
|
45
|
+
const inputLines = useInputLines(onSubmit, pasteBlocks);
|
|
46
|
+
const { inputState } = inputLines;
|
|
56
47
|
const { lines, currentLine, cursorPos } = inputState;
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// State for model menu
|
|
66
|
-
const [showModelMenu, setShowModelMenu] = useState(false);
|
|
67
|
-
const [confirmationMessage, setConfirmationMessage] = useState(null);
|
|
68
|
-
// State for config wizard
|
|
69
|
-
const [showConfigWizard, setShowConfigWizard] = useState(false);
|
|
70
|
-
const [currentSettings, setCurrentSettings] = useState(settings);
|
|
71
|
-
// State for token input
|
|
72
|
-
const [showTokenInput, setShowTokenInput] = useState(false);
|
|
73
|
-
// State for sync menu
|
|
74
|
-
const [showSyncMenu, setShowSyncMenu] = useState(false);
|
|
75
|
-
// State for upload menu
|
|
76
|
-
const [showUploadMenu, setShowUploadMenu] = useState(false);
|
|
77
|
-
// State for resume menu
|
|
78
|
-
const [showResumeMenu, setShowResumeMenu] = useState(false);
|
|
79
|
-
// State for streaming menu
|
|
80
|
-
const [showStreamingMenu, setShowStreamingMenu] = useState(false);
|
|
81
|
-
const [resumeConversations, setResumeConversations] = useState([]);
|
|
82
|
-
// Track last Escape press time for double-Escape-to-clear
|
|
83
|
-
const lastEscapeTime = useRef(0);
|
|
84
|
-
// Get filtered commands based on paletteFilter
|
|
85
|
-
const filteredCommands = useMemo(() => {
|
|
86
|
-
if (!showPalette)
|
|
87
|
-
return [];
|
|
88
|
-
return filterCommands(paletteFilter);
|
|
89
|
-
}, [showPalette, paletteFilter]);
|
|
90
|
-
// Reset selected index when filtered commands change
|
|
91
|
-
useMemo(() => {
|
|
92
|
-
if (selectedIndex >= filteredCommands.length) {
|
|
93
|
-
setSelectedIndex(Math.max(0, filteredCommands.length - 1));
|
|
94
|
-
}
|
|
95
|
-
}, [filteredCommands.length, selectedIndex]);
|
|
96
|
-
// Clear confirmation message after a delay
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
if (confirmationMessage) {
|
|
99
|
-
const timer = setTimeout(() => {
|
|
100
|
-
setConfirmationMessage(null);
|
|
101
|
-
}, 2000);
|
|
102
|
-
return () => clearTimeout(timer);
|
|
103
|
-
}
|
|
104
|
-
}, [confirmationMessage]);
|
|
105
|
-
// Sync settings prop with local state
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
if (settings) {
|
|
108
|
-
setCurrentSettings(settings);
|
|
109
|
-
}
|
|
110
|
-
}, [settings]);
|
|
111
|
-
// Config wizard callbacks
|
|
112
|
-
const closeConfigWizard = useCallback(() => {
|
|
113
|
-
setShowConfigWizard(false);
|
|
114
|
-
updateInputState({ lines: [''], cursorPos: 0 });
|
|
115
|
-
}, []);
|
|
116
|
-
const handleConfigComplete = useCallback((newSettings) => {
|
|
117
|
-
// Save the settings
|
|
118
|
-
saveSettings(newSettings);
|
|
119
|
-
setCurrentSettings(newSettings);
|
|
120
|
-
setConfirmationMessage(`Configuration saved for ${newSettings.provider}`);
|
|
121
|
-
closeConfigWizard();
|
|
122
|
-
// Trigger reconnect by submitting a special token
|
|
123
|
-
setTimeout(() => {
|
|
124
|
-
onSubmit('__CONFIG_SAVED__');
|
|
125
|
-
}, 100);
|
|
126
|
-
}, [closeConfigWizard, onSubmit]);
|
|
127
|
-
const handleConfigCancel = useCallback(() => {
|
|
128
|
-
setConfirmationMessage('Configuration cancelled');
|
|
129
|
-
closeConfigWizard();
|
|
130
|
-
}, [closeConfigWizard]);
|
|
131
|
-
// Token input callbacks
|
|
132
|
-
const closeTokenInput = useCallback(() => {
|
|
133
|
-
setShowTokenInput(false);
|
|
134
|
-
updateInputState({ lines: [''], cursorPos: 0 });
|
|
135
|
-
}, []);
|
|
136
|
-
const handleTokenSubmit = useCallback((token) => {
|
|
137
|
-
closeTokenInput();
|
|
138
|
-
// Submit with special prefix for ChatApp to handle
|
|
139
|
-
onSubmit(`__TOKEN__:${token}`);
|
|
140
|
-
}, [closeTokenInput, onSubmit]);
|
|
141
|
-
const handleTokenCancel = useCallback(() => {
|
|
142
|
-
setConfirmationMessage('Token entry cancelled');
|
|
143
|
-
closeTokenInput();
|
|
144
|
-
}, [closeTokenInput]);
|
|
145
|
-
// Sync menu callbacks
|
|
146
|
-
const closeSyncMenu = useCallback(() => {
|
|
147
|
-
setShowSyncMenu(false);
|
|
148
|
-
updateInputState({ lines: [''], cursorPos: 0 });
|
|
149
|
-
}, []);
|
|
150
|
-
const handleSyncSelect = useCallback(async (result) => {
|
|
151
|
-
closeSyncMenu();
|
|
152
|
-
// Execute the sync action - we'll submit a special command for ChatApp to handle
|
|
153
|
-
switch (result.action) {
|
|
154
|
-
case 'sync':
|
|
155
|
-
// JSON-encode the full sync parameters
|
|
156
|
-
onSubmit(`__SYNC__:${JSON.stringify({
|
|
157
|
-
scope: result.scope ?? 'all',
|
|
158
|
-
force: result.force ?? false,
|
|
159
|
-
pathFilter: result.pathFilter,
|
|
160
|
-
workspaceUser: result.workspaceUser,
|
|
161
|
-
workspaceFolders: result.workspaceFolders,
|
|
162
|
-
orgId: result.orgId,
|
|
163
|
-
})}`);
|
|
164
|
-
break;
|
|
165
|
-
case 'preview':
|
|
166
|
-
onSubmit('__SYNC__:preview');
|
|
167
|
-
break;
|
|
168
|
-
case 'status':
|
|
169
|
-
onSubmit('__SYNC__:status');
|
|
170
|
-
break;
|
|
171
|
-
case 'resume':
|
|
172
|
-
onSubmit('__SYNC__:resume');
|
|
173
|
-
break;
|
|
174
|
-
case 'cancel':
|
|
175
|
-
// Already closed menu, nothing else to do
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
}, [closeSyncMenu, onSubmit]);
|
|
179
|
-
const handleSyncCancel = useCallback(() => {
|
|
180
|
-
setConfirmationMessage('Sync cancelled');
|
|
181
|
-
closeSyncMenu();
|
|
182
|
-
}, [closeSyncMenu]);
|
|
183
|
-
// Upload menu callbacks
|
|
184
|
-
const closeUploadMenu = useCallback(() => {
|
|
185
|
-
setShowUploadMenu(false);
|
|
186
|
-
updateInputState({ lines: [''], cursorPos: 0 });
|
|
187
|
-
}, []);
|
|
188
|
-
const handleUploadSelect = useCallback((result) => {
|
|
189
|
-
closeUploadMenu();
|
|
190
|
-
if (result.action === 'upload') {
|
|
191
|
-
onSubmit(`__UPLOAD__:${JSON.stringify({
|
|
192
|
-
scope: result.scope,
|
|
193
|
-
pathFilter: result.pathFilter,
|
|
194
|
-
workspaceUser: result.workspaceUser,
|
|
195
|
-
workspaceFolders: result.workspaceFolders,
|
|
196
|
-
orgId: result.orgId,
|
|
197
|
-
})}`);
|
|
198
|
-
}
|
|
199
|
-
}, [closeUploadMenu, onSubmit]);
|
|
200
|
-
const handleUploadCancel = useCallback(() => {
|
|
201
|
-
setConfirmationMessage('Upload cancelled');
|
|
202
|
-
closeUploadMenu();
|
|
203
|
-
}, [closeUploadMenu]);
|
|
204
|
-
// Resume menu callbacks
|
|
205
|
-
const closeResumeMenu = useCallback(() => {
|
|
206
|
-
setShowResumeMenu(false);
|
|
207
|
-
updateInputState({ lines: [''], cursorPos: 0 });
|
|
208
|
-
}, []);
|
|
209
|
-
const handleResumeSelect = useCallback((conversationId) => {
|
|
210
|
-
closeResumeMenu();
|
|
211
|
-
// Call the parent callback to handle resuming the conversation
|
|
212
|
-
onResumeConversation?.(conversationId);
|
|
213
|
-
}, [closeResumeMenu, onResumeConversation]);
|
|
214
|
-
const handleResumeCancel = useCallback(() => {
|
|
215
|
-
setConfirmationMessage('Resume cancelled');
|
|
216
|
-
closeResumeMenu();
|
|
217
|
-
}, [closeResumeMenu]);
|
|
218
|
-
const handleResumeDelete = useCallback((conversationId) => {
|
|
219
|
-
// Delete the conversation
|
|
220
|
-
const deleted = deleteConversation(conversationId);
|
|
221
|
-
if (deleted) {
|
|
222
|
-
// Refresh the list
|
|
223
|
-
setResumeConversations(listConversations());
|
|
224
|
-
setConfirmationMessage('Conversation deleted');
|
|
225
|
-
}
|
|
226
|
-
}, []);
|
|
227
|
-
const handleSubmit = useCallback(() => {
|
|
228
|
-
const result = lines.join('\n').trim();
|
|
229
|
-
if (!result)
|
|
230
|
-
return;
|
|
231
|
-
// Reset history navigation state
|
|
232
|
-
setHistoryIndex(-1);
|
|
233
|
-
setSavedInput('');
|
|
234
|
-
// Clear input
|
|
235
|
-
setInputState({ lines: [''], currentLine: 0, cursorPos: 0 });
|
|
236
|
-
// Call the onSubmit callback (parent handles queuing if streaming)
|
|
237
|
-
onSubmit(result);
|
|
238
|
-
}, [lines, onSubmit]);
|
|
239
|
-
const closePalette = useCallback(() => {
|
|
240
|
-
setShowPalette(false);
|
|
241
|
-
setPaletteFilter('');
|
|
242
|
-
setSelectedIndex(0);
|
|
243
|
-
}, []);
|
|
244
|
-
// Model menu callbacks
|
|
245
|
-
const closeModelMenu = useCallback(() => {
|
|
246
|
-
setShowModelMenu(false);
|
|
247
|
-
updateInputState({ lines: [''], cursorPos: 0 });
|
|
248
|
-
}, []);
|
|
249
|
-
const handleModelSelect = useCallback((provider, modelId) => {
|
|
250
|
-
if (!currentSettings) {
|
|
251
|
-
settingsDebugLog('[handleModelSelect] No currentSettings, aborting');
|
|
252
|
-
closeModelMenu();
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
settingsDebugLog(`[handleModelSelect] Changing to provider=${provider}, model=${modelId}`);
|
|
256
|
-
settingsDebugLog(`[handleModelSelect] Before: provider=${currentSettings.provider}, isAuthenticated=${isAuthenticated()}`);
|
|
257
|
-
// Create a deep copy of settings to avoid mutation issues
|
|
258
|
-
const updatedSettings = Settings.fromDict(currentSettings.toDict());
|
|
259
|
-
// Update the provider
|
|
260
|
-
updatedSettings.provider = provider;
|
|
261
|
-
// Update the model in settings based on provider
|
|
262
|
-
if (provider === Provider.ANTHROPIC || provider === 'anthropic') {
|
|
263
|
-
updatedSettings.anthropic.model = modelId;
|
|
264
|
-
}
|
|
265
|
-
else if (provider === Provider.OPENAI || provider === 'openai') {
|
|
266
|
-
updatedSettings.openai.model = modelId;
|
|
267
|
-
}
|
|
268
|
-
else if (provider === Provider.AZURE || provider === 'azure') {
|
|
269
|
-
updatedSettings.azure.deployment = modelId;
|
|
270
|
-
}
|
|
271
|
-
// Save the updated settings to disk
|
|
272
|
-
settingsDebugLog(`[handleModelSelect] Saving settings: provider=${updatedSettings.provider}`);
|
|
273
|
-
saveSettings(updatedSettings);
|
|
274
|
-
// CRITICAL: If cloud authenticated, also update the selected cloud provider
|
|
275
|
-
// This ensures getSelectedCloudProvider() returns the correct provider
|
|
276
|
-
if (isAuthenticated()) {
|
|
277
|
-
settingsDebugLog(`[handleModelSelect] Cloud authenticated, calling setSelectedCloudProvider(${provider})`);
|
|
278
|
-
const cloudProviderSet = setSelectedCloudProvider(provider);
|
|
279
|
-
if (!cloudProviderSet) {
|
|
280
|
-
settingsDebugLog(`[handleModelSelect] WARNING: setSelectedCloudProvider returned false - provider ${provider} may not be available in cloud config`);
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
settingsDebugLog(`[handleModelSelect] Cloud provider selection saved successfully`);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
// Update local state with the new settings
|
|
287
|
-
setCurrentSettings(updatedSettings);
|
|
288
|
-
// Format confirmation message with provider and model
|
|
289
|
-
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
290
|
-
setConfirmationMessage(`Provider changed to ${providerName}, model: ${modelId}`);
|
|
291
|
-
closeModelMenu();
|
|
292
|
-
settingsDebugLog(`[handleModelSelect] Triggering reconnect via __CONFIG_SAVED__`);
|
|
293
|
-
// Trigger reconnect to use the new provider/model
|
|
294
|
-
setTimeout(() => {
|
|
295
|
-
onSubmit('__CONFIG_SAVED__');
|
|
296
|
-
}, 100);
|
|
297
|
-
}, [currentSettings, closeModelMenu, onSubmit]);
|
|
298
|
-
const handleModelCancel = useCallback(() => {
|
|
299
|
-
setConfirmationMessage('Model selection cancelled');
|
|
300
|
-
closeModelMenu();
|
|
301
|
-
}, [closeModelMenu]);
|
|
302
|
-
// Streaming menu callbacks
|
|
303
|
-
const closeStreamingMenu = useCallback(() => {
|
|
304
|
-
setShowStreamingMenu(false);
|
|
305
|
-
updateInputState({ lines: [''], cursorPos: 0 });
|
|
306
|
-
}, []);
|
|
307
|
-
const handleStreamingSelect = useCallback((streaming) => {
|
|
308
|
-
if (!currentSettings) {
|
|
309
|
-
closeStreamingMenu();
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
const updatedSettings = Settings.fromDict({ ...currentSettings.toDict(), streaming });
|
|
313
|
-
saveSettings(updatedSettings);
|
|
314
|
-
setCurrentSettings(updatedSettings);
|
|
315
|
-
onSettingsChange?.(updatedSettings);
|
|
316
|
-
const statusText = streaming ? 'enabled' : 'disabled';
|
|
317
|
-
setConfirmationMessage(`Response streaming ${statusText}`);
|
|
318
|
-
closeStreamingMenu();
|
|
319
|
-
}, [currentSettings, closeStreamingMenu, onSettingsChange]);
|
|
320
|
-
const handleStreamingCancel = useCallback(() => {
|
|
321
|
-
setConfirmationMessage('Streaming selection cancelled');
|
|
322
|
-
closeStreamingMenu();
|
|
323
|
-
}, [closeStreamingMenu]);
|
|
324
|
-
useInput((input, key) => {
|
|
325
|
-
// Ctrl+C to exit
|
|
326
|
-
if (input === 'c' && key.ctrl) {
|
|
327
|
-
exit();
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
// Config wizard handles its own input
|
|
331
|
-
if (showConfigWizard) {
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
// Token input handles its own input
|
|
335
|
-
if (showTokenInput) {
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
// Model menu handles its own input
|
|
339
|
-
if (showModelMenu) {
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
// Sync menu handles its own input
|
|
343
|
-
if (showSyncMenu) {
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
// Upload menu handles its own input
|
|
347
|
-
if (showUploadMenu) {
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
// Resume menu handles its own input
|
|
351
|
-
if (showResumeMenu) {
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
// Streaming menu handles its own input
|
|
355
|
-
if (showStreamingMenu) {
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
// NOTE: We no longer block input when streaming (isDisabled).
|
|
359
|
-
// Users can type, search history, and submit messages while streaming.
|
|
360
|
-
// The parent component handles queuing messages submitted during streaming.
|
|
361
|
-
// Double-Escape to clear input (when no palette/menu is open)
|
|
362
|
-
if (key.escape && !showPalette) {
|
|
363
|
-
const now = Date.now();
|
|
364
|
-
if (now - lastEscapeTime.current < 500) {
|
|
365
|
-
// Double Escape: clear the input
|
|
366
|
-
setInputState({ lines: [''], currentLine: 0, cursorPos: 0 });
|
|
367
|
-
setHistoryIndex(-1);
|
|
368
|
-
setSavedInput('');
|
|
369
|
-
lastEscapeTime.current = 0;
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
lastEscapeTime.current = now;
|
|
373
|
-
}
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
// Handle command palette navigation when it's open
|
|
377
|
-
if (showPalette) {
|
|
378
|
-
// Escape to close palette
|
|
379
|
-
if (key.escape) {
|
|
380
|
-
closePalette();
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
// Arrow Up: navigate up in palette
|
|
384
|
-
if (key.upArrow) {
|
|
385
|
-
setSelectedIndex(prev => (prev > 0 ? prev - 1 : filteredCommands.length - 1));
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
// Arrow Down: navigate down in palette
|
|
389
|
-
if (key.downArrow) {
|
|
390
|
-
setSelectedIndex(prev => (prev < filteredCommands.length - 1 ? prev + 1 : 0));
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
// Tab: autocomplete with the highlighted command (keep palette open)
|
|
394
|
-
if (key.tab) {
|
|
395
|
-
if (filteredCommands.length > 0 && selectedIndex < filteredCommands.length) {
|
|
396
|
-
const selectedCommand = filteredCommands[selectedIndex].name;
|
|
397
|
-
// Update the input line with the full command and cursor position atomically
|
|
398
|
-
setInputState(prev => {
|
|
399
|
-
const newLines = [...prev.lines];
|
|
400
|
-
newLines[prev.currentLine] = selectedCommand;
|
|
401
|
-
return { ...prev, lines: newLines, cursorPos: selectedCommand.length };
|
|
402
|
-
});
|
|
403
|
-
// Reset filter to match the full command (minus the leading "/")
|
|
404
|
-
setPaletteFilter(selectedCommand.slice(1));
|
|
405
|
-
// Reset selected index
|
|
406
|
-
setSelectedIndex(0);
|
|
407
|
-
// Keep palette open - don't close it
|
|
408
|
-
}
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
// Enter: select the highlighted command or submit full input
|
|
412
|
-
if (key.return) {
|
|
413
|
-
if (filteredCommands.length > 0 && selectedIndex < filteredCommands.length) {
|
|
414
|
-
const selectedCommand = filteredCommands[selectedIndex].name;
|
|
415
|
-
// Special handling for /config command
|
|
416
|
-
if (selectedCommand === '/config' && currentSettings) {
|
|
417
|
-
closePalette();
|
|
418
|
-
setShowConfigWizard(true);
|
|
419
|
-
// Clear the input since we're handling it inline
|
|
420
|
-
updateInputState({ lines: [''], cursorPos: 0 });
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
// Special handling for /token command
|
|
424
|
-
if (selectedCommand === '/token') {
|
|
425
|
-
closePalette();
|
|
426
|
-
setShowTokenInput(true);
|
|
427
|
-
// Clear the input since we're handling it inline
|
|
428
|
-
updateInputState({ lines: [''], cursorPos: 0 });
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
// Special handling for /model command
|
|
432
|
-
if (selectedCommand === '/model' && currentSettings) {
|
|
433
|
-
closePalette();
|
|
434
|
-
setShowModelMenu(true);
|
|
435
|
-
// Clear the input since we're handling it inline
|
|
436
|
-
updateInputState({ lines: [''], cursorPos: 0 });
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
// Special handling for /streaming command
|
|
440
|
-
if (selectedCommand === '/streaming' && currentSettings) {
|
|
441
|
-
closePalette();
|
|
442
|
-
setShowStreamingMenu(true);
|
|
443
|
-
// Clear the input since we're handling it inline
|
|
444
|
-
updateInputState({ lines: [''], cursorPos: 0 });
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
// Special handling for /sync command
|
|
448
|
-
if (selectedCommand === '/sync') {
|
|
449
|
-
closePalette();
|
|
450
|
-
setShowSyncMenu(true);
|
|
451
|
-
// Clear the input since we're handling it inline
|
|
452
|
-
updateInputState({ lines: [''], cursorPos: 0 });
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
// Special handling for /upload command
|
|
456
|
-
if (selectedCommand === '/upload') {
|
|
457
|
-
closePalette();
|
|
458
|
-
setShowUploadMenu(true);
|
|
459
|
-
// Clear the input since we're handling it inline
|
|
460
|
-
updateInputState({ lines: [''], cursorPos: 0 });
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
// Special handling for /resume command
|
|
464
|
-
if (selectedCommand === '/resume') {
|
|
465
|
-
closePalette();
|
|
466
|
-
// Load conversations list before showing menu
|
|
467
|
-
setResumeConversations(listConversations());
|
|
468
|
-
setShowResumeMenu(true);
|
|
469
|
-
// Clear the input since we're handling it inline
|
|
470
|
-
updateInputState({ lines: [''], cursorPos: 0 });
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
// Set the command in input and submit immediately
|
|
474
|
-
closePalette();
|
|
475
|
-
setInputState({ lines: [''], currentLine: 0, cursorPos: 0 });
|
|
476
|
-
onSubmit(selectedCommand);
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
479
|
-
// No matching commands - submit the full input as-is (for commands with arguments)
|
|
480
|
-
const fullInput = '/' + paletteFilter;
|
|
481
|
-
closePalette();
|
|
482
|
-
setInputState({ lines: [''], currentLine: 0, cursorPos: 0 });
|
|
483
|
-
onSubmit(fullInput);
|
|
484
|
-
}
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
// Backspace: remove from filter or close palette
|
|
488
|
-
if (key.backspace || key.delete) {
|
|
489
|
-
if (paletteFilter.length > 0) {
|
|
490
|
-
// Remove last character from filter
|
|
491
|
-
const newFilter = paletteFilter.slice(0, -1);
|
|
492
|
-
setPaletteFilter(newFilter);
|
|
493
|
-
setSelectedIndex(0);
|
|
494
|
-
// Update the input line and cursor atomically
|
|
495
|
-
setInputState(prev => {
|
|
496
|
-
const newLines = [...prev.lines];
|
|
497
|
-
newLines[prev.currentLine] = '/' + newFilter;
|
|
498
|
-
return { ...prev, lines: newLines, cursorPos: 1 + newFilter.length };
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
else {
|
|
502
|
-
// Filter is empty, delete the "/" and close palette
|
|
503
|
-
setInputState(prev => {
|
|
504
|
-
const newLines = [...prev.lines];
|
|
505
|
-
const line = newLines[prev.currentLine];
|
|
506
|
-
if (line.startsWith('/')) {
|
|
507
|
-
newLines[prev.currentLine] = line.slice(1);
|
|
508
|
-
}
|
|
509
|
-
return { ...prev, lines: newLines, cursorPos: Math.max(0, prev.cursorPos - 1) };
|
|
510
|
-
});
|
|
511
|
-
closePalette();
|
|
512
|
-
}
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
// Regular character input: add to filter
|
|
516
|
-
if (input && !key.ctrl && !key.meta) {
|
|
517
|
-
const newFilter = paletteFilter + input;
|
|
518
|
-
setPaletteFilter(newFilter);
|
|
519
|
-
setSelectedIndex(0);
|
|
520
|
-
// Update the input line and cursor atomically
|
|
521
|
-
setInputState(prev => {
|
|
522
|
-
const newLines = [...prev.lines];
|
|
523
|
-
newLines[prev.currentLine] = '/' + newFilter;
|
|
524
|
-
return { ...prev, lines: newLines, cursorPos: 1 + newFilter.length };
|
|
525
|
-
});
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
|
-
// Normal input handling when palette is closed
|
|
531
|
-
// Ctrl+J for new line
|
|
532
|
-
if (input === 'j' && key.ctrl) {
|
|
533
|
-
setInputState(prev => {
|
|
534
|
-
const newLines = [...prev.lines];
|
|
535
|
-
// Split current line at cursor position
|
|
536
|
-
const currentLineContent = newLines[prev.currentLine];
|
|
537
|
-
const beforeCursor = currentLineContent.slice(0, prev.cursorPos);
|
|
538
|
-
const afterCursor = currentLineContent.slice(prev.cursorPos);
|
|
539
|
-
newLines[prev.currentLine] = beforeCursor;
|
|
540
|
-
newLines.splice(prev.currentLine + 1, 0, afterCursor);
|
|
541
|
-
return { lines: newLines, currentLine: prev.currentLine + 1, cursorPos: 0 };
|
|
542
|
-
});
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
// Enter to submit
|
|
546
|
-
if (key.return) {
|
|
547
|
-
handleSubmit();
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
// Arrow Up - navigate lines or history
|
|
551
|
-
if (key.upArrow) {
|
|
552
|
-
// If on first line, navigate history backward
|
|
553
|
-
if (currentLine === 0 && history.length > 0) {
|
|
554
|
-
const nextIndex = historyIndex + 1;
|
|
555
|
-
if (nextIndex < history.length) {
|
|
556
|
-
// Save current input when first starting to navigate
|
|
557
|
-
if (historyIndex === -1) {
|
|
558
|
-
setSavedInput(lines.join('\n'));
|
|
559
|
-
}
|
|
560
|
-
setHistoryIndex(nextIndex);
|
|
561
|
-
// History is stored newest first, so index 0 = most recent
|
|
562
|
-
const historyItem = history[nextIndex];
|
|
563
|
-
const historyLines = historyItem.split('\n');
|
|
564
|
-
setInputState({
|
|
565
|
-
lines: historyLines,
|
|
566
|
-
currentLine: 0,
|
|
567
|
-
cursorPos: historyLines[0].length,
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
// Otherwise navigate between lines
|
|
573
|
-
setInputState(prev => {
|
|
574
|
-
if (prev.currentLine > 0) {
|
|
575
|
-
const newLine = prev.currentLine - 1;
|
|
576
|
-
return {
|
|
577
|
-
...prev,
|
|
578
|
-
currentLine: newLine,
|
|
579
|
-
cursorPos: Math.min(prev.cursorPos, prev.lines[newLine].length),
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
return prev;
|
|
583
|
-
});
|
|
584
|
-
return;
|
|
585
|
-
}
|
|
586
|
-
// Arrow Down - navigate lines or history
|
|
587
|
-
if (key.downArrow) {
|
|
588
|
-
// If navigating history, move forward (toward more recent)
|
|
589
|
-
if (historyIndex >= 0) {
|
|
590
|
-
const nextIndex = historyIndex - 1;
|
|
591
|
-
if (nextIndex >= 0) {
|
|
592
|
-
// Still in history
|
|
593
|
-
setHistoryIndex(nextIndex);
|
|
594
|
-
const historyItem = history[nextIndex];
|
|
595
|
-
const historyLines = historyItem.split('\n');
|
|
596
|
-
setInputState({
|
|
597
|
-
lines: historyLines,
|
|
598
|
-
currentLine: 0,
|
|
599
|
-
cursorPos: historyLines[0].length,
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
else {
|
|
603
|
-
// Reached end of history, restore saved input
|
|
604
|
-
setHistoryIndex(-1);
|
|
605
|
-
const restoredLines = savedInput ? savedInput.split('\n') : [''];
|
|
606
|
-
setInputState({
|
|
607
|
-
lines: restoredLines,
|
|
608
|
-
currentLine: 0,
|
|
609
|
-
cursorPos: restoredLines[0].length,
|
|
610
|
-
});
|
|
611
|
-
setSavedInput('');
|
|
612
|
-
}
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
// Otherwise navigate between lines
|
|
616
|
-
setInputState(prev => {
|
|
617
|
-
if (prev.currentLine < prev.lines.length - 1) {
|
|
618
|
-
const newLine = prev.currentLine + 1;
|
|
619
|
-
return {
|
|
620
|
-
...prev,
|
|
621
|
-
currentLine: newLine,
|
|
622
|
-
cursorPos: Math.min(prev.cursorPos, prev.lines[newLine].length),
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
return prev;
|
|
626
|
-
});
|
|
627
|
-
return;
|
|
628
|
-
}
|
|
629
|
-
// Arrow Left
|
|
630
|
-
if (key.leftArrow) {
|
|
631
|
-
setInputState(prev => {
|
|
632
|
-
if (prev.cursorPos > 0) {
|
|
633
|
-
return { ...prev, cursorPos: prev.cursorPos - 1 };
|
|
634
|
-
}
|
|
635
|
-
else if (prev.currentLine > 0) {
|
|
636
|
-
// Move to end of previous line
|
|
637
|
-
const newLine = prev.currentLine - 1;
|
|
638
|
-
return { ...prev, currentLine: newLine, cursorPos: prev.lines[newLine].length };
|
|
639
|
-
}
|
|
640
|
-
return prev;
|
|
641
|
-
});
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
// Arrow Right
|
|
645
|
-
if (key.rightArrow) {
|
|
646
|
-
setInputState(prev => {
|
|
647
|
-
if (prev.cursorPos < prev.lines[prev.currentLine].length) {
|
|
648
|
-
return { ...prev, cursorPos: prev.cursorPos + 1 };
|
|
649
|
-
}
|
|
650
|
-
else if (prev.currentLine < prev.lines.length - 1) {
|
|
651
|
-
// Move to start of next line
|
|
652
|
-
return { ...prev, currentLine: prev.currentLine + 1, cursorPos: 0 };
|
|
653
|
-
}
|
|
654
|
-
return prev;
|
|
655
|
-
});
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
// Alt+Backspace: delete word before cursor
|
|
659
|
-
if (key.meta && (key.backspace || key.delete)) {
|
|
660
|
-
setInputState(prev => {
|
|
661
|
-
if (prev.cursorPos === 0)
|
|
662
|
-
return prev;
|
|
663
|
-
const line = prev.lines[prev.currentLine];
|
|
664
|
-
let pos = prev.cursorPos;
|
|
665
|
-
// Skip any whitespace immediately before cursor
|
|
666
|
-
while (pos > 0 && /\s/.test(line[pos - 1])) {
|
|
667
|
-
pos--;
|
|
668
|
-
}
|
|
669
|
-
// Delete back to the previous whitespace or start of line
|
|
670
|
-
while (pos > 0 && !/\s/.test(line[pos - 1])) {
|
|
671
|
-
pos--;
|
|
672
|
-
}
|
|
673
|
-
const newLines = [...prev.lines];
|
|
674
|
-
newLines[prev.currentLine] = line.slice(0, pos) + line.slice(prev.cursorPos);
|
|
675
|
-
return { ...prev, lines: newLines, cursorPos: pos };
|
|
676
|
-
});
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
// Backspace
|
|
680
|
-
if (key.backspace || key.delete) {
|
|
681
|
-
setInputState(prev => {
|
|
682
|
-
if (prev.cursorPos > 0) {
|
|
683
|
-
// Delete character before cursor
|
|
684
|
-
const newLines = [...prev.lines];
|
|
685
|
-
const line = newLines[prev.currentLine];
|
|
686
|
-
newLines[prev.currentLine] = line.slice(0, prev.cursorPos - 1) + line.slice(prev.cursorPos);
|
|
687
|
-
return { ...prev, lines: newLines, cursorPos: prev.cursorPos - 1 };
|
|
688
|
-
}
|
|
689
|
-
else if (prev.currentLine > 0) {
|
|
690
|
-
// Merge with previous line
|
|
691
|
-
const prevLineLen = prev.lines[prev.currentLine - 1].length;
|
|
692
|
-
const newLines = [...prev.lines];
|
|
693
|
-
newLines[prev.currentLine - 1] += newLines[prev.currentLine];
|
|
694
|
-
newLines.splice(prev.currentLine, 1);
|
|
695
|
-
return { lines: newLines, currentLine: prev.currentLine - 1, cursorPos: prevLineLen };
|
|
696
|
-
}
|
|
697
|
-
return prev;
|
|
698
|
-
});
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
// Home key (Ctrl+A in some terminals)
|
|
702
|
-
if (input === 'a' && key.ctrl) {
|
|
703
|
-
updateInputState({ cursorPos: 0 });
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
|
-
// End key (Ctrl+E in some terminals)
|
|
707
|
-
if (input === 'e' && key.ctrl) {
|
|
708
|
-
setInputState(prev => ({ ...prev, cursorPos: prev.lines[prev.currentLine].length }));
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
|
-
// Regular character input (including multi-character paste)
|
|
712
|
-
if (input && !key.ctrl && !key.meta) {
|
|
713
|
-
// Check if "/" is typed at the beginning of an empty line or at cursor position 0
|
|
714
|
-
if (input === '/' && (inputState.lines[inputState.currentLine] === '' || inputState.cursorPos === 0)) {
|
|
715
|
-
setShowPalette(true);
|
|
716
|
-
setPaletteFilter('');
|
|
717
|
-
setSelectedIndex(0);
|
|
718
|
-
}
|
|
719
|
-
setInputState(prev => {
|
|
720
|
-
const newLines = [...prev.lines];
|
|
721
|
-
const line = newLines[prev.currentLine];
|
|
722
|
-
newLines[prev.currentLine] = line.slice(0, prev.cursorPos) + input + line.slice(prev.cursorPos);
|
|
723
|
-
return {
|
|
724
|
-
...prev,
|
|
725
|
-
lines: newLines,
|
|
726
|
-
cursorPos: prev.cursorPos + input.length
|
|
727
|
-
};
|
|
728
|
-
});
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
48
|
+
const commandPalette = useCommandPalette();
|
|
49
|
+
const { showPalette, filteredCommands, selectedIndex, paletteFilter } = commandPalette;
|
|
50
|
+
const menus = useMenus({
|
|
51
|
+
settings,
|
|
52
|
+
onSubmit,
|
|
53
|
+
onResumeConversation,
|
|
54
|
+
onSettingsChange,
|
|
55
|
+
updateInputState: inputLines.updateInputState,
|
|
731
56
|
});
|
|
732
|
-
|
|
57
|
+
const { confirmationMessage, currentSettings, showModelMenu, handleModelSelect, handleModelCancel, showStreamingMenu, handleStreamingSelect, handleStreamingCancel, showConfigWizard, handleConfigComplete, handleConfigCancel, showTokenInput, handleTokenSubmit, handleTokenCancel, showSyncMenu, handleSyncSelect, handleSyncCancel, showUploadMenu, handleUploadSelect, handleUploadCancel, showResumeMenu, handleResumeSelect, handleResumeCancel, handleResumeDelete, resumeConversations, } = menus;
|
|
58
|
+
useInputKeyHandler({
|
|
59
|
+
pasteBlocks,
|
|
60
|
+
inputLines,
|
|
61
|
+
commandPalette,
|
|
62
|
+
menus,
|
|
63
|
+
history,
|
|
64
|
+
onSubmit,
|
|
65
|
+
});
|
|
66
|
+
// --- Render helpers ---
|
|
733
67
|
const renderLines = () => {
|
|
734
68
|
const maxDisplayLines = 5;
|
|
735
69
|
const displayLines = lines.slice(0, maxDisplayLines);
|
|
736
70
|
return displayLines.map((line, index) => {
|
|
737
71
|
const isCurrentLine = index === currentLine;
|
|
72
|
+
// Render paste markers as collapsed summary
|
|
73
|
+
const marker = parsePasteMarker(line);
|
|
74
|
+
if (marker) {
|
|
75
|
+
return (_jsx(Box, { children: _jsxs(Text, { color: "yellow", children: ["[Pasted text #", marker.id, " +", marker.lineCount, " lines]"] }) }, index));
|
|
76
|
+
}
|
|
738
77
|
if (isCurrentLine) {
|
|
739
78
|
// Show cursor in current line
|
|
740
79
|
// Use sibling Text components instead of nesting to avoid Ink layout bugs
|