@lvce-editor/chat-view 1.14.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.
- package/dist/chatViewWorkerMain.js +880 -57
- package/package.json +1 -1
|
@@ -1036,6 +1036,7 @@ const create$2 = rpcId => {
|
|
|
1036
1036
|
};
|
|
1037
1037
|
|
|
1038
1038
|
const {
|
|
1039
|
+
invoke: invoke$1,
|
|
1039
1040
|
set: set$2
|
|
1040
1041
|
} = create$2(ExtensionHostWorker);
|
|
1041
1042
|
|
|
@@ -1048,6 +1049,9 @@ const sendMessagePortToExtensionHostWorker$1 = async (port, rpcId = 0) => {
|
|
|
1048
1049
|
const command = 'HandleMessagePort.handleMessagePort2';
|
|
1049
1050
|
await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToExtensionHostWorker', port, command, rpcId);
|
|
1050
1051
|
};
|
|
1052
|
+
const activateByEvent$1 = (event, assetDir, platform) => {
|
|
1053
|
+
return invoke('ExtensionHostManagement.activateByEvent', event, assetDir, platform);
|
|
1054
|
+
};
|
|
1051
1055
|
const getPreference = async key => {
|
|
1052
1056
|
return await invoke('Preferences.get', key);
|
|
1053
1057
|
};
|
|
@@ -1175,9 +1179,45 @@ const terminate = () => {
|
|
|
1175
1179
|
globalThis.close();
|
|
1176
1180
|
};
|
|
1177
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
|
+
|
|
1178
1217
|
const clearInput = async state => {
|
|
1179
1218
|
return {
|
|
1180
1219
|
...state,
|
|
1220
|
+
composerHeight: getMinComposerHeightForState(state),
|
|
1181
1221
|
composerValue: ''
|
|
1182
1222
|
};
|
|
1183
1223
|
};
|
|
@@ -1224,15 +1264,24 @@ const composePlaceholder = () => {
|
|
|
1224
1264
|
const openRouterApiKeyPlaceholder = () => {
|
|
1225
1265
|
return i18nString('Enter OpenRouter API key');
|
|
1226
1266
|
};
|
|
1267
|
+
const openApiApiKeyPlaceholder = () => {
|
|
1268
|
+
return i18nString('Enter OpenAI API key');
|
|
1269
|
+
};
|
|
1227
1270
|
const sendMessage = () => {
|
|
1228
1271
|
return i18nString('Send message');
|
|
1229
1272
|
};
|
|
1230
1273
|
const save = () => {
|
|
1231
1274
|
return i18nString('Save');
|
|
1232
1275
|
};
|
|
1276
|
+
const saving = () => {
|
|
1277
|
+
return i18nString('Saving...');
|
|
1278
|
+
};
|
|
1233
1279
|
const getOpenRouterApiKey = () => {
|
|
1234
1280
|
return i18nString('Get API Key');
|
|
1235
1281
|
};
|
|
1282
|
+
const getOpenApiApiKey = () => {
|
|
1283
|
+
return i18nString('Get API Key');
|
|
1284
|
+
};
|
|
1236
1285
|
const deleteChatSession$1 = () => {
|
|
1237
1286
|
return i18nString('Delete chat session');
|
|
1238
1287
|
};
|
|
@@ -1260,6 +1309,18 @@ const getDefaultModels = () => {
|
|
|
1260
1309
|
id: defaultModelId,
|
|
1261
1310
|
name: 'test',
|
|
1262
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'
|
|
1263
1324
|
}, {
|
|
1264
1325
|
id: 'codex-5.3',
|
|
1265
1326
|
name: 'Codex 5.3',
|
|
@@ -1313,8 +1374,14 @@ const getDefaultModels = () => {
|
|
|
1313
1374
|
const createDefaultState = () => {
|
|
1314
1375
|
const defaultSessionId = 'session-1';
|
|
1315
1376
|
const defaultModelId = 'test';
|
|
1377
|
+
const composerFontSize = 13;
|
|
1378
|
+
const composerLineHeight = 20;
|
|
1316
1379
|
return {
|
|
1317
1380
|
assetDir: '',
|
|
1381
|
+
composerFontFamily: 'system-ui',
|
|
1382
|
+
composerFontSize,
|
|
1383
|
+
composerHeight: composerLineHeight + 8,
|
|
1384
|
+
composerLineHeight,
|
|
1318
1385
|
composerValue: '',
|
|
1319
1386
|
errorCount: 0,
|
|
1320
1387
|
focus: 'composer',
|
|
@@ -1325,12 +1392,18 @@ const createDefaultState = () => {
|
|
|
1325
1392
|
inputSource: 'script',
|
|
1326
1393
|
lastSubmittedSessionId: '',
|
|
1327
1394
|
listItemHeight: 40,
|
|
1395
|
+
mockApiCommandId: '',
|
|
1328
1396
|
models: getDefaultModels(),
|
|
1329
1397
|
nextMessageId: 1,
|
|
1398
|
+
openApiApiBaseUrl: 'https://api.openai.com/v1',
|
|
1399
|
+
openApiApiKey: '',
|
|
1400
|
+
openApiApiKeyInput: '',
|
|
1401
|
+
openApiApiKeysSettingsUrl: 'https://platform.openai.com/api-keys',
|
|
1330
1402
|
openRouterApiBaseUrl: 'https://openrouter.ai/api/v1',
|
|
1331
1403
|
openRouterApiKey: '',
|
|
1332
1404
|
openRouterApiKeyInput: '',
|
|
1333
1405
|
openRouterApiKeysSettingsUrl: 'https://openrouter.ai/settings/keys',
|
|
1406
|
+
openRouterApiKeyState: 'idle',
|
|
1334
1407
|
platform: 0,
|
|
1335
1408
|
renamingSessionId: '',
|
|
1336
1409
|
selectedModelId: defaultModelId,
|
|
@@ -1344,6 +1417,7 @@ const createDefaultState = () => {
|
|
|
1344
1417
|
tokensUsed: 0,
|
|
1345
1418
|
uid: 0,
|
|
1346
1419
|
usageOverviewEnabled: false,
|
|
1420
|
+
useMockApi: false,
|
|
1347
1421
|
viewMode: 'list',
|
|
1348
1422
|
warningCount: 0,
|
|
1349
1423
|
width: 0,
|
|
@@ -2084,14 +2158,23 @@ const deleteSession = async (state, id) => {
|
|
|
2084
2158
|
};
|
|
2085
2159
|
};
|
|
2086
2160
|
|
|
2161
|
+
const handleClickOpenApiApiKeySettings = async state => {
|
|
2162
|
+
await openExternal(state.openApiApiKeysSettingsUrl);
|
|
2163
|
+
return state;
|
|
2164
|
+
};
|
|
2165
|
+
|
|
2087
2166
|
const handleClickOpenRouterApiKeySettings = async state => {
|
|
2088
2167
|
await openExternal(state.openRouterApiKeysSettingsUrl);
|
|
2089
2168
|
return state;
|
|
2090
2169
|
};
|
|
2091
2170
|
|
|
2171
|
+
const openApiApiKeyRequiredMessage = 'OpenAI API key is not configured. Enter your OpenAI API key below and click Save.';
|
|
2172
|
+
const openApiRequestFailedMessage = 'OpenAI request failed.';
|
|
2092
2173
|
const openRouterApiKeyRequiredMessage = 'OpenRouter API key is not configured. Enter your OpenRouter API key below and click Save.';
|
|
2093
2174
|
const openRouterRequestFailedMessage = 'OpenRouter request failed. Possible reasons:';
|
|
2175
|
+
const openRouterTooManyRequestsMessage = 'OpenRouter rate limit reached (429). Please try again soon. Helpful tips:';
|
|
2094
2176
|
const openRouterRequestFailureReasons = ['ContentSecurityPolicyViolation: Check DevTools for details.', 'OpenRouter server offline: Check DevTools for details.', 'Check your internet connection.'];
|
|
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.'];
|
|
2095
2178
|
|
|
2096
2179
|
const delay = async ms => {
|
|
2097
2180
|
await new Promise(resolve => setTimeout(resolve, ms));
|
|
@@ -2102,10 +2185,133 @@ const getMockAiResponse = async userMessage => {
|
|
|
2102
2185
|
return `Mock AI response: I received "${userMessage}".`;
|
|
2103
2186
|
};
|
|
2104
2187
|
|
|
2105
|
-
const
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2188
|
+
const activateByEvent = (event, assetDir, platform) => {
|
|
2189
|
+
// @ts-ignore
|
|
2190
|
+
return activateByEvent$1(event, assetDir, platform);
|
|
2191
|
+
};
|
|
2192
|
+
|
|
2193
|
+
const executeProvider = async ({
|
|
2194
|
+
assetDir,
|
|
2195
|
+
event,
|
|
2196
|
+
method,
|
|
2197
|
+
noProviderFoundMessage,
|
|
2198
|
+
params,
|
|
2199
|
+
platform
|
|
2200
|
+
}) => {
|
|
2201
|
+
await activateByEvent(event, assetDir, platform);
|
|
2202
|
+
// @ts-ignore
|
|
2203
|
+
const result = invoke$1(method, ...params);
|
|
2204
|
+
return result;
|
|
2205
|
+
};
|
|
2206
|
+
|
|
2207
|
+
const CommandExecute = 'ExtensionHostCommand.executeCommand';
|
|
2208
|
+
|
|
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;
|
|
2227
|
+
};
|
|
2228
|
+
|
|
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
|
+
}
|
|
2311
|
+
};
|
|
2312
|
+
|
|
2313
|
+
const getOpenApiApiEndpoint = openApiApiBaseUrl => {
|
|
2314
|
+
return `${openApiApiBaseUrl}/chat/completions`;
|
|
2109
2315
|
};
|
|
2110
2316
|
|
|
2111
2317
|
const getTextContent = content => {
|
|
@@ -2129,15 +2335,255 @@ const getTextContent = content => {
|
|
|
2129
2335
|
return textParts.join('\n');
|
|
2130
2336
|
};
|
|
2131
2337
|
|
|
2132
|
-
const
|
|
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
|
+
};
|
|
2361
|
+
const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApiApiBaseUrl) => {
|
|
2362
|
+
let response;
|
|
2363
|
+
try {
|
|
2364
|
+
response = await fetch(getOpenApiApiEndpoint(openApiApiBaseUrl), {
|
|
2365
|
+
body: JSON.stringify({
|
|
2366
|
+
messages: messages.map(message => ({
|
|
2367
|
+
content: message.text,
|
|
2368
|
+
role: message.role
|
|
2369
|
+
})),
|
|
2370
|
+
model: modelId
|
|
2371
|
+
}),
|
|
2372
|
+
headers: {
|
|
2373
|
+
Authorization: `Bearer ${openApiApiKey}`,
|
|
2374
|
+
'Content-Type': 'application/json'
|
|
2375
|
+
},
|
|
2376
|
+
method: 'POST'
|
|
2377
|
+
});
|
|
2378
|
+
} catch {
|
|
2379
|
+
return {
|
|
2380
|
+
details: 'request-failed',
|
|
2381
|
+
type: 'error'
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
if (!response.ok) {
|
|
2385
|
+
const {
|
|
2386
|
+
errorCode,
|
|
2387
|
+
errorMessage,
|
|
2388
|
+
errorType
|
|
2389
|
+
} = await getOpenApiErrorDetails(response);
|
|
2390
|
+
return {
|
|
2391
|
+
details: 'http-error',
|
|
2392
|
+
errorCode,
|
|
2393
|
+
errorMessage,
|
|
2394
|
+
errorType,
|
|
2395
|
+
statusCode: response.status,
|
|
2396
|
+
type: 'error'
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
let parsed;
|
|
2400
|
+
try {
|
|
2401
|
+
parsed = await response.json();
|
|
2402
|
+
} catch {
|
|
2403
|
+
return {
|
|
2404
|
+
details: 'request-failed',
|
|
2405
|
+
type: 'error'
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2409
|
+
return {
|
|
2410
|
+
text: '',
|
|
2411
|
+
type: 'success'
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
const choices = Reflect.get(parsed, 'choices');
|
|
2415
|
+
if (!Array.isArray(choices)) {
|
|
2416
|
+
return {
|
|
2417
|
+
text: '',
|
|
2418
|
+
type: 'success'
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
const firstChoice = choices[0];
|
|
2422
|
+
if (!firstChoice || typeof firstChoice !== 'object') {
|
|
2423
|
+
return {
|
|
2424
|
+
text: '',
|
|
2425
|
+
type: 'success'
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2428
|
+
const message = Reflect.get(firstChoice, 'message');
|
|
2429
|
+
if (!message || typeof message !== 'object') {
|
|
2430
|
+
return {
|
|
2431
|
+
text: '',
|
|
2432
|
+
type: 'success'
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
const content = Reflect.get(message, 'content');
|
|
2436
|
+
return {
|
|
2437
|
+
text: getTextContent(content),
|
|
2438
|
+
type: 'success'
|
|
2439
|
+
};
|
|
2440
|
+
};
|
|
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
|
+
|
|
2487
|
+
const getOpenApiModelId = selectedModelId => {
|
|
2488
|
+
const openApiPrefix = 'openapi/';
|
|
2489
|
+
const openAiPrefix = 'openai/';
|
|
2490
|
+
const normalizedModelId = selectedModelId.toLowerCase();
|
|
2491
|
+
if (normalizedModelId.startsWith(openApiPrefix)) {
|
|
2492
|
+
return selectedModelId.slice(openApiPrefix.length);
|
|
2493
|
+
}
|
|
2494
|
+
if (normalizedModelId.startsWith(openAiPrefix)) {
|
|
2495
|
+
return selectedModelId.slice(openAiPrefix.length);
|
|
2496
|
+
}
|
|
2497
|
+
return selectedModelId;
|
|
2498
|
+
};
|
|
2499
|
+
|
|
2500
|
+
const getOpenRouterApiEndpoint = openRouterApiBaseUrl => {
|
|
2501
|
+
const trimmedBaseUrl = openRouterApiBaseUrl.replace(/\/+$/, '');
|
|
2502
|
+
return `${trimmedBaseUrl}/chat/completions`;
|
|
2503
|
+
};
|
|
2504
|
+
|
|
2505
|
+
const getOpenRouterKeyEndpoint = openRouterApiBaseUrl => {
|
|
2506
|
+
const trimmedBaseUrl = openRouterApiBaseUrl.replace(/\/+$/, '');
|
|
2507
|
+
return `${trimmedBaseUrl}/auth/key`;
|
|
2508
|
+
};
|
|
2509
|
+
|
|
2510
|
+
const getOpenRouterRaw429Message = async response => {
|
|
2511
|
+
let parsed;
|
|
2512
|
+
try {
|
|
2513
|
+
parsed = await response.json();
|
|
2514
|
+
} catch {
|
|
2515
|
+
return undefined;
|
|
2516
|
+
}
|
|
2517
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2518
|
+
return undefined;
|
|
2519
|
+
}
|
|
2520
|
+
const error = Reflect.get(parsed, 'error');
|
|
2521
|
+
if (!error || typeof error !== 'object') {
|
|
2522
|
+
return undefined;
|
|
2523
|
+
}
|
|
2524
|
+
const metadata = Reflect.get(error, 'metadata');
|
|
2525
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
2526
|
+
return undefined;
|
|
2527
|
+
}
|
|
2528
|
+
const raw = Reflect.get(metadata, 'raw');
|
|
2529
|
+
if (typeof raw !== 'string' || !raw) {
|
|
2530
|
+
return undefined;
|
|
2531
|
+
}
|
|
2532
|
+
return raw;
|
|
2533
|
+
};
|
|
2534
|
+
const getOpenRouterLimitInfo = async (openRouterApiKey, openRouterApiBaseUrl) => {
|
|
2535
|
+
let response;
|
|
2536
|
+
try {
|
|
2537
|
+
response = await fetch(getOpenRouterKeyEndpoint(openRouterApiBaseUrl), {
|
|
2538
|
+
headers: {
|
|
2539
|
+
Authorization: `Bearer ${openRouterApiKey}`
|
|
2540
|
+
},
|
|
2541
|
+
method: 'GET'
|
|
2542
|
+
});
|
|
2543
|
+
} catch {
|
|
2544
|
+
return undefined;
|
|
2545
|
+
}
|
|
2546
|
+
if (!response.ok) {
|
|
2547
|
+
return undefined;
|
|
2548
|
+
}
|
|
2549
|
+
let parsed;
|
|
2550
|
+
try {
|
|
2551
|
+
parsed = await response.json();
|
|
2552
|
+
} catch {
|
|
2553
|
+
return undefined;
|
|
2554
|
+
}
|
|
2555
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2556
|
+
return undefined;
|
|
2557
|
+
}
|
|
2558
|
+
const data = Reflect.get(parsed, 'data');
|
|
2559
|
+
if (!data || typeof data !== 'object') {
|
|
2560
|
+
return undefined;
|
|
2561
|
+
}
|
|
2562
|
+
const limitRemaining = Reflect.get(data, 'limit_remaining');
|
|
2563
|
+
const limitReset = Reflect.get(data, 'limit_reset');
|
|
2564
|
+
const usage = Reflect.get(data, 'usage');
|
|
2565
|
+
const usageDaily = Reflect.get(data, 'usage_daily');
|
|
2566
|
+
const normalizedLimitInfo = {
|
|
2567
|
+
limitRemaining: typeof limitRemaining === 'number' || limitRemaining === null ? limitRemaining : undefined,
|
|
2568
|
+
limitReset: typeof limitReset === 'string' || limitReset === null ? limitReset : undefined,
|
|
2569
|
+
usage: typeof usage === 'number' ? usage : undefined,
|
|
2570
|
+
usageDaily: typeof usageDaily === 'number' ? usageDaily : undefined
|
|
2571
|
+
};
|
|
2572
|
+
const hasLimitInfo = normalizedLimitInfo.limitRemaining !== undefined || normalizedLimitInfo.limitReset !== undefined || normalizedLimitInfo.usage !== undefined || normalizedLimitInfo.usageDaily !== undefined;
|
|
2573
|
+
if (!hasLimitInfo) {
|
|
2574
|
+
return undefined;
|
|
2575
|
+
}
|
|
2576
|
+
return normalizedLimitInfo;
|
|
2577
|
+
};
|
|
2578
|
+
const getOpenRouterAssistantText = async (messages, modelId, openRouterApiKey, openRouterApiBaseUrl) => {
|
|
2133
2579
|
let response;
|
|
2134
2580
|
try {
|
|
2135
2581
|
response = await fetch(getOpenRouterApiEndpoint(openRouterApiBaseUrl), {
|
|
2136
2582
|
body: JSON.stringify({
|
|
2137
|
-
messages:
|
|
2138
|
-
content:
|
|
2139
|
-
role:
|
|
2140
|
-
}
|
|
2583
|
+
messages: messages.map(message => ({
|
|
2584
|
+
content: message.text,
|
|
2585
|
+
role: message.role
|
|
2586
|
+
})),
|
|
2141
2587
|
model: modelId
|
|
2142
2588
|
}),
|
|
2143
2589
|
headers: {
|
|
@@ -2147,29 +2593,117 @@ const getOpenRouterAssistantText = async (userText, modelId, openRouterApiKey, o
|
|
|
2147
2593
|
method: 'POST'
|
|
2148
2594
|
});
|
|
2149
2595
|
} catch {
|
|
2150
|
-
|
|
2596
|
+
return {
|
|
2597
|
+
details: 'request-failed',
|
|
2598
|
+
type: 'error'
|
|
2599
|
+
};
|
|
2151
2600
|
}
|
|
2152
2601
|
if (!response.ok) {
|
|
2153
|
-
|
|
2602
|
+
if (response.status === 429) {
|
|
2603
|
+
const retryAfter = response.headers?.get?.('retry-after') ?? null;
|
|
2604
|
+
const rawMessage = await getOpenRouterRaw429Message(response);
|
|
2605
|
+
const limitInfo = await getOpenRouterLimitInfo(openRouterApiKey, openRouterApiBaseUrl);
|
|
2606
|
+
return {
|
|
2607
|
+
details: 'too-many-requests',
|
|
2608
|
+
limitInfo: limitInfo || retryAfter ? {
|
|
2609
|
+
...limitInfo,
|
|
2610
|
+
retryAfter
|
|
2611
|
+
} : undefined,
|
|
2612
|
+
rawMessage,
|
|
2613
|
+
statusCode: 429,
|
|
2614
|
+
type: 'error'
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
return {
|
|
2618
|
+
details: 'http-error',
|
|
2619
|
+
statusCode: response.status,
|
|
2620
|
+
type: 'error'
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
let parsed;
|
|
2624
|
+
try {
|
|
2625
|
+
parsed = await response.json();
|
|
2626
|
+
} catch {
|
|
2627
|
+
return {
|
|
2628
|
+
details: 'request-failed',
|
|
2629
|
+
type: 'error'
|
|
2630
|
+
};
|
|
2154
2631
|
}
|
|
2155
|
-
const parsed = await response.json();
|
|
2156
2632
|
if (!parsed || typeof parsed !== 'object') {
|
|
2157
|
-
return
|
|
2633
|
+
return {
|
|
2634
|
+
text: '',
|
|
2635
|
+
type: 'success'
|
|
2636
|
+
};
|
|
2158
2637
|
}
|
|
2159
2638
|
const choices = Reflect.get(parsed, 'choices');
|
|
2160
2639
|
if (!Array.isArray(choices)) {
|
|
2161
|
-
return
|
|
2640
|
+
return {
|
|
2641
|
+
text: '',
|
|
2642
|
+
type: 'success'
|
|
2643
|
+
};
|
|
2162
2644
|
}
|
|
2163
2645
|
const firstChoice = choices[0];
|
|
2164
2646
|
if (!firstChoice || typeof firstChoice !== 'object') {
|
|
2165
|
-
return
|
|
2647
|
+
return {
|
|
2648
|
+
text: '',
|
|
2649
|
+
type: 'success'
|
|
2650
|
+
};
|
|
2166
2651
|
}
|
|
2167
2652
|
const message = Reflect.get(firstChoice, 'message');
|
|
2168
2653
|
if (!message || typeof message !== 'object') {
|
|
2169
|
-
return
|
|
2654
|
+
return {
|
|
2655
|
+
text: '',
|
|
2656
|
+
type: 'success'
|
|
2657
|
+
};
|
|
2170
2658
|
}
|
|
2171
2659
|
const content = Reflect.get(message, 'content');
|
|
2172
|
-
return
|
|
2660
|
+
return {
|
|
2661
|
+
text: getTextContent(content),
|
|
2662
|
+
type: 'success'
|
|
2663
|
+
};
|
|
2664
|
+
};
|
|
2665
|
+
|
|
2666
|
+
const getOpenRouterTooManyRequestsMessage = errorResult => {
|
|
2667
|
+
const details = [];
|
|
2668
|
+
if (errorResult.rawMessage) {
|
|
2669
|
+
details.push(errorResult.rawMessage);
|
|
2670
|
+
}
|
|
2671
|
+
const {
|
|
2672
|
+
limitInfo
|
|
2673
|
+
} = errorResult;
|
|
2674
|
+
if (limitInfo) {
|
|
2675
|
+
if (limitInfo.retryAfter) {
|
|
2676
|
+
details.push(`Retry after: ${limitInfo.retryAfter}.`);
|
|
2677
|
+
}
|
|
2678
|
+
if (limitInfo.limitReset) {
|
|
2679
|
+
details.push(`Limit resets: ${limitInfo.limitReset}.`);
|
|
2680
|
+
}
|
|
2681
|
+
if (limitInfo.limitRemaining === null) {
|
|
2682
|
+
details.push('Credits remaining: unlimited.');
|
|
2683
|
+
} else if (typeof limitInfo.limitRemaining === 'number') {
|
|
2684
|
+
details.push(`Credits remaining: ${limitInfo.limitRemaining}.`);
|
|
2685
|
+
}
|
|
2686
|
+
if (typeof limitInfo.usageDaily === 'number') {
|
|
2687
|
+
details.push(`Credits used today (UTC): ${limitInfo.usageDaily}.`);
|
|
2688
|
+
}
|
|
2689
|
+
if (typeof limitInfo.usage === 'number') {
|
|
2690
|
+
details.push(`Credits used (all time): ${limitInfo.usage}.`);
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
if (details.length === 0) {
|
|
2694
|
+
return openRouterTooManyRequestsMessage;
|
|
2695
|
+
}
|
|
2696
|
+
return `${openRouterTooManyRequestsMessage} ${details.join(' ')}`;
|
|
2697
|
+
};
|
|
2698
|
+
|
|
2699
|
+
const getOpenRouterErrorMessage = errorResult => {
|
|
2700
|
+
switch (errorResult.details) {
|
|
2701
|
+
case 'http-error':
|
|
2702
|
+
case 'request-failed':
|
|
2703
|
+
return openRouterRequestFailedMessage;
|
|
2704
|
+
case 'too-many-requests':
|
|
2705
|
+
return getOpenRouterTooManyRequestsMessage(errorResult);
|
|
2706
|
+
}
|
|
2173
2707
|
};
|
|
2174
2708
|
|
|
2175
2709
|
/* eslint-disable @cspell/spellchecker */
|
|
@@ -2181,6 +2715,16 @@ const getOpenRouterModelId = selectedModelId => {
|
|
|
2181
2715
|
return selectedModelId;
|
|
2182
2716
|
};
|
|
2183
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;
|
|
2723
|
+
}
|
|
2724
|
+
const normalizedModelId = selectedModelId.toLowerCase();
|
|
2725
|
+
return normalizedModelId.startsWith('openapi/') || normalizedModelId.startsWith('openai/');
|
|
2726
|
+
};
|
|
2727
|
+
|
|
2184
2728
|
/* eslint-disable @cspell/spellchecker */
|
|
2185
2729
|
|
|
2186
2730
|
const isOpenRouterModel = (selectedModelId, models) => {
|
|
@@ -2192,25 +2736,65 @@ const isOpenRouterModel = (selectedModelId, models) => {
|
|
|
2192
2736
|
return selectedModelId.toLowerCase().startsWith('openrouter/');
|
|
2193
2737
|
};
|
|
2194
2738
|
|
|
2195
|
-
const getAiResponse = async (
|
|
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
|
+
}) => {
|
|
2196
2754
|
let text = '';
|
|
2755
|
+
const usesOpenApiModel = isOpenApiModel(selectedModelId, models);
|
|
2197
2756
|
const usesOpenRouterModel = isOpenRouterModel(selectedModelId, models);
|
|
2198
|
-
if (
|
|
2199
|
-
if (
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2757
|
+
if (usesOpenApiModel) {
|
|
2758
|
+
if (openApiApiKey) {
|
|
2759
|
+
const result = await getOpenApiAssistantText(messages, getOpenApiModelId(selectedModelId), openApiApiKey, openApiApiBaseUrl);
|
|
2760
|
+
if (result.type === 'success') {
|
|
2761
|
+
const {
|
|
2762
|
+
text: assistantText
|
|
2763
|
+
} = result;
|
|
2764
|
+
text = assistantText;
|
|
2765
|
+
} else {
|
|
2766
|
+
text = getOpenApiErrorMessage(result);
|
|
2767
|
+
}
|
|
2768
|
+
} else {
|
|
2769
|
+
text = openApiApiKeyRequiredMessage;
|
|
2770
|
+
}
|
|
2771
|
+
} else if (usesOpenRouterModel) {
|
|
2772
|
+
const modelId = getOpenRouterModelId(selectedModelId);
|
|
2773
|
+
if (useMockApi) {
|
|
2774
|
+
const result = await getMockOpenRouterAssistantText(messages, modelId, openRouterApiBaseUrl, openRouterApiKey, mockApiCommandId, assetDir, platform);
|
|
2775
|
+
if (result.type === 'success') {
|
|
2776
|
+
const {
|
|
2777
|
+
text: assistantText
|
|
2778
|
+
} = result;
|
|
2779
|
+
text = assistantText;
|
|
2780
|
+
} else {
|
|
2781
|
+
text = getOpenRouterErrorMessage(result);
|
|
2782
|
+
}
|
|
2783
|
+
} else if (openRouterApiKey) {
|
|
2784
|
+
const result = await getOpenRouterAssistantText(messages, modelId, openRouterApiKey, openRouterApiBaseUrl);
|
|
2785
|
+
if (result.type === 'success') {
|
|
2786
|
+
const {
|
|
2787
|
+
text: assistantText
|
|
2788
|
+
} = result;
|
|
2789
|
+
text = assistantText;
|
|
2790
|
+
} else {
|
|
2791
|
+
text = getOpenRouterErrorMessage(result);
|
|
2208
2792
|
}
|
|
2209
2793
|
} else {
|
|
2210
2794
|
text = openRouterApiKeyRequiredMessage;
|
|
2211
2795
|
}
|
|
2212
2796
|
}
|
|
2213
|
-
if (!text && !usesOpenRouterModel) {
|
|
2797
|
+
if (!text && !usesOpenApiModel && !usesOpenRouterModel) {
|
|
2214
2798
|
text = await getMockAiResponse(userText);
|
|
2215
2799
|
}
|
|
2216
2800
|
const assistantTime = new Date().toLocaleTimeString([], {
|
|
@@ -2232,6 +2816,75 @@ const update = async settings => {
|
|
|
2232
2816
|
await invoke('Preferences.update', settings);
|
|
2233
2817
|
};
|
|
2234
2818
|
|
|
2819
|
+
const setOpenApiApiKey = async (state, openApiApiKey, persist = true) => {
|
|
2820
|
+
if (persist) {
|
|
2821
|
+
await update({
|
|
2822
|
+
'secrets.openApiKey': openApiApiKey
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
return {
|
|
2826
|
+
...state,
|
|
2827
|
+
openApiApiKey,
|
|
2828
|
+
openApiApiKeyInput: openApiApiKey
|
|
2829
|
+
};
|
|
2830
|
+
};
|
|
2831
|
+
|
|
2832
|
+
const handleClickSaveOpenApiApiKey = async state => {
|
|
2833
|
+
const {
|
|
2834
|
+
openApiApiKeyInput
|
|
2835
|
+
} = state;
|
|
2836
|
+
const openApiApiKey = openApiApiKeyInput.trim();
|
|
2837
|
+
if (!openApiApiKey) {
|
|
2838
|
+
return state;
|
|
2839
|
+
}
|
|
2840
|
+
const updatedState = await setOpenApiApiKey(state, openApiApiKey);
|
|
2841
|
+
const session = updatedState.sessions.find(item => item.id === updatedState.selectedSessionId);
|
|
2842
|
+
if (!session) {
|
|
2843
|
+
return updatedState;
|
|
2844
|
+
}
|
|
2845
|
+
const lastMessage = session.messages.at(-1);
|
|
2846
|
+
const shouldRetryOpenApi = lastMessage?.role === 'assistant' && lastMessage.text === openApiApiKeyRequiredMessage;
|
|
2847
|
+
if (!shouldRetryOpenApi) {
|
|
2848
|
+
return updatedState;
|
|
2849
|
+
}
|
|
2850
|
+
const previousUserMessage = session.messages.toReversed().find(item => item.role === 'user');
|
|
2851
|
+
if (!previousUserMessage) {
|
|
2852
|
+
return updatedState;
|
|
2853
|
+
}
|
|
2854
|
+
const retryMessages = session.messages.slice(0, -1);
|
|
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
|
+
});
|
|
2870
|
+
const updatedSession = {
|
|
2871
|
+
...session,
|
|
2872
|
+
messages: [...session.messages.slice(0, -1), assistantMessage]
|
|
2873
|
+
};
|
|
2874
|
+
await saveChatSession(updatedSession);
|
|
2875
|
+
const updatedSessions = updatedState.sessions.map(item => {
|
|
2876
|
+
if (item.id !== updatedState.selectedSessionId) {
|
|
2877
|
+
return item;
|
|
2878
|
+
}
|
|
2879
|
+
return updatedSession;
|
|
2880
|
+
});
|
|
2881
|
+
return {
|
|
2882
|
+
...updatedState,
|
|
2883
|
+
nextMessageId: updatedState.nextMessageId + 1,
|
|
2884
|
+
sessions: updatedSessions
|
|
2885
|
+
};
|
|
2886
|
+
};
|
|
2887
|
+
|
|
2235
2888
|
const setOpenRouterApiKey = async (state, openRouterApiKey, persist = true) => {
|
|
2236
2889
|
if (persist) {
|
|
2237
2890
|
await update({
|
|
@@ -2253,7 +2906,18 @@ const handleClickSaveOpenRouterApiKey = async state => {
|
|
|
2253
2906
|
if (!openRouterApiKey) {
|
|
2254
2907
|
return state;
|
|
2255
2908
|
}
|
|
2256
|
-
const
|
|
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
|
+
};
|
|
2257
2921
|
const session = updatedState.sessions.find(item => item.id === updatedState.selectedSessionId);
|
|
2258
2922
|
if (!session) {
|
|
2259
2923
|
return updatedState;
|
|
@@ -2267,7 +2931,22 @@ const handleClickSaveOpenRouterApiKey = async state => {
|
|
|
2267
2931
|
if (!previousUserMessage) {
|
|
2268
2932
|
return updatedState;
|
|
2269
2933
|
}
|
|
2270
|
-
const
|
|
2934
|
+
const retryMessages = session.messages.slice(0, -1);
|
|
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
|
+
});
|
|
2271
2950
|
const updatedSession = {
|
|
2272
2951
|
...session,
|
|
2273
2952
|
messages: [...session.messages.slice(0, -1), assistantMessage]
|
|
@@ -2282,6 +2961,7 @@ const handleClickSaveOpenRouterApiKey = async state => {
|
|
|
2282
2961
|
return {
|
|
2283
2962
|
...updatedState,
|
|
2284
2963
|
nextMessageId: updatedState.nextMessageId + 1,
|
|
2964
|
+
openRouterApiKeyState: 'idle',
|
|
2285
2965
|
sessions: updatedSessions
|
|
2286
2966
|
};
|
|
2287
2967
|
};
|
|
@@ -2296,14 +2976,20 @@ const focusInput = state => {
|
|
|
2296
2976
|
|
|
2297
2977
|
const handleSubmit = async state => {
|
|
2298
2978
|
const {
|
|
2979
|
+
assetDir,
|
|
2299
2980
|
composerValue,
|
|
2981
|
+
mockApiCommandId,
|
|
2300
2982
|
models,
|
|
2301
2983
|
nextMessageId,
|
|
2984
|
+
openApiApiBaseUrl,
|
|
2985
|
+
openApiApiKey,
|
|
2302
2986
|
openRouterApiBaseUrl,
|
|
2303
2987
|
openRouterApiKey,
|
|
2988
|
+
platform,
|
|
2304
2989
|
selectedModelId,
|
|
2305
2990
|
selectedSessionId,
|
|
2306
2991
|
sessions,
|
|
2992
|
+
useMockApi,
|
|
2307
2993
|
viewMode
|
|
2308
2994
|
} = state;
|
|
2309
2995
|
const userText = composerValue.trim();
|
|
@@ -2343,6 +3029,7 @@ const handleSubmit = async state => {
|
|
|
2343
3029
|
await saveChatSession(newSession);
|
|
2344
3030
|
optimisticState = focusInput({
|
|
2345
3031
|
...state,
|
|
3032
|
+
composerHeight: getMinComposerHeightForState(state),
|
|
2346
3033
|
composerValue: '',
|
|
2347
3034
|
inputSource: 'script',
|
|
2348
3035
|
lastSubmittedSessionId: newSessionId,
|
|
@@ -2367,6 +3054,7 @@ const handleSubmit = async state => {
|
|
|
2367
3054
|
}
|
|
2368
3055
|
optimisticState = focusInput({
|
|
2369
3056
|
...state,
|
|
3057
|
+
composerHeight: getMinComposerHeightForState(state),
|
|
2370
3058
|
composerValue: '',
|
|
2371
3059
|
inputSource: 'script',
|
|
2372
3060
|
lastSubmittedSessionId: selectedSessionId,
|
|
@@ -2377,7 +3065,23 @@ const handleSubmit = async state => {
|
|
|
2377
3065
|
set(state.uid, state, optimisticState);
|
|
2378
3066
|
// @ts-ignore
|
|
2379
3067
|
await invoke('Chat.rerender');
|
|
2380
|
-
const
|
|
3068
|
+
const selectedOptimisticSession = optimisticState.sessions.find(session => session.id === optimisticState.selectedSessionId);
|
|
3069
|
+
const messages = selectedOptimisticSession?.messages ?? [];
|
|
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
|
+
});
|
|
2381
3085
|
const updatedSessions = optimisticState.sessions.map(session => {
|
|
2382
3086
|
if (session.id !== optimisticState.selectedSessionId) {
|
|
2383
3087
|
return session;
|
|
@@ -2438,6 +3142,10 @@ const getRenameIdFromInputName = name => {
|
|
|
2438
3142
|
return name.slice(RenamePrefix.length);
|
|
2439
3143
|
};
|
|
2440
3144
|
|
|
3145
|
+
const OpenApiApiKeyInput = 'open-api-api-key';
|
|
3146
|
+
const SaveOpenApiApiKey = 'save-openapi-api-key';
|
|
3147
|
+
const OpenOpenApiApiKeySettings = 'open-openapi-api-key-settings';
|
|
3148
|
+
|
|
2441
3149
|
/* eslint-disable @cspell/spellchecker */
|
|
2442
3150
|
const OpenRouterApiKeyInput = 'open-router-api-key';
|
|
2443
3151
|
const SaveOpenRouterApiKey = 'save-openrouter-api-key';
|
|
@@ -2524,9 +3232,15 @@ const handleClick = async (state, name, id = '') => {
|
|
|
2524
3232
|
if (name === SaveOpenRouterApiKey) {
|
|
2525
3233
|
return handleClickSaveOpenRouterApiKey(state);
|
|
2526
3234
|
}
|
|
3235
|
+
if (name === SaveOpenApiApiKey) {
|
|
3236
|
+
return handleClickSaveOpenApiApiKey(state);
|
|
3237
|
+
}
|
|
2527
3238
|
if (name === OpenOpenRouterApiKeySettings) {
|
|
2528
3239
|
return handleClickOpenRouterApiKeySettings(state);
|
|
2529
3240
|
}
|
|
3241
|
+
if (name === OpenOpenApiApiKeySettings) {
|
|
3242
|
+
return handleClickOpenApiApiKeySettings(state);
|
|
3243
|
+
}
|
|
2530
3244
|
return state;
|
|
2531
3245
|
};
|
|
2532
3246
|
|
|
@@ -2557,6 +3271,12 @@ const handleClickSettings = async () => {
|
|
|
2557
3271
|
};
|
|
2558
3272
|
|
|
2559
3273
|
const handleInput = async (state, name, value, inputSource = 'user') => {
|
|
3274
|
+
if (name === OpenApiApiKeyInput) {
|
|
3275
|
+
return {
|
|
3276
|
+
...state,
|
|
3277
|
+
openApiApiKeyInput: value
|
|
3278
|
+
};
|
|
3279
|
+
}
|
|
2560
3280
|
if (name === OpenRouterApiKeyInput) {
|
|
2561
3281
|
return {
|
|
2562
3282
|
...state,
|
|
@@ -2566,8 +3286,10 @@ const handleInput = async (state, name, value, inputSource = 'user') => {
|
|
|
2566
3286
|
if (name !== Composer) {
|
|
2567
3287
|
return state;
|
|
2568
3288
|
}
|
|
3289
|
+
const composerHeight = await getComposerHeight(state, value);
|
|
2569
3290
|
return {
|
|
2570
3291
|
...state,
|
|
3292
|
+
composerHeight,
|
|
2571
3293
|
composerValue: value,
|
|
2572
3294
|
inputSource
|
|
2573
3295
|
};
|
|
@@ -2632,6 +3354,7 @@ const submitRename = async state => {
|
|
|
2632
3354
|
}
|
|
2633
3355
|
return {
|
|
2634
3356
|
...state,
|
|
3357
|
+
composerHeight: getMinComposerHeightForState(state),
|
|
2635
3358
|
composerValue: '',
|
|
2636
3359
|
inputSource: 'script',
|
|
2637
3360
|
renamingSessionId: '',
|
|
@@ -2776,6 +3499,23 @@ const loadSelectedSessionMessages = async (sessions, selectedSessionId) => {
|
|
|
2776
3499
|
const loadContent = async (state, savedState) => {
|
|
2777
3500
|
const savedSelectedModelId = getSavedSelectedModelId(savedState);
|
|
2778
3501
|
const savedViewMode = getSavedViewMode(savedState);
|
|
3502
|
+
let openApiApiKey = '';
|
|
3503
|
+
try {
|
|
3504
|
+
const savedOpenApiKey = await get('secrets.openApiKey');
|
|
3505
|
+
if (typeof savedOpenApiKey === 'string' && savedOpenApiKey) {
|
|
3506
|
+
openApiApiKey = savedOpenApiKey;
|
|
3507
|
+
} else {
|
|
3508
|
+
const legacySavedOpenApiApiKey = await get('secrets.openApiApiKey');
|
|
3509
|
+
if (typeof legacySavedOpenApiApiKey === 'string' && legacySavedOpenApiApiKey) {
|
|
3510
|
+
openApiApiKey = legacySavedOpenApiApiKey;
|
|
3511
|
+
} else {
|
|
3512
|
+
const legacySavedOpenAiApiKey = await get('secrets.openAiApiKey');
|
|
3513
|
+
openApiApiKey = typeof legacySavedOpenAiApiKey === 'string' ? legacySavedOpenAiApiKey : '';
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
} catch {
|
|
3517
|
+
openApiApiKey = '';
|
|
3518
|
+
}
|
|
2779
3519
|
let openRouterApiKey = '';
|
|
2780
3520
|
try {
|
|
2781
3521
|
const savedOpenRouterApiKey = await get('secrets.openRouterApiKey');
|
|
@@ -2808,6 +3548,8 @@ const loadContent = async (state, savedState) => {
|
|
|
2808
3548
|
return {
|
|
2809
3549
|
...state,
|
|
2810
3550
|
initial: false,
|
|
3551
|
+
openApiApiKey,
|
|
3552
|
+
openApiApiKeyInput: openApiApiKey,
|
|
2811
3553
|
openRouterApiKey,
|
|
2812
3554
|
openRouterApiKeyInput: openRouterApiKey,
|
|
2813
3555
|
selectedModelId,
|
|
@@ -2906,6 +3648,8 @@ const ChatListItemLabel = 'ChatListItemLabel';
|
|
|
2906
3648
|
const Markdown = 'Markdown';
|
|
2907
3649
|
const Message = 'Message';
|
|
2908
3650
|
const ChatMessageContent = 'ChatMessageContent';
|
|
3651
|
+
const ChatOrderedList = 'ChatOrderedList';
|
|
3652
|
+
const ChatOrderedListItem = 'ChatOrderedListItem';
|
|
2909
3653
|
const MessageUser = 'MessageUser';
|
|
2910
3654
|
const MessageAssistant = 'MessageAssistant';
|
|
2911
3655
|
const MultilineInputBox = 'MultilineInputBox';
|
|
@@ -2935,8 +3679,12 @@ const getModelLabel = model => {
|
|
|
2935
3679
|
if (model.provider === 'openRouter') {
|
|
2936
3680
|
return `${model.name} (OpenRouter)`;
|
|
2937
3681
|
}
|
|
3682
|
+
if (model.provider === 'openApi' || model.provider === 'openAI' || model.provider === 'openai') {
|
|
3683
|
+
return `${model.name} (OpenAI)`;
|
|
3684
|
+
}
|
|
2938
3685
|
return model.name;
|
|
2939
3686
|
};
|
|
3687
|
+
|
|
2940
3688
|
const getModelOptionDOm = (model, selectedModelId) => {
|
|
2941
3689
|
return [{
|
|
2942
3690
|
childCount: 1,
|
|
@@ -3004,7 +3752,7 @@ const getUsageOverviewDom = (tokensUsed, tokensMax) => {
|
|
|
3004
3752
|
}, text(usageLabel)];
|
|
3005
3753
|
};
|
|
3006
3754
|
|
|
3007
|
-
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) => {
|
|
3008
3756
|
const isSendDisabled = composerValue.trim() === '';
|
|
3009
3757
|
const modelOptions = models.flatMap(model => getModelOptionDOm(model, selectedModelId));
|
|
3010
3758
|
return [{
|
|
@@ -3022,7 +3770,7 @@ const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOvervie
|
|
|
3022
3770
|
onFocus: HandleFocus,
|
|
3023
3771
|
onInput: HandleInput,
|
|
3024
3772
|
placeholder: composePlaceholder(),
|
|
3025
|
-
|
|
3773
|
+
style: `height:${composerHeight}px;font-size:${composerFontSize}px;font-family:${composerFontFamily};line-height:${composerLineHeight}px;`,
|
|
3026
3774
|
type: TextArea,
|
|
3027
3775
|
value: composerValue
|
|
3028
3776
|
}, {
|
|
@@ -3111,18 +3859,27 @@ const getChatHeaderDomDetailMode = selectedSessionTitle => {
|
|
|
3111
3859
|
}, text(selectedSessionTitle), ...getChatHeaderActionsDom()];
|
|
3112
3860
|
};
|
|
3113
3861
|
|
|
3114
|
-
const
|
|
3862
|
+
const getMissingApiKeyDom = ({
|
|
3863
|
+
getApiKeyText,
|
|
3864
|
+
inputName,
|
|
3865
|
+
inputValue,
|
|
3866
|
+
openSettingsButtonName,
|
|
3867
|
+
placeholder,
|
|
3868
|
+
saveButtonDisabled = false,
|
|
3869
|
+
saveButtonName,
|
|
3870
|
+
saveButtonText = save()
|
|
3871
|
+
}) => {
|
|
3115
3872
|
return [{
|
|
3116
3873
|
childCount: 2,
|
|
3117
3874
|
type: Div
|
|
3118
3875
|
}, {
|
|
3119
3876
|
childCount: 0,
|
|
3120
3877
|
className: InputBox,
|
|
3121
|
-
name:
|
|
3878
|
+
name: inputName,
|
|
3122
3879
|
onInput: HandleInput,
|
|
3123
|
-
placeholder
|
|
3880
|
+
placeholder,
|
|
3124
3881
|
type: Input,
|
|
3125
|
-
value:
|
|
3882
|
+
value: inputValue
|
|
3126
3883
|
}, {
|
|
3127
3884
|
childCount: 2,
|
|
3128
3885
|
className: Actions,
|
|
@@ -3130,35 +3887,77 @@ const getMissingOpenRouterApiKeyDom = openRouterApiKeyInput => {
|
|
|
3130
3887
|
}, {
|
|
3131
3888
|
childCount: 1,
|
|
3132
3889
|
className: mergeClassNames(Button, ButtonPrimary),
|
|
3133
|
-
|
|
3890
|
+
disabled: saveButtonDisabled,
|
|
3891
|
+
name: saveButtonName,
|
|
3134
3892
|
onClick: HandleClick,
|
|
3135
3893
|
type: Button$1
|
|
3136
|
-
}, text(
|
|
3894
|
+
}, text(saveButtonText), {
|
|
3137
3895
|
childCount: 1,
|
|
3138
3896
|
className: mergeClassNames(Button, ButtonSecondary),
|
|
3139
|
-
name:
|
|
3897
|
+
name: openSettingsButtonName,
|
|
3140
3898
|
onClick: HandleClick,
|
|
3141
3899
|
type: Button$1
|
|
3142
|
-
}, text(
|
|
3900
|
+
}, text(getApiKeyText)];
|
|
3901
|
+
};
|
|
3902
|
+
|
|
3903
|
+
const getMissingOpenApiApiKeyDom = openApiApiKeyInput => {
|
|
3904
|
+
return getMissingApiKeyDom({
|
|
3905
|
+
getApiKeyText: getOpenApiApiKey(),
|
|
3906
|
+
inputName: OpenApiApiKeyInput,
|
|
3907
|
+
inputValue: openApiApiKeyInput,
|
|
3908
|
+
openSettingsButtonName: OpenOpenApiApiKeySettings,
|
|
3909
|
+
placeholder: openApiApiKeyPlaceholder(),
|
|
3910
|
+
saveButtonName: SaveOpenApiApiKey
|
|
3911
|
+
});
|
|
3912
|
+
};
|
|
3913
|
+
|
|
3914
|
+
const getMissingOpenRouterApiKeyDom = (openRouterApiKeyInput, openRouterApiKeyState = 'idle') => {
|
|
3915
|
+
const isSaving = openRouterApiKeyState === 'saving';
|
|
3916
|
+
return getMissingApiKeyDom({
|
|
3917
|
+
getApiKeyText: getOpenRouterApiKey(),
|
|
3918
|
+
inputName: OpenRouterApiKeyInput,
|
|
3919
|
+
inputValue: openRouterApiKeyInput,
|
|
3920
|
+
openSettingsButtonName: OpenOpenRouterApiKeySettings,
|
|
3921
|
+
placeholder: openRouterApiKeyPlaceholder(),
|
|
3922
|
+
saveButtonDisabled: isSaving,
|
|
3923
|
+
saveButtonName: SaveOpenRouterApiKey,
|
|
3924
|
+
saveButtonText: isSaving ? saving() : save()
|
|
3925
|
+
});
|
|
3143
3926
|
};
|
|
3144
3927
|
|
|
3145
3928
|
const getOpenRouterRequestFailedDom = () => {
|
|
3146
3929
|
return [{
|
|
3147
3930
|
childCount: openRouterRequestFailureReasons.length,
|
|
3148
|
-
className:
|
|
3931
|
+
className: ChatOrderedList,
|
|
3149
3932
|
type: Ol
|
|
3150
3933
|
}, ...openRouterRequestFailureReasons.flatMap(reason => {
|
|
3151
3934
|
return [{
|
|
3152
3935
|
childCount: 1,
|
|
3936
|
+
className: ChatOrderedListItem,
|
|
3937
|
+
type: Li
|
|
3938
|
+
}, text(reason)];
|
|
3939
|
+
})];
|
|
3940
|
+
};
|
|
3941
|
+
const getOpenRouterTooManyRequestsDom = () => {
|
|
3942
|
+
return [{
|
|
3943
|
+
childCount: openRouterTooManyRequestsReasons.length,
|
|
3944
|
+
className: ChatOrderedList,
|
|
3945
|
+
type: Ol
|
|
3946
|
+
}, ...openRouterTooManyRequestsReasons.flatMap(reason => {
|
|
3947
|
+
return [{
|
|
3948
|
+
childCount: 1,
|
|
3949
|
+
className: ChatOrderedListItem,
|
|
3153
3950
|
type: Li
|
|
3154
3951
|
}, text(reason)];
|
|
3155
3952
|
})];
|
|
3156
3953
|
};
|
|
3157
|
-
const getChatMessageDom = (message, openRouterApiKeyInput) => {
|
|
3954
|
+
const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput = '', openRouterApiKeyState = 'idle') => {
|
|
3158
3955
|
const roleClassName = message.role === 'user' ? MessageUser : MessageAssistant;
|
|
3956
|
+
const isOpenApiApiKeyMissingMessage = message.role === 'assistant' && message.text === openApiApiKeyRequiredMessage;
|
|
3159
3957
|
const isOpenRouterApiKeyMissingMessage = message.role === 'assistant' && message.text === openRouterApiKeyRequiredMessage;
|
|
3160
3958
|
const isOpenRouterRequestFailedMessage = message.role === 'assistant' && message.text === openRouterRequestFailedMessage;
|
|
3161
|
-
const
|
|
3959
|
+
const isOpenRouterTooManyRequestsMessage = message.role === 'assistant' && message.text.startsWith(openRouterTooManyRequestsMessage);
|
|
3960
|
+
const extraChildCount = isOpenApiApiKeyMissingMessage || isOpenRouterApiKeyMissingMessage || isOpenRouterRequestFailedMessage || isOpenRouterTooManyRequestsMessage ? 2 : 1;
|
|
3162
3961
|
return [{
|
|
3163
3962
|
childCount: 1,
|
|
3164
3963
|
className: mergeClassNames(Message, roleClassName),
|
|
@@ -3171,7 +3970,7 @@ const getChatMessageDom = (message, openRouterApiKeyInput) => {
|
|
|
3171
3970
|
childCount: 1,
|
|
3172
3971
|
className: Markdown,
|
|
3173
3972
|
type: P
|
|
3174
|
-
}, text(message.text), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : [])];
|
|
3973
|
+
}, text(message.text), ...(isOpenApiApiKeyMissingMessage ? getMissingOpenApiApiKeyDom(openApiApiKeyInput) : []), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput, openRouterApiKeyState) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : []), ...(isOpenRouterTooManyRequestsMessage ? getOpenRouterTooManyRequestsDom() : [])];
|
|
3175
3974
|
};
|
|
3176
3975
|
|
|
3177
3976
|
const getEmptyMessagesDom = () => {
|
|
@@ -3181,7 +3980,8 @@ const getEmptyMessagesDom = () => {
|
|
|
3181
3980
|
type: Div
|
|
3182
3981
|
}, text(startConversation())];
|
|
3183
3982
|
};
|
|
3184
|
-
|
|
3983
|
+
|
|
3984
|
+
const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = '', openRouterApiKeyState = 'idle') => {
|
|
3185
3985
|
if (messages.length === 0) {
|
|
3186
3986
|
return getEmptyMessagesDom();
|
|
3187
3987
|
}
|
|
@@ -3189,10 +3989,10 @@ const getMessagesDom = (messages, openRouterApiKeyInput) => {
|
|
|
3189
3989
|
childCount: messages.length,
|
|
3190
3990
|
className: 'ChatMessages',
|
|
3191
3991
|
type: Div
|
|
3192
|
-
}, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput))];
|
|
3992
|
+
}, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState))];
|
|
3193
3993
|
};
|
|
3194
3994
|
|
|
3195
|
-
const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, 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) => {
|
|
3196
3996
|
const selectedSession = sessions.find(session => session.id === selectedSessionId);
|
|
3197
3997
|
const selectedSessionTitle = selectedSession?.title || chatTitle();
|
|
3198
3998
|
const messages = selectedSession ? selectedSession.messages : [];
|
|
@@ -3200,7 +4000,7 @@ const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue,
|
|
|
3200
4000
|
childCount: 3,
|
|
3201
4001
|
className: mergeClassNames(Viewlet, Chat),
|
|
3202
4002
|
type: Div
|
|
3203
|
-
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput), ...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)];
|
|
3204
4004
|
};
|
|
3205
4005
|
|
|
3206
4006
|
const getChatHeaderListModeDom = () => {
|
|
@@ -3269,12 +4069,12 @@ const getChatListDom = (sessions, selectedSessionId) => {
|
|
|
3269
4069
|
}, ...sessions.flatMap(getSessionDom)];
|
|
3270
4070
|
};
|
|
3271
4071
|
|
|
3272
|
-
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) => {
|
|
3273
4073
|
return [{
|
|
3274
4074
|
childCount: 3,
|
|
3275
4075
|
className: mergeClassNames(Viewlet, Chat),
|
|
3276
4076
|
type: Div
|
|
3277
|
-
}, ...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)];
|
|
3278
4078
|
};
|
|
3279
4079
|
|
|
3280
4080
|
const getChatModeUnsupportedVirtualDom = () => {
|
|
@@ -3284,12 +4084,12 @@ const getChatModeUnsupportedVirtualDom = () => {
|
|
|
3284
4084
|
}, text(unknownViewMode())];
|
|
3285
4085
|
};
|
|
3286
4086
|
|
|
3287
|
-
const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
|
|
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) => {
|
|
3288
4088
|
switch (viewMode) {
|
|
3289
4089
|
case 'detail':
|
|
3290
|
-
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
4090
|
+
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight);
|
|
3291
4091
|
case 'list':
|
|
3292
|
-
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);
|
|
3293
4093
|
default:
|
|
3294
4094
|
return getChatModeUnsupportedVirtualDom();
|
|
3295
4095
|
}
|
|
@@ -3297,10 +4097,16 @@ const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRoute
|
|
|
3297
4097
|
|
|
3298
4098
|
const renderItems = (oldState, newState) => {
|
|
3299
4099
|
const {
|
|
4100
|
+
composerFontFamily,
|
|
4101
|
+
composerFontSize,
|
|
4102
|
+
composerHeight,
|
|
4103
|
+
composerLineHeight,
|
|
3300
4104
|
composerValue,
|
|
3301
4105
|
initial,
|
|
3302
4106
|
models,
|
|
4107
|
+
openApiApiKeyInput,
|
|
3303
4108
|
openRouterApiKeyInput,
|
|
4109
|
+
openRouterApiKeyState,
|
|
3304
4110
|
selectedModelId,
|
|
3305
4111
|
selectedSessionId,
|
|
3306
4112
|
sessions,
|
|
@@ -3313,7 +4119,7 @@ const renderItems = (oldState, newState) => {
|
|
|
3313
4119
|
if (initial) {
|
|
3314
4120
|
return [SetDom2, uid, []];
|
|
3315
4121
|
}
|
|
3316
|
-
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
4122
|
+
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight);
|
|
3317
4123
|
return [SetDom2, uid, dom];
|
|
3318
4124
|
};
|
|
3319
4125
|
|
|
@@ -3424,6 +4230,7 @@ const reset = async state => {
|
|
|
3424
4230
|
await clearChatSessions();
|
|
3425
4231
|
return {
|
|
3426
4232
|
...state,
|
|
4233
|
+
composerHeight: getMinComposerHeightForState(state),
|
|
3427
4234
|
composerValue: '',
|
|
3428
4235
|
openRouterApiKey: '',
|
|
3429
4236
|
selectedSessionId: '',
|
|
@@ -3488,6 +4295,21 @@ const setChatList = state => {
|
|
|
3488
4295
|
};
|
|
3489
4296
|
};
|
|
3490
4297
|
|
|
4298
|
+
const defaultMockApiCommandId = 'ChatE2e.mockApi';
|
|
4299
|
+
const useMockApi = (state, value, mockApiCommandId = defaultMockApiCommandId) => {
|
|
4300
|
+
if (!value) {
|
|
4301
|
+
return {
|
|
4302
|
+
...state,
|
|
4303
|
+
useMockApi: false
|
|
4304
|
+
};
|
|
4305
|
+
}
|
|
4306
|
+
return {
|
|
4307
|
+
...state,
|
|
4308
|
+
mockApiCommandId,
|
|
4309
|
+
useMockApi: true
|
|
4310
|
+
};
|
|
4311
|
+
};
|
|
4312
|
+
|
|
3491
4313
|
const commandMap = {
|
|
3492
4314
|
'Chat.clearInput': wrapCommand(clearInput),
|
|
3493
4315
|
'Chat.create': create,
|
|
@@ -3521,7 +4343,8 @@ const commandMap = {
|
|
|
3521
4343
|
'Chat.saveState': wrapGetter(saveState),
|
|
3522
4344
|
'Chat.setChatList': wrapCommand(setChatList),
|
|
3523
4345
|
'Chat.setOpenRouterApiKey': wrapCommand(setOpenRouterApiKey),
|
|
3524
|
-
'Chat.terminate': terminate
|
|
4346
|
+
'Chat.terminate': terminate,
|
|
4347
|
+
'Chat.useMockApi': wrapCommand(useMockApi)
|
|
3525
4348
|
};
|
|
3526
4349
|
|
|
3527
4350
|
const listen = async () => {
|