vzcode 1.51.0 → 1.53.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-DkCXnCvn.js → index-BxRNGHcB.js} +111 -111
- package/dist/assets/{index-BXyJ2hMf.css → index-L1AANcDx.css} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/client/CodeEditor/json1PresenceDisplay.ts +40 -7
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +46 -43
- package/src/client/VZSidebar/AIChat/DiffView.scss +0 -26
- package/src/client/VZSidebar/AIChat/DiffView.tsx +1 -67
- package/src/client/VZSidebar/AIChat/Message.tsx +60 -8
- package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +1 -1
- package/src/client/VZSidebar/AIChat/index.tsx +138 -97
- package/src/client/VZSidebar/AIChat/styles.scss +27 -0
- package/src/client/VZSidebar/index.tsx +1 -1
- package/src/client/featureFlags.ts +2 -0
- package/src/server/aiChatHandler/index.ts +3 -3
- package/src/server/aiChatHandler/llmStreaming.ts +15 -12
- package/src/server/generateAIResponse.ts +3 -0
- package/src/server/handleAICopilot.ts +3 -0
- package/src/server/index.ts +7 -7
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-BxRNGHcB.js"></script>
|
|
24
|
+
<link rel="stylesheet" crossorigin href="/assets/index-L1AANcDx.css">
|
|
25
25
|
</head>
|
|
26
26
|
<body>
|
|
27
27
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -41,6 +41,9 @@ export const json1PresenceDisplay = ({
|
|
|
41
41
|
//Added variable for cursor position
|
|
42
42
|
cursorPosition = {};
|
|
43
43
|
|
|
44
|
+
// Track previous cursor positions to detect which cursors actually moved
|
|
45
|
+
previousCursorPositions = {};
|
|
46
|
+
|
|
44
47
|
// Flag to prevent multiple pending updates
|
|
45
48
|
pendingUpdate = false;
|
|
46
49
|
|
|
@@ -63,7 +66,6 @@ export const json1PresenceDisplay = ({
|
|
|
63
66
|
// this.scrollToCursor(view);
|
|
64
67
|
// });
|
|
65
68
|
// Receive remote presence changes.
|
|
66
|
-
// Receive remote presence changes.
|
|
67
69
|
docPresence.on(
|
|
68
70
|
'receive',
|
|
69
71
|
(id: PresenceId, presence: Presence) => {
|
|
@@ -80,6 +82,7 @@ export const json1PresenceDisplay = ({
|
|
|
80
82
|
delete presenceState[id];
|
|
81
83
|
// Also remove their cursor position to prevent errors.
|
|
82
84
|
delete this.cursorPosition[id];
|
|
85
|
+
delete this.previousCursorPositions[id];
|
|
83
86
|
} else {
|
|
84
87
|
// Otherwise, the user is active. Check if the presence
|
|
85
88
|
// is for the current file.
|
|
@@ -95,6 +98,7 @@ export const json1PresenceDisplay = ({
|
|
|
95
98
|
// If it's for another file, remove them from this view's state.
|
|
96
99
|
delete presenceState[id];
|
|
97
100
|
delete this.cursorPosition[id];
|
|
101
|
+
delete this.previousCursorPositions[id];
|
|
98
102
|
}
|
|
99
103
|
}
|
|
100
104
|
|
|
@@ -110,6 +114,11 @@ export const json1PresenceDisplay = ({
|
|
|
110
114
|
);
|
|
111
115
|
const { username } = presence;
|
|
112
116
|
|
|
117
|
+
// Check if this cursor actually moved by comparing with previous position
|
|
118
|
+
const previousPosition =
|
|
119
|
+
this.previousCursorPositions[id];
|
|
120
|
+
const cursorMoved = previousPosition !== from;
|
|
121
|
+
|
|
113
122
|
presenceDecorations.push({
|
|
114
123
|
from,
|
|
115
124
|
to: from,
|
|
@@ -120,6 +129,7 @@ export const json1PresenceDisplay = ({
|
|
|
120
129
|
'' + Math.random(),
|
|
121
130
|
userColor,
|
|
122
131
|
username,
|
|
132
|
+
cursorMoved, // Pass whether this cursor moved
|
|
123
133
|
),
|
|
124
134
|
}),
|
|
125
135
|
});
|
|
@@ -141,9 +151,12 @@ export const json1PresenceDisplay = ({
|
|
|
141
151
|
}
|
|
142
152
|
if (view.state.doc.length >= from) {
|
|
143
153
|
this.cursorPosition[id] = from;
|
|
154
|
+
// Update previous position for next comparison
|
|
155
|
+
this.previousCursorPositions[id] = from;
|
|
144
156
|
} else {
|
|
145
157
|
// The cursor position is invalid, so remove it.
|
|
146
158
|
delete this.cursorPosition[id];
|
|
159
|
+
delete this.previousCursorPositions[id];
|
|
147
160
|
}
|
|
148
161
|
}
|
|
149
162
|
|
|
@@ -229,19 +242,25 @@ class PresenceWidget extends WidgetType {
|
|
|
229
242
|
color: string;
|
|
230
243
|
username: Username;
|
|
231
244
|
timeout: number;
|
|
245
|
+
cursorMoved: boolean;
|
|
232
246
|
constructor(
|
|
233
247
|
id: string,
|
|
234
248
|
color: string,
|
|
235
249
|
username: Username,
|
|
250
|
+
cursorMoved: boolean = true, // Default to true for backward compatibility
|
|
236
251
|
) {
|
|
237
252
|
super();
|
|
238
253
|
this.id = id;
|
|
239
254
|
this.color = color;
|
|
240
255
|
this.username = username;
|
|
256
|
+
this.cursorMoved = cursorMoved;
|
|
241
257
|
}
|
|
242
258
|
|
|
243
259
|
eq(other: PresenceWidget) {
|
|
244
|
-
return
|
|
260
|
+
return (
|
|
261
|
+
other.id === this.id &&
|
|
262
|
+
other.cursorMoved === this.cursorMoved
|
|
263
|
+
);
|
|
245
264
|
// return false;
|
|
246
265
|
}
|
|
247
266
|
|
|
@@ -274,12 +293,26 @@ class PresenceWidget extends WidgetType {
|
|
|
274
293
|
);
|
|
275
294
|
span.appendChild(userDiv);
|
|
276
295
|
|
|
277
|
-
//
|
|
278
|
-
this.
|
|
279
|
-
//
|
|
280
|
-
|
|
296
|
+
// Only reset opacity and start timeout for cursors that actually moved
|
|
297
|
+
if (this.cursorMoved) {
|
|
298
|
+
// Start with full opacity when cursor moves
|
|
299
|
+
userDiv.style.opacity = '1';
|
|
300
|
+
|
|
301
|
+
// Clear any existing timeout to prevent interference
|
|
302
|
+
if (this.timeout) {
|
|
303
|
+
window.clearTimeout(this.timeout);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// after 2 seconds of inactivity, username is made less visible
|
|
307
|
+
this.timeout = window.setTimeout(() => {
|
|
308
|
+
// userDiv.style.backgroundColor = `rgba(${this.color}, 0.2)`;
|
|
309
|
+
// userDiv.style.color = 'rgba(0,0,0,0.2)';
|
|
310
|
+
userDiv.style.opacity = '0.3';
|
|
311
|
+
}, 2000);
|
|
312
|
+
} else {
|
|
313
|
+
// For cursors that didn't move, keep their reduced opacity
|
|
281
314
|
userDiv.style.opacity = '0.3';
|
|
282
|
-
}
|
|
315
|
+
}
|
|
283
316
|
|
|
284
317
|
return span;
|
|
285
318
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
ButtonGroup,
|
|
11
11
|
ToggleButton,
|
|
12
12
|
} from '../../bootstrap';
|
|
13
|
+
import { enableAskMode } from '../../featureFlags';
|
|
13
14
|
|
|
14
15
|
interface ChatInputProps {
|
|
15
16
|
aiChatMessage: string;
|
|
@@ -58,50 +59,52 @@ const ChatInputComponent = ({
|
|
|
58
59
|
|
|
59
60
|
return (
|
|
60
61
|
<div className="ai-chat-input-container">
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
62
|
+
{enableAskMode && (
|
|
63
|
+
<div
|
|
64
|
+
className="ai-chat-mode-toggle"
|
|
65
|
+
style={{ marginBottom: '8px' }}
|
|
66
|
+
>
|
|
67
|
+
<ButtonGroup size="sm">
|
|
68
|
+
<ToggleButton
|
|
69
|
+
id="ai-chat-mode-ask"
|
|
70
|
+
type="radio"
|
|
71
|
+
variant={
|
|
72
|
+
aiChatMode === 'ask'
|
|
73
|
+
? 'primary'
|
|
74
|
+
: 'outline-primary'
|
|
75
|
+
}
|
|
76
|
+
name="ai-chat-mode"
|
|
77
|
+
value="ask"
|
|
78
|
+
checked={aiChatMode === 'ask'}
|
|
79
|
+
onChange={() => setAIChatMode('ask')}
|
|
80
|
+
disabled={isLoading}
|
|
81
|
+
>
|
|
82
|
+
💬 Ask
|
|
83
|
+
</ToggleButton>
|
|
84
|
+
<ToggleButton
|
|
85
|
+
id="ai-chat-mode-edit"
|
|
86
|
+
type="radio"
|
|
87
|
+
variant={
|
|
88
|
+
aiChatMode === 'edit'
|
|
89
|
+
? 'primary'
|
|
90
|
+
: 'outline-primary'
|
|
91
|
+
}
|
|
92
|
+
name="ai-chat-mode"
|
|
93
|
+
value="edit"
|
|
94
|
+
checked={aiChatMode === 'edit'}
|
|
95
|
+
onChange={() => setAIChatMode('edit')}
|
|
96
|
+
disabled={isLoading}
|
|
97
|
+
>
|
|
98
|
+
✏️ Edit
|
|
99
|
+
</ToggleButton>
|
|
100
|
+
</ButtonGroup>
|
|
101
|
+
<div className="ai-chat-mode-description">
|
|
102
|
+
{aiChatMode === 'ask'
|
|
103
|
+
? 'Ask questions without editing files'
|
|
104
|
+
: 'Get answers and code edits'}
|
|
105
|
+
</div>
|
|
103
106
|
</div>
|
|
104
|
-
|
|
107
|
+
)}
|
|
105
108
|
<Form.Group className="ai-chat-input-group">
|
|
106
109
|
<Form.Control
|
|
107
110
|
as="textarea"
|
|
@@ -31,32 +31,6 @@
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
.undo-button-container {
|
|
35
|
-
display: flex;
|
|
36
|
-
justify-content: flex-end;
|
|
37
|
-
margin-top: 16px;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.undo-button {
|
|
41
|
-
background-color: #dc3545;
|
|
42
|
-
color: white;
|
|
43
|
-
border: none;
|
|
44
|
-
border-radius: 4px;
|
|
45
|
-
padding: 4px 8px;
|
|
46
|
-
font-size: 12px;
|
|
47
|
-
cursor: pointer;
|
|
48
|
-
transition: background-color 0.2s;
|
|
49
|
-
|
|
50
|
-
&:hover:not(:disabled) {
|
|
51
|
-
background-color: #c82333;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
&:disabled {
|
|
55
|
-
background-color: #6c757d;
|
|
56
|
-
cursor: not-allowed;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
34
|
.diff-files {
|
|
61
35
|
display: flex;
|
|
62
36
|
flex-direction: column;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import {
|
|
3
3
|
UnifiedFilesDiff,
|
|
4
4
|
parseUnifiedDiffStats,
|
|
@@ -6,28 +6,15 @@ import {
|
|
|
6
6
|
} from '../../../utils/fileDiff';
|
|
7
7
|
import * as Diff2Html from 'diff2html';
|
|
8
8
|
import 'diff2html/bundles/css/diff2html.min.css';
|
|
9
|
-
import { VZCodeContext } from '../../VZCodeContext';
|
|
10
9
|
import './DiffView.scss';
|
|
11
10
|
|
|
12
11
|
interface DiffViewProps {
|
|
13
12
|
diffData: UnifiedFilesDiff;
|
|
14
|
-
messageId?: string;
|
|
15
|
-
chatId?: string;
|
|
16
|
-
beforeFiles?: any;
|
|
17
|
-
canUndo?: boolean;
|
|
18
13
|
}
|
|
19
14
|
|
|
20
15
|
export const DiffView: React.FC<DiffViewProps> = ({
|
|
21
16
|
diffData,
|
|
22
|
-
messageId,
|
|
23
|
-
chatId,
|
|
24
|
-
beforeFiles,
|
|
25
|
-
canUndo = false,
|
|
26
17
|
}) => {
|
|
27
|
-
const [isUndoing, setIsUndoing] = useState(false);
|
|
28
|
-
const { aiChatUndoEndpoint, aiChatOptions = {} } =
|
|
29
|
-
useContext(VZCodeContext);
|
|
30
|
-
|
|
31
18
|
const unifiedDiffs = Object.values(diffData).filter(
|
|
32
19
|
(diff) => diff.length > 0,
|
|
33
20
|
);
|
|
@@ -36,47 +23,6 @@ export const DiffView: React.FC<DiffViewProps> = ({
|
|
|
36
23
|
return null;
|
|
37
24
|
}
|
|
38
25
|
|
|
39
|
-
const handleUndo = async () => {
|
|
40
|
-
if (
|
|
41
|
-
!messageId ||
|
|
42
|
-
!chatId ||
|
|
43
|
-
!beforeFiles ||
|
|
44
|
-
isUndoing ||
|
|
45
|
-
!aiChatUndoEndpoint
|
|
46
|
-
) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
setIsUndoing(true);
|
|
51
|
-
try {
|
|
52
|
-
const response = await fetch(aiChatUndoEndpoint, {
|
|
53
|
-
method: 'POST',
|
|
54
|
-
headers: {
|
|
55
|
-
'Content-Type': 'application/json',
|
|
56
|
-
},
|
|
57
|
-
body: JSON.stringify({
|
|
58
|
-
vizId: aiChatOptions.vizId,
|
|
59
|
-
chatId,
|
|
60
|
-
messageId,
|
|
61
|
-
}),
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
if (!response.ok) {
|
|
65
|
-
throw new Error(
|
|
66
|
-
`HTTP error! status: ${response.status}`,
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// The server will handle the ShareDB operations to undo the changes
|
|
71
|
-
// The UI will update automatically via ShareDB
|
|
72
|
-
} catch (error) {
|
|
73
|
-
console.error('Error undoing AI edit:', error);
|
|
74
|
-
// TODO: Show user-friendly error message
|
|
75
|
-
} finally {
|
|
76
|
-
setIsUndoing(false);
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
26
|
// Calculate statistics from all unified diffs
|
|
81
27
|
let totalAdditions = 0;
|
|
82
28
|
let totalDeletions = 0;
|
|
@@ -121,18 +67,6 @@ export const DiffView: React.FC<DiffViewProps> = ({
|
|
|
121
67
|
className="diff-files"
|
|
122
68
|
dangerouslySetInnerHTML={{ __html: diffHtml }}
|
|
123
69
|
/>
|
|
124
|
-
{canUndo && beforeFiles && (
|
|
125
|
-
<div className="undo-button-container">
|
|
126
|
-
<button
|
|
127
|
-
className="undo-button"
|
|
128
|
-
onClick={handleUndo}
|
|
129
|
-
disabled={isUndoing}
|
|
130
|
-
title="Undo this AI edit"
|
|
131
|
-
>
|
|
132
|
-
{isUndoing ? 'Undoing...' : 'Undo'}
|
|
133
|
-
</button>
|
|
134
|
-
</div>
|
|
135
|
-
)}
|
|
136
70
|
</div>
|
|
137
71
|
);
|
|
138
72
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { timestampToDate } from '@vizhub/viz-utils';
|
|
2
2
|
import Markdown from 'react-markdown';
|
|
3
3
|
import remarkGfm from 'remark-gfm';
|
|
4
|
-
import { useMemo, memo } from 'react';
|
|
4
|
+
import { useMemo, memo, useState, useContext } from 'react';
|
|
5
5
|
import { DiffView } from './DiffView';
|
|
6
6
|
import { UnifiedFilesDiff } from '../../../utils/fileDiff';
|
|
7
7
|
import { enableDiffView } from '../../featureFlags';
|
|
8
|
+
import { VZCodeContext } from '../../VZCodeContext';
|
|
8
9
|
|
|
9
10
|
interface MessageProps {
|
|
10
11
|
id: string;
|
|
@@ -29,6 +30,10 @@ const MessageComponent = ({
|
|
|
29
30
|
chatId,
|
|
30
31
|
canUndo,
|
|
31
32
|
}: MessageProps) => {
|
|
33
|
+
const [isUndoing, setIsUndoing] = useState(false);
|
|
34
|
+
const { aiChatUndoEndpoint, aiChatOptions = {} } =
|
|
35
|
+
useContext(VZCodeContext);
|
|
36
|
+
|
|
32
37
|
// Memoize date formatting to avoid repeated computation
|
|
33
38
|
const formattedTime = useMemo(() => {
|
|
34
39
|
return timestampToDate(timestamp).toLocaleTimeString(
|
|
@@ -45,6 +50,47 @@ const MessageComponent = ({
|
|
|
45
50
|
return `ai-chat-message ${role}${isStreaming ? ' streaming' : ''}`;
|
|
46
51
|
}, [role, isStreaming]);
|
|
47
52
|
|
|
53
|
+
const handleUndo = async () => {
|
|
54
|
+
if (
|
|
55
|
+
!id ||
|
|
56
|
+
!chatId ||
|
|
57
|
+
!beforeFiles ||
|
|
58
|
+
isUndoing ||
|
|
59
|
+
!aiChatUndoEndpoint
|
|
60
|
+
) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
setIsUndoing(true);
|
|
65
|
+
try {
|
|
66
|
+
const response = await fetch(aiChatUndoEndpoint, {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: {
|
|
69
|
+
'Content-Type': 'application/json',
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify({
|
|
72
|
+
vizId: aiChatOptions.vizId,
|
|
73
|
+
chatId,
|
|
74
|
+
messageId: id,
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`HTTP error! status: ${response.status}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The server will handle the ShareDB operations to undo the changes
|
|
85
|
+
// The UI will update automatically via ShareDB
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('Error undoing AI edit:', error);
|
|
88
|
+
// TODO: Show user-friendly error message
|
|
89
|
+
} finally {
|
|
90
|
+
setIsUndoing(false);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
48
94
|
return (
|
|
49
95
|
<div className={messageClassName}>
|
|
50
96
|
<div className="ai-chat-message-content">
|
|
@@ -54,14 +100,20 @@ const MessageComponent = ({
|
|
|
54
100
|
{enableDiffView &&
|
|
55
101
|
diffData &&
|
|
56
102
|
Object.keys(diffData).length > 0 && (
|
|
57
|
-
<DiffView
|
|
58
|
-
diffData={diffData}
|
|
59
|
-
messageId={id}
|
|
60
|
-
chatId={chatId}
|
|
61
|
-
beforeFiles={beforeFiles}
|
|
62
|
-
canUndo={canUndo}
|
|
63
|
-
/>
|
|
103
|
+
<DiffView diffData={diffData} />
|
|
64
104
|
)}
|
|
105
|
+
{canUndo && beforeFiles && (
|
|
106
|
+
<div className="undo-button-container">
|
|
107
|
+
<button
|
|
108
|
+
className="undo-button"
|
|
109
|
+
onClick={handleUndo}
|
|
110
|
+
disabled={isUndoing}
|
|
111
|
+
title="Undo this AI edit"
|
|
112
|
+
>
|
|
113
|
+
{isUndoing ? 'Undoing...' : 'Undo'}
|
|
114
|
+
</button>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
65
117
|
</div>
|
|
66
118
|
<div className="ai-chat-message-time">
|
|
67
119
|
{formattedTime}
|
|
@@ -20,7 +20,7 @@ const ThinkingScratchpadComponent = ({
|
|
|
20
20
|
<div className="thinking-scratchpad-header">
|
|
21
21
|
<span className="thinking-scratchpad-icon">🧠</span>
|
|
22
22
|
<span className="thinking-scratchpad-title">
|
|
23
|
-
|
|
23
|
+
AI is thinking...
|
|
24
24
|
</span>
|
|
25
25
|
</div>
|
|
26
26
|
<div className="thinking-scratchpad-content">
|