vzcode 2.18.0 → 2.22.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 (57) hide show
  1. package/README.md +359 -0
  2. package/dist/assets/index-CfDs-dAu.js +476 -0
  3. package/dist/assets/{index-BvGPtSrr.css → index-fSroLVgi.css} +1 -1
  4. package/dist/assets/{worker-y5jhqCTR.js → worker-Cm3I3tnR.js} +57 -57
  5. package/dist/assets/{worker-ClF0pBYr.js → worker-KiuLM-X4.js} +71 -71
  6. package/dist/index.html +2 -2
  7. package/dist/llm-streaming-server/aiEditing.js +58 -0
  8. package/dist/llm-streaming-server/chatOperations.js +494 -0
  9. package/dist/llm-streaming-server/errorHandling.js +49 -0
  10. package/dist/llm-streaming-server/index.js +20 -0
  11. package/dist/llm-streaming-server/llmStreaming.js +265 -0
  12. package/dist/llm-streaming-server/validation.js +19 -0
  13. package/dist/server/aiChatHandler/chatOperations.js +3 -4
  14. package/dist/server/aiChatHandler/index.js +5 -5
  15. package/dist/server/aiChatHandler/llmStreaming.js +57 -4
  16. package/dist/server/prettier.js +18 -16
  17. package/dist/utils/fileDiff.js +25 -1
  18. package/package.json +45 -45
  19. package/src/client/CodeEditor/getOrCreateEditor.tsx +272 -258
  20. package/src/client/CodeEditor/index.tsx +3 -3
  21. package/src/client/VZRight.tsx +4 -0
  22. package/src/client/VZSidebar/AIChat/ChatInput.tsx +1 -1
  23. package/src/client/VZSidebar/AIChat/DiffView.scss +37 -1
  24. package/src/client/VZSidebar/AIChat/DiffView.tsx +55 -16
  25. package/src/client/VZSidebar/AIChat/styles.scss +38 -0
  26. package/src/client/VZSidebar/FileTypeIcon.tsx +1 -0
  27. package/src/client/VZSidebar/index.tsx +1 -1
  28. package/src/client/featureFlags.ts +7 -0
  29. package/src/llm-streaming-server/README.md +71 -0
  30. package/src/llm-streaming-server/aiEditing.ts +102 -0
  31. package/src/llm-streaming-server/chatOperations.ts +655 -0
  32. package/src/llm-streaming-server/errorHandling.ts +66 -0
  33. package/src/llm-streaming-server/index.ts +51 -0
  34. package/src/llm-streaming-server/llmStreaming.ts +403 -0
  35. package/src/llm-streaming-server/validation.ts +24 -0
  36. package/src/llm-streaming-ui/README.md +103 -0
  37. package/src/llm-streaming-ui/components/ChatInput.tsx +237 -0
  38. package/src/llm-streaming-ui/components/DiffView.scss +173 -0
  39. package/src/llm-streaming-ui/components/DiffView.tsx +236 -0
  40. package/src/llm-streaming-ui/components/FileEditingIndicator.tsx +89 -0
  41. package/src/llm-streaming-ui/components/IndividualFileDiff.tsx +86 -0
  42. package/src/llm-streaming-ui/components/JumpToLatestButton.tsx +64 -0
  43. package/src/llm-streaming-ui/components/Message.tsx +145 -0
  44. package/src/llm-streaming-ui/components/MessageList.tsx +241 -0
  45. package/src/llm-streaming-ui/components/ThinkingScratchpad.tsx +42 -0
  46. package/src/llm-streaming-ui/components/TypingIndicator.tsx +19 -0
  47. package/src/llm-streaming-ui/components/index.tsx +388 -0
  48. package/src/llm-streaming-ui/components/styles.scss +831 -0
  49. package/src/llm-streaming-ui/components/useSpeechRecognition.ts +116 -0
  50. package/src/llm-streaming-ui/index.ts +34 -0
  51. package/src/server/aiChatHandler/chatOperations.ts +3 -4
  52. package/src/server/aiChatHandler/index.ts +5 -5
  53. package/src/server/aiChatHandler/llmStreaming.ts +87 -4
  54. package/src/server/prettier.ts +30 -31
  55. package/src/types.ts +5 -0
  56. package/src/utils/fileDiff.ts +36 -2
  57. package/dist/assets/index-DuDXdJWC.js +0 -477
@@ -67,8 +67,10 @@ import {
67
67
  } from '@valtown/codemirror-ts';
68
68
  import { getFileExtension } from '../utils/fileExtension';
69
69
  import { SparklesSVG } from '../Icons/SparklesSVG';
70
+ import { MINIMAL_EXTENSIONS } from '../featureFlags';
70
71
 
71
72
  const DEBUG = false;
73
+ const enableToDoPlugin = true;
72
74
 
73
75
  // Define a StateField to store the file name.
74
76
  // This should be defined at the module level if it's to be imported by other modules.
@@ -136,6 +138,7 @@ const languageExtensions = {
136
138
  html: () => html(htmlConfig),
137
139
  css,
138
140
  md: markdown,
141
+ prompt: markdown,
139
142
  svelte,
140
143
  };
141
144
 
@@ -186,7 +189,7 @@ export const getOrCreateEditor = async ({
186
189
  esLintSource,
187
190
  rainbowBracketsEnabled = true,
188
191
  setIsAIChatOpen,
189
- handleSendMessage,
192
+ setAIChatMessage,
190
193
  }: {
191
194
  // TODO pass this in from the outside
192
195
  paneId?: PaneId;
@@ -223,7 +226,7 @@ export const getOrCreateEditor = async ({
223
226
  ) => Promise<readonly Diagnostic[]>;
224
227
  rainbowBracketsEnabled?: boolean; // New parameter type
225
228
  setIsAIChatOpen: (isAIChatOpen: boolean) => void;
226
- handleSendMessage: any; // TODO fix types
229
+ setAIChatMessage: (message: string) => void;
227
230
  }): Promise<ExtendedEditorCacheValue> => {
228
231
  // Cache hit
229
232
 
@@ -253,8 +256,10 @@ export const getOrCreateEditor = async ({
253
256
  // Create a compartment for rainbow brackets so that it can be enabled/disabled dynamically.
254
257
  const rainbowBracketsCompartment = new Compartment();
255
258
 
259
+ // Create a compartment for language so that it can be changed dynamically.
260
+ const languageCompartment = new Compartment();
261
+
256
262
  // The CodeMirror extensions to use.
257
- // const extensions = [autocompletion(), html(htmlConfig)]
258
263
  const extensions = [];
259
264
 
260
265
  // Initialize the fileNameStateField with the actual file name
@@ -273,26 +278,29 @@ export const getOrCreateEditor = async ({
273
278
  }),
274
279
  );
275
280
 
276
- // Deals with broadcasting changes in cursor location and selection.
277
- if (localPresence) {
278
- extensions.push(
279
- json1PresenceBroadcast({
280
- path: textPath,
281
- localPresence,
282
- usernameRef,
283
- }),
284
- );
285
- }
281
+ // Only add presence extensions if MINIMAL_EXTENSIONS is false
282
+ if (!MINIMAL_EXTENSIONS) {
283
+ // Deals with broadcasting changes in cursor location and selection.
284
+ if (localPresence) {
285
+ extensions.push(
286
+ json1PresenceBroadcast({
287
+ path: textPath,
288
+ localPresence,
289
+ usernameRef,
290
+ }),
291
+ );
292
+ }
286
293
 
287
- // Deals with receiving the broadcast from other clients and displaying them.
288
- if (docPresence) {
289
- extensions.push(
290
- json1PresenceDisplay({
291
- path: textPath,
292
- docPresence,
293
- enableAutoFollowRef,
294
- }),
295
- );
294
+ // Deals with receiving the broadcast from other clients and displaying them.
295
+ if (docPresence) {
296
+ extensions.push(
297
+ json1PresenceDisplay({
298
+ path: textPath,
299
+ docPresence,
300
+ enableAutoFollowRef,
301
+ }),
302
+ );
303
+ }
296
304
  }
297
305
  } else {
298
306
  // If the ShareDB document is not provided,
@@ -300,44 +308,7 @@ export const getOrCreateEditor = async ({
300
308
  extensions.push(EditorView.editable.of(false));
301
309
  }
302
310
 
303
- extensions.push(colorsInTextPlugin);
304
-
305
- // This is the "basic setup" for CodeMirror,
306
- // which actually adds a ton on functionality.
307
- // TODO vet this functionality, and determine how much
308
- // we want to replace with
309
- // https://github.com/vizhub-core/vzcode/issues/134
310
- extensions.push(basicSetup);
311
-
312
- if (esLintSource) {
313
- extensions.push(lintGutter()); // Show lint icons in the gutter
314
- extensions.push(
315
- linter(esLintSource, {
316
- // You can configure linter options here, e.g., delay
317
- delay: 750,
318
- }),
319
- );
320
- }
321
-
322
- // This supports dynamic changing of the theme.
323
- extensions.push(
324
- themeCompartment.of(themeOptionsByLabel[theme].value),
325
- );
326
-
327
- // Adds compartment for rainbow brackets with initial toggle state.
328
- extensions.push(
329
- rainbowBracketsCompartment.of(
330
- rainbowBracketsEnabled ? rainbowBrackets() : [],
331
- ),
332
- );
333
-
334
- // TODO handle dynamic changing of the file extension.
335
- // TODO handle dynamic file extensions by making
336
- // the CodeMirror language extension dynamic
337
- // using a Compartment.
338
- const languageCompartment = new Compartment();
339
- // See https://github.com/vizhub-core/vzcode/issues/55
340
-
311
+ // Add language extension (needed for both minimal and full modes)
341
312
  const languageExtension =
342
313
  getLanguageExtension(fileExtension);
343
314
  if (languageExtension) {
@@ -345,232 +316,275 @@ export const getOrCreateEditor = async ({
345
316
  languageCompartment.of(languageExtension),
346
317
  );
347
318
  } else {
348
- // Not sure if this case even works.
349
- // TODO manually test this case by creating a file
350
- // that has no extension, opening it up,
351
- // and then adding an extension.
352
- // console.warn(
353
- // `No language extension for file extension: ${fileExtension}`,
354
- // );
355
- // We still need to push the compartment,
356
- // otherwise the compartment won't work when
357
- // a file extension _is_ added later on.
358
319
  extensions.push(languageCompartment.of([]));
359
320
  }
360
321
 
361
- // Enable line wrapping for Markdown files
362
- if (fileExtension === 'md') {
363
- extensions.push(EditorView.lineWrapping);
364
- }
322
+ // If MINIMAL_EXTENSIONS is true, only include the JSON1 OT extension and essential functionality
323
+ if (MINIMAL_EXTENSIONS) {
324
+ // Only the most basic extensions are added
325
+ // The JSON1 OT extension and language extension are already added above
326
+ } else {
327
+ // Full feature set - include all extensions
328
+ extensions.push(colorsInTextPlugin);
329
+
330
+ // This is the "basic setup" for CodeMirror,
331
+ // which actually adds a ton on functionality.
332
+ // TODO vet this functionality, and determine how much
333
+ // we want to replace with
334
+ // https://github.com/vizhub-core/vzcode/issues/134
335
+ extensions.push(basicSetup);
336
+
337
+ if (esLintSource) {
338
+ extensions.push(lintGutter()); // Show lint icons in the gutter
339
+ extensions.push(
340
+ linter(esLintSource, {
341
+ // You can configure linter options here, e.g., delay
342
+ delay: 750,
343
+ }),
344
+ );
345
+ }
365
346
 
366
- // Add interactive widgets.
367
- // Includes the Alt+drag functionality for numbers.
368
- // Calls `onInteract` when one of those widgets is interacted with.
369
- // This can be used to trigger a transition to throttled mode
370
- // for hot reloading.
371
- // TODO consider leveraging the new `dragEnd` event handler.
372
- // and removing the `onInteract` callback, replacing it with
373
- // `onInteractStart` and `onInteractEnd`.
374
- // That may be tricky for one-off interactions though, like
375
- // the boolean checkboxes. The color pickers are also tricky,
376
- // as they would also need to be able to handle `onInteractEnd`.
377
- // See https://github.com/replit/codemirror-interact/issues/14
378
- extensions.push(
379
- widgets({ onInteract, customInteractRules }),
380
- );
347
+ // This supports dynamic changing of the theme.
348
+ extensions.push(
349
+ themeCompartment.of(themeOptionsByLabel[theme].value),
350
+ );
381
351
 
382
- // TODO fix the bugginess in this one where
383
- // the highlight persists after the mouse leaves.
384
- // extensions.push(highlightWidgets);
385
-
386
- extensions.push(rotationIndicator);
387
-
388
- // extensions.push(
389
- // AIAssistCodeMirrorKeyMap({
390
- // shareDBDoc,
391
- // fileId,
392
- // tabList,
393
- // aiAssistEndpoint,
394
- // aiAssistOptions,
395
- // }),
396
- // );
397
-
398
- // Add the extension that provides indentation markers.
399
- extensions.push(
400
- indentationMarkers({
401
- // thickness: 2,
402
- colors: {
403
- light: '#4d586b',
404
- dark: '#4d586b',
405
- activeLight: '#8e949f',
406
- activeDark: '#8e949f',
407
- },
408
- }),
409
- );
352
+ // Adds compartment for rainbow brackets with initial toggle state.
353
+ extensions.push(
354
+ rainbowBracketsCompartment.of(
355
+ rainbowBracketsEnabled ? rainbowBrackets() : [],
356
+ ),
357
+ );
410
358
 
411
- if (name.endsWith('ts') || name.endsWith('tsx')) {
412
- // Initialize worker if needed
413
- const tsWorker = await initializeWorker();
359
+ // Enable line wrapping for Markdown files
360
+ if (
361
+ fileExtension === 'md' ||
362
+ fileExtension === 'prompt'
363
+ ) {
364
+ extensions.push(EditorView.lineWrapping);
365
+ }
414
366
 
367
+ // Add interactive widgets.
368
+ // Includes the Alt+drag functionality for numbers.
369
+ // Calls `onInteract` when one of those widgets is interacted with.
370
+ // This can be used to trigger a transition to throttled mode
371
+ // for hot reloading.
372
+ // TODO consider leveraging the new `dragEnd` event handler.
373
+ // and removing the `onInteract` callback, replacing it with
374
+ // `onInteractStart` and `onInteractEnd`.
375
+ // That may be tricky for one-off interactions though, like
376
+ // the boolean checkboxes. The color pickers are also tricky,
377
+ // as they would also need to be able to handle `onInteractEnd`.
378
+ // See https://github.com/replit/codemirror-interact/issues/14
415
379
  extensions.push(
416
- ...[
417
- tsFacetWorker.of({ worker: tsWorker, path: name }),
418
- tsSyncWorker(),
419
- tsLinterWorker(),
420
- autocompletion({
421
- override: [tsAutocompleteWorker()],
422
- }),
423
- tsHoverWorker(),
424
- ],
380
+ widgets({ onInteract, customInteractRules }),
425
381
  );
426
- }
427
382
 
428
- // Show the minimap
429
- // See https://github.com/replit/codemirror-minimap#usage
430
- // This extension has poor performance, so it's disabled for now.
431
- // extensions.push(
432
- // showMinimap.compute(['doc'], () => ({
433
- // create: () => ({
434
- // dom: document.createElement('div'),
435
- // }),
436
- // // Without this, performance is terrible.
437
- // displayText: 'blocks',
438
- // })),
439
- // );
440
-
441
- // VSCode keybindings
442
- // See https://github.com/replit/codemirror-vscode-keymap#usage
443
- // extensions.push(keymap.of(vscodeKeymap));
444
- extensions.push(
445
- keymap.of(
446
- vscodeKeymap.map((binding) => {
447
- // Here we override the Shift+Enter behavior specifically,
448
- // as that can be used to trigger a manual save/Prettier,
449
- // and the default behavior from the keymap interferes.
450
- if (binding.key === 'Enter') {
451
- delete binding.shift;
452
- }
453
- return binding;
454
- }),
455
- ),
456
- );
383
+ // TODO fix the bugginess in this one where
384
+ // the highlight persists after the mouse leaves.
385
+ // extensions.push(highlightWidgets);
457
386
 
458
- // Adds copilot completions
459
- DEBUG &&
460
- console.log(
461
- '[getOrCreateEditor] aiCopilotEndpoint: ',
462
- aiCopilotEndpoint,
387
+ extensions.push(rotationIndicator);
388
+
389
+ // extensions.push(
390
+ // AIAssistCodeMirrorKeyMap({
391
+ // shareDBDoc,
392
+ // fileId,
393
+ // tabList,
394
+ // aiAssistEndpoint,
395
+ // aiAssistOptions,
396
+ // }),
397
+ // );
398
+
399
+ // Add the extension that provides indentation markers.
400
+ extensions.push(
401
+ indentationMarkers({
402
+ // thickness: 2,
403
+ colors: {
404
+ light: '#4d586b',
405
+ dark: '#4d586b',
406
+ activeLight: '#8e949f',
407
+ activeDark: '#8e949f',
408
+ },
409
+ }),
463
410
  );
464
- if (aiCopilotEndpoint) {
465
- extensions.push(copilot({ aiCopilotEndpoint }));
466
- }
467
411
 
468
- // const { setIsAIChatOpen } = useContext(VZCodeContext);
412
+ if (name.endsWith('ts') || name.endsWith('tsx')) {
413
+ // Initialize worker if needed
414
+ const tsWorker = await initializeWorker();
469
415
 
470
- // Widget appears after instances of "todo" in code editor, allowing AI to implement todo tasks when clicked
471
- function createToDoPlugin() {
472
- // setIsAIChatOpen: (open: boolean) => void,
473
- class ToDoWidget extends WidgetType {
474
- constructor() {
475
- super();
476
- }
416
+ extensions.push(
417
+ ...[
418
+ tsFacetWorker.of({
419
+ worker: tsWorker,
420
+ path: name,
421
+ }),
422
+ tsSyncWorker(),
423
+ tsLinterWorker(),
424
+ autocompletion({
425
+ override: [tsAutocompleteWorker()],
426
+ }),
427
+ tsHoverWorker(),
428
+ ],
429
+ );
430
+ }
477
431
 
478
- eq(_other: ToDoWidget) {
479
- return false;
480
- }
432
+ // Show the minimap
433
+ // See https://github.com/replit/codemirror-minimap#usage
434
+ // This extension has poor performance, so it's disabled for now.
435
+ // extensions.push(
436
+ // showMinimap.compute(['doc'], () => ({
437
+ // create: () => ({
438
+ // dom: document.createElement('div'),
439
+ // }),
440
+ // // Without this, performance is terrible.
441
+ // displayText: 'blocks',
442
+ // })),
443
+ // );
481
444
 
482
- toDOM() {
483
- const wrap = document.createElement('i');
484
- wrap.style.display = 'inline-flex'; // prevent block expansion
485
- wrap.style.alignItems = 'center';
486
- wrap.style.justifyContent = 'center';
487
- wrap.className = 'icon-button icon-button-dark';
488
- const reactContainer =
489
- document.createElement('div');
490
- wrap.appendChild(reactContainer);
491
-
492
- const root = createRoot(reactContainer);
493
- root.render(
494
- <div
495
- onClick={() => {
496
- setIsAIChatOpen(true);
497
- // setAIChatMessage('Implement the TODO');
498
- handleSendMessage('Implement the TODO');
499
- }}
500
- >
501
- <SparklesSVG width={14} height={14} />
502
- </div>,
503
- );
504
- return wrap;
505
- }
445
+ // VSCode keybindings
446
+ // See https://github.com/replit/codemirror-vscode-keymap#usage
447
+ // extensions.push(keymap.of(vscodeKeymap));
448
+ extensions.push(
449
+ keymap.of(
450
+ vscodeKeymap.map((binding) => {
451
+ // Here we override the Shift+Enter behavior specifically,
452
+ // as that can be used to trigger a manual save/Prettier,
453
+ // and the default behavior from the keymap interferes.
454
+ if (binding.key === 'Enter') {
455
+ delete binding.shift;
456
+ }
457
+ return binding;
458
+ }),
459
+ ),
460
+ );
506
461
 
507
- ignoreEvent() {
508
- return false;
509
- }
462
+ // Adds copilot completions
463
+ DEBUG &&
464
+ console.log(
465
+ '[getOrCreateEditor] aiCopilotEndpoint: ',
466
+ aiCopilotEndpoint,
467
+ );
468
+ if (aiCopilotEndpoint) {
469
+ extensions.push(copilot({ aiCopilotEndpoint }));
510
470
  }
511
471
 
512
- function toDoWidgets(editor: EditorView) {
513
- const { state } = editor;
514
- const tree = syntaxTree(state);
515
- const findToDo = new RegExpCursor(state.doc, 'todo', {
516
- ignoreCase: true,
517
- });
518
- const widgets: Range<Decoration>[] = [];
519
-
520
- while (!findToDo.next().done) {
521
- const { from, to } = findToDo.value;
522
- const node = tree.resolve(from);
523
- let inComment = false;
524
-
525
- // Walk up the parents until we hit the root, looking for a comment node
526
- for (let cur: any = node; cur; cur = cur.parent) {
527
- if (
528
- cur.type.is('Comment') || // grammars that group comments
529
- cur.type.is('comment') || // generic lowercase group
530
- /Comment$/.test(cur.type.name) // fallback for e.g. LineComment
531
- ) {
532
- inComment = true;
533
- break;
534
- }
472
+ // const { setIsAIChatOpen } = useContext(VZCodeContext);
473
+
474
+ // Widget appears after instances of "todo" in code editor, allowing AI to implement todo tasks when clicked
475
+ function createToDoPlugin() {
476
+ // setIsAIChatOpen: (open: boolean) => void,
477
+ class ToDoWidget extends WidgetType {
478
+ constructor() {
479
+ super();
535
480
  }
536
- if (!inComment) continue; // skip non-comment TODOs
537
481
 
538
- const deco = Decoration.widget({
539
- widget: new ToDoWidget(),
540
- side: 1,
541
- }).range(to);
542
- widgets.push(deco);
543
- }
544
- return Decoration.set(widgets);
545
- }
482
+ eq(_other: ToDoWidget) {
483
+ return false;
484
+ }
546
485
 
547
- const todoPlugin = ViewPlugin.fromClass(
548
- class {
549
- decorations: DecorationSet;
486
+ toDOM() {
487
+ const wrap = document.createElement('i');
488
+ wrap.style.display = 'inline-flex'; // prevent block expansion
489
+ wrap.style.alignItems = 'center';
490
+ wrap.style.justifyContent = 'center';
491
+ wrap.className = 'icon-button icon-button-dark';
492
+ wrap.style.cursor = 'pointer';
493
+
494
+ // Handle click directly on the wrapper element
495
+ wrap.addEventListener('click', (e) => {
496
+ e.preventDefault();
497
+ e.stopPropagation();
498
+ setAIChatMessage('Implement the TODO');
499
+ setIsAIChatOpen(true);
500
+ });
501
+
502
+ const reactContainer =
503
+ document.createElement('div');
504
+ wrap.appendChild(reactContainer);
505
+
506
+ const root = createRoot(reactContainer);
507
+ root.render(
508
+ <SparklesSVG width={14} height={14} />,
509
+ );
510
+
511
+ return wrap;
512
+ }
550
513
 
551
- constructor(view: EditorView) {
552
- this.decorations = toDoWidgets(view);
514
+ ignoreEvent() {
515
+ return false;
553
516
  }
517
+ }
518
+
519
+ function toDoWidgets(editor: EditorView) {
520
+ const { state } = editor;
521
+ const tree = syntaxTree(state);
522
+ const findToDo = new RegExpCursor(
523
+ state.doc,
524
+ 'todo',
525
+ {
526
+ ignoreCase: true,
527
+ },
528
+ );
529
+ const widgets: Range<Decoration>[] = [];
530
+
531
+ while (!findToDo.next().done) {
532
+ const { from, to } = findToDo.value;
533
+ const node = tree.resolve(from);
534
+ let inComment = false;
535
+
536
+ // Walk up the parents until we hit the root, looking for a comment node
537
+ for (let cur: any = node; cur; cur = cur.parent) {
538
+ if (
539
+ cur.type.is('Comment') || // grammars that group comments
540
+ cur.type.is('comment') || // generic lowercase group
541
+ /Comment$/.test(cur.type.name) // fallback for e.g. LineComment
542
+ ) {
543
+ inComment = true;
544
+ break;
545
+ }
546
+ }
547
+ if (!inComment) continue; // skip non-comment TODOs
554
548
 
555
- update(update: ViewUpdate) {
556
- if (
557
- update.docChanged ||
558
- update.viewportChanged ||
559
- syntaxTree(update.startState) !==
560
- syntaxTree(update.state)
561
- )
562
- this.decorations = toDoWidgets(update.view);
549
+ const deco = Decoration.widget({
550
+ widget: new ToDoWidget(),
551
+ side: 1,
552
+ }).range(to);
553
+ widgets.push(deco);
563
554
  }
564
- },
565
- {
566
- decorations: (v) => v.decorations,
567
- },
568
- );
555
+ return Decoration.set(widgets);
556
+ }
569
557
 
570
- return todoPlugin;
571
- }
558
+ const todoPlugin = ViewPlugin.fromClass(
559
+ class {
560
+ decorations: DecorationSet;
561
+
562
+ constructor(view: EditorView) {
563
+ this.decorations = toDoWidgets(view);
564
+ }
572
565
 
573
- extensions.push(createToDoPlugin());
566
+ update(update: ViewUpdate) {
567
+ if (
568
+ update.docChanged ||
569
+ update.viewportChanged ||
570
+ syntaxTree(update.startState) !==
571
+ syntaxTree(update.state)
572
+ )
573
+ this.decorations = toDoWidgets(update.view);
574
+ }
575
+ },
576
+ {
577
+ decorations: (v) => v.decorations,
578
+ },
579
+ );
580
+
581
+ return todoPlugin;
582
+ }
583
+
584
+ if (enableToDoPlugin) {
585
+ extensions.push(createToDoPlugin());
586
+ }
587
+ }
574
588
 
575
589
  const editor = new EditorView({
576
590
  state: EditorState.create({
@@ -43,7 +43,7 @@ export const CodeEditor = ({
43
43
  codeEditorRef,
44
44
  enableAutoFollow,
45
45
  setIsAIChatOpen,
46
- handleSendMessage,
46
+ setAIChatMessage,
47
47
  } = useContext(VZCodeContext);
48
48
 
49
49
  // Set `doc.data.isInteracting` to `true` when the user is interacting
@@ -117,7 +117,7 @@ export const CodeEditor = ({
117
117
  aiCopilotEndpoint,
118
118
  esLintSource,
119
119
  setIsAIChatOpen,
120
- handleSendMessage,
120
+ setAIChatMessage,
121
121
  });
122
122
 
123
123
  if (isMounted) {
@@ -142,7 +142,7 @@ export const CodeEditor = ({
142
142
  aiCopilotEndpoint,
143
143
  esLintSource,
144
144
  setIsAIChatOpen,
145
- handleSendMessage,
145
+ setAIChatMessage,
146
146
  ]);
147
147
 
148
148
  // Every time the active file switches from one file to another,
@@ -92,6 +92,10 @@ export const VZRight = () => {
92
92
  enableHotReloading: isInteracting,
93
93
  enableSourcemap: true,
94
94
  vizId: 'example-viz',
95
+
96
+ // Don't clear the console here in VZCode, since
97
+ // we often want to see debug logs across multiple runs.
98
+ clearConsole: false,
95
99
  });
96
100
  isFirstRunRef.current = false;
97
101
  }
@@ -168,7 +168,7 @@ const ChatInputComponent = ({
168
168
  <Form.Group className="ai-chat-input-group">
169
169
  <Form.Control
170
170
  as="textarea"
171
- rows={5}
171
+ rows={10}
172
172
  value={aiChatMessage}
173
173
  onChange={handleChange}
174
174
  onKeyDown={handleKeyDown}