vzcode 1.57.0 → 1.58.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-DqIc-0Gp.js → index-CtGus8Qk.js} +124 -124
- package/dist/index.html +1 -1
- package/package.json +3 -3
- package/src/client/CodeEditor/getOrCreateEditor.tsx +7 -7
- package/src/client/CodeEditor/index.tsx +2 -0
- package/src/client/VZCodeContext.tsx +145 -0
- package/src/client/VZSidebar/AIChat/Message.tsx +20 -4
- package/src/client/VZSidebar/AIChat/index.tsx +12 -125
- package/src/server/aiChatHandler/chatOperations.ts +4 -2
- package/src/server/aiChatHandler/index.ts +9 -1
- package/src/server/aiChatHandler/llmStreaming.ts +23 -10
package/dist/index.html
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
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-
|
|
23
|
+
<script type="module" crossorigin src="/assets/index-CtGus8Qk.js"></script>
|
|
24
24
|
<link rel="stylesheet" crossorigin href="/assets/index-CE_xP06t.css">
|
|
25
25
|
</head>
|
|
26
26
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vzcode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.58.0",
|
|
4
4
|
"description": "Multiplayer code editor system",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
13
|
"test": "vitest run",
|
|
14
|
-
"test-interactive": "cd test/sampleDirectories/kitchenSink && node --import
|
|
14
|
+
"test-interactive": "cd test/sampleDirectories/kitchenSink && node --import \"data:text/javascript,import { register } from 'node:module'; import { pathToFileURL } from 'node:url'; register('ts-node/esm', pathToFileURL('./'));\" ../../../src/server/index.ts",
|
|
15
15
|
"prettier": "prettier {*.*,**/*.*} --write",
|
|
16
16
|
"typecheck": "tsc --noEmit",
|
|
17
17
|
"dev": "cross-env EDITOR_PORT=5173 concurrently \"npm run test-interactive\" \"vite\"",
|
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
"globals": "^16.3.0",
|
|
187
187
|
"npm-check-updates": "^18.0.2",
|
|
188
188
|
"prettier": "^3.6.2",
|
|
189
|
-
"sass": "^1.
|
|
189
|
+
"sass": "^1.90.0",
|
|
190
190
|
"ts-node": "^10.9.2",
|
|
191
191
|
"typescript": "^5.9.2",
|
|
192
192
|
"vite": "^7.0.6",
|
|
@@ -74,6 +74,7 @@ import { getFileExtension } from '../utils/fileExtension';
|
|
|
74
74
|
import { SparklesSVG } from '../Icons/SparklesSVG';
|
|
75
75
|
import { VZCodeContext } from '../VZCodeContext';
|
|
76
76
|
import { useContext, useMemo } from 'react';
|
|
77
|
+
import { handleAIChatMessage } from '../../server/aiChatHandler';
|
|
77
78
|
|
|
78
79
|
const DEBUG = false;
|
|
79
80
|
|
|
@@ -194,6 +195,7 @@ export const getOrCreateEditor = async ({
|
|
|
194
195
|
rainbowBracketsEnabled = true,
|
|
195
196
|
setIsAIChatOpen,
|
|
196
197
|
setAIChatMessage,
|
|
198
|
+
handleSendMessage,
|
|
197
199
|
}: {
|
|
198
200
|
// TODO pass this in from the outside
|
|
199
201
|
paneId?: PaneId;
|
|
@@ -229,8 +231,9 @@ export const getOrCreateEditor = async ({
|
|
|
229
231
|
view: EditorView,
|
|
230
232
|
) => Promise<readonly Diagnostic[]>;
|
|
231
233
|
rainbowBracketsEnabled?: boolean; // New parameter type
|
|
232
|
-
setIsAIChatOpen:
|
|
233
|
-
setAIChatMessage:
|
|
234
|
+
setIsAIChatOpen: (isAIChatOpen: boolean) => void;
|
|
235
|
+
setAIChatMessage: (message: string) => void;
|
|
236
|
+
handleSendMessage: any; // TODO fix types
|
|
234
237
|
}): Promise<ExtendedEditorCacheValue> => {
|
|
235
238
|
// Cache hit
|
|
236
239
|
|
|
@@ -500,12 +503,9 @@ export const getOrCreateEditor = async ({
|
|
|
500
503
|
root.render(
|
|
501
504
|
<div
|
|
502
505
|
onClick={() => {
|
|
503
|
-
console.log(
|
|
504
|
-
'TODO set the chat prompt to "Implement the TODO"',
|
|
505
|
-
);
|
|
506
506
|
setIsAIChatOpen(true);
|
|
507
|
-
setAIChatMessage('Implement the TODO');
|
|
508
|
-
|
|
507
|
+
// setAIChatMessage('Implement the TODO');
|
|
508
|
+
handleSendMessage('Implement the TODO');
|
|
509
509
|
}}
|
|
510
510
|
>
|
|
511
511
|
<SparklesSVG width={14} height={14} />
|
|
@@ -45,6 +45,7 @@ export const CodeEditor = ({
|
|
|
45
45
|
enableAutoFollow,
|
|
46
46
|
setIsAIChatOpen,
|
|
47
47
|
setAIChatMessage,
|
|
48
|
+
handleSendMessage,
|
|
48
49
|
} = useContext(VZCodeContext);
|
|
49
50
|
|
|
50
51
|
// Set `doc.data.isInteracting` to `true` when the user is interacting
|
|
@@ -116,6 +117,7 @@ export const CodeEditor = ({
|
|
|
116
117
|
esLintSource,
|
|
117
118
|
setIsAIChatOpen,
|
|
118
119
|
setAIChatMessage,
|
|
120
|
+
handleSendMessage,
|
|
119
121
|
});
|
|
120
122
|
|
|
121
123
|
if (isMounted) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Context,
|
|
2
3
|
createContext,
|
|
3
4
|
useCallback,
|
|
4
5
|
useReducer,
|
|
@@ -39,6 +40,7 @@ import { useURLSync } from './useURLSync';
|
|
|
39
40
|
import { createInitialState, vzReducer } from './vzReducer';
|
|
40
41
|
import { findPane } from './vzReducer/findPane';
|
|
41
42
|
import { usePresenceAutoFollow } from './usePresenceAutoFollow';
|
|
43
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
42
44
|
|
|
43
45
|
// This context centralizes all the "smart" logic
|
|
44
46
|
// to do with the application state. This includes
|
|
@@ -195,6 +197,12 @@ export type VZCodeContextValue = {
|
|
|
195
197
|
setVoiceChatModalOpen: (state: boolean) => void;
|
|
196
198
|
aiChatMessage: string;
|
|
197
199
|
setAIChatMessage: (message: string) => void;
|
|
200
|
+
isLoading: boolean;
|
|
201
|
+
setIsLoading: (state: boolean) => void;
|
|
202
|
+
currentChatId: string;
|
|
203
|
+
aiErrorMessage: string | null;
|
|
204
|
+
setAIErrorMessage: (state: string | null) => void;
|
|
205
|
+
handleSendMessage: (messageToSend?: string) => void;
|
|
198
206
|
|
|
199
207
|
// Auto-fork functions for VizHub integration
|
|
200
208
|
autoForkAndRetryAI?: (
|
|
@@ -207,6 +215,13 @@ export type VZCodeContextValue = {
|
|
|
207
215
|
prompt: string;
|
|
208
216
|
modelName: string;
|
|
209
217
|
} | null;
|
|
218
|
+
|
|
219
|
+
// Additional widgets that can be rendered in AI chat messages
|
|
220
|
+
additionalWidgets?: React.ComponentType<{
|
|
221
|
+
messageId: string;
|
|
222
|
+
chatId: string;
|
|
223
|
+
canUndo: boolean;
|
|
224
|
+
}>;
|
|
210
225
|
};
|
|
211
226
|
|
|
212
227
|
export const VZCodeProvider = ({
|
|
@@ -233,6 +248,7 @@ export const VZCodeProvider = ({
|
|
|
233
248
|
autoForkAndRetryAI,
|
|
234
249
|
clearStoredAIPrompt,
|
|
235
250
|
getStoredAIPrompt,
|
|
251
|
+
additionalWidgets,
|
|
236
252
|
}: {
|
|
237
253
|
content: VizContent;
|
|
238
254
|
shareDBDoc: ShareDBDoc<VizContent>;
|
|
@@ -264,6 +280,11 @@ export const VZCodeProvider = ({
|
|
|
264
280
|
prompt: string;
|
|
265
281
|
modelName: string;
|
|
266
282
|
} | null;
|
|
283
|
+
additionalWidgets?: React.ComponentType<{
|
|
284
|
+
messageId: string;
|
|
285
|
+
chatId: string;
|
|
286
|
+
canUndo: boolean;
|
|
287
|
+
}>;
|
|
267
288
|
}) => {
|
|
268
289
|
// Auto-run Pretter after local changes.
|
|
269
290
|
const { prettierError, runPrettierRef } = usePrettier({
|
|
@@ -324,6 +345,7 @@ export const VZCodeProvider = ({
|
|
|
324
345
|
username,
|
|
325
346
|
enableAutoFollow,
|
|
326
347
|
sidebarPresenceIndicators,
|
|
348
|
+
aiChatMode,
|
|
327
349
|
} = state;
|
|
328
350
|
|
|
329
351
|
const activePane: Pane = findPane(pane, activePaneId);
|
|
@@ -479,6 +501,120 @@ export const VZCodeProvider = ({
|
|
|
479
501
|
|
|
480
502
|
const [aiChatMessage, setAIChatMessage] = useState('');
|
|
481
503
|
|
|
504
|
+
const DEBUG = false;
|
|
505
|
+
|
|
506
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
507
|
+
const [currentChatId] = useState(() => uuidv4());
|
|
508
|
+
const [aiErrorMessage, setAIErrorMessage] = useState<
|
|
509
|
+
string | null
|
|
510
|
+
>(null);
|
|
511
|
+
|
|
512
|
+
const handleSendMessage = useCallback(
|
|
513
|
+
async (messageToSend?: string) => {
|
|
514
|
+
DEBUG &&
|
|
515
|
+
console.log(
|
|
516
|
+
'AIChat: handleSendMessage called with:',
|
|
517
|
+
messageToSend,
|
|
518
|
+
'aiChatMessage:',
|
|
519
|
+
aiChatMessage,
|
|
520
|
+
);
|
|
521
|
+
const messageContent = messageToSend || aiChatMessage;
|
|
522
|
+
|
|
523
|
+
if (
|
|
524
|
+
!messageContent ||
|
|
525
|
+
typeof messageContent !== 'string' ||
|
|
526
|
+
!messageContent.trim() ||
|
|
527
|
+
isLoading
|
|
528
|
+
) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const currentPrompt = aiChatMessage.trim();
|
|
533
|
+
setAIChatMessage('');
|
|
534
|
+
setIsLoading(true);
|
|
535
|
+
setAIErrorMessage(null); // Clear any previous errors
|
|
536
|
+
|
|
537
|
+
// Call backend endpoint for AI response
|
|
538
|
+
// The server will handle all ShareDB operations including adding the user message
|
|
539
|
+
try {
|
|
540
|
+
const response = await fetch(aiChatEndpoint, {
|
|
541
|
+
method: 'POST',
|
|
542
|
+
headers: {
|
|
543
|
+
'Content-Type': 'application/json',
|
|
544
|
+
},
|
|
545
|
+
body: JSON.stringify({
|
|
546
|
+
...aiChatOptions,
|
|
547
|
+
vizId: aiChatOptions.vizId,
|
|
548
|
+
content: messageContent.trim(),
|
|
549
|
+
chatId: currentChatId,
|
|
550
|
+
mode: aiChatMode,
|
|
551
|
+
}),
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
if (!response.ok) {
|
|
555
|
+
throw new Error(
|
|
556
|
+
`HTTP error! status: ${response.status}`,
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Parse the response to check for errors
|
|
561
|
+
const responseData = await response.json();
|
|
562
|
+
|
|
563
|
+
// Check if the response contains a VizHub error
|
|
564
|
+
if (
|
|
565
|
+
responseData.outcome === 'failure' &&
|
|
566
|
+
responseData.error
|
|
567
|
+
) {
|
|
568
|
+
const errorMessage = responseData.error.message;
|
|
569
|
+
|
|
570
|
+
// Check if this is the specific permission error that should trigger auto-fork
|
|
571
|
+
if (
|
|
572
|
+
aiErrorMessage ===
|
|
573
|
+
'You do not have permission to use AI chat on this visualization. Only users with edit access can use this feature. Fork the viz to edit it.'
|
|
574
|
+
) {
|
|
575
|
+
// Trigger auto-fork instead of showing error
|
|
576
|
+
try {
|
|
577
|
+
await autoForkAndRetryAI?.(
|
|
578
|
+
currentPrompt,
|
|
579
|
+
aiChatMode,
|
|
580
|
+
);
|
|
581
|
+
// If we reach here, the fork was successful and redirect should happen
|
|
582
|
+
return;
|
|
583
|
+
} catch (forkError) {
|
|
584
|
+
console.error('Auto-fork failed:', forkError);
|
|
585
|
+
setAIErrorMessage(
|
|
586
|
+
'Failed to fork visualization. Please try forking manually.',
|
|
587
|
+
);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// For other errors, show the error message
|
|
593
|
+
setAIErrorMessage(aiErrorMessage);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// The backend handles all ShareDB operations for successful responses
|
|
598
|
+
} catch (error) {
|
|
599
|
+
console.error('Error getting AI response:', error);
|
|
600
|
+
setAIErrorMessage(
|
|
601
|
+
'Failed to send message. Please try again.',
|
|
602
|
+
);
|
|
603
|
+
} finally {
|
|
604
|
+
setIsLoading(false);
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
[
|
|
608
|
+
aiChatMessage,
|
|
609
|
+
isLoading,
|
|
610
|
+
aiChatEndpoint,
|
|
611
|
+
aiChatOptions,
|
|
612
|
+
currentChatId,
|
|
613
|
+
aiChatMode,
|
|
614
|
+
autoForkAndRetryAI,
|
|
615
|
+
],
|
|
616
|
+
);
|
|
617
|
+
|
|
482
618
|
// The value provided by this context.
|
|
483
619
|
const value: VZCodeContextValue = {
|
|
484
620
|
content,
|
|
@@ -589,11 +725,20 @@ export const VZCodeProvider = ({
|
|
|
589
725
|
|
|
590
726
|
aiChatMessage,
|
|
591
727
|
setAIChatMessage,
|
|
728
|
+
isLoading,
|
|
729
|
+
setIsLoading,
|
|
730
|
+
currentChatId,
|
|
731
|
+
aiErrorMessage,
|
|
732
|
+
setAIErrorMessage,
|
|
733
|
+
handleSendMessage,
|
|
592
734
|
|
|
593
735
|
// Auto-fork functions for VizHub integration
|
|
594
736
|
autoForkAndRetryAI,
|
|
595
737
|
clearStoredAIPrompt,
|
|
596
738
|
getStoredAIPrompt,
|
|
739
|
+
|
|
740
|
+
// Additional widgets that can be rendered in AI chat messages
|
|
741
|
+
additionalWidgets,
|
|
597
742
|
};
|
|
598
743
|
|
|
599
744
|
return (
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { timestampToDate } from '@vizhub/viz-utils';
|
|
2
2
|
import Markdown from 'react-markdown';
|
|
3
3
|
import remarkGfm from 'remark-gfm';
|
|
4
|
-
import
|
|
4
|
+
import React, {
|
|
5
|
+
useMemo,
|
|
6
|
+
memo,
|
|
7
|
+
useState,
|
|
8
|
+
useContext,
|
|
9
|
+
} from 'react';
|
|
5
10
|
import { DiffView } from './DiffView';
|
|
6
11
|
import { UnifiedFilesDiff } from '../../../utils/fileDiff';
|
|
7
12
|
import { enableDiffView } from '../../featureFlags';
|
|
@@ -31,8 +36,11 @@ const MessageComponent = ({
|
|
|
31
36
|
canUndo,
|
|
32
37
|
}: MessageProps) => {
|
|
33
38
|
const [isUndoing, setIsUndoing] = useState(false);
|
|
34
|
-
const {
|
|
35
|
-
|
|
39
|
+
const {
|
|
40
|
+
aiChatUndoEndpoint,
|
|
41
|
+
aiChatOptions = {},
|
|
42
|
+
additionalWidgets,
|
|
43
|
+
} = useContext(VZCodeContext);
|
|
36
44
|
|
|
37
45
|
// Memoize date formatting to avoid repeated computation
|
|
38
46
|
const formattedTime = useMemo(() => {
|
|
@@ -102,7 +110,15 @@ const MessageComponent = ({
|
|
|
102
110
|
Object.keys(diffData).length > 0 && (
|
|
103
111
|
<DiffView diffData={diffData} />
|
|
104
112
|
)}
|
|
105
|
-
{
|
|
113
|
+
{additionalWidgets &&
|
|
114
|
+
canUndo &&
|
|
115
|
+
chatId &&
|
|
116
|
+
React.createElement(additionalWidgets, {
|
|
117
|
+
messageId: id,
|
|
118
|
+
chatId: chatId,
|
|
119
|
+
canUndo: canUndo,
|
|
120
|
+
})}
|
|
121
|
+
{!additionalWidgets && canUndo && beforeFiles && (
|
|
106
122
|
<div className="undo-button-container">
|
|
107
123
|
<button
|
|
108
124
|
className="undo-button"
|
|
@@ -15,28 +15,27 @@ const defaultAIChatEndpoint = '/api/ai-chat/';
|
|
|
15
15
|
|
|
16
16
|
const DEBUG = false;
|
|
17
17
|
|
|
18
|
-
const showSuggestedRequests =
|
|
18
|
+
const showSuggestedRequests = true;
|
|
19
19
|
|
|
20
20
|
export const AIChat = () => {
|
|
21
|
-
const { aiChatMessage, setAIChatMessage } =
|
|
22
|
-
useContext(VZCodeContext);
|
|
23
|
-
|
|
24
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
25
|
-
const [currentChatId] = useState(() => uuidv4());
|
|
26
|
-
const [errorMessage, setErrorMessage] = useState<
|
|
27
|
-
string | null
|
|
28
|
-
>(null);
|
|
29
|
-
|
|
30
21
|
const {
|
|
31
22
|
aiChatFocused,
|
|
32
23
|
content,
|
|
33
24
|
aiChatEndpoint = defaultAIChatEndpoint,
|
|
34
25
|
aiChatOptions = {},
|
|
35
26
|
aiChatMode,
|
|
27
|
+
aiChatMessage,
|
|
28
|
+
isLoading,
|
|
29
|
+
currentChatId,
|
|
30
|
+
aiErrorMessage,
|
|
36
31
|
setAIChatMode,
|
|
37
32
|
autoForkAndRetryAI,
|
|
38
33
|
clearStoredAIPrompt,
|
|
39
34
|
getStoredAIPrompt,
|
|
35
|
+
setAIChatMessage,
|
|
36
|
+
handleSendMessage,
|
|
37
|
+
setIsLoading,
|
|
38
|
+
setAIErrorMessage,
|
|
40
39
|
} = useContext(VZCodeContext);
|
|
41
40
|
|
|
42
41
|
// Get current chat data from content
|
|
@@ -58,118 +57,6 @@ export const AIChat = () => {
|
|
|
58
57
|
// Check if this is the first time opening the chat (no messages)
|
|
59
58
|
const isEmptyState = rawMessages.length === 0;
|
|
60
59
|
|
|
61
|
-
const handleSendMessage = useCallback(
|
|
62
|
-
async (messageToSend?: string) => {
|
|
63
|
-
DEBUG &&
|
|
64
|
-
console.log(
|
|
65
|
-
'AIChat: handleSendMessage called with:',
|
|
66
|
-
messageToSend,
|
|
67
|
-
'aiChatMessage:',
|
|
68
|
-
aiChatMessage,
|
|
69
|
-
);
|
|
70
|
-
const messageContent = messageToSend || aiChatMessage;
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
!messageContent ||
|
|
74
|
-
typeof messageContent !== 'string' ||
|
|
75
|
-
!messageContent.trim() ||
|
|
76
|
-
isLoading
|
|
77
|
-
) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const currentPrompt = aiChatMessage.trim();
|
|
82
|
-
setAIChatMessage('');
|
|
83
|
-
setIsLoading(true);
|
|
84
|
-
setErrorMessage(null); // Clear any previous errors
|
|
85
|
-
|
|
86
|
-
// Call backend endpoint for AI response
|
|
87
|
-
// The server will handle all ShareDB operations including adding the user message
|
|
88
|
-
try {
|
|
89
|
-
const response = await fetch(aiChatEndpoint, {
|
|
90
|
-
method: 'POST',
|
|
91
|
-
headers: {
|
|
92
|
-
'Content-Type': 'application/json',
|
|
93
|
-
},
|
|
94
|
-
body: JSON.stringify({
|
|
95
|
-
...aiChatOptions,
|
|
96
|
-
vizId: aiChatOptions.vizId,
|
|
97
|
-
content: messageContent.trim(),
|
|
98
|
-
chatId: currentChatId,
|
|
99
|
-
mode: aiChatMode,
|
|
100
|
-
}),
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
if (!response.ok) {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`HTTP error! status: ${response.status}`,
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Parse the response to check for errors
|
|
110
|
-
const responseData = await response.json();
|
|
111
|
-
|
|
112
|
-
// Check if the response contains a VizHub error
|
|
113
|
-
if (
|
|
114
|
-
responseData.outcome === 'failure' &&
|
|
115
|
-
responseData.error
|
|
116
|
-
) {
|
|
117
|
-
const errorMessage = responseData.error.message;
|
|
118
|
-
|
|
119
|
-
// Check if this is the specific permission error that should trigger auto-fork
|
|
120
|
-
if (
|
|
121
|
-
errorMessage ===
|
|
122
|
-
'You do not have permission to use AI chat on this visualization. Only users with edit access can use this feature. Fork the viz to edit it.'
|
|
123
|
-
) {
|
|
124
|
-
// Trigger auto-fork instead of showing error
|
|
125
|
-
try {
|
|
126
|
-
await autoForkAndRetryAI?.(
|
|
127
|
-
currentPrompt,
|
|
128
|
-
aiChatMode,
|
|
129
|
-
);
|
|
130
|
-
// If we reach here, the fork was successful and redirect should happen
|
|
131
|
-
return;
|
|
132
|
-
} catch (forkError) {
|
|
133
|
-
console.error('Auto-fork failed:', forkError);
|
|
134
|
-
setErrorMessage(
|
|
135
|
-
'Failed to fork visualization. Please try forking manually.',
|
|
136
|
-
);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// For other errors, show the error message
|
|
142
|
-
setErrorMessage(errorMessage);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// The backend handles all ShareDB operations for successful responses
|
|
147
|
-
} catch (error) {
|
|
148
|
-
console.error('Error getting AI response:', error);
|
|
149
|
-
// Fail silently in case of timeout,
|
|
150
|
-
// which happens frequently with the longer running LLM calls.
|
|
151
|
-
// TODO refactor this so that the network request to the server
|
|
152
|
-
// always returns immediately, and we track the `isLoading` state
|
|
153
|
-
// within the ShareDB document itself.
|
|
154
|
-
//
|
|
155
|
-
// setErrorMessage(
|
|
156
|
-
// 'Failed to send message. Please try again.',
|
|
157
|
-
// );
|
|
158
|
-
} finally {
|
|
159
|
-
setIsLoading(false);
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
[
|
|
163
|
-
aiChatMessage,
|
|
164
|
-
isLoading,
|
|
165
|
-
aiChatEndpoint,
|
|
166
|
-
aiChatOptions,
|
|
167
|
-
currentChatId,
|
|
168
|
-
aiChatMode,
|
|
169
|
-
autoForkAndRetryAI,
|
|
170
|
-
],
|
|
171
|
-
);
|
|
172
|
-
|
|
173
60
|
// Check for stored AI prompt on component mount (post-fork restoration)
|
|
174
61
|
useEffect(() => {
|
|
175
62
|
DEBUG &&
|
|
@@ -350,18 +237,18 @@ export const AIChat = () => {
|
|
|
350
237
|
aiScratchpad={aiScratchpad}
|
|
351
238
|
/>
|
|
352
239
|
)}
|
|
353
|
-
{
|
|
240
|
+
{aiErrorMessage && (
|
|
354
241
|
<div className="ai-chat-error">
|
|
355
242
|
<div className="ai-chat-error-content">
|
|
356
243
|
<span className="ai-chat-error-icon">
|
|
357
244
|
⚠️
|
|
358
245
|
</span>
|
|
359
246
|
<span className="ai-chat-error-message">
|
|
360
|
-
{
|
|
247
|
+
{aiErrorMessage}
|
|
361
248
|
</span>
|
|
362
249
|
<button
|
|
363
250
|
className="ai-chat-error-dismiss"
|
|
364
|
-
onClick={() =>
|
|
251
|
+
onClick={() => setAIErrorMessage(null)}
|
|
365
252
|
aria-label="Dismiss error"
|
|
366
253
|
>
|
|
367
254
|
×
|
|
@@ -256,7 +256,8 @@ export const addDiffToAIMessage = (
|
|
|
256
256
|
shareDBDoc: ShareDBDoc<VizContent>,
|
|
257
257
|
chatId: VizChatId,
|
|
258
258
|
diffData: any,
|
|
259
|
-
beforeFiles?: VizFiles, // Optional snapshot for undo functionality
|
|
259
|
+
beforeFiles?: VizFiles, // Optional snapshot for undo functionality (legacy)
|
|
260
|
+
beforeCommitId?: string, // Commit ID before AI changes for VizHub integration
|
|
260
261
|
) => {
|
|
261
262
|
const chat = shareDBDoc.data.chats[chatId];
|
|
262
263
|
const messages = [...chat.messages];
|
|
@@ -270,7 +271,8 @@ export const addDiffToAIMessage = (
|
|
|
270
271
|
const newMessage = {
|
|
271
272
|
...messages[lastAIMessageIndex],
|
|
272
273
|
diffData,
|
|
273
|
-
...(beforeFiles && { beforeFiles }), // Add beforeFiles only if provided
|
|
274
|
+
...(beforeFiles && { beforeFiles }), // Add beforeFiles only if provided (legacy)
|
|
275
|
+
...(beforeCommitId && { beforeCommitId }), // Add beforeCommitId for VizHub integration
|
|
274
276
|
};
|
|
275
277
|
// Use type assertion to extend the message with diffData
|
|
276
278
|
(messages[lastAIMessageIndex] as any) = newMessage;
|
|
@@ -23,10 +23,12 @@ export const handleAIChatMessage =
|
|
|
23
23
|
shareDBDoc,
|
|
24
24
|
createAIEditLocalPresence,
|
|
25
25
|
onCreditDeduction,
|
|
26
|
+
getCurrentCommitId,
|
|
26
27
|
}: {
|
|
27
28
|
shareDBDoc: ShareDBDoc<VizContent>;
|
|
28
29
|
createAIEditLocalPresence: () => any;
|
|
29
30
|
onCreditDeduction?: any;
|
|
31
|
+
getCurrentCommitId?: () => string | null;
|
|
30
32
|
}) =>
|
|
31
33
|
async (req: any, res: any) => {
|
|
32
34
|
const { content, chatId, mode = 'edit' } = req.body;
|
|
@@ -52,6 +54,11 @@ export const handleAIChatMessage =
|
|
|
52
54
|
ensureChatsExist(shareDBDoc);
|
|
53
55
|
ensureChatExists(shareDBDoc, chatId);
|
|
54
56
|
|
|
57
|
+
// Capture the current commit ID before making changes (for VizHub integration)
|
|
58
|
+
const beforeCommitId = getCurrentCommitId
|
|
59
|
+
? getCurrentCommitId()
|
|
60
|
+
: null;
|
|
61
|
+
|
|
55
62
|
// Add user message to chat
|
|
56
63
|
addUserMessage(shareDBDoc, chatId, content);
|
|
57
64
|
|
|
@@ -92,7 +99,8 @@ export const handleAIChatMessage =
|
|
|
92
99
|
shareDBDoc,
|
|
93
100
|
chatId,
|
|
94
101
|
editResult.diffData,
|
|
95
|
-
(editResult as any).beforeFiles, // Pass the beforeFiles snapshot for undo (
|
|
102
|
+
(editResult as any).beforeFiles, // Pass the beforeFiles snapshot for undo (legacy)
|
|
103
|
+
beforeCommitId, // Pass the commit ID before AI changes (for VizHub integration)
|
|
96
104
|
);
|
|
97
105
|
}
|
|
98
106
|
|
|
@@ -45,10 +45,15 @@ export const createLLMFunction = ({
|
|
|
45
45
|
shareDBDoc,
|
|
46
46
|
createAIEditLocalPresence,
|
|
47
47
|
chatId,
|
|
48
|
+
// Feature flag to enable/disable reasoning tokens.
|
|
49
|
+
// When false, reasoning tokens are not requested from the API
|
|
50
|
+
// and reasoning content is not processed in the streaming response.
|
|
51
|
+
enableReasoningTokens = false,
|
|
48
52
|
}: {
|
|
49
53
|
shareDBDoc: ShareDBDoc<VizContent>;
|
|
50
54
|
createAIEditLocalPresence: () => any;
|
|
51
55
|
chatId: VizChatId;
|
|
56
|
+
enableReasoningTokens?: boolean;
|
|
52
57
|
}) => {
|
|
53
58
|
return async (fullPrompt: string) => {
|
|
54
59
|
const localPresence = enableStreamingEditing
|
|
@@ -197,21 +202,29 @@ export const createLLMFunction = ({
|
|
|
197
202
|
const modelName =
|
|
198
203
|
process.env.VIZHUB_EDIT_WITH_AI_MODEL_NAME ||
|
|
199
204
|
'anthropic/claude-3.5-sonnet';
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
205
|
+
|
|
206
|
+
// Configure reasoning tokens based on enableReasoningTokens flag
|
|
207
|
+
const requestConfig: any = {
|
|
203
208
|
model: modelName,
|
|
204
209
|
messages: [{ role: 'user', content: fullPrompt }],
|
|
205
|
-
reasoning: {
|
|
206
|
-
effort: 'low',
|
|
207
|
-
exclude: false,
|
|
208
|
-
},
|
|
209
210
|
provider: {
|
|
210
211
|
sort: 'price',
|
|
211
212
|
},
|
|
212
213
|
usage: { include: true },
|
|
213
214
|
stream: true,
|
|
214
|
-
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Only include reasoning configuration if reasoning tokens are enabled
|
|
218
|
+
if (enableReasoningTokens) {
|
|
219
|
+
requestConfig.reasoning = {
|
|
220
|
+
effort: 'low',
|
|
221
|
+
exclude: false,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const stream = await (
|
|
226
|
+
openRouterClient.chat.completions.create as any
|
|
227
|
+
)(requestConfig);
|
|
215
228
|
|
|
216
229
|
let reasoningStarted = false;
|
|
217
230
|
let contentStarted = false;
|
|
@@ -219,8 +232,8 @@ export const createLLMFunction = ({
|
|
|
219
232
|
for await (const chunk of stream) {
|
|
220
233
|
const delta = chunk.choices[0]?.delta as any; // Type assertion for OpenRouter-specific reasoning fields
|
|
221
234
|
|
|
222
|
-
if (delta?.reasoning) {
|
|
223
|
-
// Handle reasoning tokens (thinking)
|
|
235
|
+
if (delta?.reasoning && enableReasoningTokens) {
|
|
236
|
+
// Handle reasoning tokens (thinking) - only if enabled
|
|
224
237
|
if (!reasoningStarted) {
|
|
225
238
|
reasoningStarted = true;
|
|
226
239
|
updateAIStatus(shareDBDoc, chatId, 'Thinking...');
|