@lvce-editor/chat-view 1.13.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 +1153 -261
- 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,9 +1049,15 @@ 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
|
};
|
|
1058
|
+
const openExternal = async uri => {
|
|
1059
|
+
await invoke('Open.openExternal', uri);
|
|
1060
|
+
};
|
|
1054
1061
|
|
|
1055
1062
|
const toCommandId = key => {
|
|
1056
1063
|
const dotIndex = key.indexOf('.');
|
|
@@ -1191,25 +1198,128 @@ const i18nString = (key, placeholders = emptyObject) => {
|
|
|
1191
1198
|
return key.replaceAll(RE_PLACEHOLDER, replacer);
|
|
1192
1199
|
};
|
|
1193
1200
|
|
|
1194
|
-
const chatTitle =
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
const
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
const
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1211
|
-
|
|
1201
|
+
const chatTitle = () => {
|
|
1202
|
+
return i18nString('Chat');
|
|
1203
|
+
};
|
|
1204
|
+
const chats = () => {
|
|
1205
|
+
return i18nString('Chats');
|
|
1206
|
+
};
|
|
1207
|
+
const newChat = () => {
|
|
1208
|
+
return i18nString('New Chat');
|
|
1209
|
+
};
|
|
1210
|
+
const backToChats = () => {
|
|
1211
|
+
return i18nString('Back to chats');
|
|
1212
|
+
};
|
|
1213
|
+
const settings = () => {
|
|
1214
|
+
return i18nString('Settings');
|
|
1215
|
+
};
|
|
1216
|
+
const closeChat = () => {
|
|
1217
|
+
return i18nString('Close Chat');
|
|
1218
|
+
};
|
|
1219
|
+
const clickToOpenNewChat = () => {
|
|
1220
|
+
return i18nString('Click the + button to open a new chat.');
|
|
1221
|
+
};
|
|
1222
|
+
const startConversation = () => {
|
|
1223
|
+
return i18nString('Start a conversation by typing below.');
|
|
1224
|
+
};
|
|
1225
|
+
const composePlaceholder = () => {
|
|
1226
|
+
return i18nString('Type your message. Enter to send, Shift+Enter for newline.');
|
|
1227
|
+
};
|
|
1228
|
+
const openRouterApiKeyPlaceholder = () => {
|
|
1229
|
+
return i18nString('Enter OpenRouter API key');
|
|
1230
|
+
};
|
|
1231
|
+
const openApiApiKeyPlaceholder = () => {
|
|
1232
|
+
return i18nString('Enter OpenAI API key');
|
|
1233
|
+
};
|
|
1234
|
+
const sendMessage = () => {
|
|
1235
|
+
return i18nString('Send message');
|
|
1236
|
+
};
|
|
1237
|
+
const save = () => {
|
|
1238
|
+
return i18nString('Save');
|
|
1239
|
+
};
|
|
1240
|
+
const getOpenRouterApiKey = () => {
|
|
1241
|
+
return i18nString('Get API Key');
|
|
1242
|
+
};
|
|
1243
|
+
const getOpenApiApiKey = () => {
|
|
1244
|
+
return i18nString('Get API Key');
|
|
1245
|
+
};
|
|
1246
|
+
const deleteChatSession$1 = () => {
|
|
1247
|
+
return i18nString('Delete chat session');
|
|
1248
|
+
};
|
|
1249
|
+
const defaultSessionTitle = () => {
|
|
1250
|
+
return i18nString('Chat 1');
|
|
1251
|
+
};
|
|
1252
|
+
const dummyChatA = () => {
|
|
1253
|
+
return i18nString('Dummy Chat A');
|
|
1254
|
+
};
|
|
1255
|
+
const dummyChatB = () => {
|
|
1256
|
+
return i18nString('Dummy Chat B');
|
|
1257
|
+
};
|
|
1258
|
+
const dummyChatC = () => {
|
|
1259
|
+
return i18nString('Dummy Chat C');
|
|
1260
|
+
};
|
|
1261
|
+
const unknownViewMode = () => {
|
|
1262
|
+
return i18nString('Unknown view mode');
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
/* eslint-disable @cspell/spellchecker */
|
|
1212
1266
|
|
|
1267
|
+
const getDefaultModels = () => {
|
|
1268
|
+
const defaultModelId = 'test';
|
|
1269
|
+
return [{
|
|
1270
|
+
id: defaultModelId,
|
|
1271
|
+
name: 'test',
|
|
1272
|
+
provider: 'test'
|
|
1273
|
+
}, {
|
|
1274
|
+
id: 'codex-5.3',
|
|
1275
|
+
name: 'Codex 5.3',
|
|
1276
|
+
provider: 'openRouter'
|
|
1277
|
+
}, {
|
|
1278
|
+
id: 'claude-code',
|
|
1279
|
+
name: 'Claude Code',
|
|
1280
|
+
provider: 'openRouter'
|
|
1281
|
+
}, {
|
|
1282
|
+
id: 'claude-haiku',
|
|
1283
|
+
name: 'Claude Haiku',
|
|
1284
|
+
provider: 'openRouter'
|
|
1285
|
+
}, {
|
|
1286
|
+
id: 'openRouter/openai/gpt-4o-mini',
|
|
1287
|
+
name: 'GPT-4o Mini',
|
|
1288
|
+
provider: 'openRouter'
|
|
1289
|
+
}, {
|
|
1290
|
+
id: 'openRouter/anthropic/claude-3.5-haiku',
|
|
1291
|
+
name: 'Claude 3.5 Haiku',
|
|
1292
|
+
provider: 'openRouter'
|
|
1293
|
+
}, {
|
|
1294
|
+
id: 'openRouter/google/gemini-2.0-flash-001',
|
|
1295
|
+
name: 'Gemini 2.0 Flash',
|
|
1296
|
+
provider: 'openRouter'
|
|
1297
|
+
}, {
|
|
1298
|
+
id: 'openRouter/openai/gpt-oss-20b:free',
|
|
1299
|
+
name: 'GPT OSS 20B (Free)',
|
|
1300
|
+
provider: 'openRouter'
|
|
1301
|
+
}, {
|
|
1302
|
+
id: 'openRouter/openai/gpt-oss-120b:free',
|
|
1303
|
+
name: 'GPT OSS 120B (Free)',
|
|
1304
|
+
provider: 'openRouter'
|
|
1305
|
+
}, {
|
|
1306
|
+
id: 'openRouter/meta-llama/llama-3.3-70b-instruct:free',
|
|
1307
|
+
name: 'Llama 3.3 70B Instruct (Free)',
|
|
1308
|
+
provider: 'openRouter'
|
|
1309
|
+
}, {
|
|
1310
|
+
id: 'openRouter/google/gemma-3-27b-it:free',
|
|
1311
|
+
name: 'Gemma 3 27B IT (Free)',
|
|
1312
|
+
provider: 'openRouter'
|
|
1313
|
+
}, {
|
|
1314
|
+
id: 'openRouter/qwen/qwen3-coder:free',
|
|
1315
|
+
name: 'Qwen3 Coder (Free)',
|
|
1316
|
+
provider: 'openRouter'
|
|
1317
|
+
}, {
|
|
1318
|
+
id: 'openRouter/mistralai/mistral-small-3.1-24b-instruct:free',
|
|
1319
|
+
name: 'Mistral Small 3.1 24B Instruct (Free)',
|
|
1320
|
+
provider: 'openRouter'
|
|
1321
|
+
}];
|
|
1322
|
+
};
|
|
1213
1323
|
const createDefaultState = () => {
|
|
1214
1324
|
const defaultSessionId = 'session-1';
|
|
1215
1325
|
const defaultModelId = 'test';
|
|
@@ -1225,38 +1335,17 @@ const createDefaultState = () => {
|
|
|
1225
1335
|
inputSource: 'script',
|
|
1226
1336
|
lastSubmittedSessionId: '',
|
|
1227
1337
|
listItemHeight: 40,
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
name: 'test',
|
|
1231
|
-
provider: 'test'
|
|
1232
|
-
}, {
|
|
1233
|
-
id: 'codex-5.3',
|
|
1234
|
-
name: 'Codex 5.3',
|
|
1235
|
-
provider: 'openRouter'
|
|
1236
|
-
}, {
|
|
1237
|
-
id: 'claude-code',
|
|
1238
|
-
name: 'Claude Code',
|
|
1239
|
-
provider: 'openRouter'
|
|
1240
|
-
}, {
|
|
1241
|
-
id: 'claude-haiku',
|
|
1242
|
-
name: 'Claude Haiku',
|
|
1243
|
-
provider: 'openRouter'
|
|
1244
|
-
}, {
|
|
1245
|
-
id: 'openRouter/openai/gpt-4o-mini',
|
|
1246
|
-
name: 'GPT-4o Mini',
|
|
1247
|
-
provider: 'openRouter'
|
|
1248
|
-
}, {
|
|
1249
|
-
id: 'openRouter/anthropic/claude-3.5-haiku',
|
|
1250
|
-
name: 'Claude 3.5 Haiku',
|
|
1251
|
-
provider: 'openRouter'
|
|
1252
|
-
}, {
|
|
1253
|
-
id: 'openRouter/google/gemini-2.0-flash-001',
|
|
1254
|
-
name: 'Gemini 2.0 Flash',
|
|
1255
|
-
provider: 'openRouter'
|
|
1256
|
-
}],
|
|
1338
|
+
mockApiCommandId: '',
|
|
1339
|
+
models: getDefaultModels(),
|
|
1257
1340
|
nextMessageId: 1,
|
|
1341
|
+
openApiApiBaseUrl: 'https://api.openai.com/v1',
|
|
1342
|
+
openApiApiKey: '',
|
|
1343
|
+
openApiApiKeyInput: '',
|
|
1344
|
+
openApiApiKeysSettingsUrl: 'https://platform.openai.com/api-keys',
|
|
1258
1345
|
openRouterApiBaseUrl: 'https://openrouter.ai/api/v1',
|
|
1259
1346
|
openRouterApiKey: '',
|
|
1347
|
+
openRouterApiKeyInput: '',
|
|
1348
|
+
openRouterApiKeysSettingsUrl: 'https://openrouter.ai/settings/keys',
|
|
1260
1349
|
platform: 0,
|
|
1261
1350
|
renamingSessionId: '',
|
|
1262
1351
|
selectedModelId: defaultModelId,
|
|
@@ -1264,12 +1353,13 @@ const createDefaultState = () => {
|
|
|
1264
1353
|
sessions: [{
|
|
1265
1354
|
id: defaultSessionId,
|
|
1266
1355
|
messages: [],
|
|
1267
|
-
title: defaultSessionTitle
|
|
1356
|
+
title: defaultSessionTitle()
|
|
1268
1357
|
}],
|
|
1269
1358
|
tokensMax: 0,
|
|
1270
1359
|
tokensUsed: 0,
|
|
1271
1360
|
uid: 0,
|
|
1272
1361
|
usageOverviewEnabled: false,
|
|
1362
|
+
useMockApi: false,
|
|
1273
1363
|
viewMode: 'list',
|
|
1274
1364
|
warningCount: 0,
|
|
1275
1365
|
width: 0,
|
|
@@ -1369,12 +1459,15 @@ const SetPatches = 'Viewlet.setPatches';
|
|
|
1369
1459
|
|
|
1370
1460
|
const FocusChatInput = 8000;
|
|
1371
1461
|
|
|
1372
|
-
const Button$
|
|
1462
|
+
const Button$2 = 'button';
|
|
1373
1463
|
|
|
1374
|
-
const Button = 1;
|
|
1464
|
+
const Button$1 = 1;
|
|
1375
1465
|
const Div = 4;
|
|
1466
|
+
const Input = 6;
|
|
1376
1467
|
const Span = 8;
|
|
1377
1468
|
const Text = 12;
|
|
1469
|
+
const Li = 48;
|
|
1470
|
+
const Ol = 49;
|
|
1378
1471
|
const P = 50;
|
|
1379
1472
|
const TextArea = 62;
|
|
1380
1473
|
const Select$1 = 63;
|
|
@@ -1697,6 +1790,10 @@ const getKeyBindings = () => {
|
|
|
1697
1790
|
}];
|
|
1698
1791
|
};
|
|
1699
1792
|
|
|
1793
|
+
const getSelectedSessionId = state => {
|
|
1794
|
+
return state.selectedSessionId;
|
|
1795
|
+
};
|
|
1796
|
+
|
|
1700
1797
|
const getListIndex = (state, eventX, eventY) => {
|
|
1701
1798
|
const {
|
|
1702
1799
|
headerHeight,
|
|
@@ -1734,96 +1831,143 @@ const handleChatListContextMenu = async (state, eventX, eventY) => {
|
|
|
1734
1831
|
return state;
|
|
1735
1832
|
};
|
|
1736
1833
|
|
|
1737
|
-
const LVCE_CHAT_SESSIONS_DB_NAME = 'lvce-chat-view-sessions';
|
|
1738
|
-
const LVCE_CHAT_SESSIONS_DB_VERSION = 1;
|
|
1739
|
-
const LVCE_CHAT_SESSIONS_STORE = 'chat-sessions';
|
|
1740
1834
|
const toError = error => {
|
|
1741
1835
|
if (error instanceof Error) {
|
|
1742
1836
|
return error;
|
|
1743
1837
|
}
|
|
1744
1838
|
return new Error('IndexedDB request failed');
|
|
1745
1839
|
};
|
|
1840
|
+
|
|
1746
1841
|
const requestToPromise = async createRequest => {
|
|
1747
1842
|
const request = createRequest();
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1843
|
+
const {
|
|
1844
|
+
promise,
|
|
1845
|
+
reject,
|
|
1846
|
+
resolve
|
|
1847
|
+
} = Promise.withResolvers();
|
|
1848
|
+
request.addEventListener('success', () => {
|
|
1849
|
+
resolve(request.result);
|
|
1755
1850
|
});
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
const transaction = createTransaction();
|
|
1759
|
-
return new Promise((resolve, reject) => {
|
|
1760
|
-
transaction.addEventListener('complete', () => {
|
|
1761
|
-
resolve();
|
|
1762
|
-
});
|
|
1763
|
-
transaction.addEventListener('error', () => {
|
|
1764
|
-
reject(toError(transaction.error));
|
|
1765
|
-
});
|
|
1766
|
-
transaction.addEventListener('abort', () => {
|
|
1767
|
-
reject(toError(transaction.error));
|
|
1768
|
-
});
|
|
1851
|
+
request.addEventListener('error', () => {
|
|
1852
|
+
reject(toError(request.error));
|
|
1769
1853
|
});
|
|
1854
|
+
return promise;
|
|
1770
1855
|
};
|
|
1771
|
-
|
|
1772
|
-
|
|
1856
|
+
|
|
1857
|
+
const openSessionsDatabase = async (databaseName, databaseVersion, storeName) => {
|
|
1858
|
+
const request = indexedDB.open(databaseName, databaseVersion);
|
|
1773
1859
|
request.addEventListener('upgradeneeded', () => {
|
|
1774
1860
|
const database = request.result;
|
|
1775
|
-
if (!database.objectStoreNames.contains(
|
|
1776
|
-
database.createObjectStore(
|
|
1861
|
+
if (!database.objectStoreNames.contains(storeName)) {
|
|
1862
|
+
database.createObjectStore(storeName, {
|
|
1777
1863
|
keyPath: 'id'
|
|
1778
1864
|
});
|
|
1779
1865
|
}
|
|
1780
1866
|
});
|
|
1781
1867
|
return requestToPromise(() => request);
|
|
1782
1868
|
};
|
|
1869
|
+
|
|
1870
|
+
const getDatabase = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName) => {
|
|
1871
|
+
const existingDatabasePromise = getDatabasePromise();
|
|
1872
|
+
if (existingDatabasePromise) {
|
|
1873
|
+
return existingDatabasePromise;
|
|
1874
|
+
}
|
|
1875
|
+
const nextDatabasePromise = openSessionsDatabase(databaseName, databaseVersion, storeName);
|
|
1876
|
+
setDatabasePromise(nextDatabasePromise);
|
|
1877
|
+
return nextDatabasePromise;
|
|
1878
|
+
};
|
|
1879
|
+
|
|
1880
|
+
const transactionToPromise = async createTransaction => {
|
|
1881
|
+
const transaction = createTransaction();
|
|
1882
|
+
const {
|
|
1883
|
+
promise,
|
|
1884
|
+
reject,
|
|
1885
|
+
resolve
|
|
1886
|
+
} = Promise.withResolvers();
|
|
1887
|
+
transaction.addEventListener('complete', () => {
|
|
1888
|
+
resolve();
|
|
1889
|
+
});
|
|
1890
|
+
transaction.addEventListener('error', () => {
|
|
1891
|
+
reject(toError(transaction.error));
|
|
1892
|
+
});
|
|
1893
|
+
transaction.addEventListener('abort', () => {
|
|
1894
|
+
reject(toError(transaction.error));
|
|
1895
|
+
});
|
|
1896
|
+
return promise;
|
|
1897
|
+
};
|
|
1898
|
+
|
|
1899
|
+
const clear = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName) => {
|
|
1900
|
+
const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
|
|
1901
|
+
const transaction = database.transaction(storeName, 'readwrite');
|
|
1902
|
+
const createTransaction = () => transaction;
|
|
1903
|
+
const store = transaction.objectStore(storeName);
|
|
1904
|
+
store.clear();
|
|
1905
|
+
await transactionToPromise(createTransaction);
|
|
1906
|
+
};
|
|
1907
|
+
|
|
1908
|
+
const deleteSession$1 = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName, id) => {
|
|
1909
|
+
const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
|
|
1910
|
+
const transaction = database.transaction(storeName, 'readwrite');
|
|
1911
|
+
const createTransaction = () => transaction;
|
|
1912
|
+
const store = transaction.objectStore(storeName);
|
|
1913
|
+
store.delete(id);
|
|
1914
|
+
await transactionToPromise(createTransaction);
|
|
1915
|
+
};
|
|
1916
|
+
|
|
1917
|
+
const getSession = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName, id) => {
|
|
1918
|
+
const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
|
|
1919
|
+
const transaction = database.transaction(storeName, 'readonly');
|
|
1920
|
+
const store = transaction.objectStore(storeName);
|
|
1921
|
+
const result = await requestToPromise(() => store.get(id));
|
|
1922
|
+
return result;
|
|
1923
|
+
};
|
|
1924
|
+
|
|
1925
|
+
const listSessions = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName) => {
|
|
1926
|
+
const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
|
|
1927
|
+
const transaction = database.transaction(storeName, 'readonly');
|
|
1928
|
+
const store = transaction.objectStore(storeName);
|
|
1929
|
+
const result = await requestToPromise(() => store.getAll());
|
|
1930
|
+
return result;
|
|
1931
|
+
};
|
|
1932
|
+
|
|
1933
|
+
const setSession = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName, session) => {
|
|
1934
|
+
const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
|
|
1935
|
+
const transaction = database.transaction(storeName, 'readwrite');
|
|
1936
|
+
const createTransaction = () => transaction;
|
|
1937
|
+
const store = transaction.objectStore(storeName);
|
|
1938
|
+
store.put(session);
|
|
1939
|
+
await transactionToPromise(createTransaction);
|
|
1940
|
+
};
|
|
1941
|
+
|
|
1783
1942
|
class IndexedDbChatSessionStorage {
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1943
|
+
constructor(options = {}) {
|
|
1944
|
+
this.state = {
|
|
1945
|
+
databaseName: options.databaseName || 'lvce-chat-view-sessions',
|
|
1946
|
+
databasePromise: undefined,
|
|
1947
|
+
databaseVersion: options.databaseVersion || 1,
|
|
1948
|
+
storeName: options.storeName || 'chat-sessions'
|
|
1949
|
+
};
|
|
1789
1950
|
}
|
|
1951
|
+
getDatabasePromise = () => {
|
|
1952
|
+
return this.state.databasePromise;
|
|
1953
|
+
};
|
|
1954
|
+
setDatabasePromise = databasePromise => {
|
|
1955
|
+
this.state.databasePromise = databasePromise;
|
|
1956
|
+
};
|
|
1790
1957
|
async clear() {
|
|
1791
|
-
|
|
1792
|
-
const transaction = database.transaction(LVCE_CHAT_SESSIONS_STORE, 'readwrite');
|
|
1793
|
-
const createTransaction = () => transaction;
|
|
1794
|
-
const store = transaction.objectStore(LVCE_CHAT_SESSIONS_STORE);
|
|
1795
|
-
store.clear();
|
|
1796
|
-
await transactionToPromise(createTransaction);
|
|
1958
|
+
return clear(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName);
|
|
1797
1959
|
}
|
|
1798
1960
|
async deleteSession(id) {
|
|
1799
|
-
|
|
1800
|
-
const transaction = database.transaction(LVCE_CHAT_SESSIONS_STORE, 'readwrite');
|
|
1801
|
-
const createTransaction = () => transaction;
|
|
1802
|
-
const store = transaction.objectStore(LVCE_CHAT_SESSIONS_STORE);
|
|
1803
|
-
store.delete(id);
|
|
1804
|
-
await transactionToPromise(createTransaction);
|
|
1961
|
+
return deleteSession$1(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName, id);
|
|
1805
1962
|
}
|
|
1806
1963
|
async getSession(id) {
|
|
1807
|
-
|
|
1808
|
-
const transaction = database.transaction(LVCE_CHAT_SESSIONS_STORE, 'readonly');
|
|
1809
|
-
const store = transaction.objectStore(LVCE_CHAT_SESSIONS_STORE);
|
|
1810
|
-
const result = await requestToPromise(() => store.get(id));
|
|
1811
|
-
return result;
|
|
1964
|
+
return getSession(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName, id);
|
|
1812
1965
|
}
|
|
1813
1966
|
async listSessions() {
|
|
1814
|
-
|
|
1815
|
-
const transaction = database.transaction(LVCE_CHAT_SESSIONS_STORE, 'readonly');
|
|
1816
|
-
const store = transaction.objectStore(LVCE_CHAT_SESSIONS_STORE);
|
|
1817
|
-
const result = await requestToPromise(() => store.getAll());
|
|
1818
|
-
return result;
|
|
1967
|
+
return listSessions(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName);
|
|
1819
1968
|
}
|
|
1820
1969
|
async setSession(session) {
|
|
1821
|
-
|
|
1822
|
-
const transaction = database.transaction(LVCE_CHAT_SESSIONS_STORE, 'readwrite');
|
|
1823
|
-
const createTransaction = () => transaction;
|
|
1824
|
-
const store = transaction.objectStore(LVCE_CHAT_SESSIONS_STORE);
|
|
1825
|
-
store.put(session);
|
|
1826
|
-
await transactionToPromise(createTransaction);
|
|
1970
|
+
return setSession(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName, session);
|
|
1827
1971
|
}
|
|
1828
1972
|
}
|
|
1829
1973
|
|
|
@@ -1956,14 +2100,45 @@ const deleteSession = async (state, id) => {
|
|
|
1956
2100
|
};
|
|
1957
2101
|
};
|
|
1958
2102
|
|
|
1959
|
-
const
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
focus: 'composer',
|
|
1963
|
-
focused: true
|
|
1964
|
-
};
|
|
2103
|
+
const handleClickOpenApiApiKeySettings = async state => {
|
|
2104
|
+
await openExternal(state.openApiApiKeysSettingsUrl);
|
|
2105
|
+
return state;
|
|
1965
2106
|
};
|
|
1966
2107
|
|
|
2108
|
+
const handleClickOpenRouterApiKeySettings = async state => {
|
|
2109
|
+
await openExternal(state.openRouterApiKeysSettingsUrl);
|
|
2110
|
+
return state;
|
|
2111
|
+
};
|
|
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.';
|
|
2115
|
+
const openRouterApiKeyRequiredMessage = 'OpenRouter API key is not configured. Enter your OpenRouter API key below and click Save.';
|
|
2116
|
+
const openRouterRequestFailedMessage = 'OpenRouter request failed. Possible reasons:';
|
|
2117
|
+
const openRouterTooManyRequestsMessage = 'OpenRouter rate limit reached (429). Please try again soon. Helpful tips:';
|
|
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';
|
|
2141
|
+
|
|
1967
2142
|
const delay = async ms => {
|
|
1968
2143
|
await new Promise(resolve => setTimeout(resolve, ms));
|
|
1969
2144
|
};
|
|
@@ -1973,6 +2148,10 @@ const getMockAiResponse = async userMessage => {
|
|
|
1973
2148
|
return `Mock AI response: I received "${userMessage}".`;
|
|
1974
2149
|
};
|
|
1975
2150
|
|
|
2151
|
+
const getOpenApiApiEndpoint = openApiApiBaseUrl => {
|
|
2152
|
+
return `${openApiApiBaseUrl}/chat/completions`;
|
|
2153
|
+
};
|
|
2154
|
+
|
|
1976
2155
|
const getTextContent = content => {
|
|
1977
2156
|
if (typeof content === 'string') {
|
|
1978
2157
|
return content;
|
|
@@ -1994,75 +2173,482 @@ const getTextContent = content => {
|
|
|
1994
2173
|
return textParts.join('\n');
|
|
1995
2174
|
};
|
|
1996
2175
|
|
|
1997
|
-
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
|
+
|
|
1998
2262
|
const getOpenRouterApiEndpoint = openRouterApiBaseUrl => {
|
|
1999
|
-
const trimmedBaseUrl =
|
|
2263
|
+
const trimmedBaseUrl = openRouterApiBaseUrl.replace(/\/+$/, '');
|
|
2000
2264
|
return `${trimmedBaseUrl}/chat/completions`;
|
|
2001
2265
|
};
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
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
|
+
}
|
|
2017
2308
|
if (!response.ok) {
|
|
2018
|
-
|
|
2309
|
+
return undefined;
|
|
2310
|
+
}
|
|
2311
|
+
let parsed;
|
|
2312
|
+
try {
|
|
2313
|
+
parsed = await response.json();
|
|
2314
|
+
} catch {
|
|
2315
|
+
return undefined;
|
|
2019
2316
|
}
|
|
2020
|
-
const parsed = await response.json();
|
|
2021
2317
|
if (!parsed || typeof parsed !== 'object') {
|
|
2022
|
-
return
|
|
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) => {
|
|
2341
|
+
let response;
|
|
2342
|
+
try {
|
|
2343
|
+
response = await fetch(getOpenRouterApiEndpoint(openRouterApiBaseUrl), {
|
|
2344
|
+
body: JSON.stringify({
|
|
2345
|
+
messages: messages.map(message => ({
|
|
2346
|
+
content: message.text,
|
|
2347
|
+
role: message.role
|
|
2348
|
+
})),
|
|
2349
|
+
model: modelId
|
|
2350
|
+
}),
|
|
2351
|
+
headers: {
|
|
2352
|
+
Authorization: `Bearer ${openRouterApiKey}`,
|
|
2353
|
+
'Content-Type': 'application/json'
|
|
2354
|
+
},
|
|
2355
|
+
method: 'POST'
|
|
2356
|
+
});
|
|
2357
|
+
} catch {
|
|
2358
|
+
return {
|
|
2359
|
+
details: 'request-failed',
|
|
2360
|
+
type: 'error'
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
if (!response.ok) {
|
|
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
|
+
};
|
|
2393
|
+
}
|
|
2394
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2395
|
+
return {
|
|
2396
|
+
text: '',
|
|
2397
|
+
type: 'success'
|
|
2398
|
+
};
|
|
2023
2399
|
}
|
|
2024
2400
|
const choices = Reflect.get(parsed, 'choices');
|
|
2025
2401
|
if (!Array.isArray(choices)) {
|
|
2026
|
-
return
|
|
2402
|
+
return {
|
|
2403
|
+
text: '',
|
|
2404
|
+
type: 'success'
|
|
2405
|
+
};
|
|
2027
2406
|
}
|
|
2028
2407
|
const firstChoice = choices[0];
|
|
2029
2408
|
if (!firstChoice || typeof firstChoice !== 'object') {
|
|
2030
|
-
return
|
|
2409
|
+
return {
|
|
2410
|
+
text: '',
|
|
2411
|
+
type: 'success'
|
|
2412
|
+
};
|
|
2031
2413
|
}
|
|
2032
2414
|
const message = Reflect.get(firstChoice, 'message');
|
|
2033
2415
|
if (!message || typeof message !== 'object') {
|
|
2034
|
-
return
|
|
2416
|
+
return {
|
|
2417
|
+
text: '',
|
|
2418
|
+
type: 'success'
|
|
2419
|
+
};
|
|
2035
2420
|
}
|
|
2036
2421
|
const content = Reflect.get(message, 'content');
|
|
2037
|
-
return
|
|
2422
|
+
return {
|
|
2423
|
+
text: getTextContent(content),
|
|
2424
|
+
type: 'success'
|
|
2425
|
+
};
|
|
2038
2426
|
};
|
|
2039
2427
|
|
|
2428
|
+
/* eslint-disable @cspell/spellchecker */
|
|
2040
2429
|
const getOpenRouterModelId = selectedModelId => {
|
|
2041
|
-
|
|
2042
|
-
|
|
2430
|
+
const openRouterPrefix = 'openrouter/';
|
|
2431
|
+
if (selectedModelId.toLowerCase().startsWith(openRouterPrefix)) {
|
|
2432
|
+
return selectedModelId.slice(openRouterPrefix.length);
|
|
2043
2433
|
}
|
|
2044
2434
|
return selectedModelId;
|
|
2045
2435
|
};
|
|
2046
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
|
+
|
|
2047
2449
|
const isOpenRouterModel = (selectedModelId, models) => {
|
|
2048
2450
|
const selectedModel = models.find(model => model.id === selectedModelId);
|
|
2049
|
-
|
|
2451
|
+
const normalizedProvider = selectedModel?.provider?.toLowerCase();
|
|
2452
|
+
if (normalizedProvider === 'openrouter' || normalizedProvider === 'open-router') {
|
|
2050
2453
|
return true;
|
|
2051
2454
|
}
|
|
2052
|
-
return selectedModelId.startsWith('
|
|
2455
|
+
return selectedModelId.toLowerCase().startsWith('openrouter/');
|
|
2053
2456
|
};
|
|
2054
2457
|
|
|
2055
|
-
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) => {
|
|
2056
2608
|
let text = '';
|
|
2057
|
-
const
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2609
|
+
const usesOpenApiModel = isOpenApiModel(selectedModelId, models);
|
|
2610
|
+
const usesOpenRouterModel = isOpenRouterModel(selectedModelId, models);
|
|
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);
|
|
2646
|
+
}
|
|
2647
|
+
} else {
|
|
2648
|
+
text = openRouterApiKeyRequiredMessage;
|
|
2063
2649
|
}
|
|
2064
2650
|
}
|
|
2065
|
-
if (!text) {
|
|
2651
|
+
if (!text && !usesOpenApiModel && !usesOpenRouterModel) {
|
|
2066
2652
|
text = await getMockAiResponse(userText);
|
|
2067
2653
|
}
|
|
2068
2654
|
const assistantTime = new Date().toLocaleTimeString([], {
|
|
@@ -2077,16 +2663,149 @@ const getAiResponse = async (userText, nextMessageId, selectedModelId, models, o
|
|
|
2077
2663
|
};
|
|
2078
2664
|
};
|
|
2079
2665
|
|
|
2666
|
+
const get = async key => {
|
|
2667
|
+
return getPreference(key);
|
|
2668
|
+
};
|
|
2669
|
+
const update = async settings => {
|
|
2670
|
+
await invoke('Preferences.update', settings);
|
|
2671
|
+
};
|
|
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
|
+
|
|
2728
|
+
const setOpenRouterApiKey = async (state, openRouterApiKey, persist = true) => {
|
|
2729
|
+
if (persist) {
|
|
2730
|
+
await update({
|
|
2731
|
+
'secrets.openRouterApiKey': openRouterApiKey
|
|
2732
|
+
});
|
|
2733
|
+
}
|
|
2734
|
+
return {
|
|
2735
|
+
...state,
|
|
2736
|
+
openRouterApiKey,
|
|
2737
|
+
openRouterApiKeyInput: openRouterApiKey
|
|
2738
|
+
};
|
|
2739
|
+
};
|
|
2740
|
+
|
|
2741
|
+
const handleClickSaveOpenRouterApiKey = async state => {
|
|
2742
|
+
const {
|
|
2743
|
+
openRouterApiKeyInput
|
|
2744
|
+
} = state;
|
|
2745
|
+
const openRouterApiKey = openRouterApiKeyInput.trim();
|
|
2746
|
+
if (!openRouterApiKey) {
|
|
2747
|
+
return state;
|
|
2748
|
+
}
|
|
2749
|
+
const updatedState = await setOpenRouterApiKey(state, openRouterApiKey);
|
|
2750
|
+
const session = updatedState.sessions.find(item => item.id === updatedState.selectedSessionId);
|
|
2751
|
+
if (!session) {
|
|
2752
|
+
return updatedState;
|
|
2753
|
+
}
|
|
2754
|
+
const lastMessage = session.messages.at(-1);
|
|
2755
|
+
const shouldRetryOpenRouter = lastMessage?.role === 'assistant' && lastMessage.text === openRouterApiKeyRequiredMessage;
|
|
2756
|
+
if (!shouldRetryOpenRouter) {
|
|
2757
|
+
return updatedState;
|
|
2758
|
+
}
|
|
2759
|
+
const previousUserMessage = session.messages.toReversed().find(item => item.role === 'user');
|
|
2760
|
+
if (!previousUserMessage) {
|
|
2761
|
+
return updatedState;
|
|
2762
|
+
}
|
|
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);
|
|
2767
|
+
const updatedSession = {
|
|
2768
|
+
...session,
|
|
2769
|
+
messages: [...session.messages.slice(0, -1), assistantMessage]
|
|
2770
|
+
};
|
|
2771
|
+
await saveChatSession(updatedSession);
|
|
2772
|
+
const updatedSessions = updatedState.sessions.map(item => {
|
|
2773
|
+
if (item.id !== updatedState.selectedSessionId) {
|
|
2774
|
+
return item;
|
|
2775
|
+
}
|
|
2776
|
+
return updatedSession;
|
|
2777
|
+
});
|
|
2778
|
+
return {
|
|
2779
|
+
...updatedState,
|
|
2780
|
+
nextMessageId: updatedState.nextMessageId + 1,
|
|
2781
|
+
sessions: updatedSessions
|
|
2782
|
+
};
|
|
2783
|
+
};
|
|
2784
|
+
|
|
2785
|
+
const focusInput = state => {
|
|
2786
|
+
return {
|
|
2787
|
+
...state,
|
|
2788
|
+
focus: 'composer',
|
|
2789
|
+
focused: true
|
|
2790
|
+
};
|
|
2791
|
+
};
|
|
2792
|
+
|
|
2080
2793
|
const handleSubmit = async state => {
|
|
2081
2794
|
const {
|
|
2795
|
+
assetDir,
|
|
2082
2796
|
composerValue,
|
|
2797
|
+
mockApiCommandId,
|
|
2083
2798
|
models,
|
|
2084
2799
|
nextMessageId,
|
|
2800
|
+
openApiApiBaseUrl,
|
|
2801
|
+
openApiApiKey,
|
|
2085
2802
|
openRouterApiBaseUrl,
|
|
2086
2803
|
openRouterApiKey,
|
|
2804
|
+
platform,
|
|
2087
2805
|
selectedModelId,
|
|
2088
2806
|
selectedSessionId,
|
|
2089
2807
|
sessions,
|
|
2808
|
+
useMockApi,
|
|
2090
2809
|
viewMode
|
|
2091
2810
|
} = state;
|
|
2092
2811
|
const userText = composerValue.trim();
|
|
@@ -2160,7 +2879,9 @@ const handleSubmit = async state => {
|
|
|
2160
2879
|
set(state.uid, state, optimisticState);
|
|
2161
2880
|
// @ts-ignore
|
|
2162
2881
|
await invoke('Chat.rerender');
|
|
2163
|
-
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);
|
|
2164
2885
|
const updatedSessions = optimisticState.sessions.map(session => {
|
|
2165
2886
|
if (session.id !== optimisticState.selectedSessionId) {
|
|
2166
2887
|
return session;
|
|
@@ -2195,6 +2916,41 @@ const handleClickSend = async state => {
|
|
|
2195
2916
|
return handleSubmit(submitState);
|
|
2196
2917
|
};
|
|
2197
2918
|
|
|
2919
|
+
const Composer = 'composer';
|
|
2920
|
+
const Send = 'send';
|
|
2921
|
+
const Back = 'back';
|
|
2922
|
+
const Model = 'model';
|
|
2923
|
+
const CreateSession = 'create-session';
|
|
2924
|
+
const Settings = 'settings';
|
|
2925
|
+
const CloseChat = 'close-chat';
|
|
2926
|
+
const SessionDelete = 'SessionDelete';
|
|
2927
|
+
const SessionPrefix = 'session:';
|
|
2928
|
+
const RenamePrefix = 'session-rename:';
|
|
2929
|
+
const getSessionInputName = sessionId => {
|
|
2930
|
+
return `${SessionPrefix}${sessionId}`;
|
|
2931
|
+
};
|
|
2932
|
+
const isSessionInputName = name => {
|
|
2933
|
+
return name.startsWith(SessionPrefix);
|
|
2934
|
+
};
|
|
2935
|
+
const getSessionIdFromInputName = name => {
|
|
2936
|
+
return name.slice(SessionPrefix.length);
|
|
2937
|
+
};
|
|
2938
|
+
const isRenameInputName = name => {
|
|
2939
|
+
return name.startsWith(RenamePrefix);
|
|
2940
|
+
};
|
|
2941
|
+
const getRenameIdFromInputName = name => {
|
|
2942
|
+
return name.slice(RenamePrefix.length);
|
|
2943
|
+
};
|
|
2944
|
+
|
|
2945
|
+
const OpenApiApiKeyInput = 'open-api-api-key';
|
|
2946
|
+
const SaveOpenApiApiKey = 'save-openapi-api-key';
|
|
2947
|
+
const OpenOpenApiApiKeySettings = 'open-openapi-api-key-settings';
|
|
2948
|
+
|
|
2949
|
+
/* eslint-disable @cspell/spellchecker */
|
|
2950
|
+
const OpenRouterApiKeyInput = 'open-router-api-key';
|
|
2951
|
+
const SaveOpenRouterApiKey = 'save-openrouter-api-key';
|
|
2952
|
+
const OpenOpenRouterApiKeySettings = 'open-openrouter-api-key-settings';
|
|
2953
|
+
|
|
2198
2954
|
const selectSession = async (state, id) => {
|
|
2199
2955
|
const exists = state.sessions.some(session => session.id === id);
|
|
2200
2956
|
if (!exists) {
|
|
@@ -2252,32 +3008,39 @@ const handleClickList = async (state, eventX, eventY) => {
|
|
|
2252
3008
|
return selectListIndex(state, index);
|
|
2253
3009
|
};
|
|
2254
3010
|
|
|
2255
|
-
const CREATE_SESSION = 'create-session';
|
|
2256
|
-
const SESSION_PREFIX = 'session:';
|
|
2257
|
-
const RENAME_PREFIX = 'session-rename:';
|
|
2258
|
-
const SESSION_DELETE = 'SessionDelete';
|
|
2259
|
-
const SEND = 'send';
|
|
2260
3011
|
const handleClick = async (state, name, id = '') => {
|
|
2261
3012
|
if (!name) {
|
|
2262
3013
|
return state;
|
|
2263
3014
|
}
|
|
2264
|
-
if (name ===
|
|
3015
|
+
if (name === CreateSession) {
|
|
2265
3016
|
return createSession(state);
|
|
2266
3017
|
}
|
|
2267
|
-
if (name
|
|
2268
|
-
const
|
|
2269
|
-
return selectSession(state,
|
|
3018
|
+
if (isSessionInputName(name)) {
|
|
3019
|
+
const sessionId = getSessionIdFromInputName(name);
|
|
3020
|
+
return selectSession(state, sessionId);
|
|
2270
3021
|
}
|
|
2271
|
-
if (name
|
|
2272
|
-
const
|
|
2273
|
-
return startRename(state,
|
|
3022
|
+
if (isRenameInputName(name)) {
|
|
3023
|
+
const sessionId = getRenameIdFromInputName(name);
|
|
3024
|
+
return startRename(state, sessionId);
|
|
2274
3025
|
}
|
|
2275
|
-
if (name ===
|
|
3026
|
+
if (name === SessionDelete) {
|
|
2276
3027
|
return deleteSession(state, id);
|
|
2277
3028
|
}
|
|
2278
|
-
if (name ===
|
|
3029
|
+
if (name === Send) {
|
|
2279
3030
|
return handleClickSend(state);
|
|
2280
3031
|
}
|
|
3032
|
+
if (name === SaveOpenRouterApiKey) {
|
|
3033
|
+
return handleClickSaveOpenRouterApiKey(state);
|
|
3034
|
+
}
|
|
3035
|
+
if (name === SaveOpenApiApiKey) {
|
|
3036
|
+
return handleClickSaveOpenApiApiKey(state);
|
|
3037
|
+
}
|
|
3038
|
+
if (name === OpenOpenRouterApiKeySettings) {
|
|
3039
|
+
return handleClickOpenRouterApiKeySettings(state);
|
|
3040
|
+
}
|
|
3041
|
+
if (name === OpenOpenApiApiKeySettings) {
|
|
3042
|
+
return handleClickOpenApiApiKeySettings(state);
|
|
3043
|
+
}
|
|
2281
3044
|
return state;
|
|
2282
3045
|
};
|
|
2283
3046
|
|
|
@@ -2307,7 +3070,22 @@ const handleClickSettings = async () => {
|
|
|
2307
3070
|
await invoke('Main.openUri', 'app://settings.json');
|
|
2308
3071
|
};
|
|
2309
3072
|
|
|
2310
|
-
const handleInput = async (state, value, inputSource = 'user') => {
|
|
3073
|
+
const handleInput = async (state, name, value, inputSource = 'user') => {
|
|
3074
|
+
if (name === OpenApiApiKeyInput) {
|
|
3075
|
+
return {
|
|
3076
|
+
...state,
|
|
3077
|
+
openApiApiKeyInput: value
|
|
3078
|
+
};
|
|
3079
|
+
}
|
|
3080
|
+
if (name === OpenRouterApiKeyInput) {
|
|
3081
|
+
return {
|
|
3082
|
+
...state,
|
|
3083
|
+
openRouterApiKeyInput: value
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
3086
|
+
if (name !== Composer) {
|
|
3087
|
+
return state;
|
|
3088
|
+
}
|
|
2311
3089
|
return {
|
|
2312
3090
|
...state,
|
|
2313
3091
|
composerValue: value,
|
|
@@ -2316,24 +3094,24 @@ const handleInput = async (state, value, inputSource = 'user') => {
|
|
|
2316
3094
|
};
|
|
2317
3095
|
|
|
2318
3096
|
const handleInputFocus = async (state, name) => {
|
|
2319
|
-
if (name ===
|
|
3097
|
+
if (name === Composer) {
|
|
2320
3098
|
return focusInput(state);
|
|
2321
3099
|
}
|
|
2322
|
-
if (name ===
|
|
3100
|
+
if (name === Send) {
|
|
2323
3101
|
return {
|
|
2324
3102
|
...state,
|
|
2325
3103
|
focus: 'send-button',
|
|
2326
3104
|
focused: true
|
|
2327
3105
|
};
|
|
2328
3106
|
}
|
|
2329
|
-
if (name
|
|
3107
|
+
if (isSessionInputName(name) || name === SessionDelete) {
|
|
2330
3108
|
return {
|
|
2331
3109
|
...state,
|
|
2332
3110
|
focus: 'list',
|
|
2333
3111
|
focused: true
|
|
2334
3112
|
};
|
|
2335
3113
|
}
|
|
2336
|
-
if (name ===
|
|
3114
|
+
if (name === CreateSession || name === Settings || name === CloseChat || name === Back) {
|
|
2337
3115
|
return {
|
|
2338
3116
|
...state,
|
|
2339
3117
|
focus: 'header',
|
|
@@ -2412,7 +3190,7 @@ const handleModelChange = async (state, value) => {
|
|
|
2412
3190
|
};
|
|
2413
3191
|
|
|
2414
3192
|
const handleNewline = async state => {
|
|
2415
|
-
return handleInput(state, `${state.composerValue}\n`);
|
|
3193
|
+
return handleInput(state, Composer, `${state.composerValue}\n`);
|
|
2416
3194
|
};
|
|
2417
3195
|
|
|
2418
3196
|
const id = 7201;
|
|
@@ -2441,36 +3219,6 @@ const isObject = value => {
|
|
|
2441
3219
|
return typeof value === 'object' && value !== null;
|
|
2442
3220
|
};
|
|
2443
3221
|
|
|
2444
|
-
const getSavedBounds = savedState => {
|
|
2445
|
-
if (!isObject(savedState)) {
|
|
2446
|
-
return undefined;
|
|
2447
|
-
}
|
|
2448
|
-
const {
|
|
2449
|
-
height,
|
|
2450
|
-
width,
|
|
2451
|
-
x,
|
|
2452
|
-
y
|
|
2453
|
-
} = savedState;
|
|
2454
|
-
if (typeof x !== 'number') {
|
|
2455
|
-
return undefined;
|
|
2456
|
-
}
|
|
2457
|
-
if (typeof y !== 'number') {
|
|
2458
|
-
return undefined;
|
|
2459
|
-
}
|
|
2460
|
-
if (typeof width !== 'number') {
|
|
2461
|
-
return undefined;
|
|
2462
|
-
}
|
|
2463
|
-
if (typeof height !== 'number') {
|
|
2464
|
-
return undefined;
|
|
2465
|
-
}
|
|
2466
|
-
return {
|
|
2467
|
-
height,
|
|
2468
|
-
width,
|
|
2469
|
-
x,
|
|
2470
|
-
y
|
|
2471
|
-
};
|
|
2472
|
-
};
|
|
2473
|
-
|
|
2474
3222
|
const getSavedSelectedModelId = savedState => {
|
|
2475
3223
|
if (!isObject(savedState)) {
|
|
2476
3224
|
return undefined;
|
|
@@ -2523,10 +3271,6 @@ const getSavedViewMode = savedState => {
|
|
|
2523
3271
|
return viewMode;
|
|
2524
3272
|
};
|
|
2525
3273
|
|
|
2526
|
-
const get = async key => {
|
|
2527
|
-
return getPreference(key);
|
|
2528
|
-
};
|
|
2529
|
-
|
|
2530
3274
|
const toSummarySession = session => {
|
|
2531
3275
|
return {
|
|
2532
3276
|
id: session.id,
|
|
@@ -2550,9 +3294,25 @@ const loadSelectedSessionMessages = async (sessions, selectedSessionId) => {
|
|
|
2550
3294
|
});
|
|
2551
3295
|
};
|
|
2552
3296
|
const loadContent = async (state, savedState) => {
|
|
2553
|
-
const savedBounds = getSavedBounds(savedState);
|
|
2554
3297
|
const savedSelectedModelId = getSavedSelectedModelId(savedState);
|
|
2555
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
|
+
}
|
|
2556
3316
|
let openRouterApiKey = '';
|
|
2557
3317
|
try {
|
|
2558
3318
|
const savedOpenRouterApiKey = await get('secrets.openRouterApiKey');
|
|
@@ -2584,9 +3344,11 @@ const loadContent = async (state, savedState) => {
|
|
|
2584
3344
|
const viewMode = sessions.length === 0 || !selectedSessionId ? 'list' : preferredViewMode === 'detail' ? 'detail' : 'list';
|
|
2585
3345
|
return {
|
|
2586
3346
|
...state,
|
|
2587
|
-
...savedBounds,
|
|
2588
3347
|
initial: false,
|
|
3348
|
+
openApiApiKey,
|
|
3349
|
+
openApiApiKeyInput: openApiApiKey,
|
|
2589
3350
|
openRouterApiKey,
|
|
3351
|
+
openRouterApiKeyInput: openRouterApiKey,
|
|
2590
3352
|
selectedModelId,
|
|
2591
3353
|
selectedSessionId,
|
|
2592
3354
|
sessions,
|
|
@@ -2595,11 +3357,14 @@ const loadContent = async (state, savedState) => {
|
|
|
2595
3357
|
};
|
|
2596
3358
|
|
|
2597
3359
|
const openMockSession = async (state, mockSessionId, mockChatMessages) => {
|
|
3360
|
+
const {
|
|
3361
|
+
sessions: currentSessions
|
|
3362
|
+
} = state;
|
|
2598
3363
|
if (!mockSessionId) {
|
|
2599
3364
|
return state;
|
|
2600
3365
|
}
|
|
2601
|
-
const existingSession =
|
|
2602
|
-
const sessions = existingSession ?
|
|
3366
|
+
const existingSession = currentSessions.find(session => session.id === mockSessionId);
|
|
3367
|
+
const sessions = existingSession ? currentSessions.map(session => {
|
|
2603
3368
|
if (session.id !== mockSessionId) {
|
|
2604
3369
|
return session;
|
|
2605
3370
|
}
|
|
@@ -2607,7 +3372,7 @@ const openMockSession = async (state, mockSessionId, mockChatMessages) => {
|
|
|
2607
3372
|
...session,
|
|
2608
3373
|
messages: mockChatMessages
|
|
2609
3374
|
};
|
|
2610
|
-
}) : [...
|
|
3375
|
+
}) : [...currentSessions, {
|
|
2611
3376
|
id: mockSessionId,
|
|
2612
3377
|
messages: mockChatMessages,
|
|
2613
3378
|
title: mockSessionId
|
|
@@ -2657,6 +3422,7 @@ const renderFocusContext = (oldState, newState) => {
|
|
|
2657
3422
|
return [SetFocusContext, newState.uid, FocusChatInput];
|
|
2658
3423
|
};
|
|
2659
3424
|
|
|
3425
|
+
const Actions = 'Actions';
|
|
2660
3426
|
const ChatActions = 'ChatActions';
|
|
2661
3427
|
const ChatName = 'ChatName';
|
|
2662
3428
|
const ChatSendArea = 'ChatSendArea';
|
|
@@ -2665,7 +3431,11 @@ const ChatSendAreaBottom = 'ChatSendAreaBottom';
|
|
|
2665
3431
|
const ChatSendAreaContent = 'ChatSendAreaContent';
|
|
2666
3432
|
const Chat = 'Chat';
|
|
2667
3433
|
const ChatHeader = 'ChatHeader';
|
|
3434
|
+
const Button = 'Button';
|
|
3435
|
+
const ButtonPrimary = 'ButtonPrimary';
|
|
3436
|
+
const ButtonSecondary = 'ButtonSecondary';
|
|
2668
3437
|
const IconButton = 'IconButton';
|
|
3438
|
+
const InputBox = 'InputBox';
|
|
2669
3439
|
const Label = 'Label';
|
|
2670
3440
|
const LabelDetail = 'LabelDetail';
|
|
2671
3441
|
const ChatList = 'ChatList';
|
|
@@ -2675,6 +3445,8 @@ const ChatListItemLabel = 'ChatListItemLabel';
|
|
|
2675
3445
|
const Markdown = 'Markdown';
|
|
2676
3446
|
const Message = 'Message';
|
|
2677
3447
|
const ChatMessageContent = 'ChatMessageContent';
|
|
3448
|
+
const ChatOrderedList = 'ChatOrderedList';
|
|
3449
|
+
const ChatOrderedListItem = 'ChatOrderedListItem';
|
|
2678
3450
|
const MessageUser = 'MessageUser';
|
|
2679
3451
|
const MessageAssistant = 'MessageAssistant';
|
|
2680
3452
|
const MultilineInputBox = 'MultilineInputBox';
|
|
@@ -2704,8 +3476,12 @@ const getModelLabel = model => {
|
|
|
2704
3476
|
if (model.provider === 'openRouter') {
|
|
2705
3477
|
return `${model.name} (OpenRouter)`;
|
|
2706
3478
|
}
|
|
3479
|
+
if (model.provider === 'openApi' || model.provider === 'openAI' || model.provider === 'openai') {
|
|
3480
|
+
return `${model.name} (OpenAI)`;
|
|
3481
|
+
}
|
|
2707
3482
|
return model.name;
|
|
2708
3483
|
};
|
|
3484
|
+
|
|
2709
3485
|
const getModelOptionDOm = (model, selectedModelId) => {
|
|
2710
3486
|
return [{
|
|
2711
3487
|
childCount: 1,
|
|
@@ -2726,11 +3502,11 @@ const getSendButtonDom = isSendDisabled => {
|
|
|
2726
3502
|
childCount: 1,
|
|
2727
3503
|
className: sendButtonClassName,
|
|
2728
3504
|
disabled: isSendDisabled,
|
|
2729
|
-
name:
|
|
3505
|
+
name: Send,
|
|
2730
3506
|
onClick: HandleSubmit,
|
|
2731
|
-
role: Button$
|
|
2732
|
-
title: sendMessage,
|
|
2733
|
-
type: Button
|
|
3507
|
+
role: Button$2,
|
|
3508
|
+
title: sendMessage(),
|
|
3509
|
+
type: Button$1
|
|
2734
3510
|
}, {
|
|
2735
3511
|
childCount: 0,
|
|
2736
3512
|
className: 'MaskIcon MaskIconSend',
|
|
@@ -2787,10 +3563,10 @@ const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOvervie
|
|
|
2787
3563
|
}, {
|
|
2788
3564
|
childCount: 0,
|
|
2789
3565
|
className: MultilineInputBox,
|
|
2790
|
-
name:
|
|
3566
|
+
name: Composer,
|
|
2791
3567
|
onFocus: HandleFocus,
|
|
2792
3568
|
onInput: HandleInput,
|
|
2793
|
-
placeholder: composePlaceholder,
|
|
3569
|
+
placeholder: composePlaceholder(),
|
|
2794
3570
|
rows: 4,
|
|
2795
3571
|
type: TextArea,
|
|
2796
3572
|
value: composerValue
|
|
@@ -2801,7 +3577,7 @@ const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOvervie
|
|
|
2801
3577
|
}, {
|
|
2802
3578
|
childCount: models.length,
|
|
2803
3579
|
className: Select,
|
|
2804
|
-
name:
|
|
3580
|
+
name: Model,
|
|
2805
3581
|
onInput: HandleModelChange,
|
|
2806
3582
|
type: Select$1,
|
|
2807
3583
|
value: selectedModelId
|
|
@@ -2812,11 +3588,11 @@ const getBackButtonVirtualDom = () => {
|
|
|
2812
3588
|
return [{
|
|
2813
3589
|
childCount: 1,
|
|
2814
3590
|
className: IconButton,
|
|
2815
|
-
name:
|
|
3591
|
+
name: Back,
|
|
2816
3592
|
onClick: HandleClickBack,
|
|
2817
|
-
role: Button$
|
|
2818
|
-
title: backToChats,
|
|
2819
|
-
type: Button
|
|
3593
|
+
role: Button$2,
|
|
3594
|
+
title: backToChats(),
|
|
3595
|
+
type: Button$1
|
|
2820
3596
|
}, {
|
|
2821
3597
|
childCount: 0,
|
|
2822
3598
|
className: 'MaskIcon MaskIconArrowLeft',
|
|
@@ -2830,9 +3606,9 @@ const getHeaderActionVirtualDom = item => {
|
|
|
2830
3606
|
className: IconButton,
|
|
2831
3607
|
name: item.name,
|
|
2832
3608
|
onClick: item.onClick,
|
|
2833
|
-
role: Button$
|
|
3609
|
+
role: Button$2,
|
|
2834
3610
|
title: item.title,
|
|
2835
|
-
type: Button
|
|
3611
|
+
type: Button$1
|
|
2836
3612
|
}, {
|
|
2837
3613
|
childCount: 0,
|
|
2838
3614
|
className: item.icon,
|
|
@@ -2843,19 +3619,19 @@ const getHeaderActionVirtualDom = item => {
|
|
|
2843
3619
|
const getChatHeaderActionsDom = () => {
|
|
2844
3620
|
const items = [{
|
|
2845
3621
|
icon: 'MaskIcon MaskIconAdd',
|
|
2846
|
-
name:
|
|
3622
|
+
name: CreateSession,
|
|
2847
3623
|
onClick: HandleClickNew,
|
|
2848
|
-
title: newChat
|
|
3624
|
+
title: newChat()
|
|
2849
3625
|
}, {
|
|
2850
3626
|
icon: 'MaskIcon MaskIconSettingsGear',
|
|
2851
|
-
name:
|
|
3627
|
+
name: Settings,
|
|
2852
3628
|
onClick: HandleClickSettings,
|
|
2853
|
-
title: settings
|
|
3629
|
+
title: settings()
|
|
2854
3630
|
}, {
|
|
2855
3631
|
icon: 'MaskIcon MaskIconClose',
|
|
2856
|
-
name:
|
|
3632
|
+
name: CloseChat,
|
|
2857
3633
|
onClick: HandleClickClose,
|
|
2858
|
-
title: closeChat
|
|
3634
|
+
title: closeChat()
|
|
2859
3635
|
}];
|
|
2860
3636
|
return [{
|
|
2861
3637
|
childCount: items.length,
|
|
@@ -2880,47 +3656,142 @@ const getChatHeaderDomDetailMode = selectedSessionTitle => {
|
|
|
2880
3656
|
}, text(selectedSessionTitle), ...getChatHeaderActionsDom()];
|
|
2881
3657
|
};
|
|
2882
3658
|
|
|
2883
|
-
const
|
|
3659
|
+
const getMissingApiKeyDom = ({
|
|
3660
|
+
getApiKeyText,
|
|
3661
|
+
inputName,
|
|
3662
|
+
inputValue,
|
|
3663
|
+
openSettingsButtonName,
|
|
3664
|
+
placeholder,
|
|
3665
|
+
saveButtonName
|
|
3666
|
+
}) => {
|
|
3667
|
+
return [{
|
|
3668
|
+
childCount: 2,
|
|
3669
|
+
type: Div
|
|
3670
|
+
}, {
|
|
3671
|
+
childCount: 0,
|
|
3672
|
+
className: InputBox,
|
|
3673
|
+
name: inputName,
|
|
3674
|
+
onInput: HandleInput,
|
|
3675
|
+
placeholder,
|
|
3676
|
+
type: Input,
|
|
3677
|
+
value: inputValue
|
|
3678
|
+
}, {
|
|
3679
|
+
childCount: 2,
|
|
3680
|
+
className: Actions,
|
|
3681
|
+
type: Div
|
|
3682
|
+
}, {
|
|
3683
|
+
childCount: 1,
|
|
3684
|
+
className: mergeClassNames(Button, ButtonPrimary),
|
|
3685
|
+
name: saveButtonName,
|
|
3686
|
+
onClick: HandleClick,
|
|
3687
|
+
type: Button$1
|
|
3688
|
+
}, text(save()), {
|
|
3689
|
+
childCount: 1,
|
|
3690
|
+
className: mergeClassNames(Button, ButtonSecondary),
|
|
3691
|
+
name: openSettingsButtonName,
|
|
3692
|
+
onClick: HandleClick,
|
|
3693
|
+
type: Button$1
|
|
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
|
+
});
|
|
3717
|
+
};
|
|
3718
|
+
|
|
3719
|
+
const getOpenRouterRequestFailedDom = () => {
|
|
3720
|
+
return [{
|
|
3721
|
+
childCount: openRouterRequestFailureReasons.length,
|
|
3722
|
+
className: ChatOrderedList,
|
|
3723
|
+
type: Ol
|
|
3724
|
+
}, ...openRouterRequestFailureReasons.flatMap(reason => {
|
|
3725
|
+
return [{
|
|
3726
|
+
childCount: 1,
|
|
3727
|
+
className: ChatOrderedListItem,
|
|
3728
|
+
type: Li
|
|
3729
|
+
}, text(reason)];
|
|
3730
|
+
})];
|
|
3731
|
+
};
|
|
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 = '') => {
|
|
2884
3746
|
const roleClassName = message.role === 'user' ? MessageUser : MessageAssistant;
|
|
3747
|
+
const isOpenApiApiKeyMissingMessage = message.role === 'assistant' && message.text === openApiApiKeyRequiredMessage;
|
|
3748
|
+
const isOpenRouterApiKeyMissingMessage = message.role === 'assistant' && message.text === openRouterApiKeyRequiredMessage;
|
|
3749
|
+
const isOpenRouterRequestFailedMessage = message.role === 'assistant' && message.text === openRouterRequestFailedMessage;
|
|
3750
|
+
const isOpenRouterTooManyRequestsMessage = message.role === 'assistant' && message.text.startsWith(openRouterTooManyRequestsMessage);
|
|
3751
|
+
const extraChildCount = isOpenApiApiKeyMissingMessage || isOpenRouterApiKeyMissingMessage || isOpenRouterRequestFailedMessage || isOpenRouterTooManyRequestsMessage ? 2 : 1;
|
|
2885
3752
|
return [{
|
|
2886
3753
|
childCount: 1,
|
|
2887
3754
|
className: mergeClassNames(Message, roleClassName),
|
|
2888
3755
|
type: Div
|
|
2889
3756
|
}, {
|
|
2890
|
-
childCount:
|
|
3757
|
+
childCount: extraChildCount,
|
|
2891
3758
|
className: ChatMessageContent,
|
|
2892
3759
|
type: Div
|
|
2893
3760
|
}, {
|
|
2894
3761
|
childCount: 1,
|
|
2895
3762
|
className: Markdown,
|
|
2896
3763
|
type: P
|
|
2897
|
-
}, text(message.text)];
|
|
3764
|
+
}, text(message.text), ...(isOpenApiApiKeyMissingMessage ? getMissingOpenApiApiKeyDom(openApiApiKeyInput) : []), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : []), ...(isOpenRouterTooManyRequestsMessage ? getOpenRouterTooManyRequestsDom() : [])];
|
|
2898
3765
|
};
|
|
2899
3766
|
|
|
2900
|
-
const
|
|
3767
|
+
const getEmptyMessagesDom = () => {
|
|
3768
|
+
return [{
|
|
3769
|
+
childCount: 1,
|
|
3770
|
+
className: ChatWelcomeMessage,
|
|
3771
|
+
type: Div
|
|
3772
|
+
}, text(startConversation())];
|
|
3773
|
+
};
|
|
3774
|
+
|
|
3775
|
+
const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = '') => {
|
|
2901
3776
|
if (messages.length === 0) {
|
|
2902
|
-
return
|
|
2903
|
-
childCount: 1,
|
|
2904
|
-
className: ChatWelcomeMessage,
|
|
2905
|
-
type: Div
|
|
2906
|
-
}, text(startConversation)];
|
|
3777
|
+
return getEmptyMessagesDom();
|
|
2907
3778
|
}
|
|
2908
3779
|
return [{
|
|
2909
3780
|
childCount: messages.length,
|
|
2910
3781
|
className: 'ChatMessages',
|
|
2911
3782
|
type: Div
|
|
2912
|
-
}, ...messages.flatMap(getChatMessageDom)];
|
|
3783
|
+
}, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput, openApiApiKeyInput))];
|
|
2913
3784
|
};
|
|
2914
3785
|
|
|
2915
|
-
const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
|
|
3786
|
+
const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
|
|
2916
3787
|
const selectedSession = sessions.find(session => session.id === selectedSessionId);
|
|
2917
|
-
const selectedSessionTitle = selectedSession?.title || chatTitle;
|
|
3788
|
+
const selectedSessionTitle = selectedSession?.title || chatTitle();
|
|
2918
3789
|
const messages = selectedSession ? selectedSession.messages : [];
|
|
2919
3790
|
return [{
|
|
2920
3791
|
childCount: 3,
|
|
2921
3792
|
className: mergeClassNames(Viewlet, Chat),
|
|
2922
3793
|
type: Div
|
|
2923
|
-
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax)];
|
|
3794
|
+
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput, openApiApiKeyInput), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax)];
|
|
2924
3795
|
};
|
|
2925
3796
|
|
|
2926
3797
|
const getChatHeaderListModeDom = () => {
|
|
@@ -2932,7 +3803,7 @@ const getChatHeaderListModeDom = () => {
|
|
|
2932
3803
|
childCount: 1,
|
|
2933
3804
|
className: Label,
|
|
2934
3805
|
type: Span
|
|
2935
|
-
}, text(chats), ...getChatHeaderActionsDom()];
|
|
3806
|
+
}, text(chats()), ...getChatHeaderActionsDom()];
|
|
2936
3807
|
};
|
|
2937
3808
|
|
|
2938
3809
|
const getEmptyChatSessionsDom = () => {
|
|
@@ -2944,7 +3815,7 @@ const getEmptyChatSessionsDom = () => {
|
|
|
2944
3815
|
childCount: 1,
|
|
2945
3816
|
className: Label,
|
|
2946
3817
|
type: Div
|
|
2947
|
-
}, text(clickToOpenNewChat)];
|
|
3818
|
+
}, text(clickToOpenNewChat())];
|
|
2948
3819
|
};
|
|
2949
3820
|
|
|
2950
3821
|
const getSessionDom = session => {
|
|
@@ -2956,7 +3827,7 @@ const getSessionDom = session => {
|
|
|
2956
3827
|
}, {
|
|
2957
3828
|
childCount: 1,
|
|
2958
3829
|
className: ChatListItemLabel,
|
|
2959
|
-
name:
|
|
3830
|
+
name: getSessionInputName(session.id),
|
|
2960
3831
|
onContextMenu: HandleListContextMenu,
|
|
2961
3832
|
tabIndex: 0,
|
|
2962
3833
|
type: Div
|
|
@@ -2968,12 +3839,12 @@ const getSessionDom = session => {
|
|
|
2968
3839
|
childCount: 1,
|
|
2969
3840
|
className: IconButton,
|
|
2970
3841
|
'data-id': session.id,
|
|
2971
|
-
name:
|
|
3842
|
+
name: SessionDelete,
|
|
2972
3843
|
onClick: HandleClickDelete,
|
|
2973
|
-
role: Button$
|
|
3844
|
+
role: Button$2,
|
|
2974
3845
|
tabIndex: 0,
|
|
2975
|
-
title: deleteChatSession$1,
|
|
2976
|
-
type: Button
|
|
3846
|
+
title: deleteChatSession$1(),
|
|
3847
|
+
type: Button$1
|
|
2977
3848
|
}, text('🗑')];
|
|
2978
3849
|
};
|
|
2979
3850
|
|
|
@@ -3001,13 +3872,13 @@ const getChatModeUnsupportedVirtualDom = () => {
|
|
|
3001
3872
|
return [{
|
|
3002
3873
|
childCount: 1,
|
|
3003
3874
|
type: Div
|
|
3004
|
-
}, text(
|
|
3875
|
+
}, text(unknownViewMode())];
|
|
3005
3876
|
};
|
|
3006
3877
|
|
|
3007
|
-
const getChatVirtualDom = (sessions, selectedSessionId, composerValue, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
|
|
3878
|
+
const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput = '') => {
|
|
3008
3879
|
switch (viewMode) {
|
|
3009
3880
|
case 'detail':
|
|
3010
|
-
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
3881
|
+
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
3011
3882
|
case 'list':
|
|
3012
3883
|
return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
3013
3884
|
default:
|
|
@@ -3020,6 +3891,8 @@ const renderItems = (oldState, newState) => {
|
|
|
3020
3891
|
composerValue,
|
|
3021
3892
|
initial,
|
|
3022
3893
|
models,
|
|
3894
|
+
openApiApiKeyInput,
|
|
3895
|
+
openRouterApiKeyInput,
|
|
3023
3896
|
selectedModelId,
|
|
3024
3897
|
selectedSessionId,
|
|
3025
3898
|
sessions,
|
|
@@ -3032,7 +3905,7 @@ const renderItems = (oldState, newState) => {
|
|
|
3032
3905
|
if (initial) {
|
|
3033
3906
|
return [SetDom2, uid, []];
|
|
3034
3907
|
}
|
|
3035
|
-
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
3908
|
+
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput);
|
|
3036
3909
|
return [SetDom2, uid, dom];
|
|
3037
3910
|
};
|
|
3038
3911
|
|
|
@@ -3047,7 +3920,7 @@ const renderValue = (oldState, newState) => {
|
|
|
3047
3920
|
const {
|
|
3048
3921
|
composerValue
|
|
3049
3922
|
} = newState;
|
|
3050
|
-
return [SetValueByName, newState.uid,
|
|
3923
|
+
return [SetValueByName, newState.uid, Composer, composerValue];
|
|
3051
3924
|
};
|
|
3052
3925
|
|
|
3053
3926
|
const getRenderer = diffType => {
|
|
@@ -3119,7 +3992,7 @@ const renderEventListeners = () => {
|
|
|
3119
3992
|
params: ['handleClickList', ClientX, ClientY]
|
|
3120
3993
|
}, {
|
|
3121
3994
|
name: HandleInput,
|
|
3122
|
-
params: ['handleInput', TargetValue]
|
|
3995
|
+
params: ['handleInput', TargetName, TargetValue]
|
|
3123
3996
|
}, {
|
|
3124
3997
|
name: HandleModelChange,
|
|
3125
3998
|
params: ['handleModelChange', TargetValue]
|
|
@@ -3144,6 +4017,7 @@ const reset = async state => {
|
|
|
3144
4017
|
return {
|
|
3145
4018
|
...state,
|
|
3146
4019
|
composerValue: '',
|
|
4020
|
+
openRouterApiKey: '',
|
|
3147
4021
|
selectedSessionId: '',
|
|
3148
4022
|
sessions: [],
|
|
3149
4023
|
viewMode: 'list'
|
|
@@ -3187,15 +4061,15 @@ const saveState = state => {
|
|
|
3187
4061
|
const dummySessions = [{
|
|
3188
4062
|
id: 'session-a',
|
|
3189
4063
|
messages: [],
|
|
3190
|
-
title: dummyChatA
|
|
4064
|
+
title: dummyChatA()
|
|
3191
4065
|
}, {
|
|
3192
4066
|
id: 'session-b',
|
|
3193
4067
|
messages: [],
|
|
3194
|
-
title: dummyChatB
|
|
4068
|
+
title: dummyChatB()
|
|
3195
4069
|
}, {
|
|
3196
4070
|
id: 'session-c',
|
|
3197
4071
|
messages: [],
|
|
3198
|
-
title: dummyChatC
|
|
4072
|
+
title: dummyChatC()
|
|
3199
4073
|
}];
|
|
3200
4074
|
const setChatList = state => {
|
|
3201
4075
|
return {
|
|
@@ -3206,6 +4080,21 @@ const setChatList = state => {
|
|
|
3206
4080
|
};
|
|
3207
4081
|
};
|
|
3208
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
|
+
|
|
3209
4098
|
const commandMap = {
|
|
3210
4099
|
'Chat.clearInput': wrapCommand(clearInput),
|
|
3211
4100
|
'Chat.create': create,
|
|
@@ -3213,6 +4102,7 @@ const commandMap = {
|
|
|
3213
4102
|
'Chat.enterNewLine': wrapCommand(handleNewline),
|
|
3214
4103
|
'Chat.getCommandIds': getCommandIds,
|
|
3215
4104
|
'Chat.getKeyBindings': getKeyBindings,
|
|
4105
|
+
'Chat.getSelectedSessionId': wrapGetter(getSelectedSessionId),
|
|
3216
4106
|
'Chat.handleChatListContextMenu': handleChatListContextMenu,
|
|
3217
4107
|
'Chat.handleClick': wrapCommand(handleClick),
|
|
3218
4108
|
'Chat.handleClickBack': wrapCommand(handleClickBack),
|
|
@@ -3237,7 +4127,9 @@ const commandMap = {
|
|
|
3237
4127
|
'Chat.resize': wrapCommand(resize),
|
|
3238
4128
|
'Chat.saveState': wrapGetter(saveState),
|
|
3239
4129
|
'Chat.setChatList': wrapCommand(setChatList),
|
|
3240
|
-
'Chat.
|
|
4130
|
+
'Chat.setOpenRouterApiKey': wrapCommand(setOpenRouterApiKey),
|
|
4131
|
+
'Chat.terminate': terminate,
|
|
4132
|
+
'Chat.useMockApi': wrapCommand(useMockApi)
|
|
3241
4133
|
};
|
|
3242
4134
|
|
|
3243
4135
|
const listen = async () => {
|