@lvce-editor/chat-view 6.2.0 → 6.4.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.
@@ -3336,7 +3336,7 @@ const executeChatTool = async (name, rawArguments, options) => {
3336
3336
  const getReadFileTool = () => {
3337
3337
  return {
3338
3338
  function: {
3339
- description: 'Read UTF-8 text content from a file inside the currently open workspace folder. Only pass an absolute URI.',
3339
+ description: 'Read UTF-8 text content from a file inside the currently open workspace folder. Only pass an absolute URI. When you reference files in your response, use markdown links like [index.ts](file:///workspace/src/index.ts).',
3340
3340
  name: 'read_file',
3341
3341
  parameters: {
3342
3342
  additionalProperties: false,
@@ -4042,6 +4042,25 @@ const getToolCallExecutionStatus = content => {
4042
4042
  status: 'error'
4043
4043
  };
4044
4044
  };
4045
+ const getToolCallResult = (name, content) => {
4046
+ if (name !== 'getWorkspaceUri') {
4047
+ return undefined;
4048
+ }
4049
+ let parsed;
4050
+ try {
4051
+ parsed = JSON.parse(content);
4052
+ } catch {
4053
+ return undefined;
4054
+ }
4055
+ if (!parsed || typeof parsed !== 'object') {
4056
+ return undefined;
4057
+ }
4058
+ const workspaceUri = Reflect.get(parsed, 'workspaceUri');
4059
+ if (typeof workspaceUri !== 'string' || !workspaceUri) {
4060
+ return undefined;
4061
+ }
4062
+ return workspaceUri;
4063
+ };
4045
4064
  const getResponseOutputText = parsed => {
4046
4065
  if (!parsed || typeof parsed !== 'object') {
4047
4066
  return '';
@@ -4645,6 +4664,7 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
4645
4664
  useChatToolWorker
4646
4665
  });
4647
4666
  const executionStatus = getToolCallExecutionStatus(content);
4667
+ const toolCallResult = getToolCallResult(toolCall.name, content);
4648
4668
  executedToolCalls.push({
4649
4669
  arguments: toolCall.arguments,
4650
4670
  ...(executionStatus.errorStack ? {
@@ -4658,6 +4678,9 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
4658
4678
  } : {}),
4659
4679
  id: toolCall.callId,
4660
4680
  name: toolCall.name,
4681
+ ...(toolCallResult ? {
4682
+ result: toolCallResult
4683
+ } : {}),
4661
4684
  ...(executionStatus.status ? {
4662
4685
  status: executionStatus.status
4663
4686
  } : {})
@@ -4790,6 +4813,7 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
4790
4813
  useChatToolWorker
4791
4814
  });
4792
4815
  const executionStatus = getToolCallExecutionStatus(content);
4816
+ const toolCallResult = getToolCallResult(toolCall.name, content);
4793
4817
  executedToolCalls.push({
4794
4818
  arguments: toolCall.arguments,
4795
4819
  ...(executionStatus.errorStack ? {
@@ -4803,6 +4827,9 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
4803
4827
  } : {}),
4804
4828
  id: toolCall.callId,
4805
4829
  name: toolCall.name,
4830
+ ...(toolCallResult ? {
4831
+ result: toolCallResult
4832
+ } : {}),
4806
4833
  ...(executionStatus.status ? {
4807
4834
  status: executionStatus.status
4808
4835
  } : {})
@@ -4863,6 +4890,7 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
4863
4890
  }) : '{}';
4864
4891
  if (typeof name === 'string') {
4865
4892
  const executionStatus = getToolCallExecutionStatus(content);
4893
+ const toolCallResult = getToolCallResult(name, content);
4866
4894
  executedToolCalls.push({
4867
4895
  arguments: typeof rawArguments === 'string' ? rawArguments : '',
4868
4896
  ...(executionStatus.errorStack ? {
@@ -4876,6 +4904,9 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
4876
4904
  } : {}),
4877
4905
  id,
4878
4906
  name,
4907
+ ...(toolCallResult ? {
4908
+ result: toolCallResult
4909
+ } : {}),
4879
4910
  ...(executionStatus.status ? {
4880
4911
  status: executionStatus.status
4881
4912
  } : {})
@@ -5756,7 +5787,14 @@ const isAlphaNumeric = value => {
5756
5787
  }
5757
5788
  return code >= 97 && code <= 122;
5758
5789
  };
5759
- const sanitizeUrl = url => {
5790
+ const sanitizeLinkUrl = url => {
5791
+ const normalized = url.trim().toLowerCase();
5792
+ if (normalized.startsWith('http://') || normalized.startsWith('https://') || normalized.startsWith('file://')) {
5793
+ return url;
5794
+ }
5795
+ return '#';
5796
+ };
5797
+ const sanitizeImageUrl = url => {
5760
5798
  const normalized = url.trim().toLowerCase();
5761
5799
  if (normalized.startsWith('http://') || normalized.startsWith('https://')) {
5762
5800
  return url;
@@ -5794,7 +5832,7 @@ const parseLinkToken = (value, start) => {
5794
5832
  return {
5795
5833
  length: index - start + 1,
5796
5834
  node: {
5797
- href: sanitizeUrl(href),
5835
+ href: sanitizeLinkUrl(href),
5798
5836
  text,
5799
5837
  type: 'link'
5800
5838
  }
@@ -5837,7 +5875,7 @@ const parseImageToken = (value, start) => {
5837
5875
  length: index - start + 1,
5838
5876
  node: {
5839
5877
  alt,
5840
- src: sanitizeUrl(src),
5878
+ src: sanitizeImageUrl(src),
5841
5879
  type: 'image'
5842
5880
  }
5843
5881
  };
@@ -5870,9 +5908,6 @@ const parseBoldToken = (value, start) => {
5870
5908
  const findItalicEnd = (value, start) => {
5871
5909
  let index = start + 1;
5872
5910
  while (index < value.length) {
5873
- if (value[index] === '\n') {
5874
- return -1;
5875
- }
5876
5911
  if (value[index] !== '*') {
5877
5912
  index++;
5878
5913
  continue;
@@ -5897,7 +5932,7 @@ const parseItalicToken = (value, start) => {
5897
5932
  return undefined;
5898
5933
  }
5899
5934
  const text = value.slice(start + 1, end);
5900
- if (!text || text.includes('\n')) {
5935
+ if (!text) {
5901
5936
  return undefined;
5902
5937
  }
5903
5938
  return {
@@ -5928,6 +5963,26 @@ const parseStrikethroughToken = (value, start) => {
5928
5963
  }
5929
5964
  };
5930
5965
  };
5966
+ const parseInlineCodeToken = (value, start) => {
5967
+ if (value[start] !== '`') {
5968
+ return undefined;
5969
+ }
5970
+ const end = value.indexOf('`', start + 1);
5971
+ if (end === -1) {
5972
+ return undefined;
5973
+ }
5974
+ const codeText = value.slice(start + 1, end);
5975
+ if (!codeText || codeText.includes('\n')) {
5976
+ return undefined;
5977
+ }
5978
+ return {
5979
+ length: end - start + 1,
5980
+ node: {
5981
+ text: codeText,
5982
+ type: 'inline-code'
5983
+ }
5984
+ };
5985
+ };
5931
5986
  const parseMathToken = (value, start) => {
5932
5987
  if (value[start] !== '$') {
5933
5988
  return undefined;
@@ -5974,7 +6029,7 @@ const parseMathToken = (value, start) => {
5974
6029
  return undefined;
5975
6030
  };
5976
6031
  const parseInlineToken = (value, start) => {
5977
- return parseImageToken(value, start) || parseLinkToken(value, start) || parseBoldToken(value, start) || parseItalicToken(value, start) || parseLinkToken(value, start) || parseBoldToken(value, start) || parseItalicToken(value, start) || parseStrikethroughToken(value, start) || parseMathToken(value, start);
6032
+ return parseImageToken(value, start) || parseLinkToken(value, start) || parseBoldToken(value, start) || parseItalicToken(value, start) || parseLinkToken(value, start) || parseBoldToken(value, start) || parseItalicToken(value, start) || parseStrikethroughToken(value, start) || parseInlineCodeToken(value, start) || parseMathToken(value, start);
5978
6033
  };
5979
6034
  const parseInlineNodes = value => {
5980
6035
  const nodes = [];
@@ -6299,7 +6354,6 @@ const parseBlockTokens = tokens => {
6299
6354
  for (let i = 0; i < tokens.length; i++) {
6300
6355
  const token = tokens[i];
6301
6356
  if (token.type === 'blank-line') {
6302
- flushList();
6303
6357
  flushParagraph();
6304
6358
  continue;
6305
6359
  }
@@ -6842,6 +6896,33 @@ const getSseEventType = value => {
6842
6896
  return value && typeof value === 'object' && Reflect.get(value, 'type') === 'response.completed' ? 'sse-response-completed' : 'sse-response-part';
6843
6897
  };
6844
6898
 
6899
+ const getToolCallMergeKey = toolCall => {
6900
+ if (toolCall.id) {
6901
+ return `id:${toolCall.id}`;
6902
+ }
6903
+ return `value:${toolCall.name}:${toolCall.arguments}`;
6904
+ };
6905
+ const mergeToolCalls = (existing = [], incoming) => {
6906
+ if (incoming.length === 0) {
6907
+ return existing;
6908
+ }
6909
+ const merged = [...existing];
6910
+ const indexByKey = new Map();
6911
+ for (let i = 0; i < merged.length; i++) {
6912
+ indexByKey.set(getToolCallMergeKey(merged[i]), i);
6913
+ }
6914
+ for (const toolCall of incoming) {
6915
+ const key = getToolCallMergeKey(toolCall);
6916
+ const existingIndex = indexByKey.get(key);
6917
+ if (existingIndex === undefined) {
6918
+ indexByKey.set(key, merged.length);
6919
+ merged.push(toolCall);
6920
+ continue;
6921
+ }
6922
+ merged[existingIndex] = toolCall;
6923
+ }
6924
+ return merged;
6925
+ };
6845
6926
  const updateMessageTextInSelectedSession = async (sessions, parsedMessages, selectedSessionId, messageId, text, inProgress) => {
6846
6927
  let updatedMessage;
6847
6928
  const updatedSessions = sessions.map(session => {
@@ -6886,7 +6967,7 @@ const updateMessageToolCallsInSelectedSession = (sessions, parsedMessages, selec
6886
6967
  }
6887
6968
  const updatedMessage = {
6888
6969
  ...message,
6889
- toolCalls
6970
+ toolCalls: mergeToolCalls(message.toolCalls, toolCalls)
6890
6971
  };
6891
6972
  nextParsedMessages = copyParsedMessageContent(nextParsedMessages, message.id, updatedMessage.id);
6892
6973
  return updatedMessage;
@@ -7743,6 +7824,13 @@ const handleMessagesContextMenu = async state => {
7743
7824
  return state;
7744
7825
  };
7745
7826
 
7827
+ const handleMissingApiKeySubmit = async (state, submitterName = '') => {
7828
+ if (!submitterName) {
7829
+ return state;
7830
+ }
7831
+ return handleClick(state, submitterName);
7832
+ };
7833
+
7746
7834
  const handleModelChange = async (state, value) => {
7747
7835
  return {
7748
7836
  ...state,
@@ -8568,6 +8656,30 @@ const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessage
8568
8656
  .StrikeThrough {
8569
8657
  text-decoration: line-through;
8570
8658
  }
8659
+
8660
+ /* syntax highlight token colors */
8661
+ .TokenComment {
8662
+ color: var(--ColorSymbolIconColorForeground, #7f8794);
8663
+ }
8664
+
8665
+ .TokenString {
8666
+ color: var(--ColorChartsGreen, #a6d189);
8667
+ }
8668
+
8669
+ .TokenNumber,
8670
+ .TokenValue {
8671
+ color: var(--ColorChartsBlue, #8caaee);
8672
+ }
8673
+
8674
+ .TokenKeyword,
8675
+ .TokenTag {
8676
+ color: var(--ColorChartsPurple, #ca9ee6);
8677
+ }
8678
+
8679
+ .TokenAttribute,
8680
+ .TokenProperty {
8681
+ color: var(--ColorChartsOrange, #ef9f76);
8682
+ }
8571
8683
  `;
8572
8684
  if (!renderHtmlCss.trim()) {
8573
8685
  return baseCss;
@@ -8723,6 +8835,7 @@ const HandleDragOverChatView = 31;
8723
8835
  const HandleProjectListScroll = 32;
8724
8836
  const HandleProjectListContextMenu = 33;
8725
8837
  const HandleClickDictationButton = 34;
8838
+ const HandleMissingApiKeySubmit = 35;
8726
8839
 
8727
8840
  const getModelLabel = model => {
8728
8841
  if (model.provider === 'openRouter') {
@@ -8857,6 +8970,9 @@ const getImageAltText = alt => {
8857
8970
  }
8858
8971
  return `${alt} (image could not be loaded)`;
8859
8972
  };
8973
+ const isFileUri = href => {
8974
+ return href.trim().toLowerCase().startsWith('file://');
8975
+ };
8860
8976
  const getInlineNodeDom = (inlineNode, useChatMathWorker = false) => {
8861
8977
  if (inlineNode.type === 'text') {
8862
8978
  return [text(inlineNode.text)];
@@ -8889,6 +9005,12 @@ const getInlineNodeDom = (inlineNode, useChatMathWorker = false) => {
8889
9005
  type: Span
8890
9006
  }, ...inlineNode.children.flatMap(child => getInlineNodeDom(child, useChatMathWorker))];
8891
9007
  }
9008
+ if (inlineNode.type === 'inline-code') {
9009
+ return [{
9010
+ childCount: 1,
9011
+ type: Code
9012
+ }, text(inlineNode.text)];
9013
+ }
8892
9014
  if (inlineNode.type === 'math-inline') {
8893
9015
  const fallback = inlineNode.displayMode ? `$$${inlineNode.text}$$` : `$${inlineNode.text}$`;
8894
9016
  return [text(fallback)];
@@ -8896,6 +9018,17 @@ const getInlineNodeDom = (inlineNode, useChatMathWorker = false) => {
8896
9018
  if (inlineNode.type === 'math-inline-dom') {
8897
9019
  return inlineNode.dom;
8898
9020
  }
9021
+ if (isFileUri(inlineNode.href)) {
9022
+ return [{
9023
+ childCount: 1,
9024
+ className: ChatMessageLink,
9025
+ 'data-uri': inlineNode.href,
9026
+ href: '#',
9027
+ onClick: HandleClickReadFile,
9028
+ title: inlineNode.href,
9029
+ type: A
9030
+ }, text(inlineNode.text)];
9031
+ }
8899
9032
  return [{
8900
9033
  childCount: 1,
8901
9034
  className: ChatMessageLink,
@@ -8970,6 +9103,22 @@ const cssRules = [{
8970
9103
  className: TokenProperty,
8971
9104
  regex: /[a-zA-Z-]+(?=\s*:)/
8972
9105
  }];
9106
+ const pythonRules = [{
9107
+ className: TokenComment,
9108
+ regex: /#[^\n]*/
9109
+ }, {
9110
+ className: TokenString,
9111
+ regex: /"[^"\\]*(?:\\.[^"\\]*)*"/
9112
+ }, {
9113
+ className: TokenString,
9114
+ regex: /'[^'\\]*(?:\\.[^'\\]*)*'/
9115
+ }, {
9116
+ className: TokenNumber,
9117
+ regex: /\b\d+\.?\d*\b/
9118
+ }, {
9119
+ className: TokenKeyword,
9120
+ regex: /\b(?:def|class|return|if|elif|else|for|while|in|import|from|as|with|try|except|finally|raise|lambda|yield|pass|break|continue|True|False|None|and|or|not|is)\b/
9121
+ }];
8973
9122
  const tokenize = (code, rules) => {
8974
9123
  const tokens = [];
8975
9124
  let pos = 0;
@@ -9023,6 +9172,9 @@ const highlightCode = (code, language) => {
9023
9172
  if (normalized === 'js' || normalized === 'javascript') {
9024
9173
  return tokenize(code, jsRules);
9025
9174
  }
9175
+ if (normalized === 'py' || normalized === 'python') {
9176
+ return tokenize(code, pythonRules);
9177
+ }
9026
9178
  return [{
9027
9179
  className: '',
9028
9180
  text: code
@@ -9197,6 +9349,8 @@ const getMissingApiKeyDom = ({
9197
9349
  }) => {
9198
9350
  return [{
9199
9351
  childCount: 2,
9352
+ method: useForm ? 'GET' : undefined,
9353
+ onSubmit: useForm ? HandleMissingApiKeySubmit : undefined,
9200
9354
  type: useForm ? Form : Div
9201
9355
  }, {
9202
9356
  childCount: 0,
@@ -9218,7 +9372,7 @@ const getMissingApiKeyDom = ({
9218
9372
  className: mergeClassNames(Button, ButtonPrimary),
9219
9373
  disabled: saveButtonDisabled,
9220
9374
  name: saveButtonName,
9221
- onClick: HandleClick,
9375
+ onClick: useForm ? undefined : HandleClick,
9222
9376
  type: Button$1
9223
9377
  }, text(saveButtonText), {
9224
9378
  childCount: 1,
@@ -9255,7 +9409,9 @@ const getMissingOpenRouterApiKeyDom = (openRouterApiKeyInput, openRouterApiKeySt
9255
9409
  placeholder: openRouterApiKeyPlaceholder(),
9256
9410
  saveButtonDisabled: isSaving,
9257
9411
  saveButtonName: SaveOpenRouterApiKey,
9258
- saveButtonText: isSaving ? saving() : save()
9412
+ saveButtonText: isSaving ? saving() : save(),
9413
+ saveButtonType: 'submit',
9414
+ useForm: true
9259
9415
  });
9260
9416
  };
9261
9417
 
@@ -9746,6 +9902,9 @@ const getToolCallDisplayName = name => {
9746
9902
  };
9747
9903
  const getToolCallLabel = toolCall => {
9748
9904
  const displayName = getToolCallDisplayName(toolCall.name);
9905
+ if (toolCall.name === 'getWorkspaceUri' && toolCall.result) {
9906
+ return `${displayName} ${toolCall.result}`;
9907
+ }
9749
9908
  const argumentPreview = getToolCallArgumentPreview(toolCall.arguments);
9750
9909
  const statusLabel = getToolCallStatusLabel(toolCall);
9751
9910
  if (argumentPreview === '{}') {
@@ -10259,7 +10418,19 @@ const renderItems = (oldState, newState) => {
10259
10418
  return [SetDom2, uid, dom];
10260
10419
  };
10261
10420
 
10421
+ const getSelectedSessionToolCallSignature = state => {
10422
+ const selectedSession = state.sessions.find(session => session.id === state.selectedSessionId);
10423
+ if (!selectedSession) {
10424
+ return '';
10425
+ }
10426
+ return selectedSession.messages.map(message => `${message.id}:${JSON.stringify(message.toolCalls || [])}`).join('|');
10427
+ };
10262
10428
  const renderIncremental = (oldState, newState) => {
10429
+ const oldToolCallSignature = getSelectedSessionToolCallSignature(oldState);
10430
+ const newToolCallSignature = getSelectedSessionToolCallSignature(newState);
10431
+ if (oldToolCallSignature !== newToolCallSignature) {
10432
+ return renderItems(oldState, newState);
10433
+ }
10263
10434
  const oldDom = renderItems(oldState, oldState)[2];
10264
10435
  const newDom = renderItems(newState, newState)[2];
10265
10436
  const patches = diffTree(oldDom, newDom);
@@ -10473,6 +10644,10 @@ const renderEventListeners = () => {
10473
10644
  }, {
10474
10645
  name: HandleSubmit,
10475
10646
  params: ['handleSubmit']
10647
+ }, {
10648
+ name: HandleMissingApiKeySubmit,
10649
+ params: ['handleMissingApiKeySubmit', 'event.submitter?.name || ""'],
10650
+ preventDefault: true
10476
10651
  }];
10477
10652
  };
10478
10653
 
@@ -10658,6 +10833,7 @@ const commandMap = {
10658
10833
  'Chat.handleKeyDown': wrapCommand(handleKeyDown),
10659
10834
  'Chat.handleMessagesContextMenu': wrapCommand(handleMessagesContextMenu),
10660
10835
  'Chat.handleMessagesScroll': wrapCommand(handleMessagesScroll),
10836
+ 'Chat.handleMissingApiKeySubmit': wrapCommand(handleMissingApiKeySubmit),
10661
10837
  'Chat.handleModelChange': wrapCommand(handleModelChange),
10662
10838
  'Chat.handleProjectListContextMenu': wrapCommand(handleProjectListContextMenu),
10663
10839
  'Chat.handleProjectListScroll': wrapCommand(handleProjectListScroll),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "6.2.0",
3
+ "version": "6.4.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",