vzcode 2.3.0 → 2.4.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/package.json +3 -2
- package/src/cli.ts +208 -0
- package/src/client/AIAssist/AIAssistCodeMirrorKeyMap.ts +42 -0
- package/src/client/AIAssist/AIAssistWidget/index.tsx +135 -0
- package/src/client/AIAssist/AIAssistWidget/style.scss +47 -0
- package/src/client/AIAssist/Spinner.jsx +76 -0
- package/src/client/AIAssist/formatFile.ts +27 -0
- package/src/client/AIAssist/generateFilesContext.ts +25 -0
- package/src/client/AIAssist/index.ts +2 -0
- package/src/client/AIAssist/startAIAssist.ts +95 -0
- package/src/client/App/index.tsx +152 -0
- package/src/client/App/style.scss +82 -0
- package/src/client/App/usePending.ts +51 -0
- package/src/client/App/useShareDB.ts +161 -0
- package/src/client/CodeEditor/Copilot/index.ts +74 -0
- package/src/client/CodeEditor/InteractiveWidgets/boolToggler.ts +12 -0
- package/src/client/CodeEditor/InteractiveWidgets/colorPicker.ts +68 -0
- package/src/client/CodeEditor/InteractiveWidgets/colorsInTextPlugin.ts +174 -0
- package/src/client/CodeEditor/InteractiveWidgets/highlightWidgets.ts +79 -0
- package/src/client/CodeEditor/InteractiveWidgets/index.ts +44 -0
- package/src/client/CodeEditor/InteractiveWidgets/markdownCheckboxToggler.ts +12 -0
- package/src/client/CodeEditor/InteractiveWidgets/numberDragger.ts +20 -0
- package/src/client/CodeEditor/InteractiveWidgets/rotateWidget.ts +143 -0
- package/src/client/CodeEditor/InteractiveWidgets/urlClicker.test.ts +48 -0
- package/src/client/CodeEditor/InteractiveWidgets/urlClicker.ts +12 -0
- package/src/client/CodeEditor/InteractiveWidgets/vec2Slider.ts +25 -0
- package/src/client/CodeEditor/basicSetup.ts +73 -0
- package/src/client/CodeEditor/getOrCreateEditor.tsx +615 -0
- package/src/client/CodeEditor/index.tsx +202 -0
- package/src/client/CodeEditor/json1PresenceBroadcast.ts +41 -0
- package/src/client/CodeEditor/json1PresenceDisplay.ts +350 -0
- package/src/client/CodeEditor/rainbowBrackets.ts +122 -0
- package/src/client/CodeEditor/style.scss +156 -0
- package/src/client/CodeEditor/typescriptExtension/worker.ts +32 -0
- package/src/client/CodeErrorOverlay/index.tsx +77 -0
- package/src/client/CodeErrorOverlay/style.scss +33 -0
- package/src/client/Fonts/fonts.ts +8 -0
- package/src/client/Icons/AdjustmentSVG.tsx +19 -0
- package/src/client/Icons/BugSVG.tsx +20 -0
- package/src/client/Icons/CloseSVG.tsx +19 -0
- package/src/client/Icons/CssSVG.tsx +16 -0
- package/src/client/Icons/DirectoryArrowSVG.tsx +19 -0
- package/src/client/Icons/EditSVG.tsx +19 -0
- package/src/client/Icons/FileSVG.tsx +19 -0
- package/src/client/Icons/FolderSVG.tsx +20 -0
- package/src/client/Icons/GearSVG.tsx +16 -0
- package/src/client/Icons/HtmlSVG.tsx +16 -0
- package/src/client/Icons/JavaScriptSVG.tsx +16 -0
- package/src/client/Icons/JsonSVG.tsx +16 -0
- package/src/client/Icons/MarkdownSVG.tsx +16 -0
- package/src/client/Icons/MicOffSVG.tsx +35 -0
- package/src/client/Icons/MicSVG.tsx +25 -0
- package/src/client/Icons/NewSVG.tsx +19 -0
- package/src/client/Icons/PinSVG.tsx +14 -0
- package/src/client/Icons/PlaySVG.tsx +16 -0
- package/src/client/Icons/QuestionMarkSVG.tsx +18 -0
- package/src/client/Icons/ReactSVG.tsx +16 -0
- package/src/client/Icons/SearchSVG.tsx +20 -0
- package/src/client/Icons/SparklesSVG.tsx +23 -0
- package/src/client/Icons/SplitEditorSVG.tsx +34 -0
- package/src/client/Icons/StopSVG.tsx +15 -0
- package/src/client/Icons/SvelteSVG.tsx +16 -0
- package/src/client/Icons/TrashSVG.tsx +19 -0
- package/src/client/Icons/TypeScriptSVG.tsx +16 -0
- package/src/client/Icons/index.tsx +26 -0
- package/src/client/ImageViewer/index.tsx +91 -0
- package/src/client/ImageViewer/style.scss +41 -0
- package/src/client/PresenceNotifications/PresenceNotifications.test.ts +134 -0
- package/src/client/PresenceNotifications/index.tsx +158 -0
- package/src/client/PresenceNotifications/style.scss +17 -0
- package/src/client/RunCodeWidget/index.tsx +117 -0
- package/src/client/RunCodeWidget/style.scss +24 -0
- package/src/client/SplitPaneResizeContext.tsx +159 -0
- package/src/client/TabList/Tab.tsx +73 -0
- package/src/client/TabList/index.tsx +35 -0
- package/src/client/TabList/style.scss +51 -0
- package/src/client/VZCodeContext/index.tsx +29 -0
- package/src/client/VZCodeContext/types.ts +248 -0
- package/src/client/VZCodeContext/useVZCodeState.ts +602 -0
- package/src/client/VZKeyboardShortcutsDoc.tsx +148 -0
- package/src/client/VZLeft.tsx +28 -0
- package/src/client/VZMiddle.tsx +172 -0
- package/src/client/VZResizer/index.tsx +128 -0
- package/src/client/VZResizer/styles.scss +33 -0
- package/src/client/VZRight.tsx +113 -0
- package/src/client/VZSettings.tsx +275 -0
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +243 -0
- package/src/client/VZSidebar/AIChat/DiffView.scss +136 -0
- package/src/client/VZSidebar/AIChat/DiffView.tsx +176 -0
- package/src/client/VZSidebar/AIChat/Message.tsx +79 -0
- package/src/client/VZSidebar/AIChat/MessageList.tsx +190 -0
- package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +35 -0
- package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +156 -0
- package/src/client/VZSidebar/AIChat/TypingIndicator.tsx +19 -0
- package/src/client/VZSidebar/AIChat/index.tsx +357 -0
- package/src/client/VZSidebar/AIChat/styles.scss +646 -0
- package/src/client/VZSidebar/AIChat/useSpeechRecognition.ts +117 -0
- package/src/client/VZSidebar/CreateDirModal.tsx +111 -0
- package/src/client/VZSidebar/CreateFileModal.tsx +111 -0
- package/src/client/VZSidebar/DeleteConfirmationModal.tsx +76 -0
- package/src/client/VZSidebar/DirectoryListing.tsx +89 -0
- package/src/client/VZSidebar/EmptyState.tsx +11 -0
- package/src/client/VZSidebar/FileListing.tsx +91 -0
- package/src/client/VZSidebar/FileTypeIcon.tsx +49 -0
- package/src/client/VZSidebar/Item.tsx +237 -0
- package/src/client/VZSidebar/Listing.tsx +47 -0
- package/src/client/VZSidebar/Search.tsx +527 -0
- package/src/client/VZSidebar/VisualEditor.tsx +282 -0
- package/src/client/VZSidebar/VoiceChatModal.tsx +121 -0
- package/src/client/VZSidebar/aiCopyPaste.ts +199 -0
- package/src/client/VZSidebar/index.tsx +728 -0
- package/src/client/VZSidebar/styles.scss +612 -0
- package/src/client/VZSidebar/useDragAndDrop.tsx +408 -0
- package/src/client/bootstrap.ts +26 -0
- package/src/client/buildWorker.ts +3 -0
- package/src/client/featureFlags.ts +8 -0
- package/src/client/generateRequestId.ts +6 -0
- package/src/client/getFileTree.ts +53 -0
- package/src/client/index.ts +19 -0
- package/src/client/main.jsx +10 -0
- package/src/client/presenceColor.ts +8 -0
- package/src/client/shouldTriggerRun.ts +31 -0
- package/src/client/sortFileTree.ts +21 -0
- package/src/client/tabsSearchParameters.test.ts +79 -0
- package/src/client/tabsSearchParameters.ts +137 -0
- package/src/client/themes/index.ts +7 -0
- package/src/client/themes/themes.ts +77 -0
- package/src/client/themes/uiwImports.ts +32 -0
- package/src/client/themes/useDynamicTheme.ts +29 -0
- package/src/client/themes/vizhubTheme/colors.ts +80 -0
- package/src/client/themes/vizhubTheme/index.ts +193 -0
- package/src/client/useActions.ts +288 -0
- package/src/client/useESLint/index.ts +72 -0
- package/src/client/useESLint/worker.ts +113 -0
- package/src/client/useEditorCache.ts +35 -0
- package/src/client/useFileCRUD.ts +228 -0
- package/src/client/useKeyboardShortcuts.ts +451 -0
- package/src/client/useOpenDirectories.ts +99 -0
- package/src/client/usePresenceAutoFollow.ts +85 -0
- package/src/client/usePrettier/index.ts +178 -0
- package/src/client/usePrettier/worker.ts +109 -0
- package/src/client/useRunCode.tsx +15 -0
- package/src/client/useRuntimeError.ts +32 -0
- package/src/client/useSubmitOperation.ts +15 -0
- package/src/client/useURLSync.ts +137 -0
- package/src/client/usernameLocalStorage.ts +43 -0
- package/src/client/utils/fileExtension.ts +10 -0
- package/src/client/utils/isImageFile.ts +12 -0
- package/src/client/vzReducer/aiChatReducer.ts +44 -0
- package/src/client/vzReducer/closeTabReducer.test.ts +205 -0
- package/src/client/vzReducer/closeTabsReducer.ts +96 -0
- package/src/client/vzReducer/createInitialState.ts +38 -0
- package/src/client/vzReducer/editorNoLongerWantsFocusReducer.ts +9 -0
- package/src/client/vzReducer/findPane.ts +19 -0
- package/src/client/vzReducer/index.ts +250 -0
- package/src/client/vzReducer/openTabReducer.test.ts +127 -0
- package/src/client/vzReducer/openTabReducer.ts +74 -0
- package/src/client/vzReducer/searchReducer.test.ts +292 -0
- package/src/client/vzReducer/searchReducer.ts +181 -0
- package/src/client/vzReducer/setActiveFileIdReducer.ts +28 -0
- package/src/client/vzReducer/setActiveFileLeftRightReducer.ts +76 -0
- package/src/client/vzReducer/setIsDocOpenReducer.ts +9 -0
- package/src/client/vzReducer/setIsSettingsOpenReducer.ts +9 -0
- package/src/client/vzReducer/setThemeReducer.ts +9 -0
- package/src/client/vzReducer/setUsernameReducer.ts +9 -0
- package/src/client/vzReducer/splitCurrentPaneReducer.ts +66 -0
- package/src/client/vzReducer/toggleAutoFollowReducer.ts +12 -0
- package/src/client/vzReducer/updatePane.ts +40 -0
- package/src/client/vzReducer/updatePresenceIndicatorReducer.ts +30 -0
- package/src/client/vzReducer/visualEditorReducer.ts +9 -0
- package/src/ot.ts +24 -0
- package/src/randomId.ts +15 -0
- package/src/reactRouterExports.ts +15 -0
- package/src/runCode.ts +27 -0
- package/src/server/aiChatHandler/aiEditing.ts +151 -0
- package/src/server/aiChatHandler/chatOperations.ts +476 -0
- package/src/server/aiChatHandler/errorHandling.ts +66 -0
- package/src/server/aiChatHandler/index.ts +199 -0
- package/src/server/aiChatHandler/llmStreaming.ts +419 -0
- package/src/server/aiChatHandler/validation.ts +24 -0
- package/src/server/computeInitialDocument.ts +220 -0
- package/src/server/config.ts +24 -0
- package/src/server/featureFlags.ts +8 -0
- package/src/server/generateAIResponse.ts +188 -0
- package/src/server/handleAIAssist.ts +53 -0
- package/src/server/handleAIChatMessage.ts +2 -0
- package/src/server/handleAICopilot.ts +97 -0
- package/src/server/index.ts +379 -0
- package/src/server/isDirectory.ts +1 -0
- package/src/server/livekit.ts +24 -0
- package/src/server/prettier.ts +115 -0
- package/src/server/setupEnv.ts +8 -0
- package/src/submitOperation.ts +25 -0
- package/src/types.ts +205 -0
- package/src/utils/fileDiff.ts +117 -0
- package/src/vite-env.d.ts +10 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useContext,
|
|
4
|
+
useState,
|
|
5
|
+
useRef,
|
|
6
|
+
useEffect,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import { VZCodeContext } from '../VZCodeContext';
|
|
9
|
+
import { VizContent, VizFileId } from '@vizhub/viz-types';
|
|
10
|
+
import { VisualEditorConfigEntry } from '../../types';
|
|
11
|
+
import { EmptyState } from './EmptyState';
|
|
12
|
+
|
|
13
|
+
const CONFIG_FILE_NAME = 'config.json';
|
|
14
|
+
|
|
15
|
+
// Helper function to calculate percentage for progress fill
|
|
16
|
+
const calculatePercentage = (
|
|
17
|
+
value: number,
|
|
18
|
+
min: number,
|
|
19
|
+
max: number,
|
|
20
|
+
) => {
|
|
21
|
+
return ((value - min) / (max - min)) * 100;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Helper function to format values nicely
|
|
25
|
+
const formatValue = (
|
|
26
|
+
value: number,
|
|
27
|
+
min: number,
|
|
28
|
+
max: number,
|
|
29
|
+
) => {
|
|
30
|
+
// Determine decimal places based on the range
|
|
31
|
+
const range = max - min;
|
|
32
|
+
const decimalPlaces =
|
|
33
|
+
range < 10 ? 2 : range < 100 ? 1 : 0;
|
|
34
|
+
return Number(value).toFixed(decimalPlaces);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const VisualEditor = () => {
|
|
38
|
+
const { files, submitOperation, iframeRef } =
|
|
39
|
+
useContext(VZCodeContext);
|
|
40
|
+
|
|
41
|
+
// Local state to track slider values during user interaction
|
|
42
|
+
const [localValues, setLocalValues] = useState<{
|
|
43
|
+
[key: string]: number;
|
|
44
|
+
}>({});
|
|
45
|
+
|
|
46
|
+
let configFileId: VizFileId | null = null;
|
|
47
|
+
for (const fileId in files) {
|
|
48
|
+
if (files[fileId].name === CONFIG_FILE_NAME) {
|
|
49
|
+
configFileId = fileId;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (configFileId === null) {
|
|
55
|
+
return (
|
|
56
|
+
<EmptyState>
|
|
57
|
+
To begin using the visual editor, create a
|
|
58
|
+
config.json.
|
|
59
|
+
</EmptyState>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let configData;
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
configData = JSON.parse(files[configFileId].text);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return (
|
|
69
|
+
<EmptyState>
|
|
70
|
+
Your config.json file is not valid json.
|
|
71
|
+
</EmptyState>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!('visualEditorWidgets' in configData)) {
|
|
76
|
+
return (
|
|
77
|
+
<EmptyState>
|
|
78
|
+
To begin using the visual editor, make sure your
|
|
79
|
+
config.json has a key called "visualEditorWidgets",
|
|
80
|
+
whose value is the config for the visual editor.
|
|
81
|
+
</EmptyState>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!Array.isArray(configData.visualEditorWidgets)) {
|
|
86
|
+
return (
|
|
87
|
+
<EmptyState>
|
|
88
|
+
Your config.json file has "visualEditorWidgets" but
|
|
89
|
+
it is not an array. Please make sure
|
|
90
|
+
"visualEditorWidgets" is an array of widget
|
|
91
|
+
configurations.
|
|
92
|
+
</EmptyState>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const onSliderChange = useCallback(
|
|
97
|
+
(property: string) =>
|
|
98
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
99
|
+
const newValue = parseFloat(
|
|
100
|
+
event.currentTarget.value,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Update local state immediately for responsive UI
|
|
104
|
+
setLocalValues((prev) => ({
|
|
105
|
+
...prev,
|
|
106
|
+
[property]: newValue,
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
// Update config.json
|
|
110
|
+
const newConfigData = {
|
|
111
|
+
...configData,
|
|
112
|
+
[property]: newValue,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
submitOperation((document: VizContent) => ({
|
|
116
|
+
...document,
|
|
117
|
+
files: {
|
|
118
|
+
...files,
|
|
119
|
+
[configFileId]: {
|
|
120
|
+
name: 'config.json',
|
|
121
|
+
text: JSON.stringify(newConfigData, null, 2),
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
}));
|
|
125
|
+
},
|
|
126
|
+
[configData, files, configFileId, setLocalValues],
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const visualEditorWidgets: VisualEditorConfigEntry[] =
|
|
130
|
+
configData.visualEditorWidgets;
|
|
131
|
+
|
|
132
|
+
// Sync local values with config data when it changes (including remote updates)
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
const newLocalValues: { [key: string]: number } = {};
|
|
135
|
+
visualEditorWidgets.forEach((widget) => {
|
|
136
|
+
if (widget.type === 'number') {
|
|
137
|
+
newLocalValues[widget.property] =
|
|
138
|
+
configData[widget.property];
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
setLocalValues(newLocalValues);
|
|
142
|
+
}, [configData, visualEditorWidgets]);
|
|
143
|
+
|
|
144
|
+
// Track previous config state to detect changes from any source (remote clients, text editor, etc.)
|
|
145
|
+
const previousConfigRef = useRef<any>(null);
|
|
146
|
+
|
|
147
|
+
// Detect config.json changes and send updates to iframe
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (!configFileId || !files || !files[configFileId]) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let newConfigData;
|
|
154
|
+
try {
|
|
155
|
+
newConfigData = JSON.parse(files[configFileId].text);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
// If config is invalid JSON, we can't process changes
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const previousConfig = previousConfigRef.current;
|
|
162
|
+
|
|
163
|
+
// Update the ref with the new config
|
|
164
|
+
previousConfigRef.current = newConfigData;
|
|
165
|
+
|
|
166
|
+
// Skip processing if this is the first time or if there's no previous config
|
|
167
|
+
if (!previousConfig) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Find changed top-level properties
|
|
172
|
+
const changedProperties: { [key: string]: any } = {};
|
|
173
|
+
|
|
174
|
+
// Check all properties in the new config
|
|
175
|
+
for (const key in newConfigData) {
|
|
176
|
+
if (newConfigData[key] !== previousConfig[key]) {
|
|
177
|
+
// Deep comparison for objects to detect actual changes
|
|
178
|
+
if (
|
|
179
|
+
typeof newConfigData[key] === 'object' &&
|
|
180
|
+
typeof previousConfig[key] === 'object'
|
|
181
|
+
) {
|
|
182
|
+
if (
|
|
183
|
+
JSON.stringify(newConfigData[key]) !==
|
|
184
|
+
JSON.stringify(previousConfig[key])
|
|
185
|
+
) {
|
|
186
|
+
changedProperties[key] = newConfigData[key];
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
changedProperties[key] = newConfigData[key];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Check for deleted properties (properties that existed before but don't exist now)
|
|
195
|
+
for (const key in previousConfig) {
|
|
196
|
+
if (!(key in newConfigData)) {
|
|
197
|
+
changedProperties[key] = undefined;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Send changed properties to iframe if any changes were detected
|
|
202
|
+
if (Object.keys(changedProperties).length > 0) {
|
|
203
|
+
try {
|
|
204
|
+
iframeRef.current.contentWindow.postMessage(
|
|
205
|
+
changedProperties,
|
|
206
|
+
);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error(
|
|
209
|
+
'Failed to send config changes to iframe:',
|
|
210
|
+
error,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}, [files, configFileId, iframeRef]);
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<div className="visual-editor">
|
|
218
|
+
{visualEditorWidgets.map((widgetConfig, index) => {
|
|
219
|
+
if (widgetConfig.type === 'number') {
|
|
220
|
+
// Use local value if available, otherwise fall back to config value
|
|
221
|
+
const currentValue =
|
|
222
|
+
localValues[widgetConfig.property] ??
|
|
223
|
+
configData[widgetConfig.property];
|
|
224
|
+
const percentage = calculatePercentage(
|
|
225
|
+
currentValue,
|
|
226
|
+
widgetConfig.min,
|
|
227
|
+
widgetConfig.max,
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<div
|
|
232
|
+
key={widgetConfig.property}
|
|
233
|
+
className="visual-editor-slider"
|
|
234
|
+
>
|
|
235
|
+
<div className="slider-header">
|
|
236
|
+
<label
|
|
237
|
+
htmlFor={widgetConfig.property}
|
|
238
|
+
className="slider-label"
|
|
239
|
+
>
|
|
240
|
+
{widgetConfig.label}
|
|
241
|
+
</label>
|
|
242
|
+
<span className="slider-value">
|
|
243
|
+
{formatValue(
|
|
244
|
+
currentValue,
|
|
245
|
+
widgetConfig.min,
|
|
246
|
+
widgetConfig.max,
|
|
247
|
+
)}
|
|
248
|
+
</span>
|
|
249
|
+
</div>
|
|
250
|
+
<div className="slider-container">
|
|
251
|
+
<input
|
|
252
|
+
type="range"
|
|
253
|
+
id={widgetConfig.property}
|
|
254
|
+
className="slider-input"
|
|
255
|
+
min={widgetConfig.min}
|
|
256
|
+
max={widgetConfig.max}
|
|
257
|
+
step="any"
|
|
258
|
+
value={currentValue}
|
|
259
|
+
onChange={onSliderChange(
|
|
260
|
+
widgetConfig.property,
|
|
261
|
+
)}
|
|
262
|
+
/>
|
|
263
|
+
<div
|
|
264
|
+
className="slider-track-fill"
|
|
265
|
+
style={{ width: `${percentage}%` }}
|
|
266
|
+
/>
|
|
267
|
+
</div>
|
|
268
|
+
<div className="slider-bounds">
|
|
269
|
+
<span className="min-value">
|
|
270
|
+
{widgetConfig.min}
|
|
271
|
+
</span>
|
|
272
|
+
<span className="max-value">
|
|
273
|
+
{widgetConfig.max}
|
|
274
|
+
</span>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
})}
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ParticipantLoop,
|
|
3
|
+
ParticipantName,
|
|
4
|
+
useParticipants,
|
|
5
|
+
useRoomContext,
|
|
6
|
+
} from '@livekit/components-react';
|
|
7
|
+
import { useContext, useState } from 'react';
|
|
8
|
+
import { Button, Form, Modal } from 'react-bootstrap';
|
|
9
|
+
import { v4 } from 'uuid';
|
|
10
|
+
import { VZCodeContext } from '../VZCodeContext';
|
|
11
|
+
|
|
12
|
+
export function VoiceChatModal() {
|
|
13
|
+
const {
|
|
14
|
+
liveKitRoomName,
|
|
15
|
+
setLiveKitRoom,
|
|
16
|
+
liveKitToken,
|
|
17
|
+
setLiveKitToken,
|
|
18
|
+
liveKitConnection,
|
|
19
|
+
setLiveKitConnection,
|
|
20
|
+
voiceChatModalOpen,
|
|
21
|
+
setVoiceChatModalOpen,
|
|
22
|
+
} = useContext(VZCodeContext);
|
|
23
|
+
const participants = useParticipants();
|
|
24
|
+
|
|
25
|
+
const [roomName, setRoomName] = useState(
|
|
26
|
+
liveKitRoomName || '',
|
|
27
|
+
);
|
|
28
|
+
const [username, setUsername] = useState('');
|
|
29
|
+
const room = useRoomContext();
|
|
30
|
+
|
|
31
|
+
const handleConnect = () => {
|
|
32
|
+
if (liveKitConnection) {
|
|
33
|
+
setLiveKitConnection(false);
|
|
34
|
+
setLiveKitRoom(`${v4()}`);
|
|
35
|
+
setLiveKitToken('');
|
|
36
|
+
room.disconnect(true);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
setLiveKitRoom(roomName);
|
|
40
|
+
const fetchToken = async () => {
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch(
|
|
43
|
+
`/livekit-token?room=${roomName}&username=${username}`,
|
|
44
|
+
{
|
|
45
|
+
method: 'GET',
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
const tokenResponse = await res.text();
|
|
49
|
+
setLiveKitToken(tokenResponse);
|
|
50
|
+
setLiveKitConnection(true);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('Error fetching token:', error);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
fetchToken();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleClose = () => {
|
|
60
|
+
setVoiceChatModalOpen(false);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Modal
|
|
65
|
+
className="vz-settings"
|
|
66
|
+
show={voiceChatModalOpen}
|
|
67
|
+
onHide={handleClose}
|
|
68
|
+
animation={false}
|
|
69
|
+
>
|
|
70
|
+
<Modal.Header closeButton>
|
|
71
|
+
<Modal.Title>Connect to LiveKit Room</Modal.Title>
|
|
72
|
+
</Modal.Header>
|
|
73
|
+
<Modal.Body>
|
|
74
|
+
<Form>
|
|
75
|
+
<Form.Group controlId="formRoomName">
|
|
76
|
+
<Form.Label>Room Name</Form.Label>
|
|
77
|
+
<Form.Control
|
|
78
|
+
type="text"
|
|
79
|
+
placeholder="Enter room name"
|
|
80
|
+
value={roomName}
|
|
81
|
+
onChange={(e) => setRoomName(e.target.value)}
|
|
82
|
+
/>
|
|
83
|
+
</Form.Group>
|
|
84
|
+
</Form>
|
|
85
|
+
<Form>
|
|
86
|
+
<Form.Group controlId="formUserName">
|
|
87
|
+
<Form.Label>Username</Form.Label>
|
|
88
|
+
<Form.Control
|
|
89
|
+
type="text"
|
|
90
|
+
placeholder="Enter Username"
|
|
91
|
+
value={username}
|
|
92
|
+
onChange={(e) => setUsername(e.target.value)}
|
|
93
|
+
/>
|
|
94
|
+
</Form.Group>
|
|
95
|
+
</Form>
|
|
96
|
+
<div
|
|
97
|
+
style={{
|
|
98
|
+
display: 'flex',
|
|
99
|
+
flexDirection: 'column',
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
<b>Users in Room: </b>
|
|
103
|
+
<ParticipantLoop participants={participants}>
|
|
104
|
+
<ParticipantName />
|
|
105
|
+
</ParticipantLoop>
|
|
106
|
+
</div>
|
|
107
|
+
</Modal.Body>
|
|
108
|
+
<Modal.Footer>
|
|
109
|
+
<Button variant="secondary" onClick={handleClose}>
|
|
110
|
+
Cancel
|
|
111
|
+
</Button>
|
|
112
|
+
<Button
|
|
113
|
+
variant={liveKitConnection ? 'danger' : 'primary'}
|
|
114
|
+
onClick={handleConnect}
|
|
115
|
+
>
|
|
116
|
+
{liveKitConnection ? 'Disconnect' : 'Connect'}
|
|
117
|
+
</Button>
|
|
118
|
+
</Modal.Footer>
|
|
119
|
+
</Modal>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatMarkdownFiles,
|
|
3
|
+
parseMarkdownFiles,
|
|
4
|
+
} from 'llm-code-format';
|
|
5
|
+
import {
|
|
6
|
+
FORMAT_INSTRUCTIONS,
|
|
7
|
+
mergeFileChanges,
|
|
8
|
+
} from 'editcodewithai';
|
|
9
|
+
import { VizFiles } from '@vizhub/viz-types';
|
|
10
|
+
import {
|
|
11
|
+
generateRunId,
|
|
12
|
+
vizFilesToFileCollection,
|
|
13
|
+
} from '@vizhub/viz-utils';
|
|
14
|
+
import JSZip from 'jszip';
|
|
15
|
+
import { generate } from '@langchain/core/dist/utils/fast-json-patch';
|
|
16
|
+
|
|
17
|
+
export const createAICopyPasteHandlers = (
|
|
18
|
+
files: VizFiles,
|
|
19
|
+
submitOperation: any,
|
|
20
|
+
setCopyButtonText: (text: string) => void,
|
|
21
|
+
setPasteButtonText: (text: string) => void,
|
|
22
|
+
setExportButtonText: (text: string) => void,
|
|
23
|
+
) => {
|
|
24
|
+
// Copy for AI - formats all files and copies to clipboard
|
|
25
|
+
const handleCopyForAI = async () => {
|
|
26
|
+
if (!files || Object.keys(files).length === 0) {
|
|
27
|
+
setCopyButtonText('No files to copy');
|
|
28
|
+
setTimeout(
|
|
29
|
+
() => setCopyButtonText('Copy for AI'),
|
|
30
|
+
2000,
|
|
31
|
+
);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const fileCollection =
|
|
37
|
+
vizFilesToFileCollection(files);
|
|
38
|
+
|
|
39
|
+
// Format files for AI consumption
|
|
40
|
+
const formattedFiles =
|
|
41
|
+
formatMarkdownFiles(fileCollection) +
|
|
42
|
+
'\n\n' +
|
|
43
|
+
FORMAT_INSTRUCTIONS.whole;
|
|
44
|
+
|
|
45
|
+
// Copy to clipboard
|
|
46
|
+
await navigator.clipboard.writeText(formattedFiles);
|
|
47
|
+
|
|
48
|
+
// Show success feedback
|
|
49
|
+
setCopyButtonText('Copied!');
|
|
50
|
+
setTimeout(
|
|
51
|
+
() => setCopyButtonText('Copy for AI'),
|
|
52
|
+
2000,
|
|
53
|
+
);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('Failed to copy files for AI:', error);
|
|
56
|
+
setCopyButtonText('Error');
|
|
57
|
+
setTimeout(
|
|
58
|
+
() => setCopyButtonText('Copy for AI'),
|
|
59
|
+
2000,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Paste for AI - parses clipboard content and updates files
|
|
65
|
+
const handlePasteForAI = async () => {
|
|
66
|
+
if (!submitOperation) return;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// Read from clipboard
|
|
70
|
+
const clipboardText =
|
|
71
|
+
await navigator.clipboard.readText();
|
|
72
|
+
|
|
73
|
+
// Handle various line endings
|
|
74
|
+
const normalized = clipboardText.replace(
|
|
75
|
+
/\r\n?/g,
|
|
76
|
+
'\n',
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (!normalized.trim()) {
|
|
80
|
+
setPasteButtonText('Empty clipboard');
|
|
81
|
+
setTimeout(
|
|
82
|
+
() => setPasteButtonText('Paste for AI'),
|
|
83
|
+
2000,
|
|
84
|
+
);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Preprocess to remove formatting instructions section to avoid creating extra files
|
|
89
|
+
const preprocessed = normalized
|
|
90
|
+
.split('## Formatting Instructions')[0]
|
|
91
|
+
.trim();
|
|
92
|
+
|
|
93
|
+
// Parse the markdown files format
|
|
94
|
+
const parsed = parseMarkdownFiles(
|
|
95
|
+
preprocessed,
|
|
96
|
+
'bold',
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
parsed.files &&
|
|
101
|
+
Object.keys(parsed.files).length > 0
|
|
102
|
+
) {
|
|
103
|
+
// Use mergeFileChanges utility from editcodewithai
|
|
104
|
+
const mergedFiles = mergeFileChanges(
|
|
105
|
+
files || {},
|
|
106
|
+
parsed.files,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Update files using the submit operation
|
|
110
|
+
submitOperation((document) => ({
|
|
111
|
+
...document,
|
|
112
|
+
files: mergedFiles,
|
|
113
|
+
runId: generateRunId(),
|
|
114
|
+
}));
|
|
115
|
+
|
|
116
|
+
const fileCount = Object.keys(parsed.files).length;
|
|
117
|
+
setPasteButtonText('Pasted!');
|
|
118
|
+
setTimeout(
|
|
119
|
+
() => setPasteButtonText('Paste for AI'),
|
|
120
|
+
2000,
|
|
121
|
+
);
|
|
122
|
+
} else {
|
|
123
|
+
setPasteButtonText('No files found');
|
|
124
|
+
setTimeout(
|
|
125
|
+
() => setPasteButtonText('Paste for AI'),
|
|
126
|
+
2000,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error(
|
|
131
|
+
'Failed to paste files from AI:',
|
|
132
|
+
error,
|
|
133
|
+
);
|
|
134
|
+
setPasteButtonText('Error');
|
|
135
|
+
setTimeout(
|
|
136
|
+
() => setPasteButtonText('Paste for AI'),
|
|
137
|
+
2000,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Export to ZIP - creates and downloads a ZIP file with all viz files
|
|
143
|
+
const handleExportToZip = async () => {
|
|
144
|
+
if (!files || Object.keys(files).length === 0) {
|
|
145
|
+
setExportButtonText('No files to export');
|
|
146
|
+
setTimeout(
|
|
147
|
+
() => setExportButtonText('Export to ZIP'),
|
|
148
|
+
2000,
|
|
149
|
+
);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
// Create the ZIP
|
|
155
|
+
const zip = new JSZip();
|
|
156
|
+
|
|
157
|
+
// Add each file to the ZIP
|
|
158
|
+
Object.entries(files).forEach(([fileId, file]) => {
|
|
159
|
+
const fileName = file.name || fileId;
|
|
160
|
+
const fileContent = file.text || '';
|
|
161
|
+
zip.file(fileName, fileContent);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Generate and download
|
|
165
|
+
const blob = await zip.generateAsync({
|
|
166
|
+
type: 'blob',
|
|
167
|
+
});
|
|
168
|
+
const url = URL.createObjectURL(blob);
|
|
169
|
+
const a = document.createElement('a');
|
|
170
|
+
a.href = url;
|
|
171
|
+
a.download = 'viz-files.zip';
|
|
172
|
+
a.click();
|
|
173
|
+
URL.revokeObjectURL(url);
|
|
174
|
+
|
|
175
|
+
// Show success feedback
|
|
176
|
+
setExportButtonText('Exported!');
|
|
177
|
+
setTimeout(
|
|
178
|
+
() => setExportButtonText('Export to ZIP'),
|
|
179
|
+
2000,
|
|
180
|
+
);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error(
|
|
183
|
+
'Failed to export files to ZIP:',
|
|
184
|
+
error,
|
|
185
|
+
);
|
|
186
|
+
setExportButtonText('Error');
|
|
187
|
+
setTimeout(
|
|
188
|
+
() => setExportButtonText('Export to ZIP'),
|
|
189
|
+
2000,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
handleCopyForAI,
|
|
196
|
+
handlePasteForAI,
|
|
197
|
+
handleExportToZip,
|
|
198
|
+
};
|
|
199
|
+
};
|