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/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-DkCXnCvn.js"></script>
24
- <link rel="stylesheet" crossorigin href="/assets/index-BXyJ2hMf.css">
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "1.51.0",
3
+ "version": "1.53.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -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 other.id === this.id;
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
- // after 2 seconds of inactivity, username is made less visible
278
- this.timeout = window.setTimeout(() => {
279
- // userDiv.style.backgroundColor = `rgba(${this.color}, 0.2)`;
280
- // userDiv.style.color = 'rgba(0,0,0,0.2)';
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
- }, 2000);
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
- <div
62
- className="ai-chat-mode-toggle"
63
- style={{ marginBottom: '8px' }}
64
- >
65
- <ButtonGroup size="sm">
66
- <ToggleButton
67
- id="ai-chat-mode-ask"
68
- type="radio"
69
- variant={
70
- aiChatMode === 'ask'
71
- ? 'primary'
72
- : 'outline-primary'
73
- }
74
- name="ai-chat-mode"
75
- value="ask"
76
- checked={aiChatMode === 'ask'}
77
- onChange={() => setAIChatMode('ask')}
78
- disabled={isLoading}
79
- >
80
- 💬 Ask
81
- </ToggleButton>
82
- <ToggleButton
83
- id="ai-chat-mode-edit"
84
- type="radio"
85
- variant={
86
- aiChatMode === 'edit'
87
- ? 'primary'
88
- : 'outline-primary'
89
- }
90
- name="ai-chat-mode"
91
- value="edit"
92
- checked={aiChatMode === 'edit'}
93
- onChange={() => setAIChatMode('edit')}
94
- disabled={isLoading}
95
- >
96
- ✏️ Edit
97
- </ToggleButton>
98
- </ButtonGroup>
99
- <div className="ai-chat-mode-description">
100
- {aiChatMode === 'ask'
101
- ? 'Ask questions without editing files'
102
- : 'Get answers and code edits'}
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
- </div>
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, { useState, useContext } from '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
- VizBot is thinking...
23
+ AI is thinking...
24
24
  </span>
25
25
  </div>
26
26
  <div className="thinking-scratchpad-content">