@lvce-editor/chat-view 2.8.0 → 2.9.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.
@@ -2649,7 +2649,8 @@ const deleteSession = async (state, id) => {
2649
2649
  };
2650
2650
 
2651
2651
  const handleClickOpenApiApiKeySettings = async state => {
2652
- await openExternal(state.openApiApiKeysSettingsUrl);
2652
+ // Open the built-in settings editor so the user can inspect or edit their OpenAI API key.
2653
+ await invoke('Main.openUri', 'app://settings.json');
2653
2654
  return state;
2654
2655
  };
2655
2656
 
@@ -3053,8 +3054,6 @@ const getMockOpenRouterAssistantText = async (messages, modelId, openRouterApiBa
3053
3054
  }
3054
3055
  };
3055
3056
 
3056
- const OnFileSystem = 'onFileSystem';
3057
-
3058
3057
  const isPathTraversalAttempt = path => {
3059
3058
  if (!path) {
3060
3059
  return false;
@@ -3071,6 +3070,7 @@ const isPathTraversalAttempt = path => {
3071
3070
  const segments = path.split(/[\\/]/);
3072
3071
  return segments.includes('..');
3073
3072
  };
3073
+
3074
3074
  const normalizeRelativePath = path => {
3075
3075
  const segments = path.split(/[\\/]/).filter(segment => segment && segment !== '.');
3076
3076
  if (segments.length === 0) {
@@ -3078,6 +3078,87 @@ const normalizeRelativePath = path => {
3078
3078
  }
3079
3079
  return segments.join('/');
3080
3080
  };
3081
+
3082
+ const executeListFilesTool = async (args, _options) => {
3083
+ const folderPath = typeof args.path === 'string' && args.path ? args.path : '.';
3084
+ if (isPathTraversalAttempt(folderPath)) {
3085
+ return JSON.stringify({
3086
+ error: 'Access denied: path must be relative and stay within the open workspace folder.'
3087
+ });
3088
+ }
3089
+ const normalizedPath = normalizeRelativePath(folderPath);
3090
+ try {
3091
+ const entries = await invoke('FileSystem.readDirWithFileTypes', normalizedPath);
3092
+ return JSON.stringify({
3093
+ entries,
3094
+ path: normalizedPath
3095
+ });
3096
+ } catch (error) {
3097
+ return JSON.stringify({
3098
+ error: String(error),
3099
+ path: normalizedPath
3100
+ });
3101
+ }
3102
+ };
3103
+
3104
+ const executeReadFileTool = async (args, _options) => {
3105
+ const filePath = typeof args.path === 'string' ? args.path : '';
3106
+ if (!filePath || isPathTraversalAttempt(filePath)) {
3107
+ return JSON.stringify({
3108
+ error: 'Access denied: path must be relative and stay within the open workspace folder.'
3109
+ });
3110
+ }
3111
+ const normalizedPath = normalizeRelativePath(filePath);
3112
+ try {
3113
+ const content = await readFile(normalizedPath);
3114
+ return JSON.stringify({
3115
+ content,
3116
+ path: normalizedPath
3117
+ });
3118
+ } catch (error) {
3119
+ return JSON.stringify({
3120
+ error: String(error),
3121
+ path: normalizedPath
3122
+ });
3123
+ }
3124
+ };
3125
+
3126
+ const OnFileSystem = 'onFileSystem';
3127
+
3128
+ const executeFileSystemCommand = async (method, params, options) => {
3129
+ return executeProvider({
3130
+ assetDir: options.assetDir,
3131
+ event: OnFileSystem,
3132
+ method,
3133
+ noProviderFoundMessage: 'No file system provider found',
3134
+ params,
3135
+ platform: options.platform
3136
+ });
3137
+ };
3138
+
3139
+ const executeWriteFileTool = async (args, options) => {
3140
+ const filePath = typeof args.path === 'string' ? args.path : '';
3141
+ const content = typeof args.content === 'string' ? args.content : '';
3142
+ if (!filePath || isPathTraversalAttempt(filePath)) {
3143
+ return JSON.stringify({
3144
+ error: 'Access denied: path must be relative and stay within the open workspace folder.'
3145
+ });
3146
+ }
3147
+ const normalizedPath = normalizeRelativePath(filePath);
3148
+ try {
3149
+ await executeFileSystemCommand(FileSystemWriteFile, ['file', normalizedPath, content], options);
3150
+ return JSON.stringify({
3151
+ ok: true,
3152
+ path: normalizedPath
3153
+ });
3154
+ } catch (error) {
3155
+ return JSON.stringify({
3156
+ error: String(error),
3157
+ path: normalizedPath
3158
+ });
3159
+ }
3160
+ };
3161
+
3081
3162
  const parseToolArguments = rawArguments => {
3082
3163
  if (typeof rawArguments !== 'string') {
3083
3164
  return {};
@@ -3092,16 +3173,23 @@ const parseToolArguments = rawArguments => {
3092
3173
  return {};
3093
3174
  }
3094
3175
  };
3095
- const executeFileSystemCommand = async (method, params, options) => {
3096
- return executeProvider({
3097
- assetDir: options.assetDir,
3098
- event: OnFileSystem,
3099
- method,
3100
- noProviderFoundMessage: 'No file system provider found',
3101
- params,
3102
- platform: options.platform
3176
+
3177
+ const executeChatTool = async (name, rawArguments, options) => {
3178
+ const args = parseToolArguments(rawArguments);
3179
+ if (name === 'read_file') {
3180
+ return executeReadFileTool(args);
3181
+ }
3182
+ if (name === 'write_file') {
3183
+ return executeWriteFileTool(args, options);
3184
+ }
3185
+ if (name === 'list_files') {
3186
+ return executeListFilesTool(args);
3187
+ }
3188
+ return JSON.stringify({
3189
+ error: `Unknown tool: ${name}`
3103
3190
  });
3104
3191
  };
3192
+
3105
3193
  const getBasicChatTools = () => {
3106
3194
  return [{
3107
3195
  function: {
@@ -3159,76 +3247,6 @@ const getBasicChatTools = () => {
3159
3247
  type: 'function'
3160
3248
  }];
3161
3249
  };
3162
- const executeChatTool = async (name, rawArguments, options) => {
3163
- const args = parseToolArguments(rawArguments);
3164
- if (name === 'read_file') {
3165
- const filePath = typeof args.path === 'string' ? args.path : '';
3166
- if (!filePath || isPathTraversalAttempt(filePath)) {
3167
- return JSON.stringify({
3168
- error: 'Access denied: path must be relative and stay within the open workspace folder.'
3169
- });
3170
- }
3171
- const normalizedPath = normalizeRelativePath(filePath);
3172
- try {
3173
- const content = await readFile(normalizedPath);
3174
- return JSON.stringify({
3175
- content,
3176
- path: normalizedPath
3177
- });
3178
- } catch (error) {
3179
- return JSON.stringify({
3180
- error: String(error),
3181
- path: normalizedPath
3182
- });
3183
- }
3184
- }
3185
- if (name === 'write_file') {
3186
- const filePath = typeof args.path === 'string' ? args.path : '';
3187
- const content = typeof args.content === 'string' ? args.content : '';
3188
- if (!filePath || isPathTraversalAttempt(filePath)) {
3189
- return JSON.stringify({
3190
- error: 'Access denied: path must be relative and stay within the open workspace folder.'
3191
- });
3192
- }
3193
- const normalizedPath = normalizeRelativePath(filePath);
3194
- try {
3195
- await executeFileSystemCommand(FileSystemWriteFile, ['file', normalizedPath, content], options);
3196
- return JSON.stringify({
3197
- ok: true,
3198
- path: normalizedPath
3199
- });
3200
- } catch (error) {
3201
- return JSON.stringify({
3202
- error: String(error),
3203
- path: normalizedPath
3204
- });
3205
- }
3206
- }
3207
- if (name === 'list_files') {
3208
- const folderPath = typeof args.path === 'string' && args.path ? args.path : '.';
3209
- if (isPathTraversalAttempt(folderPath)) {
3210
- return JSON.stringify({
3211
- error: 'Access denied: path must be relative and stay within the open workspace folder.'
3212
- });
3213
- }
3214
- const normalizedPath = normalizeRelativePath(folderPath);
3215
- try {
3216
- const entries = await invoke('FileSystem.readDirWithFileTypes', normalizedPath);
3217
- return JSON.stringify({
3218
- entries,
3219
- path: normalizedPath
3220
- });
3221
- } catch (error) {
3222
- return JSON.stringify({
3223
- error: String(error),
3224
- path: normalizedPath
3225
- });
3226
- }
3227
- }
3228
- return JSON.stringify({
3229
- error: `Unknown tool: ${name}`
3230
- });
3231
- };
3232
3250
 
3233
3251
  const getClientRequestIdHeader = () => {
3234
3252
  return {
@@ -4049,6 +4067,12 @@ const getOpenApiErrorMessage = errorResult => {
4049
4067
  const errorMessage = errorResult.errorMessage?.trim();
4050
4068
  const hasErrorCode = typeof errorResult.errorCode === 'string' && errorResult.errorCode.length > 0;
4051
4069
  const hasErrorType = typeof errorResult.errorType === 'string' && errorResult.errorType.length > 0;
4070
+
4071
+ // Provide a concise, user-friendly message when OpenAI reports an invalid API key.
4072
+ if (errorResult.errorCode === 'invalid_api_key') {
4073
+ const status = typeof errorResult.statusCode === 'number' ? errorResult.statusCode : 401;
4074
+ return `OpenAI request failed (status ${status}): Invalid API key. Please verify your OpenAI API key in Chat settings.`;
4075
+ }
4052
4076
  if (errorResult.statusCode === 429) {
4053
4077
  let prefix = 'OpenAI rate limit exceeded (429)';
4054
4078
  if (hasErrorCode) {
@@ -5970,6 +5994,31 @@ const getChatHeaderDomDetailMode = selectedSessionTitle => {
5970
5994
  }, text(selectedSessionTitle), ...getChatHeaderActionsDom()];
5971
5995
  };
5972
5996
 
5997
+ const getMessageNodeDom = node => {
5998
+ if (node.type === 'text') {
5999
+ return [{
6000
+ childCount: 1,
6001
+ className: Markdown,
6002
+ type: P
6003
+ }, text(node.text)];
6004
+ }
6005
+ return [{
6006
+ childCount: node.items.length,
6007
+ className: ChatOrderedList,
6008
+ type: Ol
6009
+ }, ...node.items.flatMap(item => {
6010
+ return [{
6011
+ childCount: 1,
6012
+ className: ChatOrderedListItem,
6013
+ type: Li
6014
+ }, text(item.text)];
6015
+ })];
6016
+ };
6017
+
6018
+ const getMessageContentDom = nodes => {
6019
+ return nodes.flatMap(getMessageNodeDom);
6020
+ };
6021
+
5973
6022
  const getMissingApiKeyDom = ({
5974
6023
  getApiKeyText,
5975
6024
  inputName,
@@ -6272,28 +6321,6 @@ const parseMessageContent = rawMessage => {
6272
6321
  type: 'text'
6273
6322
  }] : nodes;
6274
6323
  };
6275
- const getMessageContentDom = nodes => {
6276
- return nodes.flatMap(node => {
6277
- if (node.type === 'text') {
6278
- return [{
6279
- childCount: 1,
6280
- className: Markdown,
6281
- type: P
6282
- }, text(node.text)];
6283
- }
6284
- return [{
6285
- childCount: node.items.length,
6286
- className: ChatOrderedList,
6287
- type: Ol
6288
- }, ...node.items.flatMap(item => {
6289
- return [{
6290
- childCount: 1,
6291
- className: ChatOrderedListItem,
6292
- type: Li
6293
- }, text(item.text)];
6294
- })];
6295
- });
6296
- };
6297
6324
 
6298
6325
  const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput = '', openRouterApiKeyState = 'idle') => {
6299
6326
  const roleClassName = message.role === 'user' ? MessageUser : MessageAssistant;
@@ -6630,30 +6657,22 @@ const saveState = state => {
6630
6657
  const {
6631
6658
  chatListScrollTop,
6632
6659
  composerValue,
6633
- height,
6634
6660
  messagesScrollTop,
6635
6661
  nextMessageId,
6636
6662
  renamingSessionId,
6637
6663
  selectedModelId,
6638
6664
  selectedSessionId,
6639
- viewMode,
6640
- width,
6641
- x,
6642
- y
6665
+ viewMode
6643
6666
  } = state;
6644
6667
  return {
6645
6668
  chatListScrollTop,
6646
6669
  composerValue,
6647
- height,
6648
6670
  messagesScrollTop,
6649
6671
  nextMessageId,
6650
6672
  renamingSessionId,
6651
6673
  selectedModelId,
6652
6674
  selectedSessionId,
6653
- viewMode,
6654
- width,
6655
- x,
6656
- y
6675
+ viewMode
6657
6676
  };
6658
6677
  };
6659
6678
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",