vzcode 2.21.0 → 2.22.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/README.md +359 -0
- package/dist/assets/{index-D6-he0oi.js → index-CfDs-dAu.js} +99 -99
- package/dist/assets/{index-fYq6iaqF.css → index-fSroLVgi.css} +1 -1
- package/dist/index.html +2 -2
- package/dist/llm-streaming-server/aiEditing.js +58 -0
- package/dist/llm-streaming-server/chatOperations.js +494 -0
- package/dist/llm-streaming-server/errorHandling.js +49 -0
- package/dist/llm-streaming-server/index.js +20 -0
- package/dist/llm-streaming-server/llmStreaming.js +265 -0
- package/dist/llm-streaming-server/validation.js +19 -0
- package/dist/server/aiChatHandler/index.js +5 -5
- package/package.json +35 -35
- package/src/client/CodeEditor/getOrCreateEditor.tsx +20 -13
- package/src/client/CodeEditor/index.tsx +3 -3
- package/src/client/VZSidebar/AIChat/styles.scss +38 -0
- package/src/client/VZSidebar/FileTypeIcon.tsx +1 -0
- package/src/client/VZSidebar/index.tsx +1 -1
- package/src/llm-streaming-server/README.md +71 -0
- package/src/llm-streaming-server/aiEditing.ts +102 -0
- package/src/llm-streaming-server/chatOperations.ts +655 -0
- package/src/llm-streaming-server/errorHandling.ts +66 -0
- package/src/llm-streaming-server/index.ts +51 -0
- package/src/llm-streaming-server/llmStreaming.ts +403 -0
- package/src/llm-streaming-server/validation.ts +24 -0
- package/src/llm-streaming-ui/README.md +103 -0
- package/src/llm-streaming-ui/components/ChatInput.tsx +237 -0
- package/src/llm-streaming-ui/components/DiffView.scss +173 -0
- package/src/llm-streaming-ui/components/DiffView.tsx +236 -0
- package/src/llm-streaming-ui/components/FileEditingIndicator.tsx +89 -0
- package/src/llm-streaming-ui/components/IndividualFileDiff.tsx +86 -0
- package/src/llm-streaming-ui/components/JumpToLatestButton.tsx +64 -0
- package/src/llm-streaming-ui/components/Message.tsx +145 -0
- package/src/llm-streaming-ui/components/MessageList.tsx +241 -0
- package/src/llm-streaming-ui/components/ThinkingScratchpad.tsx +42 -0
- package/src/llm-streaming-ui/components/TypingIndicator.tsx +19 -0
- package/src/llm-streaming-ui/components/index.tsx +388 -0
- package/src/llm-streaming-ui/components/styles.scss +831 -0
- package/src/llm-streaming-ui/components/useSpeechRecognition.ts +116 -0
- package/src/llm-streaming-ui/index.ts +34 -0
- package/src/server/aiChatHandler/index.ts +5 -5
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ VZCode offers a multiplayer code editing environment that caters to a real-time
|
|
|
10
10
|
- [Development](#development)
|
|
11
11
|
- [Features](#features)
|
|
12
12
|
- [Use Cases](#use-cases)
|
|
13
|
+
- [Visual Editor](#visual-editor)
|
|
13
14
|
- [Stack](#stack)
|
|
14
15
|
- [Goals](#goals)
|
|
15
16
|
- [Prior Work](#prior-work)
|
|
@@ -120,6 +121,364 @@ You can also use [npm link](https://docs.npmjs.com/cli/v8/commands/npm-link) to
|
|
|
120
121
|
- **Staging Site Editor (Experimental)**:
|
|
121
122
|
Use VZCode on a persistent server, making code changes with multiplayer mode remotely, reflecting instantly on a staging site.
|
|
122
123
|
|
|
124
|
+
## Visual Editor
|
|
125
|
+
|
|
126
|
+
VZCode includes a visual editor feature that allows you to create interactive widgets (like sliders and color pickers) for tweaking configuration parameters in real-time. This is particularly useful for data visualizations where you want to adjust visual properties dynamically without editing code.
|
|
127
|
+
|
|
128
|
+
### Overview
|
|
129
|
+
|
|
130
|
+
The visual editor works by:
|
|
131
|
+
|
|
132
|
+
1. Loading a `config.json` file that defines configuration parameters
|
|
133
|
+
2. Defining interactive widgets in the `visualEditorWidgets` array
|
|
134
|
+
3. Listening for configuration updates via `postMessage`
|
|
135
|
+
4. Re-rendering the visualization when configuration changes
|
|
136
|
+
|
|
137
|
+
### Configuration Structure
|
|
138
|
+
|
|
139
|
+
Your project should include a `config.json` file with the following structure:
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"xValue": "sepal_length",
|
|
144
|
+
"yValue": "sepal_width",
|
|
145
|
+
"margin": {
|
|
146
|
+
"top": 20,
|
|
147
|
+
"right": 67,
|
|
148
|
+
"bottom": 60,
|
|
149
|
+
"left": 60
|
|
150
|
+
},
|
|
151
|
+
"fontSize": "14px",
|
|
152
|
+
"fontFamily": "sans-serif",
|
|
153
|
+
"pointRadius": 17.2675034867503,
|
|
154
|
+
"pointFill": "black",
|
|
155
|
+
"pointOpacity": 0.7,
|
|
156
|
+
"loadingFontSize": "24px",
|
|
157
|
+
"loadingFontFamily": "sans-serif",
|
|
158
|
+
"loadingMessage": "Loading...",
|
|
159
|
+
"dataUrl": "iris.csv",
|
|
160
|
+
"colorScale": {
|
|
161
|
+
"setosa": "#1f77b4",
|
|
162
|
+
"versicolor": "#ff7f0e",
|
|
163
|
+
"virginica": "#2ca02c"
|
|
164
|
+
},
|
|
165
|
+
"visualEditorWidgets": [
|
|
166
|
+
{
|
|
167
|
+
"type": "slider",
|
|
168
|
+
"label": "Point Radius",
|
|
169
|
+
"property": "pointRadius",
|
|
170
|
+
"min": 1,
|
|
171
|
+
"max": 30
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"type": "slider",
|
|
175
|
+
"label": "Left Margin",
|
|
176
|
+
"property": "margin.left",
|
|
177
|
+
"min": 0,
|
|
178
|
+
"max": 200
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Widget Configuration
|
|
185
|
+
|
|
186
|
+
The `visualEditorWidgets` array defines the interactive controls that will be available in the visual editor. Each widget has the following properties:
|
|
187
|
+
|
|
188
|
+
- **type**: The type of widget. Supported types are:
|
|
189
|
+
- `"slider"` - A slider control for numeric values
|
|
190
|
+
- `"checkbox"` - A checkbox for boolean values
|
|
191
|
+
- `"textInput"` - A text input field for string values
|
|
192
|
+
- `"dropdown"` - A dropdown selector with predefined options
|
|
193
|
+
- `"color"` - A color picker with HCL (Hue, Chroma, Lightness) sliders
|
|
194
|
+
- **label**: Human-readable label displayed in the UI
|
|
195
|
+
- **property**: The configuration property to modify (supports nested properties using dot notation like `"margin.left"`)
|
|
196
|
+
- **min**: Minimum value (required for `slider` widgets)
|
|
197
|
+
- **max**: Maximum value (required for `slider` widgets)
|
|
198
|
+
- **step**: Step increment for `slider` widgets (optional, defaults to 1 if not specified)
|
|
199
|
+
- **options**: Array of string options (required for `dropdown` widgets)
|
|
200
|
+
|
|
201
|
+
### Setting Up Your Visualization
|
|
202
|
+
|
|
203
|
+
To make your visualization work with the visual editor, you need to:
|
|
204
|
+
|
|
205
|
+
#### 1. Load the Configuration
|
|
206
|
+
|
|
207
|
+
Use d3-rosetta's state management to load the configuration file:
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
import { createStateField } from 'd3-rosetta';
|
|
211
|
+
import { json } from 'd3';
|
|
212
|
+
|
|
213
|
+
export const viz = (container, state, setState) => {
|
|
214
|
+
const stateField = createStateField(state, setState);
|
|
215
|
+
const [config, setConfig] = stateField('config');
|
|
216
|
+
|
|
217
|
+
// Load config first if not already loaded
|
|
218
|
+
if (!config) {
|
|
219
|
+
json('config.json')
|
|
220
|
+
.then((loadedConfig) => {
|
|
221
|
+
setConfig(loadedConfig);
|
|
222
|
+
})
|
|
223
|
+
.catch((error) => {
|
|
224
|
+
console.error('Failed to load config:', error);
|
|
225
|
+
});
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ... rest of your visualization code
|
|
230
|
+
};
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### 2. Set Up postMessage Event Listener
|
|
234
|
+
|
|
235
|
+
Add an event listener to receive configuration updates from the visual editor:
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
export const viz = (container, state, setState) => {
|
|
239
|
+
// ... other code ...
|
|
240
|
+
|
|
241
|
+
// Set up postMessage event listener if not already set
|
|
242
|
+
if (!state.eventListenerAttached) {
|
|
243
|
+
window.addEventListener('message', (event) => {
|
|
244
|
+
// Verify the message contains config data
|
|
245
|
+
if (event.data && typeof event.data === 'object') {
|
|
246
|
+
// Update the config with the received data
|
|
247
|
+
setState((state) => ({
|
|
248
|
+
...state,
|
|
249
|
+
config: {
|
|
250
|
+
...state.config,
|
|
251
|
+
...event.data,
|
|
252
|
+
},
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Mark that we've attached the event listener to avoid duplicates
|
|
258
|
+
setState((prevState) => ({
|
|
259
|
+
...prevState,
|
|
260
|
+
eventListenerAttached: true,
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ... rest of your visualization code
|
|
265
|
+
};
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### 3. Use Configuration in Your Rendering
|
|
269
|
+
|
|
270
|
+
Use the configuration values when rendering your visualization:
|
|
271
|
+
|
|
272
|
+
```javascript
|
|
273
|
+
// Example: Rendering data points with configurable properties
|
|
274
|
+
export const renderMarks = (
|
|
275
|
+
svg,
|
|
276
|
+
{
|
|
277
|
+
data,
|
|
278
|
+
xScale,
|
|
279
|
+
yScale,
|
|
280
|
+
xValue,
|
|
281
|
+
yValue,
|
|
282
|
+
pointRadius,
|
|
283
|
+
colorScale,
|
|
284
|
+
pointOpacity,
|
|
285
|
+
},
|
|
286
|
+
) =>
|
|
287
|
+
svg
|
|
288
|
+
.selectAll('circle.data-point')
|
|
289
|
+
.data(data)
|
|
290
|
+
.join('circle')
|
|
291
|
+
.attr('class', 'data-point')
|
|
292
|
+
.attr('cx', (d) => xScale(xValue(d)))
|
|
293
|
+
.attr('cy', (d) => yScale(yValue(d)))
|
|
294
|
+
.attr('r', pointRadius) // Uses config value
|
|
295
|
+
.attr('fill', (d) => colorScale[d.species])
|
|
296
|
+
.attr('opacity', pointOpacity); // Uses config value
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
#### 4. Handle Loading States
|
|
300
|
+
|
|
301
|
+
Display loading states while data is being fetched:
|
|
302
|
+
|
|
303
|
+
```javascript
|
|
304
|
+
export const renderLoadingState = (
|
|
305
|
+
svg,
|
|
306
|
+
{ x, y, text, shouldShow, fontSize, fontFamily },
|
|
307
|
+
) => {
|
|
308
|
+
svg
|
|
309
|
+
.selectAll('text.loading-text')
|
|
310
|
+
.data(shouldShow ? [null] : [])
|
|
311
|
+
.join('text')
|
|
312
|
+
.attr('class', 'loading-text')
|
|
313
|
+
.attr('x', x)
|
|
314
|
+
.attr('y', y)
|
|
315
|
+
.attr('text-anchor', 'middle')
|
|
316
|
+
.attr('dominant-baseline', 'middle')
|
|
317
|
+
.attr('font-size', fontSize) // Uses config value
|
|
318
|
+
.attr('font-family', fontFamily) // Uses config value
|
|
319
|
+
.text(text);
|
|
320
|
+
};
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
#### 5. Manage Asynchronous Data Loading
|
|
324
|
+
|
|
325
|
+
Handle asynchronous data requests with proper state management:
|
|
326
|
+
|
|
327
|
+
```javascript
|
|
328
|
+
export const asyncRequest = (
|
|
329
|
+
setDataRequest,
|
|
330
|
+
loadAndParseData,
|
|
331
|
+
) => {
|
|
332
|
+
setDataRequest({ status: 'Loading' });
|
|
333
|
+
loadAndParseData()
|
|
334
|
+
.then((data) => {
|
|
335
|
+
setDataRequest({ status: 'Succeeded', data });
|
|
336
|
+
})
|
|
337
|
+
.catch((error) => {
|
|
338
|
+
setDataRequest({ status: 'Failed', error });
|
|
339
|
+
});
|
|
340
|
+
};
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Complete Example
|
|
344
|
+
|
|
345
|
+
Here's how everything fits together in your main visualization file:
|
|
346
|
+
|
|
347
|
+
```javascript
|
|
348
|
+
import { createStateField } from 'd3-rosetta';
|
|
349
|
+
import { setupSVG } from './setupSVG.js';
|
|
350
|
+
import { renderLoadingState } from './renderLoadingState.js';
|
|
351
|
+
import { asyncRequest } from './asyncRequest.js';
|
|
352
|
+
import { loadAndParseData } from './loadAndParseData.js';
|
|
353
|
+
import { scatterPlot } from './scatterPlot.js';
|
|
354
|
+
import { measureDimensions } from './measureDimensions.js';
|
|
355
|
+
import { json } from 'd3';
|
|
356
|
+
|
|
357
|
+
export const viz = (container, state, setState) => {
|
|
358
|
+
const stateField = createStateField(state, setState);
|
|
359
|
+
const [dataRequest, setDataRequest] =
|
|
360
|
+
stateField('dataRequest');
|
|
361
|
+
const [config, setConfig] = stateField('config');
|
|
362
|
+
|
|
363
|
+
// Set up postMessage event listener if not already set
|
|
364
|
+
if (!state.eventListenerAttached) {
|
|
365
|
+
window.addEventListener('message', (event) => {
|
|
366
|
+
if (event.data && typeof event.data === 'object') {
|
|
367
|
+
setState((state) => ({
|
|
368
|
+
...state,
|
|
369
|
+
config: {
|
|
370
|
+
...state.config,
|
|
371
|
+
...event.data,
|
|
372
|
+
},
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
setState((prevState) => ({
|
|
378
|
+
...prevState,
|
|
379
|
+
eventListenerAttached: true,
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Load config first if not already loaded
|
|
384
|
+
if (!config) {
|
|
385
|
+
json('config.json')
|
|
386
|
+
.then((loadedConfig) => {
|
|
387
|
+
setConfig(loadedConfig);
|
|
388
|
+
})
|
|
389
|
+
.catch((error) => {
|
|
390
|
+
console.error('Failed to load config:', error);
|
|
391
|
+
});
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// After config is loaded, load the data
|
|
396
|
+
if (!dataRequest) {
|
|
397
|
+
return asyncRequest(setDataRequest, () =>
|
|
398
|
+
loadAndParseData(config.dataUrl),
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const { data, error } = dataRequest;
|
|
403
|
+
const dimensions = measureDimensions(container);
|
|
404
|
+
const svg = setupSVG(container, dimensions);
|
|
405
|
+
|
|
406
|
+
renderLoadingState(svg, {
|
|
407
|
+
shouldShow: !data,
|
|
408
|
+
text: error
|
|
409
|
+
? `Error: ${error.message}`
|
|
410
|
+
: config.loadingMessage,
|
|
411
|
+
x: dimensions.width / 2,
|
|
412
|
+
y: dimensions.height / 2,
|
|
413
|
+
fontSize: config.loadingFontSize,
|
|
414
|
+
fontFamily: config.loadingFontFamily,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
if (data) {
|
|
418
|
+
// Transform string properties in config to accessor functions
|
|
419
|
+
const configWithAccessors = {
|
|
420
|
+
...config,
|
|
421
|
+
xValue: (d) => d[config.xValue],
|
|
422
|
+
yValue: (d) => d[config.yValue],
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
scatterPlot(svg, {
|
|
426
|
+
...configWithAccessors,
|
|
427
|
+
data,
|
|
428
|
+
dimensions,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Required HTML Structure
|
|
435
|
+
|
|
436
|
+
Your `index.html` should include a container element and proper script imports:
|
|
437
|
+
|
|
438
|
+
```html
|
|
439
|
+
<!DOCTYPE html>
|
|
440
|
+
<html>
|
|
441
|
+
<head>
|
|
442
|
+
<meta charset="utf-8" />
|
|
443
|
+
<title>Visual Editor Example</title>
|
|
444
|
+
<link rel="stylesheet" href="styles.css" />
|
|
445
|
+
<script type="importmap">
|
|
446
|
+
{
|
|
447
|
+
"imports": {
|
|
448
|
+
"d3": "https://cdn.jsdelivr.net/npm/d3@7.9.0/+esm",
|
|
449
|
+
"d3-rosetta": "https://cdn.jsdelivr.net/npm/d3-rosetta@3.0.0/+esm"
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
</script>
|
|
453
|
+
</head>
|
|
454
|
+
<body>
|
|
455
|
+
<div id="viz-container"></div>
|
|
456
|
+
<script type="module" src="index.js"></script>
|
|
457
|
+
</body>
|
|
458
|
+
</html>
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Styling
|
|
462
|
+
|
|
463
|
+
Use CSS to ensure your visualization container fills the viewport:
|
|
464
|
+
|
|
465
|
+
```css
|
|
466
|
+
#viz-container {
|
|
467
|
+
position: fixed;
|
|
468
|
+
inset: 0;
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Best Practices
|
|
473
|
+
|
|
474
|
+
1. **Use d3-rosetta**: The visual editor is designed to work with d3-rosetta's unidirectional data flow pattern
|
|
475
|
+
2. **Modular Code**: Separate your rendering logic into small, focused functions
|
|
476
|
+
3. **Configuration-Driven**: Make visual properties configurable through `config.json`
|
|
477
|
+
4. **Nested Properties**: Use dot notation (e.g., `"margin.left"`) to modify nested configuration values
|
|
478
|
+
5. **Hot Reloading**: The visual editor works seamlessly with VZCode's throttled auto-save for hot reloading environments
|
|
479
|
+
|
|
480
|
+
For a complete working example, see the [visualEditor sample directory](test/sampleDirectories/visualEditor) in this repository.
|
|
481
|
+
|
|
123
482
|
## Stack
|
|
124
483
|
|
|
125
484
|
Built using technologies such as:
|