vzcode 1.45.0 → 1.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{index-C1lTrzOZ.js → index-BMYXp1Uw.js} +114 -114
- package/dist/assets/{index-DeeOcIzw.css → index-BXyJ2hMf.css} +1 -1
- package/dist/index.html +2 -2
- package/package.json +7 -3
- package/src/client/CodeEditor/index.tsx +7 -4
- package/src/client/RunCodeWidget/index.tsx +18 -15
- package/src/client/SplitPaneResizeContext.tsx +26 -108
- package/src/client/VZCodeContext.tsx +8 -1
- package/src/client/VZRight.tsx +32 -4
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +72 -2
- package/src/client/VZSidebar/AIChat/DiffView.scss +29 -0
- package/src/client/VZSidebar/AIChat/DiffView.tsx +62 -1
- package/src/client/VZSidebar/AIChat/Message.tsx +14 -1
- package/src/client/VZSidebar/AIChat/MessageList.tsx +134 -13
- package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +37 -0
- package/src/client/VZSidebar/AIChat/index.tsx +187 -16
- package/src/client/VZSidebar/AIChat/styles.scss +278 -1
- package/src/client/VZSidebar/aiCopyPaste.ts +6 -1
- package/src/client/VZSidebar/index.tsx +10 -2
- package/src/client/bootstrap.ts +11 -1
- package/src/client/useActions.ts +12 -0
- package/src/client/usePrettier/index.ts +1 -3
- package/src/client/vzReducer/aiChatReducer.ts +13 -0
- package/src/client/vzReducer/createInitialState.ts +1 -0
- package/src/client/vzReducer/index.ts +9 -0
- package/src/client/vzReducer/searchReducer.test.ts +44 -2
- package/src/runCode.ts +5 -17
- package/src/server/aiChatHandler/aiEditing.ts +37 -0
- package/src/server/aiChatHandler/chatOperations.ts +38 -1
- package/src/server/aiChatHandler/index.ts +20 -9
- package/src/server/aiChatHandler/llmStreaming.ts +101 -19
- package/src/server/generateAIResponse.ts +4 -0
- package/src/server/index.ts +52 -0
|
@@ -2,7 +2,84 @@
|
|
|
2
2
|
height: 100%;
|
|
3
3
|
display: flex;
|
|
4
4
|
flex-direction: column;
|
|
5
|
-
padding:
|
|
5
|
+
padding: 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.ai-chat-empty {
|
|
9
|
+
flex: 1;
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
align-items: center;
|
|
13
|
+
text-align: center;
|
|
14
|
+
padding: 20px;
|
|
15
|
+
overflow-y: auto;
|
|
16
|
+
|
|
17
|
+
.ai-chat-empty-icon {
|
|
18
|
+
font-size: 42px;
|
|
19
|
+
margin-bottom: 15px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.ai-chat-empty-title {
|
|
23
|
+
font-size: 18px;
|
|
24
|
+
font-weight: 600;
|
|
25
|
+
color: var(--vh-color-neutral-04);
|
|
26
|
+
margin-bottom: 15px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ai-chat-empty-text {
|
|
30
|
+
font-size: 14px;
|
|
31
|
+
color: var(--vh-color-neutral-04);
|
|
32
|
+
margin-bottom: 20px;
|
|
33
|
+
line-height: 1.5;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.ai-chat-empty-examples {
|
|
37
|
+
background: var(--vh-color-neutral-02);
|
|
38
|
+
border-radius: 8px;
|
|
39
|
+
padding: 15px;
|
|
40
|
+
margin-bottom: 20px;
|
|
41
|
+
text-align: left;
|
|
42
|
+
width: 100%;
|
|
43
|
+
|
|
44
|
+
h4 {
|
|
45
|
+
font-size: 15px;
|
|
46
|
+
font-weight: 600;
|
|
47
|
+
color: var(--vh-color-neutral-04);
|
|
48
|
+
margin-bottom: 10px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ai-chat-suggested-prompts {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
gap: 8px;
|
|
55
|
+
margin: 0;
|
|
56
|
+
|
|
57
|
+
.ai-chat-suggested-prompt {
|
|
58
|
+
background: var(--vh-color-neutral-03);
|
|
59
|
+
border: 1px solid var(--vh-color-neutral-04);
|
|
60
|
+
border-radius: 6px;
|
|
61
|
+
padding: 8px 12px;
|
|
62
|
+
font-size: 13px;
|
|
63
|
+
color: var(--vh-color-neutral-04);
|
|
64
|
+
text-align: left;
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
transition: all 0.2s ease;
|
|
67
|
+
line-height: 1.4;
|
|
68
|
+
|
|
69
|
+
&:hover {
|
|
70
|
+
background: var(--vh-color-neutral-04);
|
|
71
|
+
color: var(--vh-color-neutral-01);
|
|
72
|
+
border-color: var(--vh-color-primary-01);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&:active {
|
|
76
|
+
background: var(--vh-color-primary-01);
|
|
77
|
+
color: white;
|
|
78
|
+
border-color: var(--vh-color-primary-01);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
6
83
|
}
|
|
7
84
|
|
|
8
85
|
.ai-chat-messages {
|
|
@@ -10,6 +87,7 @@
|
|
|
10
87
|
overflow-y: auto;
|
|
11
88
|
margin-bottom: 10px;
|
|
12
89
|
padding-right: 5px;
|
|
90
|
+
scroll-behavior: smooth;
|
|
13
91
|
|
|
14
92
|
&::-webkit-scrollbar {
|
|
15
93
|
width: 6px;
|
|
@@ -194,13 +272,117 @@
|
|
|
194
272
|
}
|
|
195
273
|
}
|
|
196
274
|
|
|
275
|
+
.ai-chat-error {
|
|
276
|
+
margin: 10px 0;
|
|
277
|
+
padding: 0;
|
|
278
|
+
border-radius: 8px;
|
|
279
|
+
background: rgba(220, 53, 69, 0.1);
|
|
280
|
+
border: 1px solid rgba(220, 53, 69, 0.3);
|
|
281
|
+
|
|
282
|
+
.ai-chat-error-content {
|
|
283
|
+
display: flex;
|
|
284
|
+
align-items: center;
|
|
285
|
+
padding: 12px;
|
|
286
|
+
gap: 10px;
|
|
287
|
+
|
|
288
|
+
.ai-chat-error-icon {
|
|
289
|
+
font-size: 16px;
|
|
290
|
+
flex-shrink: 0;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.ai-chat-error-message {
|
|
294
|
+
flex: 1;
|
|
295
|
+
font-size: 14px;
|
|
296
|
+
color: #dc3545;
|
|
297
|
+
line-height: 1.4;
|
|
298
|
+
font-weight: 500;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.ai-chat-error-dismiss {
|
|
302
|
+
background: none;
|
|
303
|
+
border: none;
|
|
304
|
+
color: #dc3545;
|
|
305
|
+
font-size: 18px;
|
|
306
|
+
font-weight: bold;
|
|
307
|
+
cursor: pointer;
|
|
308
|
+
padding: 0;
|
|
309
|
+
width: 20px;
|
|
310
|
+
height: 20px;
|
|
311
|
+
display: flex;
|
|
312
|
+
align-items: center;
|
|
313
|
+
justify-content: center;
|
|
314
|
+
border-radius: 50%;
|
|
315
|
+
transition: background-color 0.2s ease;
|
|
316
|
+
flex-shrink: 0;
|
|
317
|
+
|
|
318
|
+
&:hover {
|
|
319
|
+
background: rgba(220, 53, 69, 0.1);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
&:focus {
|
|
323
|
+
outline: 2px solid rgba(220, 53, 69, 0.3);
|
|
324
|
+
outline-offset: 1px;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
197
330
|
.ai-chat-input-container {
|
|
198
331
|
border-top: 1px solid var(--vh-color-neutral-02);
|
|
199
332
|
padding-top: 10px;
|
|
200
333
|
}
|
|
201
334
|
|
|
335
|
+
.ai-chat-mode-toggle {
|
|
336
|
+
display: flex;
|
|
337
|
+
flex-direction: column;
|
|
338
|
+
gap: 4px;
|
|
339
|
+
|
|
340
|
+
.btn-group {
|
|
341
|
+
justify-content: center;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.btn-group .btn {
|
|
345
|
+
font-size: 12px;
|
|
346
|
+
font-weight: 500;
|
|
347
|
+
padding: 4px 12px;
|
|
348
|
+
border: 1px solid var(--vh-color-neutral-03);
|
|
349
|
+
background: var(--vh-color-neutral-02);
|
|
350
|
+
color: var(--vh-color-neutral-04);
|
|
351
|
+
transition: all 0.2s ease;
|
|
352
|
+
|
|
353
|
+
&:hover {
|
|
354
|
+
background: var(--vh-color-neutral-03);
|
|
355
|
+
border-color: var(--vh-color-neutral-04);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
&.btn-primary {
|
|
359
|
+
background: var(--vh-color-primary-01);
|
|
360
|
+
border-color: var(--vh-color-primary-01);
|
|
361
|
+
color: white;
|
|
362
|
+
|
|
363
|
+
&:hover {
|
|
364
|
+
background: var(--vh-color-primary-02);
|
|
365
|
+
border-color: var(--vh-color-primary-02);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
&:disabled {
|
|
370
|
+
opacity: 0.6;
|
|
371
|
+
cursor: not-allowed;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.ai-chat-mode-description {
|
|
376
|
+
font-size: 11px;
|
|
377
|
+
color: var(--vh-color-neutral-03);
|
|
378
|
+
text-align: center;
|
|
379
|
+
font-style: italic;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
202
383
|
.ai-chat-input-group {
|
|
203
384
|
display: flex;
|
|
385
|
+
flex-direction: column;
|
|
204
386
|
gap: 8px;
|
|
205
387
|
margin-bottom: 0;
|
|
206
388
|
|
|
@@ -233,6 +415,18 @@
|
|
|
233
415
|
}
|
|
234
416
|
}
|
|
235
417
|
|
|
418
|
+
.ai-chat-input-info {
|
|
419
|
+
display: flex;
|
|
420
|
+
justify-content: space-between;
|
|
421
|
+
font-size: 12px;
|
|
422
|
+
color: var(--vh-color-neutral-03);
|
|
423
|
+
margin-bottom: 6px;
|
|
424
|
+
|
|
425
|
+
.ai-chat-hint {
|
|
426
|
+
font-style: italic;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
236
430
|
.ai-chat-send-button {
|
|
237
431
|
background: var(--vh-color-primary-01);
|
|
238
432
|
border: none;
|
|
@@ -243,6 +437,8 @@
|
|
|
243
437
|
font-weight: 500;
|
|
244
438
|
cursor: pointer;
|
|
245
439
|
transition: background-color 0.2s ease;
|
|
440
|
+
align-self: flex-end;
|
|
441
|
+
min-width: 80px;
|
|
246
442
|
|
|
247
443
|
&:hover:not(:disabled) {
|
|
248
444
|
background: var(--vh-color-primary-02);
|
|
@@ -254,3 +450,84 @@
|
|
|
254
450
|
}
|
|
255
451
|
}
|
|
256
452
|
}
|
|
453
|
+
|
|
454
|
+
// Thinking scratchpad styles
|
|
455
|
+
.thinking-scratchpad {
|
|
456
|
+
background: var(--vh-color-neutral-02);
|
|
457
|
+
border: 1px solid var(--vh-color-neutral-03);
|
|
458
|
+
border-radius: 8px;
|
|
459
|
+
margin: 10px 0;
|
|
460
|
+
padding: 0;
|
|
461
|
+
overflow: hidden;
|
|
462
|
+
font-size: 13px;
|
|
463
|
+
|
|
464
|
+
.thinking-scratchpad-header {
|
|
465
|
+
background: var(--vh-color-neutral-03);
|
|
466
|
+
color: var(--vh-color-neutral-04);
|
|
467
|
+
padding: 8px 12px;
|
|
468
|
+
font-weight: 600;
|
|
469
|
+
display: flex;
|
|
470
|
+
align-items: center;
|
|
471
|
+
gap: 8px;
|
|
472
|
+
|
|
473
|
+
.thinking-scratchpad-icon {
|
|
474
|
+
font-size: 16px;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.thinking-scratchpad-title {
|
|
478
|
+
font-size: 13px;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.thinking-scratchpad-content {
|
|
483
|
+
padding: 12px;
|
|
484
|
+
max-height: 200px;
|
|
485
|
+
overflow-y: auto;
|
|
486
|
+
background: var(--vh-color-neutral-02);
|
|
487
|
+
|
|
488
|
+
// Style markdown content in thinking scratchpad
|
|
489
|
+
p {
|
|
490
|
+
margin: 0 0 8px 0;
|
|
491
|
+
line-height: 1.4;
|
|
492
|
+
color: var(--vh-color-neutral-04);
|
|
493
|
+
|
|
494
|
+
&:last-child {
|
|
495
|
+
margin-bottom: 0;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
code {
|
|
500
|
+
background: rgba(0, 0, 0, 0.3);
|
|
501
|
+
color: #e6e6e6;
|
|
502
|
+
padding: 1px 4px;
|
|
503
|
+
border-radius: 3px;
|
|
504
|
+
font-size: 12px;
|
|
505
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
ul,
|
|
509
|
+
ol {
|
|
510
|
+
margin: 0 0 8px 0;
|
|
511
|
+
padding-left: 20px;
|
|
512
|
+
|
|
513
|
+
li {
|
|
514
|
+
margin-bottom: 4px;
|
|
515
|
+
color: var(--vh-color-neutral-04);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
h1,
|
|
520
|
+
h2,
|
|
521
|
+
h3,
|
|
522
|
+
h4,
|
|
523
|
+
h5,
|
|
524
|
+
h6 {
|
|
525
|
+
margin: 8px 0 6px 0;
|
|
526
|
+
color: var(--vh-color-neutral-04);
|
|
527
|
+
|
|
528
|
+
&:first-child {
|
|
529
|
+
margin-top: 0;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
@@ -7,8 +7,12 @@ import {
|
|
|
7
7
|
mergeFileChanges,
|
|
8
8
|
} from 'editcodewithai';
|
|
9
9
|
import { VizFiles } from '@vizhub/viz-types';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
generateRunId,
|
|
12
|
+
vizFilesToFileCollection,
|
|
13
|
+
} from '@vizhub/viz-utils';
|
|
11
14
|
import JSZip from 'jszip';
|
|
15
|
+
import { generate } from '@langchain/core/dist/utils/fast-json-patch';
|
|
12
16
|
|
|
13
17
|
export const createAICopyPasteHandlers = (
|
|
14
18
|
files: VizFiles,
|
|
@@ -106,6 +110,7 @@ export const createAICopyPasteHandlers = (
|
|
|
106
110
|
submitOperation((document) => ({
|
|
107
111
|
...document,
|
|
108
112
|
files: mergedFiles,
|
|
113
|
+
runId: generateRunId(),
|
|
109
114
|
}));
|
|
110
115
|
|
|
111
116
|
const fileCount = Object.keys(parsed.files).length;
|
|
@@ -110,7 +110,7 @@ export const VZSidebar = ({
|
|
|
110
110
|
),
|
|
111
111
|
aiChatToolTipText = (
|
|
112
112
|
<div>
|
|
113
|
-
<strong>AI
|
|
113
|
+
<strong>VizBot - AI Code Editor</strong>
|
|
114
114
|
</div>
|
|
115
115
|
),
|
|
116
116
|
}: {
|
|
@@ -184,7 +184,7 @@ export const VZSidebar = ({
|
|
|
184
184
|
[files, submitOperation],
|
|
185
185
|
);
|
|
186
186
|
|
|
187
|
-
const { sidebarWidth } = useContext(
|
|
187
|
+
const { sidebarWidth, setSidebarView } = useContext(
|
|
188
188
|
SplitPaneResizeContext,
|
|
189
189
|
);
|
|
190
190
|
|
|
@@ -282,6 +282,11 @@ export const VZSidebar = ({
|
|
|
282
282
|
previousPendingRef.current = pending;
|
|
283
283
|
}, [pending, connected, isConnecting]);
|
|
284
284
|
|
|
285
|
+
// Automatically adjust sidebar width when view changes
|
|
286
|
+
useEffect(() => {
|
|
287
|
+
setSidebarView(isAIChatOpen);
|
|
288
|
+
}, [isAIChatOpen, setSidebarView]);
|
|
289
|
+
|
|
285
290
|
return (
|
|
286
291
|
<div
|
|
287
292
|
className="vz-sidebar"
|
|
@@ -307,6 +312,7 @@ export const VZSidebar = ({
|
|
|
307
312
|
onClick={() => {
|
|
308
313
|
setIsSearchOpen(false);
|
|
309
314
|
setIsAIChatOpen(false);
|
|
315
|
+
setSidebarView(false); // Switch to files view
|
|
310
316
|
}}
|
|
311
317
|
>
|
|
312
318
|
<FolderSVG />
|
|
@@ -327,6 +333,7 @@ export const VZSidebar = ({
|
|
|
327
333
|
onClick={() => {
|
|
328
334
|
setIsSearchOpen(true);
|
|
329
335
|
setIsAIChatOpen(false);
|
|
336
|
+
setSidebarView(false); // Switch to files view (search uses same width as files)
|
|
330
337
|
}}
|
|
331
338
|
>
|
|
332
339
|
<SearchSVG />
|
|
@@ -348,6 +355,7 @@ export const VZSidebar = ({
|
|
|
348
355
|
onClick={() => {
|
|
349
356
|
setIsAIChatOpen(true);
|
|
350
357
|
setIsSearchOpen(false);
|
|
358
|
+
setSidebarView(true); // Switch to AI chat view
|
|
351
359
|
}}
|
|
352
360
|
>
|
|
353
361
|
<SparklesSVG />
|
package/src/client/bootstrap.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// Otherwise we get `Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import`
|
|
4
4
|
import Form from 'react-bootstrap/cjs/Form.js';
|
|
5
5
|
import Button from 'react-bootstrap/cjs/Button.js';
|
|
6
|
+
import ButtonGroup from 'react-bootstrap/cjs/ButtonGroup.js';
|
|
7
|
+
import ToggleButton from 'react-bootstrap/cjs/ToggleButton.js';
|
|
6
8
|
import Modal from 'react-bootstrap/cjs/Modal.js';
|
|
7
9
|
import OverlayTrigger from 'react-bootstrap/cjs/OverlayTrigger.js';
|
|
8
10
|
import Tooltip from 'react-bootstrap/cjs/Tooltip.js';
|
|
@@ -10,4 +12,12 @@ import Tooltip from 'react-bootstrap/cjs/Tooltip.js';
|
|
|
10
12
|
// Pull in custom CSS from vizhub-ui.
|
|
11
13
|
import 'vizhub-ui/dist/vizhub-ui.css';
|
|
12
14
|
|
|
13
|
-
export {
|
|
15
|
+
export {
|
|
16
|
+
Form,
|
|
17
|
+
Button,
|
|
18
|
+
ButtonGroup,
|
|
19
|
+
ToggleButton,
|
|
20
|
+
Modal,
|
|
21
|
+
OverlayTrigger,
|
|
22
|
+
Tooltip,
|
|
23
|
+
};
|
package/src/client/useActions.ts
CHANGED
|
@@ -168,6 +168,17 @@ export const useActions = (
|
|
|
168
168
|
});
|
|
169
169
|
}, [dispatch]);
|
|
170
170
|
|
|
171
|
+
// Set the AI chat mode (ask or edit)
|
|
172
|
+
const setAIChatMode = useCallback(
|
|
173
|
+
(mode: 'ask' | 'edit') => {
|
|
174
|
+
dispatch({
|
|
175
|
+
type: 'set_ai_chat_mode',
|
|
176
|
+
mode,
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
[dispatch],
|
|
180
|
+
);
|
|
181
|
+
|
|
171
182
|
// True to show the settings modal.
|
|
172
183
|
const setIsSettingsOpen = useCallback(
|
|
173
184
|
(value: boolean) => {
|
|
@@ -251,6 +262,7 @@ export const useActions = (
|
|
|
251
262
|
toggleSearchFocused,
|
|
252
263
|
setIsAIChatOpen,
|
|
253
264
|
toggleAIChatFocused,
|
|
265
|
+
setAIChatMode,
|
|
254
266
|
setIsSettingsOpen,
|
|
255
267
|
setIsDocOpen,
|
|
256
268
|
closeSettings,
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { shouldTriggerRun } from '../shouldTriggerRun';
|
|
2
|
-
import { JSONOp } from '../../ot';
|
|
3
1
|
import { VizFileId, VizContent } from '@vizhub/viz-types';
|
|
4
|
-
import { ShareDBDoc } from '../../types';
|
|
2
|
+
import { JSONOp, ShareDBDoc } from '../../types';
|
|
5
3
|
import { useEffect, useRef, useState } from 'react';
|
|
6
4
|
|
|
7
5
|
// The time in milliseconds by which auto-saving is debounced.
|
|
@@ -29,3 +29,16 @@ export const toggleAIChatFocusedReducer = (
|
|
|
29
29
|
}
|
|
30
30
|
return state;
|
|
31
31
|
};
|
|
32
|
+
|
|
33
|
+
export const setAIChatModeReducer = (
|
|
34
|
+
state: VZState,
|
|
35
|
+
action: VZAction,
|
|
36
|
+
): VZState => {
|
|
37
|
+
if (action.type === 'set_ai_chat_mode') {
|
|
38
|
+
return {
|
|
39
|
+
...state,
|
|
40
|
+
aiChatMode: action.mode,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return state;
|
|
44
|
+
};
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
import {
|
|
34
34
|
setIsAIChatOpenReducer,
|
|
35
35
|
toggleAIChatFocusedReducer,
|
|
36
|
+
setAIChatModeReducer,
|
|
36
37
|
} from './aiChatReducer';
|
|
37
38
|
import { toggleAutoFollowReducer } from './toggleAutoFollowReducer';
|
|
38
39
|
import { updatePresenceIndicatorReducer } from './updatePresenceIndicatorReducer';
|
|
@@ -62,6 +63,9 @@ export type VZState = {
|
|
|
62
63
|
// True if the AI chat input should focus on the next render.
|
|
63
64
|
aiChatFocused: boolean;
|
|
64
65
|
|
|
66
|
+
// The current AI chat mode: 'ask' or 'edit'
|
|
67
|
+
aiChatMode: 'ask' | 'edit';
|
|
68
|
+
|
|
65
69
|
// True to show the settings modal.
|
|
66
70
|
isSettingsOpen: boolean;
|
|
67
71
|
|
|
@@ -133,6 +137,10 @@ export type VZAction =
|
|
|
133
137
|
// * Toggles focused variable to trigger AI chat input focus
|
|
134
138
|
| { type: 'toggle_ai_chat_focused' }
|
|
135
139
|
|
|
140
|
+
// `set_ai_chat_mode`
|
|
141
|
+
// * Sets the AI chat mode (ask or edit)
|
|
142
|
+
| { type: 'set_ai_chat_mode'; mode: 'ask' | 'edit' }
|
|
143
|
+
|
|
136
144
|
// `set_search`
|
|
137
145
|
// * Sets the current search pattern
|
|
138
146
|
| { type: 'set_search'; value: string }
|
|
@@ -212,6 +220,7 @@ const reducers = [
|
|
|
212
220
|
setIsSearchOpenReducer,
|
|
213
221
|
setIsAIChatOpenReducer,
|
|
214
222
|
toggleAIChatFocusedReducer,
|
|
223
|
+
setAIChatModeReducer,
|
|
215
224
|
setIsSettingsOpenReducer,
|
|
216
225
|
setIsDocOpenReducer,
|
|
217
226
|
editorNoLongerWantsFocusReducer,
|
|
@@ -2,10 +2,52 @@ import { describe, expect, it } from 'vitest';
|
|
|
2
2
|
import { setSearchResultsReducer } from './searchReducer';
|
|
3
3
|
import { VZAction, VZState, createInitialState } from '.';
|
|
4
4
|
import { defaultTheme } from '../themes';
|
|
5
|
+
import { ShareDBDoc } from '../../types';
|
|
6
|
+
import { VizContent } from '@vizhub/viz-types';
|
|
5
7
|
|
|
6
8
|
// Mock ShareDBDoc for testing
|
|
7
|
-
const createMockShareDBDoc = (
|
|
8
|
-
|
|
9
|
+
const createMockShareDBDoc = (
|
|
10
|
+
files: any,
|
|
11
|
+
): ShareDBDoc<VizContent> => ({
|
|
12
|
+
data: { files, id: 'mock-id' },
|
|
13
|
+
ingestSnapshot: (snapshot: any, callback: any) => {
|
|
14
|
+
// No-op for testing
|
|
15
|
+
if (callback) callback();
|
|
16
|
+
},
|
|
17
|
+
subscribe: (callback: any) => {
|
|
18
|
+
// No-op for testing
|
|
19
|
+
if (callback) callback();
|
|
20
|
+
},
|
|
21
|
+
on: (
|
|
22
|
+
event: string,
|
|
23
|
+
callback: (op: any, source: boolean) => void,
|
|
24
|
+
) => {
|
|
25
|
+
// No-op for testing
|
|
26
|
+
},
|
|
27
|
+
off: (
|
|
28
|
+
event: string,
|
|
29
|
+
callback: (op: any, source: boolean) => void,
|
|
30
|
+
) => {
|
|
31
|
+
// No-op for testing
|
|
32
|
+
},
|
|
33
|
+
removeListener: (
|
|
34
|
+
event: string,
|
|
35
|
+
callback: (op: any, source: boolean) => void,
|
|
36
|
+
) => {
|
|
37
|
+
// No-op for testing
|
|
38
|
+
},
|
|
39
|
+
submitOp: (
|
|
40
|
+
op: any,
|
|
41
|
+
options?: any,
|
|
42
|
+
callback?: () => void,
|
|
43
|
+
) => {
|
|
44
|
+
// No-op for testing
|
|
45
|
+
if (callback) callback();
|
|
46
|
+
},
|
|
47
|
+
whenNothingPending: (callback: () => void) => {
|
|
48
|
+
// No-op for testing
|
|
49
|
+
if (callback) callback();
|
|
50
|
+
},
|
|
9
51
|
});
|
|
10
52
|
|
|
11
53
|
const initialState: VZState = createInitialState({
|
package/src/runCode.ts
CHANGED
|
@@ -1,30 +1,18 @@
|
|
|
1
|
+
import { generateRunId } from '@vizhub/viz-utils';
|
|
1
2
|
import { SubmitOperation } from './types';
|
|
2
3
|
import { VizContent } from '@vizhub/viz-types';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
* Creates a runCode function that triggers code execution by
|
|
6
|
+
* Creates a runCode function that triggers code execution by changing the `runId` in the content.
|
|
6
7
|
* This works for both client-side (with submitOperation) and server-side (with ShareDB document).
|
|
7
8
|
*/
|
|
8
|
-
export const createRunCodeFunction =
|
|
9
|
-
submitOperation: SubmitOperation
|
|
10
|
-
) => {
|
|
11
|
-
return () => {
|
|
12
|
-
// Use the unified submitOperation approach for both client and server
|
|
9
|
+
export const createRunCodeFunction =
|
|
10
|
+
(submitOperation: SubmitOperation) => () => {
|
|
13
11
|
submitOperation((content: VizContent) => ({
|
|
14
12
|
...content,
|
|
15
|
-
|
|
13
|
+
runId: generateRunId(),
|
|
16
14
|
}));
|
|
17
|
-
|
|
18
|
-
setTimeout(() => {
|
|
19
|
-
// This somewhat cryptic logic
|
|
20
|
-
// deletes the `isInteracting` property
|
|
21
|
-
// from the document.
|
|
22
|
-
submitOperation(
|
|
23
|
-
({ isInteracting, ...newDocument }) => newDocument,
|
|
24
|
-
);
|
|
25
|
-
}, 100);
|
|
26
15
|
};
|
|
27
|
-
};
|
|
28
16
|
|
|
29
17
|
/**
|
|
30
18
|
* Creates a runCodeRef object compatible with React refs.
|
|
@@ -12,6 +12,42 @@ import {
|
|
|
12
12
|
// Useful for debugging and testing purposes, e.g. checking the typing indicator.
|
|
13
13
|
const delayStart = false;
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Performs AI chat without editing - just generates a response
|
|
17
|
+
*/
|
|
18
|
+
export const performAIChat = async ({
|
|
19
|
+
prompt,
|
|
20
|
+
shareDBDoc,
|
|
21
|
+
llmFunction,
|
|
22
|
+
}) => {
|
|
23
|
+
const preparedFiles = prepareFilesForPrompt(
|
|
24
|
+
shareDBDoc.data.files,
|
|
25
|
+
);
|
|
26
|
+
const filesContext = formatMarkdownFiles(preparedFiles);
|
|
27
|
+
|
|
28
|
+
// 2. Assemble the final prompt for Q&A mode
|
|
29
|
+
const fullPrompt = assembleFullPrompt({
|
|
30
|
+
filesContext,
|
|
31
|
+
prompt,
|
|
32
|
+
editFormat: 'whole',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (delayStart) {
|
|
36
|
+
await new Promise((resolve) =>
|
|
37
|
+
setTimeout(resolve, 1000),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Call the LLM function which will handle streaming but won't edit files
|
|
42
|
+
const result = await llmFunction(fullPrompt);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
content: result.content,
|
|
46
|
+
generationId: result.generationId,
|
|
47
|
+
diffData: {}, // No file changes in ask mode
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
15
51
|
/**
|
|
16
52
|
* Performs AI editing operations using streaming with incremental OT operations
|
|
17
53
|
*/
|
|
@@ -62,5 +98,6 @@ export const performAIEditing = async ({
|
|
|
62
98
|
content: result.content,
|
|
63
99
|
generationId: result.generationId,
|
|
64
100
|
diffData,
|
|
101
|
+
beforeFiles, // Store the snapshot for undo functionality
|
|
65
102
|
};
|
|
66
103
|
};
|