@syntero/orca-cli 1.3.3 → 1.3.4

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.
Files changed (46) hide show
  1. package/dist/assistant/prompts.d.ts.map +1 -1
  2. package/dist/assistant/prompts.js +22 -0
  3. package/dist/assistant/prompts.js.map +1 -1
  4. package/dist/components/ChatApp.d.ts.map +1 -1
  5. package/dist/components/ChatApp.js +8 -6
  6. package/dist/components/ChatApp.js.map +1 -1
  7. package/dist/components/InputFooter.d.ts.map +1 -1
  8. package/dist/components/InputFooter.js +34 -695
  9. package/dist/components/InputFooter.js.map +1 -1
  10. package/dist/components/ResumeMenu.d.ts.map +1 -1
  11. package/dist/components/ResumeMenu.js +24 -3
  12. package/dist/components/ResumeMenu.js.map +1 -1
  13. package/dist/components/hooks/useCommandPalette.d.ts +13 -0
  14. package/dist/components/hooks/useCommandPalette.d.ts.map +1 -0
  15. package/dist/components/hooks/useCommandPalette.js +38 -0
  16. package/dist/components/hooks/useCommandPalette.js.map +1 -0
  17. package/dist/components/hooks/useInputKeyHandler.d.ts +14 -0
  18. package/dist/components/hooks/useInputKeyHandler.d.ts.map +1 -0
  19. package/dist/components/hooks/useInputKeyHandler.js +584 -0
  20. package/dist/components/hooks/useInputKeyHandler.js.map +1 -0
  21. package/dist/components/hooks/useInputLines.d.ts +18 -0
  22. package/dist/components/hooks/useInputLines.d.ts.map +1 -0
  23. package/dist/components/hooks/useInputLines.js +46 -0
  24. package/dist/components/hooks/useInputLines.js.map +1 -0
  25. package/dist/components/hooks/useMenus.d.ts +60 -0
  26. package/dist/components/hooks/useMenus.d.ts.map +1 -0
  27. package/dist/components/hooks/useMenus.js +266 -0
  28. package/dist/components/hooks/useMenus.js.map +1 -0
  29. package/dist/components/hooks/usePasteBlocks.d.ts +14 -0
  30. package/dist/components/hooks/usePasteBlocks.d.ts.map +1 -0
  31. package/dist/components/hooks/usePasteBlocks.js +32 -0
  32. package/dist/components/hooks/usePasteBlocks.js.map +1 -0
  33. package/dist/models.js +4 -4
  34. package/dist/settings.js +1 -1
  35. package/dist/tools/handler.d.ts.map +1 -1
  36. package/dist/tools/handler.js +11 -0
  37. package/dist/tools/handler.js.map +1 -1
  38. package/dist/tools/index.d.ts +152 -0
  39. package/dist/tools/index.d.ts.map +1 -1
  40. package/dist/tools/index.js +2 -0
  41. package/dist/tools/index.js.map +1 -1
  42. package/dist/tools/inference.d.ts +127 -0
  43. package/dist/tools/inference.d.ts.map +1 -0
  44. package/dist/tools/inference.js +318 -0
  45. package/dist/tools/inference.js.map +1 -0
  46. package/package.json +4 -4
@@ -1,6 +1,5 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
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 { listConversations, deleteConversation } from '../conversations/index.js';
14
- import { filterCommands } from '../autocomplete.js';
15
- import { Settings, Provider, saveSettings, debugLog as settingsDebugLog } from '../settings.js';
16
- import { isAuthenticated, setSelectedCloudProvider } from '../auth.js';
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
- const { exit } = useApp();
44
- // State for multi-line input - combined into single object for atomic updates
45
- const [inputState, setInputState] = useState({
46
- lines: [''],
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
- // Helper to update input state atomically
58
- const updateInputState = (updates) => {
59
- setInputState(prev => ({ ...prev, ...updates }));
60
- };
61
- // State for command palette
62
- const [showPalette, setShowPalette] = useState(false);
63
- const [paletteFilter, setPaletteFilter] = useState('');
64
- const [selectedIndex, setSelectedIndex] = useState(0);
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
- // Render lines with cursor indicator
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