wave-code 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +120 -0
  2. package/bin/wave-code.js +16 -0
  3. package/dist/cli.d.ts +6 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +62 -0
  6. package/dist/components/App.d.ts +8 -0
  7. package/dist/components/App.d.ts.map +1 -0
  8. package/dist/components/App.js +10 -0
  9. package/dist/components/BashHistorySelector.d.ts +10 -0
  10. package/dist/components/BashHistorySelector.d.ts.map +1 -0
  11. package/dist/components/BashHistorySelector.js +83 -0
  12. package/dist/components/BashShellManager.d.ts +6 -0
  13. package/dist/components/BashShellManager.d.ts.map +1 -0
  14. package/dist/components/BashShellManager.js +116 -0
  15. package/dist/components/ChatInterface.d.ts +3 -0
  16. package/dist/components/ChatInterface.d.ts.map +1 -0
  17. package/dist/components/ChatInterface.js +31 -0
  18. package/dist/components/CommandOutputDisplay.d.ts +9 -0
  19. package/dist/components/CommandOutputDisplay.d.ts.map +1 -0
  20. package/dist/components/CommandOutputDisplay.js +40 -0
  21. package/dist/components/CommandSelector.d.ts +11 -0
  22. package/dist/components/CommandSelector.d.ts.map +1 -0
  23. package/dist/components/CommandSelector.js +60 -0
  24. package/dist/components/CompressDisplay.d.ts +9 -0
  25. package/dist/components/CompressDisplay.d.ts.map +1 -0
  26. package/dist/components/CompressDisplay.js +17 -0
  27. package/dist/components/DiffViewer.d.ts +9 -0
  28. package/dist/components/DiffViewer.d.ts.map +1 -0
  29. package/dist/components/DiffViewer.js +221 -0
  30. package/dist/components/FileSelector.d.ts +13 -0
  31. package/dist/components/FileSelector.d.ts.map +1 -0
  32. package/dist/components/FileSelector.js +48 -0
  33. package/dist/components/InputBox.d.ts +23 -0
  34. package/dist/components/InputBox.d.ts.map +1 -0
  35. package/dist/components/InputBox.js +124 -0
  36. package/dist/components/McpManager.d.ts +10 -0
  37. package/dist/components/McpManager.d.ts.map +1 -0
  38. package/dist/components/McpManager.js +123 -0
  39. package/dist/components/MemoryDisplay.d.ts +8 -0
  40. package/dist/components/MemoryDisplay.d.ts.map +1 -0
  41. package/dist/components/MemoryDisplay.js +25 -0
  42. package/dist/components/MemoryTypeSelector.d.ts +8 -0
  43. package/dist/components/MemoryTypeSelector.d.ts.map +1 -0
  44. package/dist/components/MemoryTypeSelector.js +38 -0
  45. package/dist/components/MessageList.d.ts +12 -0
  46. package/dist/components/MessageList.d.ts.map +1 -0
  47. package/dist/components/MessageList.js +36 -0
  48. package/dist/components/ToolResultDisplay.d.ts +9 -0
  49. package/dist/components/ToolResultDisplay.d.ts.map +1 -0
  50. package/dist/components/ToolResultDisplay.js +52 -0
  51. package/dist/contexts/useAppConfig.d.ts +11 -0
  52. package/dist/contexts/useAppConfig.d.ts.map +1 -0
  53. package/dist/contexts/useAppConfig.js +13 -0
  54. package/dist/contexts/useChat.d.ts +36 -0
  55. package/dist/contexts/useChat.d.ts.map +1 -0
  56. package/dist/contexts/useChat.js +208 -0
  57. package/dist/hooks/useBashHistorySelector.d.ts +15 -0
  58. package/dist/hooks/useBashHistorySelector.d.ts.map +1 -0
  59. package/dist/hooks/useBashHistorySelector.js +61 -0
  60. package/dist/hooks/useCommandSelector.d.ts +24 -0
  61. package/dist/hooks/useCommandSelector.d.ts.map +1 -0
  62. package/dist/hooks/useCommandSelector.js +98 -0
  63. package/dist/hooks/useFileSelector.d.ts +16 -0
  64. package/dist/hooks/useFileSelector.d.ts.map +1 -0
  65. package/dist/hooks/useFileSelector.js +174 -0
  66. package/dist/hooks/useImageManager.d.ts +13 -0
  67. package/dist/hooks/useImageManager.d.ts.map +1 -0
  68. package/dist/hooks/useImageManager.js +46 -0
  69. package/dist/hooks/useInputHistory.d.ts +11 -0
  70. package/dist/hooks/useInputHistory.d.ts.map +1 -0
  71. package/dist/hooks/useInputHistory.js +64 -0
  72. package/dist/hooks/useInputKeyboardHandler.d.ts +83 -0
  73. package/dist/hooks/useInputKeyboardHandler.d.ts.map +1 -0
  74. package/dist/hooks/useInputKeyboardHandler.js +507 -0
  75. package/dist/hooks/useInputState.d.ts +14 -0
  76. package/dist/hooks/useInputState.d.ts.map +1 -0
  77. package/dist/hooks/useInputState.js +57 -0
  78. package/dist/hooks/useMemoryTypeSelector.d.ts +9 -0
  79. package/dist/hooks/useMemoryTypeSelector.d.ts.map +1 -0
  80. package/dist/hooks/useMemoryTypeSelector.js +27 -0
  81. package/dist/hooks/usePagination.d.ts +20 -0
  82. package/dist/hooks/usePagination.d.ts.map +1 -0
  83. package/dist/hooks/usePagination.js +168 -0
  84. package/dist/index.d.ts +5 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +91 -0
  87. package/dist/plain-cli.d.ts +7 -0
  88. package/dist/plain-cli.d.ts.map +1 -0
  89. package/dist/plain-cli.js +49 -0
  90. package/dist/utils/clipboard.d.ts +22 -0
  91. package/dist/utils/clipboard.d.ts.map +1 -0
  92. package/dist/utils/clipboard.js +347 -0
  93. package/dist/utils/constants.d.ts +17 -0
  94. package/dist/utils/constants.d.ts.map +1 -0
  95. package/dist/utils/constants.js +18 -0
  96. package/dist/utils/logger.d.ts +72 -0
  97. package/dist/utils/logger.d.ts.map +1 -0
  98. package/dist/utils/logger.js +245 -0
  99. package/package.json +60 -0
  100. package/src/cli.tsx +82 -0
  101. package/src/components/App.tsx +31 -0
  102. package/src/components/BashHistorySelector.tsx +163 -0
  103. package/src/components/BashShellManager.tsx +306 -0
  104. package/src/components/ChatInterface.tsx +88 -0
  105. package/src/components/CommandOutputDisplay.tsx +81 -0
  106. package/src/components/CommandSelector.tsx +144 -0
  107. package/src/components/CompressDisplay.tsx +58 -0
  108. package/src/components/DiffViewer.tsx +321 -0
  109. package/src/components/FileSelector.tsx +137 -0
  110. package/src/components/InputBox.tsx +310 -0
  111. package/src/components/McpManager.tsx +328 -0
  112. package/src/components/MemoryDisplay.tsx +62 -0
  113. package/src/components/MemoryTypeSelector.tsx +96 -0
  114. package/src/components/MessageList.tsx +215 -0
  115. package/src/components/ToolResultDisplay.tsx +138 -0
  116. package/src/contexts/useAppConfig.tsx +32 -0
  117. package/src/contexts/useChat.tsx +300 -0
  118. package/src/hooks/useBashHistorySelector.ts +77 -0
  119. package/src/hooks/useCommandSelector.ts +131 -0
  120. package/src/hooks/useFileSelector.ts +227 -0
  121. package/src/hooks/useImageManager.ts +64 -0
  122. package/src/hooks/useInputHistory.ts +74 -0
  123. package/src/hooks/useInputKeyboardHandler.ts +778 -0
  124. package/src/hooks/useInputState.ts +66 -0
  125. package/src/hooks/useMemoryTypeSelector.ts +40 -0
  126. package/src/hooks/usePagination.ts +203 -0
  127. package/src/index.ts +108 -0
  128. package/src/plain-cli.ts +66 -0
  129. package/src/utils/clipboard.ts +384 -0
  130. package/src/utils/constants.ts +22 -0
  131. package/src/utils/logger.ts +301 -0
@@ -0,0 +1,507 @@
1
+ import { useCallback, useRef, useEffect } from "react";
2
+ import { useInput } from "ink";
3
+ import { logger } from "../utils/logger.js";
4
+ export const useInputKeyboardHandler = (props) => {
5
+ const { inputText, setInputText, cursorPosition, setCursorPosition, moveCursorLeft, moveCursorRight, moveCursorToStart, moveCursorToEnd, deleteCharAtCursor, insertTextAtCursor, clearInput, resetHistoryNavigation, navigateHistory, handlePasteImage, attachedImages, clearImages, showFileSelector, activateFileSelector, handleFileSelect, handleCancelFileSelect, updateSearchQuery, checkForAtDeletion, atPosition, showCommandSelector, activateCommandSelector, handleCommandSelect, handleCommandInsert, handleCancelCommandSelect, updateCommandSearchQuery, checkForSlashDeletion, slashPosition, showBashHistorySelector, activateBashHistorySelector, handleBashHistorySelect, handleBashHistoryExecute, handleCancelBashHistorySelect, updateBashHistorySearchQuery, checkForExclamationDeletion, exclamationPosition, showMemoryTypeSelector, activateMemoryTypeSelector, handleMemoryTypeSelect, showBashManager, showMcpManager, isCommandRunning, isLoading, sendMessage, abortMessage, saveMemory, } = props;
6
+ // Debounce for paste operations
7
+ const pasteDebounceRef = useRef({
8
+ timer: null,
9
+ buffer: "",
10
+ initialCursorPosition: 0,
11
+ isPasting: false,
12
+ });
13
+ // Long text compression management
14
+ const longTextCounterRef = useRef(0);
15
+ const longTextMapRef = useRef(new Map());
16
+ const generateCompressedText = (originalText) => {
17
+ longTextCounterRef.current += 1;
18
+ const compressedLabel = `[LongText#${longTextCounterRef.current}]`;
19
+ longTextMapRef.current.set(compressedLabel, originalText);
20
+ return compressedLabel;
21
+ };
22
+ const expandLongTextPlaceholders = (text) => {
23
+ let expandedText = text;
24
+ const longTextRegex = /\[LongText#(\d+)\]/g;
25
+ const matches = [...text.matchAll(longTextRegex)];
26
+ for (const match of matches) {
27
+ const placeholder = match[0];
28
+ const originalText = longTextMapRef.current.get(placeholder);
29
+ if (originalText) {
30
+ expandedText = expandedText.replace(placeholder, originalText);
31
+ }
32
+ }
33
+ return expandedText;
34
+ };
35
+ // Cleanup on unmount
36
+ useEffect(() => {
37
+ const currentDebounceRef = pasteDebounceRef.current;
38
+ return () => {
39
+ if (currentDebounceRef.timer) {
40
+ clearTimeout(currentDebounceRef.timer);
41
+ }
42
+ };
43
+ }, []);
44
+ const handleSelectorInput = useCallback((input, key) => {
45
+ if (key.backspace || key.delete) {
46
+ if (cursorPosition > 0) {
47
+ const newInput = inputText.substring(0, cursorPosition - 1) +
48
+ inputText.substring(cursorPosition);
49
+ setInputText(newInput);
50
+ setCursorPosition(cursorPosition - 1);
51
+ // Update search query
52
+ if (atPosition >= 0) {
53
+ const queryStart = atPosition + 1;
54
+ const queryEnd = cursorPosition - 1;
55
+ if (queryEnd <= atPosition) {
56
+ // Deleted @ symbol, close file selector
57
+ handleCancelFileSelect();
58
+ }
59
+ else {
60
+ const newQuery = newInput.substring(queryStart, queryEnd);
61
+ updateSearchQuery(newQuery);
62
+ }
63
+ }
64
+ else if (slashPosition >= 0) {
65
+ const queryStart = slashPosition + 1;
66
+ const queryEnd = cursorPosition - 1;
67
+ if (queryEnd <= slashPosition) {
68
+ // Deleted / symbol, close command selector
69
+ handleCancelCommandSelect();
70
+ }
71
+ else {
72
+ const newQuery = newInput.substring(queryStart, queryEnd);
73
+ updateCommandSearchQuery(newQuery);
74
+ }
75
+ }
76
+ else if (exclamationPosition >= 0) {
77
+ const queryStart = exclamationPosition + 1;
78
+ const queryEnd = cursorPosition - 1;
79
+ if (queryEnd <= exclamationPosition) {
80
+ // Deleted ! symbol, close bash history selector
81
+ handleCancelBashHistorySelect();
82
+ }
83
+ else {
84
+ const newQuery = newInput.substring(queryStart, queryEnd);
85
+ updateBashHistorySearchQuery(newQuery);
86
+ }
87
+ }
88
+ }
89
+ return;
90
+ }
91
+ // Arrow keys should be handled by selector components, no need to filter here
92
+ if (key.upArrow || key.downArrow) {
93
+ // Let selector component handle arrow key navigation
94
+ return;
95
+ }
96
+ if (input &&
97
+ !key.ctrl &&
98
+ !("alt" in key && key.alt) &&
99
+ !key.meta &&
100
+ !key.return &&
101
+ !key.escape &&
102
+ !key.leftArrow &&
103
+ !key.rightArrow &&
104
+ !("home" in key && key.home) &&
105
+ !("end" in key && key.end)) {
106
+ // Handle character input for search
107
+ const char = input;
108
+ const newInput = inputText.substring(0, cursorPosition) +
109
+ char +
110
+ inputText.substring(cursorPosition);
111
+ setInputText(newInput);
112
+ setCursorPosition(cursorPosition + input.length);
113
+ // Update search query
114
+ if (atPosition >= 0) {
115
+ const queryStart = atPosition + 1;
116
+ const queryEnd = cursorPosition + input.length;
117
+ const newQuery = newInput.substring(queryStart, queryEnd);
118
+ updateSearchQuery(newQuery);
119
+ }
120
+ else if (slashPosition >= 0) {
121
+ const queryStart = slashPosition + 1;
122
+ const queryEnd = cursorPosition + input.length;
123
+ const newQuery = newInput.substring(queryStart, queryEnd);
124
+ updateCommandSearchQuery(newQuery);
125
+ }
126
+ else if (exclamationPosition >= 0) {
127
+ const queryStart = exclamationPosition + 1;
128
+ const queryEnd = cursorPosition + input.length;
129
+ const newQuery = newInput.substring(queryStart, queryEnd);
130
+ updateBashHistorySearchQuery(newQuery);
131
+ }
132
+ }
133
+ }, [
134
+ inputText,
135
+ cursorPosition,
136
+ setInputText,
137
+ setCursorPosition,
138
+ atPosition,
139
+ slashPosition,
140
+ exclamationPosition,
141
+ handleCancelFileSelect,
142
+ handleCancelCommandSelect,
143
+ handleCancelBashHistorySelect,
144
+ updateSearchQuery,
145
+ updateCommandSearchQuery,
146
+ updateBashHistorySearchQuery,
147
+ ]);
148
+ const handleNormalInput = useCallback(async (input, key) => {
149
+ if (key.return) {
150
+ // Prevent submission during loading or command execution
151
+ if (isLoading || isCommandRunning) {
152
+ return;
153
+ }
154
+ if (inputText.trim()) {
155
+ const trimmedInput = inputText.trim();
156
+ // Check if it's a memory message (starts with # and only one line)
157
+ if (trimmedInput.startsWith("#") && !trimmedInput.includes("\n")) {
158
+ // Activate memory type selector
159
+ activateMemoryTypeSelector(trimmedInput);
160
+ return;
161
+ }
162
+ // Extract image information
163
+ const imageRegex = /\[Image #(\d+)\]/g;
164
+ const matches = [...inputText.matchAll(imageRegex)];
165
+ const referencedImages = matches
166
+ .map((match) => {
167
+ const imageId = parseInt(match[1], 10);
168
+ return attachedImages.find((img) => img.id === imageId);
169
+ })
170
+ .filter((img) => img !== undefined)
171
+ .map((img) => ({ path: img.path, mimeType: img.mimeType }));
172
+ // Remove image placeholders, expand long text placeholders, send message
173
+ let cleanContent = inputText.replace(imageRegex, "").trim();
174
+ cleanContent = expandLongTextPlaceholders(cleanContent);
175
+ sendMessage(cleanContent, referencedImages.length > 0 ? referencedImages : undefined);
176
+ clearInput();
177
+ clearImages();
178
+ resetHistoryNavigation();
179
+ // Clear long text mapping
180
+ longTextMapRef.current.clear();
181
+ }
182
+ return;
183
+ }
184
+ if (key.escape) {
185
+ if (showFileSelector) {
186
+ handleCancelFileSelect();
187
+ }
188
+ else if (showCommandSelector) {
189
+ handleCancelCommandSelect();
190
+ }
191
+ else if (showBashHistorySelector) {
192
+ handleCancelBashHistorySelect();
193
+ }
194
+ return;
195
+ }
196
+ if (key.backspace || key.delete) {
197
+ if (cursorPosition > 0) {
198
+ deleteCharAtCursor();
199
+ resetHistoryNavigation();
200
+ // Check if we deleted any special characters
201
+ const newCursorPosition = cursorPosition - 1;
202
+ checkForAtDeletion(newCursorPosition);
203
+ checkForSlashDeletion(newCursorPosition);
204
+ checkForExclamationDeletion(newCursorPosition);
205
+ }
206
+ return;
207
+ }
208
+ if (key.leftArrow) {
209
+ moveCursorLeft();
210
+ return;
211
+ }
212
+ if (key.rightArrow) {
213
+ moveCursorRight();
214
+ return;
215
+ }
216
+ if (("home" in key && key.home) || (key.ctrl && input === "a")) {
217
+ moveCursorToStart();
218
+ return;
219
+ }
220
+ if (("end" in key && key.end) || (key.ctrl && input === "e")) {
221
+ moveCursorToEnd();
222
+ return;
223
+ }
224
+ // Handle Ctrl+V for pasting images
225
+ if (key.ctrl && input === "v") {
226
+ handlePasteImage().catch((error) => {
227
+ console.warn("Failed to handle paste image:", error);
228
+ });
229
+ return;
230
+ }
231
+ // Handle up/down keys for history navigation (only when no selector is active)
232
+ if (key.upArrow &&
233
+ !showFileSelector &&
234
+ !showCommandSelector &&
235
+ !showBashHistorySelector) {
236
+ const { newInput, newCursorPosition } = navigateHistory("up", inputText);
237
+ setInputText(newInput);
238
+ setCursorPosition(newCursorPosition);
239
+ return;
240
+ }
241
+ if (key.downArrow &&
242
+ !showFileSelector &&
243
+ !showCommandSelector &&
244
+ !showBashHistorySelector) {
245
+ const { newInput, newCursorPosition } = navigateHistory("down", inputText);
246
+ setInputText(newInput);
247
+ setCursorPosition(newCursorPosition);
248
+ return;
249
+ }
250
+ // Handle typing input
251
+ if (input &&
252
+ !key.ctrl &&
253
+ !("alt" in key && key.alt) &&
254
+ !key.meta &&
255
+ !key.return &&
256
+ !key.escape &&
257
+ !key.backspace &&
258
+ !key.delete &&
259
+ !key.leftArrow &&
260
+ !key.rightArrow &&
261
+ !("home" in key && key.home) &&
262
+ !("end" in key && key.end)) {
263
+ const inputString = input;
264
+ // Detect if it's a paste operation (input contains multiple characters or newlines)
265
+ const isPasteOperation = inputString.length > 1 ||
266
+ inputString.includes("\n") ||
267
+ inputString.includes("\r");
268
+ if (isPasteOperation) {
269
+ logger.debug("[InputBox] 🔍 Detected paste operation:", {
270
+ inputLength: inputString.length,
271
+ input: inputString.substring(0, 50) +
272
+ (inputString.length > 50 ? "..." : ""),
273
+ hasNewlines: inputString.includes("\n") || inputString.includes("\r"),
274
+ });
275
+ // Start or continue the debounce handling for paste operation
276
+ if (!pasteDebounceRef.current.isPasting) {
277
+ // Start new paste operation
278
+ logger.debug("[InputBox] 🚀 Starting new paste operation - initializing debounce buffer");
279
+ pasteDebounceRef.current.isPasting = true;
280
+ pasteDebounceRef.current.buffer = inputString;
281
+ pasteDebounceRef.current.initialCursorPosition = cursorPosition;
282
+ }
283
+ else {
284
+ // Continue paste operation, add new input to buffer
285
+ logger.debug("[InputBox] 📝 Merging paste content to buffer:", {
286
+ previousBufferLength: pasteDebounceRef.current.buffer.length,
287
+ newInputLength: inputString.length,
288
+ newTotalLength: pasteDebounceRef.current.buffer.length + inputString.length,
289
+ });
290
+ pasteDebounceRef.current.buffer += inputString;
291
+ }
292
+ // Clear previous timer
293
+ if (pasteDebounceRef.current.timer) {
294
+ logger.debug("[InputBox] ⏰ Clearing previous debounce timer, resetting 30ms delay");
295
+ clearTimeout(pasteDebounceRef.current.timer);
296
+ }
297
+ // Set new 30ms timer
298
+ pasteDebounceRef.current.timer = setTimeout(() => {
299
+ logger.debug("[InputBox] ✅ Debounce complete - processing merged paste content:", {
300
+ finalBufferLength: pasteDebounceRef.current.buffer.length,
301
+ content: pasteDebounceRef.current.buffer.substring(0, 100) +
302
+ (pasteDebounceRef.current.buffer.length > 100 ? "..." : ""),
303
+ });
304
+ // Process all paste content in buffer
305
+ let processedInput = pasteDebounceRef.current.buffer.replace(/\r/g, "\n");
306
+ // Check if long text compression is needed (over 200 characters)
307
+ if (processedInput.length > 200) {
308
+ const originalText = processedInput;
309
+ const compressedLabel = generateCompressedText(originalText);
310
+ logger.info("[InputBox] 📦 Long text compression: originalLength:", originalText.length, "compressedLabel:", compressedLabel, "preview:", originalText.substring(0, 50) + "...");
311
+ processedInput = compressedLabel;
312
+ }
313
+ insertTextAtCursor(processedInput);
314
+ resetHistoryNavigation();
315
+ // Reset paste state
316
+ pasteDebounceRef.current.isPasting = false;
317
+ pasteDebounceRef.current.buffer = "";
318
+ pasteDebounceRef.current.timer = null;
319
+ logger.debug("[InputBox] 🎯 Paste debounce processing complete, state reset");
320
+ }, 30);
321
+ }
322
+ else {
323
+ // Handle single character input
324
+ let char = inputString;
325
+ // Check if it's Chinese exclamation mark, convert to English if at beginning
326
+ if (char === "!" && cursorPosition === 0) {
327
+ char = "!";
328
+ }
329
+ // First update input text and cursor position
330
+ const newInputText = inputText.substring(0, cursorPosition) +
331
+ char +
332
+ inputText.substring(cursorPosition);
333
+ const newCursorPosition = cursorPosition + char.length;
334
+ insertTextAtCursor(char);
335
+ resetHistoryNavigation();
336
+ // Check special characters and set corresponding selectors
337
+ if (char === "@") {
338
+ activateFileSelector(cursorPosition);
339
+ }
340
+ else if (char === "/") {
341
+ activateCommandSelector(cursorPosition);
342
+ }
343
+ else if (char === "!" && cursorPosition === 0) {
344
+ // ! must be the first character to trigger bash selector
345
+ activateBashHistorySelector(cursorPosition);
346
+ }
347
+ else if (char === "#" && cursorPosition === 0) {
348
+ // # at beginning position, will be auto-detected as memory message when sent
349
+ logger.debug("[InputBox] 📝 Memory message detection, input starts with #");
350
+ }
351
+ else if (showFileSelector && atPosition >= 0) {
352
+ // Update search query
353
+ const queryStart = atPosition + 1;
354
+ const queryEnd = newCursorPosition;
355
+ const newQuery = newInputText.substring(queryStart, queryEnd);
356
+ updateSearchQuery(newQuery);
357
+ }
358
+ else if (showCommandSelector && slashPosition >= 0) {
359
+ // Update command search query
360
+ const queryStart = slashPosition + 1;
361
+ const queryEnd = newCursorPosition;
362
+ const newQuery = newInputText.substring(queryStart, queryEnd);
363
+ updateCommandSearchQuery(newQuery);
364
+ }
365
+ else if (showBashHistorySelector && exclamationPosition >= 0) {
366
+ // Update bash history search query
367
+ const queryStart = exclamationPosition + 1;
368
+ const queryEnd = newCursorPosition;
369
+ const newQuery = newInputText.substring(queryStart, queryEnd);
370
+ updateBashHistorySearchQuery(newQuery);
371
+ }
372
+ }
373
+ }
374
+ }, [
375
+ inputText,
376
+ cursorPosition,
377
+ sendMessage,
378
+ clearInput,
379
+ resetHistoryNavigation,
380
+ showFileSelector,
381
+ showCommandSelector,
382
+ showBashHistorySelector,
383
+ handleCancelFileSelect,
384
+ handleCancelCommandSelect,
385
+ handleCancelBashHistorySelect,
386
+ deleteCharAtCursor,
387
+ checkForAtDeletion,
388
+ checkForSlashDeletion,
389
+ checkForExclamationDeletion,
390
+ moveCursorLeft,
391
+ moveCursorRight,
392
+ moveCursorToStart,
393
+ moveCursorToEnd,
394
+ navigateHistory,
395
+ setInputText,
396
+ setCursorPosition,
397
+ insertTextAtCursor,
398
+ activateFileSelector,
399
+ activateCommandSelector,
400
+ activateBashHistorySelector,
401
+ atPosition,
402
+ slashPosition,
403
+ exclamationPosition,
404
+ updateSearchQuery,
405
+ updateCommandSearchQuery,
406
+ updateBashHistorySearchQuery,
407
+ attachedImages,
408
+ clearImages,
409
+ handlePasteImage,
410
+ activateMemoryTypeSelector,
411
+ isLoading,
412
+ isCommandRunning,
413
+ ]);
414
+ useInput((input, key) => {
415
+ // Handle interrupt request - use Esc key to interrupt AI request or command
416
+ if (key.escape && (isLoading || isCommandRunning)) {
417
+ // Unified interrupt for AI message generation and command execution
418
+ if (typeof abortMessage === "function") {
419
+ abortMessage();
420
+ }
421
+ return;
422
+ }
423
+ // During loading or command execution, except for Esc key, other input operations continue normally
424
+ // but will prevent Enter submission in handleNormalInput
425
+ if (showFileSelector ||
426
+ showCommandSelector ||
427
+ showBashHistorySelector ||
428
+ showMemoryTypeSelector ||
429
+ showBashManager ||
430
+ showMcpManager) {
431
+ if (showMemoryTypeSelector || showBashManager || showMcpManager) {
432
+ // Memory type selector, bash manager and MCP manager don't need to handle input, handled by component itself
433
+ return;
434
+ }
435
+ handleSelectorInput(input, key);
436
+ }
437
+ else {
438
+ handleNormalInput(input, key);
439
+ }
440
+ });
441
+ return {
442
+ handleFileSelect: useCallback((filePath) => {
443
+ const { newInput, newCursorPosition } = handleFileSelect(filePath, inputText, cursorPosition);
444
+ setInputText(newInput);
445
+ setCursorPosition(newCursorPosition);
446
+ }, [
447
+ handleFileSelect,
448
+ inputText,
449
+ cursorPosition,
450
+ setInputText,
451
+ setCursorPosition,
452
+ ]),
453
+ handleCommandSelect: useCallback((command) => {
454
+ const { newInput, newCursorPosition } = handleCommandSelect(command, inputText, cursorPosition);
455
+ setInputText(newInput);
456
+ setCursorPosition(newCursorPosition);
457
+ }, [
458
+ handleCommandSelect,
459
+ inputText,
460
+ cursorPosition,
461
+ setInputText,
462
+ setCursorPosition,
463
+ ]),
464
+ handleCommandInsert: useCallback((command) => {
465
+ const { newInput, newCursorPosition } = handleCommandInsert(command, inputText, cursorPosition);
466
+ setInputText(newInput);
467
+ setCursorPosition(newCursorPosition);
468
+ }, [
469
+ handleCommandInsert,
470
+ inputText,
471
+ cursorPosition,
472
+ setInputText,
473
+ setCursorPosition,
474
+ ]),
475
+ handleBashHistorySelect: useCallback((command) => {
476
+ const { newInput, newCursorPosition } = handleBashHistorySelect(command, inputText, cursorPosition);
477
+ setInputText(newInput);
478
+ setCursorPosition(newCursorPosition);
479
+ }, [
480
+ handleBashHistorySelect,
481
+ inputText,
482
+ cursorPosition,
483
+ setInputText,
484
+ setCursorPosition,
485
+ ]),
486
+ handleBashHistoryExecute: useCallback((command) => {
487
+ const commandToExecute = handleBashHistoryExecute(command);
488
+ // Clear input box and execute command, ensure command starts with !
489
+ const bashCommand = commandToExecute.startsWith("!")
490
+ ? commandToExecute
491
+ : `!${commandToExecute}`;
492
+ setInputText("");
493
+ setCursorPosition(0);
494
+ sendMessage(bashCommand);
495
+ }, [handleBashHistoryExecute, setInputText, setCursorPosition, sendMessage]),
496
+ handleMemoryTypeSelect: useCallback(async (type) => {
497
+ const currentMessage = inputText.trim();
498
+ if (currentMessage.startsWith("#")) {
499
+ await saveMemory(currentMessage, type);
500
+ }
501
+ // Call the handler function from useMemoryTypeSelector to close the selector
502
+ handleMemoryTypeSelect(type);
503
+ // Clear input box
504
+ clearInput();
505
+ }, [inputText, saveMemory, handleMemoryTypeSelect, clearInput]),
506
+ };
507
+ };
@@ -0,0 +1,14 @@
1
+ export declare const useInputState: () => {
2
+ inputText: string;
3
+ setInputText: import("react").Dispatch<import("react").SetStateAction<string>>;
4
+ cursorPosition: number;
5
+ setCursorPosition: import("react").Dispatch<import("react").SetStateAction<number>>;
6
+ insertTextAtCursor: (text: string) => void;
7
+ deleteCharAtCursor: () => void;
8
+ clearInput: () => void;
9
+ moveCursorLeft: () => void;
10
+ moveCursorRight: () => void;
11
+ moveCursorToStart: () => void;
12
+ moveCursorToEnd: () => void;
13
+ };
14
+ //# sourceMappingURL=useInputState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useInputState.d.ts","sourceRoot":"","sources":["../../src/hooks/useInputState.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa;;;;;+BAIsB,MAAM;;;;;;;CA2DrD,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { useState, useCallback } from "react";
2
+ export const useInputState = () => {
3
+ const [inputText, setInputText] = useState("");
4
+ const [cursorPosition, setCursorPosition] = useState(0);
5
+ const insertTextAtCursor = useCallback((text) => {
6
+ setCursorPosition((currentCursor) => {
7
+ setInputText((currentText) => {
8
+ const beforeCursor = currentText.substring(0, currentCursor);
9
+ const afterCursor = currentText.substring(currentCursor);
10
+ return beforeCursor + text + afterCursor;
11
+ });
12
+ return currentCursor + text.length;
13
+ });
14
+ }, []);
15
+ const deleteCharAtCursor = useCallback(() => {
16
+ setCursorPosition((currentCursor) => {
17
+ if (currentCursor > 0) {
18
+ setInputText((currentText) => {
19
+ const beforeCursor = currentText.substring(0, currentCursor - 1);
20
+ const afterCursor = currentText.substring(currentCursor);
21
+ return beforeCursor + afterCursor;
22
+ });
23
+ return currentCursor - 1;
24
+ }
25
+ return currentCursor;
26
+ });
27
+ }, []);
28
+ const clearInput = useCallback(() => {
29
+ setInputText("");
30
+ setCursorPosition(0);
31
+ }, []);
32
+ const moveCursorLeft = useCallback(() => {
33
+ setCursorPosition((current) => Math.max(0, current - 1));
34
+ }, []);
35
+ const moveCursorRight = useCallback(() => {
36
+ setCursorPosition((current) => Math.min(inputText.length, current + 1));
37
+ }, [inputText.length]);
38
+ const moveCursorToStart = useCallback(() => {
39
+ setCursorPosition(0);
40
+ }, []);
41
+ const moveCursorToEnd = useCallback(() => {
42
+ setCursorPosition(inputText.length);
43
+ }, [inputText.length]);
44
+ return {
45
+ inputText,
46
+ setInputText,
47
+ cursorPosition,
48
+ setCursorPosition,
49
+ insertTextAtCursor,
50
+ deleteCharAtCursor,
51
+ clearInput,
52
+ moveCursorLeft,
53
+ moveCursorRight,
54
+ moveCursorToStart,
55
+ moveCursorToEnd,
56
+ };
57
+ };
@@ -0,0 +1,9 @@
1
+ export interface UseMemoryTypeSelectorReturn {
2
+ showMemoryTypeSelector: boolean;
3
+ memoryMessage: string;
4
+ activateMemoryTypeSelector: (message: string) => void;
5
+ handleMemoryTypeSelect: (type: "project" | "user") => void;
6
+ handleCancelMemoryTypeSelect: () => void;
7
+ }
8
+ export declare const useMemoryTypeSelector: () => UseMemoryTypeSelectorReturn;
9
+ //# sourceMappingURL=useMemoryTypeSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMemoryTypeSelector.d.ts","sourceRoot":"","sources":["../../src/hooks/useMemoryTypeSelector.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,2BAA2B;IAC1C,sBAAsB,EAAE,OAAO,CAAC;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,0BAA0B,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,sBAAsB,EAAE,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,KAAK,IAAI,CAAC;IAC3D,4BAA4B,EAAE,MAAM,IAAI,CAAC;CAC1C;AAED,eAAO,MAAM,qBAAqB,QAAO,2BA6BxC,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { useState } from "react";
2
+ export const useMemoryTypeSelector = () => {
3
+ const [showMemoryTypeSelector, setShowMemoryTypeSelector] = useState(false);
4
+ const [memoryMessage, setMemoryMessage] = useState("");
5
+ const activateMemoryTypeSelector = (message) => {
6
+ setMemoryMessage(message);
7
+ setShowMemoryTypeSelector(true);
8
+ };
9
+ const handleMemoryTypeSelect = (type) => {
10
+ setShowMemoryTypeSelector(false);
11
+ setMemoryMessage("");
12
+ // Type parameter is passed by external component, here only responsible for closing selector
13
+ // Actual type handling is completed in useInputKeyboardHandler
14
+ void type; // Explicitly mark parameter as known but unused
15
+ };
16
+ const handleCancelMemoryTypeSelect = () => {
17
+ setShowMemoryTypeSelector(false);
18
+ setMemoryMessage("");
19
+ };
20
+ return {
21
+ showMemoryTypeSelector,
22
+ memoryMessage,
23
+ activateMemoryTypeSelector,
24
+ handleMemoryTypeSelect,
25
+ handleCancelMemoryTypeSelect,
26
+ };
27
+ };
@@ -0,0 +1,20 @@
1
+ import type { Message } from "wave-agent-sdk";
2
+ interface PaginationInfo {
3
+ currentPage: number;
4
+ totalPages: number;
5
+ startIndex: number;
6
+ endIndex: number;
7
+ messagesPerPage: number;
8
+ }
9
+ export declare const usePagination: (messages: Message[]) => {
10
+ displayInfo: PaginationInfo;
11
+ manualPage: number | null;
12
+ setManualPage: import("react").Dispatch<import("react").SetStateAction<number | null>>;
13
+ goToPage: (page: number | null) => void;
14
+ goToPrevPage: () => void;
15
+ goToNextPage: () => void;
16
+ goToFirstPage: () => void;
17
+ goToLastPage: () => void;
18
+ };
19
+ export {};
20
+ //# sourceMappingURL=usePagination.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePagination.d.ts","sourceRoot":"","sources":["../../src/hooks/usePagination.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAG9C,UAAU,cAAc;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,aAAa,GAAI,UAAU,OAAO,EAAE;;;;qBAwIvB,MAAM,GAAG,IAAI;;;;;CAqDtC,CAAC"}