vzcode 2.16.0 → 2.18.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-DVR90LwF.css → index-BvGPtSrr.css} +1 -1
- package/dist/assets/{index-C2BLPyQP.js → index-DuDXdJWC.js} +127 -124
- package/dist/assets/{worker-CmHJK_do.js → worker-ClF0pBYr.js} +74 -74
- package/dist/index.html +2 -2
- package/dist/server/aiChatHandler/aiEditing.js +3 -34
- package/dist/server/aiChatHandler/chatOperations.js +2 -0
- package/dist/server/aiChatHandler/llmStreaming.js +13 -31
- package/dist/server/prettier.js +15 -6
- package/package.json +17 -17
- package/src/client/CodeEditor/index.tsx +1 -4
- package/src/client/VZSidebar/AIChat/FileEditingIndicator.tsx +12 -4
- package/src/client/VZSidebar/AIChat/Message.tsx +71 -18
- package/src/client/VZSidebar/AIChat/MessageList.tsx +57 -37
- package/src/client/VZSidebar/AIChat/index.tsx +0 -3
- package/src/client/VZSidebar/AIChat/styles.scss +33 -29
- package/src/client/hooks/useAutoScroll.ts +0 -1
- package/src/server/aiChatHandler/aiEditing.ts +3 -52
- package/src/server/aiChatHandler/chatOperations.ts +2 -0
- package/src/server/aiChatHandler/llmStreaming.ts +25 -44
- package/src/server/prettier.ts +25 -9
- package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +0 -77
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-DuDXdJWC.js"></script>
|
|
24
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BvGPtSrr.css">
|
|
25
25
|
</head>
|
|
26
26
|
<body>
|
|
27
27
|
<div id="root"></div>
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { assembleFullPrompt, prepareFilesForPrompt, } from 'editcodewithai';
|
|
2
2
|
import { formatMarkdownFiles } from 'llm-code-format';
|
|
3
3
|
import { createFilesSnapshot, generateFilesUnifiedDiff, } from '../../utils/fileDiff.js';
|
|
4
|
-
import { formatFiles } from '../prettier.js';
|
|
5
|
-
import { createSubmitOperation } from '../../submitOperation.js';
|
|
6
4
|
// Dev flag for waiting 1 second before starting the LLM function.
|
|
7
5
|
// Useful for debugging and testing purposes, e.g. checking the typing indicator.
|
|
8
6
|
const delayStart = false;
|
|
@@ -33,11 +31,11 @@ export const performAIChat = async ({ prompt, shareDBDoc, llmFunction, }) => {
|
|
|
33
31
|
* Performs AI editing operations using streaming with incremental OT operations
|
|
34
32
|
*/
|
|
35
33
|
export const performAIEditing = async ({ prompt, shareDBDoc, llmFunction, runCode, }) => {
|
|
36
|
-
//
|
|
34
|
+
// Capture the current state of files before editing
|
|
37
35
|
const beforeFiles = createFilesSnapshot(shareDBDoc.data.files);
|
|
38
36
|
const { files } = prepareFilesForPrompt(shareDBDoc.data.files);
|
|
39
37
|
const filesContext = formatMarkdownFiles(files);
|
|
40
|
-
//
|
|
38
|
+
// Assemble the final prompt
|
|
41
39
|
const fullPrompt = assembleFullPrompt({
|
|
42
40
|
filesContext,
|
|
43
41
|
prompt,
|
|
@@ -46,37 +44,8 @@ export const performAIEditing = async ({ prompt, shareDBDoc, llmFunction, runCod
|
|
|
46
44
|
if (delayStart) {
|
|
47
45
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
48
46
|
}
|
|
49
|
-
// Call the LLM function which will handle streaming
|
|
47
|
+
// Call the LLM function which will handle streaming, incremental file updates, and Prettier formatting
|
|
50
48
|
const result = await llmFunction(fullPrompt);
|
|
51
|
-
// 3. Run Prettier on changed files before running code
|
|
52
|
-
const afterLLMFiles = createFilesSnapshot(shareDBDoc.data.files);
|
|
53
|
-
// Find files that were changed by the AI
|
|
54
|
-
const changedFileIds = Object.keys(afterLLMFiles).filter((fileId) => beforeFiles[fileId]?.text !==
|
|
55
|
-
afterLLMFiles[fileId]?.text);
|
|
56
|
-
if (changedFileIds.length > 0) {
|
|
57
|
-
// Format the changed files
|
|
58
|
-
const formattedFiles = await formatFiles(shareDBDoc.data.files, changedFileIds);
|
|
59
|
-
// Apply formatted versions to shareDBDoc if formatting succeeded
|
|
60
|
-
if (Object.keys(formattedFiles).length > 0) {
|
|
61
|
-
const submitOperation = createSubmitOperation(shareDBDoc);
|
|
62
|
-
submitOperation((document) => {
|
|
63
|
-
const updatedFiles = { ...document.files };
|
|
64
|
-
// Apply each formatted file
|
|
65
|
-
Object.entries(formattedFiles).forEach(([fileId, formattedText]) => {
|
|
66
|
-
if (updatedFiles[fileId]) {
|
|
67
|
-
updatedFiles[fileId] = {
|
|
68
|
-
...updatedFiles[fileId],
|
|
69
|
-
text: formattedText,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
return {
|
|
74
|
-
...document,
|
|
75
|
-
files: updatedFiles,
|
|
76
|
-
};
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
49
|
runCode();
|
|
81
50
|
// 4. Capture the state of files after editing and formatting, then generate diff
|
|
82
51
|
const afterFiles = createFilesSnapshot(shareDBDoc.data.files);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
2
|
import { parseMarkdownFiles, StreamingMarkdownParser, } from 'llm-code-format';
|
|
3
3
|
import { mergeFileChanges } from 'editcodewithai';
|
|
4
|
-
import { generateRunId } from '@vizhub/viz-utils';
|
|
5
4
|
import { updateFiles, updateAIScratchpad, createStreamingAIMessage, addStreamingEvent, updateStreamingStatus, finalizeStreamingMessage, } from './chatOperations.js';
|
|
6
|
-
import {
|
|
5
|
+
import { formatFiles } from '../prettier.js';
|
|
7
6
|
const DEBUG = false;
|
|
8
7
|
// Useful for testing/debugging the streaming behavior
|
|
9
8
|
const slowMode = false;
|
|
@@ -191,37 +190,20 @@ enableReasoningTokens = false, model, aiRequestOptions, }) => {
|
|
|
191
190
|
if (currentEditingFileName) {
|
|
192
191
|
await completeFileEditing(currentEditingFileName);
|
|
193
192
|
}
|
|
193
|
+
// // Capture the current state of files before applying changes
|
|
194
|
+
// const beforeFiles = createFilesSnapshot(
|
|
195
|
+
// shareDBDoc.data.files,
|
|
196
|
+
// );
|
|
197
|
+
// Parse the full content to extract file changes
|
|
198
|
+
// export type FileCollection = Record<string, string>;
|
|
199
|
+
const newFilesUnformatted = parseMarkdownFiles(fullContent, 'bold').files;
|
|
200
|
+
// Run Prettier on `newFiles` before applying them
|
|
201
|
+
const newFilesFormatted = await formatFiles(newFilesUnformatted);
|
|
194
202
|
// Apply all the edits at once
|
|
195
|
-
|
|
203
|
+
const mergedChanges = mergeFileChanges(shareDBDoc.data.files, newFilesFormatted);
|
|
204
|
+
updateFiles(shareDBDoc, mergedChanges);
|
|
196
205
|
// Finalize streaming message
|
|
197
|
-
|
|
198
|
-
// Generate a new runId to trigger a run when AI finishes editing
|
|
199
|
-
// This will trigger a re-run without hot reloading
|
|
200
|
-
const newRunId = generateRunId();
|
|
201
|
-
const runIdOp = diff(shareDBDoc.data, {
|
|
202
|
-
...shareDBDoc.data,
|
|
203
|
-
runId: newRunId,
|
|
204
|
-
});
|
|
205
|
-
shareDBDoc.submitOp(runIdOp, (error) => {
|
|
206
|
-
if (error) {
|
|
207
|
-
console.warn('Error setting runId after AI editing:', error);
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
DEBUG &&
|
|
211
|
-
console.log('Set new runId after AI editing:', newRunId);
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
// Write chunks file for debugging
|
|
215
|
-
// if (DEBUG) {
|
|
216
|
-
// const chunksFileJSONpath = `./ai-chunks-${chatId}.json`;
|
|
217
|
-
// fs.writeFileSync(
|
|
218
|
-
// chunksFileJSONpath,
|
|
219
|
-
// JSON.stringify(chunks, null, 2),
|
|
220
|
-
// );
|
|
221
|
-
// console.log(
|
|
222
|
-
// `AI chunks written to ${chunksFileJSONpath}`,
|
|
223
|
-
// );
|
|
224
|
-
// }
|
|
206
|
+
finalizeStreamingMessage(shareDBDoc, chatId);
|
|
225
207
|
return {
|
|
226
208
|
content: fullContent,
|
|
227
209
|
generationId: generationId,
|
package/dist/server/prettier.js
CHANGED
|
@@ -72,16 +72,25 @@ export const formatFile = async (fileText, fileName) => {
|
|
|
72
72
|
* Only formats files that have supported extensions
|
|
73
73
|
* Returns a map of fileId -> formatted text for successfully formatted files
|
|
74
74
|
*/
|
|
75
|
-
export const formatFiles = async (
|
|
75
|
+
export const formatFiles = async (fileCollection) => {
|
|
76
76
|
const results = {};
|
|
77
|
-
const targetFileIds =
|
|
77
|
+
const targetFileIds = Object.keys(fileCollection);
|
|
78
78
|
// Process files in parallel for better performance
|
|
79
79
|
const formatPromises = targetFileIds.map(async (fileId) => {
|
|
80
|
-
const
|
|
81
|
-
if (!
|
|
80
|
+
const fileData = fileCollection[fileId];
|
|
81
|
+
if (!fileData)
|
|
82
82
|
return;
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
// Handle both FileCollection format and test format
|
|
84
|
+
const fileName = typeof fileData === 'string'
|
|
85
|
+
? fileId
|
|
86
|
+
: fileData.name;
|
|
87
|
+
const fileText = typeof fileData === 'string'
|
|
88
|
+
? fileData
|
|
89
|
+
: fileData.text;
|
|
90
|
+
if (!fileText)
|
|
91
|
+
return;
|
|
92
|
+
const formatted = await formatFile(fileText, fileName);
|
|
93
|
+
if (formatted !== null && formatted !== fileText) {
|
|
85
94
|
results[fileId] = formatted;
|
|
86
95
|
}
|
|
87
96
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vzcode",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.0",
|
|
4
4
|
"description": "Multiplayer code editor system",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -130,15 +130,15 @@
|
|
|
130
130
|
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
|
131
131
|
"@teamwork/websocket-json-stream": "^2.0.0",
|
|
132
132
|
"@typescript/vfs": "^1.6.1",
|
|
133
|
-
"@uiw/codemirror-theme-abcdef": "^4.25.
|
|
134
|
-
"@uiw/codemirror-theme-dracula": "^4.25.
|
|
135
|
-
"@uiw/codemirror-theme-eclipse": "^4.25.
|
|
136
|
-
"@uiw/codemirror-theme-github": "^4.25.
|
|
137
|
-
"@uiw/codemirror-theme-material": "^4.25.
|
|
138
|
-
"@uiw/codemirror-theme-nord": "^4.25.
|
|
139
|
-
"@uiw/codemirror-theme-okaidia": "^4.25.
|
|
140
|
-
"@uiw/codemirror-theme-xcode": "^4.25.
|
|
141
|
-
"@uiw/codemirror-themes": "^4.25.
|
|
133
|
+
"@uiw/codemirror-theme-abcdef": "^4.25.2",
|
|
134
|
+
"@uiw/codemirror-theme-dracula": "^4.25.2",
|
|
135
|
+
"@uiw/codemirror-theme-eclipse": "^4.25.2",
|
|
136
|
+
"@uiw/codemirror-theme-github": "^4.25.2",
|
|
137
|
+
"@uiw/codemirror-theme-material": "^4.25.2",
|
|
138
|
+
"@uiw/codemirror-theme-nord": "^4.25.2",
|
|
139
|
+
"@uiw/codemirror-theme-okaidia": "^4.25.2",
|
|
140
|
+
"@uiw/codemirror-theme-xcode": "^4.25.2",
|
|
141
|
+
"@uiw/codemirror-themes": "^4.25.2",
|
|
142
142
|
"@valtown/codemirror-ts": "^2.3.1",
|
|
143
143
|
"@vizhub/runtime": "^4.5.0",
|
|
144
144
|
"@vizhub/viz-types": "^0.5.0",
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
"bootstrap-icons": "^1.13.1",
|
|
148
148
|
"codemirror": "^6.0.2",
|
|
149
149
|
"codemirror-copilot": "^0.0.7",
|
|
150
|
-
"codemirror-ot": "^5.
|
|
150
|
+
"codemirror-ot": "^5.6.0",
|
|
151
151
|
"color-hash": "^2.0.2",
|
|
152
152
|
"comlink": "^4.4.2",
|
|
153
153
|
"d3-array": "^3.2.4",
|
|
@@ -157,7 +157,7 @@
|
|
|
157
157
|
"diff2html": "^3.4.52",
|
|
158
158
|
"dotenv": "^17.2.2",
|
|
159
159
|
"editcodewithai": "^2.4.0",
|
|
160
|
-
"eslint-linter-browserify": "^9.
|
|
160
|
+
"eslint-linter-browserify": "^9.36.0",
|
|
161
161
|
"express": "^5.1.0",
|
|
162
162
|
"ignore": "^7.0.5",
|
|
163
163
|
"json0-ot-diff": "^1.1.2",
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
"ws": "^8.18.3"
|
|
182
182
|
},
|
|
183
183
|
"devDependencies": {
|
|
184
|
-
"@eslint/js": "^9.
|
|
184
|
+
"@eslint/js": "^9.36.0",
|
|
185
185
|
"@tauri-apps/cli": "^2.8.4",
|
|
186
186
|
"@types/d3-color": "^3.1.3",
|
|
187
187
|
"@types/react": "^18",
|
|
@@ -191,12 +191,12 @@
|
|
|
191
191
|
"@vitejs/plugin-react": "^5.0.3",
|
|
192
192
|
"concurrently": "^9.2.1",
|
|
193
193
|
"cross-env": "^10.0.0",
|
|
194
|
-
"eslint": "^9.
|
|
194
|
+
"eslint": "^9.36.0",
|
|
195
195
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
196
196
|
"eslint-plugin-react": "^7.37.5",
|
|
197
197
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
198
198
|
"globals": "^16.4.0",
|
|
199
|
-
"npm-check-updates": "^18.
|
|
199
|
+
"npm-check-updates": "^18.2.1",
|
|
200
200
|
"prettier": "^3.6.2",
|
|
201
201
|
"sass": "^1.92.1",
|
|
202
202
|
"ts-node": "^10.9.2",
|
|
@@ -205,7 +205,7 @@
|
|
|
205
205
|
"vitest": "^3.2.4"
|
|
206
206
|
},
|
|
207
207
|
"optionalDependencies": {
|
|
208
|
-
"@rollup/rollup-darwin-arm64": "^4.
|
|
209
|
-
"@rollup/rollup-win32-x64-msvc": "^4.
|
|
208
|
+
"@rollup/rollup-darwin-arm64": "^4.51.0",
|
|
209
|
+
"@rollup/rollup-win32-x64-msvc": "^4.51.0"
|
|
210
210
|
}
|
|
211
211
|
}
|
|
@@ -32,7 +32,6 @@ export const CodeEditor = ({
|
|
|
32
32
|
const {
|
|
33
33
|
activePane,
|
|
34
34
|
shareDBDoc,
|
|
35
|
-
content,
|
|
36
35
|
submitOperation,
|
|
37
36
|
localPresence,
|
|
38
37
|
docPresence,
|
|
@@ -152,9 +151,7 @@ export const CodeEditor = ({
|
|
|
152
151
|
useLayoutEffect(() => {
|
|
153
152
|
// Guard against cases where page is still loading.
|
|
154
153
|
if (!codeEditorRef.current) return;
|
|
155
|
-
if (!content) return;
|
|
156
154
|
if (!editorCacheValue) return; // Guard against null editorCacheValue
|
|
157
|
-
// if (openSplitPane) return;
|
|
158
155
|
|
|
159
156
|
const codeEditorEl = codeEditorRef.current;
|
|
160
157
|
|
|
@@ -178,7 +175,7 @@ export const CodeEditor = ({
|
|
|
178
175
|
);
|
|
179
176
|
}
|
|
180
177
|
};
|
|
181
|
-
}, [codeEditorRef,
|
|
178
|
+
}, [codeEditorRef, editorCacheValue]);
|
|
182
179
|
|
|
183
180
|
// Focus the editor
|
|
184
181
|
useEffect(() => {
|
|
@@ -3,11 +3,12 @@ import { Spinner } from '../../AIAssist/Spinner';
|
|
|
3
3
|
interface AIEditingStatusIndicatorProps {
|
|
4
4
|
status: string;
|
|
5
5
|
fileName?: string;
|
|
6
|
+
additionalWidgets?: React.ReactNode;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
export const AIEditingStatusIndicator: React.FC<
|
|
9
10
|
AIEditingStatusIndicatorProps
|
|
10
|
-
> = ({ status, fileName }) => {
|
|
11
|
+
> = ({ status, fileName, additionalWidgets }) => {
|
|
11
12
|
const getStatusDisplay = (status: string) => {
|
|
12
13
|
switch (status) {
|
|
13
14
|
case 'Analyzing request...':
|
|
@@ -36,9 +37,16 @@ export const AIEditingStatusIndicator: React.FC<
|
|
|
36
37
|
);
|
|
37
38
|
case 'Done':
|
|
38
39
|
return (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
<div className="file-editing-done-container">
|
|
41
|
+
<div className="file-editing-done-status">
|
|
42
|
+
✅ <span>Done</span>
|
|
43
|
+
</div>
|
|
44
|
+
{additionalWidgets && (
|
|
45
|
+
<div className="file-editing-done-widgets">
|
|
46
|
+
{additionalWidgets}
|
|
47
|
+
</div>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
42
50
|
);
|
|
43
51
|
default:
|
|
44
52
|
// Handle file editing status (e.g., "Editing filename.js...")
|
|
@@ -1,31 +1,36 @@
|
|
|
1
|
-
import { timestampToDate } from '@vizhub/viz-utils';
|
|
2
|
-
import Markdown from 'react-markdown';
|
|
3
|
-
import remarkGfm from 'remark-gfm';
|
|
4
1
|
import React, {
|
|
5
2
|
useMemo,
|
|
6
|
-
memo,
|
|
7
3
|
useContext,
|
|
8
4
|
forwardRef,
|
|
9
5
|
} from 'react';
|
|
6
|
+
import { timestampToDate } from '@vizhub/viz-utils';
|
|
7
|
+
import Markdown from 'react-markdown';
|
|
8
|
+
import remarkGfm from 'remark-gfm';
|
|
9
|
+
import { StreamingEvent } from '../../../types.js';
|
|
10
|
+
import { IndividualFileDiff } from './IndividualFileDiff';
|
|
11
|
+
import { VZCodeContext } from '../../VZCodeContext';
|
|
10
12
|
import { DiffView, DiffViewRef } from './DiffView';
|
|
11
13
|
import { UnifiedFilesDiff } from '../../../utils/fileDiff';
|
|
12
14
|
import { enableDiffView } from '../../featureFlags';
|
|
13
|
-
|
|
15
|
+
|
|
16
|
+
const DEBUG = false;
|
|
14
17
|
|
|
15
18
|
interface MessageProps {
|
|
16
19
|
id: string;
|
|
17
20
|
role: 'user' | 'assistant';
|
|
18
|
-
content
|
|
21
|
+
content?: string; // For non-streaming messages
|
|
19
22
|
timestamp: number;
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
events?: StreamingEvent[]; // For streaming messages
|
|
24
|
+
isActive?: boolean; // Is this the currently streaming message?
|
|
22
25
|
chatId?: string;
|
|
23
26
|
showAdditionalWidgets?: boolean;
|
|
27
|
+
isStreaming?: boolean;
|
|
28
|
+
diffData?: UnifiedFilesDiff;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
const
|
|
31
|
+
export const Message = forwardRef<
|
|
27
32
|
DiffViewRef,
|
|
28
|
-
MessageProps
|
|
33
|
+
React.PropsWithChildren<MessageProps>
|
|
29
34
|
>(
|
|
30
35
|
(
|
|
31
36
|
{
|
|
@@ -33,16 +38,25 @@ const MessageComponent = forwardRef<
|
|
|
33
38
|
role,
|
|
34
39
|
content,
|
|
35
40
|
timestamp,
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
events = [],
|
|
42
|
+
isActive,
|
|
43
|
+
children,
|
|
38
44
|
chatId,
|
|
39
45
|
showAdditionalWidgets = false,
|
|
46
|
+
isStreaming = false,
|
|
47
|
+
diffData,
|
|
40
48
|
},
|
|
41
49
|
ref,
|
|
42
50
|
) => {
|
|
43
51
|
const { additionalWidgets, handleSendMessage } =
|
|
44
52
|
useContext(VZCodeContext);
|
|
45
53
|
|
|
54
|
+
DEBUG &&
|
|
55
|
+
console.log(
|
|
56
|
+
'StreamingMessage: Rendered with events:',
|
|
57
|
+
events,
|
|
58
|
+
);
|
|
59
|
+
|
|
46
60
|
// Memoize date formatting to avoid repeated computation
|
|
47
61
|
const formattedTime = useMemo(() => {
|
|
48
62
|
return timestampToDate(timestamp).toLocaleTimeString(
|
|
@@ -62,22 +76,63 @@ const MessageComponent = forwardRef<
|
|
|
62
76
|
return (
|
|
63
77
|
<div className={messageClassName}>
|
|
64
78
|
<div className="ai-chat-message-content">
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
79
|
+
{/* Render regular content for non-streaming messages */}
|
|
80
|
+
{content && (
|
|
81
|
+
<Markdown remarkPlugins={[remarkGfm]}>
|
|
82
|
+
{content}
|
|
83
|
+
</Markdown>
|
|
84
|
+
)}
|
|
85
|
+
|
|
86
|
+
{/* Render diff view if present */}
|
|
68
87
|
{enableDiffView &&
|
|
69
88
|
diffData &&
|
|
70
89
|
Object.keys(diffData).length > 0 && (
|
|
71
90
|
<DiffView diffData={diffData} ref={ref} />
|
|
72
91
|
)}
|
|
92
|
+
|
|
93
|
+
{/* Render events in order for streaming messages */}
|
|
94
|
+
{events.map((event, index) => {
|
|
95
|
+
switch (event.type) {
|
|
96
|
+
case 'text_chunk':
|
|
97
|
+
return (
|
|
98
|
+
<div
|
|
99
|
+
key={`text-${index}`}
|
|
100
|
+
className="text-chunk"
|
|
101
|
+
>
|
|
102
|
+
<Markdown remarkPlugins={[remarkGfm]}>
|
|
103
|
+
{event.content}
|
|
104
|
+
</Markdown>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
case 'file_complete':
|
|
108
|
+
return (
|
|
109
|
+
<IndividualFileDiff
|
|
110
|
+
key={`file-${event.fileName}-${index}`}
|
|
111
|
+
fileName={event.fileName}
|
|
112
|
+
beforeContent={
|
|
113
|
+
event.beforeContent || ''
|
|
114
|
+
}
|
|
115
|
+
afterContent={event.afterContent || ''}
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
case 'file_start':
|
|
119
|
+
// File start events are now handled by centralized status logic in MessageList
|
|
120
|
+
return null;
|
|
121
|
+
default:
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
})}
|
|
125
|
+
{/* Additional widgets are now shown within the "Done" status indicator */}
|
|
73
126
|
{showAdditionalWidgets &&
|
|
74
127
|
additionalWidgets &&
|
|
75
128
|
chatId &&
|
|
129
|
+
!isStreaming && // Only show here for non-streaming messages (completed messages)
|
|
76
130
|
additionalWidgets({
|
|
77
131
|
messageId: id,
|
|
78
132
|
chatId: chatId,
|
|
79
133
|
handleSendMessage,
|
|
80
134
|
})}
|
|
135
|
+
{isActive && children}
|
|
81
136
|
</div>
|
|
82
137
|
<div className="ai-chat-message-time">
|
|
83
138
|
{formattedTime}
|
|
@@ -87,6 +142,4 @@ const MessageComponent = forwardRef<
|
|
|
87
142
|
},
|
|
88
143
|
);
|
|
89
144
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
export const Message = memo(MessageComponent);
|
|
145
|
+
Message.displayName = 'Message';
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useRef,
|
|
3
|
+
useEffect,
|
|
4
|
+
memo,
|
|
5
|
+
useState,
|
|
6
|
+
useContext,
|
|
7
|
+
} from 'react';
|
|
2
8
|
import { Message } from './Message';
|
|
3
|
-
import { StreamingMessage } from './StreamingMessage';
|
|
4
9
|
import { TypingIndicator } from './TypingIndicator';
|
|
5
10
|
import { ThinkingScratchpad } from './ThinkingScratchpad';
|
|
6
11
|
import { AIEditingStatusIndicator } from './FileEditingIndicator';
|
|
7
12
|
import { VizChatMessage } from '@vizhub/viz-types';
|
|
8
13
|
import { ExtendedVizChatMessage } from '../../../types.js';
|
|
9
14
|
import { DiffViewRef } from './DiffView.js';
|
|
15
|
+
import { VZCodeContext } from '../../VZCodeContext';
|
|
10
16
|
|
|
11
17
|
const MessageListComponent = ({
|
|
12
18
|
messages,
|
|
@@ -32,6 +38,10 @@ const MessageListComponent = ({
|
|
|
32
38
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
33
39
|
const diffViewRef = useRef<DiffViewRef>(null);
|
|
34
40
|
|
|
41
|
+
// Get additional widgets from context
|
|
42
|
+
const { additionalWidgets, handleSendMessage } =
|
|
43
|
+
useContext(VZCodeContext);
|
|
44
|
+
|
|
35
45
|
// Track previous loading state to detect when AI generation completes
|
|
36
46
|
const [prevIsLoading, setPrevIsLoading] =
|
|
37
47
|
useState(isLoading);
|
|
@@ -162,54 +172,64 @@ const MessageListComponent = ({
|
|
|
162
172
|
msg.role === 'assistant' &&
|
|
163
173
|
index === lastAssistantMessageIndex;
|
|
164
174
|
|
|
165
|
-
// Use
|
|
166
|
-
if (
|
|
167
|
-
msg.role === 'assistant' &&
|
|
168
|
-
isStreamingMessage
|
|
169
|
-
) {
|
|
170
|
-
return (
|
|
171
|
-
<StreamingMessage
|
|
172
|
-
key={msg.id}
|
|
173
|
-
timestamp={msg.timestamp}
|
|
174
|
-
events={extendedMsg.streamingEvents || []}
|
|
175
|
-
isActive={index === lastAssistantMessageIndex}
|
|
176
|
-
>
|
|
177
|
-
{showThinkingScratchpad && (
|
|
178
|
-
<ThinkingScratchpad
|
|
179
|
-
content={aiScratchpad || ''}
|
|
180
|
-
isVisible={showThinkingScratchpad}
|
|
181
|
-
/>
|
|
182
|
-
)}
|
|
183
|
-
|
|
184
|
-
{showConsolidatedStatusIndicator && (
|
|
185
|
-
<AIEditingStatusIndicator
|
|
186
|
-
status={consolidatedStatus?.status || ''}
|
|
187
|
-
fileName={consolidatedStatus?.fileName}
|
|
188
|
-
/>
|
|
189
|
-
)}
|
|
190
|
-
|
|
191
|
-
{showTypingIndicator && <TypingIndicator />}
|
|
192
|
-
</StreamingMessage>
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Use regular Message component for user messages and non-streaming assistant messages
|
|
175
|
+
// Use Message for all messages now
|
|
197
176
|
return (
|
|
198
177
|
<Message
|
|
199
178
|
key={msg.id}
|
|
200
179
|
id={msg.id}
|
|
201
180
|
role={msg.role}
|
|
202
|
-
content={
|
|
181
|
+
content={
|
|
182
|
+
isStreamingMessage ? undefined : msg.content
|
|
183
|
+
}
|
|
203
184
|
timestamp={msg.timestamp}
|
|
204
|
-
|
|
185
|
+
events={
|
|
186
|
+
isStreamingMessage
|
|
187
|
+
? extendedMsg.streamingEvents || []
|
|
188
|
+
: []
|
|
189
|
+
}
|
|
190
|
+
isActive={index === lastAssistantMessageIndex}
|
|
205
191
|
chatId={chatId}
|
|
206
192
|
showAdditionalWidgets={showAdditionalWidgets}
|
|
193
|
+
isStreaming={isStreamingMessage}
|
|
194
|
+
diffData={(msg as any).diffData}
|
|
207
195
|
ref={
|
|
208
196
|
isLastMessage && (msg as any).diffData
|
|
209
197
|
? diffViewRef
|
|
210
198
|
: null
|
|
211
199
|
}
|
|
212
|
-
|
|
200
|
+
>
|
|
201
|
+
{isStreamingMessage &&
|
|
202
|
+
showThinkingScratchpad && (
|
|
203
|
+
<ThinkingScratchpad
|
|
204
|
+
content={aiScratchpad || ''}
|
|
205
|
+
isVisible={showThinkingScratchpad}
|
|
206
|
+
/>
|
|
207
|
+
)}
|
|
208
|
+
|
|
209
|
+
{isStreamingMessage &&
|
|
210
|
+
showConsolidatedStatusIndicator && (
|
|
211
|
+
<AIEditingStatusIndicator
|
|
212
|
+
status={consolidatedStatus?.status || ''}
|
|
213
|
+
fileName={consolidatedStatus?.fileName}
|
|
214
|
+
additionalWidgets={
|
|
215
|
+
consolidatedStatus?.status === 'Done' &&
|
|
216
|
+
showAdditionalWidgets &&
|
|
217
|
+
additionalWidgets &&
|
|
218
|
+
chatId
|
|
219
|
+
? additionalWidgets({
|
|
220
|
+
messageId: msg.id,
|
|
221
|
+
chatId: chatId,
|
|
222
|
+
handleSendMessage,
|
|
223
|
+
})
|
|
224
|
+
: undefined
|
|
225
|
+
}
|
|
226
|
+
/>
|
|
227
|
+
)}
|
|
228
|
+
|
|
229
|
+
{isStreamingMessage && showTypingIndicator && (
|
|
230
|
+
<TypingIndicator />
|
|
231
|
+
)}
|
|
232
|
+
</Message>
|
|
213
233
|
);
|
|
214
234
|
})}
|
|
215
235
|
|
|
@@ -78,9 +78,6 @@ export const AIChat = () => {
|
|
|
78
78
|
afterRender,
|
|
79
79
|
} = useAutoScroll({ threshold: 24 });
|
|
80
80
|
|
|
81
|
-
// TODO fix this - currently always false
|
|
82
|
-
console.log('showJumpButton:', showJumpButton);
|
|
83
|
-
|
|
84
81
|
// Get the active chat ID and chat data
|
|
85
82
|
// If selectedChatId is set, use it; otherwise, if no chat selected, don't default to currentChatId
|
|
86
83
|
const activeChatId = selectedChatId;
|