vzcode 1.44.0 → 1.45.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/index.html CHANGED
@@ -20,8 +20,8 @@
20
20
  href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
21
21
  rel="stylesheet"
22
22
  />
23
- <script type="module" crossorigin src="/assets/index-C46Hp5ym.js"></script>
24
- <link rel="stylesheet" crossorigin href="/assets/index-Beqaj63x.css">
23
+ <script type="module" crossorigin src="/assets/index-C1lTrzOZ.js"></script>
24
+ <link rel="stylesheet" crossorigin href="/assets/index-DeeOcIzw.css">
25
25
  </head>
26
26
  <body>
27
27
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "1.44.0",
3
+ "version": "1.45.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -109,7 +109,7 @@
109
109
  "@codemirror/lang-html": "^6.4.9",
110
110
  "@codemirror/lang-javascript": "^6.2.4",
111
111
  "@codemirror/lang-json": "^6.0.2",
112
- "@codemirror/lang-markdown": "^6.3.3",
112
+ "@codemirror/lang-markdown": "^6.3.4",
113
113
  "@codemirror/lint": "^6.8.5",
114
114
  "@codemirror/state": "^6.5.2",
115
115
  "@codemirror/theme-one-dark": "^6.1.3",
@@ -134,7 +134,7 @@
134
134
  "@uiw/codemirror-theme-xcode": "^4.24.2",
135
135
  "@uiw/codemirror-themes": "^4.24.2",
136
136
  "@valtown/codemirror-ts": "^2.3.1",
137
- "@vizhub/runtime": "^4.2.0",
137
+ "@vizhub/runtime": "^4.3.0",
138
138
  "@vizhub/viz-types": "^0.3.0",
139
139
  "@vizhub/viz-utils": "^1.3.0",
140
140
  "body-parser": "^2.2.0",
@@ -144,7 +144,9 @@
144
144
  "color-hash": "^2.0.2",
145
145
  "comlink": "^4.4.2",
146
146
  "d3-array": "^3.2.4",
147
+ "diff": "^8.0.2",
147
148
  "diff-match-patch": "^1.0.5",
149
+ "diff2html": "^3.4.52",
148
150
  "dotenv": "^17.2.1",
149
151
  "editcodewithai": "^2.3.0",
150
152
  "eslint-linter-browserify": "^9.32.0",
@@ -154,7 +156,7 @@
154
156
  "jszip": "^3.10.1",
155
157
  "livekit-server-sdk": "^2.13.1",
156
158
  "llm-code-format": "^3.0.0",
157
- "npm": "^11.5.1",
159
+ "npm": "^11.5.2",
158
160
  "open": "^10.2.0",
159
161
  "prettier-plugin-svelte": "^3.4.0",
160
162
  "react": "^18",
@@ -182,7 +184,7 @@
182
184
  "prettier": "^3.6.2",
183
185
  "sass": "^1.89.2",
184
186
  "ts-node": "^10.9.2",
185
- "typescript": "^5.8.3",
187
+ "typescript": "^5.9.2",
186
188
  "vite": "^7.0.6",
187
189
  "vitest": "^3.2.4"
188
190
  },
@@ -1,3 +1,4 @@
1
+ import { createRoot } from 'react-dom/client';
1
2
  import { EditorView } from 'codemirror';
2
3
  import {
3
4
  Compartment,
@@ -43,7 +44,17 @@ import {
43
44
  EditorCacheValue,
44
45
  } from '../useEditorCache';
45
46
  import { ThemeLabel, themeOptionsByLabel } from '../themes';
46
- import { keymap } from '@codemirror/view';
47
+ import {
48
+ keymap,
49
+ Decoration,
50
+ WidgetType,
51
+ ViewUpdate,
52
+ ViewPlugin,
53
+ DecorationSet,
54
+ } from '@codemirror/view';
55
+ import { Range } from '@codemirror/state';
56
+ import { syntaxTree } from '@codemirror/language';
57
+ import { RegExpCursor } from '@codemirror/search';
47
58
  import { basicSetup } from './basicSetup';
48
59
  import { InteractRule } from '@replit/codemirror-interact';
49
60
  import rainbowBrackets from './rainbowBrackets';
@@ -60,6 +71,9 @@ import {
60
71
  tsSyncWorker,
61
72
  } from '@valtown/codemirror-ts';
62
73
  import { getFileExtension } from '../utils/fileExtension';
74
+ import { SparklesSVG } from '../Icons/SparklesSVG';
75
+ import { VZCodeContext } from '../VZCodeContext';
76
+ import { useContext, useMemo } from 'react';
63
77
 
64
78
  const DEBUG = false;
65
79
 
@@ -178,6 +192,8 @@ export const getOrCreateEditor = async ({
178
192
  aiCopilotEndpoint,
179
193
  esLintSource,
180
194
  rainbowBracketsEnabled = true,
195
+ setIsAIChatOpen,
196
+ setAIChatMessage,
181
197
  }: {
182
198
  // TODO pass this in from the outside
183
199
  paneId?: PaneId;
@@ -213,6 +229,8 @@ export const getOrCreateEditor = async ({
213
229
  view: EditorView,
214
230
  ) => Promise<readonly Diagnostic[]>;
215
231
  rainbowBracketsEnabled?: boolean; // New parameter type
232
+ setIsAIChatOpen: any; // TODO fix types
233
+ setAIChatMessage: any; // TODO fix types
216
234
  }): Promise<ExtendedEditorCacheValue> => {
217
235
  // Cache hit
218
236
 
@@ -454,6 +472,116 @@ export const getOrCreateEditor = async ({
454
472
  extensions.push(copilot({ aiCopilotEndpoint }));
455
473
  }
456
474
 
475
+ // const { setIsAIChatOpen } = useContext(VZCodeContext);
476
+
477
+ // Widget appears after instances of "todo" in code editor, allowing AI to implement todo tasks when clicked
478
+ function createToDoPlugin() {
479
+ // setIsAIChatOpen: (open: boolean) => void,
480
+ class ToDoWidget extends WidgetType {
481
+ constructor() {
482
+ super();
483
+ }
484
+
485
+ eq(other: ToDoWidget) {
486
+ return false;
487
+ }
488
+
489
+ toDOM() {
490
+ const wrap = document.createElement('i');
491
+ wrap.style.display = 'inline-flex'; // prevent block expansion
492
+ wrap.style.alignItems = 'center';
493
+ wrap.style.justifyContent = 'center';
494
+ wrap.className = 'icon-button icon-button-dark';
495
+ const reactContainer =
496
+ document.createElement('div');
497
+ wrap.appendChild(reactContainer);
498
+
499
+ const root = createRoot(reactContainer);
500
+ root.render(
501
+ <div
502
+ onClick={() => {
503
+ console.log(
504
+ 'TODO set the chat prompt to "Implement the TODO"',
505
+ );
506
+ setIsAIChatOpen(true);
507
+ setAIChatMessage('Implement the TODO');
508
+ // submitAIChatMessage()
509
+ }}
510
+ >
511
+ <SparklesSVG width={14} height={14} />
512
+ </div>,
513
+ );
514
+ return wrap;
515
+ }
516
+
517
+ ignoreEvent() {
518
+ return false;
519
+ }
520
+ }
521
+
522
+ function toDoWidgets(editor: EditorView) {
523
+ const { state } = editor;
524
+ const tree = syntaxTree(state);
525
+ const findToDo = new RegExpCursor(state.doc, 'todo', {
526
+ ignoreCase: true,
527
+ });
528
+ const widgets: Range<Decoration>[] = [];
529
+
530
+ while (!findToDo.next().done) {
531
+ const { from, to } = findToDo.value;
532
+ const node = tree.resolve(from);
533
+ let inComment = false;
534
+
535
+ // Walk up the parents until we hit the root, looking for a comment node
536
+ for (let cur: any = node; cur; cur = cur.parent) {
537
+ if (
538
+ cur.type.is('Comment') || // grammars that group comments
539
+ cur.type.is('comment') || // generic lowercase group
540
+ /Comment$/.test(cur.type.name) // fallback for e.g. LineComment
541
+ ) {
542
+ inComment = true;
543
+ break;
544
+ }
545
+ }
546
+ if (!inComment) continue; // skip non-comment TODOs
547
+
548
+ const deco = Decoration.widget({
549
+ widget: new ToDoWidget(),
550
+ side: 1,
551
+ }).range(to);
552
+ widgets.push(deco);
553
+ }
554
+ return Decoration.set(widgets);
555
+ }
556
+
557
+ const todoPlugin = ViewPlugin.fromClass(
558
+ class {
559
+ decorations: DecorationSet;
560
+
561
+ constructor(view: EditorView) {
562
+ this.decorations = toDoWidgets(view);
563
+ }
564
+
565
+ update(update: ViewUpdate) {
566
+ if (
567
+ update.docChanged ||
568
+ update.viewportChanged ||
569
+ syntaxTree(update.startState) !==
570
+ syntaxTree(update.state)
571
+ )
572
+ this.decorations = toDoWidgets(update.view);
573
+ }
574
+ },
575
+ {
576
+ decorations: (v) => v.decorations,
577
+ },
578
+ );
579
+
580
+ return todoPlugin;
581
+ }
582
+
583
+ extensions.push(createToDoPlugin());
584
+
457
585
  const editor = new EditorView({
458
586
  state: EditorState.create({
459
587
  doc: text,
@@ -44,6 +44,8 @@ export const CodeEditor = ({
44
44
  codeEditorRef,
45
45
  enableAutoFollow,
46
46
  openTab,
47
+ setIsAIChatOpen,
48
+ setAIChatMessage,
47
49
  } = useContext(VZCodeContext);
48
50
 
49
51
  // Set `doc.data.isInteracting` to `true` when the user is interacting
@@ -109,6 +111,8 @@ export const CodeEditor = ({
109
111
  enableAutoFollowRef,
110
112
  aiCopilotEndpoint,
111
113
  esLintSource,
114
+ setIsAIChatOpen,
115
+ setAIChatMessage,
112
116
  });
113
117
 
114
118
  if (isMounted) {
@@ -1,8 +1,14 @@
1
- export const SparklesSVG = () => (
1
+ export const SparklesSVG = ({
2
+ width = '32',
3
+ height = '32',
4
+ }: {
5
+ width?: string | number;
6
+ height?: string | number;
7
+ }) => (
2
8
  <svg
3
9
  xmlns="http://www.w3.org/2000/svg"
4
- width="32"
5
- height="32"
10
+ width={width}
11
+ height={height}
6
12
  viewBox="0 0 24 24"
7
13
  fill="currentColor"
8
14
  stroke="currentColor"
@@ -188,6 +188,8 @@ export type VZCodeContextValue = {
188
188
  setLiveKitConnection: (state: boolean) => void;
189
189
  voiceChatModalOpen: boolean;
190
190
  setVoiceChatModalOpen: (state: boolean) => void;
191
+ aiChatMessage: string;
192
+ setAIChatMessage: (message: string) => void;
191
193
  };
192
194
 
193
195
  export const VZCodeProvider = ({
@@ -442,6 +444,8 @@ export const VZCodeProvider = ({
442
444
  const [voiceChatModalOpen, setVoiceChatModalOpen] =
443
445
  useState(false);
444
446
 
447
+ const [aiChatMessage, setAIChatMessage] = useState('');
448
+
445
449
  // The value provided by this context.
446
450
  const value: VZCodeContextValue = {
447
451
  content,
@@ -546,6 +550,9 @@ export const VZCodeProvider = ({
546
550
  setLiveKitConnection,
547
551
  voiceChatModalOpen,
548
552
  setVoiceChatModalOpen,
553
+
554
+ aiChatMessage,
555
+ setAIChatMessage,
549
556
  };
550
557
 
551
558
  return (
@@ -58,7 +58,7 @@ export const VZRight = () => {
58
58
 
59
59
  runtimeRef.current?.run({
60
60
  files,
61
- enableHotReloading: true,
61
+ enableHotReloading: !isFirstRunRef.current,
62
62
  enableSourcemap: true,
63
63
  vizId: 'example-viz',
64
64
  });
@@ -7,16 +7,16 @@ import {
7
7
  import { Form, Button } from '../../bootstrap';
8
8
 
9
9
  interface ChatInputProps {
10
- message: string;
11
- setMessage: (message: string) => void;
10
+ aiChatMessage: string;
11
+ setAIChatMessage: (message: string) => void;
12
12
  onSendMessage: () => void;
13
13
  isLoading: boolean;
14
14
  focused: boolean;
15
15
  }
16
16
 
17
17
  const ChatInputComponent = ({
18
- message,
19
- setMessage,
18
+ aiChatMessage,
19
+ setAIChatMessage,
20
20
  onSendMessage,
21
21
  isLoading,
22
22
  focused,
@@ -42,9 +42,9 @@ const ChatInputComponent = ({
42
42
 
43
43
  const handleChange = useCallback(
44
44
  (event: React.ChangeEvent<HTMLTextAreaElement>) => {
45
- setMessage(event.target.value);
45
+ setAIChatMessage(event.target.value);
46
46
  },
47
- [setMessage],
47
+ [setAIChatMessage],
48
48
  );
49
49
 
50
50
  return (
@@ -53,7 +53,7 @@ const ChatInputComponent = ({
53
53
  <Form.Control
54
54
  as="textarea"
55
55
  rows={2}
56
- value={message}
56
+ value={aiChatMessage}
57
57
  onChange={handleChange}
58
58
  onKeyDown={handleKeyDown}
59
59
  ref={inputRef}
@@ -64,7 +64,7 @@ const ChatInputComponent = ({
64
64
  <Button
65
65
  variant="primary"
66
66
  onClick={onSendMessage}
67
- disabled={!message.trim() || isLoading}
67
+ disabled={!aiChatMessage.trim() || isLoading}
68
68
  className="ai-chat-send-button"
69
69
  >
70
70
  Send
@@ -0,0 +1,125 @@
1
+ .diff-view {
2
+ margin: 12px 0;
3
+
4
+ .diff-summary {
5
+ margin-bottom: 8px;
6
+
7
+ .diff-stats {
8
+ display: flex;
9
+ align-items: center;
10
+ gap: 8px;
11
+ font-size: 12px;
12
+ color: var(--bs-secondary-color);
13
+
14
+ .files-changed {
15
+ color: white;
16
+ font-weight: 600;
17
+ }
18
+
19
+ .additions {
20
+ color: #28a745;
21
+ font-weight: 600;
22
+ }
23
+
24
+ .deletions {
25
+ color: #dc3545;
26
+ font-weight: 600;
27
+ }
28
+ }
29
+ }
30
+
31
+ .diff-files {
32
+ display: flex;
33
+ flex-direction: column;
34
+ gap: 4px;
35
+
36
+ // Fix diff2html styling issues for dark theme and layout
37
+ .d2h-wrapper {
38
+ // Override diff2html variables for dark theme
39
+ --d2h-bg-color: rgb(41, 44, 52);
40
+ --d2h-color: #e6edf3;
41
+ --d2h-border-color: #30363d;
42
+ --d2h-dim-color: #6e7681;
43
+ --d2h-info-bg-color: #161b22;
44
+ --d2h-file-header-bg-color: #161b22;
45
+ --d2h-file-header-border-color: #30363d;
46
+ --d2h-line-border-color: #21262d;
47
+ --d2h-ins-bg-color: rgba(46, 160, 67, 0.15);
48
+ --d2h-ins-border-color: rgba(46, 160, 67, 0.4);
49
+ --d2h-ins-highlight-bg-color: rgba(46, 160, 67, 0.4);
50
+ --d2h-ins-label-color: #3fb950;
51
+ --d2h-del-bg-color: rgba(248, 81, 73, 0.1);
52
+ --d2h-del-border-color: rgba(248, 81, 73, 0.4);
53
+ --d2h-del-highlight-bg-color: rgba(248, 81, 73, 0.4);
54
+ --d2h-del-label-color: #f85149;
55
+
56
+ background-color: var(--d2h-bg-color);
57
+ color: var(--d2h-color);
58
+ border-radius: 6px;
59
+ overflow: hidden;
60
+ }
61
+
62
+ // Fix layout issues with line numbers
63
+ .d2h-diff-table {
64
+ position: relative;
65
+ background-color: var(--d2h-bg-color);
66
+ }
67
+
68
+ .d2h-code-linenumber {
69
+ position: relative !important;
70
+ float: none !important;
71
+ display: table-cell !important;
72
+ width: auto !important;
73
+ background-color: var(--d2h-bg-color) !important;
74
+ border-color: var(--d2h-line-border-color) !important;
75
+ color: var(--d2h-dim-color) !important;
76
+ }
77
+
78
+ .d2h-code-side-linenumber {
79
+ position: relative !important;
80
+ float: none !important;
81
+ display: table-cell !important;
82
+ width: auto !important;
83
+ background-color: var(--d2h-bg-color) !important;
84
+ border-color: var(--d2h-line-border-color) !important;
85
+ color: var(--d2h-dim-color) !important;
86
+ }
87
+
88
+ // Ensure proper table layout
89
+ .d2h-diff-tbody tr {
90
+ display: table-row;
91
+ }
92
+
93
+ .d2h-diff-tbody td {
94
+ display: table-cell;
95
+ vertical-align: top;
96
+ }
97
+
98
+ // Fix file header styling
99
+ .d2h-file-header {
100
+ background-color: var(
101
+ --d2h-file-header-bg-color
102
+ ) !important;
103
+ border-bottom: 1px solid
104
+ var(--d2h-file-header-border-color) !important;
105
+ color: var(--d2h-color);
106
+ }
107
+
108
+ // Ensure proper line styling
109
+ .d2h-del {
110
+ background-color: var(--d2h-del-bg-color) !important;
111
+ border-color: var(--d2h-del-border-color) !important;
112
+ }
113
+
114
+ .d2h-ins {
115
+ background-color: var(--d2h-ins-bg-color) !important;
116
+ border-color: var(--d2h-ins-border-color) !important;
117
+ }
118
+
119
+ // Fix wrapper border
120
+ .d2h-file-wrapper {
121
+ border: 1px solid var(--d2h-border-color) !important;
122
+ background-color: var(--d2h-bg-color);
123
+ }
124
+ }
125
+ }
@@ -0,0 +1,72 @@
1
+ import React from 'react';
2
+ import {
3
+ UnifiedFilesDiff,
4
+ parseUnifiedDiffStats,
5
+ combineUnifiedDiffs,
6
+ } from '../../../utils/fileDiff';
7
+ import * as Diff2Html from 'diff2html';
8
+ import 'diff2html/bundles/css/diff2html.min.css';
9
+ import './DiffView.scss';
10
+
11
+ interface DiffViewProps {
12
+ diffData: UnifiedFilesDiff;
13
+ }
14
+
15
+ export const DiffView: React.FC<DiffViewProps> = ({
16
+ diffData,
17
+ }) => {
18
+ const unifiedDiffs = Object.values(diffData).filter(
19
+ (diff) => diff.length > 0,
20
+ );
21
+
22
+ if (unifiedDiffs.length === 0) {
23
+ return null;
24
+ }
25
+
26
+ // Calculate statistics from all unified diffs
27
+ let totalAdditions = 0;
28
+ let totalDeletions = 0;
29
+
30
+ for (const unifiedDiff of unifiedDiffs) {
31
+ const stats = parseUnifiedDiffStats(unifiedDiff);
32
+ totalAdditions += stats.additions;
33
+ totalDeletions += stats.deletions;
34
+ }
35
+
36
+ // Combine all unified diffs and convert to HTML using diff2html
37
+ const combinedUnifiedDiff = combineUnifiedDiffs(diffData);
38
+ const diffHtml = Diff2Html.html(combinedUnifiedDiff, {
39
+ drawFileList: false,
40
+ matching: 'words',
41
+ diffStyle: 'word',
42
+ outputFormat: 'line-by-line',
43
+ });
44
+
45
+ return (
46
+ <div className="diff-view">
47
+ <div className="diff-summary">
48
+ <div className="diff-stats">
49
+ <span className="files-changed">
50
+ {unifiedDiffs.length} file
51
+ {unifiedDiffs.length !== 1 ? 's' : ''} changed
52
+ </span>
53
+ {totalAdditions > 0 && (
54
+ <span className="additions">
55
+ +{totalAdditions}
56
+ </span>
57
+ )}
58
+ {totalDeletions > 0 && (
59
+ <span className="deletions">
60
+ -{totalDeletions}
61
+ </span>
62
+ )}
63
+ </div>
64
+ </div>
65
+
66
+ <div
67
+ className="diff-files"
68
+ dangerouslySetInnerHTML={{ __html: diffHtml }}
69
+ />
70
+ </div>
71
+ );
72
+ };
@@ -2,6 +2,9 @@ import { timestampToDate } from '@vizhub/viz-utils';
2
2
  import Markdown from 'react-markdown';
3
3
  import remarkGfm from 'remark-gfm';
4
4
  import { useMemo, memo } from 'react';
5
+ import { DiffView } from './DiffView';
6
+ import { UnifiedFilesDiff } from '../../../utils/fileDiff';
7
+ import { enableDiffView } from '../../featureFlags';
5
8
 
6
9
  interface MessageProps {
7
10
  id: string;
@@ -9,6 +12,7 @@ interface MessageProps {
9
12
  content: string;
10
13
  timestamp: number;
11
14
  isStreaming?: boolean;
15
+ diffData?: UnifiedFilesDiff;
12
16
  }
13
17
 
14
18
  const MessageComponent = ({
@@ -16,6 +20,7 @@ const MessageComponent = ({
16
20
  content,
17
21
  timestamp,
18
22
  isStreaming,
23
+ diffData,
19
24
  }: MessageProps) => {
20
25
  // Memoize date formatting to avoid repeated computation
21
26
  const formattedTime = useMemo(() => {
@@ -32,12 +37,18 @@ const MessageComponent = ({
32
37
  const messageClassName = useMemo(() => {
33
38
  return `ai-chat-message ${role}${isStreaming ? ' streaming' : ''}`;
34
39
  }, [role, isStreaming]);
40
+
35
41
  return (
36
42
  <div className={messageClassName}>
37
43
  <div className="ai-chat-message-content">
38
44
  <Markdown remarkPlugins={[remarkGfm]}>
39
45
  {content}
40
46
  </Markdown>
47
+ {enableDiffView &&
48
+ diffData &&
49
+ Object.keys(diffData).length > 0 && (
50
+ <DiffView diffData={diffData} />
51
+ )}
41
52
  </div>
42
53
  <div className="ai-chat-message-time">
43
54
  {formattedTime}
@@ -48,6 +48,7 @@ const MessageListComponent = ({
48
48
  role={msg.role}
49
49
  content={msg.content}
50
50
  timestamp={msg.timestamp}
51
+ diffData={(msg as any).diffData}
51
52
  />
52
53
  ))}
53
54
 
@@ -13,7 +13,9 @@ import './styles.scss';
13
13
  const defaultAIChatEndpoint = '/ai-chat-message';
14
14
 
15
15
  export const AIChat = () => {
16
- const [message, setMessage] = useState('');
16
+ const { aiChatMessage, setAIChatMessage } =
17
+ useContext(VZCodeContext);
18
+
17
19
  const [isLoading, setIsLoading] = useState(false);
18
20
  const [currentChatId] = useState(() => uuidv4());
19
21
 
@@ -40,9 +42,9 @@ export const AIChat = () => {
40
42
  );
41
43
 
42
44
  const handleSendMessage = useCallback(async () => {
43
- if (!message.trim() || isLoading) return;
45
+ if (!aiChatMessage.trim() || isLoading) return;
44
46
 
45
- setMessage('');
47
+ setAIChatMessage('');
46
48
  setIsLoading(true);
47
49
 
48
50
  // Call backend endpoint for AI response
@@ -55,7 +57,7 @@ export const AIChat = () => {
55
57
  },
56
58
  body: JSON.stringify({
57
59
  ...aiChatOptions,
58
- content: message.trim(),
60
+ content: aiChatMessage.trim(),
59
61
  chatId: currentChatId,
60
62
  }),
61
63
  });
@@ -75,7 +77,7 @@ export const AIChat = () => {
75
77
  setIsLoading(false);
76
78
  }
77
79
  }, [
78
- message,
80
+ aiChatMessage,
79
81
  isLoading,
80
82
  aiChatEndpoint,
81
83
  aiChatOptions,
@@ -90,8 +92,8 @@ export const AIChat = () => {
90
92
  isLoading={isLoading}
91
93
  />
92
94
  <ChatInput
93
- message={message}
94
- setMessage={setMessage}
95
+ aiChatMessage={aiChatMessage}
96
+ setAIChatMessage={setAIChatMessage}
95
97
  onSendMessage={handleSendMessage}
96
98
  isLoading={isLoading}
97
99
  focused={aiChatFocused}
@@ -81,8 +81,16 @@ export const createAICopyPasteHandlers = (
81
81
  return;
82
82
  }
83
83
 
84
+ // Preprocess to remove formatting instructions section to avoid creating extra files
85
+ const preprocessed = normalized
86
+ .split('## Formatting Instructions')[0]
87
+ .trim();
88
+
84
89
  // Parse the markdown files format
85
- const parsed = parseMarkdownFiles(normalized, 'bold');
90
+ const parsed = parseMarkdownFiles(
91
+ preprocessed,
92
+ 'bold',
93
+ );
86
94
 
87
95
  if (
88
96
  parsed.files &&
@@ -2,3 +2,5 @@ export const enableLiveKit =
2
2
  import.meta.env.VITE_ENABLE_LIVEKIT === 'true';
3
3
 
4
4
  export const enableAIChat = true;
5
+
6
+ export const enableDiffView = true;