@lvce-editor/chat-view 1.15.0 → 1.16.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.
@@ -1179,9 +1179,45 @@ const terminate = () => {
1179
1179
  globalThis.close();
1180
1180
  };
1181
1181
 
1182
+ const measureTextBlockHeight = async (text, fontFamily, fontSize, lineHeight, width) => {
1183
+ return invoke('MeasureTextBlockHeight.measureTextBlockHeight', text, fontSize, fontFamily, lineHeight, width);
1184
+ };
1185
+
1186
+ const getComposerWidth = width => {
1187
+ return Math.max(1, width - 32);
1188
+ };
1189
+ const getMinComposerHeight = lineHeight => {
1190
+ return lineHeight + 8;
1191
+ };
1192
+ const estimateComposerHeight = (value, lineHeight) => {
1193
+ const lineCount = value.split('\n').length;
1194
+ return lineCount * lineHeight + 8;
1195
+ };
1196
+ const getComposerHeight = async (state, value, width = state.width) => {
1197
+ const {
1198
+ composerFontFamily,
1199
+ composerFontSize,
1200
+ composerLineHeight
1201
+ } = state;
1202
+ const minimumHeight = getMinComposerHeight(composerLineHeight);
1203
+ const content = value || ' ';
1204
+ const composerWidth = getComposerWidth(width);
1205
+ try {
1206
+ const measuredHeight = await measureTextBlockHeight(content, composerFontFamily, composerFontSize, composerLineHeight, composerWidth);
1207
+ const height = Math.ceil(measuredHeight) + 8;
1208
+ return Math.max(minimumHeight, height);
1209
+ } catch {
1210
+ return Math.max(minimumHeight, estimateComposerHeight(value, composerLineHeight));
1211
+ }
1212
+ };
1213
+ const getMinComposerHeightForState = state => {
1214
+ return getMinComposerHeight(state.composerLineHeight);
1215
+ };
1216
+
1182
1217
  const clearInput = async state => {
1183
1218
  return {
1184
1219
  ...state,
1220
+ composerHeight: getMinComposerHeightForState(state),
1185
1221
  composerValue: ''
1186
1222
  };
1187
1223
  };
@@ -1237,6 +1273,9 @@ const sendMessage = () => {
1237
1273
  const save = () => {
1238
1274
  return i18nString('Save');
1239
1275
  };
1276
+ const saving = () => {
1277
+ return i18nString('Saving...');
1278
+ };
1240
1279
  const getOpenRouterApiKey = () => {
1241
1280
  return i18nString('Get API Key');
1242
1281
  };
@@ -1270,6 +1309,18 @@ const getDefaultModels = () => {
1270
1309
  id: defaultModelId,
1271
1310
  name: 'test',
1272
1311
  provider: 'test'
1312
+ }, {
1313
+ id: 'openapi/gpt-4o-mini',
1314
+ name: 'GPT-4o Mini',
1315
+ provider: 'openApi'
1316
+ }, {
1317
+ id: 'openapi/gpt-4o',
1318
+ name: 'GPT-4o',
1319
+ provider: 'openApi'
1320
+ }, {
1321
+ id: 'openapi/gpt-4.1-mini',
1322
+ name: 'GPT-4.1 Mini',
1323
+ provider: 'openApi'
1273
1324
  }, {
1274
1325
  id: 'codex-5.3',
1275
1326
  name: 'Codex 5.3',
@@ -1323,8 +1374,14 @@ const getDefaultModels = () => {
1323
1374
  const createDefaultState = () => {
1324
1375
  const defaultSessionId = 'session-1';
1325
1376
  const defaultModelId = 'test';
1377
+ const composerFontSize = 13;
1378
+ const composerLineHeight = 20;
1326
1379
  return {
1327
1380
  assetDir: '',
1381
+ composerFontFamily: 'system-ui',
1382
+ composerFontSize,
1383
+ composerHeight: composerLineHeight + 8,
1384
+ composerLineHeight,
1328
1385
  composerValue: '',
1329
1386
  errorCount: 0,
1330
1387
  focus: 'composer',
@@ -1346,6 +1403,7 @@ const createDefaultState = () => {
1346
1403
  openRouterApiKey: '',
1347
1404
  openRouterApiKeyInput: '',
1348
1405
  openRouterApiKeysSettingsUrl: 'https://openrouter.ai/settings/keys',
1406
+ openRouterApiKeyState: 'idle',
1349
1407
  platform: 0,
1350
1408
  renamingSessionId: '',
1351
1409
  selectedModelId: defaultModelId,
@@ -2118,6 +2176,15 @@ const openRouterTooManyRequestsMessage = 'OpenRouter rate limit reached (429). P
2118
2176
  const openRouterRequestFailureReasons = ['ContentSecurityPolicyViolation: Check DevTools for details.', 'OpenRouter server offline: Check DevTools for details.', 'Check your internet connection.'];
2119
2177
  const openRouterTooManyRequestsReasons = ['Wait a short time and retry your request.', 'Reduce request frequency to avoid rate limits.', 'Use a different model if this one is saturated.'];
2120
2178
 
2179
+ const delay = async ms => {
2180
+ await new Promise(resolve => setTimeout(resolve, ms));
2181
+ };
2182
+
2183
+ const getMockAiResponse = async userMessage => {
2184
+ await delay(800);
2185
+ return `Mock AI response: I received "${userMessage}".`;
2186
+ };
2187
+
2121
2188
  const activateByEvent = (event, assetDir, platform) => {
2122
2189
  // @ts-ignore
2123
2190
  return activateByEvent$1(event, assetDir, platform);
@@ -2139,13 +2206,108 @@ const executeProvider = async ({
2139
2206
 
2140
2207
  const CommandExecute = 'ExtensionHostCommand.executeCommand';
2141
2208
 
2142
- const delay = async ms => {
2143
- await new Promise(resolve => setTimeout(resolve, ms));
2209
+ const normalizeLimitInfo = value => {
2210
+ if (!value || typeof value !== 'object') {
2211
+ return undefined;
2212
+ }
2213
+ const limitRemaining = Reflect.get(value, 'limitRemaining');
2214
+ const limitReset = Reflect.get(value, 'limitReset');
2215
+ const retryAfter = Reflect.get(value, 'retryAfter');
2216
+ const usage = Reflect.get(value, 'usage');
2217
+ const usageDaily = Reflect.get(value, 'usageDaily');
2218
+ const normalized = {
2219
+ limitRemaining: typeof limitRemaining === 'number' || limitRemaining === null ? limitRemaining : undefined,
2220
+ limitReset: typeof limitReset === 'string' || limitReset === null ? limitReset : undefined,
2221
+ retryAfter: typeof retryAfter === 'string' || retryAfter === null ? retryAfter : undefined,
2222
+ usage: typeof usage === 'number' ? usage : undefined,
2223
+ usageDaily: typeof usageDaily === 'number' ? usageDaily : undefined
2224
+ };
2225
+ const hasDetails = normalized.limitRemaining !== undefined || normalized.limitReset !== undefined || normalized.retryAfter !== undefined || normalized.usage !== undefined || normalized.usageDaily !== undefined;
2226
+ return hasDetails ? normalized : undefined;
2144
2227
  };
2145
2228
 
2146
- const getMockAiResponse = async userMessage => {
2147
- await delay(800);
2148
- return `Mock AI response: I received "${userMessage}".`;
2229
+ const normalizeMockResult = value => {
2230
+ if (typeof value === 'string') {
2231
+ return {
2232
+ text: value,
2233
+ type: 'success'
2234
+ };
2235
+ }
2236
+ if (!value || typeof value !== 'object') {
2237
+ return {
2238
+ details: 'request-failed',
2239
+ type: 'error'
2240
+ };
2241
+ }
2242
+ const type = Reflect.get(value, 'type');
2243
+ if (type === 'success') {
2244
+ const text = Reflect.get(value, 'text');
2245
+ if (typeof text === 'string') {
2246
+ return {
2247
+ text,
2248
+ type: 'success'
2249
+ };
2250
+ }
2251
+ return {
2252
+ details: 'request-failed',
2253
+ type: 'error'
2254
+ };
2255
+ }
2256
+ if (type === 'error') {
2257
+ const details = Reflect.get(value, 'details');
2258
+ if (details === 'request-failed' || details === 'too-many-requests' || details === 'http-error') {
2259
+ const rawMessage = Reflect.get(value, 'rawMessage');
2260
+ const statusCode = Reflect.get(value, 'statusCode');
2261
+ return {
2262
+ details,
2263
+ limitInfo: normalizeLimitInfo(Reflect.get(value, 'limitInfo')),
2264
+ rawMessage: typeof rawMessage === 'string' ? rawMessage : undefined,
2265
+ statusCode: typeof statusCode === 'number' ? statusCode : undefined,
2266
+ type: 'error'
2267
+ };
2268
+ }
2269
+ }
2270
+ const text = Reflect.get(value, 'text');
2271
+ if (typeof text === 'string') {
2272
+ return {
2273
+ text,
2274
+ type: 'success'
2275
+ };
2276
+ }
2277
+ return {
2278
+ details: 'request-failed',
2279
+ type: 'error'
2280
+ };
2281
+ };
2282
+
2283
+ const getMockOpenRouterAssistantText = async (messages, modelId, openRouterApiBaseUrl, openRouterApiKey, mockApiCommandId, assetDir, platform) => {
2284
+ if (!mockApiCommandId) {
2285
+ return {
2286
+ details: 'request-failed',
2287
+ type: 'error'
2288
+ };
2289
+ }
2290
+ try {
2291
+ const result = await executeProvider({
2292
+ assetDir,
2293
+ event: `onCommand:${mockApiCommandId}`,
2294
+ method: CommandExecute,
2295
+ noProviderFoundMessage: 'No mock api command found',
2296
+ params: [mockApiCommandId, {
2297
+ messages,
2298
+ modelId,
2299
+ openRouterApiBaseUrl,
2300
+ openRouterApiKey
2301
+ }],
2302
+ platform
2303
+ });
2304
+ return normalizeMockResult(result);
2305
+ } catch {
2306
+ return {
2307
+ details: 'request-failed',
2308
+ type: 'error'
2309
+ };
2310
+ }
2149
2311
  };
2150
2312
 
2151
2313
  const getOpenApiApiEndpoint = openApiApiBaseUrl => {
@@ -2173,6 +2335,29 @@ const getTextContent = content => {
2173
2335
  return textParts.join('\n');
2174
2336
  };
2175
2337
 
2338
+ const getOpenApiErrorDetails = async response => {
2339
+ let parsed;
2340
+ try {
2341
+ parsed = await response.json();
2342
+ } catch {
2343
+ return {};
2344
+ }
2345
+ if (!parsed || typeof parsed !== 'object') {
2346
+ return {};
2347
+ }
2348
+ const error = Reflect.get(parsed, 'error');
2349
+ if (!error || typeof error !== 'object') {
2350
+ return {};
2351
+ }
2352
+ const errorCode = Reflect.get(error, 'code');
2353
+ const errorMessage = Reflect.get(error, 'message');
2354
+ const errorType = Reflect.get(error, 'type');
2355
+ return {
2356
+ errorCode: typeof errorCode === 'string' ? errorCode : undefined,
2357
+ errorMessage: typeof errorMessage === 'string' ? errorMessage : undefined,
2358
+ errorType: typeof errorType === 'string' ? errorType : undefined
2359
+ };
2360
+ };
2176
2361
  const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApiApiBaseUrl) => {
2177
2362
  let response;
2178
2363
  try {
@@ -2197,8 +2382,16 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
2197
2382
  };
2198
2383
  }
2199
2384
  if (!response.ok) {
2385
+ const {
2386
+ errorCode,
2387
+ errorMessage,
2388
+ errorType
2389
+ } = await getOpenApiErrorDetails(response);
2200
2390
  return {
2201
2391
  details: 'http-error',
2392
+ errorCode,
2393
+ errorMessage,
2394
+ errorType,
2202
2395
  statusCode: response.status,
2203
2396
  type: 'error'
2204
2397
  };
@@ -2246,6 +2439,51 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
2246
2439
  };
2247
2440
  };
2248
2441
 
2442
+ const getOpenApiErrorMessage = errorResult => {
2443
+ switch (errorResult.details) {
2444
+ case 'http-error':
2445
+ {
2446
+ const errorMessage = errorResult.errorMessage?.trim();
2447
+ const hasErrorCode = typeof errorResult.errorCode === 'string' && errorResult.errorCode.length > 0;
2448
+ const hasErrorType = typeof errorResult.errorType === 'string' && errorResult.errorType.length > 0;
2449
+ if (errorResult.statusCode === 429) {
2450
+ let prefix = 'OpenAI rate limit exceeded (429)';
2451
+ if (hasErrorCode) {
2452
+ prefix = `OpenAI rate limit exceeded (429: ${errorResult.errorCode})`;
2453
+ }
2454
+ if (hasErrorType) {
2455
+ prefix += ` [${errorResult.errorType}]`;
2456
+ }
2457
+ prefix += '.';
2458
+ if (!errorMessage) {
2459
+ return prefix;
2460
+ }
2461
+ return `${prefix} ${errorMessage}`;
2462
+ }
2463
+ if (typeof errorResult.statusCode === 'number') {
2464
+ let prefix = `OpenAI request failed (status ${errorResult.statusCode})`;
2465
+ if (hasErrorCode) {
2466
+ prefix += `: ${errorResult.errorCode}`;
2467
+ }
2468
+ if (hasErrorType) {
2469
+ prefix += ` [${errorResult.errorType}]`;
2470
+ }
2471
+ prefix += '.';
2472
+ if (!errorMessage) {
2473
+ return prefix;
2474
+ }
2475
+ return `${prefix} ${errorMessage}`;
2476
+ }
2477
+ if (errorMessage) {
2478
+ return `OpenAI request failed. ${errorMessage}`;
2479
+ }
2480
+ return openApiRequestFailedMessage;
2481
+ }
2482
+ case 'request-failed':
2483
+ return openApiRequestFailedMessage;
2484
+ }
2485
+ };
2486
+
2249
2487
  const getOpenApiModelId = selectedModelId => {
2250
2488
  const openApiPrefix = 'openapi/';
2251
2489
  const openAiPrefix = 'openai/';
@@ -2425,36 +2663,6 @@ const getOpenRouterAssistantText = async (messages, modelId, openRouterApiKey, o
2425
2663
  };
2426
2664
  };
2427
2665
 
2428
- /* eslint-disable @cspell/spellchecker */
2429
- const getOpenRouterModelId = selectedModelId => {
2430
- const openRouterPrefix = 'openrouter/';
2431
- if (selectedModelId.toLowerCase().startsWith(openRouterPrefix)) {
2432
- return selectedModelId.slice(openRouterPrefix.length);
2433
- }
2434
- return selectedModelId;
2435
- };
2436
-
2437
- const isOpenApiModel = (selectedModelId, models) => {
2438
- const selectedModel = models.find(model => model.id === selectedModelId);
2439
- const normalizedProvider = selectedModel?.provider?.toLowerCase();
2440
- if (normalizedProvider === 'openapi' || normalizedProvider === 'openai' || normalizedProvider === 'open-ai') {
2441
- return true;
2442
- }
2443
- const normalizedModelId = selectedModelId.toLowerCase();
2444
- return normalizedModelId.startsWith('openapi/') || normalizedModelId.startsWith('openai/');
2445
- };
2446
-
2447
- /* eslint-disable @cspell/spellchecker */
2448
-
2449
- const isOpenRouterModel = (selectedModelId, models) => {
2450
- const selectedModel = models.find(model => model.id === selectedModelId);
2451
- const normalizedProvider = selectedModel?.provider?.toLowerCase();
2452
- if (normalizedProvider === 'openrouter' || normalizedProvider === 'open-router') {
2453
- return true;
2454
- }
2455
- return selectedModelId.toLowerCase().startsWith('openrouter/');
2456
- };
2457
-
2458
2666
  const getOpenRouterTooManyRequestsMessage = errorResult => {
2459
2667
  const details = [];
2460
2668
  if (errorResult.rawMessage) {
@@ -2487,6 +2695,7 @@ const getOpenRouterTooManyRequestsMessage = errorResult => {
2487
2695
  }
2488
2696
  return `${openRouterTooManyRequestsMessage} ${details.join(' ')}`;
2489
2697
  };
2698
+
2490
2699
  const getOpenRouterErrorMessage = errorResult => {
2491
2700
  switch (errorResult.details) {
2492
2701
  case 'http-error':
@@ -2496,115 +2705,52 @@ const getOpenRouterErrorMessage = errorResult => {
2496
2705
  return getOpenRouterTooManyRequestsMessage(errorResult);
2497
2706
  }
2498
2707
  };
2499
- const getOpenApiErrorMessage = errorResult => {
2500
- switch (errorResult.details) {
2501
- case 'http-error':
2502
- case 'request-failed':
2503
- return openApiRequestFailedMessage;
2504
- }
2505
- };
2506
- const normalizeLimitInfo = value => {
2507
- if (!value || typeof value !== 'object') {
2508
- return undefined;
2708
+
2709
+ /* eslint-disable @cspell/spellchecker */
2710
+ const getOpenRouterModelId = selectedModelId => {
2711
+ const openRouterPrefix = 'openrouter/';
2712
+ if (selectedModelId.toLowerCase().startsWith(openRouterPrefix)) {
2713
+ return selectedModelId.slice(openRouterPrefix.length);
2509
2714
  }
2510
- const limitRemaining = Reflect.get(value, 'limitRemaining');
2511
- const limitReset = Reflect.get(value, 'limitReset');
2512
- const retryAfter = Reflect.get(value, 'retryAfter');
2513
- const usage = Reflect.get(value, 'usage');
2514
- const usageDaily = Reflect.get(value, 'usageDaily');
2515
- const normalized = {
2516
- limitRemaining: typeof limitRemaining === 'number' || limitRemaining === null ? limitRemaining : undefined,
2517
- limitReset: typeof limitReset === 'string' || limitReset === null ? limitReset : undefined,
2518
- retryAfter: typeof retryAfter === 'string' || retryAfter === null ? retryAfter : undefined,
2519
- usage: typeof usage === 'number' ? usage : undefined,
2520
- usageDaily: typeof usageDaily === 'number' ? usageDaily : undefined
2521
- };
2522
- const hasDetails = normalized.limitRemaining !== undefined || normalized.limitReset !== undefined || normalized.retryAfter !== undefined || normalized.usage !== undefined || normalized.usageDaily !== undefined;
2523
- return hasDetails ? normalized : undefined;
2715
+ return selectedModelId;
2524
2716
  };
2525
- const normalizeMockResult = value => {
2526
- if (typeof value === 'string') {
2527
- return {
2528
- text: value,
2529
- type: 'success'
2530
- };
2531
- }
2532
- if (!value || typeof value !== 'object') {
2533
- return {
2534
- details: 'request-failed',
2535
- type: 'error'
2536
- };
2537
- }
2538
- const type = Reflect.get(value, 'type');
2539
- if (type === 'success') {
2540
- const text = Reflect.get(value, 'text');
2541
- if (typeof text === 'string') {
2542
- return {
2543
- text,
2544
- type: 'success'
2545
- };
2546
- }
2547
- return {
2548
- details: 'request-failed',
2549
- type: 'error'
2550
- };
2551
- }
2552
- if (type === 'error') {
2553
- const details = Reflect.get(value, 'details');
2554
- if (details === 'request-failed' || details === 'too-many-requests' || details === 'http-error') {
2555
- const rawMessage = Reflect.get(value, 'rawMessage');
2556
- const statusCode = Reflect.get(value, 'statusCode');
2557
- return {
2558
- details,
2559
- limitInfo: normalizeLimitInfo(Reflect.get(value, 'limitInfo')),
2560
- rawMessage: typeof rawMessage === 'string' ? rawMessage : undefined,
2561
- statusCode: typeof statusCode === 'number' ? statusCode : undefined,
2562
- type: 'error'
2563
- };
2564
- }
2565
- }
2566
- const text = Reflect.get(value, 'text');
2567
- if (typeof text === 'string') {
2568
- return {
2569
- text,
2570
- type: 'success'
2571
- };
2717
+
2718
+ const isOpenApiModel = (selectedModelId, models) => {
2719
+ const selectedModel = models.find(model => model.id === selectedModelId);
2720
+ const normalizedProvider = selectedModel?.provider?.toLowerCase();
2721
+ if (normalizedProvider === 'openapi' || normalizedProvider === 'openai' || normalizedProvider === 'open-ai') {
2722
+ return true;
2572
2723
  }
2573
- return {
2574
- details: 'request-failed',
2575
- type: 'error'
2576
- };
2724
+ const normalizedModelId = selectedModelId.toLowerCase();
2725
+ return normalizedModelId.startsWith('openapi/') || normalizedModelId.startsWith('openai/');
2577
2726
  };
2578
- const getMockOpenRouterAssistantText = async (messages, modelId, openRouterApiBaseUrl, openRouterApiKey, mockApiCommandId, assetDir, platform) => {
2579
- if (!mockApiCommandId) {
2580
- return {
2581
- details: 'request-failed',
2582
- type: 'error'
2583
- };
2584
- }
2585
- try {
2586
- const result = await executeProvider({
2587
- assetDir,
2588
- event: `onCommand:${mockApiCommandId}`,
2589
- method: CommandExecute,
2590
- noProviderFoundMessage: 'No mock api command found',
2591
- params: [mockApiCommandId, {
2592
- messages,
2593
- modelId,
2594
- openRouterApiBaseUrl,
2595
- openRouterApiKey
2596
- }],
2597
- platform
2598
- });
2599
- return normalizeMockResult(result);
2600
- } catch {
2601
- return {
2602
- details: 'request-failed',
2603
- type: 'error'
2604
- };
2727
+
2728
+ /* eslint-disable @cspell/spellchecker */
2729
+
2730
+ const isOpenRouterModel = (selectedModelId, models) => {
2731
+ const selectedModel = models.find(model => model.id === selectedModelId);
2732
+ const normalizedProvider = selectedModel?.provider?.toLowerCase();
2733
+ if (normalizedProvider === 'openrouter' || normalizedProvider === 'open-router') {
2734
+ return true;
2605
2735
  }
2736
+ return selectedModelId.toLowerCase().startsWith('openrouter/');
2606
2737
  };
2607
- const getAiResponse = async (userText, messages, nextMessageId, selectedModelId, models, openApiApiKey, openApiApiBaseUrl, openRouterApiKey, openRouterApiBaseUrl, useMockApi, mockApiCommandId, assetDir, platform) => {
2738
+
2739
+ const getAiResponse = async ({
2740
+ assetDir,
2741
+ messages,
2742
+ mockApiCommandId,
2743
+ models,
2744
+ nextMessageId,
2745
+ openApiApiBaseUrl,
2746
+ openApiApiKey,
2747
+ openRouterApiBaseUrl,
2748
+ openRouterApiKey,
2749
+ platform,
2750
+ selectedModelId,
2751
+ useMockApi,
2752
+ userText
2753
+ }) => {
2608
2754
  let text = '';
2609
2755
  const usesOpenApiModel = isOpenApiModel(selectedModelId, models);
2610
2756
  const usesOpenRouterModel = isOpenRouterModel(selectedModelId, models);
@@ -2706,7 +2852,21 @@ const handleClickSaveOpenApiApiKey = async state => {
2706
2852
  return updatedState;
2707
2853
  }
2708
2854
  const retryMessages = session.messages.slice(0, -1);
2709
- const assistantMessage = await getAiResponse(previousUserMessage.text, retryMessages, updatedState.nextMessageId, updatedState.selectedModelId, updatedState.models, updatedState.openApiApiKey, updatedState.openApiApiBaseUrl, updatedState.openRouterApiKey, updatedState.openRouterApiBaseUrl, updatedState.useMockApi, updatedState.mockApiCommandId, updatedState.assetDir, updatedState.platform);
2855
+ const assistantMessage = await getAiResponse({
2856
+ assetDir: updatedState.assetDir,
2857
+ messages: retryMessages,
2858
+ mockApiCommandId: updatedState.mockApiCommandId,
2859
+ models: updatedState.models,
2860
+ nextMessageId: updatedState.nextMessageId,
2861
+ openApiApiBaseUrl: updatedState.openApiApiBaseUrl,
2862
+ openApiApiKey: updatedState.openApiApiKey,
2863
+ openRouterApiBaseUrl: updatedState.openRouterApiBaseUrl,
2864
+ openRouterApiKey: updatedState.openRouterApiKey,
2865
+ platform: updatedState.platform,
2866
+ selectedModelId: updatedState.selectedModelId,
2867
+ useMockApi: updatedState.useMockApi,
2868
+ userText: previousUserMessage.text
2869
+ });
2710
2870
  const updatedSession = {
2711
2871
  ...session,
2712
2872
  messages: [...session.messages.slice(0, -1), assistantMessage]
@@ -2746,7 +2906,18 @@ const handleClickSaveOpenRouterApiKey = async state => {
2746
2906
  if (!openRouterApiKey) {
2747
2907
  return state;
2748
2908
  }
2749
- const updatedState = await setOpenRouterApiKey(state, openRouterApiKey);
2909
+ const optimisticState = {
2910
+ ...state,
2911
+ openRouterApiKeyState: 'saving'
2912
+ };
2913
+ set(state.uid, state, optimisticState);
2914
+ // @ts-ignore
2915
+ await invoke('Chat.rerender');
2916
+ const persistedState = await setOpenRouterApiKey(optimisticState, openRouterApiKey);
2917
+ const updatedState = {
2918
+ ...persistedState,
2919
+ openRouterApiKeyState: 'idle'
2920
+ };
2750
2921
  const session = updatedState.sessions.find(item => item.id === updatedState.selectedSessionId);
2751
2922
  if (!session) {
2752
2923
  return updatedState;
@@ -2761,9 +2932,21 @@ const handleClickSaveOpenRouterApiKey = async state => {
2761
2932
  return updatedState;
2762
2933
  }
2763
2934
  const retryMessages = session.messages.slice(0, -1);
2764
-
2765
- // @ts-ignore
2766
- const assistantMessage = await getAiResponse(previousUserMessage.text, retryMessages, updatedState.nextMessageId, updatedState.selectedModelId, updatedState.models, updatedState.openApiApiKey, updatedState.openApiApiBaseUrl, openRouterApiKey, updatedState.openRouterApiBaseUrl, updatedState.useMockApi, updatedState.mockApiCommandId, updatedState.assetDir, updatedState.platform);
2935
+ const assistantMessage = await getAiResponse({
2936
+ assetDir: updatedState.assetDir,
2937
+ messages: retryMessages,
2938
+ mockApiCommandId: updatedState.mockApiCommandId,
2939
+ models: updatedState.models,
2940
+ nextMessageId: updatedState.nextMessageId,
2941
+ openApiApiBaseUrl: updatedState.openApiApiBaseUrl,
2942
+ openApiApiKey: updatedState.openApiApiKey,
2943
+ openRouterApiBaseUrl: updatedState.openRouterApiBaseUrl,
2944
+ openRouterApiKey,
2945
+ platform: updatedState.platform,
2946
+ selectedModelId: updatedState.selectedModelId,
2947
+ useMockApi: updatedState.useMockApi,
2948
+ userText: previousUserMessage.text
2949
+ });
2767
2950
  const updatedSession = {
2768
2951
  ...session,
2769
2952
  messages: [...session.messages.slice(0, -1), assistantMessage]
@@ -2778,6 +2961,7 @@ const handleClickSaveOpenRouterApiKey = async state => {
2778
2961
  return {
2779
2962
  ...updatedState,
2780
2963
  nextMessageId: updatedState.nextMessageId + 1,
2964
+ openRouterApiKeyState: 'idle',
2781
2965
  sessions: updatedSessions
2782
2966
  };
2783
2967
  };
@@ -2845,6 +3029,7 @@ const handleSubmit = async state => {
2845
3029
  await saveChatSession(newSession);
2846
3030
  optimisticState = focusInput({
2847
3031
  ...state,
3032
+ composerHeight: getMinComposerHeightForState(state),
2848
3033
  composerValue: '',
2849
3034
  inputSource: 'script',
2850
3035
  lastSubmittedSessionId: newSessionId,
@@ -2869,6 +3054,7 @@ const handleSubmit = async state => {
2869
3054
  }
2870
3055
  optimisticState = focusInput({
2871
3056
  ...state,
3057
+ composerHeight: getMinComposerHeightForState(state),
2872
3058
  composerValue: '',
2873
3059
  inputSource: 'script',
2874
3060
  lastSubmittedSessionId: selectedSessionId,
@@ -2881,7 +3067,21 @@ const handleSubmit = async state => {
2881
3067
  await invoke('Chat.rerender');
2882
3068
  const selectedOptimisticSession = optimisticState.sessions.find(session => session.id === optimisticState.selectedSessionId);
2883
3069
  const messages = selectedOptimisticSession?.messages ?? [];
2884
- const assistantMessage = await getAiResponse(userText, messages, optimisticState.nextMessageId, selectedModelId, models, openApiApiKey, openApiApiBaseUrl, openRouterApiKey, openRouterApiBaseUrl, useMockApi, mockApiCommandId, assetDir, platform);
3070
+ const assistantMessage = await getAiResponse({
3071
+ assetDir,
3072
+ messages,
3073
+ mockApiCommandId,
3074
+ models,
3075
+ nextMessageId: optimisticState.nextMessageId,
3076
+ openApiApiBaseUrl,
3077
+ openApiApiKey,
3078
+ openRouterApiBaseUrl,
3079
+ openRouterApiKey,
3080
+ platform,
3081
+ selectedModelId,
3082
+ useMockApi,
3083
+ userText
3084
+ });
2885
3085
  const updatedSessions = optimisticState.sessions.map(session => {
2886
3086
  if (session.id !== optimisticState.selectedSessionId) {
2887
3087
  return session;
@@ -3086,8 +3286,10 @@ const handleInput = async (state, name, value, inputSource = 'user') => {
3086
3286
  if (name !== Composer) {
3087
3287
  return state;
3088
3288
  }
3289
+ const composerHeight = await getComposerHeight(state, value);
3089
3290
  return {
3090
3291
  ...state,
3292
+ composerHeight,
3091
3293
  composerValue: value,
3092
3294
  inputSource
3093
3295
  };
@@ -3152,6 +3354,7 @@ const submitRename = async state => {
3152
3354
  }
3153
3355
  return {
3154
3356
  ...state,
3357
+ composerHeight: getMinComposerHeightForState(state),
3155
3358
  composerValue: '',
3156
3359
  inputSource: 'script',
3157
3360
  renamingSessionId: '',
@@ -3549,7 +3752,7 @@ const getUsageOverviewDom = (tokensUsed, tokensMax) => {
3549
3752
  }, text(usageLabel)];
3550
3753
  };
3551
3754
 
3552
- const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
3755
+ const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20) => {
3553
3756
  const isSendDisabled = composerValue.trim() === '';
3554
3757
  const modelOptions = models.flatMap(model => getModelOptionDOm(model, selectedModelId));
3555
3758
  return [{
@@ -3567,7 +3770,7 @@ const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOvervie
3567
3770
  onFocus: HandleFocus,
3568
3771
  onInput: HandleInput,
3569
3772
  placeholder: composePlaceholder(),
3570
- rows: 4,
3773
+ style: `height:${composerHeight}px;font-size:${composerFontSize}px;font-family:${composerFontFamily};line-height:${composerLineHeight}px;`,
3571
3774
  type: TextArea,
3572
3775
  value: composerValue
3573
3776
  }, {
@@ -3662,7 +3865,9 @@ const getMissingApiKeyDom = ({
3662
3865
  inputValue,
3663
3866
  openSettingsButtonName,
3664
3867
  placeholder,
3665
- saveButtonName
3868
+ saveButtonDisabled = false,
3869
+ saveButtonName,
3870
+ saveButtonText = save()
3666
3871
  }) => {
3667
3872
  return [{
3668
3873
  childCount: 2,
@@ -3682,10 +3887,11 @@ const getMissingApiKeyDom = ({
3682
3887
  }, {
3683
3888
  childCount: 1,
3684
3889
  className: mergeClassNames(Button, ButtonPrimary),
3890
+ disabled: saveButtonDisabled,
3685
3891
  name: saveButtonName,
3686
3892
  onClick: HandleClick,
3687
3893
  type: Button$1
3688
- }, text(save()), {
3894
+ }, text(saveButtonText), {
3689
3895
  childCount: 1,
3690
3896
  className: mergeClassNames(Button, ButtonSecondary),
3691
3897
  name: openSettingsButtonName,
@@ -3705,14 +3911,17 @@ const getMissingOpenApiApiKeyDom = openApiApiKeyInput => {
3705
3911
  });
3706
3912
  };
3707
3913
 
3708
- const getMissingOpenRouterApiKeyDom = openRouterApiKeyInput => {
3914
+ const getMissingOpenRouterApiKeyDom = (openRouterApiKeyInput, openRouterApiKeyState = 'idle') => {
3915
+ const isSaving = openRouterApiKeyState === 'saving';
3709
3916
  return getMissingApiKeyDom({
3710
3917
  getApiKeyText: getOpenRouterApiKey(),
3711
3918
  inputName: OpenRouterApiKeyInput,
3712
3919
  inputValue: openRouterApiKeyInput,
3713
3920
  openSettingsButtonName: OpenOpenRouterApiKeySettings,
3714
3921
  placeholder: openRouterApiKeyPlaceholder(),
3715
- saveButtonName: SaveOpenRouterApiKey
3922
+ saveButtonDisabled: isSaving,
3923
+ saveButtonName: SaveOpenRouterApiKey,
3924
+ saveButtonText: isSaving ? saving() : save()
3716
3925
  });
3717
3926
  };
3718
3927
 
@@ -3742,7 +3951,7 @@ const getOpenRouterTooManyRequestsDom = () => {
3742
3951
  }, text(reason)];
3743
3952
  })];
3744
3953
  };
3745
- const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput = '') => {
3954
+ const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput = '', openRouterApiKeyState = 'idle') => {
3746
3955
  const roleClassName = message.role === 'user' ? MessageUser : MessageAssistant;
3747
3956
  const isOpenApiApiKeyMissingMessage = message.role === 'assistant' && message.text === openApiApiKeyRequiredMessage;
3748
3957
  const isOpenRouterApiKeyMissingMessage = message.role === 'assistant' && message.text === openRouterApiKeyRequiredMessage;
@@ -3761,7 +3970,7 @@ const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput =
3761
3970
  childCount: 1,
3762
3971
  className: Markdown,
3763
3972
  type: P
3764
- }, text(message.text), ...(isOpenApiApiKeyMissingMessage ? getMissingOpenApiApiKeyDom(openApiApiKeyInput) : []), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : []), ...(isOpenRouterTooManyRequestsMessage ? getOpenRouterTooManyRequestsDom() : [])];
3973
+ }, text(message.text), ...(isOpenApiApiKeyMissingMessage ? getMissingOpenApiApiKeyDom(openApiApiKeyInput) : []), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput, openRouterApiKeyState) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : []), ...(isOpenRouterTooManyRequestsMessage ? getOpenRouterTooManyRequestsDom() : [])];
3765
3974
  };
3766
3975
 
3767
3976
  const getEmptyMessagesDom = () => {
@@ -3772,7 +3981,7 @@ const getEmptyMessagesDom = () => {
3772
3981
  }, text(startConversation())];
3773
3982
  };
3774
3983
 
3775
- const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = '') => {
3984
+ const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = '', openRouterApiKeyState = 'idle') => {
3776
3985
  if (messages.length === 0) {
3777
3986
  return getEmptyMessagesDom();
3778
3987
  }
@@ -3780,10 +3989,10 @@ const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = ''
3780
3989
  childCount: messages.length,
3781
3990
  className: 'ChatMessages',
3782
3991
  type: Div
3783
- }, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput, openApiApiKeyInput))];
3992
+ }, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState))];
3784
3993
  };
3785
3994
 
3786
- const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
3995
+ const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState = 'idle', composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20) => {
3787
3996
  const selectedSession = sessions.find(session => session.id === selectedSessionId);
3788
3997
  const selectedSessionTitle = selectedSession?.title || chatTitle();
3789
3998
  const messages = selectedSession ? selectedSession.messages : [];
@@ -3791,7 +4000,7 @@ const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue,
3791
4000
  childCount: 3,
3792
4001
  className: mergeClassNames(Viewlet, Chat),
3793
4002
  type: Div
3794
- }, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput, openApiApiKeyInput), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax)];
4003
+ }, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight)];
3795
4004
  };
3796
4005
 
3797
4006
  const getChatHeaderListModeDom = () => {
@@ -3860,12 +4069,12 @@ const getChatListDom = (sessions, selectedSessionId) => {
3860
4069
  }, ...sessions.flatMap(getSessionDom)];
3861
4070
  };
3862
4071
 
3863
- const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
4072
+ const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20) => {
3864
4073
  return [{
3865
4074
  childCount: 3,
3866
4075
  className: mergeClassNames(Viewlet, Chat),
3867
4076
  type: Div
3868
- }, ...getChatHeaderListModeDom(), ...getChatListDom(sessions), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax)];
4077
+ }, ...getChatHeaderListModeDom(), ...getChatListDom(sessions), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight)];
3869
4078
  };
3870
4079
 
3871
4080
  const getChatModeUnsupportedVirtualDom = () => {
@@ -3875,12 +4084,12 @@ const getChatModeUnsupportedVirtualDom = () => {
3875
4084
  }, text(unknownViewMode())];
3876
4085
  };
3877
4086
 
3878
- const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput = '') => {
4087
+ const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput = '', openRouterApiKeyState = 'idle', composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20) => {
3879
4088
  switch (viewMode) {
3880
4089
  case 'detail':
3881
- return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
4090
+ return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight);
3882
4091
  case 'list':
3883
- return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
4092
+ return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight);
3884
4093
  default:
3885
4094
  return getChatModeUnsupportedVirtualDom();
3886
4095
  }
@@ -3888,11 +4097,16 @@ const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRoute
3888
4097
 
3889
4098
  const renderItems = (oldState, newState) => {
3890
4099
  const {
4100
+ composerFontFamily,
4101
+ composerFontSize,
4102
+ composerHeight,
4103
+ composerLineHeight,
3891
4104
  composerValue,
3892
4105
  initial,
3893
4106
  models,
3894
4107
  openApiApiKeyInput,
3895
4108
  openRouterApiKeyInput,
4109
+ openRouterApiKeyState,
3896
4110
  selectedModelId,
3897
4111
  selectedSessionId,
3898
4112
  sessions,
@@ -3905,7 +4119,7 @@ const renderItems = (oldState, newState) => {
3905
4119
  if (initial) {
3906
4120
  return [SetDom2, uid, []];
3907
4121
  }
3908
- const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput);
4122
+ const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight);
3909
4123
  return [SetDom2, uid, dom];
3910
4124
  };
3911
4125
 
@@ -4016,6 +4230,7 @@ const reset = async state => {
4016
4230
  await clearChatSessions();
4017
4231
  return {
4018
4232
  ...state,
4233
+ composerHeight: getMinComposerHeightForState(state),
4019
4234
  composerValue: '',
4020
4235
  openRouterApiKey: '',
4021
4236
  selectedSessionId: '',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",