wave-code 0.0.4 → 0.0.6
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/README.md +2 -2
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +4 -24
- package/dist/components/CommandSelector.js +4 -4
- package/dist/components/DiffViewer.d.ts +1 -1
- package/dist/components/DiffViewer.d.ts.map +1 -1
- package/dist/components/DiffViewer.js +15 -15
- package/dist/components/FileSelector.js +2 -2
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +46 -101
- package/dist/components/Markdown.d.ts +6 -0
- package/dist/components/Markdown.d.ts.map +1 -0
- package/dist/components/Markdown.js +22 -0
- package/dist/components/MessageItem.d.ts +9 -0
- package/dist/components/MessageItem.d.ts.map +1 -0
- package/dist/components/MessageItem.js +15 -0
- package/dist/components/MessageList.d.ts +1 -1
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +33 -32
- package/dist/components/SubagentBlock.d.ts +1 -2
- package/dist/components/SubagentBlock.d.ts.map +1 -1
- package/dist/components/SubagentBlock.js +29 -20
- package/dist/components/ToolResultDisplay.js +5 -5
- package/dist/contexts/useChat.d.ts +1 -0
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +29 -2
- package/dist/hooks/useInputManager.d.ts +93 -0
- package/dist/hooks/useInputManager.d.ts.map +1 -0
- package/dist/hooks/useInputManager.js +332 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -10
- package/dist/managers/InputManager.d.ts +171 -0
- package/dist/managers/InputManager.d.ts.map +1 -0
- package/dist/managers/InputManager.js +826 -0
- package/dist/print-cli.d.ts +8 -0
- package/dist/print-cli.d.ts.map +1 -0
- package/dist/print-cli.js +128 -0
- package/dist/utils/constants.d.ts +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/fileSearch.d.ts +20 -0
- package/dist/utils/fileSearch.d.ts.map +1 -0
- package/dist/utils/fileSearch.js +102 -0
- package/dist/utils/logger.js +3 -3
- package/dist/utils/usageSummary.d.ts +33 -0
- package/dist/utils/usageSummary.d.ts.map +1 -0
- package/dist/utils/usageSummary.js +154 -0
- package/package.json +10 -6
- package/src/components/ChatInterface.tsx +13 -43
- package/src/components/CommandSelector.tsx +5 -5
- package/src/components/DiffViewer.tsx +18 -16
- package/src/components/FileSelector.tsx +2 -2
- package/src/components/InputBox.tsx +78 -169
- package/src/components/Markdown.tsx +29 -0
- package/src/components/MessageItem.tsx +104 -0
- package/src/components/MessageList.tsx +142 -198
- package/src/components/SubagentBlock.tsx +56 -73
- package/src/components/ToolResultDisplay.tsx +6 -6
- package/src/contexts/useChat.tsx +34 -2
- package/src/hooks/useInputManager.ts +461 -0
- package/src/index.ts +20 -10
- package/src/managers/InputManager.ts +1132 -0
- package/src/print-cli.ts +160 -0
- package/src/utils/constants.ts +1 -1
- package/src/utils/fileSearch.ts +133 -0
- package/src/utils/logger.ts +3 -3
- package/src/utils/usageSummary.ts +234 -0
- package/dist/hooks/useBashHistorySelector.d.ts +0 -15
- package/dist/hooks/useBashHistorySelector.d.ts.map +0 -1
- package/dist/hooks/useBashHistorySelector.js +0 -61
- package/dist/hooks/useCommandSelector.d.ts +0 -24
- package/dist/hooks/useCommandSelector.d.ts.map +0 -1
- package/dist/hooks/useCommandSelector.js +0 -98
- package/dist/hooks/useFileSelector.d.ts +0 -16
- package/dist/hooks/useFileSelector.d.ts.map +0 -1
- package/dist/hooks/useFileSelector.js +0 -174
- package/dist/hooks/useImageManager.d.ts +0 -13
- package/dist/hooks/useImageManager.d.ts.map +0 -1
- package/dist/hooks/useImageManager.js +0 -46
- package/dist/hooks/useInputHistory.d.ts +0 -11
- package/dist/hooks/useInputHistory.d.ts.map +0 -1
- package/dist/hooks/useInputHistory.js +0 -64
- package/dist/hooks/useInputKeyboardHandler.d.ts +0 -83
- package/dist/hooks/useInputKeyboardHandler.d.ts.map +0 -1
- package/dist/hooks/useInputKeyboardHandler.js +0 -507
- package/dist/hooks/useInputState.d.ts +0 -14
- package/dist/hooks/useInputState.d.ts.map +0 -1
- package/dist/hooks/useInputState.js +0 -57
- package/dist/hooks/useMemoryTypeSelector.d.ts +0 -9
- package/dist/hooks/useMemoryTypeSelector.d.ts.map +0 -1
- package/dist/hooks/useMemoryTypeSelector.js +0 -27
- package/dist/plain-cli.d.ts +0 -7
- package/dist/plain-cli.d.ts.map +0 -1
- package/dist/plain-cli.js +0 -44
- package/src/hooks/useBashHistorySelector.ts +0 -77
- package/src/hooks/useCommandSelector.ts +0 -131
- package/src/hooks/useFileSelector.ts +0 -227
- package/src/hooks/useImageManager.ts +0 -64
- package/src/hooks/useInputHistory.ts +0 -74
- package/src/hooks/useInputKeyboardHandler.ts +0 -778
- package/src/hooks/useInputState.ts +0 -66
- package/src/hooks/useMemoryTypeSelector.ts +0 -40
- package/src/plain-cli.ts +0 -60
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
import { searchFiles as searchFilesUtil } from "../utils/fileSearch.js";
|
|
2
|
+
import { readClipboardImage } from "../utils/clipboard.js";
|
|
3
|
+
export class InputManager {
|
|
4
|
+
constructor(callbacks = {}) {
|
|
5
|
+
// Core input state
|
|
6
|
+
this.inputText = "";
|
|
7
|
+
this.cursorPosition = 0;
|
|
8
|
+
// File selector state
|
|
9
|
+
this.showFileSelector = false;
|
|
10
|
+
this.atPosition = -1;
|
|
11
|
+
this.fileSearchQuery = "";
|
|
12
|
+
this.filteredFiles = [];
|
|
13
|
+
this.fileSearchDebounceTimer = null;
|
|
14
|
+
// Command selector state
|
|
15
|
+
this.showCommandSelector = false;
|
|
16
|
+
this.slashPosition = -1;
|
|
17
|
+
this.commandSearchQuery = "";
|
|
18
|
+
// Bash history selector state
|
|
19
|
+
this.showBashHistorySelector = false;
|
|
20
|
+
this.exclamationPosition = -1;
|
|
21
|
+
this.bashHistorySearchQuery = "";
|
|
22
|
+
// Memory type selector state
|
|
23
|
+
this.showMemoryTypeSelector = false;
|
|
24
|
+
this.memoryMessage = "";
|
|
25
|
+
// Input history state
|
|
26
|
+
this.userInputHistory = [];
|
|
27
|
+
this.historyIndex = -1;
|
|
28
|
+
this.historyBuffer = "";
|
|
29
|
+
// Paste debounce state
|
|
30
|
+
this.pasteDebounceTimer = null;
|
|
31
|
+
this.pasteBuffer = "";
|
|
32
|
+
this.initialPasteCursorPosition = 0;
|
|
33
|
+
this.isPasting = false;
|
|
34
|
+
// Long text compression state
|
|
35
|
+
this.longTextCounter = 0;
|
|
36
|
+
this.longTextMap = new Map();
|
|
37
|
+
// Image management state
|
|
38
|
+
this.attachedImages = [];
|
|
39
|
+
this.imageIdCounter = 1;
|
|
40
|
+
// Additional UI state
|
|
41
|
+
this.showBashManager = false;
|
|
42
|
+
this.showMcpManager = false;
|
|
43
|
+
// Flag to prevent handleInput conflicts when selector selection occurs
|
|
44
|
+
this.selectorJustUsed = false;
|
|
45
|
+
this.callbacks = callbacks;
|
|
46
|
+
}
|
|
47
|
+
// Update callbacks
|
|
48
|
+
updateCallbacks(callbacks) {
|
|
49
|
+
this.callbacks = { ...this.callbacks, ...callbacks };
|
|
50
|
+
}
|
|
51
|
+
// Core input methods
|
|
52
|
+
getInputText() {
|
|
53
|
+
return this.inputText;
|
|
54
|
+
}
|
|
55
|
+
setInputText(text) {
|
|
56
|
+
this.inputText = text;
|
|
57
|
+
this.callbacks.onInputTextChange?.(text);
|
|
58
|
+
}
|
|
59
|
+
getCursorPosition() {
|
|
60
|
+
return this.cursorPosition;
|
|
61
|
+
}
|
|
62
|
+
setCursorPosition(position) {
|
|
63
|
+
this.cursorPosition = Math.max(0, Math.min(this.inputText.length, position));
|
|
64
|
+
this.callbacks.onCursorPositionChange?.(this.cursorPosition);
|
|
65
|
+
}
|
|
66
|
+
insertTextAtCursor(text, callback) {
|
|
67
|
+
const beforeCursor = this.inputText.substring(0, this.cursorPosition);
|
|
68
|
+
const afterCursor = this.inputText.substring(this.cursorPosition);
|
|
69
|
+
const newText = beforeCursor + text + afterCursor;
|
|
70
|
+
const newCursorPosition = this.cursorPosition + text.length;
|
|
71
|
+
this.inputText = newText;
|
|
72
|
+
this.cursorPosition = newCursorPosition;
|
|
73
|
+
this.callbacks.onInputTextChange?.(newText);
|
|
74
|
+
this.callbacks.onCursorPositionChange?.(newCursorPosition);
|
|
75
|
+
callback?.(newText, newCursorPosition);
|
|
76
|
+
}
|
|
77
|
+
deleteCharAtCursor(callback) {
|
|
78
|
+
if (this.cursorPosition > 0) {
|
|
79
|
+
const beforeCursor = this.inputText.substring(0, this.cursorPosition - 1);
|
|
80
|
+
const afterCursor = this.inputText.substring(this.cursorPosition);
|
|
81
|
+
const newText = beforeCursor + afterCursor;
|
|
82
|
+
const newCursorPosition = this.cursorPosition - 1;
|
|
83
|
+
this.inputText = newText;
|
|
84
|
+
this.cursorPosition = newCursorPosition;
|
|
85
|
+
this.callbacks.onInputTextChange?.(newText);
|
|
86
|
+
this.callbacks.onCursorPositionChange?.(newCursorPosition);
|
|
87
|
+
callback?.(newText, newCursorPosition);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
clearInput() {
|
|
91
|
+
this.inputText = "";
|
|
92
|
+
this.cursorPosition = 0;
|
|
93
|
+
this.callbacks.onInputTextChange?.("");
|
|
94
|
+
this.callbacks.onCursorPositionChange?.(0);
|
|
95
|
+
}
|
|
96
|
+
moveCursorLeft() {
|
|
97
|
+
this.setCursorPosition(this.cursorPosition - 1);
|
|
98
|
+
}
|
|
99
|
+
moveCursorRight() {
|
|
100
|
+
this.setCursorPosition(this.cursorPosition + 1);
|
|
101
|
+
}
|
|
102
|
+
moveCursorToStart() {
|
|
103
|
+
this.setCursorPosition(0);
|
|
104
|
+
}
|
|
105
|
+
moveCursorToEnd() {
|
|
106
|
+
this.setCursorPosition(this.inputText.length);
|
|
107
|
+
}
|
|
108
|
+
// File selector methods
|
|
109
|
+
async searchFiles(query) {
|
|
110
|
+
try {
|
|
111
|
+
const fileItems = await searchFilesUtil(query);
|
|
112
|
+
this.filteredFiles = fileItems;
|
|
113
|
+
this.callbacks.onFileSelectorStateChange?.(this.showFileSelector, this.filteredFiles, this.fileSearchQuery, this.atPosition);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
console.error("File search error:", error);
|
|
117
|
+
this.filteredFiles = [];
|
|
118
|
+
this.callbacks.onFileSelectorStateChange?.(this.showFileSelector, [], this.fileSearchQuery, this.atPosition);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
debouncedSearchFiles(query) {
|
|
122
|
+
if (this.fileSearchDebounceTimer) {
|
|
123
|
+
clearTimeout(this.fileSearchDebounceTimer);
|
|
124
|
+
}
|
|
125
|
+
const debounceDelay = parseInt(process.env.FILE_SELECTOR_DEBOUNCE_MS || "300", 10);
|
|
126
|
+
this.fileSearchDebounceTimer = setTimeout(() => {
|
|
127
|
+
this.searchFiles(query);
|
|
128
|
+
}, debounceDelay);
|
|
129
|
+
}
|
|
130
|
+
activateFileSelector(position) {
|
|
131
|
+
this.showFileSelector = true;
|
|
132
|
+
this.atPosition = position;
|
|
133
|
+
this.fileSearchQuery = "";
|
|
134
|
+
this.filteredFiles = [];
|
|
135
|
+
// Immediately trigger search to display initial file list
|
|
136
|
+
this.searchFiles("");
|
|
137
|
+
this.callbacks.onFileSelectorStateChange?.(true, this.filteredFiles, "", position);
|
|
138
|
+
}
|
|
139
|
+
updateFileSearchQuery(query) {
|
|
140
|
+
this.fileSearchQuery = query;
|
|
141
|
+
this.debouncedSearchFiles(query);
|
|
142
|
+
}
|
|
143
|
+
handleFileSelect(filePath) {
|
|
144
|
+
if (this.atPosition >= 0) {
|
|
145
|
+
const beforeAt = this.inputText.substring(0, this.atPosition);
|
|
146
|
+
const afterQuery = this.inputText.substring(this.cursorPosition);
|
|
147
|
+
const newInput = beforeAt + `${filePath} ` + afterQuery;
|
|
148
|
+
const newCursorPosition = beforeAt.length + filePath.length + 1;
|
|
149
|
+
this.inputText = newInput;
|
|
150
|
+
this.cursorPosition = newCursorPosition;
|
|
151
|
+
this.callbacks.onInputTextChange?.(newInput);
|
|
152
|
+
this.callbacks.onCursorPositionChange?.(newCursorPosition);
|
|
153
|
+
// Cancel file selector AFTER updating the input
|
|
154
|
+
this.handleCancelFileSelect();
|
|
155
|
+
// Set flag to prevent handleInput from processing the same Enter key
|
|
156
|
+
this.selectorJustUsed = true;
|
|
157
|
+
// Reset flag after a short delay
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
this.selectorJustUsed = false;
|
|
160
|
+
}, 0);
|
|
161
|
+
return { newInput, newCursorPosition };
|
|
162
|
+
}
|
|
163
|
+
return { newInput: this.inputText, newCursorPosition: this.cursorPosition };
|
|
164
|
+
}
|
|
165
|
+
handleCancelFileSelect() {
|
|
166
|
+
this.showFileSelector = false;
|
|
167
|
+
this.atPosition = -1;
|
|
168
|
+
this.fileSearchQuery = "";
|
|
169
|
+
this.filteredFiles = [];
|
|
170
|
+
this.callbacks.onFileSelectorStateChange?.(false, [], "", -1);
|
|
171
|
+
}
|
|
172
|
+
checkForAtDeletion(cursorPosition) {
|
|
173
|
+
if (this.showFileSelector && cursorPosition <= this.atPosition) {
|
|
174
|
+
this.handleCancelFileSelect();
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
// Command selector methods
|
|
180
|
+
activateCommandSelector(position) {
|
|
181
|
+
this.showCommandSelector = true;
|
|
182
|
+
this.slashPosition = position;
|
|
183
|
+
this.commandSearchQuery = "";
|
|
184
|
+
this.callbacks.onCommandSelectorStateChange?.(true, "", position);
|
|
185
|
+
}
|
|
186
|
+
updateCommandSearchQuery(query) {
|
|
187
|
+
this.commandSearchQuery = query;
|
|
188
|
+
this.callbacks.onCommandSelectorStateChange?.(this.showCommandSelector, query, this.slashPosition);
|
|
189
|
+
}
|
|
190
|
+
handleCommandSelect(command) {
|
|
191
|
+
if (this.slashPosition >= 0) {
|
|
192
|
+
// Replace command part, keep other content
|
|
193
|
+
const beforeSlash = this.inputText.substring(0, this.slashPosition);
|
|
194
|
+
const afterQuery = this.inputText.substring(this.cursorPosition);
|
|
195
|
+
const newInput = beforeSlash + afterQuery;
|
|
196
|
+
const newCursorPosition = beforeSlash.length;
|
|
197
|
+
this.inputText = newInput;
|
|
198
|
+
this.cursorPosition = newCursorPosition;
|
|
199
|
+
// Execute command asynchronously
|
|
200
|
+
(async () => {
|
|
201
|
+
// First check if it's an agent command
|
|
202
|
+
let commandExecuted = false;
|
|
203
|
+
if (this.callbacks.onSendMessage &&
|
|
204
|
+
this.callbacks.onHasSlashCommand?.(command)) {
|
|
205
|
+
// Execute complete command (replace partial input with complete command name)
|
|
206
|
+
const fullCommand = `/${command}`;
|
|
207
|
+
try {
|
|
208
|
+
await this.callbacks.onSendMessage(fullCommand);
|
|
209
|
+
commandExecuted = true;
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
console.error("Failed to execute slash command:", error);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// If not an agent command or execution failed, check local commands
|
|
216
|
+
if (!commandExecuted) {
|
|
217
|
+
if (command === "bashes" && this.callbacks.onShowBashManager) {
|
|
218
|
+
this.callbacks.onShowBashManager();
|
|
219
|
+
commandExecuted = true;
|
|
220
|
+
}
|
|
221
|
+
else if (command === "mcp" && this.callbacks.onShowMcpManager) {
|
|
222
|
+
this.callbacks.onShowMcpManager();
|
|
223
|
+
commandExecuted = true;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
})();
|
|
227
|
+
this.handleCancelCommandSelect();
|
|
228
|
+
// Set flag to prevent handleInput from processing the same Enter key
|
|
229
|
+
this.selectorJustUsed = true;
|
|
230
|
+
setTimeout(() => {
|
|
231
|
+
this.selectorJustUsed = false;
|
|
232
|
+
}, 0);
|
|
233
|
+
this.callbacks.onInputTextChange?.(newInput);
|
|
234
|
+
this.callbacks.onCursorPositionChange?.(newCursorPosition);
|
|
235
|
+
return { newInput, newCursorPosition };
|
|
236
|
+
}
|
|
237
|
+
return { newInput: this.inputText, newCursorPosition: this.cursorPosition };
|
|
238
|
+
}
|
|
239
|
+
handleCommandInsert(command) {
|
|
240
|
+
if (this.slashPosition >= 0) {
|
|
241
|
+
const beforeSlash = this.inputText.substring(0, this.slashPosition);
|
|
242
|
+
const afterQuery = this.inputText.substring(this.cursorPosition);
|
|
243
|
+
const newInput = beforeSlash + `/${command} ` + afterQuery;
|
|
244
|
+
const newCursorPosition = beforeSlash.length + command.length + 2;
|
|
245
|
+
this.inputText = newInput;
|
|
246
|
+
this.cursorPosition = newCursorPosition;
|
|
247
|
+
this.handleCancelCommandSelect();
|
|
248
|
+
// Set flag to prevent handleInput from processing the same Enter key
|
|
249
|
+
this.selectorJustUsed = true;
|
|
250
|
+
setTimeout(() => {
|
|
251
|
+
this.selectorJustUsed = false;
|
|
252
|
+
}, 0);
|
|
253
|
+
this.callbacks.onInputTextChange?.(newInput);
|
|
254
|
+
this.callbacks.onCursorPositionChange?.(newCursorPosition);
|
|
255
|
+
return { newInput, newCursorPosition };
|
|
256
|
+
}
|
|
257
|
+
return { newInput: this.inputText, newCursorPosition: this.cursorPosition };
|
|
258
|
+
}
|
|
259
|
+
handleCancelCommandSelect() {
|
|
260
|
+
this.showCommandSelector = false;
|
|
261
|
+
this.slashPosition = -1;
|
|
262
|
+
this.commandSearchQuery = "";
|
|
263
|
+
this.callbacks.onCommandSelectorStateChange?.(false, "", -1);
|
|
264
|
+
}
|
|
265
|
+
checkForSlashDeletion(cursorPosition) {
|
|
266
|
+
if (this.showCommandSelector && cursorPosition <= this.slashPosition) {
|
|
267
|
+
this.handleCancelCommandSelect();
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
// Bash history selector methods
|
|
273
|
+
activateBashHistorySelector(position) {
|
|
274
|
+
this.showBashHistorySelector = true;
|
|
275
|
+
this.exclamationPosition = position;
|
|
276
|
+
this.bashHistorySearchQuery = "";
|
|
277
|
+
this.callbacks.onBashHistorySelectorStateChange?.(true, "", position);
|
|
278
|
+
}
|
|
279
|
+
updateBashHistorySearchQuery(query) {
|
|
280
|
+
this.bashHistorySearchQuery = query;
|
|
281
|
+
this.callbacks.onBashHistorySelectorStateChange?.(this.showBashHistorySelector, query, this.exclamationPosition);
|
|
282
|
+
}
|
|
283
|
+
handleBashHistorySelect(command) {
|
|
284
|
+
if (this.exclamationPosition >= 0) {
|
|
285
|
+
const beforeExclamation = this.inputText.substring(0, this.exclamationPosition);
|
|
286
|
+
const afterQuery = this.inputText.substring(this.cursorPosition);
|
|
287
|
+
const newInput = beforeExclamation + `!${command}` + afterQuery;
|
|
288
|
+
const newCursorPosition = beforeExclamation.length + command.length + 1;
|
|
289
|
+
this.inputText = newInput;
|
|
290
|
+
this.cursorPosition = newCursorPosition;
|
|
291
|
+
this.handleCancelBashHistorySelect();
|
|
292
|
+
// Set flag to prevent handleInput from processing the same Enter key
|
|
293
|
+
this.selectorJustUsed = true;
|
|
294
|
+
setTimeout(() => {
|
|
295
|
+
this.selectorJustUsed = false;
|
|
296
|
+
}, 0);
|
|
297
|
+
this.callbacks.onInputTextChange?.(newInput);
|
|
298
|
+
this.callbacks.onCursorPositionChange?.(newCursorPosition);
|
|
299
|
+
return { newInput, newCursorPosition };
|
|
300
|
+
}
|
|
301
|
+
return { newInput: this.inputText, newCursorPosition: this.cursorPosition };
|
|
302
|
+
}
|
|
303
|
+
handleCancelBashHistorySelect() {
|
|
304
|
+
this.showBashHistorySelector = false;
|
|
305
|
+
this.exclamationPosition = -1;
|
|
306
|
+
this.bashHistorySearchQuery = "";
|
|
307
|
+
this.callbacks.onBashHistorySelectorStateChange?.(false, "", -1);
|
|
308
|
+
}
|
|
309
|
+
handleBashHistoryExecute(command) {
|
|
310
|
+
this.showBashHistorySelector = false;
|
|
311
|
+
this.exclamationPosition = -1;
|
|
312
|
+
this.bashHistorySearchQuery = "";
|
|
313
|
+
this.callbacks.onBashHistorySelectorStateChange?.(false, "", -1);
|
|
314
|
+
return command; // Return command to execute
|
|
315
|
+
}
|
|
316
|
+
handleBashHistoryExecuteAndSend(command) {
|
|
317
|
+
const commandToExecute = this.handleBashHistoryExecute(command);
|
|
318
|
+
// Clear input box and execute command, ensure command starts with !
|
|
319
|
+
const bashCommand = commandToExecute.startsWith("!")
|
|
320
|
+
? commandToExecute
|
|
321
|
+
: `!${commandToExecute}`;
|
|
322
|
+
this.clearInput();
|
|
323
|
+
this.callbacks.onSendMessage?.(bashCommand);
|
|
324
|
+
}
|
|
325
|
+
checkForExclamationDeletion(cursorPosition) {
|
|
326
|
+
if (this.showBashHistorySelector &&
|
|
327
|
+
cursorPosition <= this.exclamationPosition) {
|
|
328
|
+
this.handleCancelBashHistorySelect();
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
// Memory type selector methods
|
|
334
|
+
activateMemoryTypeSelector(message) {
|
|
335
|
+
this.showMemoryTypeSelector = true;
|
|
336
|
+
this.memoryMessage = message;
|
|
337
|
+
this.callbacks.onMemoryTypeSelectorStateChange?.(true, message);
|
|
338
|
+
}
|
|
339
|
+
async handleMemoryTypeSelect(type) {
|
|
340
|
+
const currentMessage = this.inputText.trim();
|
|
341
|
+
if (currentMessage.startsWith("#")) {
|
|
342
|
+
await this.callbacks.onSaveMemory?.(currentMessage, type);
|
|
343
|
+
}
|
|
344
|
+
// Close the selector
|
|
345
|
+
this.showMemoryTypeSelector = false;
|
|
346
|
+
this.memoryMessage = "";
|
|
347
|
+
this.callbacks.onMemoryTypeSelectorStateChange?.(false, "");
|
|
348
|
+
// Clear input box
|
|
349
|
+
this.clearInput();
|
|
350
|
+
}
|
|
351
|
+
handleCancelMemoryTypeSelect() {
|
|
352
|
+
this.showMemoryTypeSelector = false;
|
|
353
|
+
this.memoryMessage = "";
|
|
354
|
+
this.callbacks.onMemoryTypeSelectorStateChange?.(false, "");
|
|
355
|
+
}
|
|
356
|
+
// Input history methods
|
|
357
|
+
setUserInputHistory(history) {
|
|
358
|
+
this.userInputHistory = history;
|
|
359
|
+
}
|
|
360
|
+
navigateHistory(direction, currentInput) {
|
|
361
|
+
if (this.historyIndex === -1) {
|
|
362
|
+
this.historyBuffer = currentInput;
|
|
363
|
+
}
|
|
364
|
+
if (direction === "up") {
|
|
365
|
+
if (this.historyIndex < this.userInputHistory.length - 1) {
|
|
366
|
+
this.historyIndex++;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
// Down direction
|
|
371
|
+
if (this.historyIndex > 0) {
|
|
372
|
+
this.historyIndex--;
|
|
373
|
+
}
|
|
374
|
+
else if (this.historyIndex === 0) {
|
|
375
|
+
// Go from first history item to draft
|
|
376
|
+
this.historyIndex = -1;
|
|
377
|
+
}
|
|
378
|
+
else if (this.historyIndex === -1) {
|
|
379
|
+
// Go from draft to empty (beyond history bottom)
|
|
380
|
+
this.historyIndex = -2;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
let newInput;
|
|
384
|
+
if (this.historyIndex === -1) {
|
|
385
|
+
newInput = this.historyBuffer;
|
|
386
|
+
}
|
|
387
|
+
else if (this.historyIndex === -2) {
|
|
388
|
+
// Beyond history bottom, clear input
|
|
389
|
+
newInput = "";
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
const historyItem = this.userInputHistory[this.userInputHistory.length - 1 - this.historyIndex];
|
|
393
|
+
newInput = historyItem || "";
|
|
394
|
+
}
|
|
395
|
+
const newCursorPosition = newInput.length;
|
|
396
|
+
this.inputText = newInput;
|
|
397
|
+
this.cursorPosition = newCursorPosition;
|
|
398
|
+
this.callbacks.onInputTextChange?.(newInput);
|
|
399
|
+
this.callbacks.onCursorPositionChange?.(newCursorPosition);
|
|
400
|
+
return { newInput, newCursorPosition };
|
|
401
|
+
}
|
|
402
|
+
resetHistoryNavigation() {
|
|
403
|
+
this.historyIndex = -1;
|
|
404
|
+
this.historyBuffer = "";
|
|
405
|
+
}
|
|
406
|
+
// Getter methods for state
|
|
407
|
+
isFileSelectorActive() {
|
|
408
|
+
return this.showFileSelector;
|
|
409
|
+
}
|
|
410
|
+
isCommandSelectorActive() {
|
|
411
|
+
return this.showCommandSelector;
|
|
412
|
+
}
|
|
413
|
+
isBashHistorySelectorActive() {
|
|
414
|
+
return this.showBashHistorySelector;
|
|
415
|
+
}
|
|
416
|
+
isMemoryTypeSelectorActive() {
|
|
417
|
+
return this.showMemoryTypeSelector;
|
|
418
|
+
}
|
|
419
|
+
getFileSelectorState() {
|
|
420
|
+
return {
|
|
421
|
+
show: this.showFileSelector,
|
|
422
|
+
files: this.filteredFiles,
|
|
423
|
+
query: this.fileSearchQuery,
|
|
424
|
+
position: this.atPosition,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
getCommandSelectorState() {
|
|
428
|
+
return {
|
|
429
|
+
show: this.showCommandSelector,
|
|
430
|
+
query: this.commandSearchQuery,
|
|
431
|
+
position: this.slashPosition,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
getBashHistorySelectorState() {
|
|
435
|
+
return {
|
|
436
|
+
show: this.showBashHistorySelector,
|
|
437
|
+
query: this.bashHistorySearchQuery,
|
|
438
|
+
position: this.exclamationPosition,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
getMemoryTypeSelectorState() {
|
|
442
|
+
return {
|
|
443
|
+
show: this.showMemoryTypeSelector,
|
|
444
|
+
message: this.memoryMessage,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
// Update search queries for active selectors
|
|
448
|
+
updateSearchQueriesForActiveSelectors(inputText, cursorPosition) {
|
|
449
|
+
if (this.showFileSelector && this.atPosition >= 0) {
|
|
450
|
+
const queryStart = this.atPosition + 1;
|
|
451
|
+
const queryEnd = cursorPosition;
|
|
452
|
+
const newQuery = inputText.substring(queryStart, queryEnd);
|
|
453
|
+
this.updateFileSearchQuery(newQuery);
|
|
454
|
+
}
|
|
455
|
+
else if (this.showCommandSelector && this.slashPosition >= 0) {
|
|
456
|
+
const queryStart = this.slashPosition + 1;
|
|
457
|
+
const queryEnd = cursorPosition;
|
|
458
|
+
const newQuery = inputText.substring(queryStart, queryEnd);
|
|
459
|
+
this.updateCommandSearchQuery(newQuery);
|
|
460
|
+
}
|
|
461
|
+
else if (this.showBashHistorySelector && this.exclamationPosition >= 0) {
|
|
462
|
+
const queryStart = this.exclamationPosition + 1;
|
|
463
|
+
const queryEnd = cursorPosition;
|
|
464
|
+
const newQuery = inputText.substring(queryStart, queryEnd);
|
|
465
|
+
this.updateBashHistorySearchQuery(newQuery);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// Handle special character input that might trigger selectors
|
|
469
|
+
handleSpecialCharInput(char) {
|
|
470
|
+
if (char === "@") {
|
|
471
|
+
this.activateFileSelector(this.cursorPosition - 1);
|
|
472
|
+
}
|
|
473
|
+
else if (char === "/" && !this.showFileSelector) {
|
|
474
|
+
// Don't activate command selector when file selector is active
|
|
475
|
+
this.activateCommandSelector(this.cursorPosition - 1);
|
|
476
|
+
}
|
|
477
|
+
else if (char === "!" && this.cursorPosition === 1) {
|
|
478
|
+
this.activateBashHistorySelector(0);
|
|
479
|
+
}
|
|
480
|
+
else if (char === "#" && this.cursorPosition === 1) {
|
|
481
|
+
// Memory message detection will be handled in submit
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
// Update search queries for active selectors
|
|
485
|
+
this.updateSearchQueriesForActiveSelectors(this.inputText, this.cursorPosition);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
// Long text compression methods
|
|
489
|
+
generateCompressedText(originalText) {
|
|
490
|
+
this.longTextCounter += 1;
|
|
491
|
+
const compressedLabel = `[LongText#${this.longTextCounter}]`;
|
|
492
|
+
this.longTextMap.set(compressedLabel, originalText);
|
|
493
|
+
return compressedLabel;
|
|
494
|
+
}
|
|
495
|
+
expandLongTextPlaceholders(text) {
|
|
496
|
+
let expandedText = text;
|
|
497
|
+
const longTextRegex = /\[LongText#(\d+)\]/g;
|
|
498
|
+
const matches = [...text.matchAll(longTextRegex)];
|
|
499
|
+
for (const match of matches) {
|
|
500
|
+
const placeholder = match[0];
|
|
501
|
+
const originalText = this.longTextMap.get(placeholder);
|
|
502
|
+
if (originalText) {
|
|
503
|
+
expandedText = expandedText.replace(placeholder, originalText);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return expandedText;
|
|
507
|
+
}
|
|
508
|
+
clearLongTextMap() {
|
|
509
|
+
this.longTextMap.clear();
|
|
510
|
+
}
|
|
511
|
+
// Paste handling methods
|
|
512
|
+
handlePasteInput(input) {
|
|
513
|
+
const inputString = input;
|
|
514
|
+
// Detect if it's a paste operation (input contains multiple characters or newlines)
|
|
515
|
+
const isPasteOperation = inputString.length > 1 ||
|
|
516
|
+
inputString.includes("\n") ||
|
|
517
|
+
inputString.includes("\r");
|
|
518
|
+
if (isPasteOperation) {
|
|
519
|
+
// Start or continue the debounce handling for paste operation
|
|
520
|
+
if (!this.isPasting) {
|
|
521
|
+
// Start new paste operation
|
|
522
|
+
this.isPasting = true;
|
|
523
|
+
this.pasteBuffer = inputString;
|
|
524
|
+
this.initialPasteCursorPosition = this.cursorPosition;
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
// Continue paste operation, add new input to buffer
|
|
528
|
+
this.pasteBuffer += inputString;
|
|
529
|
+
}
|
|
530
|
+
// Clear previous timer
|
|
531
|
+
if (this.pasteDebounceTimer) {
|
|
532
|
+
clearTimeout(this.pasteDebounceTimer);
|
|
533
|
+
}
|
|
534
|
+
// Set new timer, support environment variable configuration
|
|
535
|
+
const pasteDebounceDelay = parseInt(process.env.PASTE_DEBOUNCE_MS || "30", 10);
|
|
536
|
+
this.pasteDebounceTimer = setTimeout(() => {
|
|
537
|
+
// Process all paste content in buffer
|
|
538
|
+
let processedInput = this.pasteBuffer.replace(/\r/g, "\n");
|
|
539
|
+
// Check if long text compression is needed (over 200 characters)
|
|
540
|
+
if (processedInput.length > 200) {
|
|
541
|
+
const originalText = processedInput;
|
|
542
|
+
const compressedLabel = this.generateCompressedText(originalText);
|
|
543
|
+
processedInput = compressedLabel;
|
|
544
|
+
}
|
|
545
|
+
this.insertTextAtCursor(processedInput);
|
|
546
|
+
this.callbacks.onResetHistoryNavigation?.();
|
|
547
|
+
// Reset paste state
|
|
548
|
+
this.isPasting = false;
|
|
549
|
+
this.pasteBuffer = "";
|
|
550
|
+
this.pasteDebounceTimer = null;
|
|
551
|
+
}, pasteDebounceDelay);
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
// Handle single character input
|
|
555
|
+
let char = inputString;
|
|
556
|
+
// Check if it's Chinese exclamation mark, convert to English if at beginning
|
|
557
|
+
if (char === "!" && this.cursorPosition === 0) {
|
|
558
|
+
char = "!";
|
|
559
|
+
}
|
|
560
|
+
this.callbacks.onResetHistoryNavigation?.();
|
|
561
|
+
this.insertTextAtCursor(char, () => {
|
|
562
|
+
// Handle special character input - this will manage all selectors
|
|
563
|
+
this.handleSpecialCharInput(char);
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
// Image management methods
|
|
568
|
+
addImage(imagePath, mimeType) {
|
|
569
|
+
const newImage = {
|
|
570
|
+
id: this.imageIdCounter,
|
|
571
|
+
path: imagePath,
|
|
572
|
+
mimeType,
|
|
573
|
+
};
|
|
574
|
+
this.attachedImages = [...this.attachedImages, newImage];
|
|
575
|
+
this.imageIdCounter++;
|
|
576
|
+
this.callbacks.onImagesStateChange?.(this.attachedImages);
|
|
577
|
+
return newImage;
|
|
578
|
+
}
|
|
579
|
+
removeImage(imageId) {
|
|
580
|
+
this.attachedImages = this.attachedImages.filter((img) => img.id !== imageId);
|
|
581
|
+
this.callbacks.onImagesStateChange?.(this.attachedImages);
|
|
582
|
+
}
|
|
583
|
+
clearImages() {
|
|
584
|
+
this.attachedImages = [];
|
|
585
|
+
this.callbacks.onImagesStateChange?.(this.attachedImages);
|
|
586
|
+
}
|
|
587
|
+
getAttachedImages() {
|
|
588
|
+
return this.attachedImages;
|
|
589
|
+
}
|
|
590
|
+
async handlePasteImage() {
|
|
591
|
+
try {
|
|
592
|
+
const result = await readClipboardImage();
|
|
593
|
+
if (result.success && result.imagePath && result.mimeType) {
|
|
594
|
+
// Add image to manager
|
|
595
|
+
const attachedImage = this.addImage(result.imagePath, result.mimeType);
|
|
596
|
+
// Insert image placeholder at cursor position
|
|
597
|
+
this.insertTextAtCursor(`[Image #${attachedImage.id}]`);
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
catch (error) {
|
|
603
|
+
console.warn("Failed to paste image from clipboard:", error);
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
// Bash/MCP manager state methods
|
|
608
|
+
getShowBashManager() {
|
|
609
|
+
return this.showBashManager;
|
|
610
|
+
}
|
|
611
|
+
setShowBashManager(show) {
|
|
612
|
+
this.showBashManager = show;
|
|
613
|
+
this.callbacks.onBashManagerStateChange?.(show);
|
|
614
|
+
}
|
|
615
|
+
getShowMcpManager() {
|
|
616
|
+
return this.showMcpManager;
|
|
617
|
+
}
|
|
618
|
+
setShowMcpManager(show) {
|
|
619
|
+
this.showMcpManager = show;
|
|
620
|
+
this.callbacks.onMcpManagerStateChange?.(show);
|
|
621
|
+
}
|
|
622
|
+
// Handle submit logic
|
|
623
|
+
async handleSubmit(attachedImages, isLoading = false, isCommandRunning = false) {
|
|
624
|
+
// Prevent submission during loading or command execution
|
|
625
|
+
if (isLoading || isCommandRunning) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
if (this.inputText.trim()) {
|
|
629
|
+
const trimmedInput = this.inputText.trim();
|
|
630
|
+
// Check if it's a memory message (starts with # and only one line)
|
|
631
|
+
if (trimmedInput.startsWith("#") && !trimmedInput.includes("\n")) {
|
|
632
|
+
// Activate memory type selector
|
|
633
|
+
this.activateMemoryTypeSelector(trimmedInput);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
// Extract image information
|
|
637
|
+
const imageRegex = /\[Image #(\d+)\]/g;
|
|
638
|
+
const matches = [...this.inputText.matchAll(imageRegex)];
|
|
639
|
+
const referencedImages = matches
|
|
640
|
+
.map((match) => {
|
|
641
|
+
const imageId = parseInt(match[1], 10);
|
|
642
|
+
return attachedImages.find((img) => img.id === imageId);
|
|
643
|
+
})
|
|
644
|
+
.filter((img) => img !== undefined)
|
|
645
|
+
.map((img) => ({ path: img.path, mimeType: img.mimeType }));
|
|
646
|
+
// Remove image placeholders, expand long text placeholders, send message
|
|
647
|
+
let cleanContent = this.inputText.replace(imageRegex, "").trim();
|
|
648
|
+
cleanContent = this.expandLongTextPlaceholders(cleanContent);
|
|
649
|
+
this.callbacks.onSendMessage?.(cleanContent, referencedImages.length > 0 ? referencedImages : undefined);
|
|
650
|
+
this.clearInput();
|
|
651
|
+
this.callbacks.onResetHistoryNavigation?.();
|
|
652
|
+
// Clear long text mapping
|
|
653
|
+
this.clearLongTextMap();
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
// Handle selector input (when any selector is active)
|
|
657
|
+
handleSelectorInput(input, key) {
|
|
658
|
+
if (key.backspace || key.delete) {
|
|
659
|
+
if (this.cursorPosition > 0) {
|
|
660
|
+
this.deleteCharAtCursor((newInput, newCursorPosition) => {
|
|
661
|
+
// Check for special character deletion
|
|
662
|
+
this.checkForAtDeletion(newCursorPosition);
|
|
663
|
+
this.checkForSlashDeletion(newCursorPosition);
|
|
664
|
+
this.checkForExclamationDeletion(newCursorPosition);
|
|
665
|
+
// Update search queries using the same logic as character input
|
|
666
|
+
this.updateSearchQueriesForActiveSelectors(newInput, newCursorPosition);
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
// Arrow keys, Enter and Tab should be handled by selector components
|
|
672
|
+
if (key.upArrow || key.downArrow || key.return || key.tab) {
|
|
673
|
+
// Let selector component handle these keys, but prevent further processing
|
|
674
|
+
// by returning true (indicating we've handled the input)
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
if (input &&
|
|
678
|
+
!key.ctrl &&
|
|
679
|
+
!("alt" in key && key.alt) &&
|
|
680
|
+
!key.meta &&
|
|
681
|
+
!key.return &&
|
|
682
|
+
!key.tab &&
|
|
683
|
+
!key.escape &&
|
|
684
|
+
!key.leftArrow &&
|
|
685
|
+
!key.rightArrow &&
|
|
686
|
+
!("home" in key && key.home) &&
|
|
687
|
+
!("end" in key && key.end)) {
|
|
688
|
+
// Handle character input for search
|
|
689
|
+
this.insertTextAtCursor(input, () => {
|
|
690
|
+
// Special character handling is now managed by InputManager
|
|
691
|
+
this.handleSpecialCharInput(input);
|
|
692
|
+
});
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
// Handle normal input (when no selector is active)
|
|
698
|
+
async handleNormalInput(input, key, attachedImages, isLoading = false, isCommandRunning = false, clearImages) {
|
|
699
|
+
if (key.return) {
|
|
700
|
+
await this.handleSubmit(attachedImages, isLoading, isCommandRunning);
|
|
701
|
+
clearImages?.();
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
if (key.escape) {
|
|
705
|
+
if (this.showFileSelector) {
|
|
706
|
+
this.handleCancelFileSelect();
|
|
707
|
+
}
|
|
708
|
+
else if (this.showCommandSelector) {
|
|
709
|
+
this.handleCancelCommandSelect();
|
|
710
|
+
}
|
|
711
|
+
else if (this.showBashHistorySelector) {
|
|
712
|
+
this.handleCancelBashHistorySelect();
|
|
713
|
+
}
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
if (key.backspace || key.delete) {
|
|
717
|
+
if (this.cursorPosition > 0) {
|
|
718
|
+
this.deleteCharAtCursor();
|
|
719
|
+
this.callbacks.onResetHistoryNavigation?.();
|
|
720
|
+
// Check if we deleted any special characters
|
|
721
|
+
const newCursorPosition = this.cursorPosition - 1;
|
|
722
|
+
this.checkForAtDeletion(newCursorPosition);
|
|
723
|
+
this.checkForSlashDeletion(newCursorPosition);
|
|
724
|
+
this.checkForExclamationDeletion(newCursorPosition);
|
|
725
|
+
}
|
|
726
|
+
return true;
|
|
727
|
+
}
|
|
728
|
+
if (key.leftArrow) {
|
|
729
|
+
this.moveCursorLeft();
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
if (key.rightArrow) {
|
|
733
|
+
this.moveCursorRight();
|
|
734
|
+
return true;
|
|
735
|
+
}
|
|
736
|
+
if (("home" in key && key.home) || (key.ctrl && input === "a")) {
|
|
737
|
+
this.moveCursorToStart();
|
|
738
|
+
return true;
|
|
739
|
+
}
|
|
740
|
+
if (("end" in key && key.end) || (key.ctrl && input === "e")) {
|
|
741
|
+
this.moveCursorToEnd();
|
|
742
|
+
return true;
|
|
743
|
+
}
|
|
744
|
+
// Handle Ctrl+V for pasting images
|
|
745
|
+
if (key.ctrl && input === "v") {
|
|
746
|
+
this.handlePasteImage().catch((error) => {
|
|
747
|
+
console.warn("Failed to handle paste image:", error);
|
|
748
|
+
});
|
|
749
|
+
return true;
|
|
750
|
+
}
|
|
751
|
+
// Handle up/down keys for history navigation (only when no selector is active)
|
|
752
|
+
if (key.upArrow &&
|
|
753
|
+
!this.showFileSelector &&
|
|
754
|
+
!this.showCommandSelector &&
|
|
755
|
+
!this.showBashHistorySelector) {
|
|
756
|
+
this.navigateHistory("up", this.inputText);
|
|
757
|
+
return true;
|
|
758
|
+
}
|
|
759
|
+
if (key.downArrow &&
|
|
760
|
+
!this.showFileSelector &&
|
|
761
|
+
!this.showCommandSelector &&
|
|
762
|
+
!this.showBashHistorySelector) {
|
|
763
|
+
this.navigateHistory("down", this.inputText);
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
// Handle typing input
|
|
767
|
+
if (input &&
|
|
768
|
+
!key.ctrl &&
|
|
769
|
+
!("alt" in key && key.alt) &&
|
|
770
|
+
!key.meta &&
|
|
771
|
+
!key.return &&
|
|
772
|
+
!key.escape &&
|
|
773
|
+
!key.backspace &&
|
|
774
|
+
!key.delete &&
|
|
775
|
+
!key.leftArrow &&
|
|
776
|
+
!key.rightArrow &&
|
|
777
|
+
!("home" in key && key.home) &&
|
|
778
|
+
!("end" in key && key.end)) {
|
|
779
|
+
this.handlePasteInput(input);
|
|
780
|
+
return true;
|
|
781
|
+
}
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
// Main input handler - routes to appropriate handler based on state
|
|
785
|
+
async handleInput(input, key, attachedImages, isLoading = false, isCommandRunning = false, clearImages) {
|
|
786
|
+
// If selector was just used, ignore this input to prevent conflicts
|
|
787
|
+
if (this.selectorJustUsed) {
|
|
788
|
+
return true;
|
|
789
|
+
}
|
|
790
|
+
// Handle interrupt request - use Esc key to interrupt AI request or command
|
|
791
|
+
if (key.escape && (isLoading || isCommandRunning)) {
|
|
792
|
+
// Unified interrupt for AI message generation and command execution
|
|
793
|
+
this.callbacks.onAbortMessage?.();
|
|
794
|
+
return true;
|
|
795
|
+
}
|
|
796
|
+
// Check if any selector is active
|
|
797
|
+
if (this.showFileSelector ||
|
|
798
|
+
this.showCommandSelector ||
|
|
799
|
+
this.showBashHistorySelector ||
|
|
800
|
+
this.showMemoryTypeSelector ||
|
|
801
|
+
this.showBashManager ||
|
|
802
|
+
this.showMcpManager) {
|
|
803
|
+
if (this.showMemoryTypeSelector ||
|
|
804
|
+
this.showBashManager ||
|
|
805
|
+
this.showMcpManager) {
|
|
806
|
+
// Memory type selector, bash manager and MCP manager don't need to handle input, handled by component itself
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
return this.handleSelectorInput(input, key);
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
return await this.handleNormalInput(input, key, attachedImages, isLoading, isCommandRunning, clearImages);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
// Cleanup method
|
|
816
|
+
destroy() {
|
|
817
|
+
if (this.fileSearchDebounceTimer) {
|
|
818
|
+
clearTimeout(this.fileSearchDebounceTimer);
|
|
819
|
+
this.fileSearchDebounceTimer = null;
|
|
820
|
+
}
|
|
821
|
+
if (this.pasteDebounceTimer) {
|
|
822
|
+
clearTimeout(this.pasteDebounceTimer);
|
|
823
|
+
this.pasteDebounceTimer = null;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|