vzcode 1.31.0 → 1.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/assets/index-DPIrco_g.js +235 -0
- package/dist/assets/index-DnunmWjy.css +1 -0
- package/dist/assets/worker-BrwN8AXx.js +330 -0
- package/dist/assets/worker-CkHcCP1n.js +136 -0
- package/dist/assets/worker-DkYJN5WQ.js +453 -0
- package/dist/index.html +2 -2
- package/package.json +48 -41
- package/src/client/App/index.tsx +3 -0
- package/src/client/CodeEditor/getOrCreateEditor.ts +98 -9
- package/src/client/CodeEditor/index.tsx +56 -24
- package/src/client/CodeEditor/typescriptExtension/worker.ts +32 -0
- package/src/client/Icons/MicSVG.tsx +1 -1
- package/src/client/VZCodeContext.tsx +18 -2
- package/src/client/VZMiddle.tsx +11 -0
- package/src/client/VZSidebar/AIChat.tsx +142 -0
- package/src/client/VZSidebar/Search.tsx +11 -0
- package/src/client/VZSidebar/index.tsx +53 -8
- package/src/client/VZSidebar/styles.scss +178 -1
- package/src/client/featureFlags.ts +2 -0
- package/src/client/useActions.ts +20 -0
- package/src/client/useESLint/index.ts +72 -0
- package/src/client/useESLint/worker.ts +113 -0
- package/src/client/useEditorCache.ts +1 -0
- package/src/client/useFileCRUD.ts +47 -0
- package/src/client/useOpenDirectories.ts +43 -12
- package/src/client/utils/fileExtension.ts +10 -0
- package/src/client/vzReducer/aiChatReducer.ts +31 -0
- package/src/client/vzReducer/createInitialState.ts +2 -0
- package/src/client/vzReducer/index.ts +20 -0
- package/src/server/featureFlags.js +2 -0
- package/dist/assets/index-D01yzCWj.js +0 -234
- package/dist/assets/index-DTtcN2MP.css +0 -1
- package/dist/assets/worker-CaP6zYz7.js +0 -335
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useRef,
|
|
3
|
+
useEffect,
|
|
4
|
+
useContext,
|
|
5
|
+
useState,
|
|
6
|
+
useCallback,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import { Form, Button } from '../bootstrap';
|
|
9
|
+
import { VZCodeContext } from '../VZCodeContext';
|
|
10
|
+
|
|
11
|
+
export const AIChat = () => {
|
|
12
|
+
const [message, setMessage] = useState('');
|
|
13
|
+
const [messages, setMessages] = useState([
|
|
14
|
+
{
|
|
15
|
+
id: 1,
|
|
16
|
+
type: 'assistant',
|
|
17
|
+
content:
|
|
18
|
+
"Hello! I'm your AI assistant. How can I help you with your code today?",
|
|
19
|
+
timestamp: new Date(),
|
|
20
|
+
},
|
|
21
|
+
]);
|
|
22
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
23
|
+
const inputRef = useRef(null);
|
|
24
|
+
const messagesEndRef = useRef(null);
|
|
25
|
+
|
|
26
|
+
const { aiChatFocused } = useContext(VZCodeContext);
|
|
27
|
+
|
|
28
|
+
const scrollToBottom = () => {
|
|
29
|
+
messagesEndRef.current?.scrollIntoView({
|
|
30
|
+
behavior: 'smooth',
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
scrollToBottom();
|
|
36
|
+
}, [messages]);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
// Focus the input when the AI chat is focused
|
|
40
|
+
if (aiChatFocused && inputRef.current) {
|
|
41
|
+
inputRef.current.focus();
|
|
42
|
+
}
|
|
43
|
+
}, [aiChatFocused]);
|
|
44
|
+
|
|
45
|
+
const handleSendMessage = useCallback(async () => {
|
|
46
|
+
if (!message.trim() || isLoading) return;
|
|
47
|
+
|
|
48
|
+
const userMessage = {
|
|
49
|
+
id: Date.now(),
|
|
50
|
+
type: 'user',
|
|
51
|
+
content: message.trim(),
|
|
52
|
+
timestamp: new Date(),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
56
|
+
setMessage('');
|
|
57
|
+
setIsLoading(true);
|
|
58
|
+
|
|
59
|
+
// Simulate AI response (replace with actual AI integration later)
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
const aiResponse = {
|
|
62
|
+
id: Date.now() + 1,
|
|
63
|
+
type: 'assistant',
|
|
64
|
+
content:
|
|
65
|
+
'I understand you\'re asking about: "' +
|
|
66
|
+
userMessage.content +
|
|
67
|
+
'". This is a placeholder response. The AI chat feature is still being developed!',
|
|
68
|
+
timestamp: new Date(),
|
|
69
|
+
};
|
|
70
|
+
setMessages((prev) => [...prev, aiResponse]);
|
|
71
|
+
setIsLoading(false);
|
|
72
|
+
}, 1000);
|
|
73
|
+
}, [message, isLoading]);
|
|
74
|
+
|
|
75
|
+
const handleKeyDown = (event) => {
|
|
76
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
handleSendMessage();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div className="ai-chat-container">
|
|
84
|
+
<div className="ai-chat-messages">
|
|
85
|
+
{messages.map((msg) => (
|
|
86
|
+
<div
|
|
87
|
+
key={msg.id}
|
|
88
|
+
className={`ai-chat-message ${msg.type === 'user' ? 'user' : 'assistant'}`}
|
|
89
|
+
>
|
|
90
|
+
<div className="ai-chat-message-content">
|
|
91
|
+
{msg.content}
|
|
92
|
+
</div>
|
|
93
|
+
<div className="ai-chat-message-time">
|
|
94
|
+
{msg.timestamp.toLocaleTimeString([], {
|
|
95
|
+
hour: '2-digit',
|
|
96
|
+
minute: '2-digit',
|
|
97
|
+
})}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
))}
|
|
101
|
+
{isLoading && (
|
|
102
|
+
<div className="ai-chat-message assistant">
|
|
103
|
+
<div className="ai-chat-message-content">
|
|
104
|
+
<div className="ai-chat-typing">
|
|
105
|
+
<span></span>
|
|
106
|
+
<span></span>
|
|
107
|
+
<span></span>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
<div ref={messagesEndRef} />
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div className="ai-chat-input-container">
|
|
116
|
+
<Form.Group className="ai-chat-input-group">
|
|
117
|
+
<Form.Control
|
|
118
|
+
as="textarea"
|
|
119
|
+
rows={2}
|
|
120
|
+
value={message}
|
|
121
|
+
onChange={(event) =>
|
|
122
|
+
setMessage(event.target.value)
|
|
123
|
+
}
|
|
124
|
+
onKeyDown={handleKeyDown}
|
|
125
|
+
ref={inputRef}
|
|
126
|
+
placeholder="Ask me anything about your code..."
|
|
127
|
+
spellCheck="false"
|
|
128
|
+
disabled={isLoading}
|
|
129
|
+
/>
|
|
130
|
+
<Button
|
|
131
|
+
variant="primary"
|
|
132
|
+
onClick={handleSendMessage}
|
|
133
|
+
disabled={!message.trim() || isLoading}
|
|
134
|
+
className="ai-chat-send-button"
|
|
135
|
+
>
|
|
136
|
+
Send
|
|
137
|
+
</Button>
|
|
138
|
+
</Form.Group>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
};
|
|
@@ -64,6 +64,7 @@ export const Search = () => {
|
|
|
64
64
|
setSearchFocusedIndex,
|
|
65
65
|
shareDBDoc,
|
|
66
66
|
editorCache,
|
|
67
|
+
openTab,
|
|
67
68
|
} = useContext(VZCodeContext);
|
|
68
69
|
const {
|
|
69
70
|
pattern,
|
|
@@ -227,6 +228,9 @@ export const Search = () => {
|
|
|
227
228
|
case 'Enter':
|
|
228
229
|
case ' ':
|
|
229
230
|
const fileId: VizFileId = files[focusedIndex][0];
|
|
231
|
+
|
|
232
|
+
openTab({ fileId, isTransient: true });
|
|
233
|
+
|
|
230
234
|
setActiveFileId(fileId);
|
|
231
235
|
|
|
232
236
|
if (focusedChildIndex !== null) {
|
|
@@ -441,6 +445,13 @@ export const Search = () => {
|
|
|
441
445
|
childIndex,
|
|
442
446
|
);
|
|
443
447
|
|
|
448
|
+
openTab({
|
|
449
|
+
fileId,
|
|
450
|
+
isTransient: true,
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
setActiveFileId(fileId);
|
|
454
|
+
|
|
444
455
|
const cacheKey =
|
|
445
456
|
editorCacheKey(
|
|
446
457
|
fileId,
|
|
@@ -25,15 +25,20 @@ import {
|
|
|
25
25
|
PinSVG,
|
|
26
26
|
QuestionMarkSVG,
|
|
27
27
|
SearchSVG,
|
|
28
|
+
SparklesSVG,
|
|
28
29
|
} from '../Icons';
|
|
29
30
|
import { MicSVG } from '../Icons/MicSVG';
|
|
30
31
|
import { sortFileTree } from '../sortFileTree';
|
|
31
32
|
import { SplitPaneResizeContext } from '../SplitPaneResizeContext';
|
|
32
33
|
import { VZCodeContext } from '../VZCodeContext';
|
|
34
|
+
import { AIChat } from './AIChat';
|
|
33
35
|
import { Listing } from './Listing';
|
|
34
36
|
import { Search } from './Search';
|
|
35
37
|
import { useDragAndDrop } from './useDragAndDrop';
|
|
36
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
enableLiveKit,
|
|
40
|
+
enableAIChat,
|
|
41
|
+
} from '../featureFlags';
|
|
37
42
|
import './styles.scss';
|
|
38
43
|
|
|
39
44
|
// TODO turn this UI back on when we are actually detecting
|
|
@@ -105,6 +110,11 @@ export const VZSidebar = ({
|
|
|
105
110
|
<strong>Open Voice Chat Menu</strong>
|
|
106
111
|
</div>
|
|
107
112
|
),
|
|
113
|
+
aiChatToolTipText = (
|
|
114
|
+
<div>
|
|
115
|
+
<strong>AI Chat</strong>
|
|
116
|
+
</div>
|
|
117
|
+
),
|
|
108
118
|
}: {
|
|
109
119
|
createFileTooltipText?: React.ReactNode;
|
|
110
120
|
createDirTooltipText?: React.ReactNode;
|
|
@@ -116,6 +126,7 @@ export const VZSidebar = ({
|
|
|
116
126
|
enableAutoFollowTooltipText?: React.ReactNode;
|
|
117
127
|
disableAutoFollowTooltipText?: React.ReactNode;
|
|
118
128
|
voiceChatToolTipText?: React.ReactNode;
|
|
129
|
+
aiChatToolTipText?: React.ReactNode;
|
|
119
130
|
}) => {
|
|
120
131
|
const {
|
|
121
132
|
files,
|
|
@@ -124,6 +135,8 @@ export const VZSidebar = ({
|
|
|
124
135
|
setIsDocOpen,
|
|
125
136
|
isSearchOpen,
|
|
126
137
|
setIsSearchOpen,
|
|
138
|
+
isAIChatOpen,
|
|
139
|
+
setIsAIChatOpen,
|
|
127
140
|
handleOpenCreateFileModal,
|
|
128
141
|
handleOpenCreateDirModal,
|
|
129
142
|
connected,
|
|
@@ -270,7 +283,10 @@ export const VZSidebar = ({
|
|
|
270
283
|
<i
|
|
271
284
|
id="files-icon"
|
|
272
285
|
className="icon-button icon-button-dark"
|
|
273
|
-
onClick={() =>
|
|
286
|
+
onClick={() => {
|
|
287
|
+
setIsSearchOpen(false);
|
|
288
|
+
setIsAIChatOpen(false);
|
|
289
|
+
}}
|
|
274
290
|
>
|
|
275
291
|
<FolderSVG />
|
|
276
292
|
</i>
|
|
@@ -287,12 +303,37 @@ export const VZSidebar = ({
|
|
|
287
303
|
<i
|
|
288
304
|
id="search-icon"
|
|
289
305
|
className="icon-button icon-button-dark"
|
|
290
|
-
onClick={() =>
|
|
306
|
+
onClick={() => {
|
|
307
|
+
setIsSearchOpen(true);
|
|
308
|
+
setIsAIChatOpen(false);
|
|
309
|
+
}}
|
|
291
310
|
>
|
|
292
311
|
<SearchSVG />
|
|
293
312
|
</i>
|
|
294
313
|
</OverlayTrigger>
|
|
295
314
|
|
|
315
|
+
{enableAIChat && (
|
|
316
|
+
<OverlayTrigger
|
|
317
|
+
placement="right"
|
|
318
|
+
overlay={
|
|
319
|
+
<Tooltip id="ai-chat-tooltip">
|
|
320
|
+
{aiChatToolTipText}
|
|
321
|
+
</Tooltip>
|
|
322
|
+
}
|
|
323
|
+
>
|
|
324
|
+
<i
|
|
325
|
+
id="ai-chat-icon"
|
|
326
|
+
className="icon-button icon-button-dark"
|
|
327
|
+
onClick={() => {
|
|
328
|
+
setIsAIChatOpen(true);
|
|
329
|
+
setIsSearchOpen(false);
|
|
330
|
+
}}
|
|
331
|
+
>
|
|
332
|
+
<SparklesSVG />
|
|
333
|
+
</i>
|
|
334
|
+
</OverlayTrigger>
|
|
335
|
+
)}
|
|
336
|
+
|
|
296
337
|
<OverlayTrigger
|
|
297
338
|
placement="right"
|
|
298
339
|
overlay={
|
|
@@ -438,7 +479,15 @@ export const VZSidebar = ({
|
|
|
438
479
|
onDragLeave={handleDragLeave}
|
|
439
480
|
onDrop={handleDrop}
|
|
440
481
|
>
|
|
441
|
-
{
|
|
482
|
+
{isAIChatOpen ? (
|
|
483
|
+
<div className="sidebar-ai-chat">
|
|
484
|
+
<AIChat />
|
|
485
|
+
</div>
|
|
486
|
+
) : isSearchOpen ? (
|
|
487
|
+
<div className="sidebar-search">
|
|
488
|
+
<Search />
|
|
489
|
+
</div>
|
|
490
|
+
) : (
|
|
442
491
|
<div className="sidebar-files">
|
|
443
492
|
{isDragOver ? (
|
|
444
493
|
<div className="empty drag-over">
|
|
@@ -480,10 +529,6 @@ export const VZSidebar = ({
|
|
|
480
529
|
</div>
|
|
481
530
|
)}
|
|
482
531
|
</div>
|
|
483
|
-
) : (
|
|
484
|
-
<div className="sidebar-search">
|
|
485
|
-
<Search />
|
|
486
|
-
</div>
|
|
487
532
|
)}
|
|
488
533
|
</div>
|
|
489
534
|
</div>
|
|
@@ -66,7 +66,8 @@
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
.sidebar-files,
|
|
69
|
-
.sidebar-search
|
|
69
|
+
.sidebar-search,
|
|
70
|
+
.sidebar-ai-chat {
|
|
70
71
|
width: 100%;
|
|
71
72
|
}
|
|
72
73
|
|
|
@@ -76,6 +77,182 @@
|
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
.sidebar-ai-chat {
|
|
81
|
+
height: 100%;
|
|
82
|
+
display: flex;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
|
|
85
|
+
.ai-chat-container {
|
|
86
|
+
height: 100%;
|
|
87
|
+
display: flex;
|
|
88
|
+
flex-direction: column;
|
|
89
|
+
padding: 10px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.ai-chat-messages {
|
|
93
|
+
flex: 1;
|
|
94
|
+
overflow-y: auto;
|
|
95
|
+
margin-bottom: 10px;
|
|
96
|
+
padding-right: 5px;
|
|
97
|
+
|
|
98
|
+
&::-webkit-scrollbar {
|
|
99
|
+
width: 6px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
&::-webkit-scrollbar-track {
|
|
103
|
+
background: var(--vh-color-neutral-02);
|
|
104
|
+
border-radius: 3px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
&::-webkit-scrollbar-thumb {
|
|
108
|
+
background: var(--vh-color-neutral-04);
|
|
109
|
+
border-radius: 3px;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.ai-chat-message {
|
|
114
|
+
margin-bottom: 15px;
|
|
115
|
+
|
|
116
|
+
&.user {
|
|
117
|
+
.ai-chat-message-content {
|
|
118
|
+
background: var(--vh-color-primary-01);
|
|
119
|
+
color: white;
|
|
120
|
+
margin-left: 20px;
|
|
121
|
+
border-radius: 12px 12px 4px 12px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.ai-chat-message-time {
|
|
125
|
+
text-align: right;
|
|
126
|
+
margin-right: 5px;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
&.assistant {
|
|
131
|
+
.ai-chat-message-content {
|
|
132
|
+
background: var(--vh-color-neutral-02);
|
|
133
|
+
color: var(--vh-color-neutral-04);
|
|
134
|
+
margin-right: 20px;
|
|
135
|
+
border-radius: 12px 12px 12px 4px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.ai-chat-message-time {
|
|
139
|
+
text-align: left;
|
|
140
|
+
margin-left: 5px;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.ai-chat-message-content {
|
|
146
|
+
padding: 10px 12px;
|
|
147
|
+
font-size: 14px;
|
|
148
|
+
line-height: 1.4;
|
|
149
|
+
word-wrap: break-word;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.ai-chat-message-time {
|
|
153
|
+
font-size: 11px;
|
|
154
|
+
color: var(--vh-color-neutral-03);
|
|
155
|
+
margin-top: 4px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.ai-chat-typing {
|
|
159
|
+
display: flex;
|
|
160
|
+
gap: 4px;
|
|
161
|
+
align-items: center;
|
|
162
|
+
|
|
163
|
+
span {
|
|
164
|
+
width: 6px;
|
|
165
|
+
height: 6px;
|
|
166
|
+
background: var(--vh-color-neutral-03);
|
|
167
|
+
border-radius: 50%;
|
|
168
|
+
animation: typing 1.4s infinite ease-in-out;
|
|
169
|
+
|
|
170
|
+
&:nth-child(1) {
|
|
171
|
+
animation-delay: -0.32s;
|
|
172
|
+
}
|
|
173
|
+
&:nth-child(2) {
|
|
174
|
+
animation-delay: -0.16s;
|
|
175
|
+
}
|
|
176
|
+
&:nth-child(3) {
|
|
177
|
+
animation-delay: 0s;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.ai-chat-input-container {
|
|
183
|
+
border-top: 1px solid var(--vh-color-neutral-02);
|
|
184
|
+
padding-top: 10px;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.ai-chat-input-group {
|
|
188
|
+
display: flex;
|
|
189
|
+
gap: 8px;
|
|
190
|
+
margin-bottom: 0;
|
|
191
|
+
|
|
192
|
+
textarea {
|
|
193
|
+
flex: 1;
|
|
194
|
+
background: var(--vh-color-neutral-02);
|
|
195
|
+
border: 1px solid var(--vh-color-neutral-03);
|
|
196
|
+
color: var(--vh-color-neutral-04);
|
|
197
|
+
border-radius: 8px;
|
|
198
|
+
padding: 8px 10px;
|
|
199
|
+
font-size: 14px;
|
|
200
|
+
font-family: var(--vzcode-font-family);
|
|
201
|
+
resize: none;
|
|
202
|
+
min-height: 36px;
|
|
203
|
+
|
|
204
|
+
&:focus {
|
|
205
|
+
outline: none;
|
|
206
|
+
border-color: var(--vh-color-primary-01);
|
|
207
|
+
box-shadow: 0 0 0 2px rgba(38, 67, 153, 0.2);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
&::placeholder {
|
|
211
|
+
color: var(--vh-color-neutral-03);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
&:disabled {
|
|
215
|
+
opacity: 0.6;
|
|
216
|
+
cursor: not-allowed;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.ai-chat-send-button {
|
|
221
|
+
background: var(--vh-color-primary-01);
|
|
222
|
+
border: none;
|
|
223
|
+
color: white;
|
|
224
|
+
border-radius: 8px;
|
|
225
|
+
padding: 8px 16px;
|
|
226
|
+
font-size: 14px;
|
|
227
|
+
font-weight: 500;
|
|
228
|
+
cursor: pointer;
|
|
229
|
+
transition: background-color 0.2s ease;
|
|
230
|
+
|
|
231
|
+
&:hover:not(:disabled) {
|
|
232
|
+
background: var(--vh-color-primary-02);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
&:disabled {
|
|
236
|
+
background: var(--vh-color-neutral-03);
|
|
237
|
+
cursor: not-allowed;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
@keyframes typing {
|
|
244
|
+
0%,
|
|
245
|
+
80%,
|
|
246
|
+
100% {
|
|
247
|
+
transform: scale(0.8);
|
|
248
|
+
opacity: 0.5;
|
|
249
|
+
}
|
|
250
|
+
40% {
|
|
251
|
+
transform: scale(1);
|
|
252
|
+
opacity: 1;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
79
256
|
.sidebar-search .arrow-wrapper {
|
|
80
257
|
cursor: pointer;
|
|
81
258
|
}
|
package/src/client/useActions.ts
CHANGED
|
@@ -150,6 +150,24 @@ export const useActions = (
|
|
|
150
150
|
});
|
|
151
151
|
}, [dispatch]);
|
|
152
152
|
|
|
153
|
+
// True to show the AI chat instead of files
|
|
154
|
+
const setIsAIChatOpen = useCallback(
|
|
155
|
+
(value: boolean) => {
|
|
156
|
+
dispatch({
|
|
157
|
+
type: 'set_is_ai_chat_open',
|
|
158
|
+
value: value,
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
[dispatch],
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Toggle the focused variable, which should focus the AI chat input
|
|
165
|
+
const toggleAIChatFocused = useCallback(() => {
|
|
166
|
+
dispatch({
|
|
167
|
+
type: 'toggle_ai_chat_focused',
|
|
168
|
+
});
|
|
169
|
+
}, [dispatch]);
|
|
170
|
+
|
|
153
171
|
// True to show the settings modal.
|
|
154
172
|
const setIsSettingsOpen = useCallback(
|
|
155
173
|
(value: boolean) => {
|
|
@@ -231,6 +249,8 @@ export const useActions = (
|
|
|
231
249
|
setSearchLineVisibility,
|
|
232
250
|
setSearchFocusedIndex,
|
|
233
251
|
toggleSearchFocused,
|
|
252
|
+
setIsAIChatOpen,
|
|
253
|
+
toggleAIChatFocused,
|
|
234
254
|
setIsSettingsOpen,
|
|
235
255
|
setIsDocOpen,
|
|
236
256
|
closeSettings,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { EditorView } from '@codemirror/view';
|
|
3
|
+
import { Diagnostic } from '@codemirror/lint';
|
|
4
|
+
// @ts-ignore - Worker import
|
|
5
|
+
import ESLintWorker from './worker?worker';
|
|
6
|
+
import { enableESLint } from '../../server/featureFlags';
|
|
7
|
+
import { fileNameStateField } from '../CodeEditor/getOrCreateEditor';
|
|
8
|
+
|
|
9
|
+
let requestCounter = 0;
|
|
10
|
+
const pendingRequests = new Map();
|
|
11
|
+
|
|
12
|
+
export const useESLint = () => {
|
|
13
|
+
const [worker, setWorker] = useState(null);
|
|
14
|
+
|
|
15
|
+
// Initialize the work in an effect so it does not run during SSR.
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
setWorker(new ESLintWorker());
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
// Wait for the worker to be initialized
|
|
22
|
+
if (!worker) return;
|
|
23
|
+
|
|
24
|
+
const handleMessage = (event: MessageEvent) => {
|
|
25
|
+
const { diagnostics, requestId, error } = event.data;
|
|
26
|
+
|
|
27
|
+
if (error) {
|
|
28
|
+
console.error('[ESLint] Error:', error);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const resolve = pendingRequests.get(requestId);
|
|
32
|
+
if (resolve) {
|
|
33
|
+
resolve(diagnostics);
|
|
34
|
+
pendingRequests.delete(requestId);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
worker.addEventListener('message', handleMessage);
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
worker.removeEventListener('message', handleMessage);
|
|
42
|
+
worker.terminate();
|
|
43
|
+
};
|
|
44
|
+
}, [worker]);
|
|
45
|
+
|
|
46
|
+
const esLintSource = useCallback(
|
|
47
|
+
async (
|
|
48
|
+
view: EditorView,
|
|
49
|
+
): Promise<readonly Diagnostic[]> => {
|
|
50
|
+
if (!enableESLint) return [];
|
|
51
|
+
|
|
52
|
+
// Retrieve the file name from the editor's state
|
|
53
|
+
const fileName = view.state.field(fileNameStateField);
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const code = view.state.doc.toString();
|
|
57
|
+
const requestId = requestCounter++;
|
|
58
|
+
|
|
59
|
+
return new Promise<Diagnostic[]>((resolve) => {
|
|
60
|
+
pendingRequests.set(requestId, resolve);
|
|
61
|
+
worker.postMessage({ code, requestId, fileName });
|
|
62
|
+
});
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error('[ESLint] Error in linter:', e);
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
[worker],
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return { esLintSource };
|
|
72
|
+
};
|