@lvce-editor/chat-view 1.15.0 → 1.17.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 +817 -343
- package/package.json +1 -1
|
@@ -1045,6 +1045,9 @@ const {
|
|
|
1045
1045
|
invokeAndTransfer,
|
|
1046
1046
|
set: set$1
|
|
1047
1047
|
} = create$2(RendererWorker);
|
|
1048
|
+
const readFile = async uri => {
|
|
1049
|
+
return invoke('FileSystem.readFile', uri);
|
|
1050
|
+
};
|
|
1048
1051
|
const sendMessagePortToExtensionHostWorker$1 = async (port, rpcId = 0) => {
|
|
1049
1052
|
const command = 'HandleMessagePort.handleMessagePort2';
|
|
1050
1053
|
await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToExtensionHostWorker', port, command, rpcId);
|
|
@@ -1179,9 +1182,45 @@ const terminate = () => {
|
|
|
1179
1182
|
globalThis.close();
|
|
1180
1183
|
};
|
|
1181
1184
|
|
|
1185
|
+
const measureTextBlockHeight = async (text, fontFamily, fontSize, lineHeight, width) => {
|
|
1186
|
+
return invoke('MeasureTextBlockHeight.measureTextBlockHeight', text, fontSize, fontFamily, lineHeight, width);
|
|
1187
|
+
};
|
|
1188
|
+
|
|
1189
|
+
const getComposerWidth = width => {
|
|
1190
|
+
return Math.max(1, width - 32);
|
|
1191
|
+
};
|
|
1192
|
+
const getMinComposerHeight = lineHeight => {
|
|
1193
|
+
return lineHeight + 8;
|
|
1194
|
+
};
|
|
1195
|
+
const estimateComposerHeight = (value, lineHeight) => {
|
|
1196
|
+
const lineCount = value.split('\n').length;
|
|
1197
|
+
return lineCount * lineHeight + 8;
|
|
1198
|
+
};
|
|
1199
|
+
const getComposerHeight = async (state, value, width = state.width) => {
|
|
1200
|
+
const {
|
|
1201
|
+
composerFontFamily,
|
|
1202
|
+
composerFontSize,
|
|
1203
|
+
composerLineHeight
|
|
1204
|
+
} = state;
|
|
1205
|
+
const minimumHeight = getMinComposerHeight(composerLineHeight);
|
|
1206
|
+
const content = value || ' ';
|
|
1207
|
+
const composerWidth = getComposerWidth(width);
|
|
1208
|
+
try {
|
|
1209
|
+
const measuredHeight = await measureTextBlockHeight(content, composerFontFamily, composerFontSize, composerLineHeight, composerWidth);
|
|
1210
|
+
const height = Math.ceil(measuredHeight) + 8;
|
|
1211
|
+
return Math.max(minimumHeight, height);
|
|
1212
|
+
} catch {
|
|
1213
|
+
return Math.max(minimumHeight, estimateComposerHeight(value, composerLineHeight));
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
const getMinComposerHeightForState = state => {
|
|
1217
|
+
return getMinComposerHeight(state.composerLineHeight);
|
|
1218
|
+
};
|
|
1219
|
+
|
|
1182
1220
|
const clearInput = async state => {
|
|
1183
1221
|
return {
|
|
1184
1222
|
...state,
|
|
1223
|
+
composerHeight: getMinComposerHeightForState(state),
|
|
1185
1224
|
composerValue: ''
|
|
1186
1225
|
};
|
|
1187
1226
|
};
|
|
@@ -1237,6 +1276,9 @@ const sendMessage = () => {
|
|
|
1237
1276
|
const save = () => {
|
|
1238
1277
|
return i18nString('Save');
|
|
1239
1278
|
};
|
|
1279
|
+
const saving = () => {
|
|
1280
|
+
return i18nString('Saving...');
|
|
1281
|
+
};
|
|
1240
1282
|
const getOpenRouterApiKey = () => {
|
|
1241
1283
|
return i18nString('Get API Key');
|
|
1242
1284
|
};
|
|
@@ -1270,6 +1312,22 @@ const getDefaultModels = () => {
|
|
|
1270
1312
|
id: defaultModelId,
|
|
1271
1313
|
name: 'test',
|
|
1272
1314
|
provider: 'test'
|
|
1315
|
+
}, {
|
|
1316
|
+
id: 'openapi/gpt-5-mini',
|
|
1317
|
+
name: 'GPT-5 Mini',
|
|
1318
|
+
provider: 'openApi'
|
|
1319
|
+
}, {
|
|
1320
|
+
id: 'openapi/gpt-4o-mini',
|
|
1321
|
+
name: 'GPT-4o Mini',
|
|
1322
|
+
provider: 'openApi'
|
|
1323
|
+
}, {
|
|
1324
|
+
id: 'openapi/gpt-4o',
|
|
1325
|
+
name: 'GPT-4o',
|
|
1326
|
+
provider: 'openApi'
|
|
1327
|
+
}, {
|
|
1328
|
+
id: 'openapi/gpt-4.1-mini',
|
|
1329
|
+
name: 'GPT-4.1 Mini',
|
|
1330
|
+
provider: 'openApi'
|
|
1273
1331
|
}, {
|
|
1274
1332
|
id: 'codex-5.3',
|
|
1275
1333
|
name: 'Codex 5.3',
|
|
@@ -1323,8 +1381,14 @@ const getDefaultModels = () => {
|
|
|
1323
1381
|
const createDefaultState = () => {
|
|
1324
1382
|
const defaultSessionId = 'session-1';
|
|
1325
1383
|
const defaultModelId = 'test';
|
|
1384
|
+
const composerFontSize = 13;
|
|
1385
|
+
const composerLineHeight = 20;
|
|
1326
1386
|
return {
|
|
1327
1387
|
assetDir: '',
|
|
1388
|
+
composerFontFamily: 'system-ui',
|
|
1389
|
+
composerFontSize,
|
|
1390
|
+
composerHeight: composerLineHeight + 8,
|
|
1391
|
+
composerLineHeight,
|
|
1328
1392
|
composerValue: '',
|
|
1329
1393
|
errorCount: 0,
|
|
1330
1394
|
focus: 'composer',
|
|
@@ -1346,6 +1410,7 @@ const createDefaultState = () => {
|
|
|
1346
1410
|
openRouterApiKey: '',
|
|
1347
1411
|
openRouterApiKeyInput: '',
|
|
1348
1412
|
openRouterApiKeysSettingsUrl: 'https://openrouter.ai/settings/keys',
|
|
1413
|
+
openRouterApiKeyState: 'idle',
|
|
1349
1414
|
platform: 0,
|
|
1350
1415
|
renamingSessionId: '',
|
|
1351
1416
|
selectedModelId: defaultModelId,
|
|
@@ -2118,6 +2183,15 @@ const openRouterTooManyRequestsMessage = 'OpenRouter rate limit reached (429). P
|
|
|
2118
2183
|
const openRouterRequestFailureReasons = ['ContentSecurityPolicyViolation: Check DevTools for details.', 'OpenRouter server offline: Check DevTools for details.', 'Check your internet connection.'];
|
|
2119
2184
|
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
2185
|
|
|
2186
|
+
const delay = async ms => {
|
|
2187
|
+
await new Promise(resolve => setTimeout(resolve, ms));
|
|
2188
|
+
};
|
|
2189
|
+
|
|
2190
|
+
const getMockAiResponse = async userMessage => {
|
|
2191
|
+
await delay(800);
|
|
2192
|
+
return `Mock AI response: I received "${userMessage}".`;
|
|
2193
|
+
};
|
|
2194
|
+
|
|
2121
2195
|
const activateByEvent = (event, assetDir, platform) => {
|
|
2122
2196
|
// @ts-ignore
|
|
2123
2197
|
return activateByEvent$1(event, assetDir, platform);
|
|
@@ -2138,161 +2212,547 @@ const executeProvider = async ({
|
|
|
2138
2212
|
};
|
|
2139
2213
|
|
|
2140
2214
|
const CommandExecute = 'ExtensionHostCommand.executeCommand';
|
|
2215
|
+
const FileSystemWriteFile = 'ExtensionHostFileSystem.writeFile';
|
|
2141
2216
|
|
|
2142
|
-
const
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
const getMockAiResponse = async userMessage => {
|
|
2147
|
-
await delay(800);
|
|
2148
|
-
return `Mock AI response: I received "${userMessage}".`;
|
|
2149
|
-
};
|
|
2150
|
-
|
|
2151
|
-
const getOpenApiApiEndpoint = openApiApiBaseUrl => {
|
|
2152
|
-
return `${openApiApiBaseUrl}/chat/completions`;
|
|
2153
|
-
};
|
|
2154
|
-
|
|
2155
|
-
const getTextContent = content => {
|
|
2156
|
-
if (typeof content === 'string') {
|
|
2157
|
-
return content;
|
|
2158
|
-
}
|
|
2159
|
-
if (!Array.isArray(content)) {
|
|
2160
|
-
return '';
|
|
2161
|
-
}
|
|
2162
|
-
const textParts = [];
|
|
2163
|
-
for (const part of content) {
|
|
2164
|
-
if (!part || typeof part !== 'object') {
|
|
2165
|
-
continue;
|
|
2166
|
-
}
|
|
2167
|
-
const maybeType = Reflect.get(part, 'type');
|
|
2168
|
-
const maybeText = Reflect.get(part, 'text');
|
|
2169
|
-
if (maybeType === 'text' && typeof maybeText === 'string') {
|
|
2170
|
-
textParts.push(maybeText);
|
|
2171
|
-
}
|
|
2217
|
+
const normalizeLimitInfo = value => {
|
|
2218
|
+
if (!value || typeof value !== 'object') {
|
|
2219
|
+
return undefined;
|
|
2172
2220
|
}
|
|
2173
|
-
|
|
2221
|
+
const limitRemaining = Reflect.get(value, 'limitRemaining');
|
|
2222
|
+
const limitReset = Reflect.get(value, 'limitReset');
|
|
2223
|
+
const retryAfter = Reflect.get(value, 'retryAfter');
|
|
2224
|
+
const usage = Reflect.get(value, 'usage');
|
|
2225
|
+
const usageDaily = Reflect.get(value, 'usageDaily');
|
|
2226
|
+
const normalized = {
|
|
2227
|
+
limitRemaining: typeof limitRemaining === 'number' || limitRemaining === null ? limitRemaining : undefined,
|
|
2228
|
+
limitReset: typeof limitReset === 'string' || limitReset === null ? limitReset : undefined,
|
|
2229
|
+
retryAfter: typeof retryAfter === 'string' || retryAfter === null ? retryAfter : undefined,
|
|
2230
|
+
usage: typeof usage === 'number' ? usage : undefined,
|
|
2231
|
+
usageDaily: typeof usageDaily === 'number' ? usageDaily : undefined
|
|
2232
|
+
};
|
|
2233
|
+
const hasDetails = normalized.limitRemaining !== undefined || normalized.limitReset !== undefined || normalized.retryAfter !== undefined || normalized.usage !== undefined || normalized.usageDaily !== undefined;
|
|
2234
|
+
return hasDetails ? normalized : undefined;
|
|
2174
2235
|
};
|
|
2175
2236
|
|
|
2176
|
-
const
|
|
2177
|
-
|
|
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 {
|
|
2237
|
+
const normalizeMockResult = value => {
|
|
2238
|
+
if (typeof value === 'string') {
|
|
2194
2239
|
return {
|
|
2195
|
-
|
|
2196
|
-
type: '
|
|
2240
|
+
text: value,
|
|
2241
|
+
type: 'success'
|
|
2197
2242
|
};
|
|
2198
2243
|
}
|
|
2199
|
-
if (!
|
|
2244
|
+
if (!value || typeof value !== 'object') {
|
|
2200
2245
|
return {
|
|
2201
|
-
details: '
|
|
2202
|
-
statusCode: response.status,
|
|
2246
|
+
details: 'request-failed',
|
|
2203
2247
|
type: 'error'
|
|
2204
2248
|
};
|
|
2205
2249
|
}
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2250
|
+
const type = Reflect.get(value, 'type');
|
|
2251
|
+
if (type === 'success') {
|
|
2252
|
+
const text = Reflect.get(value, 'text');
|
|
2253
|
+
if (typeof text === 'string') {
|
|
2254
|
+
return {
|
|
2255
|
+
text,
|
|
2256
|
+
type: 'success'
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2210
2259
|
return {
|
|
2211
2260
|
details: 'request-failed',
|
|
2212
2261
|
type: 'error'
|
|
2213
2262
|
};
|
|
2214
2263
|
}
|
|
2215
|
-
if (
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2264
|
+
if (type === 'error') {
|
|
2265
|
+
const details = Reflect.get(value, 'details');
|
|
2266
|
+
if (details === 'request-failed' || details === 'too-many-requests' || details === 'http-error') {
|
|
2267
|
+
const rawMessage = Reflect.get(value, 'rawMessage');
|
|
2268
|
+
const statusCode = Reflect.get(value, 'statusCode');
|
|
2269
|
+
return {
|
|
2270
|
+
details,
|
|
2271
|
+
limitInfo: normalizeLimitInfo(Reflect.get(value, 'limitInfo')),
|
|
2272
|
+
rawMessage: typeof rawMessage === 'string' ? rawMessage : undefined,
|
|
2273
|
+
statusCode: typeof statusCode === 'number' ? statusCode : undefined,
|
|
2274
|
+
type: 'error'
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2220
2277
|
}
|
|
2221
|
-
const
|
|
2222
|
-
if (
|
|
2278
|
+
const text = Reflect.get(value, 'text');
|
|
2279
|
+
if (typeof text === 'string') {
|
|
2223
2280
|
return {
|
|
2224
|
-
text
|
|
2281
|
+
text,
|
|
2225
2282
|
type: 'success'
|
|
2226
2283
|
};
|
|
2227
2284
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2285
|
+
return {
|
|
2286
|
+
details: 'request-failed',
|
|
2287
|
+
type: 'error'
|
|
2288
|
+
};
|
|
2289
|
+
};
|
|
2290
|
+
|
|
2291
|
+
const getMockOpenRouterAssistantText = async (messages, modelId, openRouterApiBaseUrl, openRouterApiKey, mockApiCommandId, assetDir, platform) => {
|
|
2292
|
+
if (!mockApiCommandId) {
|
|
2230
2293
|
return {
|
|
2231
|
-
|
|
2232
|
-
type: '
|
|
2294
|
+
details: 'request-failed',
|
|
2295
|
+
type: 'error'
|
|
2233
2296
|
};
|
|
2234
2297
|
}
|
|
2235
|
-
|
|
2236
|
-
|
|
2298
|
+
try {
|
|
2299
|
+
const result = await executeProvider({
|
|
2300
|
+
assetDir,
|
|
2301
|
+
event: `onCommand:${mockApiCommandId}`,
|
|
2302
|
+
method: CommandExecute,
|
|
2303
|
+
noProviderFoundMessage: 'No mock api command found',
|
|
2304
|
+
params: [mockApiCommandId, {
|
|
2305
|
+
messages,
|
|
2306
|
+
modelId,
|
|
2307
|
+
openRouterApiBaseUrl,
|
|
2308
|
+
openRouterApiKey
|
|
2309
|
+
}],
|
|
2310
|
+
platform
|
|
2311
|
+
});
|
|
2312
|
+
return normalizeMockResult(result);
|
|
2313
|
+
} catch {
|
|
2237
2314
|
return {
|
|
2238
|
-
|
|
2239
|
-
type: '
|
|
2315
|
+
details: 'request-failed',
|
|
2316
|
+
type: 'error'
|
|
2240
2317
|
};
|
|
2241
2318
|
}
|
|
2242
|
-
const content = Reflect.get(message, 'content');
|
|
2243
|
-
return {
|
|
2244
|
-
text: getTextContent(content),
|
|
2245
|
-
type: 'success'
|
|
2246
|
-
};
|
|
2247
2319
|
};
|
|
2248
2320
|
|
|
2249
|
-
const
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
return selectedModelId.slice(openApiPrefix.length);
|
|
2321
|
+
const OnFileSystem = 'onFileSystem';
|
|
2322
|
+
|
|
2323
|
+
const isPathTraversalAttempt = path => {
|
|
2324
|
+
if (!path) {
|
|
2325
|
+
return false;
|
|
2255
2326
|
}
|
|
2256
|
-
if (
|
|
2257
|
-
return
|
|
2327
|
+
if (path.startsWith('/') || path.startsWith('\\')) {
|
|
2328
|
+
return true;
|
|
2258
2329
|
}
|
|
2259
|
-
|
|
2330
|
+
if (path.startsWith('file://')) {
|
|
2331
|
+
return true;
|
|
2332
|
+
}
|
|
2333
|
+
if (/^[a-zA-Z]:[\\/]/.test(path)) {
|
|
2334
|
+
return true;
|
|
2335
|
+
}
|
|
2336
|
+
const segments = path.split(/[\\/]/);
|
|
2337
|
+
return segments.includes('..');
|
|
2338
|
+
};
|
|
2339
|
+
const normalizeRelativePath = path => {
|
|
2340
|
+
const segments = path.split(/[\\/]/).filter(segment => segment && segment !== '.');
|
|
2341
|
+
if (segments.length === 0) {
|
|
2342
|
+
return '.';
|
|
2343
|
+
}
|
|
2344
|
+
return segments.join('/');
|
|
2345
|
+
};
|
|
2346
|
+
const parseToolArguments = rawArguments => {
|
|
2347
|
+
if (typeof rawArguments !== 'string') {
|
|
2348
|
+
return {};
|
|
2349
|
+
}
|
|
2350
|
+
try {
|
|
2351
|
+
const parsed = JSON.parse(rawArguments);
|
|
2352
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2353
|
+
return {};
|
|
2354
|
+
}
|
|
2355
|
+
return parsed;
|
|
2356
|
+
} catch {
|
|
2357
|
+
return {};
|
|
2358
|
+
}
|
|
2359
|
+
};
|
|
2360
|
+
const executeFileSystemCommand = async (method, params, options) => {
|
|
2361
|
+
return executeProvider({
|
|
2362
|
+
assetDir: options.assetDir,
|
|
2363
|
+
event: OnFileSystem,
|
|
2364
|
+
method,
|
|
2365
|
+
noProviderFoundMessage: 'No file system provider found',
|
|
2366
|
+
params,
|
|
2367
|
+
platform: options.platform
|
|
2368
|
+
});
|
|
2369
|
+
};
|
|
2370
|
+
const getBasicChatTools = () => {
|
|
2371
|
+
return [{
|
|
2372
|
+
function: {
|
|
2373
|
+
description: 'Read UTF-8 text content from a file inside the currently open workspace folder.',
|
|
2374
|
+
name: 'read_file',
|
|
2375
|
+
parameters: {
|
|
2376
|
+
additionalProperties: false,
|
|
2377
|
+
properties: {
|
|
2378
|
+
path: {
|
|
2379
|
+
description: 'Relative file path within the workspace (for example: src/index.ts).',
|
|
2380
|
+
type: 'string'
|
|
2381
|
+
}
|
|
2382
|
+
},
|
|
2383
|
+
required: ['path'],
|
|
2384
|
+
type: 'object'
|
|
2385
|
+
}
|
|
2386
|
+
},
|
|
2387
|
+
type: 'function'
|
|
2388
|
+
}, {
|
|
2389
|
+
function: {
|
|
2390
|
+
description: 'Write UTF-8 text content to a file inside the currently open workspace folder.',
|
|
2391
|
+
name: 'write_file',
|
|
2392
|
+
parameters: {
|
|
2393
|
+
additionalProperties: false,
|
|
2394
|
+
properties: {
|
|
2395
|
+
content: {
|
|
2396
|
+
description: 'New UTF-8 text content to write to the file.',
|
|
2397
|
+
type: 'string'
|
|
2398
|
+
},
|
|
2399
|
+
path: {
|
|
2400
|
+
description: 'Relative file path within the workspace (for example: src/index.ts).',
|
|
2401
|
+
type: 'string'
|
|
2402
|
+
}
|
|
2403
|
+
},
|
|
2404
|
+
required: ['path', 'content'],
|
|
2405
|
+
type: 'object'
|
|
2406
|
+
}
|
|
2407
|
+
},
|
|
2408
|
+
type: 'function'
|
|
2409
|
+
}, {
|
|
2410
|
+
function: {
|
|
2411
|
+
description: 'List direct children (files and folders) for a folder inside the currently open workspace folder.',
|
|
2412
|
+
name: 'list_files',
|
|
2413
|
+
parameters: {
|
|
2414
|
+
additionalProperties: false,
|
|
2415
|
+
properties: {
|
|
2416
|
+
path: {
|
|
2417
|
+
description: 'Relative folder path within the workspace. Use "." for the workspace root.',
|
|
2418
|
+
type: 'string'
|
|
2419
|
+
}
|
|
2420
|
+
},
|
|
2421
|
+
type: 'object'
|
|
2422
|
+
}
|
|
2423
|
+
},
|
|
2424
|
+
type: 'function'
|
|
2425
|
+
}];
|
|
2426
|
+
};
|
|
2427
|
+
const executeChatTool = async (name, rawArguments, options) => {
|
|
2428
|
+
const args = parseToolArguments(rawArguments);
|
|
2429
|
+
if (name === 'read_file') {
|
|
2430
|
+
const filePath = typeof args.path === 'string' ? args.path : '';
|
|
2431
|
+
if (!filePath || isPathTraversalAttempt(filePath)) {
|
|
2432
|
+
return JSON.stringify({
|
|
2433
|
+
error: 'Access denied: path must be relative and stay within the open workspace folder.'
|
|
2434
|
+
});
|
|
2435
|
+
}
|
|
2436
|
+
const normalizedPath = normalizeRelativePath(filePath);
|
|
2437
|
+
try {
|
|
2438
|
+
const content = await readFile(normalizedPath);
|
|
2439
|
+
return JSON.stringify({
|
|
2440
|
+
content,
|
|
2441
|
+
path: normalizedPath
|
|
2442
|
+
});
|
|
2443
|
+
} catch (error) {
|
|
2444
|
+
return JSON.stringify({
|
|
2445
|
+
error: String(error),
|
|
2446
|
+
path: normalizedPath
|
|
2447
|
+
});
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
if (name === 'write_file') {
|
|
2451
|
+
const filePath = typeof args.path === 'string' ? args.path : '';
|
|
2452
|
+
const content = typeof args.content === 'string' ? args.content : '';
|
|
2453
|
+
if (!filePath || isPathTraversalAttempt(filePath)) {
|
|
2454
|
+
return JSON.stringify({
|
|
2455
|
+
error: 'Access denied: path must be relative and stay within the open workspace folder.'
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
const normalizedPath = normalizeRelativePath(filePath);
|
|
2459
|
+
try {
|
|
2460
|
+
await executeFileSystemCommand(FileSystemWriteFile, ['file', normalizedPath, content], options);
|
|
2461
|
+
return JSON.stringify({
|
|
2462
|
+
ok: true,
|
|
2463
|
+
path: normalizedPath
|
|
2464
|
+
});
|
|
2465
|
+
} catch (error) {
|
|
2466
|
+
return JSON.stringify({
|
|
2467
|
+
error: String(error),
|
|
2468
|
+
path: normalizedPath
|
|
2469
|
+
});
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
if (name === 'list_files') {
|
|
2473
|
+
const folderPath = typeof args.path === 'string' && args.path ? args.path : '.';
|
|
2474
|
+
if (isPathTraversalAttempt(folderPath)) {
|
|
2475
|
+
return JSON.stringify({
|
|
2476
|
+
error: 'Access denied: path must be relative and stay within the open workspace folder.'
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
const normalizedPath = normalizeRelativePath(folderPath);
|
|
2480
|
+
try {
|
|
2481
|
+
const entries = await invoke('FileSystem.readDirWithFileTypes', normalizedPath);
|
|
2482
|
+
return JSON.stringify({
|
|
2483
|
+
entries,
|
|
2484
|
+
path: normalizedPath
|
|
2485
|
+
});
|
|
2486
|
+
} catch (error) {
|
|
2487
|
+
return JSON.stringify({
|
|
2488
|
+
error: String(error),
|
|
2489
|
+
path: normalizedPath
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
return JSON.stringify({
|
|
2494
|
+
error: `Unknown tool: ${name}`
|
|
2495
|
+
});
|
|
2260
2496
|
};
|
|
2261
2497
|
|
|
2262
|
-
const
|
|
2263
|
-
|
|
2264
|
-
return `${trimmedBaseUrl}/chat/completions`;
|
|
2498
|
+
const getOpenApiApiEndpoint = openApiApiBaseUrl => {
|
|
2499
|
+
return `${openApiApiBaseUrl}/chat/completions`;
|
|
2265
2500
|
};
|
|
2266
2501
|
|
|
2267
|
-
const
|
|
2268
|
-
|
|
2269
|
-
|
|
2502
|
+
const getTextContent = content => {
|
|
2503
|
+
if (typeof content === 'string') {
|
|
2504
|
+
return content;
|
|
2505
|
+
}
|
|
2506
|
+
if (!Array.isArray(content)) {
|
|
2507
|
+
return '';
|
|
2508
|
+
}
|
|
2509
|
+
const textParts = [];
|
|
2510
|
+
for (const part of content) {
|
|
2511
|
+
if (!part || typeof part !== 'object') {
|
|
2512
|
+
continue;
|
|
2513
|
+
}
|
|
2514
|
+
const maybeType = Reflect.get(part, 'type');
|
|
2515
|
+
const maybeText = Reflect.get(part, 'text');
|
|
2516
|
+
if (maybeType === 'text' && typeof maybeText === 'string') {
|
|
2517
|
+
textParts.push(maybeText);
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
return textParts.join('\n');
|
|
2270
2521
|
};
|
|
2271
2522
|
|
|
2272
|
-
const
|
|
2523
|
+
const getOpenApiErrorDetails = async response => {
|
|
2273
2524
|
let parsed;
|
|
2274
2525
|
try {
|
|
2275
2526
|
parsed = await response.json();
|
|
2276
2527
|
} catch {
|
|
2277
|
-
return
|
|
2528
|
+
return {};
|
|
2278
2529
|
}
|
|
2279
2530
|
if (!parsed || typeof parsed !== 'object') {
|
|
2280
|
-
return
|
|
2531
|
+
return {};
|
|
2281
2532
|
}
|
|
2282
2533
|
const error = Reflect.get(parsed, 'error');
|
|
2283
2534
|
if (!error || typeof error !== 'object') {
|
|
2284
|
-
return
|
|
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;
|
|
2535
|
+
return {};
|
|
2293
2536
|
}
|
|
2294
|
-
|
|
2295
|
-
|
|
2537
|
+
const errorCode = Reflect.get(error, 'code');
|
|
2538
|
+
const errorMessage = Reflect.get(error, 'message');
|
|
2539
|
+
const errorType = Reflect.get(error, 'type');
|
|
2540
|
+
return {
|
|
2541
|
+
errorCode: typeof errorCode === 'string' ? errorCode : undefined,
|
|
2542
|
+
errorMessage: typeof errorMessage === 'string' ? errorMessage : undefined,
|
|
2543
|
+
errorType: typeof errorType === 'string' ? errorType : undefined
|
|
2544
|
+
};
|
|
2545
|
+
};
|
|
2546
|
+
const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApiApiBaseUrl, assetDir, platform) => {
|
|
2547
|
+
const completionMessages = messages.map(message => ({
|
|
2548
|
+
content: message.text,
|
|
2549
|
+
role: message.role
|
|
2550
|
+
}));
|
|
2551
|
+
const tools = getBasicChatTools();
|
|
2552
|
+
const maxToolIterations = 4;
|
|
2553
|
+
for (let i = 0; i <= maxToolIterations; i++) {
|
|
2554
|
+
let response;
|
|
2555
|
+
try {
|
|
2556
|
+
response = await fetch(getOpenApiApiEndpoint(openApiApiBaseUrl), {
|
|
2557
|
+
body: JSON.stringify({
|
|
2558
|
+
messages: completionMessages,
|
|
2559
|
+
model: modelId,
|
|
2560
|
+
tool_choice: 'auto',
|
|
2561
|
+
tools
|
|
2562
|
+
}),
|
|
2563
|
+
headers: {
|
|
2564
|
+
Authorization: `Bearer ${openApiApiKey}`,
|
|
2565
|
+
'Content-Type': 'application/json'
|
|
2566
|
+
},
|
|
2567
|
+
method: 'POST'
|
|
2568
|
+
});
|
|
2569
|
+
} catch {
|
|
2570
|
+
return {
|
|
2571
|
+
details: 'request-failed',
|
|
2572
|
+
type: 'error'
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
if (!response.ok) {
|
|
2576
|
+
const {
|
|
2577
|
+
errorCode,
|
|
2578
|
+
errorMessage,
|
|
2579
|
+
errorType
|
|
2580
|
+
} = await getOpenApiErrorDetails(response);
|
|
2581
|
+
return {
|
|
2582
|
+
details: 'http-error',
|
|
2583
|
+
errorCode,
|
|
2584
|
+
errorMessage,
|
|
2585
|
+
errorType,
|
|
2586
|
+
statusCode: response.status,
|
|
2587
|
+
type: 'error'
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
let parsed;
|
|
2591
|
+
try {
|
|
2592
|
+
parsed = await response.json();
|
|
2593
|
+
} catch {
|
|
2594
|
+
return {
|
|
2595
|
+
details: 'request-failed',
|
|
2596
|
+
type: 'error'
|
|
2597
|
+
};
|
|
2598
|
+
}
|
|
2599
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2600
|
+
return {
|
|
2601
|
+
text: '',
|
|
2602
|
+
type: 'success'
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
2605
|
+
const choices = Reflect.get(parsed, 'choices');
|
|
2606
|
+
if (!Array.isArray(choices)) {
|
|
2607
|
+
return {
|
|
2608
|
+
text: '',
|
|
2609
|
+
type: 'success'
|
|
2610
|
+
};
|
|
2611
|
+
}
|
|
2612
|
+
const firstChoice = choices[0];
|
|
2613
|
+
if (!firstChoice || typeof firstChoice !== 'object') {
|
|
2614
|
+
return {
|
|
2615
|
+
text: '',
|
|
2616
|
+
type: 'success'
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
const message = Reflect.get(firstChoice, 'message');
|
|
2620
|
+
if (!message || typeof message !== 'object') {
|
|
2621
|
+
return {
|
|
2622
|
+
text: '',
|
|
2623
|
+
type: 'success'
|
|
2624
|
+
};
|
|
2625
|
+
}
|
|
2626
|
+
const toolCalls = Reflect.get(message, 'tool_calls');
|
|
2627
|
+
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
2628
|
+
completionMessages.push(message);
|
|
2629
|
+
for (const toolCall of toolCalls) {
|
|
2630
|
+
if (!toolCall || typeof toolCall !== 'object') {
|
|
2631
|
+
continue;
|
|
2632
|
+
}
|
|
2633
|
+
const id = Reflect.get(toolCall, 'id');
|
|
2634
|
+
const toolFunction = Reflect.get(toolCall, 'function');
|
|
2635
|
+
if (typeof id !== 'string' || !toolFunction || typeof toolFunction !== 'object') {
|
|
2636
|
+
continue;
|
|
2637
|
+
}
|
|
2638
|
+
const name = Reflect.get(toolFunction, 'name');
|
|
2639
|
+
const rawArguments = Reflect.get(toolFunction, 'arguments');
|
|
2640
|
+
const content = typeof name === 'string' ? await executeChatTool(name, rawArguments, {
|
|
2641
|
+
assetDir,
|
|
2642
|
+
platform
|
|
2643
|
+
}) : '{}';
|
|
2644
|
+
completionMessages.push({
|
|
2645
|
+
content,
|
|
2646
|
+
role: 'tool',
|
|
2647
|
+
tool_call_id: id
|
|
2648
|
+
});
|
|
2649
|
+
}
|
|
2650
|
+
continue;
|
|
2651
|
+
}
|
|
2652
|
+
const content = Reflect.get(message, 'content');
|
|
2653
|
+
return {
|
|
2654
|
+
text: getTextContent(content),
|
|
2655
|
+
type: 'success'
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
return {
|
|
2659
|
+
details: 'request-failed',
|
|
2660
|
+
type: 'error'
|
|
2661
|
+
};
|
|
2662
|
+
};
|
|
2663
|
+
|
|
2664
|
+
const getOpenApiErrorMessage = errorResult => {
|
|
2665
|
+
switch (errorResult.details) {
|
|
2666
|
+
case 'http-error':
|
|
2667
|
+
{
|
|
2668
|
+
const errorMessage = errorResult.errorMessage?.trim();
|
|
2669
|
+
const hasErrorCode = typeof errorResult.errorCode === 'string' && errorResult.errorCode.length > 0;
|
|
2670
|
+
const hasErrorType = typeof errorResult.errorType === 'string' && errorResult.errorType.length > 0;
|
|
2671
|
+
if (errorResult.statusCode === 429) {
|
|
2672
|
+
let prefix = 'OpenAI rate limit exceeded (429)';
|
|
2673
|
+
if (hasErrorCode) {
|
|
2674
|
+
prefix = `OpenAI rate limit exceeded (429: ${errorResult.errorCode})`;
|
|
2675
|
+
}
|
|
2676
|
+
if (hasErrorType) {
|
|
2677
|
+
prefix += ` [${errorResult.errorType}]`;
|
|
2678
|
+
}
|
|
2679
|
+
prefix += '.';
|
|
2680
|
+
if (!errorMessage) {
|
|
2681
|
+
return prefix;
|
|
2682
|
+
}
|
|
2683
|
+
return `${prefix} ${errorMessage}`;
|
|
2684
|
+
}
|
|
2685
|
+
if (typeof errorResult.statusCode === 'number') {
|
|
2686
|
+
let prefix = `OpenAI request failed (status ${errorResult.statusCode})`;
|
|
2687
|
+
if (hasErrorCode) {
|
|
2688
|
+
prefix += `: ${errorResult.errorCode}`;
|
|
2689
|
+
}
|
|
2690
|
+
if (hasErrorType) {
|
|
2691
|
+
prefix += ` [${errorResult.errorType}]`;
|
|
2692
|
+
}
|
|
2693
|
+
prefix += '.';
|
|
2694
|
+
if (!errorMessage) {
|
|
2695
|
+
return prefix;
|
|
2696
|
+
}
|
|
2697
|
+
return `${prefix} ${errorMessage}`;
|
|
2698
|
+
}
|
|
2699
|
+
if (errorMessage) {
|
|
2700
|
+
return `OpenAI request failed. ${errorMessage}`;
|
|
2701
|
+
}
|
|
2702
|
+
return openApiRequestFailedMessage;
|
|
2703
|
+
}
|
|
2704
|
+
case 'request-failed':
|
|
2705
|
+
return openApiRequestFailedMessage;
|
|
2706
|
+
}
|
|
2707
|
+
};
|
|
2708
|
+
|
|
2709
|
+
const getOpenApiModelId = selectedModelId => {
|
|
2710
|
+
const openApiPrefix = 'openapi/';
|
|
2711
|
+
const openAiPrefix = 'openai/';
|
|
2712
|
+
const normalizedModelId = selectedModelId.toLowerCase();
|
|
2713
|
+
if (normalizedModelId.startsWith(openApiPrefix)) {
|
|
2714
|
+
return selectedModelId.slice(openApiPrefix.length);
|
|
2715
|
+
}
|
|
2716
|
+
if (normalizedModelId.startsWith(openAiPrefix)) {
|
|
2717
|
+
return selectedModelId.slice(openAiPrefix.length);
|
|
2718
|
+
}
|
|
2719
|
+
return selectedModelId;
|
|
2720
|
+
};
|
|
2721
|
+
|
|
2722
|
+
const getOpenRouterApiEndpoint = openRouterApiBaseUrl => {
|
|
2723
|
+
const trimmedBaseUrl = openRouterApiBaseUrl.replace(/\/+$/, '');
|
|
2724
|
+
return `${trimmedBaseUrl}/chat/completions`;
|
|
2725
|
+
};
|
|
2726
|
+
|
|
2727
|
+
const getOpenRouterKeyEndpoint = openRouterApiBaseUrl => {
|
|
2728
|
+
const trimmedBaseUrl = openRouterApiBaseUrl.replace(/\/+$/, '');
|
|
2729
|
+
return `${trimmedBaseUrl}/auth/key`;
|
|
2730
|
+
};
|
|
2731
|
+
|
|
2732
|
+
const getOpenRouterRaw429Message = async response => {
|
|
2733
|
+
let parsed;
|
|
2734
|
+
try {
|
|
2735
|
+
parsed = await response.json();
|
|
2736
|
+
} catch {
|
|
2737
|
+
return undefined;
|
|
2738
|
+
}
|
|
2739
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2740
|
+
return undefined;
|
|
2741
|
+
}
|
|
2742
|
+
const error = Reflect.get(parsed, 'error');
|
|
2743
|
+
if (!error || typeof error !== 'object') {
|
|
2744
|
+
return undefined;
|
|
2745
|
+
}
|
|
2746
|
+
const metadata = Reflect.get(error, 'metadata');
|
|
2747
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
2748
|
+
return undefined;
|
|
2749
|
+
}
|
|
2750
|
+
const raw = Reflect.get(metadata, 'raw');
|
|
2751
|
+
if (typeof raw !== 'string' || !raw) {
|
|
2752
|
+
return undefined;
|
|
2753
|
+
}
|
|
2754
|
+
return raw;
|
|
2755
|
+
};
|
|
2296
2756
|
const getOpenRouterLimitInfo = async (openRouterApiKey, openRouterApiBaseUrl) => {
|
|
2297
2757
|
let response;
|
|
2298
2758
|
try {
|
|
@@ -2337,124 +2797,131 @@ const getOpenRouterLimitInfo = async (openRouterApiKey, openRouterApiBaseUrl) =>
|
|
|
2337
2797
|
}
|
|
2338
2798
|
return normalizedLimitInfo;
|
|
2339
2799
|
};
|
|
2340
|
-
const getOpenRouterAssistantText = async (messages, modelId, openRouterApiKey, openRouterApiBaseUrl) => {
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
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);
|
|
2800
|
+
const getOpenRouterAssistantText = async (messages, modelId, openRouterApiKey, openRouterApiBaseUrl, assetDir, platform) => {
|
|
2801
|
+
const completionMessages = messages.map(message => ({
|
|
2802
|
+
content: message.text,
|
|
2803
|
+
role: message.role
|
|
2804
|
+
}));
|
|
2805
|
+
const tools = getBasicChatTools();
|
|
2806
|
+
const maxToolIterations = 4;
|
|
2807
|
+
for (let i = 0; i <= maxToolIterations; i++) {
|
|
2808
|
+
let response;
|
|
2809
|
+
try {
|
|
2810
|
+
response = await fetch(getOpenRouterApiEndpoint(openRouterApiBaseUrl), {
|
|
2811
|
+
body: JSON.stringify({
|
|
2812
|
+
messages: completionMessages,
|
|
2813
|
+
model: modelId,
|
|
2814
|
+
tool_choice: 'auto',
|
|
2815
|
+
tools
|
|
2816
|
+
}),
|
|
2817
|
+
headers: {
|
|
2818
|
+
Authorization: `Bearer ${openRouterApiKey}`,
|
|
2819
|
+
'Content-Type': 'application/json'
|
|
2820
|
+
},
|
|
2821
|
+
method: 'POST'
|
|
2822
|
+
});
|
|
2823
|
+
} catch {
|
|
2368
2824
|
return {
|
|
2369
|
-
details: '
|
|
2370
|
-
limitInfo: limitInfo || retryAfter ? {
|
|
2371
|
-
...limitInfo,
|
|
2372
|
-
retryAfter
|
|
2373
|
-
} : undefined,
|
|
2374
|
-
rawMessage,
|
|
2375
|
-
statusCode: 429,
|
|
2825
|
+
details: 'request-failed',
|
|
2376
2826
|
type: 'error'
|
|
2377
2827
|
};
|
|
2378
2828
|
}
|
|
2829
|
+
if (!response.ok) {
|
|
2830
|
+
if (response.status === 429) {
|
|
2831
|
+
const retryAfter = response.headers?.get?.('retry-after') ?? null;
|
|
2832
|
+
const rawMessage = await getOpenRouterRaw429Message(response);
|
|
2833
|
+
const limitInfo = await getOpenRouterLimitInfo(openRouterApiKey, openRouterApiBaseUrl);
|
|
2834
|
+
return {
|
|
2835
|
+
details: 'too-many-requests',
|
|
2836
|
+
limitInfo: limitInfo || retryAfter ? {
|
|
2837
|
+
...limitInfo,
|
|
2838
|
+
retryAfter
|
|
2839
|
+
} : undefined,
|
|
2840
|
+
rawMessage,
|
|
2841
|
+
statusCode: 429,
|
|
2842
|
+
type: 'error'
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2845
|
+
return {
|
|
2846
|
+
details: 'http-error',
|
|
2847
|
+
statusCode: response.status,
|
|
2848
|
+
type: 'error'
|
|
2849
|
+
};
|
|
2850
|
+
}
|
|
2851
|
+
let parsed;
|
|
2852
|
+
try {
|
|
2853
|
+
parsed = await response.json();
|
|
2854
|
+
} catch {
|
|
2855
|
+
return {
|
|
2856
|
+
details: 'request-failed',
|
|
2857
|
+
type: 'error'
|
|
2858
|
+
};
|
|
2859
|
+
}
|
|
2860
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2861
|
+
return {
|
|
2862
|
+
text: '',
|
|
2863
|
+
type: 'success'
|
|
2864
|
+
};
|
|
2865
|
+
}
|
|
2866
|
+
const choices = Reflect.get(parsed, 'choices');
|
|
2867
|
+
if (!Array.isArray(choices)) {
|
|
2868
|
+
return {
|
|
2869
|
+
text: '',
|
|
2870
|
+
type: 'success'
|
|
2871
|
+
};
|
|
2872
|
+
}
|
|
2873
|
+
const firstChoice = choices[0];
|
|
2874
|
+
if (!firstChoice || typeof firstChoice !== 'object') {
|
|
2875
|
+
return {
|
|
2876
|
+
text: '',
|
|
2877
|
+
type: 'success'
|
|
2878
|
+
};
|
|
2879
|
+
}
|
|
2880
|
+
const message = Reflect.get(firstChoice, 'message');
|
|
2881
|
+
if (!message || typeof message !== 'object') {
|
|
2882
|
+
return {
|
|
2883
|
+
text: '',
|
|
2884
|
+
type: 'success'
|
|
2885
|
+
};
|
|
2886
|
+
}
|
|
2887
|
+
const toolCalls = Reflect.get(message, 'tool_calls');
|
|
2888
|
+
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
2889
|
+
completionMessages.push(message);
|
|
2890
|
+
for (const toolCall of toolCalls) {
|
|
2891
|
+
if (!toolCall || typeof toolCall !== 'object') {
|
|
2892
|
+
continue;
|
|
2893
|
+
}
|
|
2894
|
+
const id = Reflect.get(toolCall, 'id');
|
|
2895
|
+
const toolFunction = Reflect.get(toolCall, 'function');
|
|
2896
|
+
if (typeof id !== 'string' || !toolFunction || typeof toolFunction !== 'object') {
|
|
2897
|
+
continue;
|
|
2898
|
+
}
|
|
2899
|
+
const name = Reflect.get(toolFunction, 'name');
|
|
2900
|
+
const rawArguments = Reflect.get(toolFunction, 'arguments');
|
|
2901
|
+
const content = typeof name === 'string' ? await executeChatTool(name, rawArguments, {
|
|
2902
|
+
assetDir,
|
|
2903
|
+
platform
|
|
2904
|
+
}) : '{}';
|
|
2905
|
+
completionMessages.push({
|
|
2906
|
+
content,
|
|
2907
|
+
role: 'tool',
|
|
2908
|
+
tool_call_id: id
|
|
2909
|
+
});
|
|
2910
|
+
}
|
|
2911
|
+
continue;
|
|
2912
|
+
}
|
|
2913
|
+
const content = Reflect.get(message, 'content');
|
|
2379
2914
|
return {
|
|
2380
|
-
|
|
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
|
-
};
|
|
2393
|
-
}
|
|
2394
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
2395
|
-
return {
|
|
2396
|
-
text: '',
|
|
2397
|
-
type: 'success'
|
|
2398
|
-
};
|
|
2399
|
-
}
|
|
2400
|
-
const choices = Reflect.get(parsed, 'choices');
|
|
2401
|
-
if (!Array.isArray(choices)) {
|
|
2402
|
-
return {
|
|
2403
|
-
text: '',
|
|
2404
|
-
type: 'success'
|
|
2405
|
-
};
|
|
2406
|
-
}
|
|
2407
|
-
const firstChoice = choices[0];
|
|
2408
|
-
if (!firstChoice || typeof firstChoice !== 'object') {
|
|
2409
|
-
return {
|
|
2410
|
-
text: '',
|
|
2411
|
-
type: 'success'
|
|
2412
|
-
};
|
|
2413
|
-
}
|
|
2414
|
-
const message = Reflect.get(firstChoice, 'message');
|
|
2415
|
-
if (!message || typeof message !== 'object') {
|
|
2416
|
-
return {
|
|
2417
|
-
text: '',
|
|
2915
|
+
text: getTextContent(content),
|
|
2418
2916
|
type: 'success'
|
|
2419
2917
|
};
|
|
2420
2918
|
}
|
|
2421
|
-
const content = Reflect.get(message, 'content');
|
|
2422
2919
|
return {
|
|
2423
|
-
|
|
2424
|
-
type: '
|
|
2920
|
+
details: 'request-failed',
|
|
2921
|
+
type: 'error'
|
|
2425
2922
|
};
|
|
2426
2923
|
};
|
|
2427
2924
|
|
|
2428
|
-
/* eslint-disable @cspell/spellchecker */
|
|
2429
|
-
const getOpenRouterModelId = selectedModelId => {
|
|
2430
|
-
const openRouterPrefix = 'openrouter/';
|
|
2431
|
-
if (selectedModelId.toLowerCase().startsWith(openRouterPrefix)) {
|
|
2432
|
-
return selectedModelId.slice(openRouterPrefix.length);
|
|
2433
|
-
}
|
|
2434
|
-
return selectedModelId;
|
|
2435
|
-
};
|
|
2436
|
-
|
|
2437
|
-
const isOpenApiModel = (selectedModelId, models) => {
|
|
2438
|
-
const selectedModel = models.find(model => model.id === selectedModelId);
|
|
2439
|
-
const normalizedProvider = selectedModel?.provider?.toLowerCase();
|
|
2440
|
-
if (normalizedProvider === 'openapi' || normalizedProvider === 'openai' || normalizedProvider === 'open-ai') {
|
|
2441
|
-
return true;
|
|
2442
|
-
}
|
|
2443
|
-
const normalizedModelId = selectedModelId.toLowerCase();
|
|
2444
|
-
return normalizedModelId.startsWith('openapi/') || normalizedModelId.startsWith('openai/');
|
|
2445
|
-
};
|
|
2446
|
-
|
|
2447
|
-
/* eslint-disable @cspell/spellchecker */
|
|
2448
|
-
|
|
2449
|
-
const isOpenRouterModel = (selectedModelId, models) => {
|
|
2450
|
-
const selectedModel = models.find(model => model.id === selectedModelId);
|
|
2451
|
-
const normalizedProvider = selectedModel?.provider?.toLowerCase();
|
|
2452
|
-
if (normalizedProvider === 'openrouter' || normalizedProvider === 'open-router') {
|
|
2453
|
-
return true;
|
|
2454
|
-
}
|
|
2455
|
-
return selectedModelId.toLowerCase().startsWith('openrouter/');
|
|
2456
|
-
};
|
|
2457
|
-
|
|
2458
2925
|
const getOpenRouterTooManyRequestsMessage = errorResult => {
|
|
2459
2926
|
const details = [];
|
|
2460
2927
|
if (errorResult.rawMessage) {
|
|
@@ -2487,6 +2954,7 @@ const getOpenRouterTooManyRequestsMessage = errorResult => {
|
|
|
2487
2954
|
}
|
|
2488
2955
|
return `${openRouterTooManyRequestsMessage} ${details.join(' ')}`;
|
|
2489
2956
|
};
|
|
2957
|
+
|
|
2490
2958
|
const getOpenRouterErrorMessage = errorResult => {
|
|
2491
2959
|
switch (errorResult.details) {
|
|
2492
2960
|
case 'http-error':
|
|
@@ -2496,121 +2964,58 @@ const getOpenRouterErrorMessage = errorResult => {
|
|
|
2496
2964
|
return getOpenRouterTooManyRequestsMessage(errorResult);
|
|
2497
2965
|
}
|
|
2498
2966
|
};
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
};
|
|
2506
|
-
const normalizeLimitInfo = value => {
|
|
2507
|
-
if (!value || typeof value !== 'object') {
|
|
2508
|
-
return undefined;
|
|
2967
|
+
|
|
2968
|
+
/* eslint-disable @cspell/spellchecker */
|
|
2969
|
+
const getOpenRouterModelId = selectedModelId => {
|
|
2970
|
+
const openRouterPrefix = 'openrouter/';
|
|
2971
|
+
if (selectedModelId.toLowerCase().startsWith(openRouterPrefix)) {
|
|
2972
|
+
return selectedModelId.slice(openRouterPrefix.length);
|
|
2509
2973
|
}
|
|
2510
|
-
|
|
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;
|
|
2974
|
+
return selectedModelId;
|
|
2524
2975
|
};
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
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
|
-
};
|
|
2976
|
+
|
|
2977
|
+
const isOpenApiModel = (selectedModelId, models) => {
|
|
2978
|
+
const selectedModel = models.find(model => model.id === selectedModelId);
|
|
2979
|
+
const normalizedProvider = selectedModel?.provider?.toLowerCase();
|
|
2980
|
+
if (normalizedProvider === 'openapi' || normalizedProvider === 'openai' || normalizedProvider === 'open-ai') {
|
|
2981
|
+
return true;
|
|
2572
2982
|
}
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
type: 'error'
|
|
2576
|
-
};
|
|
2983
|
+
const normalizedModelId = selectedModelId.toLowerCase();
|
|
2984
|
+
return normalizedModelId.startsWith('openapi/') || normalizedModelId.startsWith('openai/');
|
|
2577
2985
|
};
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
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
|
-
};
|
|
2986
|
+
|
|
2987
|
+
/* eslint-disable @cspell/spellchecker */
|
|
2988
|
+
|
|
2989
|
+
const isOpenRouterModel = (selectedModelId, models) => {
|
|
2990
|
+
const selectedModel = models.find(model => model.id === selectedModelId);
|
|
2991
|
+
const normalizedProvider = selectedModel?.provider?.toLowerCase();
|
|
2992
|
+
if (normalizedProvider === 'openrouter' || normalizedProvider === 'open-router') {
|
|
2993
|
+
return true;
|
|
2605
2994
|
}
|
|
2995
|
+
return selectedModelId.toLowerCase().startsWith('openrouter/');
|
|
2606
2996
|
};
|
|
2607
|
-
|
|
2997
|
+
|
|
2998
|
+
const getAiResponse = async ({
|
|
2999
|
+
assetDir,
|
|
3000
|
+
messages,
|
|
3001
|
+
mockApiCommandId,
|
|
3002
|
+
models,
|
|
3003
|
+
nextMessageId,
|
|
3004
|
+
openApiApiBaseUrl,
|
|
3005
|
+
openApiApiKey,
|
|
3006
|
+
openRouterApiBaseUrl,
|
|
3007
|
+
openRouterApiKey,
|
|
3008
|
+
platform,
|
|
3009
|
+
selectedModelId,
|
|
3010
|
+
useMockApi,
|
|
3011
|
+
userText
|
|
3012
|
+
}) => {
|
|
2608
3013
|
let text = '';
|
|
2609
3014
|
const usesOpenApiModel = isOpenApiModel(selectedModelId, models);
|
|
2610
3015
|
const usesOpenRouterModel = isOpenRouterModel(selectedModelId, models);
|
|
2611
3016
|
if (usesOpenApiModel) {
|
|
2612
3017
|
if (openApiApiKey) {
|
|
2613
|
-
const result = await getOpenApiAssistantText(messages, getOpenApiModelId(selectedModelId), openApiApiKey, openApiApiBaseUrl);
|
|
3018
|
+
const result = await getOpenApiAssistantText(messages, getOpenApiModelId(selectedModelId), openApiApiKey, openApiApiBaseUrl, assetDir, platform);
|
|
2614
3019
|
if (result.type === 'success') {
|
|
2615
3020
|
const {
|
|
2616
3021
|
text: assistantText
|
|
@@ -2635,7 +3040,7 @@ const getAiResponse = async (userText, messages, nextMessageId, selectedModelId,
|
|
|
2635
3040
|
text = getOpenRouterErrorMessage(result);
|
|
2636
3041
|
}
|
|
2637
3042
|
} else if (openRouterApiKey) {
|
|
2638
|
-
const result = await getOpenRouterAssistantText(messages, modelId, openRouterApiKey, openRouterApiBaseUrl);
|
|
3043
|
+
const result = await getOpenRouterAssistantText(messages, modelId, openRouterApiKey, openRouterApiBaseUrl, assetDir, platform);
|
|
2639
3044
|
if (result.type === 'success') {
|
|
2640
3045
|
const {
|
|
2641
3046
|
text: assistantText
|
|
@@ -2706,7 +3111,21 @@ const handleClickSaveOpenApiApiKey = async state => {
|
|
|
2706
3111
|
return updatedState;
|
|
2707
3112
|
}
|
|
2708
3113
|
const retryMessages = session.messages.slice(0, -1);
|
|
2709
|
-
const assistantMessage = await getAiResponse(
|
|
3114
|
+
const assistantMessage = await getAiResponse({
|
|
3115
|
+
assetDir: updatedState.assetDir,
|
|
3116
|
+
messages: retryMessages,
|
|
3117
|
+
mockApiCommandId: updatedState.mockApiCommandId,
|
|
3118
|
+
models: updatedState.models,
|
|
3119
|
+
nextMessageId: updatedState.nextMessageId,
|
|
3120
|
+
openApiApiBaseUrl: updatedState.openApiApiBaseUrl,
|
|
3121
|
+
openApiApiKey: updatedState.openApiApiKey,
|
|
3122
|
+
openRouterApiBaseUrl: updatedState.openRouterApiBaseUrl,
|
|
3123
|
+
openRouterApiKey: updatedState.openRouterApiKey,
|
|
3124
|
+
platform: updatedState.platform,
|
|
3125
|
+
selectedModelId: updatedState.selectedModelId,
|
|
3126
|
+
useMockApi: updatedState.useMockApi,
|
|
3127
|
+
userText: previousUserMessage.text
|
|
3128
|
+
});
|
|
2710
3129
|
const updatedSession = {
|
|
2711
3130
|
...session,
|
|
2712
3131
|
messages: [...session.messages.slice(0, -1), assistantMessage]
|
|
@@ -2746,7 +3165,18 @@ const handleClickSaveOpenRouterApiKey = async state => {
|
|
|
2746
3165
|
if (!openRouterApiKey) {
|
|
2747
3166
|
return state;
|
|
2748
3167
|
}
|
|
2749
|
-
const
|
|
3168
|
+
const optimisticState = {
|
|
3169
|
+
...state,
|
|
3170
|
+
openRouterApiKeyState: 'saving'
|
|
3171
|
+
};
|
|
3172
|
+
set(state.uid, state, optimisticState);
|
|
3173
|
+
// @ts-ignore
|
|
3174
|
+
await invoke('Chat.rerender');
|
|
3175
|
+
const persistedState = await setOpenRouterApiKey(optimisticState, openRouterApiKey);
|
|
3176
|
+
const updatedState = {
|
|
3177
|
+
...persistedState,
|
|
3178
|
+
openRouterApiKeyState: 'idle'
|
|
3179
|
+
};
|
|
2750
3180
|
const session = updatedState.sessions.find(item => item.id === updatedState.selectedSessionId);
|
|
2751
3181
|
if (!session) {
|
|
2752
3182
|
return updatedState;
|
|
@@ -2761,9 +3191,21 @@ const handleClickSaveOpenRouterApiKey = async state => {
|
|
|
2761
3191
|
return updatedState;
|
|
2762
3192
|
}
|
|
2763
3193
|
const retryMessages = session.messages.slice(0, -1);
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
3194
|
+
const assistantMessage = await getAiResponse({
|
|
3195
|
+
assetDir: updatedState.assetDir,
|
|
3196
|
+
messages: retryMessages,
|
|
3197
|
+
mockApiCommandId: updatedState.mockApiCommandId,
|
|
3198
|
+
models: updatedState.models,
|
|
3199
|
+
nextMessageId: updatedState.nextMessageId,
|
|
3200
|
+
openApiApiBaseUrl: updatedState.openApiApiBaseUrl,
|
|
3201
|
+
openApiApiKey: updatedState.openApiApiKey,
|
|
3202
|
+
openRouterApiBaseUrl: updatedState.openRouterApiBaseUrl,
|
|
3203
|
+
openRouterApiKey,
|
|
3204
|
+
platform: updatedState.platform,
|
|
3205
|
+
selectedModelId: updatedState.selectedModelId,
|
|
3206
|
+
useMockApi: updatedState.useMockApi,
|
|
3207
|
+
userText: previousUserMessage.text
|
|
3208
|
+
});
|
|
2767
3209
|
const updatedSession = {
|
|
2768
3210
|
...session,
|
|
2769
3211
|
messages: [...session.messages.slice(0, -1), assistantMessage]
|
|
@@ -2778,6 +3220,7 @@ const handleClickSaveOpenRouterApiKey = async state => {
|
|
|
2778
3220
|
return {
|
|
2779
3221
|
...updatedState,
|
|
2780
3222
|
nextMessageId: updatedState.nextMessageId + 1,
|
|
3223
|
+
openRouterApiKeyState: 'idle',
|
|
2781
3224
|
sessions: updatedSessions
|
|
2782
3225
|
};
|
|
2783
3226
|
};
|
|
@@ -2845,6 +3288,7 @@ const handleSubmit = async state => {
|
|
|
2845
3288
|
await saveChatSession(newSession);
|
|
2846
3289
|
optimisticState = focusInput({
|
|
2847
3290
|
...state,
|
|
3291
|
+
composerHeight: getMinComposerHeightForState(state),
|
|
2848
3292
|
composerValue: '',
|
|
2849
3293
|
inputSource: 'script',
|
|
2850
3294
|
lastSubmittedSessionId: newSessionId,
|
|
@@ -2869,6 +3313,7 @@ const handleSubmit = async state => {
|
|
|
2869
3313
|
}
|
|
2870
3314
|
optimisticState = focusInput({
|
|
2871
3315
|
...state,
|
|
3316
|
+
composerHeight: getMinComposerHeightForState(state),
|
|
2872
3317
|
composerValue: '',
|
|
2873
3318
|
inputSource: 'script',
|
|
2874
3319
|
lastSubmittedSessionId: selectedSessionId,
|
|
@@ -2881,7 +3326,21 @@ const handleSubmit = async state => {
|
|
|
2881
3326
|
await invoke('Chat.rerender');
|
|
2882
3327
|
const selectedOptimisticSession = optimisticState.sessions.find(session => session.id === optimisticState.selectedSessionId);
|
|
2883
3328
|
const messages = selectedOptimisticSession?.messages ?? [];
|
|
2884
|
-
const assistantMessage = await getAiResponse(
|
|
3329
|
+
const assistantMessage = await getAiResponse({
|
|
3330
|
+
assetDir,
|
|
3331
|
+
messages,
|
|
3332
|
+
mockApiCommandId,
|
|
3333
|
+
models,
|
|
3334
|
+
nextMessageId: optimisticState.nextMessageId,
|
|
3335
|
+
openApiApiBaseUrl,
|
|
3336
|
+
openApiApiKey,
|
|
3337
|
+
openRouterApiBaseUrl,
|
|
3338
|
+
openRouterApiKey,
|
|
3339
|
+
platform,
|
|
3340
|
+
selectedModelId,
|
|
3341
|
+
useMockApi,
|
|
3342
|
+
userText
|
|
3343
|
+
});
|
|
2885
3344
|
const updatedSessions = optimisticState.sessions.map(session => {
|
|
2886
3345
|
if (session.id !== optimisticState.selectedSessionId) {
|
|
2887
3346
|
return session;
|
|
@@ -3086,8 +3545,10 @@ const handleInput = async (state, name, value, inputSource = 'user') => {
|
|
|
3086
3545
|
if (name !== Composer) {
|
|
3087
3546
|
return state;
|
|
3088
3547
|
}
|
|
3548
|
+
const composerHeight = await getComposerHeight(state, value);
|
|
3089
3549
|
return {
|
|
3090
3550
|
...state,
|
|
3551
|
+
composerHeight,
|
|
3091
3552
|
composerValue: value,
|
|
3092
3553
|
inputSource
|
|
3093
3554
|
};
|
|
@@ -3152,6 +3613,7 @@ const submitRename = async state => {
|
|
|
3152
3613
|
}
|
|
3153
3614
|
return {
|
|
3154
3615
|
...state,
|
|
3616
|
+
composerHeight: getMinComposerHeightForState(state),
|
|
3155
3617
|
composerValue: '',
|
|
3156
3618
|
inputSource: 'script',
|
|
3157
3619
|
renamingSessionId: '',
|
|
@@ -3549,7 +4011,7 @@ const getUsageOverviewDom = (tokensUsed, tokensMax) => {
|
|
|
3549
4011
|
}, text(usageLabel)];
|
|
3550
4012
|
};
|
|
3551
4013
|
|
|
3552
|
-
const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
|
|
4014
|
+
const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20) => {
|
|
3553
4015
|
const isSendDisabled = composerValue.trim() === '';
|
|
3554
4016
|
const modelOptions = models.flatMap(model => getModelOptionDOm(model, selectedModelId));
|
|
3555
4017
|
return [{
|
|
@@ -3567,7 +4029,7 @@ const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOvervie
|
|
|
3567
4029
|
onFocus: HandleFocus,
|
|
3568
4030
|
onInput: HandleInput,
|
|
3569
4031
|
placeholder: composePlaceholder(),
|
|
3570
|
-
|
|
4032
|
+
style: `height:${composerHeight}px;font-size:${composerFontSize}px;font-family:${composerFontFamily};line-height:${composerLineHeight}px;`,
|
|
3571
4033
|
type: TextArea,
|
|
3572
4034
|
value: composerValue
|
|
3573
4035
|
}, {
|
|
@@ -3662,7 +4124,9 @@ const getMissingApiKeyDom = ({
|
|
|
3662
4124
|
inputValue,
|
|
3663
4125
|
openSettingsButtonName,
|
|
3664
4126
|
placeholder,
|
|
3665
|
-
|
|
4127
|
+
saveButtonDisabled = false,
|
|
4128
|
+
saveButtonName,
|
|
4129
|
+
saveButtonText = save()
|
|
3666
4130
|
}) => {
|
|
3667
4131
|
return [{
|
|
3668
4132
|
childCount: 2,
|
|
@@ -3682,10 +4146,11 @@ const getMissingApiKeyDom = ({
|
|
|
3682
4146
|
}, {
|
|
3683
4147
|
childCount: 1,
|
|
3684
4148
|
className: mergeClassNames(Button, ButtonPrimary),
|
|
4149
|
+
disabled: saveButtonDisabled,
|
|
3685
4150
|
name: saveButtonName,
|
|
3686
4151
|
onClick: HandleClick,
|
|
3687
4152
|
type: Button$1
|
|
3688
|
-
}, text(
|
|
4153
|
+
}, text(saveButtonText), {
|
|
3689
4154
|
childCount: 1,
|
|
3690
4155
|
className: mergeClassNames(Button, ButtonSecondary),
|
|
3691
4156
|
name: openSettingsButtonName,
|
|
@@ -3705,14 +4170,17 @@ const getMissingOpenApiApiKeyDom = openApiApiKeyInput => {
|
|
|
3705
4170
|
});
|
|
3706
4171
|
};
|
|
3707
4172
|
|
|
3708
|
-
const getMissingOpenRouterApiKeyDom = openRouterApiKeyInput => {
|
|
4173
|
+
const getMissingOpenRouterApiKeyDom = (openRouterApiKeyInput, openRouterApiKeyState = 'idle') => {
|
|
4174
|
+
const isSaving = openRouterApiKeyState === 'saving';
|
|
3709
4175
|
return getMissingApiKeyDom({
|
|
3710
4176
|
getApiKeyText: getOpenRouterApiKey(),
|
|
3711
4177
|
inputName: OpenRouterApiKeyInput,
|
|
3712
4178
|
inputValue: openRouterApiKeyInput,
|
|
3713
4179
|
openSettingsButtonName: OpenOpenRouterApiKeySettings,
|
|
3714
4180
|
placeholder: openRouterApiKeyPlaceholder(),
|
|
3715
|
-
|
|
4181
|
+
saveButtonDisabled: isSaving,
|
|
4182
|
+
saveButtonName: SaveOpenRouterApiKey,
|
|
4183
|
+
saveButtonText: isSaving ? saving() : save()
|
|
3716
4184
|
});
|
|
3717
4185
|
};
|
|
3718
4186
|
|
|
@@ -3742,7 +4210,7 @@ const getOpenRouterTooManyRequestsDom = () => {
|
|
|
3742
4210
|
}, text(reason)];
|
|
3743
4211
|
})];
|
|
3744
4212
|
};
|
|
3745
|
-
const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput = '') => {
|
|
4213
|
+
const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput = '', openRouterApiKeyState = 'idle') => {
|
|
3746
4214
|
const roleClassName = message.role === 'user' ? MessageUser : MessageAssistant;
|
|
3747
4215
|
const isOpenApiApiKeyMissingMessage = message.role === 'assistant' && message.text === openApiApiKeyRequiredMessage;
|
|
3748
4216
|
const isOpenRouterApiKeyMissingMessage = message.role === 'assistant' && message.text === openRouterApiKeyRequiredMessage;
|
|
@@ -3761,7 +4229,7 @@ const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput =
|
|
|
3761
4229
|
childCount: 1,
|
|
3762
4230
|
className: Markdown,
|
|
3763
4231
|
type: P
|
|
3764
|
-
}, text(message.text), ...(isOpenApiApiKeyMissingMessage ? getMissingOpenApiApiKeyDom(openApiApiKeyInput) : []), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : []), ...(isOpenRouterTooManyRequestsMessage ? getOpenRouterTooManyRequestsDom() : [])];
|
|
4232
|
+
}, text(message.text), ...(isOpenApiApiKeyMissingMessage ? getMissingOpenApiApiKeyDom(openApiApiKeyInput) : []), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput, openRouterApiKeyState) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : []), ...(isOpenRouterTooManyRequestsMessage ? getOpenRouterTooManyRequestsDom() : [])];
|
|
3765
4233
|
};
|
|
3766
4234
|
|
|
3767
4235
|
const getEmptyMessagesDom = () => {
|
|
@@ -3772,7 +4240,7 @@ const getEmptyMessagesDom = () => {
|
|
|
3772
4240
|
}, text(startConversation())];
|
|
3773
4241
|
};
|
|
3774
4242
|
|
|
3775
|
-
const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = '') => {
|
|
4243
|
+
const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = '', openRouterApiKeyState = 'idle') => {
|
|
3776
4244
|
if (messages.length === 0) {
|
|
3777
4245
|
return getEmptyMessagesDom();
|
|
3778
4246
|
}
|
|
@@ -3780,10 +4248,10 @@ const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = ''
|
|
|
3780
4248
|
childCount: messages.length,
|
|
3781
4249
|
className: 'ChatMessages',
|
|
3782
4250
|
type: Div
|
|
3783
|
-
}, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput, openApiApiKeyInput))];
|
|
4251
|
+
}, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState))];
|
|
3784
4252
|
};
|
|
3785
4253
|
|
|
3786
|
-
const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
|
|
4254
|
+
const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState = 'idle', composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20) => {
|
|
3787
4255
|
const selectedSession = sessions.find(session => session.id === selectedSessionId);
|
|
3788
4256
|
const selectedSessionTitle = selectedSession?.title || chatTitle();
|
|
3789
4257
|
const messages = selectedSession ? selectedSession.messages : [];
|
|
@@ -3791,7 +4259,7 @@ const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue,
|
|
|
3791
4259
|
childCount: 3,
|
|
3792
4260
|
className: mergeClassNames(Viewlet, Chat),
|
|
3793
4261
|
type: Div
|
|
3794
|
-
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput, openApiApiKeyInput), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax)];
|
|
4262
|
+
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight)];
|
|
3795
4263
|
};
|
|
3796
4264
|
|
|
3797
4265
|
const getChatHeaderListModeDom = () => {
|
|
@@ -3860,12 +4328,12 @@ const getChatListDom = (sessions, selectedSessionId) => {
|
|
|
3860
4328
|
}, ...sessions.flatMap(getSessionDom)];
|
|
3861
4329
|
};
|
|
3862
4330
|
|
|
3863
|
-
const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
|
|
4331
|
+
const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20) => {
|
|
3864
4332
|
return [{
|
|
3865
4333
|
childCount: 3,
|
|
3866
4334
|
className: mergeClassNames(Viewlet, Chat),
|
|
3867
4335
|
type: Div
|
|
3868
|
-
}, ...getChatHeaderListModeDom(), ...getChatListDom(sessions), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax)];
|
|
4336
|
+
}, ...getChatHeaderListModeDom(), ...getChatListDom(sessions), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight)];
|
|
3869
4337
|
};
|
|
3870
4338
|
|
|
3871
4339
|
const getChatModeUnsupportedVirtualDom = () => {
|
|
@@ -3875,12 +4343,12 @@ const getChatModeUnsupportedVirtualDom = () => {
|
|
|
3875
4343
|
}, text(unknownViewMode())];
|
|
3876
4344
|
};
|
|
3877
4345
|
|
|
3878
|
-
const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput = '') => {
|
|
4346
|
+
const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput = '', openRouterApiKeyState = 'idle', composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20) => {
|
|
3879
4347
|
switch (viewMode) {
|
|
3880
4348
|
case 'detail':
|
|
3881
|
-
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
4349
|
+
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight);
|
|
3882
4350
|
case 'list':
|
|
3883
|
-
return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
4351
|
+
return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight);
|
|
3884
4352
|
default:
|
|
3885
4353
|
return getChatModeUnsupportedVirtualDom();
|
|
3886
4354
|
}
|
|
@@ -3888,11 +4356,16 @@ const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRoute
|
|
|
3888
4356
|
|
|
3889
4357
|
const renderItems = (oldState, newState) => {
|
|
3890
4358
|
const {
|
|
4359
|
+
composerFontFamily,
|
|
4360
|
+
composerFontSize,
|
|
4361
|
+
composerHeight,
|
|
4362
|
+
composerLineHeight,
|
|
3891
4363
|
composerValue,
|
|
3892
4364
|
initial,
|
|
3893
4365
|
models,
|
|
3894
4366
|
openApiApiKeyInput,
|
|
3895
4367
|
openRouterApiKeyInput,
|
|
4368
|
+
openRouterApiKeyState,
|
|
3896
4369
|
selectedModelId,
|
|
3897
4370
|
selectedSessionId,
|
|
3898
4371
|
sessions,
|
|
@@ -3905,7 +4378,7 @@ const renderItems = (oldState, newState) => {
|
|
|
3905
4378
|
if (initial) {
|
|
3906
4379
|
return [SetDom2, uid, []];
|
|
3907
4380
|
}
|
|
3908
|
-
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput);
|
|
4381
|
+
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight);
|
|
3909
4382
|
return [SetDom2, uid, dom];
|
|
3910
4383
|
};
|
|
3911
4384
|
|
|
@@ -4016,6 +4489,7 @@ const reset = async state => {
|
|
|
4016
4489
|
await clearChatSessions();
|
|
4017
4490
|
return {
|
|
4018
4491
|
...state,
|
|
4492
|
+
composerHeight: getMinComposerHeightForState(state),
|
|
4019
4493
|
composerValue: '',
|
|
4020
4494
|
openRouterApiKey: '',
|
|
4021
4495
|
selectedSessionId: '',
|