@lvce-editor/chat-view 3.2.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 +335 -14
- 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',
|
|
@@ -5015,6 +5016,10 @@ const handleToolCallsChunkFunction = async (uid, assistantMessageId, toolCalls,
|
|
|
5015
5016
|
};
|
|
5016
5017
|
};
|
|
5017
5018
|
|
|
5019
|
+
const slashCommandRegex = /^\/(clear|export|help|new)(?:\s+.*)?$/;
|
|
5020
|
+
const mentionRegex = /(^|\s)@([^\s]+)/g;
|
|
5021
|
+
const maxMentionCount = 5;
|
|
5022
|
+
const maxMentionTextLength = 8000;
|
|
5018
5023
|
const appendMessageToSelectedSession = (sessions, selectedSessionId, message) => {
|
|
5019
5024
|
return sessions.map(session => {
|
|
5020
5025
|
if (session.id !== selectedSessionId) {
|
|
@@ -5068,8 +5073,208 @@ const isStreamingFunctionCallEvent = parsed => {
|
|
|
5068
5073
|
const getSseEventType = value => {
|
|
5069
5074
|
return value && typeof value === 'object' && Reflect.get(value, 'type') === 'response.completed' ? 'sse-response-completed' : 'sse-response-part';
|
|
5070
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
|
+
};
|
|
5071
5275
|
const handleSubmit = async state => {
|
|
5072
5276
|
const {
|
|
5277
|
+
aiSessionTitleGenerationEnabled,
|
|
5073
5278
|
assetDir,
|
|
5074
5279
|
composerValue,
|
|
5075
5280
|
emitStreamingFunctionCallEvents,
|
|
@@ -5095,6 +5300,10 @@ const handleSubmit = async state => {
|
|
|
5095
5300
|
if (!userText) {
|
|
5096
5301
|
return state;
|
|
5097
5302
|
}
|
|
5303
|
+
const slashCommand = getSlashCommand(userText);
|
|
5304
|
+
if (slashCommand) {
|
|
5305
|
+
return executeSlashCommand(state, slashCommand);
|
|
5306
|
+
}
|
|
5098
5307
|
const userTime = new Date().toLocaleTimeString([], {
|
|
5099
5308
|
hour: '2-digit',
|
|
5100
5309
|
minute: '2-digit'
|
|
@@ -5131,6 +5340,7 @@ const handleSubmit = async state => {
|
|
|
5131
5340
|
}
|
|
5132
5341
|
}
|
|
5133
5342
|
let optimisticState;
|
|
5343
|
+
const createsNewSession = viewMode === 'list';
|
|
5134
5344
|
if (viewMode === 'list') {
|
|
5135
5345
|
const newSessionId = generateSessionId();
|
|
5136
5346
|
await appendChatViewEvent({
|
|
@@ -5188,13 +5398,15 @@ const handleSubmit = async state => {
|
|
|
5188
5398
|
};
|
|
5189
5399
|
const selectedOptimisticSession = optimisticState.sessions.find(session => session.id === optimisticState.selectedSessionId);
|
|
5190
5400
|
const messages = (selectedOptimisticSession?.messages ?? []).filter(message => !message.inProgress);
|
|
5401
|
+
const mentionContextMessage = await getMentionContextMessage(userText);
|
|
5402
|
+
const messagesWithMentionContext = mentionContextMessage ? [...messages, mentionContextMessage] : messages;
|
|
5191
5403
|
const handleTextChunkFunctionRef = streamingEnabled ? async chunk => {
|
|
5192
5404
|
handleTextChunkState = await handleTextChunkFunction(state.uid, assistantMessageId, chunk, handleTextChunkState);
|
|
5193
5405
|
} : undefined;
|
|
5194
5406
|
const assistantMessage = await getAiResponse({
|
|
5195
5407
|
assetDir,
|
|
5196
5408
|
messageId: assistantMessageId,
|
|
5197
|
-
messages,
|
|
5409
|
+
messages: messagesWithMentionContext,
|
|
5198
5410
|
mockAiResponseDelay,
|
|
5199
5411
|
mockApiCommandId,
|
|
5200
5412
|
models,
|
|
@@ -5239,7 +5451,16 @@ const handleSubmit = async state => {
|
|
|
5239
5451
|
const {
|
|
5240
5452
|
latestState
|
|
5241
5453
|
} = handleTextChunkState;
|
|
5242
|
-
|
|
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
|
+
}
|
|
5243
5464
|
const selectedSession = updatedSessions.find(session => session.id === latestState.selectedSessionId);
|
|
5244
5465
|
if (selectedSession) {
|
|
5245
5466
|
await saveChatSession(selectedSession);
|
|
@@ -5705,6 +5926,15 @@ const getSavedViewMode = savedState => {
|
|
|
5705
5926
|
return viewMode;
|
|
5706
5927
|
};
|
|
5707
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
|
+
|
|
5708
5938
|
const loadEmitStreamingFunctionCallEvents = async () => {
|
|
5709
5939
|
try {
|
|
5710
5940
|
const savedEmitStreamingFunctionCallEvents = await get('chatView.emitStreamingFunctionCallEvents');
|
|
@@ -5759,12 +5989,9 @@ const loadStreamingEnabled = async () => {
|
|
|
5759
5989
|
};
|
|
5760
5990
|
|
|
5761
5991
|
const loadPreferences = async () => {
|
|
5762
|
-
const openApiApiKey = await loadOpenApiApiKey();
|
|
5763
|
-
const openRouterApiKey = await loadOpenRouterApiKey();
|
|
5764
|
-
const emitStreamingFunctionCallEvents = await loadEmitStreamingFunctionCallEvents();
|
|
5765
|
-
const streamingEnabled = await loadStreamingEnabled();
|
|
5766
|
-
const passIncludeObfuscation = await loadPassIncludeObfuscation();
|
|
5992
|
+
const [aiSessionTitleGenerationEnabled, openApiApiKey, openRouterApiKey, emitStreamingFunctionCallEvents, streamingEnabled, passIncludeObfuscation] = await Promise.all([loadAiSessionTitleGenerationEnabled(), loadOpenApiApiKey(), loadOpenRouterApiKey(), loadEmitStreamingFunctionCallEvents(), loadStreamingEnabled(), loadPassIncludeObfuscation()]);
|
|
5767
5993
|
return {
|
|
5994
|
+
aiSessionTitleGenerationEnabled,
|
|
5768
5995
|
emitStreamingFunctionCallEvents,
|
|
5769
5996
|
openApiApiKey,
|
|
5770
5997
|
openRouterApiKey,
|
|
@@ -5800,6 +6027,7 @@ const loadContent = async (state, savedState) => {
|
|
|
5800
6027
|
const savedSelectedModelId = getSavedSelectedModelId(savedState);
|
|
5801
6028
|
const savedViewMode = getSavedViewMode(savedState);
|
|
5802
6029
|
const {
|
|
6030
|
+
aiSessionTitleGenerationEnabled,
|
|
5803
6031
|
emitStreamingFunctionCallEvents,
|
|
5804
6032
|
openApiApiKey,
|
|
5805
6033
|
openRouterApiKey,
|
|
@@ -5832,6 +6060,7 @@ const loadContent = async (state, savedState) => {
|
|
|
5832
6060
|
const viewMode = sessions.length === 0 || !selectedSessionId ? 'list' : preferredViewMode === 'detail' ? 'detail' : 'list';
|
|
5833
6061
|
return {
|
|
5834
6062
|
...state,
|
|
6063
|
+
aiSessionTitleGenerationEnabled,
|
|
5835
6064
|
chatListScrollTop,
|
|
5836
6065
|
emitStreamingFunctionCallEvents,
|
|
5837
6066
|
initial: false,
|
|
@@ -5903,6 +6132,13 @@ const openMockSession = async (state, mockSessionId, mockChatMessages) => {
|
|
|
5903
6132
|
};
|
|
5904
6133
|
};
|
|
5905
6134
|
|
|
6135
|
+
const registerMockResponse = (state, mockResponse) => {
|
|
6136
|
+
reset$1();
|
|
6137
|
+
pushChunk(mockResponse.text);
|
|
6138
|
+
finish();
|
|
6139
|
+
return state;
|
|
6140
|
+
};
|
|
6141
|
+
|
|
5906
6142
|
const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessageLineHeight, chatMessageFontFamily, renderHtmlCss) => {
|
|
5907
6143
|
const baseCss = `:root {
|
|
5908
6144
|
--ChatInputBoxHeight: ${composerHeight}px;
|
|
@@ -5968,6 +6204,17 @@ const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessage
|
|
|
5968
6204
|
cursor: pointer;
|
|
5969
6205
|
}
|
|
5970
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
|
+
|
|
5971
6218
|
.MarkdownTable {
|
|
5972
6219
|
width: 100%;
|
|
5973
6220
|
margin: 6px 0;
|
|
@@ -6072,6 +6319,8 @@ const ChatToolCallRenderHtmlBody = 'ChatToolCallRenderHtmlBody';
|
|
|
6072
6319
|
const ChatMessageLink = 'ChatMessageLink';
|
|
6073
6320
|
const ChatOrderedList = 'ChatOrderedList';
|
|
6074
6321
|
const ChatOrderedListItem = 'ChatOrderedListItem';
|
|
6322
|
+
const ChatUnorderedList = 'ChatUnorderedList';
|
|
6323
|
+
const ChatUnorderedListItem = 'ChatUnorderedListItem';
|
|
6075
6324
|
const MessageUser = 'MessageUser';
|
|
6076
6325
|
const MessageAssistant = 'MessageAssistant';
|
|
6077
6326
|
const MultilineInputBox = 'MultilineInputBox';
|
|
@@ -6331,6 +6580,13 @@ const getOrderedListItemDom = item => {
|
|
|
6331
6580
|
type: Li
|
|
6332
6581
|
}, ...item.children.flatMap(getInlineNodeDom)];
|
|
6333
6582
|
};
|
|
6583
|
+
const getUnorderedListItemDom = item => {
|
|
6584
|
+
return [{
|
|
6585
|
+
childCount: item.children.length,
|
|
6586
|
+
className: ChatUnorderedListItem,
|
|
6587
|
+
type: Li
|
|
6588
|
+
}, ...item.children.flatMap(getInlineNodeDom)];
|
|
6589
|
+
};
|
|
6334
6590
|
const getTableHeadCellDom = cell => {
|
|
6335
6591
|
return [{
|
|
6336
6592
|
childCount: cell.children.length,
|
|
@@ -6365,6 +6621,28 @@ const getTableDom = node => {
|
|
|
6365
6621
|
type: TBody
|
|
6366
6622
|
}, ...node.rows.flatMap(getTableRowDom)];
|
|
6367
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
|
+
};
|
|
6368
6646
|
const getMessageNodeDom = node => {
|
|
6369
6647
|
if (node.type === 'text') {
|
|
6370
6648
|
return [{
|
|
@@ -6379,11 +6657,21 @@ const getMessageNodeDom = node => {
|
|
|
6379
6657
|
if (node.type === 'code-block') {
|
|
6380
6658
|
return getCodeBlockDom(node);
|
|
6381
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
|
+
}
|
|
6382
6670
|
return [{
|
|
6383
6671
|
childCount: node.items.length,
|
|
6384
|
-
className:
|
|
6385
|
-
type:
|
|
6386
|
-
}, ...node.items.flatMap(
|
|
6672
|
+
className: ChatUnorderedList,
|
|
6673
|
+
type: Ul
|
|
6674
|
+
}, ...node.items.flatMap(getUnorderedListItemDom)];
|
|
6387
6675
|
};
|
|
6388
6676
|
|
|
6389
6677
|
const getMessageContentDom = nodes => {
|
|
@@ -6929,9 +7217,11 @@ const getToolCallsDom = message => {
|
|
|
6929
7217
|
};
|
|
6930
7218
|
|
|
6931
7219
|
const orderedListItemRegex = /^\s*\d+\.\s+(.*)$/;
|
|
7220
|
+
const unorderedListItemRegex = /^\s*[-*]\s+(.*)$/;
|
|
6932
7221
|
const markdownInlineRegex = /\[([^\]]+)\]\(([^)]+)\)|\*\*([^*]+)\*\*/g;
|
|
6933
7222
|
const markdownTableSeparatorCellRegex = /^:?-{3,}:?$/;
|
|
6934
7223
|
const fencedCodeBlockRegex = /^```/;
|
|
7224
|
+
const markdownHeadingRegex = /^\s*(#{1,6})\s+(.*)$/;
|
|
6935
7225
|
const normalizeInlineTables = value => {
|
|
6936
7226
|
return value.split(/\r?\n/).map(line => {
|
|
6937
7227
|
if (!line.includes('|')) {
|
|
@@ -7034,6 +7324,7 @@ const parseMessageContent = rawMessage => {
|
|
|
7034
7324
|
const nodes = [];
|
|
7035
7325
|
let paragraphLines = [];
|
|
7036
7326
|
let listItems = [];
|
|
7327
|
+
let listType = '';
|
|
7037
7328
|
const flushParagraph = () => {
|
|
7038
7329
|
if (paragraphLines.length === 0) {
|
|
7039
7330
|
return;
|
|
@@ -7050,9 +7341,10 @@ const parseMessageContent = rawMessage => {
|
|
|
7050
7341
|
}
|
|
7051
7342
|
nodes.push({
|
|
7052
7343
|
items: listItems,
|
|
7053
|
-
type: 'list'
|
|
7344
|
+
type: listType || 'ordered-list'
|
|
7054
7345
|
});
|
|
7055
7346
|
listItems = [];
|
|
7347
|
+
listType = '';
|
|
7056
7348
|
};
|
|
7057
7349
|
for (let i = 0; i < lines.length; i++) {
|
|
7058
7350
|
const line = lines[i];
|
|
@@ -7099,15 +7391,43 @@ const parseMessageContent = rawMessage => {
|
|
|
7099
7391
|
continue;
|
|
7100
7392
|
}
|
|
7101
7393
|
}
|
|
7102
|
-
const
|
|
7103
|
-
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
|
+
}
|
|
7104
7412
|
flushParagraph();
|
|
7413
|
+
listType = 'unordered-list';
|
|
7105
7414
|
listItems.push({
|
|
7106
|
-
children: parseInlineNodes(
|
|
7415
|
+
children: parseInlineNodes(unorderedMatch[1]),
|
|
7107
7416
|
type: 'list-item'
|
|
7108
7417
|
});
|
|
7109
7418
|
continue;
|
|
7110
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
|
+
}
|
|
7111
7431
|
flushList();
|
|
7112
7432
|
paragraphLines.push(line);
|
|
7113
7433
|
}
|
|
@@ -7561,6 +7881,7 @@ const commandMap = {
|
|
|
7561
7881
|
'Chat.mockOpenApiStreamPushChunk': wrapCommand(mockOpenApiStreamPushChunk),
|
|
7562
7882
|
'Chat.mockOpenApiStreamReset': wrapCommand(mockOpenApiStreamReset),
|
|
7563
7883
|
'Chat.openMockSession': wrapCommand(openMockSession),
|
|
7884
|
+
'Chat.registerMockResponse': wrapCommand(registerMockResponse),
|
|
7564
7885
|
'Chat.render2': render2,
|
|
7565
7886
|
'Chat.renderEventListeners': renderEventListeners,
|
|
7566
7887
|
'Chat.rerender': wrapCommand(rerender),
|