centaurus-cli 2.8.8 → 2.9.0
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 +82 -2
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +622 -94
- package/dist/cli-adapter.js.map +1 -1
- package/dist/config/ConfigManager.d.ts.map +1 -1
- package/dist/config/ConfigManager.js +6 -5
- package/dist/config/ConfigManager.js.map +1 -1
- package/dist/config/build-config.d.ts +42 -0
- package/dist/config/build-config.d.ts.map +1 -0
- package/dist/config/build-config.js +44 -0
- package/dist/config/build-config.js.map +1 -0
- package/dist/config/manager.d.ts +2 -2
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +9 -12
- package/dist/config/manager.js.map +1 -1
- package/dist/config/mcp-config-manager.d.ts +5 -0
- package/dist/config/mcp-config-manager.d.ts.map +1 -1
- package/dist/config/mcp-config-manager.js +8 -0
- package/dist/config/mcp-config-manager.js.map +1 -1
- package/dist/config/models.d.ts +48 -42
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +148 -133
- package/dist/config/models.js.map +1 -1
- package/dist/config/slash-commands.d.ts +2 -0
- package/dist/config/slash-commands.d.ts.map +1 -1
- package/dist/config/slash-commands.js +34 -1
- package/dist/config/slash-commands.js.map +1 -1
- package/dist/context/context-manager.d.ts.map +1 -1
- package/dist/context/context-manager.js +6 -6
- package/dist/context/context-manager.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.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -23
- 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 +2 -5
- 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 +1 -0
- package/dist/services/ai-service-client.d.ts.map +1 -1
- package/dist/services/ai-service-client.js +8 -6
- package/dist/services/ai-service-client.js.map +1 -1
- package/dist/services/api-client.d.ts +46 -34
- package/dist/services/api-client.d.ts.map +1 -1
- package/dist/services/api-client.js +47 -37
- package/dist/services/api-client.js.map +1 -1
- package/dist/services/background-task-manager.d.ts +114 -0
- package/dist/services/background-task-manager.d.ts.map +1 -0
- package/dist/services/background-task-manager.js +301 -0
- package/dist/services/background-task-manager.js.map +1 -0
- 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 +38 -4
- package/dist/services/local-chat-storage.js.map +1 -1
- 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 +6 -3
- 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/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +8 -6
- package/dist/tools/web-search.js.map +1 -1
- package/dist/ui/components/App.d.ts +34 -2
- package/dist/ui/components/App.d.ts.map +1 -1
- package/dist/ui/components/App.js +403 -67
- 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/ErrorBoundary.d.ts.map +1 -1
- package/dist/ui/components/ErrorBoundary.js +2 -1
- package/dist/ui/components/ErrorBoundary.js.map +1 -1
- package/dist/ui/components/InputBox.d.ts +4 -0
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +289 -207
- 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 +155 -41
- 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/command-history.d.ts.map +1 -1
- package/dist/utils/command-history.js +2 -1
- package/dist/utils/command-history.js.map +1 -1
- package/dist/utils/conversation-logger.d.ts +15 -0
- package/dist/utils/conversation-logger.d.ts.map +1 -1
- package/dist/utils/conversation-logger.js +56 -2
- package/dist/utils/conversation-logger.js.map +1 -1
- package/dist/utils/editor-utils.d.ts.map +1 -1
- package/dist/utils/editor-utils.js +3 -2
- 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/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +31 -1
- package/dist/utils/logger.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 -2
- package/models-config.json +0 -126
|
@@ -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, currentWorkingDirectory, commandHistory = [], onToggleAutoAccept, onToggleCommandMode, isActive = true, subshellContext, currentTokens = 0, maxTokens = 1000000, isShellRunning = false, initialValue = '', onValueChange }) => {
|
|
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 }) => {
|
|
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');
|
|
@@ -74,30 +79,34 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
74
79
|
const [slashAutocompleteVisible, setSlashAutocompleteVisible] = useState(false);
|
|
75
80
|
const [slashAutocompleteCommands, setSlashAutocompleteCommands] = useState([]);
|
|
76
81
|
const [slashAutocompleteSelectedIndex, setSlashAutocompleteSelectedIndex] = useState(0);
|
|
82
|
+
const [slashAutocompleteScrollOffset, setSlashAutocompleteScrollOffset] = useState(0);
|
|
83
|
+
// Connectivity State
|
|
84
|
+
const isConnected = useConnectivity();
|
|
85
|
+
// Terminal dimensions for height-aware autocomplete
|
|
86
|
+
const dimensions = useTerminalDimensions();
|
|
87
|
+
// Max 5 items, min 0 for very small terminals (use MIN_ROWS_FOR_STREAMING as threshold)
|
|
88
|
+
const slashMaxVisibleItems = dimensions.rows < TERMINAL_HEIGHT_CONSTANTS.MIN_ROWS_FOR_STREAMING
|
|
89
|
+
? 0
|
|
90
|
+
: Math.min(5, Math.max(1, Math.floor((dimensions.rows - 20) / 3)));
|
|
77
91
|
// File Tag Autocomplete State (@ symbol)
|
|
78
92
|
const [fileTagAutocompleteVisible, setFileTagAutocompleteVisible] = useState(false);
|
|
79
93
|
const [fileTagSuggestions, setFileTagSuggestions] = useState([]);
|
|
80
94
|
const [fileTagSelectedIndex, setFileTagSelectedIndex] = useState(0);
|
|
81
95
|
const [activeFileTagStart, setActiveFileTagStart] = useState(null);
|
|
82
96
|
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);
|
|
97
|
+
// Clipboard Image State (Alt+V paste)
|
|
89
98
|
const [confirmedClipboardImages, setConfirmedClipboardImages] = useState([]);
|
|
90
|
-
// Track positions of validated #image commands (for pink highlighting)
|
|
91
|
-
const [validatedImagePositions, setValidatedImagePositions] = useState([]);
|
|
92
99
|
// Track visual line count to force re-renders when text wraps
|
|
93
100
|
// This is necessary because Ink doesn't automatically update layout when text wraps
|
|
94
101
|
const [visualLineCount, setVisualLineCount] = useState(1);
|
|
95
102
|
// Configuration for scrolling
|
|
96
103
|
const MAX_VISIBLE_LINES = 9;
|
|
97
|
-
// Wrapper for setValue that also notifies parent of changes
|
|
104
|
+
// Wrapper for setValue that also notifies parent of changes and updates ref
|
|
98
105
|
const setValue = React.useCallback((newValue) => {
|
|
99
106
|
setValueInternal(prev => {
|
|
100
107
|
const resolvedValue = typeof newValue === 'function' ? newValue(prev) : newValue;
|
|
108
|
+
// Update ref synchronously for paste handling
|
|
109
|
+
valueRef.current = resolvedValue;
|
|
101
110
|
// Notify parent of value change for preservation across screen transitions
|
|
102
111
|
if (onValueChange) {
|
|
103
112
|
onValueChange(resolvedValue);
|
|
@@ -105,16 +114,53 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
105
114
|
return resolvedValue;
|
|
106
115
|
});
|
|
107
116
|
}, [onValueChange]);
|
|
117
|
+
// Wrapper for setCursorOffset that also updates ref
|
|
118
|
+
const setCursorOffsetWithRef = React.useCallback((newOffset) => {
|
|
119
|
+
setCursorOffset(prev => {
|
|
120
|
+
const resolvedOffset = typeof newOffset === 'function' ? newOffset(prev) : newOffset;
|
|
121
|
+
// Update ref synchronously for paste handling
|
|
122
|
+
cursorOffsetRef.current = resolvedOffset;
|
|
123
|
+
return resolvedOffset;
|
|
124
|
+
});
|
|
125
|
+
}, []);
|
|
108
126
|
// Initialize cursor position when initialValue is provided
|
|
109
127
|
useEffect(() => {
|
|
110
128
|
if (initialValue && initialValue.length > 0) {
|
|
111
129
|
setCursorOffset(initialValue.length);
|
|
130
|
+
cursorOffsetRef.current = initialValue.length;
|
|
112
131
|
}
|
|
113
132
|
}, []); // Only run on mount
|
|
133
|
+
// Keep refs in sync with state (for cases where state is updated directly)
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
valueRef.current = value;
|
|
136
|
+
}, [value]);
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
cursorOffsetRef.current = cursorOffset;
|
|
139
|
+
}, [cursorOffset]);
|
|
114
140
|
// Load history on mount
|
|
115
141
|
useEffect(() => {
|
|
116
142
|
CommandHistoryManager.getInstance().load();
|
|
117
143
|
}, []);
|
|
144
|
+
// Register setIsAutoMode callback for external control (e.g., after background task starts)
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (onSetAutoModeSetup) {
|
|
147
|
+
onSetAutoModeSetup((enabled) => {
|
|
148
|
+
setIsAutoMode(enabled);
|
|
149
|
+
if (enabled) {
|
|
150
|
+
// When enabling Auto mode, also update detected intent based on current value
|
|
151
|
+
const intent = detectIntent(value);
|
|
152
|
+
setDetectedIntent(intent);
|
|
153
|
+
// Set command mode based on intent
|
|
154
|
+
if (intent === 'command' && !commandMode && onToggleCommandMode) {
|
|
155
|
+
onToggleCommandMode();
|
|
156
|
+
}
|
|
157
|
+
else if (intent === 'ai' && commandMode && onToggleCommandMode) {
|
|
158
|
+
onToggleCommandMode();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}, [onSetAutoModeSetup, value, commandMode, onToggleCommandMode]);
|
|
118
164
|
// Force clear value if it becomes empty or whitespace-only after external changes
|
|
119
165
|
useEffect(() => {
|
|
120
166
|
if (value && value.trim() === '' && cursorOffset > 0) {
|
|
@@ -269,122 +315,6 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
269
315
|
setActiveFileTagStart(null);
|
|
270
316
|
}
|
|
271
317
|
}, [value, cursorOffset, commandMode, currentWorkingDirectory]);
|
|
272
|
-
// Hash (#) symbol detection effect for clipboard image autocomplete
|
|
273
|
-
// When user types # in Agent mode, check clipboard for images and show dropdown
|
|
274
|
-
useEffect(() => {
|
|
275
|
-
// Don't show in command mode
|
|
276
|
-
if (commandMode) {
|
|
277
|
-
setClipboardAutocompleteVisible(false);
|
|
278
|
-
setActiveClipboardStart(null);
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
// Find if cursor is right after a # character (or typing after #)
|
|
282
|
-
let hashPosition = -1;
|
|
283
|
-
for (let i = cursorOffset - 1; i >= 0; i--) {
|
|
284
|
-
const char = value[i];
|
|
285
|
-
// Stop if we hit whitespace
|
|
286
|
-
if (/[\s\n]/.test(char))
|
|
287
|
-
break;
|
|
288
|
-
if (char === '#') {
|
|
289
|
-
hashPosition = i;
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
// Only treat # as image trigger if it's at start of input OR preceded by whitespace
|
|
294
|
-
if (hashPosition === -1 || (hashPosition > 0 && !/[\s\n]/.test(value[hashPosition - 1]))) {
|
|
295
|
-
setClipboardAutocompleteVisible(false);
|
|
296
|
-
setActiveClipboardStart(null);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
// Extract what's typed after #
|
|
300
|
-
const query = value.slice(hashPosition + 1, cursorOffset).toLowerCase();
|
|
301
|
-
// If #image is fully typed (with space or at end), hide dropdown (validation will handle it)
|
|
302
|
-
if (query === 'image' || query.startsWith('image ')) {
|
|
303
|
-
setClipboardAutocompleteVisible(false);
|
|
304
|
-
setActiveClipboardStart(null);
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
// If user has typed something that doesn't match "image" prefix, hide dropdown
|
|
308
|
-
if (query.length > 0 && !'image'.startsWith(query)) {
|
|
309
|
-
setClipboardAutocompleteVisible(false);
|
|
310
|
-
setActiveClipboardStart(null);
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
// Show dropdown and check clipboard
|
|
314
|
-
setActiveClipboardStart(hashPosition);
|
|
315
|
-
setClipboardLoading(true);
|
|
316
|
-
setClipboardAutocompleteVisible(true);
|
|
317
|
-
// Check clipboard asynchronously
|
|
318
|
-
const checkClipboard = async () => {
|
|
319
|
-
try {
|
|
320
|
-
const images = await getClipboardImages();
|
|
321
|
-
setClipboardImages(images);
|
|
322
|
-
setClipboardLoading(false);
|
|
323
|
-
setClipboardSelectedIndex(0);
|
|
324
|
-
}
|
|
325
|
-
catch (error) {
|
|
326
|
-
logDebug(`Failed to check clipboard: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
327
|
-
setClipboardImages([]);
|
|
328
|
-
setClipboardLoading(false);
|
|
329
|
-
}
|
|
330
|
-
};
|
|
331
|
-
checkClipboard();
|
|
332
|
-
}, [value, cursorOffset, commandMode]);
|
|
333
|
-
// #image command detection effect
|
|
334
|
-
// When user types #image, check clipboard for images and validate
|
|
335
|
-
useEffect(() => {
|
|
336
|
-
// Don't check in command mode
|
|
337
|
-
if (commandMode)
|
|
338
|
-
return;
|
|
339
|
-
// Find all #image occurrences in the text
|
|
340
|
-
const regex = /#image\b/gi;
|
|
341
|
-
const matches = [];
|
|
342
|
-
let match;
|
|
343
|
-
while ((match = regex.exec(value)) !== null) {
|
|
344
|
-
matches.push({ start: match.index, end: match.index + match[0].length });
|
|
345
|
-
}
|
|
346
|
-
// If no #image in text, clear validated positions
|
|
347
|
-
if (matches.length === 0) {
|
|
348
|
-
if (validatedImagePositions.length > 0) {
|
|
349
|
-
setValidatedImagePositions([]);
|
|
350
|
-
setConfirmedClipboardImages([]);
|
|
351
|
-
}
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
// Check for new #image occurrences that haven't been validated yet
|
|
355
|
-
const unvalidatedMatches = matches.filter(m => !validatedImagePositions.some(v => v.start === m.start && v.end === m.end));
|
|
356
|
-
if (unvalidatedMatches.length === 0)
|
|
357
|
-
return;
|
|
358
|
-
// Check clipboard and validate new #image occurrences
|
|
359
|
-
const validateImages = async () => {
|
|
360
|
-
logDebug(`Found ${unvalidatedMatches.length} new #image occurrences to validate`);
|
|
361
|
-
try {
|
|
362
|
-
const images = await getClipboardImages();
|
|
363
|
-
logDebug(`Clipboard check returned ${images.length} images`);
|
|
364
|
-
if (images.length > 0) {
|
|
365
|
-
const image = images[0];
|
|
366
|
-
logDebug(`Image found in clipboard: ${image.displayName}, ${image.sizeBytes} bytes`);
|
|
367
|
-
// Add to confirmed clipboard images (avoid duplicates)
|
|
368
|
-
setConfirmedClipboardImages(prev => {
|
|
369
|
-
const exists = prev.some(img => img.id === image.id);
|
|
370
|
-
if (exists)
|
|
371
|
-
return prev;
|
|
372
|
-
return [...prev, image];
|
|
373
|
-
});
|
|
374
|
-
// Mark all unvalidated positions as validated
|
|
375
|
-
setValidatedImagePositions(prev => [...prev, ...unvalidatedMatches]);
|
|
376
|
-
logDebug(`Validated ${unvalidatedMatches.length} #image occurrences`);
|
|
377
|
-
}
|
|
378
|
-
else {
|
|
379
|
-
logDebug('No image in clipboard - #image command not validated');
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
catch (error) {
|
|
383
|
-
logDebug(`Failed to check clipboard: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
384
|
-
}
|
|
385
|
-
};
|
|
386
|
-
validateImages();
|
|
387
|
-
}, [value, commandMode]);
|
|
388
318
|
const pushToUndoStack = () => {
|
|
389
319
|
setUndoStack(prev => [...prev, { value, cursorOffset }]);
|
|
390
320
|
setRedoStack([]); // Clear redo stack on new action
|
|
@@ -408,11 +338,21 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
408
338
|
// Handle slash command autocomplete navigation
|
|
409
339
|
if (slashAutocompleteVisible) {
|
|
410
340
|
if (key.downArrow) {
|
|
411
|
-
|
|
341
|
+
const newIndex = Math.min(slashAutocompleteSelectedIndex + 1, slashAutocompleteCommands.length - 1);
|
|
342
|
+
setSlashAutocompleteSelectedIndex(newIndex);
|
|
343
|
+
// Scroll down if selected is below visible window
|
|
344
|
+
if (newIndex >= slashAutocompleteScrollOffset + slashMaxVisibleItems) {
|
|
345
|
+
setSlashAutocompleteScrollOffset(newIndex - slashMaxVisibleItems + 1);
|
|
346
|
+
}
|
|
412
347
|
return;
|
|
413
348
|
}
|
|
414
349
|
if (key.upArrow) {
|
|
415
|
-
|
|
350
|
+
const newIndex = Math.max(slashAutocompleteSelectedIndex - 1, 0);
|
|
351
|
+
setSlashAutocompleteSelectedIndex(newIndex);
|
|
352
|
+
// Scroll up if selected is above visible window
|
|
353
|
+
if (newIndex < slashAutocompleteScrollOffset) {
|
|
354
|
+
setSlashAutocompleteScrollOffset(newIndex);
|
|
355
|
+
}
|
|
416
356
|
return;
|
|
417
357
|
}
|
|
418
358
|
if (key.return && input.length <= 1 && !key.shift && !key.ctrl) {
|
|
@@ -441,6 +381,21 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
441
381
|
setCursorOffset(newValue.length);
|
|
442
382
|
setSlashAutocompleteVisible(false);
|
|
443
383
|
}
|
|
384
|
+
else if (value.startsWith('/background-task ') || value.startsWith('/bkg ') || value.startsWith('/bg-task ')) {
|
|
385
|
+
// We're selecting a background-task subcommand
|
|
386
|
+
const prefix = value.startsWith('/bkg ') ? '/bkg ' : (value.startsWith('/bg-task ') ? '/bg-task ' : '/background-task ');
|
|
387
|
+
const newValue = `${prefix}${selected.name} `;
|
|
388
|
+
setValue(newValue);
|
|
389
|
+
setCursorOffset(newValue.length);
|
|
390
|
+
setSlashAutocompleteVisible(false);
|
|
391
|
+
}
|
|
392
|
+
else if (value.startsWith('/sync ')) {
|
|
393
|
+
// We're selecting a sync subcommand
|
|
394
|
+
const newValue = `/sync ${selected.name} `;
|
|
395
|
+
setValue(newValue);
|
|
396
|
+
setCursorOffset(newValue.length);
|
|
397
|
+
setSlashAutocompleteVisible(false);
|
|
398
|
+
}
|
|
444
399
|
else {
|
|
445
400
|
// Regular slash command, replace everything
|
|
446
401
|
const newValue = `/${selected.name} `;
|
|
@@ -453,6 +408,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
453
408
|
if (subcommandMatches.length > 0) {
|
|
454
409
|
setSlashAutocompleteCommands(subcommandMatches);
|
|
455
410
|
setSlashAutocompleteSelectedIndex(0);
|
|
411
|
+
setSlashAutocompleteScrollOffset(0);
|
|
456
412
|
// Keep autocomplete visible for subcommands
|
|
457
413
|
}
|
|
458
414
|
else {
|
|
@@ -464,6 +420,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
464
420
|
if (subcommandMatches.length > 0) {
|
|
465
421
|
setSlashAutocompleteCommands(subcommandMatches);
|
|
466
422
|
setSlashAutocompleteSelectedIndex(0);
|
|
423
|
+
setSlashAutocompleteScrollOffset(0);
|
|
467
424
|
// Keep autocomplete visible for subcommands
|
|
468
425
|
}
|
|
469
426
|
else {
|
|
@@ -475,6 +432,31 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
475
432
|
if (subcommandMatches.length > 0) {
|
|
476
433
|
setSlashAutocompleteCommands(subcommandMatches);
|
|
477
434
|
setSlashAutocompleteSelectedIndex(0);
|
|
435
|
+
setSlashAutocompleteScrollOffset(0);
|
|
436
|
+
// Keep autocomplete visible for subcommands
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
setSlashAutocompleteVisible(false);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
else if (selected.name === 'background-task' || selected.name === 'bkg' || selected.name === 'bg-task') {
|
|
443
|
+
const subcommandMatches = filterCommands('background-task ');
|
|
444
|
+
if (subcommandMatches.length > 0) {
|
|
445
|
+
setSlashAutocompleteCommands(subcommandMatches);
|
|
446
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
447
|
+
setSlashAutocompleteScrollOffset(0);
|
|
448
|
+
// Keep autocomplete visible for subcommands
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
setSlashAutocompleteVisible(false);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
else if (selected.name === 'sync') {
|
|
455
|
+
const subcommandMatches = filterCommands('sync ');
|
|
456
|
+
if (subcommandMatches.length > 0) {
|
|
457
|
+
setSlashAutocompleteCommands(subcommandMatches);
|
|
458
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
459
|
+
setSlashAutocompleteScrollOffset(0);
|
|
478
460
|
// Keep autocomplete visible for subcommands
|
|
479
461
|
}
|
|
480
462
|
else {
|
|
@@ -565,54 +547,36 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
565
547
|
return;
|
|
566
548
|
}
|
|
567
549
|
}
|
|
568
|
-
//
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
550
|
+
// Alt+V: Paste image from clipboard
|
|
551
|
+
// Detect Alt+V on Windows/Linux (key.meta is often Alt on Windows in Ink)
|
|
552
|
+
const isAltV = (key.meta && input === 'v') || (input === '√'); // '√' is Alt+V on some systems
|
|
553
|
+
if (isAltV && !commandMode) {
|
|
554
|
+
// Check clipboard for images asynchronously
|
|
555
|
+
(async () => {
|
|
556
|
+
try {
|
|
557
|
+
const images = await getClipboardImages();
|
|
558
|
+
if (images.length > 0) {
|
|
559
|
+
const image = images[0];
|
|
560
|
+
logDebug(`Alt+V: Image found in clipboard: ${image.displayName}, ${image.sizeBytes} bytes`);
|
|
561
|
+
// Add image to confirmed list with unique ID
|
|
562
|
+
setConfirmedClipboardImages(prev => [
|
|
563
|
+
...prev,
|
|
564
|
+
{
|
|
565
|
+
...image,
|
|
566
|
+
id: `${image.id}_${Date.now()}`
|
|
567
|
+
}
|
|
568
|
+
]);
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
logDebug('Alt+V: No image in clipboard');
|
|
572
|
+
}
|
|
591
573
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if (key.escape) {
|
|
595
|
-
setClipboardAutocompleteVisible(false);
|
|
596
|
-
setActiveClipboardStart(null);
|
|
597
|
-
return;
|
|
598
|
-
}
|
|
599
|
-
// Tab also selects
|
|
600
|
-
if (key.tab && !key.shift) {
|
|
601
|
-
if (clipboardImages.length > 0 && activeClipboardStart !== null) {
|
|
602
|
-
pushToUndoStack();
|
|
603
|
-
const beforeHash = value.slice(0, activeClipboardStart);
|
|
604
|
-
const afterCursor = value.slice(cursorOffset);
|
|
605
|
-
const newValue = beforeHash + '#image ' + afterCursor;
|
|
606
|
-
const newCursorPos = activeClipboardStart + 7;
|
|
607
|
-
setValue(newValue);
|
|
608
|
-
setCursorOffset(newCursorPos);
|
|
609
|
-
setClipboardAutocompleteVisible(false);
|
|
610
|
-
setActiveClipboardStart(null);
|
|
574
|
+
catch (error) {
|
|
575
|
+
logDebug(`Alt+V: Failed to check clipboard: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
611
576
|
}
|
|
612
|
-
|
|
613
|
-
|
|
577
|
+
})();
|
|
578
|
+
return;
|
|
614
579
|
}
|
|
615
|
-
// Clipboard image paste is handled via Ctrl+V below
|
|
616
580
|
// DELETE WORD BACKWARDS - Check this FIRST before standard backspace/delete
|
|
617
581
|
// Triggers on any of these conditions:
|
|
618
582
|
// 1. Ctrl+W (char code 23) - Standard Unix terminal shortcut
|
|
@@ -646,6 +610,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
646
610
|
setSlashAutocompleteCommands(matches);
|
|
647
611
|
setSlashAutocompleteVisible(true);
|
|
648
612
|
setSlashAutocompleteSelectedIndex(0);
|
|
613
|
+
setSlashAutocompleteScrollOffset(0);
|
|
649
614
|
}
|
|
650
615
|
else {
|
|
651
616
|
setSlashAutocompleteVisible(false);
|
|
@@ -659,6 +624,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
659
624
|
setSlashAutocompleteCommands(matches);
|
|
660
625
|
setSlashAutocompleteVisible(true);
|
|
661
626
|
setSlashAutocompleteSelectedIndex(0);
|
|
627
|
+
setSlashAutocompleteScrollOffset(0);
|
|
662
628
|
}
|
|
663
629
|
else {
|
|
664
630
|
setSlashAutocompleteVisible(false);
|
|
@@ -672,6 +638,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
672
638
|
setSlashAutocompleteCommands(matches);
|
|
673
639
|
setSlashAutocompleteVisible(true);
|
|
674
640
|
setSlashAutocompleteSelectedIndex(0);
|
|
641
|
+
setSlashAutocompleteScrollOffset(0);
|
|
675
642
|
}
|
|
676
643
|
else {
|
|
677
644
|
setSlashAutocompleteVisible(false);
|
|
@@ -685,6 +652,35 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
685
652
|
setSlashAutocompleteCommands(matches);
|
|
686
653
|
setSlashAutocompleteVisible(true);
|
|
687
654
|
setSlashAutocompleteSelectedIndex(0);
|
|
655
|
+
setSlashAutocompleteScrollOffset(0);
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
setSlashAutocompleteVisible(false);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
else if (newValue.startsWith('/background-task ') || newValue.startsWith('/bkg ') || newValue.startsWith('/bg-task ')) {
|
|
662
|
+
// Background-task subcommands
|
|
663
|
+
const fullQuery = newValue.slice(1);
|
|
664
|
+
const matches = filterCommands(fullQuery);
|
|
665
|
+
if (matches.length > 0) {
|
|
666
|
+
setSlashAutocompleteCommands(matches);
|
|
667
|
+
setSlashAutocompleteVisible(true);
|
|
668
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
669
|
+
setSlashAutocompleteScrollOffset(0);
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
setSlashAutocompleteVisible(false);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
else if (newValue.startsWith('/sync ')) {
|
|
676
|
+
// Sync subcommands
|
|
677
|
+
const fullQuery = newValue.slice(1);
|
|
678
|
+
const matches = filterCommands(fullQuery);
|
|
679
|
+
if (matches.length > 0) {
|
|
680
|
+
setSlashAutocompleteCommands(matches);
|
|
681
|
+
setSlashAutocompleteVisible(true);
|
|
682
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
683
|
+
setSlashAutocompleteScrollOffset(0);
|
|
688
684
|
}
|
|
689
685
|
else {
|
|
690
686
|
setSlashAutocompleteVisible(false);
|
|
@@ -705,32 +701,41 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
705
701
|
setTimeout(() => { ignoreNextChangeRef.current = false; }, 100);
|
|
706
702
|
return;
|
|
707
703
|
}
|
|
708
|
-
// Ctrl+D: Cycle modes (Agent -> Terminal -> Auto -> Agent)
|
|
704
|
+
// Ctrl+D: Cycle modes (Agent -> Terminal -> Background -> Auto -> Agent)
|
|
709
705
|
if (key.ctrl && input.toLowerCase() === 'd') {
|
|
710
706
|
if (onToggleCommandMode) {
|
|
711
707
|
ignoreNextChangeRef.current = true;
|
|
712
|
-
// Cycle Logic
|
|
713
|
-
if (!isAutoMode && !commandMode) {
|
|
708
|
+
// Cycle Logic: Agent -> Terminal -> Background -> Auto -> Agent
|
|
709
|
+
if (!isAutoMode && !commandMode && !backgroundMode) {
|
|
714
710
|
// Agent -> Terminal
|
|
715
711
|
onToggleCommandMode();
|
|
716
712
|
}
|
|
717
|
-
else if (!isAutoMode && commandMode) {
|
|
718
|
-
// Terminal ->
|
|
713
|
+
else if (!isAutoMode && commandMode && !backgroundMode) {
|
|
714
|
+
// Terminal -> Background
|
|
715
|
+
onToggleCommandMode(); // Exit terminal mode
|
|
716
|
+
if (onToggleBackgroundMode)
|
|
717
|
+
onToggleBackgroundMode(); // Enter background mode
|
|
718
|
+
}
|
|
719
|
+
else if (!isAutoMode && !commandMode && backgroundMode) {
|
|
720
|
+
// Background -> Auto
|
|
721
|
+
if (onToggleBackgroundMode)
|
|
722
|
+
onToggleBackgroundMode(); // Exit background mode
|
|
719
723
|
setIsAutoMode(true);
|
|
720
724
|
// Trigger initial detection for Auto mode
|
|
721
725
|
const intent = detectIntent(value);
|
|
722
726
|
setDetectedIntent(intent);
|
|
723
727
|
// Set command mode based on intent
|
|
724
|
-
if (intent === '
|
|
728
|
+
if (intent === 'command')
|
|
725
729
|
onToggleCommandMode();
|
|
726
|
-
// if intent is command, we are already in command mode
|
|
727
730
|
}
|
|
728
731
|
else if (isAutoMode) {
|
|
729
732
|
// Auto -> Agent
|
|
730
733
|
setIsAutoMode(false);
|
|
731
|
-
// Ensure we go back to Agent mode (commandMode = false)
|
|
734
|
+
// Ensure we go back to Agent mode (commandMode = false, backgroundMode = false)
|
|
732
735
|
if (commandMode)
|
|
733
736
|
onToggleCommandMode();
|
|
737
|
+
if (backgroundMode && onToggleBackgroundMode)
|
|
738
|
+
onToggleBackgroundMode();
|
|
734
739
|
}
|
|
735
740
|
setTimeout(() => { ignoreNextChangeRef.current = false; }, 100);
|
|
736
741
|
}
|
|
@@ -747,7 +752,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
747
752
|
setCursorOffset(value.length);
|
|
748
753
|
return;
|
|
749
754
|
}
|
|
750
|
-
// Note: Clipboard images are handled via
|
|
755
|
+
// Note: Clipboard images are handled via Alt+V keyboard shortcut
|
|
751
756
|
// DELETE CHAR - Only runs if Delete Word did NOT trigger
|
|
752
757
|
// Triggers on:
|
|
753
758
|
// 1. Backspace or Delete key flag is present
|
|
@@ -787,6 +792,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
787
792
|
setSlashAutocompleteCommands(matches);
|
|
788
793
|
setSlashAutocompleteVisible(true);
|
|
789
794
|
setSlashAutocompleteSelectedIndex(0);
|
|
795
|
+
setSlashAutocompleteScrollOffset(0);
|
|
790
796
|
}
|
|
791
797
|
else {
|
|
792
798
|
setSlashAutocompleteVisible(false);
|
|
@@ -800,6 +806,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
800
806
|
setSlashAutocompleteCommands(matches);
|
|
801
807
|
setSlashAutocompleteVisible(true);
|
|
802
808
|
setSlashAutocompleteSelectedIndex(0);
|
|
809
|
+
setSlashAutocompleteScrollOffset(0);
|
|
803
810
|
}
|
|
804
811
|
else {
|
|
805
812
|
setSlashAutocompleteVisible(false);
|
|
@@ -813,6 +820,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
813
820
|
setSlashAutocompleteCommands(matches);
|
|
814
821
|
setSlashAutocompleteVisible(true);
|
|
815
822
|
setSlashAutocompleteSelectedIndex(0);
|
|
823
|
+
setSlashAutocompleteScrollOffset(0);
|
|
816
824
|
}
|
|
817
825
|
else {
|
|
818
826
|
setSlashAutocompleteVisible(false);
|
|
@@ -826,6 +834,35 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
826
834
|
setSlashAutocompleteCommands(matches);
|
|
827
835
|
setSlashAutocompleteVisible(true);
|
|
828
836
|
setSlashAutocompleteSelectedIndex(0);
|
|
837
|
+
setSlashAutocompleteScrollOffset(0);
|
|
838
|
+
}
|
|
839
|
+
else {
|
|
840
|
+
setSlashAutocompleteVisible(false);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
else if (newValue.startsWith('/background-task ') || newValue.startsWith('/bkg ') || newValue.startsWith('/bg-task ')) {
|
|
844
|
+
// Background-task subcommands
|
|
845
|
+
const fullQuery = newValue.slice(1);
|
|
846
|
+
const matches = filterCommands(fullQuery);
|
|
847
|
+
if (matches.length > 0) {
|
|
848
|
+
setSlashAutocompleteCommands(matches);
|
|
849
|
+
setSlashAutocompleteVisible(true);
|
|
850
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
851
|
+
setSlashAutocompleteScrollOffset(0);
|
|
852
|
+
}
|
|
853
|
+
else {
|
|
854
|
+
setSlashAutocompleteVisible(false);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
else if (newValue.startsWith('/sync ')) {
|
|
858
|
+
// Sync subcommands
|
|
859
|
+
const fullQuery = newValue.slice(1);
|
|
860
|
+
const matches = filterCommands(fullQuery);
|
|
861
|
+
if (matches.length > 0) {
|
|
862
|
+
setSlashAutocompleteCommands(matches);
|
|
863
|
+
setSlashAutocompleteVisible(true);
|
|
864
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
865
|
+
setSlashAutocompleteScrollOffset(0);
|
|
829
866
|
}
|
|
830
867
|
else {
|
|
831
868
|
setSlashAutocompleteVisible(false);
|
|
@@ -864,6 +901,16 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
864
901
|
}
|
|
865
902
|
else {
|
|
866
903
|
// Enter: Submit
|
|
904
|
+
// Check if submission is allowed when disconnected
|
|
905
|
+
// Allowed: /exit command, OR Command Mode is active, OR detected intent is 'command'
|
|
906
|
+
const isExitCommand = value.trim() === '/exit';
|
|
907
|
+
const isCommandIntent = detectedIntent === 'command';
|
|
908
|
+
const isAllowedOffline = isConnected || isExitCommand || commandMode || isCommandIntent;
|
|
909
|
+
if (!isAllowedOffline) {
|
|
910
|
+
setRejectFlash(true);
|
|
911
|
+
setTimeout(() => setRejectFlash(false), 1000);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
867
914
|
handleSubmit();
|
|
868
915
|
}
|
|
869
916
|
return;
|
|
@@ -1059,21 +1106,28 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1059
1106
|
pushToUndoStack();
|
|
1060
1107
|
// Handle paste with newlines
|
|
1061
1108
|
const cleanedInput = input.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
1062
|
-
|
|
1063
|
-
|
|
1109
|
+
// Use refs to get the latest value and cursor position
|
|
1110
|
+
// This prevents stale closure issues when Ink calls useInput multiple times during paste
|
|
1111
|
+
const currentValue = valueRef.current;
|
|
1112
|
+
const currentCursorOffset = cursorOffsetRef.current;
|
|
1113
|
+
let newValue = currentValue;
|
|
1114
|
+
let newOffset = currentCursorOffset;
|
|
1064
1115
|
if (selection) {
|
|
1065
1116
|
const start = Math.min(selection.start, selection.end);
|
|
1066
1117
|
const end = Math.max(selection.start, selection.end);
|
|
1067
|
-
newValue =
|
|
1118
|
+
newValue = currentValue.slice(0, start) + cleanedInput + currentValue.slice(end);
|
|
1068
1119
|
newOffset = start + cleanedInput.length;
|
|
1069
1120
|
setSelection(null);
|
|
1070
1121
|
}
|
|
1071
1122
|
else {
|
|
1072
|
-
newValue =
|
|
1073
|
-
newOffset =
|
|
1123
|
+
newValue = currentValue.slice(0, currentCursorOffset) + cleanedInput + currentValue.slice(currentCursorOffset);
|
|
1124
|
+
newOffset = currentCursorOffset + cleanedInput.length;
|
|
1074
1125
|
}
|
|
1126
|
+
// Update refs immediately for subsequent paste chunks
|
|
1127
|
+
valueRef.current = newValue;
|
|
1128
|
+
cursorOffsetRef.current = newOffset;
|
|
1075
1129
|
setValue(newValue);
|
|
1076
|
-
|
|
1130
|
+
setCursorOffsetWithRef(newOffset);
|
|
1077
1131
|
// Reset history/completions
|
|
1078
1132
|
setHistoryIndex(-1);
|
|
1079
1133
|
setCompletions([]);
|
|
@@ -1086,6 +1140,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1086
1140
|
setSlashAutocompleteCommands(matches);
|
|
1087
1141
|
setSlashAutocompleteVisible(true);
|
|
1088
1142
|
setSlashAutocompleteSelectedIndex(0);
|
|
1143
|
+
setSlashAutocompleteScrollOffset(0);
|
|
1089
1144
|
}
|
|
1090
1145
|
else {
|
|
1091
1146
|
setSlashAutocompleteVisible(false);
|
|
@@ -1099,6 +1154,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1099
1154
|
setSlashAutocompleteCommands(matches);
|
|
1100
1155
|
setSlashAutocompleteVisible(true);
|
|
1101
1156
|
setSlashAutocompleteSelectedIndex(0);
|
|
1157
|
+
setSlashAutocompleteScrollOffset(0);
|
|
1102
1158
|
}
|
|
1103
1159
|
else {
|
|
1104
1160
|
setSlashAutocompleteVisible(false);
|
|
@@ -1112,6 +1168,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1112
1168
|
setSlashAutocompleteCommands(matches);
|
|
1113
1169
|
setSlashAutocompleteVisible(true);
|
|
1114
1170
|
setSlashAutocompleteSelectedIndex(0);
|
|
1171
|
+
setSlashAutocompleteScrollOffset(0);
|
|
1115
1172
|
}
|
|
1116
1173
|
else {
|
|
1117
1174
|
setSlashAutocompleteVisible(false);
|
|
@@ -1125,6 +1182,35 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1125
1182
|
setSlashAutocompleteCommands(matches);
|
|
1126
1183
|
setSlashAutocompleteVisible(true);
|
|
1127
1184
|
setSlashAutocompleteSelectedIndex(0);
|
|
1185
|
+
setSlashAutocompleteScrollOffset(0);
|
|
1186
|
+
}
|
|
1187
|
+
else {
|
|
1188
|
+
setSlashAutocompleteVisible(false);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
else if (newValue.startsWith('/background-task ') || newValue.startsWith('/bkg ') || newValue.startsWith('/bg-task ')) {
|
|
1192
|
+
// Background-task subcommands (when user types "/background-task ", "/bkg ", or "/bg-task ")
|
|
1193
|
+
const fullQuery = newValue.slice(1); // Remove leading "/", pass "background-task <subquery>" to filterCommands
|
|
1194
|
+
const matches = filterCommands(fullQuery);
|
|
1195
|
+
if (matches.length > 0) {
|
|
1196
|
+
setSlashAutocompleteCommands(matches);
|
|
1197
|
+
setSlashAutocompleteVisible(true);
|
|
1198
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
1199
|
+
setSlashAutocompleteScrollOffset(0);
|
|
1200
|
+
}
|
|
1201
|
+
else {
|
|
1202
|
+
setSlashAutocompleteVisible(false);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
else if (newValue.startsWith('/sync ')) {
|
|
1206
|
+
// Sync subcommands (when user types "/sync ")
|
|
1207
|
+
const fullQuery = newValue.slice(1); // Remove leading "/", pass "sync <subquery>" to filterCommands
|
|
1208
|
+
const matches = filterCommands(fullQuery);
|
|
1209
|
+
if (matches.length > 0) {
|
|
1210
|
+
setSlashAutocompleteCommands(matches);
|
|
1211
|
+
setSlashAutocompleteVisible(true);
|
|
1212
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
1213
|
+
setSlashAutocompleteScrollOffset(0);
|
|
1128
1214
|
}
|
|
1129
1215
|
else {
|
|
1130
1216
|
setSlashAutocompleteVisible(false);
|
|
@@ -1374,17 +1460,6 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1374
1460
|
if (isInConfirmedFileTag || isInActiveFileTag) {
|
|
1375
1461
|
return React.createElement(Text, { key: charIdx, color: "#00ccff", bold: true }, char);
|
|
1376
1462
|
}
|
|
1377
|
-
// Check if this character is part of a validated #image command
|
|
1378
|
-
let isInValidatedImage = false;
|
|
1379
|
-
for (const pos of validatedImagePositions) {
|
|
1380
|
-
if (absPos >= pos.start && absPos < pos.end) {
|
|
1381
|
-
isInValidatedImage = true;
|
|
1382
|
-
break;
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
if (isInValidatedImage) {
|
|
1386
|
-
return React.createElement(Text, { key: charIdx, color: "#ff69b4", bold: true }, char);
|
|
1387
|
-
}
|
|
1388
1463
|
return React.createElement(Text, { key: charIdx }, char);
|
|
1389
1464
|
});
|
|
1390
1465
|
// Handle cursor at end of line
|
|
@@ -1406,7 +1481,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1406
1481
|
}),
|
|
1407
1482
|
endLine < totalVisualLines && React.createElement(Text, { color: "gray" }, "\u2193 ...")));
|
|
1408
1483
|
};
|
|
1409
|
-
return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: rejectFlash ? "#ff3366" : (commandMode ? "#00cc66" : "#257aa5ff"), paddingX: 1, paddingY: 0, width: "100%" },
|
|
1484
|
+
return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: rejectFlash ? "#ff3366" : (backgroundMode ? "#9966ff" : (commandMode ? "#00cc66" : "#257aa5ff")), paddingX: 1, paddingY: 0, width: "100%" },
|
|
1410
1485
|
React.createElement(Box, { marginY: 1, justifyContent: "space-between", width: "100%" },
|
|
1411
1486
|
React.createElement(Box, null,
|
|
1412
1487
|
subshellContext && subshellContext.type !== 'local' && (React.createElement(Breadcrumbs, { context: subshellContext })),
|
|
@@ -1421,19 +1496,26 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1421
1496
|
isAutoMode ? (React.createElement(Text, null,
|
|
1422
1497
|
React.createElement(Text, { color: "#00ccff", bold: true }, "Auto ["),
|
|
1423
1498
|
detectedIntent === 'command' ? (React.createElement(Text, { color: "#00cc66", bold: true }, "Terminal")) : (React.createElement(Text, { color: "#00ccff" }, "Agent")),
|
|
1424
|
-
React.createElement(Text, { color: "#00ccff", bold: true }, "]"))) : commandMode ? (React.createElement(Text, { color: "#00cc66", bold: true }, "Terminal")) : planMode ? (React.createElement(Text, { color: "#ffaa00", bold: true }, "Plan")) : (React.createElement(Text, { color: "#00ccff" }, "Agent"))))),
|
|
1499
|
+
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"))))),
|
|
1500
|
+
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 },
|
|
1501
|
+
React.createElement(Text, { color: "#ff69b4", bold: true },
|
|
1502
|
+
"image_",
|
|
1503
|
+
index + 1)))))),
|
|
1425
1504
|
React.createElement(Box, { flexDirection: "row", width: "100%" },
|
|
1426
1505
|
React.createElement(Text, { color: "#666666" }, "> "),
|
|
1427
1506
|
renderInput()),
|
|
1428
1507
|
React.createElement(Box, { marginY: 1, justifyContent: "space-between", width: "100%" },
|
|
1429
|
-
React.createElement(Text, { color: "#666666", dimColor: true }, isAutoMode ? ('Ctrl+D to switch modes • Ctrl+T to auto-approve') : commandMode ? ('Ctrl+D to switch modes • Tab for autocomplete') : ('Ctrl+D to switch modes • Ctrl+T to auto-approve • Shift+Enter for new line')),
|
|
1508
|
+
React.createElement(Text, { color: "#666666", dimColor: true }, isAutoMode ? ('Ctrl+D to switch modes • Ctrl+T to auto-approve') : backgroundMode ? ('Ctrl+D to switch modes • Commands run in background') : commandMode ? ('Ctrl+D to switch modes • Tab for autocomplete') : ('Ctrl+D to switch modes • Ctrl+T to auto-approve • Shift+Enter for new line')),
|
|
1430
1509
|
React.createElement(Box, { gap: 1 },
|
|
1431
|
-
|
|
1510
|
+
backgroundTaskCount > 0 && (React.createElement(Text, { color: "#9966ff", bold: true },
|
|
1511
|
+
"[bkg tasks: ",
|
|
1512
|
+
backgroundTaskCount,
|
|
1513
|
+
"]")),
|
|
1514
|
+
!commandMode && !backgroundMode && planMode && (React.createElement(Text, { color: "#ffaa00", bold: true }, "[PLANNING]")),
|
|
1432
1515
|
!commandMode && autoAcceptMode ? (React.createElement(Text, { color: "#00cc66", bold: true }, "[AUTO-ACCEPT: ON]")) : !commandMode ? (React.createElement(Text, { color: "#666666", dimColor: true }, "[AUTO-ACCEPT: OFF]")) : null,
|
|
1433
1516
|
!commandMode && (React.createElement(Box, { marginLeft: 1 },
|
|
1434
1517
|
React.createElement(ContextWindowIndicator, { currentTokens: currentTokens, maxTokens: maxTokens }))))),
|
|
1435
|
-
slashAutocompleteVisible && (React.createElement(SlashCommandAutocomplete, { commands: slashAutocompleteCommands, selectedIndex: slashAutocompleteSelectedIndex })),
|
|
1436
|
-
fileTagAutocompleteVisible && (React.createElement(FileTagAutocomplete, { files: fileTagSuggestions, selectedIndex: fileTagSelectedIndex }))
|
|
1437
|
-
clipboardAutocompleteVisible && (React.createElement(ClipboardImageAutocomplete, { images: clipboardImages, selectedIndex: clipboardSelectedIndex, isLoading: clipboardLoading }))));
|
|
1518
|
+
slashAutocompleteVisible && slashMaxVisibleItems > 0 && (React.createElement(SlashCommandAutocomplete, { commands: slashAutocompleteCommands, selectedIndex: slashAutocompleteSelectedIndex, maxVisibleItems: slashMaxVisibleItems, scrollOffset: slashAutocompleteScrollOffset })),
|
|
1519
|
+
fileTagAutocompleteVisible && (React.createElement(FileTagAutocomplete, { files: fileTagSuggestions, selectedIndex: fileTagSelectedIndex }))));
|
|
1438
1520
|
});
|
|
1439
1521
|
//# sourceMappingURL=InputBox.js.map
|