vzcode 1.56.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-COSAnnol.css → index-CE_xP06t.css} +1 -1
- package/dist/assets/{index-Cp6YlJo2.js → index-CtGus8Qk.js} +124 -123
- package/dist/index.html +2 -2
- 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/ChatInput.tsx +20 -21
- package/src/client/VZSidebar/AIChat/DiffView.scss +8 -0
- package/src/client/VZSidebar/AIChat/DiffView.tsx +105 -1
- package/src/client/VZSidebar/AIChat/Message.tsx +20 -4
- package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +1 -1
- package/src/client/VZSidebar/AIChat/index.tsx +158 -266
- package/src/client/VZSidebar/AIChat/styles.scss +52 -11
- package/src/server/aiChatHandler/chatOperations.ts +4 -2
- package/src/server/aiChatHandler/index.ts +9 -1
- package/src/server/aiChatHandler/llmStreaming.ts +24 -11
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-
|
|
24
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
23
|
+
<script type="module" crossorigin src="/assets/index-CtGus8Qk.js"></script>
|
|
24
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CE_xP06t.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.
|
|
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 (
|
|
@@ -112,7 +112,7 @@ const ChatInputComponent = ({
|
|
|
112
112
|
<Form.Group className="ai-chat-input-group">
|
|
113
113
|
<Form.Control
|
|
114
114
|
as="textarea"
|
|
115
|
-
rows={
|
|
115
|
+
rows={5}
|
|
116
116
|
value={aiChatMessage}
|
|
117
117
|
onChange={handleChange}
|
|
118
118
|
onKeyDown={handleKeyDown}
|
|
@@ -126,26 +126,25 @@ const ChatInputComponent = ({
|
|
|
126
126
|
disabled={isLoading}
|
|
127
127
|
aria-label="Chat message input"
|
|
128
128
|
/>
|
|
129
|
-
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
</Button>
|
|
129
|
+
<div className="ai-chat-input-footer">
|
|
130
|
+
<span className="ai-chat-hint">
|
|
131
|
+
{aiChatMessage ? 'Press Enter to send' : ''}
|
|
132
|
+
</span>
|
|
133
|
+
<Button
|
|
134
|
+
variant={
|
|
135
|
+
aiChatMessage.trim()
|
|
136
|
+
? 'primary'
|
|
137
|
+
: 'outline-secondary'
|
|
138
|
+
}
|
|
139
|
+
onClick={handleSendClick}
|
|
140
|
+
disabled={!aiChatMessage.trim() || isLoading}
|
|
141
|
+
className="ai-chat-send-button"
|
|
142
|
+
aria-label="Send message"
|
|
143
|
+
title="Send message (Enter)"
|
|
144
|
+
>
|
|
145
|
+
Send
|
|
146
|
+
</Button>
|
|
147
|
+
</div>
|
|
149
148
|
</Form.Group>
|
|
150
149
|
</div>
|
|
151
150
|
);
|
|
@@ -119,6 +119,14 @@
|
|
|
119
119
|
border-color: var(--d2h-ins-border-color) !important;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// Style clickable file names
|
|
123
|
+
.d2h-file-name {
|
|
124
|
+
&:hover {
|
|
125
|
+
text-decoration: underline !important;
|
|
126
|
+
opacity: 0.8;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
122
130
|
// Fix wrapper border
|
|
123
131
|
.d2h-file-wrapper {
|
|
124
132
|
border: 1px solid var(--d2h-border-color) !important;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React, {
|
|
2
|
+
useContext,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
} from 'react';
|
|
2
7
|
import {
|
|
3
8
|
UnifiedFilesDiff,
|
|
4
9
|
parseUnifiedDiffStats,
|
|
@@ -7,6 +12,8 @@ import {
|
|
|
7
12
|
import * as Diff2Html from 'diff2html';
|
|
8
13
|
import 'diff2html/bundles/css/diff2html.min.css';
|
|
9
14
|
import './DiffView.scss';
|
|
15
|
+
import { VZCodeContext } from '../../VZCodeContext';
|
|
16
|
+
import { VizFileId, VizFiles } from '@vizhub/viz-types';
|
|
10
17
|
|
|
11
18
|
interface DiffViewProps {
|
|
12
19
|
diffData: UnifiedFilesDiff;
|
|
@@ -15,6 +22,10 @@ interface DiffViewProps {
|
|
|
15
22
|
export const DiffView: React.FC<DiffViewProps> = ({
|
|
16
23
|
diffData,
|
|
17
24
|
}) => {
|
|
25
|
+
const { content, openTab, setIsAIChatOpen } =
|
|
26
|
+
useContext(VZCodeContext);
|
|
27
|
+
const diffContainerRef = useRef<HTMLDivElement>(null);
|
|
28
|
+
|
|
18
29
|
const unifiedDiffs = Object.values(diffData).filter(
|
|
19
30
|
(diff) => diff.length > 0,
|
|
20
31
|
);
|
|
@@ -42,6 +53,98 @@ export const DiffView: React.FC<DiffViewProps> = ({
|
|
|
42
53
|
outputFormat: 'line-by-line',
|
|
43
54
|
});
|
|
44
55
|
|
|
56
|
+
// Create a mapping from file name to file ID for click handling
|
|
57
|
+
const fileNameToIdMap = useMemo(() => {
|
|
58
|
+
const map = new Map<string, VizFileId>();
|
|
59
|
+
Object.entries(diffData).forEach(([fileId, diff]) => {
|
|
60
|
+
// Extract file name from the unified diff
|
|
61
|
+
// The diff contains lines like "--- a/filename" and "+++ b/filename"
|
|
62
|
+
const lines = diff.split('\n');
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
if (
|
|
65
|
+
line.startsWith('--- a/') ||
|
|
66
|
+
line.startsWith('+++ b/')
|
|
67
|
+
) {
|
|
68
|
+
const fileName = line.substring(6); // Remove "--- a/" or "+++ b/"
|
|
69
|
+
map.set(fileName, fileId as VizFileId);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return map;
|
|
75
|
+
}, [diffData]);
|
|
76
|
+
|
|
77
|
+
// Add click handlers to file names after HTML is rendered
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!diffContainerRef.current) return;
|
|
80
|
+
|
|
81
|
+
const handleFileNameClick = (event: MouseEvent) => {
|
|
82
|
+
const target = event.target as HTMLElement;
|
|
83
|
+
if (target.classList.contains('d2h-file-name')) {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
event.stopPropagation();
|
|
86
|
+
|
|
87
|
+
console.log('File name clicked');
|
|
88
|
+
|
|
89
|
+
const fileName = target.textContent?.trim();
|
|
90
|
+
|
|
91
|
+
console.log('Clicked file name:', fileName);
|
|
92
|
+
if (fileName) {
|
|
93
|
+
const files: VizFiles = content?.files;
|
|
94
|
+
if (files) {
|
|
95
|
+
// export type VizFiles = {
|
|
96
|
+
// [fileId: VizFileId]: VizFile;
|
|
97
|
+
// };
|
|
98
|
+
// export type VizFileId = string;
|
|
99
|
+
// export type VizFile = {
|
|
100
|
+
// name: string;
|
|
101
|
+
// text: string;
|
|
102
|
+
// };
|
|
103
|
+
|
|
104
|
+
// TODO get fileId from content.files
|
|
105
|
+
const fileId = Object.entries(files).find(
|
|
106
|
+
([id, file]) => file.name === fileName,
|
|
107
|
+
)?.[0];
|
|
108
|
+
console.log('Mapped file ID:', fileId);
|
|
109
|
+
|
|
110
|
+
if (fileId) {
|
|
111
|
+
// Open the file tab and switch to files view
|
|
112
|
+
openTab({ fileId, isTransient: false });
|
|
113
|
+
setIsAIChatOpen(false);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const container = diffContainerRef.current;
|
|
121
|
+
const fileNameElements = container.querySelectorAll(
|
|
122
|
+
'.d2h-file-name',
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Add click event listeners and cursor pointer style
|
|
126
|
+
fileNameElements.forEach((element) => {
|
|
127
|
+
element.addEventListener(
|
|
128
|
+
'click',
|
|
129
|
+
handleFileNameClick,
|
|
130
|
+
);
|
|
131
|
+
(element as HTMLElement).style.cursor = 'pointer';
|
|
132
|
+
(element as HTMLElement).style.textDecoration =
|
|
133
|
+
'underline';
|
|
134
|
+
(element as HTMLElement).style.color = '#58a6ff'; // GitHub blue link color
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return () => {
|
|
138
|
+
// Cleanup event listeners
|
|
139
|
+
fileNameElements.forEach((element) => {
|
|
140
|
+
element.removeEventListener(
|
|
141
|
+
'click',
|
|
142
|
+
handleFileNameClick,
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
}, [diffHtml, content, openTab, setIsAIChatOpen]);
|
|
147
|
+
|
|
45
148
|
return (
|
|
46
149
|
<div className="diff-view">
|
|
47
150
|
<div className="diff-summary">
|
|
@@ -65,6 +168,7 @@ export const DiffView: React.FC<DiffViewProps> = ({
|
|
|
65
168
|
|
|
66
169
|
<div
|
|
67
170
|
className="diff-files"
|
|
171
|
+
ref={diffContainerRef}
|
|
68
172
|
dangerouslySetInnerHTML={{ __html: diffHtml }}
|
|
69
173
|
/>
|
|
70
174
|
</div>
|
|
@@ -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"
|