@lvce-editor/chat-view 3.1.0 → 3.3.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/chatViewWorkerMain.js +368 -18
- package/package.json +1 -1
|
@@ -1463,6 +1463,7 @@ const createDefaultState = () => {
|
|
|
1463
1463
|
const composerFontSize = 13;
|
|
1464
1464
|
const composerLineHeight = 20;
|
|
1465
1465
|
return {
|
|
1466
|
+
aiSessionTitleGenerationEnabled: false,
|
|
1466
1467
|
assetDir: '',
|
|
1467
1468
|
chatListScrollTop: 0,
|
|
1468
1469
|
chatMessageFontFamily: 'system-ui',
|
|
@@ -1508,7 +1509,7 @@ const createDefaultState = () => {
|
|
|
1508
1509
|
messages: [],
|
|
1509
1510
|
title: defaultSessionTitle()
|
|
1510
1511
|
}],
|
|
1511
|
-
streamingEnabled:
|
|
1512
|
+
streamingEnabled: true,
|
|
1512
1513
|
tokensMax: 0,
|
|
1513
1514
|
tokensUsed: 0,
|
|
1514
1515
|
uid: 0,
|
|
@@ -4663,7 +4664,7 @@ const getAiResponse = async ({
|
|
|
4663
4664
|
passIncludeObfuscation = false,
|
|
4664
4665
|
platform,
|
|
4665
4666
|
selectedModelId,
|
|
4666
|
-
streamingEnabled =
|
|
4667
|
+
streamingEnabled = true,
|
|
4667
4668
|
useMockApi,
|
|
4668
4669
|
userText,
|
|
4669
4670
|
webSearchEnabled = false
|
|
@@ -4807,6 +4808,7 @@ const handleClickSaveOpenApiApiKey = async state => {
|
|
|
4807
4808
|
openRouterApiKey: updatedState.openRouterApiKey,
|
|
4808
4809
|
platform: updatedState.platform,
|
|
4809
4810
|
selectedModelId: updatedState.selectedModelId,
|
|
4811
|
+
streamingEnabled: updatedState.streamingEnabled,
|
|
4810
4812
|
useMockApi: updatedState.useMockApi,
|
|
4811
4813
|
userText: previousUserMessage.text
|
|
4812
4814
|
});
|
|
@@ -5014,6 +5016,10 @@ const handleToolCallsChunkFunction = async (uid, assistantMessageId, toolCalls,
|
|
|
5014
5016
|
};
|
|
5015
5017
|
};
|
|
5016
5018
|
|
|
5019
|
+
const slashCommandRegex = /^\/(clear|export|help|new)(?:\s+.*)?$/;
|
|
5020
|
+
const mentionRegex = /(^|\s)@([^\s]+)/g;
|
|
5021
|
+
const maxMentionCount = 5;
|
|
5022
|
+
const maxMentionTextLength = 8000;
|
|
5017
5023
|
const appendMessageToSelectedSession = (sessions, selectedSessionId, message) => {
|
|
5018
5024
|
return sessions.map(session => {
|
|
5019
5025
|
if (session.id !== selectedSessionId) {
|
|
@@ -5067,8 +5073,208 @@ const isStreamingFunctionCallEvent = parsed => {
|
|
|
5067
5073
|
const getSseEventType = value => {
|
|
5068
5074
|
return value && typeof value === 'object' && Reflect.get(value, 'type') === 'response.completed' ? 'sse-response-completed' : 'sse-response-part';
|
|
5069
5075
|
};
|
|
5076
|
+
const getCommandHelpText = () => {
|
|
5077
|
+
return ['Available commands:', '/new - Create and switch to a new chat session.', '/clear - Clear messages in the selected chat session.', '/export - Export current chat session as Markdown.', '/help - Show this help.'].join('\n');
|
|
5078
|
+
};
|
|
5079
|
+
const withClearedComposer = state => {
|
|
5080
|
+
return focusInput({
|
|
5081
|
+
...state,
|
|
5082
|
+
composerHeight: getMinComposerHeightForState(state),
|
|
5083
|
+
composerValue: '',
|
|
5084
|
+
inputSource: 'script'
|
|
5085
|
+
});
|
|
5086
|
+
};
|
|
5087
|
+
const toMarkdownTranscript = session => {
|
|
5088
|
+
const lines = [`# ${session.title}`, ''];
|
|
5089
|
+
for (const message of session.messages) {
|
|
5090
|
+
const role = message.role === 'assistant' ? 'Assistant' : 'User';
|
|
5091
|
+
lines.push(`## ${role}`);
|
|
5092
|
+
lines.push(message.text || '(empty)');
|
|
5093
|
+
lines.push('');
|
|
5094
|
+
}
|
|
5095
|
+
return lines.join('\n').trim();
|
|
5096
|
+
};
|
|
5097
|
+
const executeSlashCommand = async (state, command) => {
|
|
5098
|
+
if (command === 'new') {
|
|
5099
|
+
const nextState = await createSession(state);
|
|
5100
|
+
return withClearedComposer({
|
|
5101
|
+
...nextState,
|
|
5102
|
+
viewMode: 'detail'
|
|
5103
|
+
});
|
|
5104
|
+
}
|
|
5105
|
+
const selectedSession = state.sessions.find(session => session.id === state.selectedSessionId);
|
|
5106
|
+
if (!selectedSession) {
|
|
5107
|
+
return withClearedComposer(state);
|
|
5108
|
+
}
|
|
5109
|
+
if (command === 'clear') {
|
|
5110
|
+
const updatedSessions = state.sessions.map(session => {
|
|
5111
|
+
if (session.id !== state.selectedSessionId) {
|
|
5112
|
+
return session;
|
|
5113
|
+
}
|
|
5114
|
+
return {
|
|
5115
|
+
...session,
|
|
5116
|
+
messages: []
|
|
5117
|
+
};
|
|
5118
|
+
});
|
|
5119
|
+
const updatedSelectedSession = updatedSessions.find(session => session.id === state.selectedSessionId);
|
|
5120
|
+
if (updatedSelectedSession) {
|
|
5121
|
+
await saveChatSession(updatedSelectedSession);
|
|
5122
|
+
}
|
|
5123
|
+
return withClearedComposer({
|
|
5124
|
+
...state,
|
|
5125
|
+
sessions: updatedSessions
|
|
5126
|
+
});
|
|
5127
|
+
}
|
|
5128
|
+
const assistantText = command === 'help' ? getCommandHelpText() : ['```md', toMarkdownTranscript(selectedSession), '```'].join('\n');
|
|
5129
|
+
const assistantMessage = {
|
|
5130
|
+
id: crypto.randomUUID(),
|
|
5131
|
+
role: 'assistant',
|
|
5132
|
+
text: assistantText,
|
|
5133
|
+
time: new Date().toLocaleTimeString([], {
|
|
5134
|
+
hour: '2-digit',
|
|
5135
|
+
minute: '2-digit'
|
|
5136
|
+
})
|
|
5137
|
+
};
|
|
5138
|
+
const updatedSessions = appendMessageToSelectedSession(state.sessions, state.selectedSessionId, assistantMessage);
|
|
5139
|
+
const updatedSelectedSession = updatedSessions.find(session => session.id === state.selectedSessionId);
|
|
5140
|
+
if (updatedSelectedSession) {
|
|
5141
|
+
await saveChatSession(updatedSelectedSession);
|
|
5142
|
+
}
|
|
5143
|
+
return withClearedComposer({
|
|
5144
|
+
...state,
|
|
5145
|
+
sessions: updatedSessions
|
|
5146
|
+
});
|
|
5147
|
+
};
|
|
5148
|
+
const getSlashCommand = value => {
|
|
5149
|
+
const trimmed = value.trim();
|
|
5150
|
+
const match = trimmed.match(slashCommandRegex);
|
|
5151
|
+
if (!match) {
|
|
5152
|
+
return undefined;
|
|
5153
|
+
}
|
|
5154
|
+
return match[1];
|
|
5155
|
+
};
|
|
5156
|
+
const parseMentionedPaths = value => {
|
|
5157
|
+
const matches = value.matchAll(mentionRegex);
|
|
5158
|
+
const paths = [];
|
|
5159
|
+
for (const match of matches) {
|
|
5160
|
+
const rawPath = match[2] || '';
|
|
5161
|
+
const cleanedPath = rawPath.replaceAll(/[),.;:!?]+$/g, '');
|
|
5162
|
+
if (!cleanedPath || isPathTraversalAttempt(cleanedPath)) {
|
|
5163
|
+
continue;
|
|
5164
|
+
}
|
|
5165
|
+
const normalizedPath = normalizeRelativePath(cleanedPath);
|
|
5166
|
+
if (paths.includes(normalizedPath)) {
|
|
5167
|
+
continue;
|
|
5168
|
+
}
|
|
5169
|
+
paths.push(normalizedPath);
|
|
5170
|
+
if (paths.length >= maxMentionCount) {
|
|
5171
|
+
break;
|
|
5172
|
+
}
|
|
5173
|
+
}
|
|
5174
|
+
return paths;
|
|
5175
|
+
};
|
|
5176
|
+
const getMentionContextMessage = async value => {
|
|
5177
|
+
const paths = parseMentionedPaths(value);
|
|
5178
|
+
if (paths.length === 0) {
|
|
5179
|
+
return undefined;
|
|
5180
|
+
}
|
|
5181
|
+
const sections = [];
|
|
5182
|
+
for (const path of paths) {
|
|
5183
|
+
try {
|
|
5184
|
+
const fileContent = await readFile(path);
|
|
5185
|
+
const truncatedContent = fileContent.length > maxMentionTextLength ? `${fileContent.slice(0, maxMentionTextLength)}\n... [truncated]` : fileContent;
|
|
5186
|
+
sections.push([`File: ${path}`, '```text', truncatedContent, '```'].join('\n'));
|
|
5187
|
+
} catch (error) {
|
|
5188
|
+
sections.push([`File: ${path}`, `Error: ${String(error)}`].join('\n'));
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
return {
|
|
5192
|
+
id: crypto.randomUUID(),
|
|
5193
|
+
role: 'user',
|
|
5194
|
+
text: ['Referenced file context:', ...sections].join('\n\n'),
|
|
5195
|
+
time: new Date().toLocaleTimeString([], {
|
|
5196
|
+
hour: '2-digit',
|
|
5197
|
+
minute: '2-digit'
|
|
5198
|
+
})
|
|
5199
|
+
};
|
|
5200
|
+
};
|
|
5201
|
+
const isDefaultSessionTitle = title => {
|
|
5202
|
+
return /^Chat \d+$/.test(title);
|
|
5203
|
+
};
|
|
5204
|
+
const sanitizeGeneratedTitle = value => {
|
|
5205
|
+
return value.replace(/^title:\s*/i, '').replaceAll(/^['"`\s]+|['"`\s]+$/g, '').replaceAll(/\s+/g, ' ').trim().slice(0, 80);
|
|
5206
|
+
};
|
|
5207
|
+
const updateSessionTitle = (sessions, selectedSessionId, title) => {
|
|
5208
|
+
return sessions.map(session => {
|
|
5209
|
+
if (session.id !== selectedSessionId) {
|
|
5210
|
+
return session;
|
|
5211
|
+
}
|
|
5212
|
+
return {
|
|
5213
|
+
...session,
|
|
5214
|
+
title
|
|
5215
|
+
};
|
|
5216
|
+
});
|
|
5217
|
+
};
|
|
5218
|
+
const getAiSessionTitle = async (state, userText, assistantText) => {
|
|
5219
|
+
const {
|
|
5220
|
+
models,
|
|
5221
|
+
openApiApiBaseUrl,
|
|
5222
|
+
openApiApiKey,
|
|
5223
|
+
openRouterApiBaseUrl,
|
|
5224
|
+
openRouterApiKey,
|
|
5225
|
+
selectedModelId,
|
|
5226
|
+
useMockApi
|
|
5227
|
+
} = state;
|
|
5228
|
+
if (useMockApi) {
|
|
5229
|
+
return '';
|
|
5230
|
+
}
|
|
5231
|
+
const usesOpenApiModel = isOpenApiModel(selectedModelId, models);
|
|
5232
|
+
const usesOpenRouterModel = isOpenRouterModel(selectedModelId, models);
|
|
5233
|
+
if (usesOpenApiModel && !openApiApiKey) {
|
|
5234
|
+
return '';
|
|
5235
|
+
}
|
|
5236
|
+
if (usesOpenRouterModel && !openRouterApiKey) {
|
|
5237
|
+
return '';
|
|
5238
|
+
}
|
|
5239
|
+
if (!usesOpenApiModel && !usesOpenRouterModel) {
|
|
5240
|
+
return '';
|
|
5241
|
+
}
|
|
5242
|
+
const titlePrompt = `Create a concise title (max 6 words) for this conversation. Respond only with the title, no punctuation at the end.
|
|
5243
|
+
User: ${userText}
|
|
5244
|
+
Assistant: ${assistantText}`;
|
|
5245
|
+
const promptMessage = {
|
|
5246
|
+
id: crypto.randomUUID(),
|
|
5247
|
+
role: 'user',
|
|
5248
|
+
text: titlePrompt,
|
|
5249
|
+
time: new Date().toLocaleTimeString([], {
|
|
5250
|
+
hour: '2-digit',
|
|
5251
|
+
minute: '2-digit'
|
|
5252
|
+
})
|
|
5253
|
+
};
|
|
5254
|
+
const titleResponse = await getAiResponse({
|
|
5255
|
+
assetDir: state.assetDir,
|
|
5256
|
+
messages: [promptMessage],
|
|
5257
|
+
mockAiResponseDelay: state.mockAiResponseDelay,
|
|
5258
|
+
mockApiCommandId: state.mockApiCommandId,
|
|
5259
|
+
models,
|
|
5260
|
+
openApiApiBaseUrl,
|
|
5261
|
+
openApiApiKey,
|
|
5262
|
+
openRouterApiBaseUrl,
|
|
5263
|
+
openRouterApiKey,
|
|
5264
|
+
passIncludeObfuscation: state.passIncludeObfuscation,
|
|
5265
|
+
platform: state.platform,
|
|
5266
|
+
selectedModelId,
|
|
5267
|
+
streamingEnabled: false,
|
|
5268
|
+
useMockApi,
|
|
5269
|
+
userText: titlePrompt,
|
|
5270
|
+
webSearchEnabled: false
|
|
5271
|
+
});
|
|
5272
|
+
const title = sanitizeGeneratedTitle(titleResponse.text);
|
|
5273
|
+
return title && !isDefaultSessionTitle(title) ? title : '';
|
|
5274
|
+
};
|
|
5070
5275
|
const handleSubmit = async state => {
|
|
5071
5276
|
const {
|
|
5277
|
+
aiSessionTitleGenerationEnabled,
|
|
5072
5278
|
assetDir,
|
|
5073
5279
|
composerValue,
|
|
5074
5280
|
emitStreamingFunctionCallEvents,
|
|
@@ -5094,6 +5300,10 @@ const handleSubmit = async state => {
|
|
|
5094
5300
|
if (!userText) {
|
|
5095
5301
|
return state;
|
|
5096
5302
|
}
|
|
5303
|
+
const slashCommand = getSlashCommand(userText);
|
|
5304
|
+
if (slashCommand) {
|
|
5305
|
+
return executeSlashCommand(state, slashCommand);
|
|
5306
|
+
}
|
|
5097
5307
|
const userTime = new Date().toLocaleTimeString([], {
|
|
5098
5308
|
hour: '2-digit',
|
|
5099
5309
|
minute: '2-digit'
|
|
@@ -5130,6 +5340,7 @@ const handleSubmit = async state => {
|
|
|
5130
5340
|
}
|
|
5131
5341
|
}
|
|
5132
5342
|
let optimisticState;
|
|
5343
|
+
const createsNewSession = viewMode === 'list';
|
|
5133
5344
|
if (viewMode === 'list') {
|
|
5134
5345
|
const newSessionId = generateSessionId();
|
|
5135
5346
|
await appendChatViewEvent({
|
|
@@ -5187,13 +5398,15 @@ const handleSubmit = async state => {
|
|
|
5187
5398
|
};
|
|
5188
5399
|
const selectedOptimisticSession = optimisticState.sessions.find(session => session.id === optimisticState.selectedSessionId);
|
|
5189
5400
|
const messages = (selectedOptimisticSession?.messages ?? []).filter(message => !message.inProgress);
|
|
5401
|
+
const mentionContextMessage = await getMentionContextMessage(userText);
|
|
5402
|
+
const messagesWithMentionContext = mentionContextMessage ? [...messages, mentionContextMessage] : messages;
|
|
5190
5403
|
const handleTextChunkFunctionRef = streamingEnabled ? async chunk => {
|
|
5191
5404
|
handleTextChunkState = await handleTextChunkFunction(state.uid, assistantMessageId, chunk, handleTextChunkState);
|
|
5192
5405
|
} : undefined;
|
|
5193
5406
|
const assistantMessage = await getAiResponse({
|
|
5194
5407
|
assetDir,
|
|
5195
5408
|
messageId: assistantMessageId,
|
|
5196
|
-
messages,
|
|
5409
|
+
messages: messagesWithMentionContext,
|
|
5197
5410
|
mockAiResponseDelay,
|
|
5198
5411
|
mockApiCommandId,
|
|
5199
5412
|
models,
|
|
@@ -5238,7 +5451,16 @@ const handleSubmit = async state => {
|
|
|
5238
5451
|
const {
|
|
5239
5452
|
latestState
|
|
5240
5453
|
} = handleTextChunkState;
|
|
5241
|
-
|
|
5454
|
+
let updatedSessions = streamingEnabled ? updateMessageTextInSelectedSession(latestState.sessions, latestState.selectedSessionId, assistantMessageId, assistantMessage.text, false) : appendMessageToSelectedSession(latestState.sessions, latestState.selectedSessionId, assistantMessage);
|
|
5455
|
+
if (aiSessionTitleGenerationEnabled && createsNewSession) {
|
|
5456
|
+
const selectedSession = updatedSessions.find(session => session.id === latestState.selectedSessionId);
|
|
5457
|
+
if (selectedSession && isDefaultSessionTitle(selectedSession.title)) {
|
|
5458
|
+
const generatedTitle = await getAiSessionTitle(latestState, userText, assistantMessage.text);
|
|
5459
|
+
if (generatedTitle) {
|
|
5460
|
+
updatedSessions = updateSessionTitle(updatedSessions, latestState.selectedSessionId, generatedTitle);
|
|
5461
|
+
}
|
|
5462
|
+
}
|
|
5463
|
+
}
|
|
5242
5464
|
const selectedSession = updatedSessions.find(session => session.id === latestState.selectedSessionId);
|
|
5243
5465
|
if (selectedSession) {
|
|
5244
5466
|
await saveChatSession(selectedSession);
|
|
@@ -5704,6 +5926,15 @@ const getSavedViewMode = savedState => {
|
|
|
5704
5926
|
return viewMode;
|
|
5705
5927
|
};
|
|
5706
5928
|
|
|
5929
|
+
const loadAiSessionTitleGenerationEnabled = async () => {
|
|
5930
|
+
try {
|
|
5931
|
+
const savedAiSessionTitleGenerationEnabled = await get('chatView.aiSessionTitleGenerationEnabled');
|
|
5932
|
+
return typeof savedAiSessionTitleGenerationEnabled === 'boolean' ? savedAiSessionTitleGenerationEnabled : false;
|
|
5933
|
+
} catch {
|
|
5934
|
+
return false;
|
|
5935
|
+
}
|
|
5936
|
+
};
|
|
5937
|
+
|
|
5707
5938
|
const loadEmitStreamingFunctionCallEvents = async () => {
|
|
5708
5939
|
try {
|
|
5709
5940
|
const savedEmitStreamingFunctionCallEvents = await get('chatView.emitStreamingFunctionCallEvents');
|
|
@@ -5751,19 +5982,16 @@ const loadPassIncludeObfuscation = async () => {
|
|
|
5751
5982
|
const loadStreamingEnabled = async () => {
|
|
5752
5983
|
try {
|
|
5753
5984
|
const savedStreamingEnabled = await get('chatView.streamingEnabled');
|
|
5754
|
-
return typeof savedStreamingEnabled === 'boolean' ? savedStreamingEnabled :
|
|
5985
|
+
return typeof savedStreamingEnabled === 'boolean' ? savedStreamingEnabled : true;
|
|
5755
5986
|
} catch {
|
|
5756
|
-
return
|
|
5987
|
+
return true;
|
|
5757
5988
|
}
|
|
5758
5989
|
};
|
|
5759
5990
|
|
|
5760
5991
|
const loadPreferences = async () => {
|
|
5761
|
-
const openApiApiKey = await loadOpenApiApiKey();
|
|
5762
|
-
const openRouterApiKey = await loadOpenRouterApiKey();
|
|
5763
|
-
const emitStreamingFunctionCallEvents = await loadEmitStreamingFunctionCallEvents();
|
|
5764
|
-
const streamingEnabled = await loadStreamingEnabled();
|
|
5765
|
-
const passIncludeObfuscation = await loadPassIncludeObfuscation();
|
|
5992
|
+
const [aiSessionTitleGenerationEnabled, openApiApiKey, openRouterApiKey, emitStreamingFunctionCallEvents, streamingEnabled, passIncludeObfuscation] = await Promise.all([loadAiSessionTitleGenerationEnabled(), loadOpenApiApiKey(), loadOpenRouterApiKey(), loadEmitStreamingFunctionCallEvents(), loadStreamingEnabled(), loadPassIncludeObfuscation()]);
|
|
5766
5993
|
return {
|
|
5994
|
+
aiSessionTitleGenerationEnabled,
|
|
5767
5995
|
emitStreamingFunctionCallEvents,
|
|
5768
5996
|
openApiApiKey,
|
|
5769
5997
|
openRouterApiKey,
|
|
@@ -5799,6 +6027,7 @@ const loadContent = async (state, savedState) => {
|
|
|
5799
6027
|
const savedSelectedModelId = getSavedSelectedModelId(savedState);
|
|
5800
6028
|
const savedViewMode = getSavedViewMode(savedState);
|
|
5801
6029
|
const {
|
|
6030
|
+
aiSessionTitleGenerationEnabled,
|
|
5802
6031
|
emitStreamingFunctionCallEvents,
|
|
5803
6032
|
openApiApiKey,
|
|
5804
6033
|
openRouterApiKey,
|
|
@@ -5831,6 +6060,7 @@ const loadContent = async (state, savedState) => {
|
|
|
5831
6060
|
const viewMode = sessions.length === 0 || !selectedSessionId ? 'list' : preferredViewMode === 'detail' ? 'detail' : 'list';
|
|
5832
6061
|
return {
|
|
5833
6062
|
...state,
|
|
6063
|
+
aiSessionTitleGenerationEnabled,
|
|
5834
6064
|
chatListScrollTop,
|
|
5835
6065
|
emitStreamingFunctionCallEvents,
|
|
5836
6066
|
initial: false,
|
|
@@ -5902,6 +6132,13 @@ const openMockSession = async (state, mockSessionId, mockChatMessages) => {
|
|
|
5902
6132
|
};
|
|
5903
6133
|
};
|
|
5904
6134
|
|
|
6135
|
+
const registerMockResponse = (state, mockResponse) => {
|
|
6136
|
+
reset$1();
|
|
6137
|
+
pushChunk(mockResponse.text);
|
|
6138
|
+
finish();
|
|
6139
|
+
return state;
|
|
6140
|
+
};
|
|
6141
|
+
|
|
5905
6142
|
const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessageLineHeight, chatMessageFontFamily, renderHtmlCss) => {
|
|
5906
6143
|
const baseCss = `:root {
|
|
5907
6144
|
--ChatInputBoxHeight: ${composerHeight}px;
|
|
@@ -5967,6 +6204,17 @@ const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessage
|
|
|
5967
6204
|
cursor: pointer;
|
|
5968
6205
|
}
|
|
5969
6206
|
|
|
6207
|
+
.ChatOrderedList,
|
|
6208
|
+
.ChatUnorderedList {
|
|
6209
|
+
margin: 6px 0;
|
|
6210
|
+
padding-inline-start: 20px;
|
|
6211
|
+
}
|
|
6212
|
+
|
|
6213
|
+
.ChatOrderedListItem,
|
|
6214
|
+
.ChatUnorderedListItem {
|
|
6215
|
+
margin: 2px 0;
|
|
6216
|
+
}
|
|
6217
|
+
|
|
5970
6218
|
.MarkdownTable {
|
|
5971
6219
|
width: 100%;
|
|
5972
6220
|
margin: 6px 0;
|
|
@@ -6071,6 +6319,8 @@ const ChatToolCallRenderHtmlBody = 'ChatToolCallRenderHtmlBody';
|
|
|
6071
6319
|
const ChatMessageLink = 'ChatMessageLink';
|
|
6072
6320
|
const ChatOrderedList = 'ChatOrderedList';
|
|
6073
6321
|
const ChatOrderedListItem = 'ChatOrderedListItem';
|
|
6322
|
+
const ChatUnorderedList = 'ChatUnorderedList';
|
|
6323
|
+
const ChatUnorderedListItem = 'ChatUnorderedListItem';
|
|
6074
6324
|
const MessageUser = 'MessageUser';
|
|
6075
6325
|
const MessageAssistant = 'MessageAssistant';
|
|
6076
6326
|
const MultilineInputBox = 'MultilineInputBox';
|
|
@@ -6314,6 +6564,15 @@ const getInlineNodeDom = inlineNode => {
|
|
|
6314
6564
|
}, text(inlineNode.text)];
|
|
6315
6565
|
};
|
|
6316
6566
|
|
|
6567
|
+
const getCodeBlockDom = node => {
|
|
6568
|
+
return [{
|
|
6569
|
+
childCount: 1,
|
|
6570
|
+
type: Pre
|
|
6571
|
+
}, {
|
|
6572
|
+
childCount: 1,
|
|
6573
|
+
type: Code
|
|
6574
|
+
}, text(node.text)];
|
|
6575
|
+
};
|
|
6317
6576
|
const getOrderedListItemDom = item => {
|
|
6318
6577
|
return [{
|
|
6319
6578
|
childCount: item.children.length,
|
|
@@ -6321,6 +6580,13 @@ const getOrderedListItemDom = item => {
|
|
|
6321
6580
|
type: Li
|
|
6322
6581
|
}, ...item.children.flatMap(getInlineNodeDom)];
|
|
6323
6582
|
};
|
|
6583
|
+
const getUnorderedListItemDom = item => {
|
|
6584
|
+
return [{
|
|
6585
|
+
childCount: item.children.length,
|
|
6586
|
+
className: ChatUnorderedListItem,
|
|
6587
|
+
type: Li
|
|
6588
|
+
}, ...item.children.flatMap(getInlineNodeDom)];
|
|
6589
|
+
};
|
|
6324
6590
|
const getTableHeadCellDom = cell => {
|
|
6325
6591
|
return [{
|
|
6326
6592
|
childCount: cell.children.length,
|
|
@@ -6355,6 +6621,28 @@ const getTableDom = node => {
|
|
|
6355
6621
|
type: TBody
|
|
6356
6622
|
}, ...node.rows.flatMap(getTableRowDom)];
|
|
6357
6623
|
};
|
|
6624
|
+
const getHeadingElementType = level => {
|
|
6625
|
+
switch (level) {
|
|
6626
|
+
case 1:
|
|
6627
|
+
return H1;
|
|
6628
|
+
case 2:
|
|
6629
|
+
return H2;
|
|
6630
|
+
case 3:
|
|
6631
|
+
return H3;
|
|
6632
|
+
case 4:
|
|
6633
|
+
return H4;
|
|
6634
|
+
case 5:
|
|
6635
|
+
return H5;
|
|
6636
|
+
case 6:
|
|
6637
|
+
return H6;
|
|
6638
|
+
}
|
|
6639
|
+
};
|
|
6640
|
+
const getHeadingDom = node => {
|
|
6641
|
+
return [{
|
|
6642
|
+
childCount: node.children.length,
|
|
6643
|
+
type: getHeadingElementType(node.level)
|
|
6644
|
+
}, ...node.children.flatMap(getInlineNodeDom)];
|
|
6645
|
+
};
|
|
6358
6646
|
const getMessageNodeDom = node => {
|
|
6359
6647
|
if (node.type === 'text') {
|
|
6360
6648
|
return [{
|
|
@@ -6366,11 +6654,24 @@ const getMessageNodeDom = node => {
|
|
|
6366
6654
|
if (node.type === 'table') {
|
|
6367
6655
|
return getTableDom(node);
|
|
6368
6656
|
}
|
|
6657
|
+
if (node.type === 'code-block') {
|
|
6658
|
+
return getCodeBlockDom(node);
|
|
6659
|
+
}
|
|
6660
|
+
if (node.type === 'heading') {
|
|
6661
|
+
return getHeadingDom(node);
|
|
6662
|
+
}
|
|
6663
|
+
if (node.type === 'ordered-list') {
|
|
6664
|
+
return [{
|
|
6665
|
+
childCount: node.items.length,
|
|
6666
|
+
className: ChatOrderedList,
|
|
6667
|
+
type: Ol
|
|
6668
|
+
}, ...node.items.flatMap(getOrderedListItemDom)];
|
|
6669
|
+
}
|
|
6369
6670
|
return [{
|
|
6370
6671
|
childCount: node.items.length,
|
|
6371
|
-
className:
|
|
6372
|
-
type:
|
|
6373
|
-
}, ...node.items.flatMap(
|
|
6672
|
+
className: ChatUnorderedList,
|
|
6673
|
+
type: Ul
|
|
6674
|
+
}, ...node.items.flatMap(getUnorderedListItemDom)];
|
|
6374
6675
|
};
|
|
6375
6676
|
|
|
6376
6677
|
const getMessageContentDom = nodes => {
|
|
@@ -6916,8 +7217,11 @@ const getToolCallsDom = message => {
|
|
|
6916
7217
|
};
|
|
6917
7218
|
|
|
6918
7219
|
const orderedListItemRegex = /^\s*\d+\.\s+(.*)$/;
|
|
7220
|
+
const unorderedListItemRegex = /^\s*[-*]\s+(.*)$/;
|
|
6919
7221
|
const markdownInlineRegex = /\[([^\]]+)\]\(([^)]+)\)|\*\*([^*]+)\*\*/g;
|
|
6920
7222
|
const markdownTableSeparatorCellRegex = /^:?-{3,}:?$/;
|
|
7223
|
+
const fencedCodeBlockRegex = /^```/;
|
|
7224
|
+
const markdownHeadingRegex = /^\s*(#{1,6})\s+(.*)$/;
|
|
6921
7225
|
const normalizeInlineTables = value => {
|
|
6922
7226
|
return value.split(/\r?\n/).map(line => {
|
|
6923
7227
|
if (!line.includes('|')) {
|
|
@@ -7020,6 +7324,7 @@ const parseMessageContent = rawMessage => {
|
|
|
7020
7324
|
const nodes = [];
|
|
7021
7325
|
let paragraphLines = [];
|
|
7022
7326
|
let listItems = [];
|
|
7327
|
+
let listType = '';
|
|
7023
7328
|
const flushParagraph = () => {
|
|
7024
7329
|
if (paragraphLines.length === 0) {
|
|
7025
7330
|
return;
|
|
@@ -7036,9 +7341,10 @@ const parseMessageContent = rawMessage => {
|
|
|
7036
7341
|
}
|
|
7037
7342
|
nodes.push({
|
|
7038
7343
|
items: listItems,
|
|
7039
|
-
type: 'list'
|
|
7344
|
+
type: listType || 'ordered-list'
|
|
7040
7345
|
});
|
|
7041
7346
|
listItems = [];
|
|
7347
|
+
listType = '';
|
|
7042
7348
|
};
|
|
7043
7349
|
for (let i = 0; i < lines.length; i++) {
|
|
7044
7350
|
const line = lines[i];
|
|
@@ -7047,6 +7353,21 @@ const parseMessageContent = rawMessage => {
|
|
|
7047
7353
|
flushParagraph();
|
|
7048
7354
|
continue;
|
|
7049
7355
|
}
|
|
7356
|
+
if (fencedCodeBlockRegex.test(line.trim())) {
|
|
7357
|
+
flushList();
|
|
7358
|
+
flushParagraph();
|
|
7359
|
+
const codeLines = [];
|
|
7360
|
+
i++;
|
|
7361
|
+
while (i < lines.length && !fencedCodeBlockRegex.test(lines[i].trim())) {
|
|
7362
|
+
codeLines.push(lines[i]);
|
|
7363
|
+
i++;
|
|
7364
|
+
}
|
|
7365
|
+
nodes.push({
|
|
7366
|
+
text: codeLines.join('\n'),
|
|
7367
|
+
type: 'code-block'
|
|
7368
|
+
});
|
|
7369
|
+
continue;
|
|
7370
|
+
}
|
|
7050
7371
|
if (isTableRow(line) && i + 1 < lines.length) {
|
|
7051
7372
|
const headerCells = getTableCells(line);
|
|
7052
7373
|
if (isTableSeparatorRow(lines[i + 1], headerCells.length)) {
|
|
@@ -7070,15 +7391,43 @@ const parseMessageContent = rawMessage => {
|
|
|
7070
7391
|
continue;
|
|
7071
7392
|
}
|
|
7072
7393
|
}
|
|
7073
|
-
const
|
|
7074
|
-
if (
|
|
7394
|
+
const orderedMatch = line.match(orderedListItemRegex);
|
|
7395
|
+
if (orderedMatch) {
|
|
7396
|
+
if (listType && listType !== 'ordered-list') {
|
|
7397
|
+
flushList();
|
|
7398
|
+
}
|
|
7399
|
+
flushParagraph();
|
|
7400
|
+
listType = 'ordered-list';
|
|
7401
|
+
listItems.push({
|
|
7402
|
+
children: parseInlineNodes(orderedMatch[1]),
|
|
7403
|
+
type: 'list-item'
|
|
7404
|
+
});
|
|
7405
|
+
continue;
|
|
7406
|
+
}
|
|
7407
|
+
const unorderedMatch = line.match(unorderedListItemRegex);
|
|
7408
|
+
if (unorderedMatch) {
|
|
7409
|
+
if (listType && listType !== 'unordered-list') {
|
|
7410
|
+
flushList();
|
|
7411
|
+
}
|
|
7075
7412
|
flushParagraph();
|
|
7413
|
+
listType = 'unordered-list';
|
|
7076
7414
|
listItems.push({
|
|
7077
|
-
children: parseInlineNodes(
|
|
7415
|
+
children: parseInlineNodes(unorderedMatch[1]),
|
|
7078
7416
|
type: 'list-item'
|
|
7079
7417
|
});
|
|
7080
7418
|
continue;
|
|
7081
7419
|
}
|
|
7420
|
+
const headingMatch = line.match(markdownHeadingRegex);
|
|
7421
|
+
if (headingMatch) {
|
|
7422
|
+
flushList();
|
|
7423
|
+
flushParagraph();
|
|
7424
|
+
nodes.push({
|
|
7425
|
+
children: parseInlineNodes(headingMatch[2]),
|
|
7426
|
+
level: headingMatch[1].length,
|
|
7427
|
+
type: 'heading'
|
|
7428
|
+
});
|
|
7429
|
+
continue;
|
|
7430
|
+
}
|
|
7082
7431
|
flushList();
|
|
7083
7432
|
paragraphLines.push(line);
|
|
7084
7433
|
}
|
|
@@ -7532,6 +7881,7 @@ const commandMap = {
|
|
|
7532
7881
|
'Chat.mockOpenApiStreamPushChunk': wrapCommand(mockOpenApiStreamPushChunk),
|
|
7533
7882
|
'Chat.mockOpenApiStreamReset': wrapCommand(mockOpenApiStreamReset),
|
|
7534
7883
|
'Chat.openMockSession': wrapCommand(openMockSession),
|
|
7884
|
+
'Chat.registerMockResponse': wrapCommand(registerMockResponse),
|
|
7535
7885
|
'Chat.render2': render2,
|
|
7536
7886
|
'Chat.renderEventListeners': renderEventListeners,
|
|
7537
7887
|
'Chat.rerender': wrapCommand(rerender),
|