@lvce-editor/chat-view 3.0.0 → 3.1.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.
@@ -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
  };
@@ -5275,11 +5294,13 @@ const getRenameIdFromInputName = name => {
5275
5294
  const OpenApiApiKeyInput = 'open-api-api-key';
5276
5295
  const SaveOpenApiApiKey = 'save-openapi-api-key';
5277
5296
  const OpenOpenApiApiKeySettings = 'open-openapi-api-key-settings';
5297
+ const OpenOpenApiApiKeyWebsite = 'open-openapi-api-key-website';
5278
5298
 
5279
5299
  // cspell:ignore openrouter
5280
5300
  const OpenRouterApiKeyInput = 'open-router-api-key';
5281
5301
  const SaveOpenRouterApiKey = 'save-openrouter-api-key';
5282
5302
  const OpenOpenRouterApiKeySettings = 'open-openrouter-api-key-settings';
5303
+ const OpenOpenRouterApiKeyWebsite = 'open-openrouter-api-key-website';
5283
5304
 
5284
5305
  const selectSession = async (state, id) => {
5285
5306
  const exists = state.sessions.some(session => session.id === id);
@@ -5368,9 +5389,15 @@ const handleClick = async (state, name, id = '') => {
5368
5389
  if (name === OpenOpenRouterApiKeySettings) {
5369
5390
  return handleClickOpenRouterApiKeySettings(state);
5370
5391
  }
5392
+ if (name === OpenOpenRouterApiKeyWebsite) {
5393
+ return handleClickOpenRouterApiKeyWebsite(state);
5394
+ }
5371
5395
  if (name === OpenOpenApiApiKeySettings) {
5372
5396
  return handleClickOpenApiApiKeySettings(state);
5373
5397
  }
5398
+ if (name === OpenOpenApiApiKeyWebsite) {
5399
+ return handleClickOpenApiApiKeyWebsite(state);
5400
+ }
5374
5401
  return state;
5375
5402
  };
5376
5403
 
@@ -5938,6 +5965,25 @@ const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessage
5938
5965
  color: #4d94ff;
5939
5966
  text-decoration: underline;
5940
5967
  cursor: pointer;
5968
+ }
5969
+
5970
+ .MarkdownTable {
5971
+ width: 100%;
5972
+ margin: 6px 0;
5973
+ border-collapse: collapse;
5974
+ border: 1px solid var(--vscode-editorWidget-border);
5975
+ }
5976
+
5977
+ .MarkdownTable th,
5978
+ .MarkdownTable td {
5979
+ border: 1px solid var(--vscode-editorWidget-border);
5980
+ padding: 4px 8px;
5981
+ text-align: left;
5982
+ }
5983
+
5984
+ .MarkdownTable th {
5985
+ background: var(--vscode-editorWidget-background);
5986
+ }
5941
5987
  }`;
5942
5988
  if (!renderHtmlCss.trim()) {
5943
5989
  return baseCss;
@@ -6013,6 +6059,7 @@ const ChatListEmpty = 'ChatListEmpty';
6013
6059
  const ChatListItem = 'ChatListItem';
6014
6060
  const ChatListItemLabel = 'ChatListItemLabel';
6015
6061
  const Markdown = 'Markdown';
6062
+ const MarkdownTable = 'MarkdownTable';
6016
6063
  const Message = 'Message';
6017
6064
  const ChatMessageContent = 'ChatMessageContent';
6018
6065
  const ChatToolCalls = 'ChatToolCalls';
@@ -6250,6 +6297,12 @@ const getInlineNodeDom = inlineNode => {
6250
6297
  if (inlineNode.type === 'text') {
6251
6298
  return [text(inlineNode.text)];
6252
6299
  }
6300
+ if (inlineNode.type === 'bold') {
6301
+ return [{
6302
+ childCount: 1,
6303
+ type: Strong
6304
+ }, text(inlineNode.text)];
6305
+ }
6253
6306
  return [{
6254
6307
  childCount: 1,
6255
6308
  className: ChatMessageLink,
@@ -6268,6 +6321,40 @@ const getOrderedListItemDom = item => {
6268
6321
  type: Li
6269
6322
  }, ...item.children.flatMap(getInlineNodeDom)];
6270
6323
  };
6324
+ const getTableHeadCellDom = cell => {
6325
+ return [{
6326
+ childCount: cell.children.length,
6327
+ type: Th
6328
+ }, ...cell.children.flatMap(getInlineNodeDom)];
6329
+ };
6330
+ const getTableBodyCellDom = cell => {
6331
+ return [{
6332
+ childCount: cell.children.length,
6333
+ type: Td
6334
+ }, ...cell.children.flatMap(getInlineNodeDom)];
6335
+ };
6336
+ const getTableRowDom = row => {
6337
+ return [{
6338
+ childCount: row.cells.length,
6339
+ type: Tr
6340
+ }, ...row.cells.flatMap(getTableBodyCellDom)];
6341
+ };
6342
+ const getTableDom = node => {
6343
+ return [{
6344
+ childCount: 2,
6345
+ className: MarkdownTable,
6346
+ type: Table
6347
+ }, {
6348
+ childCount: 1,
6349
+ type: THead
6350
+ }, {
6351
+ childCount: node.headers.length,
6352
+ type: Tr
6353
+ }, ...node.headers.flatMap(getTableHeadCellDom), {
6354
+ childCount: node.rows.length,
6355
+ type: TBody
6356
+ }, ...node.rows.flatMap(getTableRowDom)];
6357
+ };
6271
6358
  const getMessageNodeDom = node => {
6272
6359
  if (node.type === 'text') {
6273
6360
  return [{
@@ -6276,6 +6363,9 @@ const getMessageNodeDom = node => {
6276
6363
  type: P
6277
6364
  }, ...node.children.flatMap(getInlineNodeDom)];
6278
6365
  }
6366
+ if (node.type === 'table') {
6367
+ return getTableDom(node);
6368
+ }
6279
6369
  return [{
6280
6370
  childCount: node.items.length,
6281
6371
  className: ChatOrderedList,
@@ -6785,7 +6875,7 @@ const getToolCallRenderHtmlVirtualDom = toolCall => {
6785
6875
  };
6786
6876
 
6787
6877
  const getToolCallDom = toolCall => {
6788
- if (toolCall.name === 'read_file') {
6878
+ if (toolCall.name === 'read_file' || toolCall.name === 'list_files' || toolCall.name === 'list_file') {
6789
6879
  const virtualDom = getToolCallReadFileVirtualDom(toolCall);
6790
6880
  if (virtualDom.length > 0) {
6791
6881
  return virtualDom;
@@ -6826,15 +6916,61 @@ const getToolCallsDom = message => {
6826
6916
  };
6827
6917
 
6828
6918
  const orderedListItemRegex = /^\s*\d+\.\s+(.*)$/;
6829
- const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
6919
+ const markdownInlineRegex = /\[([^\]]+)\]\(([^)]+)\)|\*\*([^*]+)\*\*/g;
6920
+ const markdownTableSeparatorCellRegex = /^:?-{3,}:?$/;
6921
+ const normalizeInlineTables = value => {
6922
+ return value.split(/\r?\n/).map(line => {
6923
+ if (!line.includes('|')) {
6924
+ return line;
6925
+ }
6926
+ if (!/\|\s*[-:]{3,}/.test(line)) {
6927
+ return line;
6928
+ }
6929
+ return line.replaceAll(/\|\s+\|/g, '|\n|');
6930
+ }).join('\n');
6931
+ };
6932
+ const isTableRow = line => {
6933
+ const trimmed = line.trim();
6934
+ if (!trimmed.startsWith('|') || !trimmed.endsWith('|')) {
6935
+ return false;
6936
+ }
6937
+ return trimmed.length > 2 && trimmed.slice(1, -1).includes('|');
6938
+ };
6939
+ const getTableCells = line => {
6940
+ const trimmed = line.trim();
6941
+ return trimmed.slice(1, -1).split('|').map(part => part.trim());
6942
+ };
6943
+ const isTableSeparatorRow = (line, expectedColumns) => {
6944
+ if (!isTableRow(line)) {
6945
+ return false;
6946
+ }
6947
+ const cells = getTableCells(line);
6948
+ if (cells.length !== expectedColumns) {
6949
+ return false;
6950
+ }
6951
+ return cells.every(cell => markdownTableSeparatorCellRegex.test(cell));
6952
+ };
6953
+ const toTableCell = value => {
6954
+ return {
6955
+ children: parseInlineNodes(value),
6956
+ type: 'table-cell'
6957
+ };
6958
+ };
6959
+ const toTableRow = line => {
6960
+ return {
6961
+ cells: getTableCells(line).map(toTableCell),
6962
+ type: 'table-row'
6963
+ };
6964
+ };
6830
6965
  const parseInlineNodes = value => {
6831
- const matches = value.matchAll(markdownLinkRegex);
6966
+ const matches = value.matchAll(markdownInlineRegex);
6832
6967
  const nodes = [];
6833
6968
  let lastIndex = 0;
6834
6969
  for (const match of matches) {
6835
6970
  const fullMatch = match[0];
6836
6971
  const linkText = match[1];
6837
6972
  const href = match[2];
6973
+ const boldText = match[3];
6838
6974
  const index = match.index ?? 0;
6839
6975
  if (index > lastIndex) {
6840
6976
  nodes.push({
@@ -6842,11 +6978,18 @@ const parseInlineNodes = value => {
6842
6978
  type: 'text'
6843
6979
  });
6844
6980
  }
6845
- nodes.push({
6846
- href,
6847
- text: linkText,
6848
- type: 'link'
6849
- });
6981
+ if (linkText && href) {
6982
+ nodes.push({
6983
+ href,
6984
+ text: linkText,
6985
+ type: 'link'
6986
+ });
6987
+ } else if (boldText) {
6988
+ nodes.push({
6989
+ text: boldText,
6990
+ type: 'bold'
6991
+ });
6992
+ }
6850
6993
  lastIndex = index + fullMatch.length;
6851
6994
  }
6852
6995
  if (lastIndex < value.length) {
@@ -6873,7 +7016,7 @@ const parseMessageContent = rawMessage => {
6873
7016
  type: 'text'
6874
7017
  }];
6875
7018
  }
6876
- const lines = rawMessage.split(/\r?\n/);
7019
+ const lines = normalizeInlineTables(rawMessage).split(/\r?\n/);
6877
7020
  const nodes = [];
6878
7021
  let paragraphLines = [];
6879
7022
  let listItems = [];
@@ -6897,12 +7040,36 @@ const parseMessageContent = rawMessage => {
6897
7040
  });
6898
7041
  listItems = [];
6899
7042
  };
6900
- for (const line of lines) {
7043
+ for (let i = 0; i < lines.length; i++) {
7044
+ const line = lines[i];
6901
7045
  if (!line.trim()) {
6902
7046
  flushList();
6903
7047
  flushParagraph();
6904
7048
  continue;
6905
7049
  }
7050
+ if (isTableRow(line) && i + 1 < lines.length) {
7051
+ const headerCells = getTableCells(line);
7052
+ if (isTableSeparatorRow(lines[i + 1], headerCells.length)) {
7053
+ flushList();
7054
+ flushParagraph();
7055
+ const rows = [];
7056
+ i += 2;
7057
+ while (i < lines.length && isTableRow(lines[i])) {
7058
+ const row = toTableRow(lines[i]);
7059
+ if (row.cells.length === headerCells.length) {
7060
+ rows.push(row);
7061
+ }
7062
+ i++;
7063
+ }
7064
+ i--;
7065
+ nodes.push({
7066
+ headers: headerCells.map(toTableCell),
7067
+ rows,
7068
+ type: 'table'
7069
+ });
7070
+ continue;
7071
+ }
7072
+ }
6906
7073
  const match = line.match(orderedListItemRegex);
6907
7074
  if (match) {
6908
7075
  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.1.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",