vzcode 1.33.0 → 1.34.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-DPIrco_g.js"></script>
24
- <link rel="stylesheet" crossorigin href="/assets/index-DnunmWjy.css">
23
+ <script type="module" crossorigin src="/assets/index-ajiBaTmx.js"></script>
24
+ <link rel="stylesheet" crossorigin href="/assets/index-lvLw6dCW.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.33.0",
3
+ "version": "1.34.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -134,7 +134,8 @@
134
134
  "@uiw/codemirror-theme-xcode": "^4.24.1",
135
135
  "@uiw/codemirror-themes": "^4.24.1",
136
136
  "@valtown/codemirror-ts": "^2.3.1",
137
- "@vizhub/viz-types": "^0.2.0",
137
+ "@vizhub/viz-types": "^0.3.0",
138
+ "@vizhub/viz-utils": "^1.3.0",
138
139
  "body-parser": "^2.2.0",
139
140
  "codemirror": "^6.0.2",
140
141
  "codemirror-copilot": "^0.0.7",
@@ -144,6 +145,7 @@
144
145
  "d3-array": "^3.2.4",
145
146
  "diff-match-patch": "^1.0.5",
146
147
  "dotenv": "^17.2.0",
148
+ "editcodewithai": "^2.0.0",
147
149
  "eslint": "^9.31.0",
148
150
  "eslint-linter-browserify": "^9.31.0",
149
151
  "express": "^5.1.0",
@@ -151,6 +153,7 @@
151
153
  "ignore": "^7.0.5",
152
154
  "json0-ot-diff": "^1.1.2",
153
155
  "livekit-server-sdk": "^2.13.1",
156
+ "llm-code-format": "^2.0.1",
154
157
  "ngrok": "^5.0.0-beta.2",
155
158
  "npm": "^11.4.2",
156
159
  "open": "^10.2.0",
@@ -158,7 +161,9 @@
158
161
  "react": "^18",
159
162
  "react-bootstrap": "^2.10.10",
160
163
  "react-dom": "^18",
164
+ "react-markdown": "^10.1.0",
161
165
  "react-router-dom": "^7.7.0",
166
+ "remark-gfm": "^4.0.1",
162
167
  "sharedb": "^5.2.2",
163
168
  "sharedb-client-browser": "^5.2.2",
164
169
  "uuid": "^11.1.0",
@@ -0,0 +1,65 @@
1
+ import { useRef, useEffect, useCallback } from 'react';
2
+ import { Form, Button } from '../../bootstrap';
3
+
4
+ interface ChatInputProps {
5
+ message: string;
6
+ setMessage: (message: string) => void;
7
+ onSendMessage: () => void;
8
+ isLoading: boolean;
9
+ focused: boolean;
10
+ }
11
+
12
+ export const ChatInput = ({
13
+ message,
14
+ setMessage,
15
+ onSendMessage,
16
+ isLoading,
17
+ focused,
18
+ }: ChatInputProps) => {
19
+ const inputRef = useRef<HTMLTextAreaElement>(null);
20
+
21
+ useEffect(() => {
22
+ // Focus the input when the AI chat is focused
23
+ if (focused && inputRef.current) {
24
+ inputRef.current.focus();
25
+ }
26
+ }, [focused]);
27
+
28
+ const handleKeyDown = useCallback(
29
+ (event: React.KeyboardEvent) => {
30
+ if (event.key === 'Enter' && !event.shiftKey) {
31
+ event.preventDefault();
32
+ onSendMessage();
33
+ }
34
+ },
35
+ [onSendMessage],
36
+ );
37
+
38
+ return (
39
+ <div className="ai-chat-input-container">
40
+ <Form.Group className="ai-chat-input-group">
41
+ <Form.Control
42
+ as="textarea"
43
+ rows={2}
44
+ value={message}
45
+ onChange={(event) =>
46
+ setMessage(event.target.value)
47
+ }
48
+ onKeyDown={handleKeyDown}
49
+ ref={inputRef}
50
+ placeholder="Describe what you'd like me to change in your code..."
51
+ spellCheck="false"
52
+ disabled={isLoading}
53
+ />
54
+ <Button
55
+ variant="primary"
56
+ onClick={onSendMessage}
57
+ disabled={!message.trim() || isLoading}
58
+ className="ai-chat-send-button"
59
+ >
60
+ Send
61
+ </Button>
62
+ </Form.Group>
63
+ </div>
64
+ );
65
+ };
@@ -0,0 +1,36 @@
1
+ import { timestampToDate } from '@vizhub/viz-utils';
2
+ import Markdown from 'react-markdown';
3
+ import remarkGfm from 'remark-gfm';
4
+
5
+ interface MessageProps {
6
+ id: string;
7
+ role: 'user' | 'assistant';
8
+ content: string;
9
+ timestamp: number;
10
+ isStreaming?: boolean;
11
+ }
12
+
13
+ export const Message = ({
14
+ role,
15
+ content,
16
+ timestamp,
17
+ isStreaming,
18
+ }: MessageProps) => {
19
+ return (
20
+ <div
21
+ className={`ai-chat-message ${role}${isStreaming ? ' streaming' : ''}`}
22
+ >
23
+ <div className="ai-chat-message-content">
24
+ <Markdown remarkPlugins={[remarkGfm]}>
25
+ {content}
26
+ </Markdown>
27
+ </div>
28
+ <div className="ai-chat-message-time">
29
+ {timestampToDate(timestamp).toLocaleTimeString([], {
30
+ hour: '2-digit',
31
+ minute: '2-digit',
32
+ })}
33
+ </div>
34
+ </div>
35
+ );
36
+ };
@@ -0,0 +1,62 @@
1
+ import { useRef, useEffect } from 'react';
2
+ import { Message } from './Message';
3
+ import { StreamingMessage } from './StreamingMessage';
4
+ import { TypingIndicator } from './TypingIndicator';
5
+
6
+ interface MessageData {
7
+ id: string;
8
+ role: 'user' | 'assistant';
9
+ content: string;
10
+ timestamp: number;
11
+ }
12
+
13
+ interface MessageListProps {
14
+ messages: MessageData[];
15
+ aiScratchpad?: string;
16
+ aiStatus?: string;
17
+ isLoading: boolean;
18
+ }
19
+
20
+ export const MessageList = ({
21
+ messages,
22
+ aiScratchpad,
23
+ aiStatus,
24
+ isLoading,
25
+ }: MessageListProps) => {
26
+ const messagesEndRef = useRef<HTMLDivElement>(null);
27
+
28
+ const scrollToBottom = () => {
29
+ messagesEndRef.current?.scrollIntoView({
30
+ behavior: 'smooth',
31
+ });
32
+ };
33
+
34
+ useEffect(() => {
35
+ scrollToBottom();
36
+ }, [messages, aiScratchpad]);
37
+
38
+ return (
39
+ <div className="ai-chat-messages">
40
+ {messages.map((msg) => (
41
+ <Message
42
+ key={msg.id}
43
+ id={msg.id}
44
+ role={msg.role}
45
+ content={msg.content}
46
+ timestamp={msg.timestamp}
47
+ />
48
+ ))}
49
+
50
+ {/* Show streaming content if available */}
51
+ {aiScratchpad && (
52
+ <StreamingMessage
53
+ content={aiScratchpad}
54
+ status={aiStatus}
55
+ />
56
+ )}
57
+
58
+ {isLoading && !aiScratchpad && <TypingIndicator />}
59
+ <div ref={messagesEndRef} />
60
+ </div>
61
+ );
62
+ };
@@ -0,0 +1,25 @@
1
+ import Markdown from 'react-markdown';
2
+ import remarkGfm from 'remark-gfm';
3
+
4
+ interface StreamingMessageProps {
5
+ content: string;
6
+ status?: string;
7
+ }
8
+
9
+ export const StreamingMessage = ({
10
+ content,
11
+ status,
12
+ }: StreamingMessageProps) => {
13
+ return (
14
+ <div className="ai-chat-message assistant streaming">
15
+ {status && (
16
+ <div className="ai-chat-status">{status}</div>
17
+ )}
18
+ <div className="ai-chat-message-content">
19
+ <Markdown remarkPlugins={[remarkGfm]}>
20
+ {content}
21
+ </Markdown>
22
+ </div>
23
+ </div>
24
+ );
25
+ };
@@ -0,0 +1,13 @@
1
+ export const TypingIndicator = () => {
2
+ return (
3
+ <div className="ai-chat-message assistant">
4
+ <div className="ai-chat-message-content">
5
+ <div className="ai-chat-typing">
6
+ <span></span>
7
+ <span></span>
8
+ <span></span>
9
+ </div>
10
+ </div>
11
+ </div>
12
+ );
13
+ };
@@ -0,0 +1,75 @@
1
+ import { useContext, useState, useCallback } from 'react';
2
+ import { VZCodeContext } from '../../VZCodeContext';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ import { MessageList } from './MessageList';
5
+ import { ChatInput } from './ChatInput';
6
+ import './styles.scss';
7
+
8
+ export const AIChat = () => {
9
+ const [message, setMessage] = useState('');
10
+ const [isLoading, setIsLoading] = useState(false);
11
+ const [currentChatId] = useState(() => uuidv4());
12
+
13
+ const { aiChatFocused, content } =
14
+ useContext(VZCodeContext);
15
+
16
+ // Get current chat data from content
17
+ const currentChat = content?.chats?.[currentChatId];
18
+ const messages = currentChat?.messages || [];
19
+ const aiScratchpad = currentChat?.aiScratchpad;
20
+ const aiStatus = currentChat?.aiStatus;
21
+
22
+ const handleSendMessage = useCallback(async () => {
23
+ if (!message.trim() || isLoading) return;
24
+
25
+ setMessage('');
26
+ setIsLoading(true);
27
+
28
+ // Call backend endpoint for AI response
29
+ // The server will handle all ShareDB operations including adding the user message
30
+ try {
31
+ const response = await fetch('/ai-chat-message', {
32
+ method: 'POST',
33
+ headers: {
34
+ 'Content-Type': 'application/json',
35
+ },
36
+ body: JSON.stringify({
37
+ content: message.trim(),
38
+ chatId: currentChatId,
39
+ }),
40
+ });
41
+
42
+ if (!response.ok) {
43
+ throw new Error(
44
+ `HTTP error! status: ${response.status}`,
45
+ );
46
+ }
47
+
48
+ // The backend handles all ShareDB operations
49
+ await response.json();
50
+ } catch (error) {
51
+ console.error('Error getting AI response:', error);
52
+ // Server will handle error messages too
53
+ } finally {
54
+ setIsLoading(false);
55
+ }
56
+ }, [message, isLoading, currentChatId]);
57
+
58
+ return (
59
+ <div className="ai-chat-container">
60
+ <MessageList
61
+ messages={messages}
62
+ aiScratchpad={aiScratchpad}
63
+ aiStatus={aiStatus}
64
+ isLoading={isLoading}
65
+ />
66
+ <ChatInput
67
+ message={message}
68
+ setMessage={setMessage}
69
+ onSendMessage={handleSendMessage}
70
+ isLoading={isLoading}
71
+ focused={aiChatFocused}
72
+ />
73
+ </div>
74
+ );
75
+ };
@@ -0,0 +1,256 @@
1
+ .ai-chat-container {
2
+ height: 100%;
3
+ display: flex;
4
+ flex-direction: column;
5
+ padding: 10px;
6
+ }
7
+
8
+ .ai-chat-messages {
9
+ flex: 1;
10
+ overflow-y: auto;
11
+ margin-bottom: 10px;
12
+ padding-right: 5px;
13
+
14
+ &::-webkit-scrollbar {
15
+ width: 6px;
16
+ }
17
+
18
+ &::-webkit-scrollbar-track {
19
+ background: var(--vh-color-neutral-02);
20
+ border-radius: 3px;
21
+ }
22
+
23
+ &::-webkit-scrollbar-thumb {
24
+ background: var(--vh-color-neutral-04);
25
+ border-radius: 3px;
26
+ }
27
+ }
28
+
29
+ .ai-chat-message {
30
+ margin-bottom: 15px;
31
+
32
+ &.user {
33
+ .ai-chat-message-content {
34
+ background: var(--vh-color-primary-01);
35
+ color: white;
36
+ margin-left: 20px;
37
+ border-radius: 12px 12px 4px 12px;
38
+ }
39
+
40
+ .ai-chat-message-time {
41
+ text-align: right;
42
+ margin-right: 5px;
43
+ }
44
+ }
45
+
46
+ &.assistant {
47
+ .ai-chat-message-content {
48
+ background: var(--vh-color-neutral-02);
49
+ color: white;
50
+ margin-right: 20px;
51
+ border-radius: 12px 12px 12px 4px;
52
+ }
53
+
54
+ .ai-chat-message-time {
55
+ text-align: left;
56
+ margin-left: 5px;
57
+ }
58
+
59
+ &.streaming {
60
+ .ai-chat-message-content {
61
+ background: var(--vh-color-neutral-02);
62
+ color: white;
63
+ border-radius: 12px 12px 12px 4px;
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ .ai-chat-message-content {
70
+ padding: 10px 12px;
71
+ font-size: 14px;
72
+ line-height: 1.4;
73
+ word-wrap: break-word;
74
+
75
+ // Markdown styling for dark backgrounds
76
+ pre {
77
+ background-color: rgba(0, 0, 0, 0.3);
78
+ color: #e6e6e6;
79
+ padding: 0.5rem;
80
+ border-radius: 0.25rem;
81
+ overflow-x: auto;
82
+ font-size: 0.875rem;
83
+ border: 1px solid rgba(255, 255, 255, 0.1);
84
+ }
85
+
86
+ code {
87
+ background-color: rgba(0, 0, 0, 0.3);
88
+ color: #e6e6e6;
89
+ padding: 0.125rem 0.25rem;
90
+ border-radius: 0.25rem;
91
+ font-size: 0.875rem;
92
+ border: 1px solid rgba(255, 255, 255, 0.1);
93
+ }
94
+
95
+ pre code {
96
+ background-color: transparent;
97
+ padding: 0;
98
+ border: none;
99
+ }
100
+
101
+ blockquote {
102
+ border-left: 3px solid var(--vh-color-neutral-03);
103
+ padding-left: 1rem;
104
+ margin: 0.5rem 0;
105
+ color: var(--vh-color-neutral-03);
106
+ font-style: italic;
107
+ }
108
+
109
+ h1,
110
+ h2,
111
+ h3,
112
+ h4,
113
+ h5,
114
+ h6 {
115
+ margin-top: 1rem;
116
+ margin-bottom: 0.5rem;
117
+ color: inherit;
118
+ }
119
+
120
+ ul,
121
+ ol {
122
+ padding-left: 1.5rem;
123
+ }
124
+
125
+ p {
126
+ margin-bottom: 0.5rem;
127
+ }
128
+
129
+ // Ensure good contrast for assistant messages
130
+ .ai-chat-message.assistant & {
131
+ color: white;
132
+ }
133
+
134
+ // Ensure user message text stays white
135
+ .ai-chat-message.user & {
136
+ color: white;
137
+ }
138
+ }
139
+
140
+ .ai-chat-message-time {
141
+ font-size: 11px;
142
+ color: var(--vh-color-neutral-03);
143
+ margin-top: 4px;
144
+ }
145
+
146
+ .ai-chat-status {
147
+ font-size: 0.75rem;
148
+ font-style: italic;
149
+ color: var(--vh-color-neutral-03);
150
+ margin-bottom: 0.5rem;
151
+ margin-left: 20px;
152
+ margin-right: 20px;
153
+ padding: 0.25rem 0.5rem;
154
+ background-color: var(--vh-color-neutral-02);
155
+ border-radius: 0.25rem;
156
+ border: 1px solid var(--vh-color-neutral-03);
157
+ text-align: left;
158
+ }
159
+
160
+ .ai-chat-typing {
161
+ display: flex;
162
+ gap: 4px;
163
+ align-items: center;
164
+
165
+ span {
166
+ width: 6px;
167
+ height: 6px;
168
+ background: var(--vh-color-neutral-03);
169
+ border-radius: 50%;
170
+ animation: typing 1.4s infinite ease-in-out;
171
+
172
+ &:nth-child(1) {
173
+ animation-delay: -0.32s;
174
+ }
175
+ &:nth-child(2) {
176
+ animation-delay: -0.16s;
177
+ }
178
+ &:nth-child(3) {
179
+ animation-delay: 0s;
180
+ }
181
+ }
182
+ }
183
+
184
+ @keyframes typing {
185
+ 0%,
186
+ 80%,
187
+ 100% {
188
+ transform: scale(0.8);
189
+ opacity: 0.5;
190
+ }
191
+ 40% {
192
+ transform: scale(1);
193
+ opacity: 1;
194
+ }
195
+ }
196
+
197
+ .ai-chat-input-container {
198
+ border-top: 1px solid var(--vh-color-neutral-02);
199
+ padding-top: 10px;
200
+ }
201
+
202
+ .ai-chat-input-group {
203
+ display: flex;
204
+ gap: 8px;
205
+ margin-bottom: 0;
206
+
207
+ textarea {
208
+ flex: 1;
209
+ background: var(--vh-color-neutral-02) !important;
210
+ border: 1px solid var(--vh-color-neutral-03) !important;
211
+ color: var(--vh-color-neutral-04) !important;
212
+ border-radius: 8px;
213
+ padding: 8px 10px;
214
+ font-size: 14px;
215
+ font-family: var(--vzcode-font-family);
216
+ resize: none;
217
+ min-height: 36px;
218
+
219
+ &:focus {
220
+ outline: none !important;
221
+ border-color: var(--vh-color-primary-01) !important;
222
+ box-shadow: 0 0 0 2px rgba(38, 67, 153, 0.2) !important;
223
+ color: var(--vh-color-neutral-04) !important;
224
+ }
225
+
226
+ &::placeholder {
227
+ color: var(--vh-color-neutral-03) !important;
228
+ }
229
+
230
+ &:disabled {
231
+ opacity: 0.6;
232
+ cursor: not-allowed;
233
+ }
234
+ }
235
+
236
+ .ai-chat-send-button {
237
+ background: var(--vh-color-primary-01);
238
+ border: none;
239
+ color: white;
240
+ border-radius: 8px;
241
+ padding: 8px 16px;
242
+ font-size: 14px;
243
+ font-weight: 500;
244
+ cursor: pointer;
245
+ transition: background-color 0.2s ease;
246
+
247
+ &:hover:not(:disabled) {
248
+ background: var(--vh-color-primary-02);
249
+ }
250
+
251
+ &:disabled {
252
+ background: var(--vh-color-neutral-03);
253
+ cursor: not-allowed;
254
+ }
255
+ }
256
+ }
@@ -112,7 +112,7 @@ export const VZSidebar = ({
112
112
  ),
113
113
  aiChatToolTipText = (
114
114
  <div>
115
- <strong>AI Chat</strong>
115
+ <strong>AI Chat (beta)</strong>
116
116
  </div>
117
117
  ),
118
118
  }: {