vzcode 1.32.0 → 1.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/assets/index-ajiBaTmx.js +263 -0
- package/dist/assets/index-lvLw6dCW.css +1 -0
- package/dist/assets/worker-BrwN8AXx.js +330 -0
- package/dist/assets/worker-CkHcCP1n.js +136 -0
- package/dist/assets/worker-DkYJN5WQ.js +453 -0
- package/dist/index.html +2 -2
- package/package.json +42 -37
- package/src/client/CodeEditor/getOrCreateEditor.ts +18 -5
- package/src/client/Icons/MicSVG.tsx +1 -1
- package/src/client/VZCodeContext.tsx +18 -2
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +65 -0
- package/src/client/VZSidebar/AIChat/Message.tsx +36 -0
- package/src/client/VZSidebar/AIChat/MessageList.tsx +62 -0
- package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +25 -0
- package/src/client/VZSidebar/AIChat/TypingIndicator.tsx +13 -0
- package/src/client/VZSidebar/AIChat/index.tsx +75 -0
- package/src/client/VZSidebar/AIChat/styles.scss +256 -0
- package/src/client/VZSidebar/index.tsx +53 -8
- package/src/client/VZSidebar/styles.scss +8 -1
- package/src/client/diff.js +1 -1
- package/src/client/featureFlags.ts +2 -0
- package/src/client/useActions.ts +20 -0
- package/src/client/useEditorCache.ts +1 -0
- package/src/client/useFileCRUD.ts +47 -0
- package/src/client/useOpenDirectories.ts +43 -12
- package/src/client/utils/fileExtension.ts +10 -0
- package/src/client/vzReducer/aiChatReducer.ts +31 -0
- package/src/client/vzReducer/createInitialState.ts +2 -0
- package/src/client/vzReducer/index.ts +20 -0
- package/src/ot.js +10 -2
- package/src/server/aiChatHandler/aiEditing.js +42 -0
- package/src/server/aiChatHandler/chatOperations.js +195 -0
- package/src/server/aiChatHandler/errorHandling.js +53 -0
- package/src/server/aiChatHandler/index.js +69 -0
- package/src/server/aiChatHandler/llmStreaming.js +105 -0
- package/src/server/aiChatHandler/validation.js +24 -0
- package/src/server/handleAIChatMessage.js +2 -0
- package/src/server/index.js +8 -0
- package/dist/assets/index-DTtcN2MP.css +0 -1
- package/dist/assets/index-sreAszZh.js +0 -240
- package/dist/assets/worker-BKfYWklR.js +0 -136
- package/dist/assets/worker-COyDRdqW.js +0 -453
- package/dist/assets/worker-CWsWXMyk.js +0 -335
|
@@ -30,6 +30,10 @@ import {
|
|
|
30
30
|
setSearchFocusedIndexReducer,
|
|
31
31
|
toggleSearchFocusedReducer,
|
|
32
32
|
} from './searchReducer';
|
|
33
|
+
import {
|
|
34
|
+
setIsAIChatOpenReducer,
|
|
35
|
+
toggleAIChatFocusedReducer,
|
|
36
|
+
} from './aiChatReducer';
|
|
33
37
|
import { toggleAutoFollowReducer } from './toggleAutoFollowReducer';
|
|
34
38
|
import { updatePresenceIndicatorReducer } from './updatePresenceIndicatorReducer';
|
|
35
39
|
import { splitCurrentPaneReducer } from './splitCurrentPaneReducer';
|
|
@@ -52,6 +56,12 @@ export type VZState = {
|
|
|
52
56
|
// True to show the search instead of files
|
|
53
57
|
isSearchOpen: boolean;
|
|
54
58
|
|
|
59
|
+
// True to show the AI chat instead of files
|
|
60
|
+
isAIChatOpen: boolean;
|
|
61
|
+
|
|
62
|
+
// True if the AI chat input should focus on the next render.
|
|
63
|
+
aiChatFocused: boolean;
|
|
64
|
+
|
|
55
65
|
// True to show the settings modal.
|
|
56
66
|
isSettingsOpen: boolean;
|
|
57
67
|
|
|
@@ -115,6 +125,14 @@ export type VZAction =
|
|
|
115
125
|
// * Sets whether the search tab is open.
|
|
116
126
|
| { type: 'set_is_search_open'; value: boolean }
|
|
117
127
|
|
|
128
|
+
// `set_is_ai_chat_open`
|
|
129
|
+
// * Sets whether the AI chat tab is open.
|
|
130
|
+
| { type: 'set_is_ai_chat_open'; value: boolean }
|
|
131
|
+
|
|
132
|
+
// `toggle_ai_chat_focused`
|
|
133
|
+
// * Toggles focused variable to trigger AI chat input focus
|
|
134
|
+
| { type: 'toggle_ai_chat_focused' }
|
|
135
|
+
|
|
118
136
|
// `set_search`
|
|
119
137
|
// * Sets the current search pattern
|
|
120
138
|
| { type: 'set_search'; value: string }
|
|
@@ -192,6 +210,8 @@ const reducers = [
|
|
|
192
210
|
setSearchFocusedIndexReducer,
|
|
193
211
|
toggleSearchFocusedReducer,
|
|
194
212
|
setIsSearchOpenReducer,
|
|
213
|
+
setIsAIChatOpenReducer,
|
|
214
|
+
toggleAIChatFocusedReducer,
|
|
195
215
|
setIsSettingsOpenReducer,
|
|
196
216
|
setIsDocOpenReducer,
|
|
197
217
|
editorNoLongerWantsFocusReducer,
|
package/src/ot.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
// Operational Transformation (OT) utilities.
|
|
1
2
|
// This file is the central point where the OT types are imported.
|
|
2
3
|
// Localized to one file so it's easy to change it in future.
|
|
3
4
|
import OTJSON1Presence from 'sharedb-client-browser/dist/ot-json1-presence-umd.cjs';
|
|
4
|
-
|
|
5
|
-
export { json1Presence, textUnicode }
|
|
5
|
+
|
|
6
|
+
export const { json1Presence, textUnicode } =
|
|
7
|
+
OTJSON1Presence;
|
|
8
|
+
|
|
9
|
+
// The OT type used throughout the codebase.
|
|
10
|
+
export const otType = json1Presence.type;
|
|
11
|
+
|
|
12
|
+
// Applies an OT op to an object.
|
|
13
|
+
export const apply = otType.apply;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { performAiEdit } from 'editcodewithai';
|
|
2
|
+
import {
|
|
3
|
+
clearAIScratchpadAndStatus,
|
|
4
|
+
updateFiles,
|
|
5
|
+
setIsInteracting,
|
|
6
|
+
} from './chatOperations.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Performs AI editing operations on the files
|
|
10
|
+
*/
|
|
11
|
+
export const performAIEditing = async (
|
|
12
|
+
shareDBDoc,
|
|
13
|
+
chatId,
|
|
14
|
+
content,
|
|
15
|
+
files,
|
|
16
|
+
llmFunction,
|
|
17
|
+
) => {
|
|
18
|
+
const editResult = await performAiEdit({
|
|
19
|
+
prompt: content,
|
|
20
|
+
files,
|
|
21
|
+
llmFunction,
|
|
22
|
+
apiKey: process.env.VIZHUB_EDIT_WITH_AI_API_KEY,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Clear the scratchpad and update status
|
|
26
|
+
clearAIScratchpadAndStatus(
|
|
27
|
+
shareDBDoc,
|
|
28
|
+
chatId,
|
|
29
|
+
'Done editing with AI.',
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Apply AI edits to files
|
|
33
|
+
updateFiles(shareDBDoc, editResult.changedFiles);
|
|
34
|
+
|
|
35
|
+
// Wait for propagation
|
|
36
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
37
|
+
|
|
38
|
+
// Unset isInteracting
|
|
39
|
+
setIsInteracting(shareDBDoc, false);
|
|
40
|
+
|
|
41
|
+
return editResult;
|
|
42
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { dateToTimestamp } from '@vizhub/viz-utils';
|
|
2
|
+
import { diff } from '../../client/diff.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Ensures the chats object exists in the ShareDB document
|
|
6
|
+
*/
|
|
7
|
+
export const ensureChatsExist = (shareDBDoc) => {
|
|
8
|
+
if (!shareDBDoc.data.chats) {
|
|
9
|
+
const op = diff(shareDBDoc.data, {
|
|
10
|
+
...shareDBDoc.data,
|
|
11
|
+
chats: {},
|
|
12
|
+
});
|
|
13
|
+
shareDBDoc.submitOp(op);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Ensures a specific chat exists in the ShareDB document
|
|
19
|
+
*/
|
|
20
|
+
export const ensureChatExists = (shareDBDoc, chatId) => {
|
|
21
|
+
if (!shareDBDoc.data.chats[chatId]) {
|
|
22
|
+
const op = diff(shareDBDoc.data, {
|
|
23
|
+
...shareDBDoc.data,
|
|
24
|
+
chats: {
|
|
25
|
+
...shareDBDoc.data.chats,
|
|
26
|
+
[chatId]: {
|
|
27
|
+
id: chatId,
|
|
28
|
+
messages: [],
|
|
29
|
+
createdAt: dateToTimestamp(new Date()),
|
|
30
|
+
updatedAt: dateToTimestamp(new Date()),
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
shareDBDoc.submitOp(op);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Adds a user message to the chat
|
|
40
|
+
*/
|
|
41
|
+
export const addUserMessage = (
|
|
42
|
+
shareDBDoc,
|
|
43
|
+
chatId,
|
|
44
|
+
content,
|
|
45
|
+
) => {
|
|
46
|
+
const userMessage = {
|
|
47
|
+
id: `user-${Date.now()}`,
|
|
48
|
+
role: 'user',
|
|
49
|
+
content: content,
|
|
50
|
+
timestamp: dateToTimestamp(new Date()),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const userMessageOp = diff(shareDBDoc.data, {
|
|
54
|
+
...shareDBDoc.data,
|
|
55
|
+
chats: {
|
|
56
|
+
...shareDBDoc.data.chats,
|
|
57
|
+
[chatId]: {
|
|
58
|
+
...shareDBDoc.data.chats[chatId],
|
|
59
|
+
messages: [
|
|
60
|
+
...shareDBDoc.data.chats[chatId].messages,
|
|
61
|
+
userMessage,
|
|
62
|
+
],
|
|
63
|
+
updatedAt: dateToTimestamp(new Date()),
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
shareDBDoc.submitOp(userMessageOp);
|
|
68
|
+
|
|
69
|
+
return userMessage;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Updates AI status in the chat
|
|
74
|
+
*/
|
|
75
|
+
export const updateAIStatus = (
|
|
76
|
+
shareDBDoc,
|
|
77
|
+
chatId,
|
|
78
|
+
status,
|
|
79
|
+
) => {
|
|
80
|
+
const op = diff(shareDBDoc.data, {
|
|
81
|
+
...shareDBDoc.data,
|
|
82
|
+
chats: {
|
|
83
|
+
...shareDBDoc.data.chats,
|
|
84
|
+
[chatId]: {
|
|
85
|
+
...shareDBDoc.data.chats[chatId],
|
|
86
|
+
aiStatus: status,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
shareDBDoc.submitOp(op);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Updates AI scratchpad content
|
|
95
|
+
*/
|
|
96
|
+
export const updateAIScratchpad = (
|
|
97
|
+
shareDBDoc,
|
|
98
|
+
chatId,
|
|
99
|
+
content,
|
|
100
|
+
) => {
|
|
101
|
+
const op = diff(shareDBDoc.data, {
|
|
102
|
+
...shareDBDoc.data,
|
|
103
|
+
chats: {
|
|
104
|
+
...shareDBDoc.data.chats,
|
|
105
|
+
[chatId]: {
|
|
106
|
+
...shareDBDoc.data.chats[chatId],
|
|
107
|
+
aiScratchpad: content,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
shareDBDoc.submitOp(op);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Clears AI scratchpad and updates status
|
|
116
|
+
*/
|
|
117
|
+
export const clearAIScratchpadAndStatus = (
|
|
118
|
+
shareDBDoc,
|
|
119
|
+
chatId,
|
|
120
|
+
status,
|
|
121
|
+
) => {
|
|
122
|
+
const op = diff(shareDBDoc.data, {
|
|
123
|
+
...shareDBDoc.data,
|
|
124
|
+
chats: {
|
|
125
|
+
...shareDBDoc.data.chats,
|
|
126
|
+
[chatId]: {
|
|
127
|
+
...shareDBDoc.data.chats[chatId],
|
|
128
|
+
aiScratchpad: undefined,
|
|
129
|
+
aiStatus: status,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
shareDBDoc.submitOp(op);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Adds an AI response message to the chat
|
|
138
|
+
*/
|
|
139
|
+
export const addAIMessage = (
|
|
140
|
+
shareDBDoc,
|
|
141
|
+
chatId,
|
|
142
|
+
content,
|
|
143
|
+
) => {
|
|
144
|
+
const aiResponse = {
|
|
145
|
+
id: Date.now() + 1,
|
|
146
|
+
role: 'assistant',
|
|
147
|
+
content: content || 'AI edit completed successfully.',
|
|
148
|
+
timestamp: dateToTimestamp(new Date()),
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const messageOp = diff(shareDBDoc.data, {
|
|
152
|
+
...shareDBDoc.data,
|
|
153
|
+
chats: {
|
|
154
|
+
...shareDBDoc.data.chats,
|
|
155
|
+
[chatId]: {
|
|
156
|
+
...shareDBDoc.data.chats[chatId],
|
|
157
|
+
messages: [
|
|
158
|
+
...shareDBDoc.data.chats[chatId].messages,
|
|
159
|
+
aiResponse,
|
|
160
|
+
],
|
|
161
|
+
updatedAt: dateToTimestamp(new Date()),
|
|
162
|
+
aiStatus: undefined,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
shareDBDoc.submitOp(messageOp);
|
|
167
|
+
|
|
168
|
+
return aiResponse;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Updates files in the ShareDB document
|
|
173
|
+
*/
|
|
174
|
+
export const updateFiles = (shareDBDoc, files) => {
|
|
175
|
+
const filesOp = diff(shareDBDoc.data, {
|
|
176
|
+
...shareDBDoc.data,
|
|
177
|
+
files: files,
|
|
178
|
+
isInteracting: true,
|
|
179
|
+
});
|
|
180
|
+
shareDBDoc.submitOp(filesOp);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Sets isInteracting flag
|
|
185
|
+
*/
|
|
186
|
+
export const setIsInteracting = (
|
|
187
|
+
shareDBDoc,
|
|
188
|
+
isInteracting,
|
|
189
|
+
) => {
|
|
190
|
+
const interactingOp = diff(
|
|
191
|
+
{ isInteracting: !isInteracting },
|
|
192
|
+
{ isInteracting: isInteracting },
|
|
193
|
+
);
|
|
194
|
+
shareDBDoc.submitOp(interactingOp);
|
|
195
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { dateToTimestamp } from '@vizhub/viz-utils';
|
|
2
|
+
import { diff } from '../../client/diff.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Handles errors by adding an error message to the chat and clearing AI state
|
|
6
|
+
*/
|
|
7
|
+
export const handleError = (
|
|
8
|
+
shareDBDoc,
|
|
9
|
+
chatId,
|
|
10
|
+
error,
|
|
11
|
+
res,
|
|
12
|
+
) => {
|
|
13
|
+
console.error('[handleAIChatMessage] error:', error);
|
|
14
|
+
|
|
15
|
+
// Clear scratchpad and add error message on error
|
|
16
|
+
try {
|
|
17
|
+
const errorResponse = {
|
|
18
|
+
id: `error-${Date.now()}`,
|
|
19
|
+
role: 'assistant',
|
|
20
|
+
content:
|
|
21
|
+
'Sorry, I encountered an error while processing your message. Please try again.',
|
|
22
|
+
timestamp: dateToTimestamp(new Date()),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const errorOp = diff(shareDBDoc.data, {
|
|
26
|
+
...shareDBDoc.data,
|
|
27
|
+
chats: {
|
|
28
|
+
...shareDBDoc.data.chats,
|
|
29
|
+
[chatId]: {
|
|
30
|
+
...shareDBDoc.data.chats[chatId],
|
|
31
|
+
messages: [
|
|
32
|
+
...shareDBDoc.data.chats[chatId].messages,
|
|
33
|
+
errorResponse,
|
|
34
|
+
],
|
|
35
|
+
aiScratchpad: undefined,
|
|
36
|
+
aiStatus: undefined,
|
|
37
|
+
updatedAt: dateToTimestamp(new Date()),
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
shareDBDoc.submitOp(errorOp);
|
|
42
|
+
} catch (opError) {
|
|
43
|
+
console.error(
|
|
44
|
+
'[handleAIChatMessage] error handling error state:',
|
|
45
|
+
opError,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
res.status(500).json({
|
|
50
|
+
error: 'Internal server error',
|
|
51
|
+
message: error.message,
|
|
52
|
+
});
|
|
53
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { validateRequest } from './validation.js';
|
|
2
|
+
import {
|
|
3
|
+
ensureChatsExist,
|
|
4
|
+
ensureChatExists,
|
|
5
|
+
addUserMessage,
|
|
6
|
+
addAIMessage,
|
|
7
|
+
} from './chatOperations.js';
|
|
8
|
+
import { createLLMFunction } from './llmStreaming.js';
|
|
9
|
+
import { performAIEditing } from './aiEditing.js';
|
|
10
|
+
import { handleError } from './errorHandling.js';
|
|
11
|
+
|
|
12
|
+
const debug = false;
|
|
13
|
+
|
|
14
|
+
export const handleAIChatMessage =
|
|
15
|
+
(shareDBDoc) => async (req, res) => {
|
|
16
|
+
const { content, chatId } = req.body;
|
|
17
|
+
|
|
18
|
+
if (debug) {
|
|
19
|
+
console.log(
|
|
20
|
+
'[handleAIChatMessage] content:',
|
|
21
|
+
content,
|
|
22
|
+
'chatId:',
|
|
23
|
+
chatId,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Validate request
|
|
28
|
+
if (!validateRequest(req, res)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
// Get existing files from ShareDB doc
|
|
34
|
+
const files = shareDBDoc.data.files;
|
|
35
|
+
|
|
36
|
+
// Ensure chats structure exists
|
|
37
|
+
ensureChatsExist(shareDBDoc);
|
|
38
|
+
ensureChatExists(shareDBDoc, chatId);
|
|
39
|
+
|
|
40
|
+
// Add user message to chat
|
|
41
|
+
addUserMessage(shareDBDoc, chatId, content);
|
|
42
|
+
|
|
43
|
+
// Create LLM function for streaming
|
|
44
|
+
const llmFunction = createLLMFunction(
|
|
45
|
+
shareDBDoc,
|
|
46
|
+
chatId,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Perform AI editing
|
|
50
|
+
const editResult = await performAIEditing(
|
|
51
|
+
shareDBDoc,
|
|
52
|
+
chatId,
|
|
53
|
+
content,
|
|
54
|
+
files,
|
|
55
|
+
llmFunction,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Add AI response message
|
|
59
|
+
const aiResponse = addAIMessage(
|
|
60
|
+
shareDBDoc,
|
|
61
|
+
chatId,
|
|
62
|
+
editResult.content,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
res.status(200).json(aiResponse);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
handleError(shareDBDoc, chatId, error, res);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { StreamingMarkdownParser } from 'llm-code-format';
|
|
2
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
3
|
+
import {
|
|
4
|
+
updateAIStatus,
|
|
5
|
+
updateAIScratchpad,
|
|
6
|
+
} from './chatOperations.js';
|
|
7
|
+
|
|
8
|
+
const debug = false;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates and configures the LLM function for streaming
|
|
12
|
+
*/
|
|
13
|
+
export const createLLMFunction = (shareDBDoc, chatId) => {
|
|
14
|
+
return async (fullPrompt) => {
|
|
15
|
+
const chatModel = new ChatOpenAI({
|
|
16
|
+
modelName:
|
|
17
|
+
process.env.VIZHUB_EDIT_WITH_AI_MODEL_NAME ||
|
|
18
|
+
'gpt-4o-mini',
|
|
19
|
+
configuration: {
|
|
20
|
+
apiKey: process.env.VIZHUB_EDIT_WITH_AI_API_KEY,
|
|
21
|
+
baseURL: process.env.VIZHUB_EDIT_WITH_AI_BASE_URL,
|
|
22
|
+
defaultHeaders: {
|
|
23
|
+
'HTTP-Referer': 'https://vizhub.com',
|
|
24
|
+
'X-Title': 'VizHub',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
streaming: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
let fullContent = '';
|
|
31
|
+
let generationId = '';
|
|
32
|
+
|
|
33
|
+
// Track expected state to avoid race conditions
|
|
34
|
+
let expectedAiScratchpad =
|
|
35
|
+
shareDBDoc.data.chats[chatId]?.aiScratchpad || '';
|
|
36
|
+
|
|
37
|
+
// Throttle updates to avoid "op too long" errors
|
|
38
|
+
let lastUpdateTime = 0;
|
|
39
|
+
const UPDATE_THROTTLE_MS = 100;
|
|
40
|
+
|
|
41
|
+
// Define callbacks for streaming parser
|
|
42
|
+
const callbacks = {
|
|
43
|
+
onFileNameChange: (fileName, format) => {
|
|
44
|
+
debug &&
|
|
45
|
+
console.log(
|
|
46
|
+
`File changed to: ${fileName} (${format})`,
|
|
47
|
+
);
|
|
48
|
+
updateAIStatus(
|
|
49
|
+
shareDBDoc,
|
|
50
|
+
chatId,
|
|
51
|
+
'Editing ' + fileName,
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
onCodeLine: (line) => {
|
|
55
|
+
debug && console.log(`Code line: ${line}`);
|
|
56
|
+
},
|
|
57
|
+
onNonCodeLine: (line) => {
|
|
58
|
+
debug && console.log(`Comment/text: ${line}`);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const parser = new StreamingMarkdownParser(callbacks);
|
|
63
|
+
|
|
64
|
+
// Stream the response
|
|
65
|
+
const stream = await chatModel.stream(fullPrompt);
|
|
66
|
+
for await (const chunk of stream) {
|
|
67
|
+
if (chunk.content) {
|
|
68
|
+
const chunkContent = String(chunk.content);
|
|
69
|
+
fullContent += chunkContent;
|
|
70
|
+
|
|
71
|
+
// Throttle updates
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
const shouldUpdate =
|
|
74
|
+
now - lastUpdateTime >= UPDATE_THROTTLE_MS;
|
|
75
|
+
|
|
76
|
+
if (shouldUpdate) {
|
|
77
|
+
updateAIScratchpad(
|
|
78
|
+
shareDBDoc,
|
|
79
|
+
chatId,
|
|
80
|
+
fullContent,
|
|
81
|
+
);
|
|
82
|
+
expectedAiScratchpad = fullContent;
|
|
83
|
+
lastUpdateTime = now;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
parser.processChunk(chunkContent);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!generationId && chunk.lc_kwargs?.id) {
|
|
90
|
+
generationId = chunk.lc_kwargs.id;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
parser.flushRemaining();
|
|
94
|
+
|
|
95
|
+
// Submit final update if there's pending content
|
|
96
|
+
if (expectedAiScratchpad !== fullContent) {
|
|
97
|
+
updateAIScratchpad(shareDBDoc, chatId, fullContent);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
content: fullContent,
|
|
102
|
+
generationId: generationId,
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates incoming request data for AI chat messages
|
|
3
|
+
*/
|
|
4
|
+
export const validateRequest = (req, res) => {
|
|
5
|
+
const { content, chatId } = req.body;
|
|
6
|
+
|
|
7
|
+
if (!content || typeof content !== 'string') {
|
|
8
|
+
res.status(400).json({
|
|
9
|
+
error:
|
|
10
|
+
'Invalid request: content is required and must be a string',
|
|
11
|
+
});
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!chatId || typeof chatId !== 'string') {
|
|
16
|
+
res.status(400).json({
|
|
17
|
+
error:
|
|
18
|
+
'Invalid request: chatId is required and must be a string',
|
|
19
|
+
});
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return true;
|
|
24
|
+
};
|
package/src/server/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { json1Presence } from '../ot.js';
|
|
|
14
14
|
import { computeInitialDocument } from './computeInitialDocument.js';
|
|
15
15
|
import { handleAIAssist } from './handleAIAssist.js';
|
|
16
16
|
import { handleAICopilot } from './handleAICopilot.js';
|
|
17
|
+
import { handleAIChatMessage } from './handleAIChatMessage.js';
|
|
17
18
|
import { isDirectory } from './isDirectory.js';
|
|
18
19
|
import { createToken } from './livekit.js';
|
|
19
20
|
import './setupEnv.js';
|
|
@@ -112,6 +113,13 @@ app.post(
|
|
|
112
113
|
handleAICopilot(),
|
|
113
114
|
);
|
|
114
115
|
|
|
116
|
+
// Handle AI Chat Message requests.
|
|
117
|
+
app.post(
|
|
118
|
+
'/ai-chat-message',
|
|
119
|
+
bodyParser.json(),
|
|
120
|
+
handleAIChatMessage(shareDBDoc),
|
|
121
|
+
);
|
|
122
|
+
|
|
115
123
|
// Livekit Token Generator
|
|
116
124
|
app.get('/livekit-token', async (req, res) => {
|
|
117
125
|
const { room, username } = req.query;
|