@lvce-editor/chat-view 3.0.0 → 3.2.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.
@@ -1508,7 +1508,7 @@ const createDefaultState = () => {
1508
1508
  messages: [],
1509
1509
  title: defaultSessionTitle()
1510
1510
  }],
1511
- streamingEnabled: false,
1511
+ streamingEnabled: true,
1512
1512
  tokensMax: 0,
1513
1513
  tokensUsed: 0,
1514
1514
  uid: 0,
@@ -2740,18 +2740,28 @@ const deleteSession = async (state, id) => {
2740
2740
  };
2741
2741
 
2742
2742
  const handleClickOpenApiApiKeySettings = async state => {
2743
- // Open the built-in settings editor so the user can inspect or edit their OpenAI API key.
2744
2743
  await invoke('Main.openUri', 'app://settings.json');
2745
2744
  return state;
2746
2745
  };
2747
2746
 
2747
+ const handleClickOpenApiApiKeyWebsite = async state => {
2748
+ await openExternal(state.openApiApiKeysSettingsUrl);
2749
+ return state;
2750
+ };
2751
+
2748
2752
  const handleClickOpenRouterApiKeySettings = async state => {
2753
+ await invoke('Main.openUri', 'app://settings.json');
2754
+ return state;
2755
+ };
2756
+
2757
+ const handleClickOpenRouterApiKeyWebsite = async state => {
2749
2758
  await openExternal(state.openRouterApiKeysSettingsUrl);
2750
2759
  return state;
2751
2760
  };
2752
2761
 
2753
2762
  const openApiApiKeyRequiredMessage = 'OpenAI API key is not configured. Enter your OpenAI API key below and click Save.';
2754
2763
  const openApiRequestFailedMessage = 'OpenAI request failed.';
2764
+ const openApiRequestFailedOfflineMessage = 'OpenAI request failed because you are offline. Please check your internet connection.';
2755
2765
  const openRouterApiKeyRequiredMessage = 'OpenRouter API key is not configured. Enter your OpenRouter API key below and click Save.';
2756
2766
  const openRouterRequestFailedMessage = 'OpenRouter request failed. Possible reasons:';
2757
2767
  const openRouterTooManyRequestsMessage = 'OpenRouter rate limit reached (429). Please try again soon. Helpful tips:';
@@ -4270,6 +4280,12 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
4270
4280
  };
4271
4281
  };
4272
4282
 
4283
+ const isOffline = () => {
4284
+ if (!globalThis.navigator) {
4285
+ return false;
4286
+ }
4287
+ return globalThis.navigator.onLine === false;
4288
+ };
4273
4289
  const getOpenApiErrorMessage = errorResult => {
4274
4290
  switch (errorResult.details) {
4275
4291
  case 'http-error':
@@ -4281,7 +4297,7 @@ const getOpenApiErrorMessage = errorResult => {
4281
4297
  // Provide a concise, user-friendly message when OpenAI reports an invalid API key.
4282
4298
  if (errorResult.errorCode === 'invalid_api_key') {
4283
4299
  const status = typeof errorResult.statusCode === 'number' ? errorResult.statusCode : 401;
4284
- return `OpenAI request failed (status ${status}): Invalid API key. Please verify your OpenAI API key in Chat settings.`;
4300
+ return `OpenAI request failed (Status ${status}): Invalid API key. Please verify your OpenAI API key in Chat Settings.`;
4285
4301
  }
4286
4302
  if (errorResult.statusCode === 429) {
4287
4303
  let prefix = 'OpenAI rate limit exceeded (429)';
@@ -4317,6 +4333,9 @@ const getOpenApiErrorMessage = errorResult => {
4317
4333
  return openApiRequestFailedMessage;
4318
4334
  }
4319
4335
  case 'request-failed':
4336
+ if (isOffline()) {
4337
+ return openApiRequestFailedOfflineMessage;
4338
+ }
4320
4339
  return openApiRequestFailedMessage;
4321
4340
  }
4322
4341
  };
@@ -4644,7 +4663,7 @@ const getAiResponse = async ({
4644
4663
  passIncludeObfuscation = false,
4645
4664
  platform,
4646
4665
  selectedModelId,
4647
- streamingEnabled = false,
4666
+ streamingEnabled = true,
4648
4667
  useMockApi,
4649
4668
  userText,
4650
4669
  webSearchEnabled = false
@@ -4788,6 +4807,7 @@ const handleClickSaveOpenApiApiKey = async state => {
4788
4807
  openRouterApiKey: updatedState.openRouterApiKey,
4789
4808
  platform: updatedState.platform,
4790
4809
  selectedModelId: updatedState.selectedModelId,
4810
+ streamingEnabled: updatedState.streamingEnabled,
4791
4811
  useMockApi: updatedState.useMockApi,
4792
4812
  userText: previousUserMessage.text
4793
4813
  });
@@ -5275,11 +5295,13 @@ const getRenameIdFromInputName = name => {
5275
5295
  const OpenApiApiKeyInput = 'open-api-api-key';
5276
5296
  const SaveOpenApiApiKey = 'save-openapi-api-key';
5277
5297
  const OpenOpenApiApiKeySettings = 'open-openapi-api-key-settings';
5298
+ const OpenOpenApiApiKeyWebsite = 'open-openapi-api-key-website';
5278
5299
 
5279
5300
  // cspell:ignore openrouter
5280
5301
  const OpenRouterApiKeyInput = 'open-router-api-key';
5281
5302
  const SaveOpenRouterApiKey = 'save-openrouter-api-key';
5282
5303
  const OpenOpenRouterApiKeySettings = 'open-openrouter-api-key-settings';
5304
+ const OpenOpenRouterApiKeyWebsite = 'open-openrouter-api-key-website';
5283
5305
 
5284
5306
  const selectSession = async (state, id) => {
5285
5307
  const exists = state.sessions.some(session => session.id === id);
@@ -5368,9 +5390,15 @@ const handleClick = async (state, name, id = '') => {
5368
5390
  if (name === OpenOpenRouterApiKeySettings) {
5369
5391
  return handleClickOpenRouterApiKeySettings(state);
5370
5392
  }
5393
+ if (name === OpenOpenRouterApiKeyWebsite) {
5394
+ return handleClickOpenRouterApiKeyWebsite(state);
5395
+ }
5371
5396
  if (name === OpenOpenApiApiKeySettings) {
5372
5397
  return handleClickOpenApiApiKeySettings(state);
5373
5398
  }
5399
+ if (name === OpenOpenApiApiKeyWebsite) {
5400
+ return handleClickOpenApiApiKeyWebsite(state);
5401
+ }
5374
5402
  return state;
5375
5403
  };
5376
5404
 
@@ -5724,9 +5752,9 @@ const loadPassIncludeObfuscation = async () => {
5724
5752
  const loadStreamingEnabled = async () => {
5725
5753
  try {
5726
5754
  const savedStreamingEnabled = await get('chatView.streamingEnabled');
5727
- return typeof savedStreamingEnabled === 'boolean' ? savedStreamingEnabled : false;
5755
+ return typeof savedStreamingEnabled === 'boolean' ? savedStreamingEnabled : true;
5728
5756
  } catch {
5729
- return false;
5757
+ return true;
5730
5758
  }
5731
5759
  };
5732
5760
 
@@ -5938,6 +5966,25 @@ const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessage
5938
5966
  color: #4d94ff;
5939
5967
  text-decoration: underline;
5940
5968
  cursor: pointer;
5969
+ }
5970
+
5971
+ .MarkdownTable {
5972
+ width: 100%;
5973
+ margin: 6px 0;
5974
+ border-collapse: collapse;
5975
+ border: 1px solid var(--vscode-editorWidget-border);
5976
+ }
5977
+
5978
+ .MarkdownTable th,
5979
+ .MarkdownTable td {
5980
+ border: 1px solid var(--vscode-editorWidget-border);
5981
+ padding: 4px 8px;
5982
+ text-align: left;
5983
+ }
5984
+
5985
+ .MarkdownTable th {
5986
+ background: var(--vscode-editorWidget-background);
5987
+ }
5941
5988
  }`;
5942
5989
  if (!renderHtmlCss.trim()) {
5943
5990
  return baseCss;
@@ -6013,6 +6060,7 @@ const ChatListEmpty = 'ChatListEmpty';
6013
6060
  const ChatListItem = 'ChatListItem';
6014
6061
  const ChatListItemLabel = 'ChatListItemLabel';
6015
6062
  const Markdown = 'Markdown';
6063
+ const MarkdownTable = 'MarkdownTable';
6016
6064
  const Message = 'Message';
6017
6065
  const ChatMessageContent = 'ChatMessageContent';
6018
6066
  const ChatToolCalls = 'ChatToolCalls';
@@ -6250,6 +6298,12 @@ const getInlineNodeDom = inlineNode => {
6250
6298
  if (inlineNode.type === 'text') {
6251
6299
  return [text(inlineNode.text)];
6252
6300
  }
6301
+ if (inlineNode.type === 'bold') {
6302
+ return [{
6303
+ childCount: 1,
6304
+ type: Strong
6305
+ }, text(inlineNode.text)];
6306
+ }
6253
6307
  return [{
6254
6308
  childCount: 1,
6255
6309
  className: ChatMessageLink,
@@ -6261,6 +6315,15 @@ const getInlineNodeDom = inlineNode => {
6261
6315
  }, text(inlineNode.text)];
6262
6316
  };
6263
6317
 
6318
+ const getCodeBlockDom = node => {
6319
+ return [{
6320
+ childCount: 1,
6321
+ type: Pre
6322
+ }, {
6323
+ childCount: 1,
6324
+ type: Code
6325
+ }, text(node.text)];
6326
+ };
6264
6327
  const getOrderedListItemDom = item => {
6265
6328
  return [{
6266
6329
  childCount: item.children.length,
@@ -6268,6 +6331,40 @@ const getOrderedListItemDom = item => {
6268
6331
  type: Li
6269
6332
  }, ...item.children.flatMap(getInlineNodeDom)];
6270
6333
  };
6334
+ const getTableHeadCellDom = cell => {
6335
+ return [{
6336
+ childCount: cell.children.length,
6337
+ type: Th
6338
+ }, ...cell.children.flatMap(getInlineNodeDom)];
6339
+ };
6340
+ const getTableBodyCellDom = cell => {
6341
+ return [{
6342
+ childCount: cell.children.length,
6343
+ type: Td
6344
+ }, ...cell.children.flatMap(getInlineNodeDom)];
6345
+ };
6346
+ const getTableRowDom = row => {
6347
+ return [{
6348
+ childCount: row.cells.length,
6349
+ type: Tr
6350
+ }, ...row.cells.flatMap(getTableBodyCellDom)];
6351
+ };
6352
+ const getTableDom = node => {
6353
+ return [{
6354
+ childCount: 2,
6355
+ className: MarkdownTable,
6356
+ type: Table
6357
+ }, {
6358
+ childCount: 1,
6359
+ type: THead
6360
+ }, {
6361
+ childCount: node.headers.length,
6362
+ type: Tr
6363
+ }, ...node.headers.flatMap(getTableHeadCellDom), {
6364
+ childCount: node.rows.length,
6365
+ type: TBody
6366
+ }, ...node.rows.flatMap(getTableRowDom)];
6367
+ };
6271
6368
  const getMessageNodeDom = node => {
6272
6369
  if (node.type === 'text') {
6273
6370
  return [{
@@ -6276,6 +6373,12 @@ const getMessageNodeDom = node => {
6276
6373
  type: P
6277
6374
  }, ...node.children.flatMap(getInlineNodeDom)];
6278
6375
  }
6376
+ if (node.type === 'table') {
6377
+ return getTableDom(node);
6378
+ }
6379
+ if (node.type === 'code-block') {
6380
+ return getCodeBlockDom(node);
6381
+ }
6279
6382
  return [{
6280
6383
  childCount: node.items.length,
6281
6384
  className: ChatOrderedList,
@@ -6785,7 +6888,7 @@ const getToolCallRenderHtmlVirtualDom = toolCall => {
6785
6888
  };
6786
6889
 
6787
6890
  const getToolCallDom = toolCall => {
6788
- if (toolCall.name === 'read_file') {
6891
+ if (toolCall.name === 'read_file' || toolCall.name === 'list_files' || toolCall.name === 'list_file') {
6789
6892
  const virtualDom = getToolCallReadFileVirtualDom(toolCall);
6790
6893
  if (virtualDom.length > 0) {
6791
6894
  return virtualDom;
@@ -6826,15 +6929,62 @@ const getToolCallsDom = message => {
6826
6929
  };
6827
6930
 
6828
6931
  const orderedListItemRegex = /^\s*\d+\.\s+(.*)$/;
6829
- const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
6932
+ const markdownInlineRegex = /\[([^\]]+)\]\(([^)]+)\)|\*\*([^*]+)\*\*/g;
6933
+ const markdownTableSeparatorCellRegex = /^:?-{3,}:?$/;
6934
+ const fencedCodeBlockRegex = /^```/;
6935
+ const normalizeInlineTables = value => {
6936
+ return value.split(/\r?\n/).map(line => {
6937
+ if (!line.includes('|')) {
6938
+ return line;
6939
+ }
6940
+ if (!/\|\s*[-:]{3,}/.test(line)) {
6941
+ return line;
6942
+ }
6943
+ return line.replaceAll(/\|\s+\|/g, '|\n|');
6944
+ }).join('\n');
6945
+ };
6946
+ const isTableRow = line => {
6947
+ const trimmed = line.trim();
6948
+ if (!trimmed.startsWith('|') || !trimmed.endsWith('|')) {
6949
+ return false;
6950
+ }
6951
+ return trimmed.length > 2 && trimmed.slice(1, -1).includes('|');
6952
+ };
6953
+ const getTableCells = line => {
6954
+ const trimmed = line.trim();
6955
+ return trimmed.slice(1, -1).split('|').map(part => part.trim());
6956
+ };
6957
+ const isTableSeparatorRow = (line, expectedColumns) => {
6958
+ if (!isTableRow(line)) {
6959
+ return false;
6960
+ }
6961
+ const cells = getTableCells(line);
6962
+ if (cells.length !== expectedColumns) {
6963
+ return false;
6964
+ }
6965
+ return cells.every(cell => markdownTableSeparatorCellRegex.test(cell));
6966
+ };
6967
+ const toTableCell = value => {
6968
+ return {
6969
+ children: parseInlineNodes(value),
6970
+ type: 'table-cell'
6971
+ };
6972
+ };
6973
+ const toTableRow = line => {
6974
+ return {
6975
+ cells: getTableCells(line).map(toTableCell),
6976
+ type: 'table-row'
6977
+ };
6978
+ };
6830
6979
  const parseInlineNodes = value => {
6831
- const matches = value.matchAll(markdownLinkRegex);
6980
+ const matches = value.matchAll(markdownInlineRegex);
6832
6981
  const nodes = [];
6833
6982
  let lastIndex = 0;
6834
6983
  for (const match of matches) {
6835
6984
  const fullMatch = match[0];
6836
6985
  const linkText = match[1];
6837
6986
  const href = match[2];
6987
+ const boldText = match[3];
6838
6988
  const index = match.index ?? 0;
6839
6989
  if (index > lastIndex) {
6840
6990
  nodes.push({
@@ -6842,11 +6992,18 @@ const parseInlineNodes = value => {
6842
6992
  type: 'text'
6843
6993
  });
6844
6994
  }
6845
- nodes.push({
6846
- href,
6847
- text: linkText,
6848
- type: 'link'
6849
- });
6995
+ if (linkText && href) {
6996
+ nodes.push({
6997
+ href,
6998
+ text: linkText,
6999
+ type: 'link'
7000
+ });
7001
+ } else if (boldText) {
7002
+ nodes.push({
7003
+ text: boldText,
7004
+ type: 'bold'
7005
+ });
7006
+ }
6850
7007
  lastIndex = index + fullMatch.length;
6851
7008
  }
6852
7009
  if (lastIndex < value.length) {
@@ -6873,7 +7030,7 @@ const parseMessageContent = rawMessage => {
6873
7030
  type: 'text'
6874
7031
  }];
6875
7032
  }
6876
- const lines = rawMessage.split(/\r?\n/);
7033
+ const lines = normalizeInlineTables(rawMessage).split(/\r?\n/);
6877
7034
  const nodes = [];
6878
7035
  let paragraphLines = [];
6879
7036
  let listItems = [];
@@ -6897,12 +7054,51 @@ const parseMessageContent = rawMessage => {
6897
7054
  });
6898
7055
  listItems = [];
6899
7056
  };
6900
- for (const line of lines) {
7057
+ for (let i = 0; i < lines.length; i++) {
7058
+ const line = lines[i];
6901
7059
  if (!line.trim()) {
6902
7060
  flushList();
6903
7061
  flushParagraph();
6904
7062
  continue;
6905
7063
  }
7064
+ if (fencedCodeBlockRegex.test(line.trim())) {
7065
+ flushList();
7066
+ flushParagraph();
7067
+ const codeLines = [];
7068
+ i++;
7069
+ while (i < lines.length && !fencedCodeBlockRegex.test(lines[i].trim())) {
7070
+ codeLines.push(lines[i]);
7071
+ i++;
7072
+ }
7073
+ nodes.push({
7074
+ text: codeLines.join('\n'),
7075
+ type: 'code-block'
7076
+ });
7077
+ continue;
7078
+ }
7079
+ if (isTableRow(line) && i + 1 < lines.length) {
7080
+ const headerCells = getTableCells(line);
7081
+ if (isTableSeparatorRow(lines[i + 1], headerCells.length)) {
7082
+ flushList();
7083
+ flushParagraph();
7084
+ const rows = [];
7085
+ i += 2;
7086
+ while (i < lines.length && isTableRow(lines[i])) {
7087
+ const row = toTableRow(lines[i]);
7088
+ if (row.cells.length === headerCells.length) {
7089
+ rows.push(row);
7090
+ }
7091
+ i++;
7092
+ }
7093
+ i--;
7094
+ nodes.push({
7095
+ headers: headerCells.map(toTableCell),
7096
+ rows,
7097
+ type: 'table'
7098
+ });
7099
+ continue;
7100
+ }
7101
+ }
6906
7102
  const match = line.match(orderedListItemRegex);
6907
7103
  if (match) {
6908
7104
  flushParagraph();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",