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/assets/{buildWorker-CRLuRnCr.js → buildWorker-C_tGhWXG.js} +135 -74
- package/dist/assets/index-C1lTrzOZ.js +457 -0
- package/dist/assets/{index-Beqaj63x.css → index-DeeOcIzw.css} +1 -1
- package/dist/assets/worker-CekYspLa.js +453 -0
- package/dist/index.html +2 -2
- package/package.json +7 -5
- package/src/client/CodeEditor/{getOrCreateEditor.ts → getOrCreateEditor.tsx} +129 -1
- package/src/client/CodeEditor/index.tsx +4 -0
- package/src/client/Icons/SparklesSVG.tsx +9 -3
- package/src/client/VZCodeContext.tsx +7 -0
- package/src/client/VZRight.tsx +1 -1
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +8 -8
- package/src/client/VZSidebar/AIChat/DiffView.scss +125 -0
- package/src/client/VZSidebar/AIChat/DiffView.tsx +72 -0
- package/src/client/VZSidebar/AIChat/Message.tsx +11 -0
- package/src/client/VZSidebar/AIChat/MessageList.tsx +1 -0
- package/src/client/VZSidebar/AIChat/index.tsx +9 -7
- package/src/client/VZSidebar/aiCopyPaste.ts +9 -1
- package/src/client/featureFlags.ts +2 -0
- package/src/server/aiChatHandler/aiEditing.ts +19 -0
- package/src/server/aiChatHandler/chatOperations.ts +40 -0
- package/src/server/aiChatHandler/index.ts +13 -0
- package/src/server/computeInitialDocument.ts +25 -6
- package/src/server/index.ts +31 -2
- package/src/utils/fileDiff.ts +114 -0
- package/dist/assets/index-C46Hp5ym.js +0 -340
- package/dist/assets/worker-DkYJN5WQ.js +0 -453
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-
|
|
24
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 {
|
|
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=
|
|
5
|
-
height=
|
|
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 (
|
package/src/client/VZRight.tsx
CHANGED
|
@@ -7,16 +7,16 @@ import {
|
|
|
7
7
|
import { Form, Button } from '../../bootstrap';
|
|
8
8
|
|
|
9
9
|
interface ChatInputProps {
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
45
|
+
setAIChatMessage(event.target.value);
|
|
46
46
|
},
|
|
47
|
-
[
|
|
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={
|
|
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={!
|
|
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}
|
|
@@ -13,7 +13,9 @@ import './styles.scss';
|
|
|
13
13
|
const defaultAIChatEndpoint = '/ai-chat-message';
|
|
14
14
|
|
|
15
15
|
export const AIChat = () => {
|
|
16
|
-
const
|
|
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 (!
|
|
45
|
+
if (!aiChatMessage.trim() || isLoading) return;
|
|
44
46
|
|
|
45
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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(
|
|
90
|
+
const parsed = parseMarkdownFiles(
|
|
91
|
+
preprocessed,
|
|
92
|
+
'bold',
|
|
93
|
+
);
|
|
86
94
|
|
|
87
95
|
if (
|
|
88
96
|
parsed.files &&
|