@lvce-editor/chat-view 1.14.0 → 1.15.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 +658 -50
- 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
|
};
|
|
@@ -1224,6 +1228,9 @@ const composePlaceholder = () => {
|
|
|
1224
1228
|
const openRouterApiKeyPlaceholder = () => {
|
|
1225
1229
|
return i18nString('Enter OpenRouter API key');
|
|
1226
1230
|
};
|
|
1231
|
+
const openApiApiKeyPlaceholder = () => {
|
|
1232
|
+
return i18nString('Enter OpenAI API key');
|
|
1233
|
+
};
|
|
1227
1234
|
const sendMessage = () => {
|
|
1228
1235
|
return i18nString('Send message');
|
|
1229
1236
|
};
|
|
@@ -1233,6 +1240,9 @@ const save = () => {
|
|
|
1233
1240
|
const getOpenRouterApiKey = () => {
|
|
1234
1241
|
return i18nString('Get API Key');
|
|
1235
1242
|
};
|
|
1243
|
+
const getOpenApiApiKey = () => {
|
|
1244
|
+
return i18nString('Get API Key');
|
|
1245
|
+
};
|
|
1236
1246
|
const deleteChatSession$1 = () => {
|
|
1237
1247
|
return i18nString('Delete chat session');
|
|
1238
1248
|
};
|
|
@@ -1325,8 +1335,13 @@ const createDefaultState = () => {
|
|
|
1325
1335
|
inputSource: 'script',
|
|
1326
1336
|
lastSubmittedSessionId: '',
|
|
1327
1337
|
listItemHeight: 40,
|
|
1338
|
+
mockApiCommandId: '',
|
|
1328
1339
|
models: getDefaultModels(),
|
|
1329
1340
|
nextMessageId: 1,
|
|
1341
|
+
openApiApiBaseUrl: 'https://api.openai.com/v1',
|
|
1342
|
+
openApiApiKey: '',
|
|
1343
|
+
openApiApiKeyInput: '',
|
|
1344
|
+
openApiApiKeysSettingsUrl: 'https://platform.openai.com/api-keys',
|
|
1330
1345
|
openRouterApiBaseUrl: 'https://openrouter.ai/api/v1',
|
|
1331
1346
|
openRouterApiKey: '',
|
|
1332
1347
|
openRouterApiKeyInput: '',
|
|
@@ -1344,6 +1359,7 @@ const createDefaultState = () => {
|
|
|
1344
1359
|
tokensUsed: 0,
|
|
1345
1360
|
uid: 0,
|
|
1346
1361
|
usageOverviewEnabled: false,
|
|
1362
|
+
useMockApi: false,
|
|
1347
1363
|
viewMode: 'list',
|
|
1348
1364
|
warningCount: 0,
|
|
1349
1365
|
width: 0,
|
|
@@ -2084,14 +2100,44 @@ const deleteSession = async (state, id) => {
|
|
|
2084
2100
|
};
|
|
2085
2101
|
};
|
|
2086
2102
|
|
|
2103
|
+
const handleClickOpenApiApiKeySettings = async state => {
|
|
2104
|
+
await openExternal(state.openApiApiKeysSettingsUrl);
|
|
2105
|
+
return state;
|
|
2106
|
+
};
|
|
2107
|
+
|
|
2087
2108
|
const handleClickOpenRouterApiKeySettings = async state => {
|
|
2088
2109
|
await openExternal(state.openRouterApiKeysSettingsUrl);
|
|
2089
2110
|
return state;
|
|
2090
2111
|
};
|
|
2091
2112
|
|
|
2113
|
+
const openApiApiKeyRequiredMessage = 'OpenAI API key is not configured. Enter your OpenAI API key below and click Save.';
|
|
2114
|
+
const openApiRequestFailedMessage = 'OpenAI request failed.';
|
|
2092
2115
|
const openRouterApiKeyRequiredMessage = 'OpenRouter API key is not configured. Enter your OpenRouter API key below and click Save.';
|
|
2093
2116
|
const openRouterRequestFailedMessage = 'OpenRouter request failed. Possible reasons:';
|
|
2117
|
+
const openRouterTooManyRequestsMessage = 'OpenRouter rate limit reached (429). Please try again soon. Helpful tips:';
|
|
2094
2118
|
const openRouterRequestFailureReasons = ['ContentSecurityPolicyViolation: Check DevTools for details.', 'OpenRouter server offline: Check DevTools for details.', 'Check your internet connection.'];
|
|
2119
|
+
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
|
+
|
|
2121
|
+
const activateByEvent = (event, assetDir, platform) => {
|
|
2122
|
+
// @ts-ignore
|
|
2123
|
+
return activateByEvent$1(event, assetDir, platform);
|
|
2124
|
+
};
|
|
2125
|
+
|
|
2126
|
+
const executeProvider = async ({
|
|
2127
|
+
assetDir,
|
|
2128
|
+
event,
|
|
2129
|
+
method,
|
|
2130
|
+
noProviderFoundMessage,
|
|
2131
|
+
params,
|
|
2132
|
+
platform
|
|
2133
|
+
}) => {
|
|
2134
|
+
await activateByEvent(event, assetDir, platform);
|
|
2135
|
+
// @ts-ignore
|
|
2136
|
+
const result = invoke$1(method, ...params);
|
|
2137
|
+
return result;
|
|
2138
|
+
};
|
|
2139
|
+
|
|
2140
|
+
const CommandExecute = 'ExtensionHostCommand.executeCommand';
|
|
2095
2141
|
|
|
2096
2142
|
const delay = async ms => {
|
|
2097
2143
|
await new Promise(resolve => setTimeout(resolve, ms));
|
|
@@ -2102,10 +2148,8 @@ const getMockAiResponse = async userMessage => {
|
|
|
2102
2148
|
return `Mock AI response: I received "${userMessage}".`;
|
|
2103
2149
|
};
|
|
2104
2150
|
|
|
2105
|
-
const
|
|
2106
|
-
|
|
2107
|
-
const trimmedBaseUrl = (openRouterApiBaseUrl || defaultOpenRouterApiBaseUrl).replace(/\/+$/, '');
|
|
2108
|
-
return `${trimmedBaseUrl}/chat/completions`;
|
|
2151
|
+
const getOpenApiApiEndpoint = openApiApiBaseUrl => {
|
|
2152
|
+
return `${openApiApiBaseUrl}/chat/completions`;
|
|
2109
2153
|
};
|
|
2110
2154
|
|
|
2111
2155
|
const getTextContent = content => {
|
|
@@ -2129,15 +2173,179 @@ const getTextContent = content => {
|
|
|
2129
2173
|
return textParts.join('\n');
|
|
2130
2174
|
};
|
|
2131
2175
|
|
|
2132
|
-
const
|
|
2176
|
+
const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApiApiBaseUrl) => {
|
|
2177
|
+
let response;
|
|
2178
|
+
try {
|
|
2179
|
+
response = await fetch(getOpenApiApiEndpoint(openApiApiBaseUrl), {
|
|
2180
|
+
body: JSON.stringify({
|
|
2181
|
+
messages: messages.map(message => ({
|
|
2182
|
+
content: message.text,
|
|
2183
|
+
role: message.role
|
|
2184
|
+
})),
|
|
2185
|
+
model: modelId
|
|
2186
|
+
}),
|
|
2187
|
+
headers: {
|
|
2188
|
+
Authorization: `Bearer ${openApiApiKey}`,
|
|
2189
|
+
'Content-Type': 'application/json'
|
|
2190
|
+
},
|
|
2191
|
+
method: 'POST'
|
|
2192
|
+
});
|
|
2193
|
+
} catch {
|
|
2194
|
+
return {
|
|
2195
|
+
details: 'request-failed',
|
|
2196
|
+
type: 'error'
|
|
2197
|
+
};
|
|
2198
|
+
}
|
|
2199
|
+
if (!response.ok) {
|
|
2200
|
+
return {
|
|
2201
|
+
details: 'http-error',
|
|
2202
|
+
statusCode: response.status,
|
|
2203
|
+
type: 'error'
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
let parsed;
|
|
2207
|
+
try {
|
|
2208
|
+
parsed = await response.json();
|
|
2209
|
+
} catch {
|
|
2210
|
+
return {
|
|
2211
|
+
details: 'request-failed',
|
|
2212
|
+
type: 'error'
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2216
|
+
return {
|
|
2217
|
+
text: '',
|
|
2218
|
+
type: 'success'
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
const choices = Reflect.get(parsed, 'choices');
|
|
2222
|
+
if (!Array.isArray(choices)) {
|
|
2223
|
+
return {
|
|
2224
|
+
text: '',
|
|
2225
|
+
type: 'success'
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
const firstChoice = choices[0];
|
|
2229
|
+
if (!firstChoice || typeof firstChoice !== 'object') {
|
|
2230
|
+
return {
|
|
2231
|
+
text: '',
|
|
2232
|
+
type: 'success'
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
const message = Reflect.get(firstChoice, 'message');
|
|
2236
|
+
if (!message || typeof message !== 'object') {
|
|
2237
|
+
return {
|
|
2238
|
+
text: '',
|
|
2239
|
+
type: 'success'
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
const content = Reflect.get(message, 'content');
|
|
2243
|
+
return {
|
|
2244
|
+
text: getTextContent(content),
|
|
2245
|
+
type: 'success'
|
|
2246
|
+
};
|
|
2247
|
+
};
|
|
2248
|
+
|
|
2249
|
+
const getOpenApiModelId = selectedModelId => {
|
|
2250
|
+
const openApiPrefix = 'openapi/';
|
|
2251
|
+
const openAiPrefix = 'openai/';
|
|
2252
|
+
const normalizedModelId = selectedModelId.toLowerCase();
|
|
2253
|
+
if (normalizedModelId.startsWith(openApiPrefix)) {
|
|
2254
|
+
return selectedModelId.slice(openApiPrefix.length);
|
|
2255
|
+
}
|
|
2256
|
+
if (normalizedModelId.startsWith(openAiPrefix)) {
|
|
2257
|
+
return selectedModelId.slice(openAiPrefix.length);
|
|
2258
|
+
}
|
|
2259
|
+
return selectedModelId;
|
|
2260
|
+
};
|
|
2261
|
+
|
|
2262
|
+
const getOpenRouterApiEndpoint = openRouterApiBaseUrl => {
|
|
2263
|
+
const trimmedBaseUrl = openRouterApiBaseUrl.replace(/\/+$/, '');
|
|
2264
|
+
return `${trimmedBaseUrl}/chat/completions`;
|
|
2265
|
+
};
|
|
2266
|
+
|
|
2267
|
+
const getOpenRouterKeyEndpoint = openRouterApiBaseUrl => {
|
|
2268
|
+
const trimmedBaseUrl = openRouterApiBaseUrl.replace(/\/+$/, '');
|
|
2269
|
+
return `${trimmedBaseUrl}/auth/key`;
|
|
2270
|
+
};
|
|
2271
|
+
|
|
2272
|
+
const getOpenRouterRaw429Message = async response => {
|
|
2273
|
+
let parsed;
|
|
2274
|
+
try {
|
|
2275
|
+
parsed = await response.json();
|
|
2276
|
+
} catch {
|
|
2277
|
+
return undefined;
|
|
2278
|
+
}
|
|
2279
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2280
|
+
return undefined;
|
|
2281
|
+
}
|
|
2282
|
+
const error = Reflect.get(parsed, 'error');
|
|
2283
|
+
if (!error || typeof error !== 'object') {
|
|
2284
|
+
return undefined;
|
|
2285
|
+
}
|
|
2286
|
+
const metadata = Reflect.get(error, 'metadata');
|
|
2287
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
2288
|
+
return undefined;
|
|
2289
|
+
}
|
|
2290
|
+
const raw = Reflect.get(metadata, 'raw');
|
|
2291
|
+
if (typeof raw !== 'string' || !raw) {
|
|
2292
|
+
return undefined;
|
|
2293
|
+
}
|
|
2294
|
+
return raw;
|
|
2295
|
+
};
|
|
2296
|
+
const getOpenRouterLimitInfo = async (openRouterApiKey, openRouterApiBaseUrl) => {
|
|
2297
|
+
let response;
|
|
2298
|
+
try {
|
|
2299
|
+
response = await fetch(getOpenRouterKeyEndpoint(openRouterApiBaseUrl), {
|
|
2300
|
+
headers: {
|
|
2301
|
+
Authorization: `Bearer ${openRouterApiKey}`
|
|
2302
|
+
},
|
|
2303
|
+
method: 'GET'
|
|
2304
|
+
});
|
|
2305
|
+
} catch {
|
|
2306
|
+
return undefined;
|
|
2307
|
+
}
|
|
2308
|
+
if (!response.ok) {
|
|
2309
|
+
return undefined;
|
|
2310
|
+
}
|
|
2311
|
+
let parsed;
|
|
2312
|
+
try {
|
|
2313
|
+
parsed = await response.json();
|
|
2314
|
+
} catch {
|
|
2315
|
+
return undefined;
|
|
2316
|
+
}
|
|
2317
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2318
|
+
return undefined;
|
|
2319
|
+
}
|
|
2320
|
+
const data = Reflect.get(parsed, 'data');
|
|
2321
|
+
if (!data || typeof data !== 'object') {
|
|
2322
|
+
return undefined;
|
|
2323
|
+
}
|
|
2324
|
+
const limitRemaining = Reflect.get(data, 'limit_remaining');
|
|
2325
|
+
const limitReset = Reflect.get(data, 'limit_reset');
|
|
2326
|
+
const usage = Reflect.get(data, 'usage');
|
|
2327
|
+
const usageDaily = Reflect.get(data, 'usage_daily');
|
|
2328
|
+
const normalizedLimitInfo = {
|
|
2329
|
+
limitRemaining: typeof limitRemaining === 'number' || limitRemaining === null ? limitRemaining : undefined,
|
|
2330
|
+
limitReset: typeof limitReset === 'string' || limitReset === null ? limitReset : undefined,
|
|
2331
|
+
usage: typeof usage === 'number' ? usage : undefined,
|
|
2332
|
+
usageDaily: typeof usageDaily === 'number' ? usageDaily : undefined
|
|
2333
|
+
};
|
|
2334
|
+
const hasLimitInfo = normalizedLimitInfo.limitRemaining !== undefined || normalizedLimitInfo.limitReset !== undefined || normalizedLimitInfo.usage !== undefined || normalizedLimitInfo.usageDaily !== undefined;
|
|
2335
|
+
if (!hasLimitInfo) {
|
|
2336
|
+
return undefined;
|
|
2337
|
+
}
|
|
2338
|
+
return normalizedLimitInfo;
|
|
2339
|
+
};
|
|
2340
|
+
const getOpenRouterAssistantText = async (messages, modelId, openRouterApiKey, openRouterApiBaseUrl) => {
|
|
2133
2341
|
let response;
|
|
2134
2342
|
try {
|
|
2135
2343
|
response = await fetch(getOpenRouterApiEndpoint(openRouterApiBaseUrl), {
|
|
2136
2344
|
body: JSON.stringify({
|
|
2137
|
-
messages:
|
|
2138
|
-
content:
|
|
2139
|
-
role:
|
|
2140
|
-
}
|
|
2345
|
+
messages: messages.map(message => ({
|
|
2346
|
+
content: message.text,
|
|
2347
|
+
role: message.role
|
|
2348
|
+
})),
|
|
2141
2349
|
model: modelId
|
|
2142
2350
|
}),
|
|
2143
2351
|
headers: {
|
|
@@ -2147,29 +2355,74 @@ const getOpenRouterAssistantText = async (userText, modelId, openRouterApiKey, o
|
|
|
2147
2355
|
method: 'POST'
|
|
2148
2356
|
});
|
|
2149
2357
|
} catch {
|
|
2150
|
-
|
|
2358
|
+
return {
|
|
2359
|
+
details: 'request-failed',
|
|
2360
|
+
type: 'error'
|
|
2361
|
+
};
|
|
2151
2362
|
}
|
|
2152
2363
|
if (!response.ok) {
|
|
2153
|
-
|
|
2364
|
+
if (response.status === 429) {
|
|
2365
|
+
const retryAfter = response.headers?.get?.('retry-after') ?? null;
|
|
2366
|
+
const rawMessage = await getOpenRouterRaw429Message(response);
|
|
2367
|
+
const limitInfo = await getOpenRouterLimitInfo(openRouterApiKey, openRouterApiBaseUrl);
|
|
2368
|
+
return {
|
|
2369
|
+
details: 'too-many-requests',
|
|
2370
|
+
limitInfo: limitInfo || retryAfter ? {
|
|
2371
|
+
...limitInfo,
|
|
2372
|
+
retryAfter
|
|
2373
|
+
} : undefined,
|
|
2374
|
+
rawMessage,
|
|
2375
|
+
statusCode: 429,
|
|
2376
|
+
type: 'error'
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
return {
|
|
2380
|
+
details: 'http-error',
|
|
2381
|
+
statusCode: response.status,
|
|
2382
|
+
type: 'error'
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
let parsed;
|
|
2386
|
+
try {
|
|
2387
|
+
parsed = await response.json();
|
|
2388
|
+
} catch {
|
|
2389
|
+
return {
|
|
2390
|
+
details: 'request-failed',
|
|
2391
|
+
type: 'error'
|
|
2392
|
+
};
|
|
2154
2393
|
}
|
|
2155
|
-
const parsed = await response.json();
|
|
2156
2394
|
if (!parsed || typeof parsed !== 'object') {
|
|
2157
|
-
return
|
|
2395
|
+
return {
|
|
2396
|
+
text: '',
|
|
2397
|
+
type: 'success'
|
|
2398
|
+
};
|
|
2158
2399
|
}
|
|
2159
2400
|
const choices = Reflect.get(parsed, 'choices');
|
|
2160
2401
|
if (!Array.isArray(choices)) {
|
|
2161
|
-
return
|
|
2402
|
+
return {
|
|
2403
|
+
text: '',
|
|
2404
|
+
type: 'success'
|
|
2405
|
+
};
|
|
2162
2406
|
}
|
|
2163
2407
|
const firstChoice = choices[0];
|
|
2164
2408
|
if (!firstChoice || typeof firstChoice !== 'object') {
|
|
2165
|
-
return
|
|
2409
|
+
return {
|
|
2410
|
+
text: '',
|
|
2411
|
+
type: 'success'
|
|
2412
|
+
};
|
|
2166
2413
|
}
|
|
2167
2414
|
const message = Reflect.get(firstChoice, 'message');
|
|
2168
2415
|
if (!message || typeof message !== 'object') {
|
|
2169
|
-
return
|
|
2416
|
+
return {
|
|
2417
|
+
text: '',
|
|
2418
|
+
type: 'success'
|
|
2419
|
+
};
|
|
2170
2420
|
}
|
|
2171
2421
|
const content = Reflect.get(message, 'content');
|
|
2172
|
-
return
|
|
2422
|
+
return {
|
|
2423
|
+
text: getTextContent(content),
|
|
2424
|
+
type: 'success'
|
|
2425
|
+
};
|
|
2173
2426
|
};
|
|
2174
2427
|
|
|
2175
2428
|
/* eslint-disable @cspell/spellchecker */
|
|
@@ -2181,6 +2434,16 @@ const getOpenRouterModelId = selectedModelId => {
|
|
|
2181
2434
|
return selectedModelId;
|
|
2182
2435
|
};
|
|
2183
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
|
+
|
|
2184
2447
|
/* eslint-disable @cspell/spellchecker */
|
|
2185
2448
|
|
|
2186
2449
|
const isOpenRouterModel = (selectedModelId, models) => {
|
|
@@ -2192,25 +2455,200 @@ const isOpenRouterModel = (selectedModelId, models) => {
|
|
|
2192
2455
|
return selectedModelId.toLowerCase().startsWith('openrouter/');
|
|
2193
2456
|
};
|
|
2194
2457
|
|
|
2195
|
-
const
|
|
2458
|
+
const getOpenRouterTooManyRequestsMessage = errorResult => {
|
|
2459
|
+
const details = [];
|
|
2460
|
+
if (errorResult.rawMessage) {
|
|
2461
|
+
details.push(errorResult.rawMessage);
|
|
2462
|
+
}
|
|
2463
|
+
const {
|
|
2464
|
+
limitInfo
|
|
2465
|
+
} = errorResult;
|
|
2466
|
+
if (limitInfo) {
|
|
2467
|
+
if (limitInfo.retryAfter) {
|
|
2468
|
+
details.push(`Retry after: ${limitInfo.retryAfter}.`);
|
|
2469
|
+
}
|
|
2470
|
+
if (limitInfo.limitReset) {
|
|
2471
|
+
details.push(`Limit resets: ${limitInfo.limitReset}.`);
|
|
2472
|
+
}
|
|
2473
|
+
if (limitInfo.limitRemaining === null) {
|
|
2474
|
+
details.push('Credits remaining: unlimited.');
|
|
2475
|
+
} else if (typeof limitInfo.limitRemaining === 'number') {
|
|
2476
|
+
details.push(`Credits remaining: ${limitInfo.limitRemaining}.`);
|
|
2477
|
+
}
|
|
2478
|
+
if (typeof limitInfo.usageDaily === 'number') {
|
|
2479
|
+
details.push(`Credits used today (UTC): ${limitInfo.usageDaily}.`);
|
|
2480
|
+
}
|
|
2481
|
+
if (typeof limitInfo.usage === 'number') {
|
|
2482
|
+
details.push(`Credits used (all time): ${limitInfo.usage}.`);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
if (details.length === 0) {
|
|
2486
|
+
return openRouterTooManyRequestsMessage;
|
|
2487
|
+
}
|
|
2488
|
+
return `${openRouterTooManyRequestsMessage} ${details.join(' ')}`;
|
|
2489
|
+
};
|
|
2490
|
+
const getOpenRouterErrorMessage = errorResult => {
|
|
2491
|
+
switch (errorResult.details) {
|
|
2492
|
+
case 'http-error':
|
|
2493
|
+
case 'request-failed':
|
|
2494
|
+
return openRouterRequestFailedMessage;
|
|
2495
|
+
case 'too-many-requests':
|
|
2496
|
+
return getOpenRouterTooManyRequestsMessage(errorResult);
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
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;
|
|
2509
|
+
}
|
|
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;
|
|
2524
|
+
};
|
|
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
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
return {
|
|
2574
|
+
details: 'request-failed',
|
|
2575
|
+
type: 'error'
|
|
2576
|
+
};
|
|
2577
|
+
};
|
|
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
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
};
|
|
2607
|
+
const getAiResponse = async (userText, messages, nextMessageId, selectedModelId, models, openApiApiKey, openApiApiBaseUrl, openRouterApiKey, openRouterApiBaseUrl, useMockApi, mockApiCommandId, assetDir, platform) => {
|
|
2196
2608
|
let text = '';
|
|
2609
|
+
const usesOpenApiModel = isOpenApiModel(selectedModelId, models);
|
|
2197
2610
|
const usesOpenRouterModel = isOpenRouterModel(selectedModelId, models);
|
|
2198
|
-
if (
|
|
2199
|
-
if (
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2611
|
+
if (usesOpenApiModel) {
|
|
2612
|
+
if (openApiApiKey) {
|
|
2613
|
+
const result = await getOpenApiAssistantText(messages, getOpenApiModelId(selectedModelId), openApiApiKey, openApiApiBaseUrl);
|
|
2614
|
+
if (result.type === 'success') {
|
|
2615
|
+
const {
|
|
2616
|
+
text: assistantText
|
|
2617
|
+
} = result;
|
|
2618
|
+
text = assistantText;
|
|
2619
|
+
} else {
|
|
2620
|
+
text = getOpenApiErrorMessage(result);
|
|
2621
|
+
}
|
|
2622
|
+
} else {
|
|
2623
|
+
text = openApiApiKeyRequiredMessage;
|
|
2624
|
+
}
|
|
2625
|
+
} else if (usesOpenRouterModel) {
|
|
2626
|
+
const modelId = getOpenRouterModelId(selectedModelId);
|
|
2627
|
+
if (useMockApi) {
|
|
2628
|
+
const result = await getMockOpenRouterAssistantText(messages, modelId, openRouterApiBaseUrl, openRouterApiKey, mockApiCommandId, assetDir, platform);
|
|
2629
|
+
if (result.type === 'success') {
|
|
2630
|
+
const {
|
|
2631
|
+
text: assistantText
|
|
2632
|
+
} = result;
|
|
2633
|
+
text = assistantText;
|
|
2634
|
+
} else {
|
|
2635
|
+
text = getOpenRouterErrorMessage(result);
|
|
2636
|
+
}
|
|
2637
|
+
} else if (openRouterApiKey) {
|
|
2638
|
+
const result = await getOpenRouterAssistantText(messages, modelId, openRouterApiKey, openRouterApiBaseUrl);
|
|
2639
|
+
if (result.type === 'success') {
|
|
2640
|
+
const {
|
|
2641
|
+
text: assistantText
|
|
2642
|
+
} = result;
|
|
2643
|
+
text = assistantText;
|
|
2644
|
+
} else {
|
|
2645
|
+
text = getOpenRouterErrorMessage(result);
|
|
2208
2646
|
}
|
|
2209
2647
|
} else {
|
|
2210
2648
|
text = openRouterApiKeyRequiredMessage;
|
|
2211
2649
|
}
|
|
2212
2650
|
}
|
|
2213
|
-
if (!text && !usesOpenRouterModel) {
|
|
2651
|
+
if (!text && !usesOpenApiModel && !usesOpenRouterModel) {
|
|
2214
2652
|
text = await getMockAiResponse(userText);
|
|
2215
2653
|
}
|
|
2216
2654
|
const assistantTime = new Date().toLocaleTimeString([], {
|
|
@@ -2232,6 +2670,61 @@ const update = async settings => {
|
|
|
2232
2670
|
await invoke('Preferences.update', settings);
|
|
2233
2671
|
};
|
|
2234
2672
|
|
|
2673
|
+
const setOpenApiApiKey = async (state, openApiApiKey, persist = true) => {
|
|
2674
|
+
if (persist) {
|
|
2675
|
+
await update({
|
|
2676
|
+
'secrets.openApiKey': openApiApiKey
|
|
2677
|
+
});
|
|
2678
|
+
}
|
|
2679
|
+
return {
|
|
2680
|
+
...state,
|
|
2681
|
+
openApiApiKey,
|
|
2682
|
+
openApiApiKeyInput: openApiApiKey
|
|
2683
|
+
};
|
|
2684
|
+
};
|
|
2685
|
+
|
|
2686
|
+
const handleClickSaveOpenApiApiKey = async state => {
|
|
2687
|
+
const {
|
|
2688
|
+
openApiApiKeyInput
|
|
2689
|
+
} = state;
|
|
2690
|
+
const openApiApiKey = openApiApiKeyInput.trim();
|
|
2691
|
+
if (!openApiApiKey) {
|
|
2692
|
+
return state;
|
|
2693
|
+
}
|
|
2694
|
+
const updatedState = await setOpenApiApiKey(state, openApiApiKey);
|
|
2695
|
+
const session = updatedState.sessions.find(item => item.id === updatedState.selectedSessionId);
|
|
2696
|
+
if (!session) {
|
|
2697
|
+
return updatedState;
|
|
2698
|
+
}
|
|
2699
|
+
const lastMessage = session.messages.at(-1);
|
|
2700
|
+
const shouldRetryOpenApi = lastMessage?.role === 'assistant' && lastMessage.text === openApiApiKeyRequiredMessage;
|
|
2701
|
+
if (!shouldRetryOpenApi) {
|
|
2702
|
+
return updatedState;
|
|
2703
|
+
}
|
|
2704
|
+
const previousUserMessage = session.messages.toReversed().find(item => item.role === 'user');
|
|
2705
|
+
if (!previousUserMessage) {
|
|
2706
|
+
return updatedState;
|
|
2707
|
+
}
|
|
2708
|
+
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);
|
|
2710
|
+
const updatedSession = {
|
|
2711
|
+
...session,
|
|
2712
|
+
messages: [...session.messages.slice(0, -1), assistantMessage]
|
|
2713
|
+
};
|
|
2714
|
+
await saveChatSession(updatedSession);
|
|
2715
|
+
const updatedSessions = updatedState.sessions.map(item => {
|
|
2716
|
+
if (item.id !== updatedState.selectedSessionId) {
|
|
2717
|
+
return item;
|
|
2718
|
+
}
|
|
2719
|
+
return updatedSession;
|
|
2720
|
+
});
|
|
2721
|
+
return {
|
|
2722
|
+
...updatedState,
|
|
2723
|
+
nextMessageId: updatedState.nextMessageId + 1,
|
|
2724
|
+
sessions: updatedSessions
|
|
2725
|
+
};
|
|
2726
|
+
};
|
|
2727
|
+
|
|
2235
2728
|
const setOpenRouterApiKey = async (state, openRouterApiKey, persist = true) => {
|
|
2236
2729
|
if (persist) {
|
|
2237
2730
|
await update({
|
|
@@ -2267,7 +2760,10 @@ const handleClickSaveOpenRouterApiKey = async state => {
|
|
|
2267
2760
|
if (!previousUserMessage) {
|
|
2268
2761
|
return updatedState;
|
|
2269
2762
|
}
|
|
2270
|
-
const
|
|
2763
|
+
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);
|
|
2271
2767
|
const updatedSession = {
|
|
2272
2768
|
...session,
|
|
2273
2769
|
messages: [...session.messages.slice(0, -1), assistantMessage]
|
|
@@ -2296,14 +2792,20 @@ const focusInput = state => {
|
|
|
2296
2792
|
|
|
2297
2793
|
const handleSubmit = async state => {
|
|
2298
2794
|
const {
|
|
2795
|
+
assetDir,
|
|
2299
2796
|
composerValue,
|
|
2797
|
+
mockApiCommandId,
|
|
2300
2798
|
models,
|
|
2301
2799
|
nextMessageId,
|
|
2800
|
+
openApiApiBaseUrl,
|
|
2801
|
+
openApiApiKey,
|
|
2302
2802
|
openRouterApiBaseUrl,
|
|
2303
2803
|
openRouterApiKey,
|
|
2804
|
+
platform,
|
|
2304
2805
|
selectedModelId,
|
|
2305
2806
|
selectedSessionId,
|
|
2306
2807
|
sessions,
|
|
2808
|
+
useMockApi,
|
|
2307
2809
|
viewMode
|
|
2308
2810
|
} = state;
|
|
2309
2811
|
const userText = composerValue.trim();
|
|
@@ -2377,7 +2879,9 @@ const handleSubmit = async state => {
|
|
|
2377
2879
|
set(state.uid, state, optimisticState);
|
|
2378
2880
|
// @ts-ignore
|
|
2379
2881
|
await invoke('Chat.rerender');
|
|
2380
|
-
const
|
|
2882
|
+
const selectedOptimisticSession = optimisticState.sessions.find(session => session.id === optimisticState.selectedSessionId);
|
|
2883
|
+
const messages = selectedOptimisticSession?.messages ?? [];
|
|
2884
|
+
const assistantMessage = await getAiResponse(userText, messages, optimisticState.nextMessageId, selectedModelId, models, openApiApiKey, openApiApiBaseUrl, openRouterApiKey, openRouterApiBaseUrl, useMockApi, mockApiCommandId, assetDir, platform);
|
|
2381
2885
|
const updatedSessions = optimisticState.sessions.map(session => {
|
|
2382
2886
|
if (session.id !== optimisticState.selectedSessionId) {
|
|
2383
2887
|
return session;
|
|
@@ -2438,6 +2942,10 @@ const getRenameIdFromInputName = name => {
|
|
|
2438
2942
|
return name.slice(RenamePrefix.length);
|
|
2439
2943
|
};
|
|
2440
2944
|
|
|
2945
|
+
const OpenApiApiKeyInput = 'open-api-api-key';
|
|
2946
|
+
const SaveOpenApiApiKey = 'save-openapi-api-key';
|
|
2947
|
+
const OpenOpenApiApiKeySettings = 'open-openapi-api-key-settings';
|
|
2948
|
+
|
|
2441
2949
|
/* eslint-disable @cspell/spellchecker */
|
|
2442
2950
|
const OpenRouterApiKeyInput = 'open-router-api-key';
|
|
2443
2951
|
const SaveOpenRouterApiKey = 'save-openrouter-api-key';
|
|
@@ -2524,9 +3032,15 @@ const handleClick = async (state, name, id = '') => {
|
|
|
2524
3032
|
if (name === SaveOpenRouterApiKey) {
|
|
2525
3033
|
return handleClickSaveOpenRouterApiKey(state);
|
|
2526
3034
|
}
|
|
3035
|
+
if (name === SaveOpenApiApiKey) {
|
|
3036
|
+
return handleClickSaveOpenApiApiKey(state);
|
|
3037
|
+
}
|
|
2527
3038
|
if (name === OpenOpenRouterApiKeySettings) {
|
|
2528
3039
|
return handleClickOpenRouterApiKeySettings(state);
|
|
2529
3040
|
}
|
|
3041
|
+
if (name === OpenOpenApiApiKeySettings) {
|
|
3042
|
+
return handleClickOpenApiApiKeySettings(state);
|
|
3043
|
+
}
|
|
2530
3044
|
return state;
|
|
2531
3045
|
};
|
|
2532
3046
|
|
|
@@ -2557,6 +3071,12 @@ const handleClickSettings = async () => {
|
|
|
2557
3071
|
};
|
|
2558
3072
|
|
|
2559
3073
|
const handleInput = async (state, name, value, inputSource = 'user') => {
|
|
3074
|
+
if (name === OpenApiApiKeyInput) {
|
|
3075
|
+
return {
|
|
3076
|
+
...state,
|
|
3077
|
+
openApiApiKeyInput: value
|
|
3078
|
+
};
|
|
3079
|
+
}
|
|
2560
3080
|
if (name === OpenRouterApiKeyInput) {
|
|
2561
3081
|
return {
|
|
2562
3082
|
...state,
|
|
@@ -2776,6 +3296,23 @@ const loadSelectedSessionMessages = async (sessions, selectedSessionId) => {
|
|
|
2776
3296
|
const loadContent = async (state, savedState) => {
|
|
2777
3297
|
const savedSelectedModelId = getSavedSelectedModelId(savedState);
|
|
2778
3298
|
const savedViewMode = getSavedViewMode(savedState);
|
|
3299
|
+
let openApiApiKey = '';
|
|
3300
|
+
try {
|
|
3301
|
+
const savedOpenApiKey = await get('secrets.openApiKey');
|
|
3302
|
+
if (typeof savedOpenApiKey === 'string' && savedOpenApiKey) {
|
|
3303
|
+
openApiApiKey = savedOpenApiKey;
|
|
3304
|
+
} else {
|
|
3305
|
+
const legacySavedOpenApiApiKey = await get('secrets.openApiApiKey');
|
|
3306
|
+
if (typeof legacySavedOpenApiApiKey === 'string' && legacySavedOpenApiApiKey) {
|
|
3307
|
+
openApiApiKey = legacySavedOpenApiApiKey;
|
|
3308
|
+
} else {
|
|
3309
|
+
const legacySavedOpenAiApiKey = await get('secrets.openAiApiKey');
|
|
3310
|
+
openApiApiKey = typeof legacySavedOpenAiApiKey === 'string' ? legacySavedOpenAiApiKey : '';
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
} catch {
|
|
3314
|
+
openApiApiKey = '';
|
|
3315
|
+
}
|
|
2779
3316
|
let openRouterApiKey = '';
|
|
2780
3317
|
try {
|
|
2781
3318
|
const savedOpenRouterApiKey = await get('secrets.openRouterApiKey');
|
|
@@ -2808,6 +3345,8 @@ const loadContent = async (state, savedState) => {
|
|
|
2808
3345
|
return {
|
|
2809
3346
|
...state,
|
|
2810
3347
|
initial: false,
|
|
3348
|
+
openApiApiKey,
|
|
3349
|
+
openApiApiKeyInput: openApiApiKey,
|
|
2811
3350
|
openRouterApiKey,
|
|
2812
3351
|
openRouterApiKeyInput: openRouterApiKey,
|
|
2813
3352
|
selectedModelId,
|
|
@@ -2906,6 +3445,8 @@ const ChatListItemLabel = 'ChatListItemLabel';
|
|
|
2906
3445
|
const Markdown = 'Markdown';
|
|
2907
3446
|
const Message = 'Message';
|
|
2908
3447
|
const ChatMessageContent = 'ChatMessageContent';
|
|
3448
|
+
const ChatOrderedList = 'ChatOrderedList';
|
|
3449
|
+
const ChatOrderedListItem = 'ChatOrderedListItem';
|
|
2909
3450
|
const MessageUser = 'MessageUser';
|
|
2910
3451
|
const MessageAssistant = 'MessageAssistant';
|
|
2911
3452
|
const MultilineInputBox = 'MultilineInputBox';
|
|
@@ -2935,8 +3476,12 @@ const getModelLabel = model => {
|
|
|
2935
3476
|
if (model.provider === 'openRouter') {
|
|
2936
3477
|
return `${model.name} (OpenRouter)`;
|
|
2937
3478
|
}
|
|
3479
|
+
if (model.provider === 'openApi' || model.provider === 'openAI' || model.provider === 'openai') {
|
|
3480
|
+
return `${model.name} (OpenAI)`;
|
|
3481
|
+
}
|
|
2938
3482
|
return model.name;
|
|
2939
3483
|
};
|
|
3484
|
+
|
|
2940
3485
|
const getModelOptionDOm = (model, selectedModelId) => {
|
|
2941
3486
|
return [{
|
|
2942
3487
|
childCount: 1,
|
|
@@ -3111,18 +3656,25 @@ const getChatHeaderDomDetailMode = selectedSessionTitle => {
|
|
|
3111
3656
|
}, text(selectedSessionTitle), ...getChatHeaderActionsDom()];
|
|
3112
3657
|
};
|
|
3113
3658
|
|
|
3114
|
-
const
|
|
3659
|
+
const getMissingApiKeyDom = ({
|
|
3660
|
+
getApiKeyText,
|
|
3661
|
+
inputName,
|
|
3662
|
+
inputValue,
|
|
3663
|
+
openSettingsButtonName,
|
|
3664
|
+
placeholder,
|
|
3665
|
+
saveButtonName
|
|
3666
|
+
}) => {
|
|
3115
3667
|
return [{
|
|
3116
3668
|
childCount: 2,
|
|
3117
3669
|
type: Div
|
|
3118
3670
|
}, {
|
|
3119
3671
|
childCount: 0,
|
|
3120
3672
|
className: InputBox,
|
|
3121
|
-
name:
|
|
3673
|
+
name: inputName,
|
|
3122
3674
|
onInput: HandleInput,
|
|
3123
|
-
placeholder
|
|
3675
|
+
placeholder,
|
|
3124
3676
|
type: Input,
|
|
3125
|
-
value:
|
|
3677
|
+
value: inputValue
|
|
3126
3678
|
}, {
|
|
3127
3679
|
childCount: 2,
|
|
3128
3680
|
className: Actions,
|
|
@@ -3130,35 +3682,73 @@ const getMissingOpenRouterApiKeyDom = openRouterApiKeyInput => {
|
|
|
3130
3682
|
}, {
|
|
3131
3683
|
childCount: 1,
|
|
3132
3684
|
className: mergeClassNames(Button, ButtonPrimary),
|
|
3133
|
-
name:
|
|
3685
|
+
name: saveButtonName,
|
|
3134
3686
|
onClick: HandleClick,
|
|
3135
3687
|
type: Button$1
|
|
3136
3688
|
}, text(save()), {
|
|
3137
3689
|
childCount: 1,
|
|
3138
3690
|
className: mergeClassNames(Button, ButtonSecondary),
|
|
3139
|
-
name:
|
|
3691
|
+
name: openSettingsButtonName,
|
|
3140
3692
|
onClick: HandleClick,
|
|
3141
3693
|
type: Button$1
|
|
3142
|
-
}, text(
|
|
3694
|
+
}, text(getApiKeyText)];
|
|
3695
|
+
};
|
|
3696
|
+
|
|
3697
|
+
const getMissingOpenApiApiKeyDom = openApiApiKeyInput => {
|
|
3698
|
+
return getMissingApiKeyDom({
|
|
3699
|
+
getApiKeyText: getOpenApiApiKey(),
|
|
3700
|
+
inputName: OpenApiApiKeyInput,
|
|
3701
|
+
inputValue: openApiApiKeyInput,
|
|
3702
|
+
openSettingsButtonName: OpenOpenApiApiKeySettings,
|
|
3703
|
+
placeholder: openApiApiKeyPlaceholder(),
|
|
3704
|
+
saveButtonName: SaveOpenApiApiKey
|
|
3705
|
+
});
|
|
3706
|
+
};
|
|
3707
|
+
|
|
3708
|
+
const getMissingOpenRouterApiKeyDom = openRouterApiKeyInput => {
|
|
3709
|
+
return getMissingApiKeyDom({
|
|
3710
|
+
getApiKeyText: getOpenRouterApiKey(),
|
|
3711
|
+
inputName: OpenRouterApiKeyInput,
|
|
3712
|
+
inputValue: openRouterApiKeyInput,
|
|
3713
|
+
openSettingsButtonName: OpenOpenRouterApiKeySettings,
|
|
3714
|
+
placeholder: openRouterApiKeyPlaceholder(),
|
|
3715
|
+
saveButtonName: SaveOpenRouterApiKey
|
|
3716
|
+
});
|
|
3143
3717
|
};
|
|
3144
3718
|
|
|
3145
3719
|
const getOpenRouterRequestFailedDom = () => {
|
|
3146
3720
|
return [{
|
|
3147
3721
|
childCount: openRouterRequestFailureReasons.length,
|
|
3148
|
-
className:
|
|
3722
|
+
className: ChatOrderedList,
|
|
3149
3723
|
type: Ol
|
|
3150
3724
|
}, ...openRouterRequestFailureReasons.flatMap(reason => {
|
|
3151
3725
|
return [{
|
|
3152
3726
|
childCount: 1,
|
|
3727
|
+
className: ChatOrderedListItem,
|
|
3153
3728
|
type: Li
|
|
3154
3729
|
}, text(reason)];
|
|
3155
3730
|
})];
|
|
3156
3731
|
};
|
|
3157
|
-
const
|
|
3732
|
+
const getOpenRouterTooManyRequestsDom = () => {
|
|
3733
|
+
return [{
|
|
3734
|
+
childCount: openRouterTooManyRequestsReasons.length,
|
|
3735
|
+
className: ChatOrderedList,
|
|
3736
|
+
type: Ol
|
|
3737
|
+
}, ...openRouterTooManyRequestsReasons.flatMap(reason => {
|
|
3738
|
+
return [{
|
|
3739
|
+
childCount: 1,
|
|
3740
|
+
className: ChatOrderedListItem,
|
|
3741
|
+
type: Li
|
|
3742
|
+
}, text(reason)];
|
|
3743
|
+
})];
|
|
3744
|
+
};
|
|
3745
|
+
const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput = '') => {
|
|
3158
3746
|
const roleClassName = message.role === 'user' ? MessageUser : MessageAssistant;
|
|
3747
|
+
const isOpenApiApiKeyMissingMessage = message.role === 'assistant' && message.text === openApiApiKeyRequiredMessage;
|
|
3159
3748
|
const isOpenRouterApiKeyMissingMessage = message.role === 'assistant' && message.text === openRouterApiKeyRequiredMessage;
|
|
3160
3749
|
const isOpenRouterRequestFailedMessage = message.role === 'assistant' && message.text === openRouterRequestFailedMessage;
|
|
3161
|
-
const
|
|
3750
|
+
const isOpenRouterTooManyRequestsMessage = message.role === 'assistant' && message.text.startsWith(openRouterTooManyRequestsMessage);
|
|
3751
|
+
const extraChildCount = isOpenApiApiKeyMissingMessage || isOpenRouterApiKeyMissingMessage || isOpenRouterRequestFailedMessage || isOpenRouterTooManyRequestsMessage ? 2 : 1;
|
|
3162
3752
|
return [{
|
|
3163
3753
|
childCount: 1,
|
|
3164
3754
|
className: mergeClassNames(Message, roleClassName),
|
|
@@ -3171,7 +3761,7 @@ const getChatMessageDom = (message, openRouterApiKeyInput) => {
|
|
|
3171
3761
|
childCount: 1,
|
|
3172
3762
|
className: Markdown,
|
|
3173
3763
|
type: P
|
|
3174
|
-
}, text(message.text), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : [])];
|
|
3764
|
+
}, text(message.text), ...(isOpenApiApiKeyMissingMessage ? getMissingOpenApiApiKeyDom(openApiApiKeyInput) : []), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : []), ...(isOpenRouterTooManyRequestsMessage ? getOpenRouterTooManyRequestsDom() : [])];
|
|
3175
3765
|
};
|
|
3176
3766
|
|
|
3177
3767
|
const getEmptyMessagesDom = () => {
|
|
@@ -3181,7 +3771,8 @@ const getEmptyMessagesDom = () => {
|
|
|
3181
3771
|
type: Div
|
|
3182
3772
|
}, text(startConversation())];
|
|
3183
3773
|
};
|
|
3184
|
-
|
|
3774
|
+
|
|
3775
|
+
const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = '') => {
|
|
3185
3776
|
if (messages.length === 0) {
|
|
3186
3777
|
return getEmptyMessagesDom();
|
|
3187
3778
|
}
|
|
@@ -3189,10 +3780,10 @@ const getMessagesDom = (messages, openRouterApiKeyInput) => {
|
|
|
3189
3780
|
childCount: messages.length,
|
|
3190
3781
|
className: 'ChatMessages',
|
|
3191
3782
|
type: Div
|
|
3192
|
-
}, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput))];
|
|
3783
|
+
}, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput, openApiApiKeyInput))];
|
|
3193
3784
|
};
|
|
3194
3785
|
|
|
3195
|
-
const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
|
|
3786
|
+
const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
|
|
3196
3787
|
const selectedSession = sessions.find(session => session.id === selectedSessionId);
|
|
3197
3788
|
const selectedSessionTitle = selectedSession?.title || chatTitle();
|
|
3198
3789
|
const messages = selectedSession ? selectedSession.messages : [];
|
|
@@ -3200,7 +3791,7 @@ const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue,
|
|
|
3200
3791
|
childCount: 3,
|
|
3201
3792
|
className: mergeClassNames(Viewlet, Chat),
|
|
3202
3793
|
type: Div
|
|
3203
|
-
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax)];
|
|
3794
|
+
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput, openApiApiKeyInput), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax)];
|
|
3204
3795
|
};
|
|
3205
3796
|
|
|
3206
3797
|
const getChatHeaderListModeDom = () => {
|
|
@@ -3284,10 +3875,10 @@ const getChatModeUnsupportedVirtualDom = () => {
|
|
|
3284
3875
|
}, text(unknownViewMode())];
|
|
3285
3876
|
};
|
|
3286
3877
|
|
|
3287
|
-
const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
|
|
3878
|
+
const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput = '') => {
|
|
3288
3879
|
switch (viewMode) {
|
|
3289
3880
|
case 'detail':
|
|
3290
|
-
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
3881
|
+
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
3291
3882
|
case 'list':
|
|
3292
3883
|
return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
3293
3884
|
default:
|
|
@@ -3300,6 +3891,7 @@ const renderItems = (oldState, newState) => {
|
|
|
3300
3891
|
composerValue,
|
|
3301
3892
|
initial,
|
|
3302
3893
|
models,
|
|
3894
|
+
openApiApiKeyInput,
|
|
3303
3895
|
openRouterApiKeyInput,
|
|
3304
3896
|
selectedModelId,
|
|
3305
3897
|
selectedSessionId,
|
|
@@ -3313,7 +3905,7 @@ const renderItems = (oldState, newState) => {
|
|
|
3313
3905
|
if (initial) {
|
|
3314
3906
|
return [SetDom2, uid, []];
|
|
3315
3907
|
}
|
|
3316
|
-
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
3908
|
+
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput);
|
|
3317
3909
|
return [SetDom2, uid, dom];
|
|
3318
3910
|
};
|
|
3319
3911
|
|
|
@@ -3488,6 +4080,21 @@ const setChatList = state => {
|
|
|
3488
4080
|
};
|
|
3489
4081
|
};
|
|
3490
4082
|
|
|
4083
|
+
const defaultMockApiCommandId = 'ChatE2e.mockApi';
|
|
4084
|
+
const useMockApi = (state, value, mockApiCommandId = defaultMockApiCommandId) => {
|
|
4085
|
+
if (!value) {
|
|
4086
|
+
return {
|
|
4087
|
+
...state,
|
|
4088
|
+
useMockApi: false
|
|
4089
|
+
};
|
|
4090
|
+
}
|
|
4091
|
+
return {
|
|
4092
|
+
...state,
|
|
4093
|
+
mockApiCommandId,
|
|
4094
|
+
useMockApi: true
|
|
4095
|
+
};
|
|
4096
|
+
};
|
|
4097
|
+
|
|
3491
4098
|
const commandMap = {
|
|
3492
4099
|
'Chat.clearInput': wrapCommand(clearInput),
|
|
3493
4100
|
'Chat.create': create,
|
|
@@ -3521,7 +4128,8 @@ const commandMap = {
|
|
|
3521
4128
|
'Chat.saveState': wrapGetter(saveState),
|
|
3522
4129
|
'Chat.setChatList': wrapCommand(setChatList),
|
|
3523
4130
|
'Chat.setOpenRouterApiKey': wrapCommand(setOpenRouterApiKey),
|
|
3524
|
-
'Chat.terminate': terminate
|
|
4131
|
+
'Chat.terminate': terminate,
|
|
4132
|
+
'Chat.useMockApi': wrapCommand(useMockApi)
|
|
3525
4133
|
};
|
|
3526
4134
|
|
|
3527
4135
|
const listen = async () => {
|