vzcode 2.3.0 → 2.4.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.
Files changed (196) hide show
  1. package/package.json +3 -2
  2. package/src/cli.ts +208 -0
  3. package/src/client/AIAssist/AIAssistCodeMirrorKeyMap.ts +42 -0
  4. package/src/client/AIAssist/AIAssistWidget/index.tsx +135 -0
  5. package/src/client/AIAssist/AIAssistWidget/style.scss +47 -0
  6. package/src/client/AIAssist/Spinner.jsx +76 -0
  7. package/src/client/AIAssist/formatFile.ts +27 -0
  8. package/src/client/AIAssist/generateFilesContext.ts +25 -0
  9. package/src/client/AIAssist/index.ts +2 -0
  10. package/src/client/AIAssist/startAIAssist.ts +95 -0
  11. package/src/client/App/index.tsx +152 -0
  12. package/src/client/App/style.scss +82 -0
  13. package/src/client/App/usePending.ts +51 -0
  14. package/src/client/App/useShareDB.ts +161 -0
  15. package/src/client/CodeEditor/Copilot/index.ts +74 -0
  16. package/src/client/CodeEditor/InteractiveWidgets/boolToggler.ts +12 -0
  17. package/src/client/CodeEditor/InteractiveWidgets/colorPicker.ts +68 -0
  18. package/src/client/CodeEditor/InteractiveWidgets/colorsInTextPlugin.ts +174 -0
  19. package/src/client/CodeEditor/InteractiveWidgets/highlightWidgets.ts +79 -0
  20. package/src/client/CodeEditor/InteractiveWidgets/index.ts +44 -0
  21. package/src/client/CodeEditor/InteractiveWidgets/markdownCheckboxToggler.ts +12 -0
  22. package/src/client/CodeEditor/InteractiveWidgets/numberDragger.ts +20 -0
  23. package/src/client/CodeEditor/InteractiveWidgets/rotateWidget.ts +143 -0
  24. package/src/client/CodeEditor/InteractiveWidgets/urlClicker.test.ts +48 -0
  25. package/src/client/CodeEditor/InteractiveWidgets/urlClicker.ts +12 -0
  26. package/src/client/CodeEditor/InteractiveWidgets/vec2Slider.ts +25 -0
  27. package/src/client/CodeEditor/basicSetup.ts +73 -0
  28. package/src/client/CodeEditor/getOrCreateEditor.tsx +615 -0
  29. package/src/client/CodeEditor/index.tsx +202 -0
  30. package/src/client/CodeEditor/json1PresenceBroadcast.ts +41 -0
  31. package/src/client/CodeEditor/json1PresenceDisplay.ts +350 -0
  32. package/src/client/CodeEditor/rainbowBrackets.ts +122 -0
  33. package/src/client/CodeEditor/style.scss +156 -0
  34. package/src/client/CodeEditor/typescriptExtension/worker.ts +32 -0
  35. package/src/client/CodeErrorOverlay/index.tsx +77 -0
  36. package/src/client/CodeErrorOverlay/style.scss +33 -0
  37. package/src/client/Fonts/fonts.ts +8 -0
  38. package/src/client/Icons/AdjustmentSVG.tsx +19 -0
  39. package/src/client/Icons/BugSVG.tsx +20 -0
  40. package/src/client/Icons/CloseSVG.tsx +19 -0
  41. package/src/client/Icons/CssSVG.tsx +16 -0
  42. package/src/client/Icons/DirectoryArrowSVG.tsx +19 -0
  43. package/src/client/Icons/EditSVG.tsx +19 -0
  44. package/src/client/Icons/FileSVG.tsx +19 -0
  45. package/src/client/Icons/FolderSVG.tsx +20 -0
  46. package/src/client/Icons/GearSVG.tsx +16 -0
  47. package/src/client/Icons/HtmlSVG.tsx +16 -0
  48. package/src/client/Icons/JavaScriptSVG.tsx +16 -0
  49. package/src/client/Icons/JsonSVG.tsx +16 -0
  50. package/src/client/Icons/MarkdownSVG.tsx +16 -0
  51. package/src/client/Icons/MicOffSVG.tsx +35 -0
  52. package/src/client/Icons/MicSVG.tsx +25 -0
  53. package/src/client/Icons/NewSVG.tsx +19 -0
  54. package/src/client/Icons/PinSVG.tsx +14 -0
  55. package/src/client/Icons/PlaySVG.tsx +16 -0
  56. package/src/client/Icons/QuestionMarkSVG.tsx +18 -0
  57. package/src/client/Icons/ReactSVG.tsx +16 -0
  58. package/src/client/Icons/SearchSVG.tsx +20 -0
  59. package/src/client/Icons/SparklesSVG.tsx +23 -0
  60. package/src/client/Icons/SplitEditorSVG.tsx +34 -0
  61. package/src/client/Icons/StopSVG.tsx +15 -0
  62. package/src/client/Icons/SvelteSVG.tsx +16 -0
  63. package/src/client/Icons/TrashSVG.tsx +19 -0
  64. package/src/client/Icons/TypeScriptSVG.tsx +16 -0
  65. package/src/client/Icons/index.tsx +26 -0
  66. package/src/client/ImageViewer/index.tsx +91 -0
  67. package/src/client/ImageViewer/style.scss +41 -0
  68. package/src/client/PresenceNotifications/PresenceNotifications.test.ts +134 -0
  69. package/src/client/PresenceNotifications/index.tsx +158 -0
  70. package/src/client/PresenceNotifications/style.scss +17 -0
  71. package/src/client/RunCodeWidget/index.tsx +117 -0
  72. package/src/client/RunCodeWidget/style.scss +24 -0
  73. package/src/client/SplitPaneResizeContext.tsx +159 -0
  74. package/src/client/TabList/Tab.tsx +73 -0
  75. package/src/client/TabList/index.tsx +35 -0
  76. package/src/client/TabList/style.scss +51 -0
  77. package/src/client/VZCodeContext/index.tsx +29 -0
  78. package/src/client/VZCodeContext/types.ts +248 -0
  79. package/src/client/VZCodeContext/useVZCodeState.ts +602 -0
  80. package/src/client/VZKeyboardShortcutsDoc.tsx +148 -0
  81. package/src/client/VZLeft.tsx +28 -0
  82. package/src/client/VZMiddle.tsx +172 -0
  83. package/src/client/VZResizer/index.tsx +128 -0
  84. package/src/client/VZResizer/styles.scss +33 -0
  85. package/src/client/VZRight.tsx +113 -0
  86. package/src/client/VZSettings.tsx +275 -0
  87. package/src/client/VZSidebar/AIChat/ChatInput.tsx +243 -0
  88. package/src/client/VZSidebar/AIChat/DiffView.scss +136 -0
  89. package/src/client/VZSidebar/AIChat/DiffView.tsx +176 -0
  90. package/src/client/VZSidebar/AIChat/Message.tsx +79 -0
  91. package/src/client/VZSidebar/AIChat/MessageList.tsx +190 -0
  92. package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +35 -0
  93. package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +156 -0
  94. package/src/client/VZSidebar/AIChat/TypingIndicator.tsx +19 -0
  95. package/src/client/VZSidebar/AIChat/index.tsx +357 -0
  96. package/src/client/VZSidebar/AIChat/styles.scss +646 -0
  97. package/src/client/VZSidebar/AIChat/useSpeechRecognition.ts +117 -0
  98. package/src/client/VZSidebar/CreateDirModal.tsx +111 -0
  99. package/src/client/VZSidebar/CreateFileModal.tsx +111 -0
  100. package/src/client/VZSidebar/DeleteConfirmationModal.tsx +76 -0
  101. package/src/client/VZSidebar/DirectoryListing.tsx +89 -0
  102. package/src/client/VZSidebar/EmptyState.tsx +11 -0
  103. package/src/client/VZSidebar/FileListing.tsx +91 -0
  104. package/src/client/VZSidebar/FileTypeIcon.tsx +49 -0
  105. package/src/client/VZSidebar/Item.tsx +237 -0
  106. package/src/client/VZSidebar/Listing.tsx +47 -0
  107. package/src/client/VZSidebar/Search.tsx +527 -0
  108. package/src/client/VZSidebar/VisualEditor.tsx +282 -0
  109. package/src/client/VZSidebar/VoiceChatModal.tsx +121 -0
  110. package/src/client/VZSidebar/aiCopyPaste.ts +199 -0
  111. package/src/client/VZSidebar/index.tsx +728 -0
  112. package/src/client/VZSidebar/styles.scss +612 -0
  113. package/src/client/VZSidebar/useDragAndDrop.tsx +408 -0
  114. package/src/client/bootstrap.ts +26 -0
  115. package/src/client/buildWorker.ts +3 -0
  116. package/src/client/featureFlags.ts +8 -0
  117. package/src/client/generateRequestId.ts +6 -0
  118. package/src/client/getFileTree.ts +53 -0
  119. package/src/client/index.ts +19 -0
  120. package/src/client/main.jsx +10 -0
  121. package/src/client/presenceColor.ts +8 -0
  122. package/src/client/shouldTriggerRun.ts +31 -0
  123. package/src/client/sortFileTree.ts +21 -0
  124. package/src/client/tabsSearchParameters.test.ts +79 -0
  125. package/src/client/tabsSearchParameters.ts +137 -0
  126. package/src/client/themes/index.ts +7 -0
  127. package/src/client/themes/themes.ts +77 -0
  128. package/src/client/themes/uiwImports.ts +32 -0
  129. package/src/client/themes/useDynamicTheme.ts +29 -0
  130. package/src/client/themes/vizhubTheme/colors.ts +80 -0
  131. package/src/client/themes/vizhubTheme/index.ts +193 -0
  132. package/src/client/useActions.ts +288 -0
  133. package/src/client/useESLint/index.ts +72 -0
  134. package/src/client/useESLint/worker.ts +113 -0
  135. package/src/client/useEditorCache.ts +35 -0
  136. package/src/client/useFileCRUD.ts +228 -0
  137. package/src/client/useKeyboardShortcuts.ts +451 -0
  138. package/src/client/useOpenDirectories.ts +99 -0
  139. package/src/client/usePresenceAutoFollow.ts +85 -0
  140. package/src/client/usePrettier/index.ts +178 -0
  141. package/src/client/usePrettier/worker.ts +109 -0
  142. package/src/client/useRunCode.tsx +15 -0
  143. package/src/client/useRuntimeError.ts +32 -0
  144. package/src/client/useSubmitOperation.ts +15 -0
  145. package/src/client/useURLSync.ts +137 -0
  146. package/src/client/usernameLocalStorage.ts +43 -0
  147. package/src/client/utils/fileExtension.ts +10 -0
  148. package/src/client/utils/isImageFile.ts +12 -0
  149. package/src/client/vzReducer/aiChatReducer.ts +44 -0
  150. package/src/client/vzReducer/closeTabReducer.test.ts +205 -0
  151. package/src/client/vzReducer/closeTabsReducer.ts +96 -0
  152. package/src/client/vzReducer/createInitialState.ts +38 -0
  153. package/src/client/vzReducer/editorNoLongerWantsFocusReducer.ts +9 -0
  154. package/src/client/vzReducer/findPane.ts +19 -0
  155. package/src/client/vzReducer/index.ts +250 -0
  156. package/src/client/vzReducer/openTabReducer.test.ts +127 -0
  157. package/src/client/vzReducer/openTabReducer.ts +74 -0
  158. package/src/client/vzReducer/searchReducer.test.ts +292 -0
  159. package/src/client/vzReducer/searchReducer.ts +181 -0
  160. package/src/client/vzReducer/setActiveFileIdReducer.ts +28 -0
  161. package/src/client/vzReducer/setActiveFileLeftRightReducer.ts +76 -0
  162. package/src/client/vzReducer/setIsDocOpenReducer.ts +9 -0
  163. package/src/client/vzReducer/setIsSettingsOpenReducer.ts +9 -0
  164. package/src/client/vzReducer/setThemeReducer.ts +9 -0
  165. package/src/client/vzReducer/setUsernameReducer.ts +9 -0
  166. package/src/client/vzReducer/splitCurrentPaneReducer.ts +66 -0
  167. package/src/client/vzReducer/toggleAutoFollowReducer.ts +12 -0
  168. package/src/client/vzReducer/updatePane.ts +40 -0
  169. package/src/client/vzReducer/updatePresenceIndicatorReducer.ts +30 -0
  170. package/src/client/vzReducer/visualEditorReducer.ts +9 -0
  171. package/src/ot.ts +24 -0
  172. package/src/randomId.ts +15 -0
  173. package/src/reactRouterExports.ts +15 -0
  174. package/src/runCode.ts +27 -0
  175. package/src/server/aiChatHandler/aiEditing.ts +151 -0
  176. package/src/server/aiChatHandler/chatOperations.ts +476 -0
  177. package/src/server/aiChatHandler/errorHandling.ts +66 -0
  178. package/src/server/aiChatHandler/index.ts +199 -0
  179. package/src/server/aiChatHandler/llmStreaming.ts +419 -0
  180. package/src/server/aiChatHandler/validation.ts +24 -0
  181. package/src/server/computeInitialDocument.ts +220 -0
  182. package/src/server/config.ts +24 -0
  183. package/src/server/featureFlags.ts +8 -0
  184. package/src/server/generateAIResponse.ts +188 -0
  185. package/src/server/handleAIAssist.ts +53 -0
  186. package/src/server/handleAIChatMessage.ts +2 -0
  187. package/src/server/handleAICopilot.ts +97 -0
  188. package/src/server/index.ts +379 -0
  189. package/src/server/isDirectory.ts +1 -0
  190. package/src/server/livekit.ts +24 -0
  191. package/src/server/prettier.ts +115 -0
  192. package/src/server/setupEnv.ts +8 -0
  193. package/src/submitOperation.ts +25 -0
  194. package/src/types.ts +205 -0
  195. package/src/utils/fileDiff.ts +117 -0
  196. package/src/vite-env.d.ts +10 -0
@@ -0,0 +1,275 @@
1
+ import {
2
+ useCallback,
3
+ useContext,
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
8
+ import { Button, Modal, Form } from './bootstrap';
9
+ import { ThemeLabel, themes } from './themes';
10
+ import { VZCodeContext } from './VZCodeContext';
11
+
12
+ const DEBUG = false;
13
+
14
+ // Font size options for the editor
15
+ const fontSizes = [
16
+ '10px',
17
+ '12px',
18
+ '14px',
19
+ '16px',
20
+ '18px',
21
+ '20px',
22
+ '24px',
23
+ ];
24
+
25
+ // List of system fonts to be detected for availability
26
+ const systemFonts = [
27
+ '-apple-system',
28
+ 'BlinkMacSystemFont',
29
+ 'Segoe UI',
30
+ 'Roboto',
31
+ 'Helvetica Neue',
32
+ 'Arial',
33
+ 'sans-serif',
34
+ 'Courier New',
35
+ 'Georgia',
36
+ 'Tahoma',
37
+ 'Verdana',
38
+ 'Times New Roman',
39
+ 'Trebuchet MS',
40
+ 'Comic Sans MS',
41
+ ];
42
+
43
+ // Utility function to detect if a given font is available
44
+ const isFontAvailable = (font: string): boolean => {
45
+ const testString = 'abcdefghijklmnopqrstuvwxyz0123456789';
46
+ const testSize = '72px';
47
+
48
+ const span = document.createElement('span');
49
+ span.style.fontSize = testSize;
50
+ span.style.visibility = 'hidden';
51
+ span.style.position = 'absolute';
52
+ span.innerHTML = testString;
53
+ document.body.appendChild(span);
54
+
55
+ const defaultFont = 'monospace';
56
+ span.style.fontFamily = defaultFont;
57
+ const defaultWidth = span.offsetWidth;
58
+
59
+ span.style.fontFamily = `${font}, ${defaultFont}`;
60
+ const newWidth = span.offsetWidth;
61
+
62
+ document.body.removeChild(span);
63
+
64
+ return newWidth !== defaultWidth;
65
+ };
66
+
67
+ export const VZSettings = ({
68
+ enableUsernameField = true,
69
+ }: {
70
+ enableUsernameField?: boolean;
71
+ }) => {
72
+ // Extracting context values for managing settings state
73
+ const {
74
+ isSettingsOpen,
75
+ closeSettings,
76
+ theme,
77
+ setTheme,
78
+ username,
79
+ setUsername,
80
+ } = useContext(VZCodeContext);
81
+
82
+ // State variables for managing font and font size
83
+ const [selectedFont, setSelectedFont] =
84
+ useState('Roboto Mono');
85
+ const [selectedFontSize, setSelectedFontSize] =
86
+ useState('16px');
87
+ const [availableFonts, setAvailableFonts] = useState<
88
+ string[]
89
+ >([]);
90
+
91
+ // Load saved settings from local storage on initial render
92
+ useEffect(() => {
93
+ if (typeof window !== 'undefined') {
94
+ const selectedFontFromLocalStorage =
95
+ window.localStorage.getItem('vzcodeSelectedFont');
96
+ const selectedFontSizeFromLocalStorage =
97
+ window.localStorage.getItem(
98
+ 'vzcodeSelectedFontSize',
99
+ );
100
+
101
+ if (selectedFontFromLocalStorage !== null) {
102
+ setSelectedFont(selectedFontFromLocalStorage);
103
+ }
104
+ if (selectedFontSizeFromLocalStorage !== null) {
105
+ setSelectedFontSize(
106
+ selectedFontSizeFromLocalStorage,
107
+ );
108
+ }
109
+ }
110
+ }, []);
111
+
112
+ // Detect available system fonts and update the state
113
+ useEffect(() => {
114
+ const detectFonts = () => {
115
+ const detectedFonts: string[] = [];
116
+ for (const font of systemFonts) {
117
+ if (isFontAvailable(font)) {
118
+ detectedFonts.push(font);
119
+ if (DEBUG) {
120
+ console.log(`${font} is available`);
121
+ }
122
+ } else if (DEBUG) {
123
+ console.log(`${font} is not available`);
124
+ }
125
+ }
126
+ setAvailableFonts(detectedFonts);
127
+ };
128
+
129
+ detectFonts();
130
+ }, []);
131
+
132
+ // Reset all settings to default values
133
+ const resetSettings = useCallback(() => {
134
+ setSelectedFont('Roboto Mono');
135
+ setSelectedFontSize('16px');
136
+ setTheme(themes[0].label);
137
+ if (enableUsernameField) {
138
+ setUsername('');
139
+ }
140
+ localStorage.removeItem('vzcodeSelectedFont');
141
+ localStorage.removeItem('vzcodeSelectedFontSize');
142
+ }, [enableUsernameField, setTheme, setUsername]);
143
+
144
+ // Update font family in the document's style
145
+ useEffect(() => {
146
+ document.body.style.setProperty(
147
+ '--vzcode-font-family',
148
+ selectedFont,
149
+ );
150
+ }, [selectedFont]);
151
+
152
+ // Update font size in the document's style
153
+ useEffect(() => {
154
+ document.body.style.setProperty(
155
+ '--vzcode-font-size',
156
+ selectedFontSize,
157
+ );
158
+ }, [selectedFontSize]);
159
+
160
+ const usernameRef = useRef<HTMLInputElement>(null);
161
+
162
+ // Handle username input change
163
+ const handleUsernameChange = useCallback(() => {
164
+ setUsername(usernameRef.current?.value || '');
165
+ }, [setUsername]);
166
+
167
+ // Close settings modal on Enter key press
168
+ useEffect(() => {
169
+ if (isSettingsOpen) {
170
+ const handleEnterKey = (event: KeyboardEvent) => {
171
+ if (event.key === 'Enter') {
172
+ closeSettings();
173
+ }
174
+ };
175
+
176
+ window.addEventListener('keydown', handleEnterKey);
177
+ return () => {
178
+ window.removeEventListener(
179
+ 'keydown',
180
+ handleEnterKey,
181
+ );
182
+ };
183
+ }
184
+ }, [isSettingsOpen, closeSettings]);
185
+
186
+ return isSettingsOpen ? (
187
+ <Modal
188
+ className="vz-settings"
189
+ show={isSettingsOpen}
190
+ onHide={closeSettings}
191
+ animation={false}
192
+ >
193
+ <Modal.Header closeButton>
194
+ <Modal.Title>Editor Settings</Modal.Title>
195
+ </Modal.Header>
196
+ <Modal.Body>
197
+ {enableUsernameField ? (
198
+ <Form.Group className="mb-3" controlId="formFork">
199
+ <Form.Label>Username</Form.Label>
200
+ <Form.Control
201
+ type="text"
202
+ ref={usernameRef}
203
+ onChange={handleUsernameChange}
204
+ placeholder="Enter username"
205
+ value={username}
206
+ />
207
+ <Form.Text className="text-muted">
208
+ Enter a username to be displayed on your
209
+ cursor
210
+ </Form.Text>
211
+ </Form.Group>
212
+ ) : null}
213
+
214
+ <Form.Group className="mb-3" controlId="formFork">
215
+ <Form.Label>Theme</Form.Label>
216
+ <select
217
+ className="form-select"
218
+ value={theme}
219
+ onChange={(e) =>
220
+ setTheme(e.target.value as ThemeLabel)
221
+ }
222
+ >
223
+ {themes.map(({ label }) => (
224
+ <option key={label} value={label}>
225
+ {label}
226
+ </option>
227
+ ))}
228
+ </select>
229
+ </Form.Group>
230
+
231
+ <Form.Group className="mb-3" controlId="formFork">
232
+ <Form.Label>Font</Form.Label>
233
+ <select
234
+ className="form-select"
235
+ onChange={(e) =>
236
+ setSelectedFont(e.target.value)
237
+ }
238
+ value={selectedFont}
239
+ >
240
+ {availableFonts.map((font) => (
241
+ <option key={font} value={font}>
242
+ {font}
243
+ </option>
244
+ ))}
245
+ </select>
246
+ </Form.Group>
247
+
248
+ <Form.Group className="mb-3" controlId="formFork">
249
+ <Form.Label>Font Size</Form.Label>
250
+ <select
251
+ className="form-select"
252
+ onChange={(e) =>
253
+ setSelectedFontSize(e.target.value)
254
+ }
255
+ value={selectedFontSize}
256
+ >
257
+ {fontSizes.map((size) => (
258
+ <option key={size} value={size}>
259
+ {size}
260
+ </option>
261
+ ))}
262
+ </select>
263
+ </Form.Group>
264
+ </Modal.Body>
265
+ <Modal.Footer>
266
+ <Button variant="secondary" onClick={resetSettings}>
267
+ Reset to Default
268
+ </Button>
269
+ <Button variant="primary" onClick={closeSettings}>
270
+ Done
271
+ </Button>
272
+ </Modal.Footer>
273
+ </Modal>
274
+ ) : null;
275
+ };
@@ -0,0 +1,243 @@
1
+ import {
2
+ useRef,
3
+ useEffect,
4
+ useCallback,
5
+ memo,
6
+ } from 'react';
7
+ import {
8
+ Form,
9
+ Button,
10
+ ButtonGroup,
11
+ ToggleButton,
12
+ } from '../../bootstrap';
13
+ import { enableAskMode } from '../../featureFlags';
14
+ import { useSpeechRecognition } from './useSpeechRecognition';
15
+ import { MicSVG, MicOffSVG } from '../../Icons';
16
+
17
+ interface ChatInputProps {
18
+ aiChatMessage: string;
19
+ setAIChatMessage: (message: string) => void;
20
+ onSendMessage: () => void;
21
+ isLoading: boolean;
22
+ focused: boolean;
23
+ aiChatMode: 'ask' | 'edit';
24
+ setAIChatMode: (mode: 'ask' | 'edit') => void;
25
+ navigateMessageHistoryUp: () => void;
26
+ navigateMessageHistoryDown: () => void;
27
+ resetMessageHistoryNavigation: () => void;
28
+ }
29
+
30
+ const ChatInputComponent = ({
31
+ aiChatMessage,
32
+ setAIChatMessage,
33
+ onSendMessage,
34
+ isLoading,
35
+ focused,
36
+ aiChatMode,
37
+ setAIChatMode,
38
+ navigateMessageHistoryUp,
39
+ navigateMessageHistoryDown,
40
+ resetMessageHistoryNavigation,
41
+ }: ChatInputProps) => {
42
+ const inputRef = useRef<HTMLTextAreaElement>(null);
43
+
44
+ // Use the speech recognition hook
45
+ const {
46
+ isSpeaking,
47
+ toggleSpeechRecognition,
48
+ stopSpeaking,
49
+ } = useSpeechRecognition(setAIChatMessage);
50
+
51
+ useEffect(() => {
52
+ // Focus the input when the AI chat is focused
53
+ if (focused && inputRef.current) {
54
+ inputRef.current.focus();
55
+ }
56
+ }, [focused]);
57
+
58
+ const handleKeyDown = useCallback(
59
+ (event: React.KeyboardEvent) => {
60
+ if (event.key === 'Enter' && !event.shiftKey) {
61
+ event.preventDefault();
62
+ onSendMessage();
63
+ } else if (event.key === 'Enter' && event.shiftKey) {
64
+ // Shift+Enter should add a newline, not trigger run code
65
+ // We prevent the event from bubbling up to the global keyboard handler
66
+ event.stopPropagation();
67
+ } else if (event.key === 'ArrowUp') {
68
+ // Only navigate history if cursor is at the beginning of the first line
69
+ const textarea =
70
+ event.target as HTMLTextAreaElement;
71
+ const { selectionStart, value } = textarea;
72
+ const lines = value
73
+ .substring(0, selectionStart)
74
+ .split('\n');
75
+
76
+ // Check if we're at the beginning of the first line
77
+ if (lines.length === 1 && selectionStart === 0) {
78
+ event.preventDefault();
79
+ navigateMessageHistoryUp();
80
+ }
81
+ } else if (event.key === 'ArrowDown') {
82
+ // Only navigate history if cursor is at the end of the last line
83
+ const textarea =
84
+ event.target as HTMLTextAreaElement;
85
+ const { selectionStart, value } = textarea;
86
+ const remainingText =
87
+ value.substring(selectionStart);
88
+ const remainingLines = remainingText.split('\n');
89
+
90
+ // Check if we're at the end of the last line
91
+ if (
92
+ remainingLines.length === 1 &&
93
+ remainingLines[0] === ''
94
+ ) {
95
+ event.preventDefault();
96
+ navigateMessageHistoryDown();
97
+ }
98
+ }
99
+ },
100
+ [
101
+ onSendMessage,
102
+ navigateMessageHistoryUp,
103
+ navigateMessageHistoryDown,
104
+ ],
105
+ );
106
+
107
+ const handleSendClick = useCallback(() => {
108
+ // If speech recognition is active, stop it
109
+ if (isSpeaking) {
110
+ stopSpeaking();
111
+ }
112
+ onSendMessage();
113
+ }, [onSendMessage, isSpeaking, stopSpeaking]);
114
+
115
+ const handleChange = useCallback(
116
+ (event: React.ChangeEvent<HTMLTextAreaElement>) => {
117
+ setAIChatMessage(event.target.value);
118
+ // Reset history navigation when user starts typing
119
+ resetMessageHistoryNavigation();
120
+ },
121
+ [setAIChatMessage, resetMessageHistoryNavigation],
122
+ );
123
+
124
+ return (
125
+ <div className="ai-chat-input-container">
126
+ {enableAskMode && (
127
+ <div
128
+ className="ai-chat-mode-toggle"
129
+ style={{ marginBottom: '8px' }}
130
+ >
131
+ <ButtonGroup size="sm">
132
+ <ToggleButton
133
+ id="ai-chat-mode-ask"
134
+ type="radio"
135
+ variant={
136
+ aiChatMode === 'ask'
137
+ ? 'primary'
138
+ : 'outline-primary'
139
+ }
140
+ name="ai-chat-mode"
141
+ value="ask"
142
+ checked={aiChatMode === 'ask'}
143
+ onChange={() => setAIChatMode('ask')}
144
+ disabled={isLoading}
145
+ >
146
+ 💬 Ask
147
+ </ToggleButton>
148
+ <ToggleButton
149
+ id="ai-chat-mode-edit"
150
+ type="radio"
151
+ variant={
152
+ aiChatMode === 'edit'
153
+ ? 'primary'
154
+ : 'outline-primary'
155
+ }
156
+ name="ai-chat-mode"
157
+ value="edit"
158
+ checked={aiChatMode === 'edit'}
159
+ onChange={() => setAIChatMode('edit')}
160
+ disabled={isLoading}
161
+ >
162
+ ✏️ Edit
163
+ </ToggleButton>
164
+ </ButtonGroup>
165
+ <div className="ai-chat-mode-description">
166
+ {aiChatMode === 'ask'
167
+ ? 'Ask questions without editing files'
168
+ : 'Get answers and code edits'}
169
+ </div>
170
+ </div>
171
+ )}
172
+ <Form.Group className="ai-chat-input-group">
173
+ <Form.Control
174
+ as="textarea"
175
+ rows={5}
176
+ value={aiChatMessage}
177
+ onChange={handleChange}
178
+ onKeyDown={handleKeyDown}
179
+ ref={inputRef}
180
+ placeholder={
181
+ aiChatMode === 'edit'
182
+ ? 'Ask me to make code changes...'
183
+ : 'Ask me anything about your code...'
184
+ }
185
+ spellCheck="false"
186
+ disabled={isLoading}
187
+ aria-label="Chat message input"
188
+ />
189
+ <div className="ai-chat-input-footer">
190
+ <span className="ai-chat-hint">
191
+ {aiChatMessage ? 'Press Enter to send' : ''}
192
+ </span>
193
+ <div style={{ display: 'flex', gap: '8px' }}>
194
+ <Button
195
+ variant={
196
+ isSpeaking ? 'danger' : 'outline-secondary'
197
+ }
198
+ size="sm"
199
+ onClick={toggleSpeechRecognition}
200
+ disabled={isLoading}
201
+ aria-label={
202
+ isSpeaking
203
+ ? 'Stop voice typing'
204
+ : 'Start voice typing'
205
+ }
206
+ title={
207
+ isSpeaking
208
+ ? 'Stop voice typing'
209
+ : 'Start voice typing'
210
+ }
211
+ style={{
212
+ borderColor: isSpeaking
213
+ ? undefined
214
+ : '#dee2e6',
215
+ borderRadius: '0.375rem',
216
+ borderWidth: '1px',
217
+ borderStyle: 'solid',
218
+ }}
219
+ >
220
+ {isSpeaking ? <MicOffSVG /> : <MicSVG />}
221
+ </Button>
222
+ <Button
223
+ variant={
224
+ aiChatMessage.trim()
225
+ ? 'primary'
226
+ : 'outline-secondary'
227
+ }
228
+ onClick={handleSendClick}
229
+ disabled={!aiChatMessage.trim() || isLoading}
230
+ className="ai-chat-send-button"
231
+ aria-label="Send message"
232
+ title="Send message (Enter)"
233
+ >
234
+ Send
235
+ </Button>
236
+ </div>
237
+ </div>
238
+ </Form.Group>
239
+ </div>
240
+ );
241
+ };
242
+
243
+ export const ChatInput = memo(ChatInputComponent);
@@ -0,0 +1,136 @@
1
+ .diff-view {
2
+ margin: 12px 0;
3
+
4
+ .diff-summary {
5
+ margin-bottom: 8px;
6
+ display: flex;
7
+ justify-content: space-between;
8
+ align-items: center;
9
+
10
+ .diff-stats {
11
+ display: flex;
12
+ align-items: center;
13
+ gap: 8px;
14
+ font-size: 12px;
15
+ color: var(--bs-secondary-color);
16
+
17
+ .files-changed {
18
+ color: white;
19
+ font-weight: 600;
20
+ }
21
+
22
+ .additions {
23
+ color: #28a745;
24
+ font-weight: 600;
25
+ }
26
+
27
+ .deletions {
28
+ color: #dc3545;
29
+ font-weight: 600;
30
+ }
31
+ }
32
+ }
33
+
34
+ .diff-files {
35
+ display: flex;
36
+ flex-direction: column;
37
+ gap: 4px;
38
+
39
+ // Fix diff2html styling issues for dark theme and layout
40
+ .d2h-wrapper {
41
+ // Override diff2html variables for dark theme
42
+ --d2h-bg-color: rgb(41, 44, 52);
43
+ --d2h-color: #e6edf3;
44
+ --d2h-border-color: #30363d;
45
+ --d2h-dim-color: #6e7681;
46
+ --d2h-info-bg-color: #161b22;
47
+ --d2h-file-header-bg-color: #161b22;
48
+ --d2h-file-header-border-color: #30363d;
49
+ --d2h-line-border-color: #21262d;
50
+ --d2h-ins-bg-color: rgba(46, 160, 67, 0.15);
51
+ --d2h-ins-border-color: rgba(46, 160, 67, 0.4);
52
+ --d2h-ins-highlight-bg-color: rgba(46, 160, 67, 0.4);
53
+ --d2h-ins-label-color: #3fb950;
54
+ --d2h-del-bg-color: rgba(248, 81, 73, 0.1);
55
+ --d2h-del-border-color: rgba(248, 81, 73, 0.4);
56
+ --d2h-del-highlight-bg-color: rgba(248, 81, 73, 0.4);
57
+ --d2h-del-label-color: #f85149;
58
+
59
+ background-color: var(--d2h-bg-color);
60
+ color: var(--d2h-color);
61
+ border-radius: 6px;
62
+ overflow: hidden;
63
+ }
64
+
65
+ // Fix layout issues with line numbers
66
+ .d2h-diff-table {
67
+ position: relative;
68
+ background-color: var(--d2h-bg-color);
69
+ }
70
+
71
+ .d2h-code-linenumber {
72
+ position: relative !important;
73
+ float: none !important;
74
+ display: table-cell !important;
75
+ width: auto !important;
76
+ background-color: var(--d2h-bg-color) !important;
77
+ border-color: var(--d2h-line-border-color) !important;
78
+ color: var(--d2h-dim-color) !important;
79
+ }
80
+
81
+ .d2h-code-side-linenumber {
82
+ position: relative !important;
83
+ float: none !important;
84
+ display: table-cell !important;
85
+ width: auto !important;
86
+ background-color: var(--d2h-bg-color) !important;
87
+ border-color: var(--d2h-line-border-color) !important;
88
+ color: var(--d2h-dim-color) !important;
89
+ }
90
+
91
+ // Ensure proper table layout
92
+ .d2h-diff-tbody tr {
93
+ display: table-row;
94
+ }
95
+
96
+ .d2h-diff-tbody td {
97
+ display: table-cell;
98
+ vertical-align: top;
99
+ }
100
+
101
+ // Fix file header styling
102
+ .d2h-file-header {
103
+ background-color: var(
104
+ --d2h-file-header-bg-color
105
+ ) !important;
106
+ border-bottom: 1px solid
107
+ var(--d2h-file-header-border-color) !important;
108
+ color: var(--d2h-color);
109
+ }
110
+
111
+ // Ensure proper line styling
112
+ .d2h-del {
113
+ background-color: var(--d2h-del-bg-color) !important;
114
+ border-color: var(--d2h-del-border-color) !important;
115
+ }
116
+
117
+ .d2h-ins {
118
+ background-color: var(--d2h-ins-bg-color) !important;
119
+ border-color: var(--d2h-ins-border-color) !important;
120
+ }
121
+
122
+ // Style clickable file names
123
+ .d2h-file-name {
124
+ &:hover {
125
+ text-decoration: underline !important;
126
+ opacity: 0.8;
127
+ }
128
+ }
129
+
130
+ // Fix wrapper border
131
+ .d2h-file-wrapper {
132
+ border: 1px solid var(--d2h-border-color) !important;
133
+ background-color: var(--d2h-bg-color);
134
+ }
135
+ }
136
+ }