centaurus-cli 2.8.9 → 2.9.1
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/cli-adapter.d.ts +29 -2
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +526 -84
- package/dist/cli-adapter.js.map +1 -1
- package/dist/config/build-config.d.ts +1 -1
- package/dist/config/build-config.js +1 -1
- package/dist/config/models.d.ts +8 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +29 -0
- package/dist/config/models.js.map +1 -1
- package/dist/config/slash-commands.d.ts +1 -0
- package/dist/config/slash-commands.d.ts.map +1 -1
- package/dist/config/slash-commands.js +14 -1
- package/dist/config/slash-commands.js.map +1 -1
- package/dist/hooks/useConnectivity.d.ts +2 -0
- package/dist/hooks/useConnectivity.d.ts.map +1 -0
- package/dist/hooks/useConnectivity.js +12 -0
- package/dist/hooks/useConnectivity.js.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/mcp-command-handler.d.ts.map +1 -1
- package/dist/mcp/mcp-command-handler.js +0 -3
- package/dist/mcp/mcp-command-handler.js.map +1 -1
- package/dist/mcp/mcp-tool-wrapper.d.ts.map +1 -1
- package/dist/mcp/mcp-tool-wrapper.js +8 -0
- package/dist/mcp/mcp-tool-wrapper.js.map +1 -1
- package/dist/services/ai-service-client.d.ts +7 -1
- package/dist/services/ai-service-client.d.ts.map +1 -1
- package/dist/services/ai-service-client.js +6 -6
- package/dist/services/ai-service-client.js.map +1 -1
- package/dist/services/api-client.d.ts +35 -38
- package/dist/services/api-client.d.ts.map +1 -1
- package/dist/services/api-client.js +38 -30
- package/dist/services/api-client.js.map +1 -1
- package/dist/services/connectivity-manager.d.ts +18 -0
- package/dist/services/connectivity-manager.d.ts.map +1 -0
- package/dist/services/connectivity-manager.js +72 -0
- package/dist/services/connectivity-manager.js.map +1 -0
- package/dist/services/local-chat-storage.d.ts +5 -0
- package/dist/services/local-chat-storage.d.ts.map +1 -1
- package/dist/services/local-chat-storage.js +33 -0
- package/dist/services/local-chat-storage.js.map +1 -1
- package/dist/services/session-quota-manager.d.ts +101 -0
- package/dist/services/session-quota-manager.d.ts.map +1 -0
- package/dist/services/session-quota-manager.js +242 -0
- package/dist/services/session-quota-manager.js.map +1 -0
- package/dist/tools/background-command.d.ts +11 -0
- package/dist/tools/background-command.d.ts.map +1 -0
- package/dist/tools/background-command.js +162 -0
- package/dist/tools/background-command.js.map +1 -0
- package/dist/tools/command.d.ts.map +1 -1
- package/dist/tools/command.js +20 -6
- package/dist/tools/command.js.map +1 -1
- package/dist/tools/create-image.d.ts +10 -0
- package/dist/tools/create-image.d.ts.map +1 -0
- package/dist/tools/create-image.js +189 -0
- package/dist/tools/create-image.js.map +1 -0
- package/dist/tools/get-diff.d.ts.map +1 -1
- package/dist/tools/get-diff.js +4 -1
- package/dist/tools/get-diff.js.map +1 -1
- package/dist/tools/task-complete.d.ts.map +1 -1
- package/dist/tools/task-complete.js +8 -14
- package/dist/tools/task-complete.js.map +1 -1
- package/dist/ui/components/App.d.ts +5 -2
- package/dist/ui/components/App.d.ts.map +1 -1
- package/dist/ui/components/App.js +165 -45
- package/dist/ui/components/App.js.map +1 -1
- package/dist/ui/components/ContextWindowIndicator.d.ts.map +1 -1
- package/dist/ui/components/ContextWindowIndicator.js +43 -22
- package/dist/ui/components/ContextWindowIndicator.js.map +1 -1
- package/dist/ui/components/InputBox.d.ts +2 -0
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +217 -200
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/MessageDisplay.d.ts.map +1 -1
- package/dist/ui/components/MessageDisplay.js +8 -15
- package/dist/ui/components/MessageDisplay.js.map +1 -1
- package/dist/ui/components/SlashCommandAutocomplete.d.ts +2 -0
- package/dist/ui/components/SlashCommandAutocomplete.d.ts.map +1 -1
- package/dist/ui/components/SlashCommandAutocomplete.js +19 -10
- package/dist/ui/components/SlashCommandAutocomplete.js.map +1 -1
- package/dist/ui/components/StatusBar.d.ts.map +1 -1
- package/dist/ui/components/StatusBar.js +4 -0
- package/dist/ui/components/StatusBar.js.map +1 -1
- package/dist/ui/components/ToolExecutionMessage.d.ts.map +1 -1
- package/dist/ui/components/ToolExecutionMessage.js +198 -39
- package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
- package/dist/ui/components/ToolExecutionStatus.d.ts.map +1 -1
- package/dist/ui/components/ToolExecutionStatus.js +1 -0
- package/dist/ui/components/ToolExecutionStatus.js.map +1 -1
- package/dist/utils/chat-formatter.d.ts +12 -0
- package/dist/utils/chat-formatter.d.ts.map +1 -0
- package/dist/utils/chat-formatter.js +326 -0
- package/dist/utils/chat-formatter.js.map +1 -0
- package/dist/utils/editor-utils.d.ts +3 -3
- package/dist/utils/editor-utils.d.ts.map +1 -1
- package/dist/utils/editor-utils.js +15 -12
- package/dist/utils/editor-utils.js.map +1 -1
- package/dist/utils/input-classifier.d.ts.map +1 -1
- package/dist/utils/input-classifier.js +140 -20
- package/dist/utils/input-classifier.js.map +1 -1
- package/dist/utils/terminal-output.d.ts.map +1 -1
- package/dist/utils/terminal-output.js +198 -171
- package/dist/utils/terminal-output.js.map +1 -1
- package/dist/utils/text-clipboard.d.ts +12 -0
- package/dist/utils/text-clipboard.d.ts.map +1 -0
- package/dist/utils/text-clipboard.js +63 -0
- package/dist/utils/text-clipboard.js.map +1 -0
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef, useMemo } from 'react';
|
|
|
2
2
|
import { Box, Text, useInput } from 'ink';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
+
import { useConnectivity } from '../../hooks/useConnectivity.js';
|
|
5
6
|
import { Breadcrumbs } from './Breadcrumbs.js';
|
|
6
7
|
import { ContextWindowIndicator } from './ContextWindowIndicator.js';
|
|
7
8
|
import { logDebug } from '../../utils/logger.js';
|
|
@@ -9,9 +10,9 @@ import { detectIntent } from '../../utils/input-classifier.js';
|
|
|
9
10
|
import { CommandHistoryManager } from '../../utils/command-history.js';
|
|
10
11
|
import { SlashCommandAutocomplete } from './SlashCommandAutocomplete.js';
|
|
11
12
|
import { FileTagAutocomplete } from './FileTagAutocomplete.js';
|
|
12
|
-
import { ClipboardImageAutocomplete } from './ClipboardImageAutocomplete.js';
|
|
13
13
|
import { filterCommands } from '../../config/slash-commands.js';
|
|
14
14
|
import { getClipboardImages } from '../../services/clipboard-service.js';
|
|
15
|
+
import { useTerminalDimensions, TERMINAL_HEIGHT_CONSTANTS } from '../../hooks/useTerminalDimensions.js';
|
|
15
16
|
const getVisualLines = (text, width) => {
|
|
16
17
|
const logicalLines = text.split('\n');
|
|
17
18
|
const visualLines = [];
|
|
@@ -49,7 +50,7 @@ const getVisualLines = (text, width) => {
|
|
|
49
50
|
});
|
|
50
51
|
return visualLines;
|
|
51
52
|
};
|
|
52
|
-
export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...', autoAcceptMode, model, planMode = false, commandMode = false, backgroundMode = false, currentWorkingDirectory, commandHistory = [], onToggleAutoAccept, onToggleCommandMode, onToggleBackgroundMode, isActive = true, subshellContext, currentTokens = 0, maxTokens = 1000000, isShellRunning = false, backgroundTaskCount = 0, initialValue = '', onValueChange, onSetAutoModeSetup }) => {
|
|
53
|
+
export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...', autoAcceptMode, model, planMode = false, commandMode = false, backgroundMode = false, currentWorkingDirectory, commandHistory = [], onToggleAutoAccept, onToggleCommandMode, onToggleBackgroundMode, isActive = true, subshellContext, currentTokens = 0, maxTokens = 1000000, isShellRunning = false, backgroundTaskCount = 0, initialValue = '', onValueChange, onSetAutoModeSetup, sessionQuotaExhausted = false, sessionQuotaTimeRemaining = '' }) => {
|
|
53
54
|
// Use initialValue for first mount, but manage state internally after that
|
|
54
55
|
const [value, setValueInternal] = useState(initialValue);
|
|
55
56
|
const [cursorOffset, setCursorOffset] = useState(0);
|
|
@@ -58,6 +59,10 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
58
59
|
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
59
60
|
const [tempValue, setTempValue] = useState('');
|
|
60
61
|
const ignoreNextChangeRef = useRef(false);
|
|
62
|
+
// Refs to track current value and cursor for paste handling
|
|
63
|
+
// This prevents stale closure issues when Ink calls useInput multiple times during paste
|
|
64
|
+
const valueRef = useRef(initialValue);
|
|
65
|
+
const cursorOffsetRef = useRef(0);
|
|
61
66
|
// Auto Mode State
|
|
62
67
|
const [isAutoMode, setIsAutoMode] = useState(true);
|
|
63
68
|
const [detectedIntent, setDetectedIntent] = useState('ai');
|
|
@@ -70,34 +75,40 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
70
75
|
const [selection, setSelection] = useState(null);
|
|
71
76
|
// Reject Flash State (turns border red when submission is blocked)
|
|
72
77
|
const [rejectFlash, setRejectFlash] = useState(false);
|
|
78
|
+
// Session Quota Message State (shows quota exhausted message)
|
|
79
|
+
const [showQuotaMessage, setShowQuotaMessage] = useState(false);
|
|
73
80
|
// Slash Command Autocomplete State
|
|
74
81
|
const [slashAutocompleteVisible, setSlashAutocompleteVisible] = useState(false);
|
|
75
82
|
const [slashAutocompleteCommands, setSlashAutocompleteCommands] = useState([]);
|
|
76
83
|
const [slashAutocompleteSelectedIndex, setSlashAutocompleteSelectedIndex] = useState(0);
|
|
84
|
+
const [slashAutocompleteScrollOffset, setSlashAutocompleteScrollOffset] = useState(0);
|
|
85
|
+
// Connectivity State
|
|
86
|
+
const isConnected = useConnectivity();
|
|
87
|
+
// Terminal dimensions for height-aware autocomplete
|
|
88
|
+
const dimensions = useTerminalDimensions();
|
|
89
|
+
// Max 5 items, min 0 for very small terminals (use MIN_ROWS_FOR_STREAMING as threshold)
|
|
90
|
+
const slashMaxVisibleItems = dimensions.rows < TERMINAL_HEIGHT_CONSTANTS.MIN_ROWS_FOR_STREAMING
|
|
91
|
+
? 0
|
|
92
|
+
: Math.min(5, Math.max(1, Math.floor((dimensions.rows - 20) / 3)));
|
|
77
93
|
// File Tag Autocomplete State (@ symbol)
|
|
78
94
|
const [fileTagAutocompleteVisible, setFileTagAutocompleteVisible] = useState(false);
|
|
79
95
|
const [fileTagSuggestions, setFileTagSuggestions] = useState([]);
|
|
80
96
|
const [fileTagSelectedIndex, setFileTagSelectedIndex] = useState(0);
|
|
81
97
|
const [activeFileTagStart, setActiveFileTagStart] = useState(null);
|
|
82
98
|
const [confirmedFileTags, setConfirmedFileTags] = useState([]);
|
|
83
|
-
// Clipboard Image State (
|
|
84
|
-
const [clipboardAutocompleteVisible, setClipboardAutocompleteVisible] = useState(false);
|
|
85
|
-
const [clipboardImages, setClipboardImages] = useState([]);
|
|
86
|
-
const [clipboardSelectedIndex, setClipboardSelectedIndex] = useState(0);
|
|
87
|
-
const [clipboardLoading, setClipboardLoading] = useState(false);
|
|
88
|
-
const [activeClipboardStart, setActiveClipboardStart] = useState(null);
|
|
99
|
+
// Clipboard Image State (Alt+V paste)
|
|
89
100
|
const [confirmedClipboardImages, setConfirmedClipboardImages] = useState([]);
|
|
90
|
-
// Track positions of validated #image commands (for pink highlighting)
|
|
91
|
-
const [validatedImagePositions, setValidatedImagePositions] = useState([]);
|
|
92
101
|
// Track visual line count to force re-renders when text wraps
|
|
93
102
|
// This is necessary because Ink doesn't automatically update layout when text wraps
|
|
94
103
|
const [visualLineCount, setVisualLineCount] = useState(1);
|
|
95
104
|
// Configuration for scrolling
|
|
96
105
|
const MAX_VISIBLE_LINES = 9;
|
|
97
|
-
// Wrapper for setValue that also notifies parent of changes
|
|
106
|
+
// Wrapper for setValue that also notifies parent of changes and updates ref
|
|
98
107
|
const setValue = React.useCallback((newValue) => {
|
|
99
108
|
setValueInternal(prev => {
|
|
100
109
|
const resolvedValue = typeof newValue === 'function' ? newValue(prev) : newValue;
|
|
110
|
+
// Update ref synchronously for paste handling
|
|
111
|
+
valueRef.current = resolvedValue;
|
|
101
112
|
// Notify parent of value change for preservation across screen transitions
|
|
102
113
|
if (onValueChange) {
|
|
103
114
|
onValueChange(resolvedValue);
|
|
@@ -105,12 +116,29 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
105
116
|
return resolvedValue;
|
|
106
117
|
});
|
|
107
118
|
}, [onValueChange]);
|
|
119
|
+
// Wrapper for setCursorOffset that also updates ref
|
|
120
|
+
const setCursorOffsetWithRef = React.useCallback((newOffset) => {
|
|
121
|
+
setCursorOffset(prev => {
|
|
122
|
+
const resolvedOffset = typeof newOffset === 'function' ? newOffset(prev) : newOffset;
|
|
123
|
+
// Update ref synchronously for paste handling
|
|
124
|
+
cursorOffsetRef.current = resolvedOffset;
|
|
125
|
+
return resolvedOffset;
|
|
126
|
+
});
|
|
127
|
+
}, []);
|
|
108
128
|
// Initialize cursor position when initialValue is provided
|
|
109
129
|
useEffect(() => {
|
|
110
130
|
if (initialValue && initialValue.length > 0) {
|
|
111
131
|
setCursorOffset(initialValue.length);
|
|
132
|
+
cursorOffsetRef.current = initialValue.length;
|
|
112
133
|
}
|
|
113
134
|
}, []); // Only run on mount
|
|
135
|
+
// Keep refs in sync with state (for cases where state is updated directly)
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
valueRef.current = value;
|
|
138
|
+
}, [value]);
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
cursorOffsetRef.current = cursorOffset;
|
|
141
|
+
}, [cursorOffset]);
|
|
114
142
|
// Load history on mount
|
|
115
143
|
useEffect(() => {
|
|
116
144
|
CommandHistoryManager.getInstance().load();
|
|
@@ -289,127 +317,6 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
289
317
|
setActiveFileTagStart(null);
|
|
290
318
|
}
|
|
291
319
|
}, [value, cursorOffset, commandMode, currentWorkingDirectory]);
|
|
292
|
-
// Hash (#) symbol detection effect for clipboard image autocomplete
|
|
293
|
-
// When user types # in Agent mode, check clipboard for images and show dropdown
|
|
294
|
-
useEffect(() => {
|
|
295
|
-
// Don't show in command mode
|
|
296
|
-
if (commandMode) {
|
|
297
|
-
setClipboardAutocompleteVisible(false);
|
|
298
|
-
setActiveClipboardStart(null);
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
// Find if cursor is right after a # character (or typing after #)
|
|
302
|
-
let hashPosition = -1;
|
|
303
|
-
for (let i = cursorOffset - 1; i >= 0; i--) {
|
|
304
|
-
const char = value[i];
|
|
305
|
-
// Stop if we hit whitespace
|
|
306
|
-
if (/[\s\n]/.test(char))
|
|
307
|
-
break;
|
|
308
|
-
if (char === '#') {
|
|
309
|
-
hashPosition = i;
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
// Only treat # as image trigger if it's at start of input OR preceded by whitespace
|
|
314
|
-
if (hashPosition === -1 || (hashPosition > 0 && !/[\s\n]/.test(value[hashPosition - 1]))) {
|
|
315
|
-
setClipboardAutocompleteVisible(false);
|
|
316
|
-
setActiveClipboardStart(null);
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
// Extract what's typed after #
|
|
320
|
-
const query = value.slice(hashPosition + 1, cursorOffset).toLowerCase();
|
|
321
|
-
// If #image is fully typed (with space or at end), hide dropdown (validation will handle it)
|
|
322
|
-
if (query === 'image' || query.startsWith('image ')) {
|
|
323
|
-
setClipboardAutocompleteVisible(false);
|
|
324
|
-
setActiveClipboardStart(null);
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
// If user has typed something that doesn't match "image" prefix, hide dropdown
|
|
328
|
-
if (query.length > 0 && !'image'.startsWith(query)) {
|
|
329
|
-
setClipboardAutocompleteVisible(false);
|
|
330
|
-
setActiveClipboardStart(null);
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
// Show dropdown and check clipboard
|
|
334
|
-
setActiveClipboardStart(hashPosition);
|
|
335
|
-
setClipboardLoading(true);
|
|
336
|
-
setClipboardAutocompleteVisible(true);
|
|
337
|
-
// Check clipboard asynchronously
|
|
338
|
-
const checkClipboard = async () => {
|
|
339
|
-
try {
|
|
340
|
-
const images = await getClipboardImages();
|
|
341
|
-
setClipboardImages(images);
|
|
342
|
-
setClipboardLoading(false);
|
|
343
|
-
setClipboardSelectedIndex(0);
|
|
344
|
-
}
|
|
345
|
-
catch (error) {
|
|
346
|
-
logDebug(`Failed to check clipboard: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
347
|
-
setClipboardImages([]);
|
|
348
|
-
setClipboardLoading(false);
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
checkClipboard();
|
|
352
|
-
}, [value, cursorOffset, commandMode]);
|
|
353
|
-
// #image command detection effect
|
|
354
|
-
// When user types #image, check clipboard for images and validate
|
|
355
|
-
useEffect(() => {
|
|
356
|
-
// Don't check in command mode
|
|
357
|
-
if (commandMode)
|
|
358
|
-
return;
|
|
359
|
-
// Find all #image occurrences in the text
|
|
360
|
-
const regex = /#image\b/gi;
|
|
361
|
-
const matches = [];
|
|
362
|
-
let match;
|
|
363
|
-
while ((match = regex.exec(value)) !== null) {
|
|
364
|
-
matches.push({ start: match.index, end: match.index + match[0].length });
|
|
365
|
-
}
|
|
366
|
-
// If no #image in text, clear validated positions
|
|
367
|
-
if (matches.length === 0) {
|
|
368
|
-
if (validatedImagePositions.length > 0) {
|
|
369
|
-
setValidatedImagePositions([]);
|
|
370
|
-
setConfirmedClipboardImages([]);
|
|
371
|
-
}
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
// Check for new #image occurrences that haven't been validated yet
|
|
375
|
-
const unvalidatedMatches = matches.filter(m => !validatedImagePositions.some(v => v.start === m.start && v.end === m.end));
|
|
376
|
-
if (unvalidatedMatches.length === 0)
|
|
377
|
-
return;
|
|
378
|
-
// Check clipboard and validate new #image occurrences
|
|
379
|
-
const validateImages = async () => {
|
|
380
|
-
logDebug(`Found ${unvalidatedMatches.length} new #image occurrences to validate`);
|
|
381
|
-
try {
|
|
382
|
-
const images = await getClipboardImages();
|
|
383
|
-
logDebug(`Clipboard check returned ${images.length} images`);
|
|
384
|
-
if (images.length > 0) {
|
|
385
|
-
const image = images[0];
|
|
386
|
-
logDebug(`Image found in clipboard: ${image.displayName}, ${image.sizeBytes} bytes`);
|
|
387
|
-
// Add the clipboard image once for EACH #image occurrence
|
|
388
|
-
// This allows multiple #image tags in the same prompt to each attach the clipboard image
|
|
389
|
-
// Each #image tag represents an intent to include the image at that location
|
|
390
|
-
setConfirmedClipboardImages(prev => {
|
|
391
|
-
// Create a unique copy of the image for each #image occurrence
|
|
392
|
-
const newImages = unvalidatedMatches.map((_, index) => ({
|
|
393
|
-
...image,
|
|
394
|
-
// Give each a unique id so they're treated as separate entries for upload
|
|
395
|
-
id: `${image.id}_${Date.now()}_${index}`
|
|
396
|
-
}));
|
|
397
|
-
return [...prev, ...newImages];
|
|
398
|
-
});
|
|
399
|
-
// Mark all unvalidated positions as validated
|
|
400
|
-
setValidatedImagePositions(prev => [...prev, ...unvalidatedMatches]);
|
|
401
|
-
logDebug(`Validated ${unvalidatedMatches.length} #image occurrences, added ${unvalidatedMatches.length} images to queue`);
|
|
402
|
-
}
|
|
403
|
-
else {
|
|
404
|
-
logDebug('No image in clipboard - #image command not validated');
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
catch (error) {
|
|
408
|
-
logDebug(`Failed to check clipboard: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
409
|
-
}
|
|
410
|
-
};
|
|
411
|
-
validateImages();
|
|
412
|
-
}, [value, commandMode]);
|
|
413
320
|
const pushToUndoStack = () => {
|
|
414
321
|
setUndoStack(prev => [...prev, { value, cursorOffset }]);
|
|
415
322
|
setRedoStack([]); // Clear redo stack on new action
|
|
@@ -433,11 +340,21 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
433
340
|
// Handle slash command autocomplete navigation
|
|
434
341
|
if (slashAutocompleteVisible) {
|
|
435
342
|
if (key.downArrow) {
|
|
436
|
-
|
|
343
|
+
const newIndex = Math.min(slashAutocompleteSelectedIndex + 1, slashAutocompleteCommands.length - 1);
|
|
344
|
+
setSlashAutocompleteSelectedIndex(newIndex);
|
|
345
|
+
// Scroll down if selected is below visible window
|
|
346
|
+
if (newIndex >= slashAutocompleteScrollOffset + slashMaxVisibleItems) {
|
|
347
|
+
setSlashAutocompleteScrollOffset(newIndex - slashMaxVisibleItems + 1);
|
|
348
|
+
}
|
|
437
349
|
return;
|
|
438
350
|
}
|
|
439
351
|
if (key.upArrow) {
|
|
440
|
-
|
|
352
|
+
const newIndex = Math.max(slashAutocompleteSelectedIndex - 1, 0);
|
|
353
|
+
setSlashAutocompleteSelectedIndex(newIndex);
|
|
354
|
+
// Scroll up if selected is above visible window
|
|
355
|
+
if (newIndex < slashAutocompleteScrollOffset) {
|
|
356
|
+
setSlashAutocompleteScrollOffset(newIndex);
|
|
357
|
+
}
|
|
441
358
|
return;
|
|
442
359
|
}
|
|
443
360
|
if (key.return && input.length <= 1 && !key.shift && !key.ctrl) {
|
|
@@ -474,6 +391,13 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
474
391
|
setCursorOffset(newValue.length);
|
|
475
392
|
setSlashAutocompleteVisible(false);
|
|
476
393
|
}
|
|
394
|
+
else if (value.startsWith('/sync ')) {
|
|
395
|
+
// We're selecting a sync subcommand
|
|
396
|
+
const newValue = `/sync ${selected.name} `;
|
|
397
|
+
setValue(newValue);
|
|
398
|
+
setCursorOffset(newValue.length);
|
|
399
|
+
setSlashAutocompleteVisible(false);
|
|
400
|
+
}
|
|
477
401
|
else {
|
|
478
402
|
// Regular slash command, replace everything
|
|
479
403
|
const newValue = `/${selected.name} `;
|
|
@@ -486,6 +410,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
486
410
|
if (subcommandMatches.length > 0) {
|
|
487
411
|
setSlashAutocompleteCommands(subcommandMatches);
|
|
488
412
|
setSlashAutocompleteSelectedIndex(0);
|
|
413
|
+
setSlashAutocompleteScrollOffset(0);
|
|
489
414
|
// Keep autocomplete visible for subcommands
|
|
490
415
|
}
|
|
491
416
|
else {
|
|
@@ -497,6 +422,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
497
422
|
if (subcommandMatches.length > 0) {
|
|
498
423
|
setSlashAutocompleteCommands(subcommandMatches);
|
|
499
424
|
setSlashAutocompleteSelectedIndex(0);
|
|
425
|
+
setSlashAutocompleteScrollOffset(0);
|
|
500
426
|
// Keep autocomplete visible for subcommands
|
|
501
427
|
}
|
|
502
428
|
else {
|
|
@@ -508,6 +434,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
508
434
|
if (subcommandMatches.length > 0) {
|
|
509
435
|
setSlashAutocompleteCommands(subcommandMatches);
|
|
510
436
|
setSlashAutocompleteSelectedIndex(0);
|
|
437
|
+
setSlashAutocompleteScrollOffset(0);
|
|
511
438
|
// Keep autocomplete visible for subcommands
|
|
512
439
|
}
|
|
513
440
|
else {
|
|
@@ -519,6 +446,19 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
519
446
|
if (subcommandMatches.length > 0) {
|
|
520
447
|
setSlashAutocompleteCommands(subcommandMatches);
|
|
521
448
|
setSlashAutocompleteSelectedIndex(0);
|
|
449
|
+
setSlashAutocompleteScrollOffset(0);
|
|
450
|
+
// Keep autocomplete visible for subcommands
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
setSlashAutocompleteVisible(false);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
else if (selected.name === 'sync') {
|
|
457
|
+
const subcommandMatches = filterCommands('sync ');
|
|
458
|
+
if (subcommandMatches.length > 0) {
|
|
459
|
+
setSlashAutocompleteCommands(subcommandMatches);
|
|
460
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
461
|
+
setSlashAutocompleteScrollOffset(0);
|
|
522
462
|
// Keep autocomplete visible for subcommands
|
|
523
463
|
}
|
|
524
464
|
else {
|
|
@@ -609,54 +549,36 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
609
549
|
return;
|
|
610
550
|
}
|
|
611
551
|
}
|
|
612
|
-
//
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
552
|
+
// Alt+V: Paste image from clipboard
|
|
553
|
+
// Detect Alt+V on Windows/Linux (key.meta is often Alt on Windows in Ink)
|
|
554
|
+
const isAltV = (key.meta && input === 'v') || (input === '√'); // '√' is Alt+V on some systems
|
|
555
|
+
if (isAltV && !commandMode) {
|
|
556
|
+
// Check clipboard for images asynchronously
|
|
557
|
+
(async () => {
|
|
558
|
+
try {
|
|
559
|
+
const images = await getClipboardImages();
|
|
560
|
+
if (images.length > 0) {
|
|
561
|
+
const image = images[0];
|
|
562
|
+
logDebug(`Alt+V: Image found in clipboard: ${image.displayName}, ${image.sizeBytes} bytes`);
|
|
563
|
+
// Add image to confirmed list with unique ID
|
|
564
|
+
setConfirmedClipboardImages(prev => [
|
|
565
|
+
...prev,
|
|
566
|
+
{
|
|
567
|
+
...image,
|
|
568
|
+
id: `${image.id}_${Date.now()}`
|
|
569
|
+
}
|
|
570
|
+
]);
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
logDebug('Alt+V: No image in clipboard');
|
|
574
|
+
}
|
|
635
575
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
if (key.escape) {
|
|
639
|
-
setClipboardAutocompleteVisible(false);
|
|
640
|
-
setActiveClipboardStart(null);
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
// Tab also selects
|
|
644
|
-
if (key.tab && !key.shift) {
|
|
645
|
-
if (clipboardImages.length > 0 && activeClipboardStart !== null) {
|
|
646
|
-
pushToUndoStack();
|
|
647
|
-
const beforeHash = value.slice(0, activeClipboardStart);
|
|
648
|
-
const afterCursor = value.slice(cursorOffset);
|
|
649
|
-
const newValue = beforeHash + '#image ' + afterCursor;
|
|
650
|
-
const newCursorPos = activeClipboardStart + 7;
|
|
651
|
-
setValue(newValue);
|
|
652
|
-
setCursorOffset(newCursorPos);
|
|
653
|
-
setClipboardAutocompleteVisible(false);
|
|
654
|
-
setActiveClipboardStart(null);
|
|
576
|
+
catch (error) {
|
|
577
|
+
logDebug(`Alt+V: Failed to check clipboard: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
655
578
|
}
|
|
656
|
-
|
|
657
|
-
|
|
579
|
+
})();
|
|
580
|
+
return;
|
|
658
581
|
}
|
|
659
|
-
// Clipboard image paste is handled via Ctrl+V below
|
|
660
582
|
// DELETE WORD BACKWARDS - Check this FIRST before standard backspace/delete
|
|
661
583
|
// Triggers on any of these conditions:
|
|
662
584
|
// 1. Ctrl+W (char code 23) - Standard Unix terminal shortcut
|
|
@@ -690,6 +612,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
690
612
|
setSlashAutocompleteCommands(matches);
|
|
691
613
|
setSlashAutocompleteVisible(true);
|
|
692
614
|
setSlashAutocompleteSelectedIndex(0);
|
|
615
|
+
setSlashAutocompleteScrollOffset(0);
|
|
693
616
|
}
|
|
694
617
|
else {
|
|
695
618
|
setSlashAutocompleteVisible(false);
|
|
@@ -703,6 +626,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
703
626
|
setSlashAutocompleteCommands(matches);
|
|
704
627
|
setSlashAutocompleteVisible(true);
|
|
705
628
|
setSlashAutocompleteSelectedIndex(0);
|
|
629
|
+
setSlashAutocompleteScrollOffset(0);
|
|
706
630
|
}
|
|
707
631
|
else {
|
|
708
632
|
setSlashAutocompleteVisible(false);
|
|
@@ -716,6 +640,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
716
640
|
setSlashAutocompleteCommands(matches);
|
|
717
641
|
setSlashAutocompleteVisible(true);
|
|
718
642
|
setSlashAutocompleteSelectedIndex(0);
|
|
643
|
+
setSlashAutocompleteScrollOffset(0);
|
|
719
644
|
}
|
|
720
645
|
else {
|
|
721
646
|
setSlashAutocompleteVisible(false);
|
|
@@ -729,6 +654,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
729
654
|
setSlashAutocompleteCommands(matches);
|
|
730
655
|
setSlashAutocompleteVisible(true);
|
|
731
656
|
setSlashAutocompleteSelectedIndex(0);
|
|
657
|
+
setSlashAutocompleteScrollOffset(0);
|
|
732
658
|
}
|
|
733
659
|
else {
|
|
734
660
|
setSlashAutocompleteVisible(false);
|
|
@@ -742,6 +668,21 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
742
668
|
setSlashAutocompleteCommands(matches);
|
|
743
669
|
setSlashAutocompleteVisible(true);
|
|
744
670
|
setSlashAutocompleteSelectedIndex(0);
|
|
671
|
+
setSlashAutocompleteScrollOffset(0);
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
setSlashAutocompleteVisible(false);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
else if (newValue.startsWith('/sync ')) {
|
|
678
|
+
// Sync subcommands
|
|
679
|
+
const fullQuery = newValue.slice(1);
|
|
680
|
+
const matches = filterCommands(fullQuery);
|
|
681
|
+
if (matches.length > 0) {
|
|
682
|
+
setSlashAutocompleteCommands(matches);
|
|
683
|
+
setSlashAutocompleteVisible(true);
|
|
684
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
685
|
+
setSlashAutocompleteScrollOffset(0);
|
|
745
686
|
}
|
|
746
687
|
else {
|
|
747
688
|
setSlashAutocompleteVisible(false);
|
|
@@ -813,7 +754,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
813
754
|
setCursorOffset(value.length);
|
|
814
755
|
return;
|
|
815
756
|
}
|
|
816
|
-
// Note: Clipboard images are handled via
|
|
757
|
+
// Note: Clipboard images are handled via Alt+V keyboard shortcut
|
|
817
758
|
// DELETE CHAR - Only runs if Delete Word did NOT trigger
|
|
818
759
|
// Triggers on:
|
|
819
760
|
// 1. Backspace or Delete key flag is present
|
|
@@ -853,6 +794,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
853
794
|
setSlashAutocompleteCommands(matches);
|
|
854
795
|
setSlashAutocompleteVisible(true);
|
|
855
796
|
setSlashAutocompleteSelectedIndex(0);
|
|
797
|
+
setSlashAutocompleteScrollOffset(0);
|
|
856
798
|
}
|
|
857
799
|
else {
|
|
858
800
|
setSlashAutocompleteVisible(false);
|
|
@@ -866,6 +808,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
866
808
|
setSlashAutocompleteCommands(matches);
|
|
867
809
|
setSlashAutocompleteVisible(true);
|
|
868
810
|
setSlashAutocompleteSelectedIndex(0);
|
|
811
|
+
setSlashAutocompleteScrollOffset(0);
|
|
869
812
|
}
|
|
870
813
|
else {
|
|
871
814
|
setSlashAutocompleteVisible(false);
|
|
@@ -879,6 +822,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
879
822
|
setSlashAutocompleteCommands(matches);
|
|
880
823
|
setSlashAutocompleteVisible(true);
|
|
881
824
|
setSlashAutocompleteSelectedIndex(0);
|
|
825
|
+
setSlashAutocompleteScrollOffset(0);
|
|
882
826
|
}
|
|
883
827
|
else {
|
|
884
828
|
setSlashAutocompleteVisible(false);
|
|
@@ -892,6 +836,35 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
892
836
|
setSlashAutocompleteCommands(matches);
|
|
893
837
|
setSlashAutocompleteVisible(true);
|
|
894
838
|
setSlashAutocompleteSelectedIndex(0);
|
|
839
|
+
setSlashAutocompleteScrollOffset(0);
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
842
|
+
setSlashAutocompleteVisible(false);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
else if (newValue.startsWith('/background-task ') || newValue.startsWith('/bkg ') || newValue.startsWith('/bg-task ')) {
|
|
846
|
+
// Background-task subcommands
|
|
847
|
+
const fullQuery = newValue.slice(1);
|
|
848
|
+
const matches = filterCommands(fullQuery);
|
|
849
|
+
if (matches.length > 0) {
|
|
850
|
+
setSlashAutocompleteCommands(matches);
|
|
851
|
+
setSlashAutocompleteVisible(true);
|
|
852
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
853
|
+
setSlashAutocompleteScrollOffset(0);
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
setSlashAutocompleteVisible(false);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
else if (newValue.startsWith('/sync ')) {
|
|
860
|
+
// Sync subcommands
|
|
861
|
+
const fullQuery = newValue.slice(1);
|
|
862
|
+
const matches = filterCommands(fullQuery);
|
|
863
|
+
if (matches.length > 0) {
|
|
864
|
+
setSlashAutocompleteCommands(matches);
|
|
865
|
+
setSlashAutocompleteVisible(true);
|
|
866
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
867
|
+
setSlashAutocompleteScrollOffset(0);
|
|
895
868
|
}
|
|
896
869
|
else {
|
|
897
870
|
setSlashAutocompleteVisible(false);
|
|
@@ -930,6 +903,28 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
930
903
|
}
|
|
931
904
|
else {
|
|
932
905
|
// Enter: Submit
|
|
906
|
+
// Check if submission is allowed when disconnected
|
|
907
|
+
// Allowed: /exit command, OR Command Mode is active, OR detected intent is 'command'
|
|
908
|
+
const isExitCommand = value.trim() === '/exit';
|
|
909
|
+
const isCommandIntent = detectedIntent === 'command';
|
|
910
|
+
const isAllowedOffline = isConnected || isExitCommand || commandMode || isCommandIntent;
|
|
911
|
+
if (!isAllowedOffline) {
|
|
912
|
+
setRejectFlash(true);
|
|
913
|
+
setTimeout(() => setRejectFlash(false), 1000);
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
// Check session quota (only for AI mode, not command mode or slash commands)
|
|
917
|
+
// Slash commands and terminal commands should always be allowed
|
|
918
|
+
const isSlashCommand = value.trim().startsWith('/');
|
|
919
|
+
if (!commandMode && !isCommandIntent && !isSlashCommand && sessionQuotaExhausted) {
|
|
920
|
+
setRejectFlash(true);
|
|
921
|
+
setShowQuotaMessage(true);
|
|
922
|
+
setTimeout(() => {
|
|
923
|
+
setRejectFlash(false);
|
|
924
|
+
setShowQuotaMessage(false);
|
|
925
|
+
}, 3000); // Show quota message for 3 seconds
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
933
928
|
handleSubmit();
|
|
934
929
|
}
|
|
935
930
|
return;
|
|
@@ -1125,21 +1120,28 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1125
1120
|
pushToUndoStack();
|
|
1126
1121
|
// Handle paste with newlines
|
|
1127
1122
|
const cleanedInput = input.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
1128
|
-
|
|
1129
|
-
|
|
1123
|
+
// Use refs to get the latest value and cursor position
|
|
1124
|
+
// This prevents stale closure issues when Ink calls useInput multiple times during paste
|
|
1125
|
+
const currentValue = valueRef.current;
|
|
1126
|
+
const currentCursorOffset = cursorOffsetRef.current;
|
|
1127
|
+
let newValue = currentValue;
|
|
1128
|
+
let newOffset = currentCursorOffset;
|
|
1130
1129
|
if (selection) {
|
|
1131
1130
|
const start = Math.min(selection.start, selection.end);
|
|
1132
1131
|
const end = Math.max(selection.start, selection.end);
|
|
1133
|
-
newValue =
|
|
1132
|
+
newValue = currentValue.slice(0, start) + cleanedInput + currentValue.slice(end);
|
|
1134
1133
|
newOffset = start + cleanedInput.length;
|
|
1135
1134
|
setSelection(null);
|
|
1136
1135
|
}
|
|
1137
1136
|
else {
|
|
1138
|
-
newValue =
|
|
1139
|
-
newOffset =
|
|
1137
|
+
newValue = currentValue.slice(0, currentCursorOffset) + cleanedInput + currentValue.slice(currentCursorOffset);
|
|
1138
|
+
newOffset = currentCursorOffset + cleanedInput.length;
|
|
1140
1139
|
}
|
|
1140
|
+
// Update refs immediately for subsequent paste chunks
|
|
1141
|
+
valueRef.current = newValue;
|
|
1142
|
+
cursorOffsetRef.current = newOffset;
|
|
1141
1143
|
setValue(newValue);
|
|
1142
|
-
|
|
1144
|
+
setCursorOffsetWithRef(newOffset);
|
|
1143
1145
|
// Reset history/completions
|
|
1144
1146
|
setHistoryIndex(-1);
|
|
1145
1147
|
setCompletions([]);
|
|
@@ -1152,6 +1154,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1152
1154
|
setSlashAutocompleteCommands(matches);
|
|
1153
1155
|
setSlashAutocompleteVisible(true);
|
|
1154
1156
|
setSlashAutocompleteSelectedIndex(0);
|
|
1157
|
+
setSlashAutocompleteScrollOffset(0);
|
|
1155
1158
|
}
|
|
1156
1159
|
else {
|
|
1157
1160
|
setSlashAutocompleteVisible(false);
|
|
@@ -1165,6 +1168,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1165
1168
|
setSlashAutocompleteCommands(matches);
|
|
1166
1169
|
setSlashAutocompleteVisible(true);
|
|
1167
1170
|
setSlashAutocompleteSelectedIndex(0);
|
|
1171
|
+
setSlashAutocompleteScrollOffset(0);
|
|
1168
1172
|
}
|
|
1169
1173
|
else {
|
|
1170
1174
|
setSlashAutocompleteVisible(false);
|
|
@@ -1178,6 +1182,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1178
1182
|
setSlashAutocompleteCommands(matches);
|
|
1179
1183
|
setSlashAutocompleteVisible(true);
|
|
1180
1184
|
setSlashAutocompleteSelectedIndex(0);
|
|
1185
|
+
setSlashAutocompleteScrollOffset(0);
|
|
1181
1186
|
}
|
|
1182
1187
|
else {
|
|
1183
1188
|
setSlashAutocompleteVisible(false);
|
|
@@ -1191,6 +1196,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1191
1196
|
setSlashAutocompleteCommands(matches);
|
|
1192
1197
|
setSlashAutocompleteVisible(true);
|
|
1193
1198
|
setSlashAutocompleteSelectedIndex(0);
|
|
1199
|
+
setSlashAutocompleteScrollOffset(0);
|
|
1194
1200
|
}
|
|
1195
1201
|
else {
|
|
1196
1202
|
setSlashAutocompleteVisible(false);
|
|
@@ -1204,6 +1210,21 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1204
1210
|
setSlashAutocompleteCommands(matches);
|
|
1205
1211
|
setSlashAutocompleteVisible(true);
|
|
1206
1212
|
setSlashAutocompleteSelectedIndex(0);
|
|
1213
|
+
setSlashAutocompleteScrollOffset(0);
|
|
1214
|
+
}
|
|
1215
|
+
else {
|
|
1216
|
+
setSlashAutocompleteVisible(false);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
else if (newValue.startsWith('/sync ')) {
|
|
1220
|
+
// Sync subcommands (when user types "/sync ")
|
|
1221
|
+
const fullQuery = newValue.slice(1); // Remove leading "/", pass "sync <subquery>" to filterCommands
|
|
1222
|
+
const matches = filterCommands(fullQuery);
|
|
1223
|
+
if (matches.length > 0) {
|
|
1224
|
+
setSlashAutocompleteCommands(matches);
|
|
1225
|
+
setSlashAutocompleteVisible(true);
|
|
1226
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
1227
|
+
setSlashAutocompleteScrollOffset(0);
|
|
1207
1228
|
}
|
|
1208
1229
|
else {
|
|
1209
1230
|
setSlashAutocompleteVisible(false);
|
|
@@ -1453,17 +1474,6 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1453
1474
|
if (isInConfirmedFileTag || isInActiveFileTag) {
|
|
1454
1475
|
return React.createElement(Text, { key: charIdx, color: "#00ccff", bold: true }, char);
|
|
1455
1476
|
}
|
|
1456
|
-
// Check if this character is part of a validated #image command
|
|
1457
|
-
let isInValidatedImage = false;
|
|
1458
|
-
for (const pos of validatedImagePositions) {
|
|
1459
|
-
if (absPos >= pos.start && absPos < pos.end) {
|
|
1460
|
-
isInValidatedImage = true;
|
|
1461
|
-
break;
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
if (isInValidatedImage) {
|
|
1465
|
-
return React.createElement(Text, { key: charIdx, color: "#ff69b4", bold: true }, char);
|
|
1466
|
-
}
|
|
1467
1477
|
return React.createElement(Text, { key: charIdx }, char);
|
|
1468
1478
|
});
|
|
1469
1479
|
// Handle cursor at end of line
|
|
@@ -1501,6 +1511,14 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1501
1511
|
React.createElement(Text, { color: "#00ccff", bold: true }, "Auto ["),
|
|
1502
1512
|
detectedIntent === 'command' ? (React.createElement(Text, { color: "#00cc66", bold: true }, "Terminal")) : (React.createElement(Text, { color: "#00ccff" }, "Agent")),
|
|
1503
1513
|
React.createElement(Text, { color: "#00ccff", bold: true }, "]"))) : backgroundMode ? (React.createElement(Text, { color: "#9966ff", bold: true }, "Background")) : commandMode ? (React.createElement(Text, { color: "#00cc66", bold: true }, "Terminal")) : planMode ? (React.createElement(Text, { color: "#ffaa00", bold: true }, "Plan")) : (React.createElement(Text, { color: "#00ccff" }, "Agent"))))),
|
|
1514
|
+
confirmedClipboardImages.length > 0 && (React.createElement(Box, { marginBottom: 1, flexDirection: "row", flexWrap: "wrap", gap: 1 }, confirmedClipboardImages.map((img, index) => (React.createElement(Box, { key: img.id, borderStyle: "round", borderColor: "#ff69b4", paddingX: 1 },
|
|
1515
|
+
React.createElement(Text, { color: "#ff69b4", bold: true },
|
|
1516
|
+
"image_",
|
|
1517
|
+
index + 1)))))),
|
|
1518
|
+
showQuotaMessage && (React.createElement(Box, { justifyContent: "flex-end", marginBottom: 1 },
|
|
1519
|
+
React.createElement(Text, { color: "#ff3366", bold: true },
|
|
1520
|
+
"Session quota reached. Try again in ",
|
|
1521
|
+
sessionQuotaTimeRemaining))),
|
|
1504
1522
|
React.createElement(Box, { flexDirection: "row", width: "100%" },
|
|
1505
1523
|
React.createElement(Text, { color: "#666666" }, "> "),
|
|
1506
1524
|
renderInput()),
|
|
@@ -1515,8 +1533,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1515
1533
|
!commandMode && autoAcceptMode ? (React.createElement(Text, { color: "#00cc66", bold: true }, "[AUTO-ACCEPT: ON]")) : !commandMode ? (React.createElement(Text, { color: "#666666", dimColor: true }, "[AUTO-ACCEPT: OFF]")) : null,
|
|
1516
1534
|
!commandMode && (React.createElement(Box, { marginLeft: 1 },
|
|
1517
1535
|
React.createElement(ContextWindowIndicator, { currentTokens: currentTokens, maxTokens: maxTokens }))))),
|
|
1518
|
-
slashAutocompleteVisible && (React.createElement(SlashCommandAutocomplete, { commands: slashAutocompleteCommands, selectedIndex: slashAutocompleteSelectedIndex })),
|
|
1519
|
-
fileTagAutocompleteVisible && (React.createElement(FileTagAutocomplete, { files: fileTagSuggestions, selectedIndex: fileTagSelectedIndex }))
|
|
1520
|
-
clipboardAutocompleteVisible && (React.createElement(ClipboardImageAutocomplete, { images: clipboardImages, selectedIndex: clipboardSelectedIndex, isLoading: clipboardLoading }))));
|
|
1536
|
+
slashAutocompleteVisible && slashMaxVisibleItems > 0 && (React.createElement(SlashCommandAutocomplete, { commands: slashAutocompleteCommands, selectedIndex: slashAutocompleteSelectedIndex, maxVisibleItems: slashMaxVisibleItems, scrollOffset: slashAutocompleteScrollOffset })),
|
|
1537
|
+
fileTagAutocompleteVisible && (React.createElement(FileTagAutocomplete, { files: fileTagSuggestions, selectedIndex: fileTagSelectedIndex }))));
|
|
1521
1538
|
});
|
|
1522
1539
|
//# sourceMappingURL=InputBox.js.map
|