@lvce-editor/chat-view 1.13.0 → 1.14.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 +537 -253
- package/package.json +1 -1
|
@@ -1051,6 +1051,9 @@ const sendMessagePortToExtensionHostWorker$1 = async (port, rpcId = 0) => {
|
|
|
1051
1051
|
const getPreference = async key => {
|
|
1052
1052
|
return await invoke('Preferences.get', key);
|
|
1053
1053
|
};
|
|
1054
|
+
const openExternal = async uri => {
|
|
1055
|
+
await invoke('Open.openExternal', uri);
|
|
1056
|
+
};
|
|
1054
1057
|
|
|
1055
1058
|
const toCommandId = key => {
|
|
1056
1059
|
const dotIndex = key.indexOf('.');
|
|
@@ -1191,25 +1194,122 @@ const i18nString = (key, placeholders = emptyObject) => {
|
|
|
1191
1194
|
return key.replaceAll(RE_PLACEHOLDER, replacer);
|
|
1192
1195
|
};
|
|
1193
1196
|
|
|
1194
|
-
const chatTitle =
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
const
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
const
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1211
|
-
|
|
1197
|
+
const chatTitle = () => {
|
|
1198
|
+
return i18nString('Chat');
|
|
1199
|
+
};
|
|
1200
|
+
const chats = () => {
|
|
1201
|
+
return i18nString('Chats');
|
|
1202
|
+
};
|
|
1203
|
+
const newChat = () => {
|
|
1204
|
+
return i18nString('New Chat');
|
|
1205
|
+
};
|
|
1206
|
+
const backToChats = () => {
|
|
1207
|
+
return i18nString('Back to chats');
|
|
1208
|
+
};
|
|
1209
|
+
const settings = () => {
|
|
1210
|
+
return i18nString('Settings');
|
|
1211
|
+
};
|
|
1212
|
+
const closeChat = () => {
|
|
1213
|
+
return i18nString('Close Chat');
|
|
1214
|
+
};
|
|
1215
|
+
const clickToOpenNewChat = () => {
|
|
1216
|
+
return i18nString('Click the + button to open a new chat.');
|
|
1217
|
+
};
|
|
1218
|
+
const startConversation = () => {
|
|
1219
|
+
return i18nString('Start a conversation by typing below.');
|
|
1220
|
+
};
|
|
1221
|
+
const composePlaceholder = () => {
|
|
1222
|
+
return i18nString('Type your message. Enter to send, Shift+Enter for newline.');
|
|
1223
|
+
};
|
|
1224
|
+
const openRouterApiKeyPlaceholder = () => {
|
|
1225
|
+
return i18nString('Enter OpenRouter API key');
|
|
1226
|
+
};
|
|
1227
|
+
const sendMessage = () => {
|
|
1228
|
+
return i18nString('Send message');
|
|
1229
|
+
};
|
|
1230
|
+
const save = () => {
|
|
1231
|
+
return i18nString('Save');
|
|
1232
|
+
};
|
|
1233
|
+
const getOpenRouterApiKey = () => {
|
|
1234
|
+
return i18nString('Get API Key');
|
|
1235
|
+
};
|
|
1236
|
+
const deleteChatSession$1 = () => {
|
|
1237
|
+
return i18nString('Delete chat session');
|
|
1238
|
+
};
|
|
1239
|
+
const defaultSessionTitle = () => {
|
|
1240
|
+
return i18nString('Chat 1');
|
|
1241
|
+
};
|
|
1242
|
+
const dummyChatA = () => {
|
|
1243
|
+
return i18nString('Dummy Chat A');
|
|
1244
|
+
};
|
|
1245
|
+
const dummyChatB = () => {
|
|
1246
|
+
return i18nString('Dummy Chat B');
|
|
1247
|
+
};
|
|
1248
|
+
const dummyChatC = () => {
|
|
1249
|
+
return i18nString('Dummy Chat C');
|
|
1250
|
+
};
|
|
1251
|
+
const unknownViewMode = () => {
|
|
1252
|
+
return i18nString('Unknown view mode');
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
/* eslint-disable @cspell/spellchecker */
|
|
1212
1256
|
|
|
1257
|
+
const getDefaultModels = () => {
|
|
1258
|
+
const defaultModelId = 'test';
|
|
1259
|
+
return [{
|
|
1260
|
+
id: defaultModelId,
|
|
1261
|
+
name: 'test',
|
|
1262
|
+
provider: 'test'
|
|
1263
|
+
}, {
|
|
1264
|
+
id: 'codex-5.3',
|
|
1265
|
+
name: 'Codex 5.3',
|
|
1266
|
+
provider: 'openRouter'
|
|
1267
|
+
}, {
|
|
1268
|
+
id: 'claude-code',
|
|
1269
|
+
name: 'Claude Code',
|
|
1270
|
+
provider: 'openRouter'
|
|
1271
|
+
}, {
|
|
1272
|
+
id: 'claude-haiku',
|
|
1273
|
+
name: 'Claude Haiku',
|
|
1274
|
+
provider: 'openRouter'
|
|
1275
|
+
}, {
|
|
1276
|
+
id: 'openRouter/openai/gpt-4o-mini',
|
|
1277
|
+
name: 'GPT-4o Mini',
|
|
1278
|
+
provider: 'openRouter'
|
|
1279
|
+
}, {
|
|
1280
|
+
id: 'openRouter/anthropic/claude-3.5-haiku',
|
|
1281
|
+
name: 'Claude 3.5 Haiku',
|
|
1282
|
+
provider: 'openRouter'
|
|
1283
|
+
}, {
|
|
1284
|
+
id: 'openRouter/google/gemini-2.0-flash-001',
|
|
1285
|
+
name: 'Gemini 2.0 Flash',
|
|
1286
|
+
provider: 'openRouter'
|
|
1287
|
+
}, {
|
|
1288
|
+
id: 'openRouter/openai/gpt-oss-20b:free',
|
|
1289
|
+
name: 'GPT OSS 20B (Free)',
|
|
1290
|
+
provider: 'openRouter'
|
|
1291
|
+
}, {
|
|
1292
|
+
id: 'openRouter/openai/gpt-oss-120b:free',
|
|
1293
|
+
name: 'GPT OSS 120B (Free)',
|
|
1294
|
+
provider: 'openRouter'
|
|
1295
|
+
}, {
|
|
1296
|
+
id: 'openRouter/meta-llama/llama-3.3-70b-instruct:free',
|
|
1297
|
+
name: 'Llama 3.3 70B Instruct (Free)',
|
|
1298
|
+
provider: 'openRouter'
|
|
1299
|
+
}, {
|
|
1300
|
+
id: 'openRouter/google/gemma-3-27b-it:free',
|
|
1301
|
+
name: 'Gemma 3 27B IT (Free)',
|
|
1302
|
+
provider: 'openRouter'
|
|
1303
|
+
}, {
|
|
1304
|
+
id: 'openRouter/qwen/qwen3-coder:free',
|
|
1305
|
+
name: 'Qwen3 Coder (Free)',
|
|
1306
|
+
provider: 'openRouter'
|
|
1307
|
+
}, {
|
|
1308
|
+
id: 'openRouter/mistralai/mistral-small-3.1-24b-instruct:free',
|
|
1309
|
+
name: 'Mistral Small 3.1 24B Instruct (Free)',
|
|
1310
|
+
provider: 'openRouter'
|
|
1311
|
+
}];
|
|
1312
|
+
};
|
|
1213
1313
|
const createDefaultState = () => {
|
|
1214
1314
|
const defaultSessionId = 'session-1';
|
|
1215
1315
|
const defaultModelId = 'test';
|
|
@@ -1225,38 +1325,12 @@ const createDefaultState = () => {
|
|
|
1225
1325
|
inputSource: 'script',
|
|
1226
1326
|
lastSubmittedSessionId: '',
|
|
1227
1327
|
listItemHeight: 40,
|
|
1228
|
-
models:
|
|
1229
|
-
id: defaultModelId,
|
|
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
|
-
}],
|
|
1328
|
+
models: getDefaultModels(),
|
|
1257
1329
|
nextMessageId: 1,
|
|
1258
1330
|
openRouterApiBaseUrl: 'https://openrouter.ai/api/v1',
|
|
1259
1331
|
openRouterApiKey: '',
|
|
1332
|
+
openRouterApiKeyInput: '',
|
|
1333
|
+
openRouterApiKeysSettingsUrl: 'https://openrouter.ai/settings/keys',
|
|
1260
1334
|
platform: 0,
|
|
1261
1335
|
renamingSessionId: '',
|
|
1262
1336
|
selectedModelId: defaultModelId,
|
|
@@ -1264,7 +1338,7 @@ const createDefaultState = () => {
|
|
|
1264
1338
|
sessions: [{
|
|
1265
1339
|
id: defaultSessionId,
|
|
1266
1340
|
messages: [],
|
|
1267
|
-
title: defaultSessionTitle
|
|
1341
|
+
title: defaultSessionTitle()
|
|
1268
1342
|
}],
|
|
1269
1343
|
tokensMax: 0,
|
|
1270
1344
|
tokensUsed: 0,
|
|
@@ -1369,12 +1443,15 @@ const SetPatches = 'Viewlet.setPatches';
|
|
|
1369
1443
|
|
|
1370
1444
|
const FocusChatInput = 8000;
|
|
1371
1445
|
|
|
1372
|
-
const Button$
|
|
1446
|
+
const Button$2 = 'button';
|
|
1373
1447
|
|
|
1374
|
-
const Button = 1;
|
|
1448
|
+
const Button$1 = 1;
|
|
1375
1449
|
const Div = 4;
|
|
1450
|
+
const Input = 6;
|
|
1376
1451
|
const Span = 8;
|
|
1377
1452
|
const Text = 12;
|
|
1453
|
+
const Li = 48;
|
|
1454
|
+
const Ol = 49;
|
|
1378
1455
|
const P = 50;
|
|
1379
1456
|
const TextArea = 62;
|
|
1380
1457
|
const Select$1 = 63;
|
|
@@ -1697,6 +1774,10 @@ const getKeyBindings = () => {
|
|
|
1697
1774
|
}];
|
|
1698
1775
|
};
|
|
1699
1776
|
|
|
1777
|
+
const getSelectedSessionId = state => {
|
|
1778
|
+
return state.selectedSessionId;
|
|
1779
|
+
};
|
|
1780
|
+
|
|
1700
1781
|
const getListIndex = (state, eventX, eventY) => {
|
|
1701
1782
|
const {
|
|
1702
1783
|
headerHeight,
|
|
@@ -1734,96 +1815,143 @@ const handleChatListContextMenu = async (state, eventX, eventY) => {
|
|
|
1734
1815
|
return state;
|
|
1735
1816
|
};
|
|
1736
1817
|
|
|
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
1818
|
const toError = error => {
|
|
1741
1819
|
if (error instanceof Error) {
|
|
1742
1820
|
return error;
|
|
1743
1821
|
}
|
|
1744
1822
|
return new Error('IndexedDB request failed');
|
|
1745
1823
|
};
|
|
1824
|
+
|
|
1746
1825
|
const requestToPromise = async createRequest => {
|
|
1747
1826
|
const request = createRequest();
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1827
|
+
const {
|
|
1828
|
+
promise,
|
|
1829
|
+
reject,
|
|
1830
|
+
resolve
|
|
1831
|
+
} = Promise.withResolvers();
|
|
1832
|
+
request.addEventListener('success', () => {
|
|
1833
|
+
resolve(request.result);
|
|
1755
1834
|
});
|
|
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
|
-
});
|
|
1835
|
+
request.addEventListener('error', () => {
|
|
1836
|
+
reject(toError(request.error));
|
|
1769
1837
|
});
|
|
1838
|
+
return promise;
|
|
1770
1839
|
};
|
|
1771
|
-
|
|
1772
|
-
|
|
1840
|
+
|
|
1841
|
+
const openSessionsDatabase = async (databaseName, databaseVersion, storeName) => {
|
|
1842
|
+
const request = indexedDB.open(databaseName, databaseVersion);
|
|
1773
1843
|
request.addEventListener('upgradeneeded', () => {
|
|
1774
1844
|
const database = request.result;
|
|
1775
|
-
if (!database.objectStoreNames.contains(
|
|
1776
|
-
database.createObjectStore(
|
|
1845
|
+
if (!database.objectStoreNames.contains(storeName)) {
|
|
1846
|
+
database.createObjectStore(storeName, {
|
|
1777
1847
|
keyPath: 'id'
|
|
1778
1848
|
});
|
|
1779
1849
|
}
|
|
1780
1850
|
});
|
|
1781
1851
|
return requestToPromise(() => request);
|
|
1782
1852
|
};
|
|
1853
|
+
|
|
1854
|
+
const getDatabase = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName) => {
|
|
1855
|
+
const existingDatabasePromise = getDatabasePromise();
|
|
1856
|
+
if (existingDatabasePromise) {
|
|
1857
|
+
return existingDatabasePromise;
|
|
1858
|
+
}
|
|
1859
|
+
const nextDatabasePromise = openSessionsDatabase(databaseName, databaseVersion, storeName);
|
|
1860
|
+
setDatabasePromise(nextDatabasePromise);
|
|
1861
|
+
return nextDatabasePromise;
|
|
1862
|
+
};
|
|
1863
|
+
|
|
1864
|
+
const transactionToPromise = async createTransaction => {
|
|
1865
|
+
const transaction = createTransaction();
|
|
1866
|
+
const {
|
|
1867
|
+
promise,
|
|
1868
|
+
reject,
|
|
1869
|
+
resolve
|
|
1870
|
+
} = Promise.withResolvers();
|
|
1871
|
+
transaction.addEventListener('complete', () => {
|
|
1872
|
+
resolve();
|
|
1873
|
+
});
|
|
1874
|
+
transaction.addEventListener('error', () => {
|
|
1875
|
+
reject(toError(transaction.error));
|
|
1876
|
+
});
|
|
1877
|
+
transaction.addEventListener('abort', () => {
|
|
1878
|
+
reject(toError(transaction.error));
|
|
1879
|
+
});
|
|
1880
|
+
return promise;
|
|
1881
|
+
};
|
|
1882
|
+
|
|
1883
|
+
const clear = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName) => {
|
|
1884
|
+
const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
|
|
1885
|
+
const transaction = database.transaction(storeName, 'readwrite');
|
|
1886
|
+
const createTransaction = () => transaction;
|
|
1887
|
+
const store = transaction.objectStore(storeName);
|
|
1888
|
+
store.clear();
|
|
1889
|
+
await transactionToPromise(createTransaction);
|
|
1890
|
+
};
|
|
1891
|
+
|
|
1892
|
+
const deleteSession$1 = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName, id) => {
|
|
1893
|
+
const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
|
|
1894
|
+
const transaction = database.transaction(storeName, 'readwrite');
|
|
1895
|
+
const createTransaction = () => transaction;
|
|
1896
|
+
const store = transaction.objectStore(storeName);
|
|
1897
|
+
store.delete(id);
|
|
1898
|
+
await transactionToPromise(createTransaction);
|
|
1899
|
+
};
|
|
1900
|
+
|
|
1901
|
+
const getSession = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName, id) => {
|
|
1902
|
+
const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
|
|
1903
|
+
const transaction = database.transaction(storeName, 'readonly');
|
|
1904
|
+
const store = transaction.objectStore(storeName);
|
|
1905
|
+
const result = await requestToPromise(() => store.get(id));
|
|
1906
|
+
return result;
|
|
1907
|
+
};
|
|
1908
|
+
|
|
1909
|
+
const listSessions = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName) => {
|
|
1910
|
+
const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
|
|
1911
|
+
const transaction = database.transaction(storeName, 'readonly');
|
|
1912
|
+
const store = transaction.objectStore(storeName);
|
|
1913
|
+
const result = await requestToPromise(() => store.getAll());
|
|
1914
|
+
return result;
|
|
1915
|
+
};
|
|
1916
|
+
|
|
1917
|
+
const setSession = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName, session) => {
|
|
1918
|
+
const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
|
|
1919
|
+
const transaction = database.transaction(storeName, 'readwrite');
|
|
1920
|
+
const createTransaction = () => transaction;
|
|
1921
|
+
const store = transaction.objectStore(storeName);
|
|
1922
|
+
store.put(session);
|
|
1923
|
+
await transactionToPromise(createTransaction);
|
|
1924
|
+
};
|
|
1925
|
+
|
|
1783
1926
|
class IndexedDbChatSessionStorage {
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1927
|
+
constructor(options = {}) {
|
|
1928
|
+
this.state = {
|
|
1929
|
+
databaseName: options.databaseName || 'lvce-chat-view-sessions',
|
|
1930
|
+
databasePromise: undefined,
|
|
1931
|
+
databaseVersion: options.databaseVersion || 1,
|
|
1932
|
+
storeName: options.storeName || 'chat-sessions'
|
|
1933
|
+
};
|
|
1789
1934
|
}
|
|
1935
|
+
getDatabasePromise = () => {
|
|
1936
|
+
return this.state.databasePromise;
|
|
1937
|
+
};
|
|
1938
|
+
setDatabasePromise = databasePromise => {
|
|
1939
|
+
this.state.databasePromise = databasePromise;
|
|
1940
|
+
};
|
|
1790
1941
|
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);
|
|
1942
|
+
return clear(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName);
|
|
1797
1943
|
}
|
|
1798
1944
|
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);
|
|
1945
|
+
return deleteSession$1(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName, id);
|
|
1805
1946
|
}
|
|
1806
1947
|
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;
|
|
1948
|
+
return getSession(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName, id);
|
|
1812
1949
|
}
|
|
1813
1950
|
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;
|
|
1951
|
+
return listSessions(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName);
|
|
1819
1952
|
}
|
|
1820
1953
|
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);
|
|
1954
|
+
return setSession(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName, session);
|
|
1827
1955
|
}
|
|
1828
1956
|
}
|
|
1829
1957
|
|
|
@@ -1956,14 +2084,15 @@ const deleteSession = async (state, id) => {
|
|
|
1956
2084
|
};
|
|
1957
2085
|
};
|
|
1958
2086
|
|
|
1959
|
-
const
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
focus: 'composer',
|
|
1963
|
-
focused: true
|
|
1964
|
-
};
|
|
2087
|
+
const handleClickOpenRouterApiKeySettings = async state => {
|
|
2088
|
+
await openExternal(state.openRouterApiKeysSettingsUrl);
|
|
2089
|
+
return state;
|
|
1965
2090
|
};
|
|
1966
2091
|
|
|
2092
|
+
const openRouterApiKeyRequiredMessage = 'OpenRouter API key is not configured. Enter your OpenRouter API key below and click Save.';
|
|
2093
|
+
const openRouterRequestFailedMessage = 'OpenRouter request failed. Possible reasons:';
|
|
2094
|
+
const openRouterRequestFailureReasons = ['ContentSecurityPolicyViolation: Check DevTools for details.', 'OpenRouter server offline: Check DevTools for details.', 'Check your internet connection.'];
|
|
2095
|
+
|
|
1967
2096
|
const delay = async ms => {
|
|
1968
2097
|
await new Promise(resolve => setTimeout(resolve, ms));
|
|
1969
2098
|
};
|
|
@@ -1973,6 +2102,12 @@ const getMockAiResponse = async userMessage => {
|
|
|
1973
2102
|
return `Mock AI response: I received "${userMessage}".`;
|
|
1974
2103
|
};
|
|
1975
2104
|
|
|
2105
|
+
const defaultOpenRouterApiBaseUrl = 'https://openrouter.ai/api/v1';
|
|
2106
|
+
const getOpenRouterApiEndpoint = openRouterApiBaseUrl => {
|
|
2107
|
+
const trimmedBaseUrl = (openRouterApiBaseUrl || defaultOpenRouterApiBaseUrl).replace(/\/+$/, '');
|
|
2108
|
+
return `${trimmedBaseUrl}/chat/completions`;
|
|
2109
|
+
};
|
|
2110
|
+
|
|
1976
2111
|
const getTextContent = content => {
|
|
1977
2112
|
if (typeof content === 'string') {
|
|
1978
2113
|
return content;
|
|
@@ -1994,26 +2129,26 @@ const getTextContent = content => {
|
|
|
1994
2129
|
return textParts.join('\n');
|
|
1995
2130
|
};
|
|
1996
2131
|
|
|
1997
|
-
const defaultOpenRouterApiBaseUrl = 'https://openrouter.ai/api/v1';
|
|
1998
|
-
const getOpenRouterApiEndpoint = openRouterApiBaseUrl => {
|
|
1999
|
-
const trimmedBaseUrl = (openRouterApiBaseUrl || defaultOpenRouterApiBaseUrl).replace(/\/+$/, '');
|
|
2000
|
-
return `${trimmedBaseUrl}/chat/completions`;
|
|
2001
|
-
};
|
|
2002
2132
|
const getOpenRouterAssistantText = async (userText, modelId, openRouterApiKey, openRouterApiBaseUrl) => {
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2133
|
+
let response;
|
|
2134
|
+
try {
|
|
2135
|
+
response = await fetch(getOpenRouterApiEndpoint(openRouterApiBaseUrl), {
|
|
2136
|
+
body: JSON.stringify({
|
|
2137
|
+
messages: [{
|
|
2138
|
+
content: userText,
|
|
2139
|
+
role: 'user'
|
|
2140
|
+
}],
|
|
2141
|
+
model: modelId
|
|
2142
|
+
}),
|
|
2143
|
+
headers: {
|
|
2144
|
+
Authorization: `Bearer ${openRouterApiKey}`,
|
|
2145
|
+
'Content-Type': 'application/json'
|
|
2146
|
+
},
|
|
2147
|
+
method: 'POST'
|
|
2148
|
+
});
|
|
2149
|
+
} catch {
|
|
2150
|
+
throw new Error(openRouterRequestFailedMessage);
|
|
2151
|
+
}
|
|
2017
2152
|
if (!response.ok) {
|
|
2018
2153
|
throw new Error(`Failed to get OpenRouter response: ${response.status}`);
|
|
2019
2154
|
}
|
|
@@ -2037,32 +2172,45 @@ const getOpenRouterAssistantText = async (userText, modelId, openRouterApiKey, o
|
|
|
2037
2172
|
return getTextContent(content);
|
|
2038
2173
|
};
|
|
2039
2174
|
|
|
2175
|
+
/* eslint-disable @cspell/spellchecker */
|
|
2040
2176
|
const getOpenRouterModelId = selectedModelId => {
|
|
2041
|
-
|
|
2042
|
-
|
|
2177
|
+
const openRouterPrefix = 'openrouter/';
|
|
2178
|
+
if (selectedModelId.toLowerCase().startsWith(openRouterPrefix)) {
|
|
2179
|
+
return selectedModelId.slice(openRouterPrefix.length);
|
|
2043
2180
|
}
|
|
2044
2181
|
return selectedModelId;
|
|
2045
2182
|
};
|
|
2046
2183
|
|
|
2184
|
+
/* eslint-disable @cspell/spellchecker */
|
|
2185
|
+
|
|
2047
2186
|
const isOpenRouterModel = (selectedModelId, models) => {
|
|
2048
2187
|
const selectedModel = models.find(model => model.id === selectedModelId);
|
|
2049
|
-
|
|
2188
|
+
const normalizedProvider = selectedModel?.provider?.toLowerCase();
|
|
2189
|
+
if (normalizedProvider === 'openrouter' || normalizedProvider === 'open-router') {
|
|
2050
2190
|
return true;
|
|
2051
2191
|
}
|
|
2052
|
-
return selectedModelId.startsWith('
|
|
2192
|
+
return selectedModelId.toLowerCase().startsWith('openrouter/');
|
|
2053
2193
|
};
|
|
2054
2194
|
|
|
2055
2195
|
const getAiResponse = async (userText, nextMessageId, selectedModelId, models, openRouterApiKey, openRouterApiBaseUrl) => {
|
|
2056
2196
|
let text = '';
|
|
2057
|
-
const
|
|
2058
|
-
if (
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2197
|
+
const usesOpenRouterModel = isOpenRouterModel(selectedModelId, models);
|
|
2198
|
+
if (usesOpenRouterModel) {
|
|
2199
|
+
if (openRouterApiKey) {
|
|
2200
|
+
try {
|
|
2201
|
+
text = await getOpenRouterAssistantText(userText, getOpenRouterModelId(selectedModelId), openRouterApiKey, openRouterApiBaseUrl);
|
|
2202
|
+
} catch (error) {
|
|
2203
|
+
if (error instanceof Error && error.message) {
|
|
2204
|
+
text = error.message;
|
|
2205
|
+
} else {
|
|
2206
|
+
text = openRouterRequestFailedMessage;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
} else {
|
|
2210
|
+
text = openRouterApiKeyRequiredMessage;
|
|
2063
2211
|
}
|
|
2064
2212
|
}
|
|
2065
|
-
if (!text) {
|
|
2213
|
+
if (!text && !usesOpenRouterModel) {
|
|
2066
2214
|
text = await getMockAiResponse(userText);
|
|
2067
2215
|
}
|
|
2068
2216
|
const assistantTime = new Date().toLocaleTimeString([], {
|
|
@@ -2077,6 +2225,75 @@ const getAiResponse = async (userText, nextMessageId, selectedModelId, models, o
|
|
|
2077
2225
|
};
|
|
2078
2226
|
};
|
|
2079
2227
|
|
|
2228
|
+
const get = async key => {
|
|
2229
|
+
return getPreference(key);
|
|
2230
|
+
};
|
|
2231
|
+
const update = async settings => {
|
|
2232
|
+
await invoke('Preferences.update', settings);
|
|
2233
|
+
};
|
|
2234
|
+
|
|
2235
|
+
const setOpenRouterApiKey = async (state, openRouterApiKey, persist = true) => {
|
|
2236
|
+
if (persist) {
|
|
2237
|
+
await update({
|
|
2238
|
+
'secrets.openRouterApiKey': openRouterApiKey
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2241
|
+
return {
|
|
2242
|
+
...state,
|
|
2243
|
+
openRouterApiKey,
|
|
2244
|
+
openRouterApiKeyInput: openRouterApiKey
|
|
2245
|
+
};
|
|
2246
|
+
};
|
|
2247
|
+
|
|
2248
|
+
const handleClickSaveOpenRouterApiKey = async state => {
|
|
2249
|
+
const {
|
|
2250
|
+
openRouterApiKeyInput
|
|
2251
|
+
} = state;
|
|
2252
|
+
const openRouterApiKey = openRouterApiKeyInput.trim();
|
|
2253
|
+
if (!openRouterApiKey) {
|
|
2254
|
+
return state;
|
|
2255
|
+
}
|
|
2256
|
+
const updatedState = await setOpenRouterApiKey(state, openRouterApiKey);
|
|
2257
|
+
const session = updatedState.sessions.find(item => item.id === updatedState.selectedSessionId);
|
|
2258
|
+
if (!session) {
|
|
2259
|
+
return updatedState;
|
|
2260
|
+
}
|
|
2261
|
+
const lastMessage = session.messages.at(-1);
|
|
2262
|
+
const shouldRetryOpenRouter = lastMessage?.role === 'assistant' && lastMessage.text === openRouterApiKeyRequiredMessage;
|
|
2263
|
+
if (!shouldRetryOpenRouter) {
|
|
2264
|
+
return updatedState;
|
|
2265
|
+
}
|
|
2266
|
+
const previousUserMessage = session.messages.toReversed().find(item => item.role === 'user');
|
|
2267
|
+
if (!previousUserMessage) {
|
|
2268
|
+
return updatedState;
|
|
2269
|
+
}
|
|
2270
|
+
const assistantMessage = await getAiResponse(previousUserMessage.text, updatedState.nextMessageId, updatedState.selectedModelId, updatedState.models, openRouterApiKey, updatedState.openRouterApiBaseUrl);
|
|
2271
|
+
const updatedSession = {
|
|
2272
|
+
...session,
|
|
2273
|
+
messages: [...session.messages.slice(0, -1), assistantMessage]
|
|
2274
|
+
};
|
|
2275
|
+
await saveChatSession(updatedSession);
|
|
2276
|
+
const updatedSessions = updatedState.sessions.map(item => {
|
|
2277
|
+
if (item.id !== updatedState.selectedSessionId) {
|
|
2278
|
+
return item;
|
|
2279
|
+
}
|
|
2280
|
+
return updatedSession;
|
|
2281
|
+
});
|
|
2282
|
+
return {
|
|
2283
|
+
...updatedState,
|
|
2284
|
+
nextMessageId: updatedState.nextMessageId + 1,
|
|
2285
|
+
sessions: updatedSessions
|
|
2286
|
+
};
|
|
2287
|
+
};
|
|
2288
|
+
|
|
2289
|
+
const focusInput = state => {
|
|
2290
|
+
return {
|
|
2291
|
+
...state,
|
|
2292
|
+
focus: 'composer',
|
|
2293
|
+
focused: true
|
|
2294
|
+
};
|
|
2295
|
+
};
|
|
2296
|
+
|
|
2080
2297
|
const handleSubmit = async state => {
|
|
2081
2298
|
const {
|
|
2082
2299
|
composerValue,
|
|
@@ -2195,6 +2412,37 @@ const handleClickSend = async state => {
|
|
|
2195
2412
|
return handleSubmit(submitState);
|
|
2196
2413
|
};
|
|
2197
2414
|
|
|
2415
|
+
const Composer = 'composer';
|
|
2416
|
+
const Send = 'send';
|
|
2417
|
+
const Back = 'back';
|
|
2418
|
+
const Model = 'model';
|
|
2419
|
+
const CreateSession = 'create-session';
|
|
2420
|
+
const Settings = 'settings';
|
|
2421
|
+
const CloseChat = 'close-chat';
|
|
2422
|
+
const SessionDelete = 'SessionDelete';
|
|
2423
|
+
const SessionPrefix = 'session:';
|
|
2424
|
+
const RenamePrefix = 'session-rename:';
|
|
2425
|
+
const getSessionInputName = sessionId => {
|
|
2426
|
+
return `${SessionPrefix}${sessionId}`;
|
|
2427
|
+
};
|
|
2428
|
+
const isSessionInputName = name => {
|
|
2429
|
+
return name.startsWith(SessionPrefix);
|
|
2430
|
+
};
|
|
2431
|
+
const getSessionIdFromInputName = name => {
|
|
2432
|
+
return name.slice(SessionPrefix.length);
|
|
2433
|
+
};
|
|
2434
|
+
const isRenameInputName = name => {
|
|
2435
|
+
return name.startsWith(RenamePrefix);
|
|
2436
|
+
};
|
|
2437
|
+
const getRenameIdFromInputName = name => {
|
|
2438
|
+
return name.slice(RenamePrefix.length);
|
|
2439
|
+
};
|
|
2440
|
+
|
|
2441
|
+
/* eslint-disable @cspell/spellchecker */
|
|
2442
|
+
const OpenRouterApiKeyInput = 'open-router-api-key';
|
|
2443
|
+
const SaveOpenRouterApiKey = 'save-openrouter-api-key';
|
|
2444
|
+
const OpenOpenRouterApiKeySettings = 'open-openrouter-api-key-settings';
|
|
2445
|
+
|
|
2198
2446
|
const selectSession = async (state, id) => {
|
|
2199
2447
|
const exists = state.sessions.some(session => session.id === id);
|
|
2200
2448
|
if (!exists) {
|
|
@@ -2252,32 +2500,33 @@ const handleClickList = async (state, eventX, eventY) => {
|
|
|
2252
2500
|
return selectListIndex(state, index);
|
|
2253
2501
|
};
|
|
2254
2502
|
|
|
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
2503
|
const handleClick = async (state, name, id = '') => {
|
|
2261
2504
|
if (!name) {
|
|
2262
2505
|
return state;
|
|
2263
2506
|
}
|
|
2264
|
-
if (name ===
|
|
2507
|
+
if (name === CreateSession) {
|
|
2265
2508
|
return createSession(state);
|
|
2266
2509
|
}
|
|
2267
|
-
if (name
|
|
2268
|
-
const
|
|
2269
|
-
return selectSession(state,
|
|
2510
|
+
if (isSessionInputName(name)) {
|
|
2511
|
+
const sessionId = getSessionIdFromInputName(name);
|
|
2512
|
+
return selectSession(state, sessionId);
|
|
2270
2513
|
}
|
|
2271
|
-
if (name
|
|
2272
|
-
const
|
|
2273
|
-
return startRename(state,
|
|
2514
|
+
if (isRenameInputName(name)) {
|
|
2515
|
+
const sessionId = getRenameIdFromInputName(name);
|
|
2516
|
+
return startRename(state, sessionId);
|
|
2274
2517
|
}
|
|
2275
|
-
if (name ===
|
|
2518
|
+
if (name === SessionDelete) {
|
|
2276
2519
|
return deleteSession(state, id);
|
|
2277
2520
|
}
|
|
2278
|
-
if (name ===
|
|
2521
|
+
if (name === Send) {
|
|
2279
2522
|
return handleClickSend(state);
|
|
2280
2523
|
}
|
|
2524
|
+
if (name === SaveOpenRouterApiKey) {
|
|
2525
|
+
return handleClickSaveOpenRouterApiKey(state);
|
|
2526
|
+
}
|
|
2527
|
+
if (name === OpenOpenRouterApiKeySettings) {
|
|
2528
|
+
return handleClickOpenRouterApiKeySettings(state);
|
|
2529
|
+
}
|
|
2281
2530
|
return state;
|
|
2282
2531
|
};
|
|
2283
2532
|
|
|
@@ -2307,7 +2556,16 @@ const handleClickSettings = async () => {
|
|
|
2307
2556
|
await invoke('Main.openUri', 'app://settings.json');
|
|
2308
2557
|
};
|
|
2309
2558
|
|
|
2310
|
-
const handleInput = async (state, value, inputSource = 'user') => {
|
|
2559
|
+
const handleInput = async (state, name, value, inputSource = 'user') => {
|
|
2560
|
+
if (name === OpenRouterApiKeyInput) {
|
|
2561
|
+
return {
|
|
2562
|
+
...state,
|
|
2563
|
+
openRouterApiKeyInput: value
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
if (name !== Composer) {
|
|
2567
|
+
return state;
|
|
2568
|
+
}
|
|
2311
2569
|
return {
|
|
2312
2570
|
...state,
|
|
2313
2571
|
composerValue: value,
|
|
@@ -2316,24 +2574,24 @@ const handleInput = async (state, value, inputSource = 'user') => {
|
|
|
2316
2574
|
};
|
|
2317
2575
|
|
|
2318
2576
|
const handleInputFocus = async (state, name) => {
|
|
2319
|
-
if (name ===
|
|
2577
|
+
if (name === Composer) {
|
|
2320
2578
|
return focusInput(state);
|
|
2321
2579
|
}
|
|
2322
|
-
if (name ===
|
|
2580
|
+
if (name === Send) {
|
|
2323
2581
|
return {
|
|
2324
2582
|
...state,
|
|
2325
2583
|
focus: 'send-button',
|
|
2326
2584
|
focused: true
|
|
2327
2585
|
};
|
|
2328
2586
|
}
|
|
2329
|
-
if (name
|
|
2587
|
+
if (isSessionInputName(name) || name === SessionDelete) {
|
|
2330
2588
|
return {
|
|
2331
2589
|
...state,
|
|
2332
2590
|
focus: 'list',
|
|
2333
2591
|
focused: true
|
|
2334
2592
|
};
|
|
2335
2593
|
}
|
|
2336
|
-
if (name ===
|
|
2594
|
+
if (name === CreateSession || name === Settings || name === CloseChat || name === Back) {
|
|
2337
2595
|
return {
|
|
2338
2596
|
...state,
|
|
2339
2597
|
focus: 'header',
|
|
@@ -2412,7 +2670,7 @@ const handleModelChange = async (state, value) => {
|
|
|
2412
2670
|
};
|
|
2413
2671
|
|
|
2414
2672
|
const handleNewline = async state => {
|
|
2415
|
-
return handleInput(state, `${state.composerValue}\n`);
|
|
2673
|
+
return handleInput(state, Composer, `${state.composerValue}\n`);
|
|
2416
2674
|
};
|
|
2417
2675
|
|
|
2418
2676
|
const id = 7201;
|
|
@@ -2441,36 +2699,6 @@ const isObject = value => {
|
|
|
2441
2699
|
return typeof value === 'object' && value !== null;
|
|
2442
2700
|
};
|
|
2443
2701
|
|
|
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
2702
|
const getSavedSelectedModelId = savedState => {
|
|
2475
2703
|
if (!isObject(savedState)) {
|
|
2476
2704
|
return undefined;
|
|
@@ -2523,10 +2751,6 @@ const getSavedViewMode = savedState => {
|
|
|
2523
2751
|
return viewMode;
|
|
2524
2752
|
};
|
|
2525
2753
|
|
|
2526
|
-
const get = async key => {
|
|
2527
|
-
return getPreference(key);
|
|
2528
|
-
};
|
|
2529
|
-
|
|
2530
2754
|
const toSummarySession = session => {
|
|
2531
2755
|
return {
|
|
2532
2756
|
id: session.id,
|
|
@@ -2550,7 +2774,6 @@ const loadSelectedSessionMessages = async (sessions, selectedSessionId) => {
|
|
|
2550
2774
|
});
|
|
2551
2775
|
};
|
|
2552
2776
|
const loadContent = async (state, savedState) => {
|
|
2553
|
-
const savedBounds = getSavedBounds(savedState);
|
|
2554
2777
|
const savedSelectedModelId = getSavedSelectedModelId(savedState);
|
|
2555
2778
|
const savedViewMode = getSavedViewMode(savedState);
|
|
2556
2779
|
let openRouterApiKey = '';
|
|
@@ -2584,9 +2807,9 @@ const loadContent = async (state, savedState) => {
|
|
|
2584
2807
|
const viewMode = sessions.length === 0 || !selectedSessionId ? 'list' : preferredViewMode === 'detail' ? 'detail' : 'list';
|
|
2585
2808
|
return {
|
|
2586
2809
|
...state,
|
|
2587
|
-
...savedBounds,
|
|
2588
2810
|
initial: false,
|
|
2589
2811
|
openRouterApiKey,
|
|
2812
|
+
openRouterApiKeyInput: openRouterApiKey,
|
|
2590
2813
|
selectedModelId,
|
|
2591
2814
|
selectedSessionId,
|
|
2592
2815
|
sessions,
|
|
@@ -2595,11 +2818,14 @@ const loadContent = async (state, savedState) => {
|
|
|
2595
2818
|
};
|
|
2596
2819
|
|
|
2597
2820
|
const openMockSession = async (state, mockSessionId, mockChatMessages) => {
|
|
2821
|
+
const {
|
|
2822
|
+
sessions: currentSessions
|
|
2823
|
+
} = state;
|
|
2598
2824
|
if (!mockSessionId) {
|
|
2599
2825
|
return state;
|
|
2600
2826
|
}
|
|
2601
|
-
const existingSession =
|
|
2602
|
-
const sessions = existingSession ?
|
|
2827
|
+
const existingSession = currentSessions.find(session => session.id === mockSessionId);
|
|
2828
|
+
const sessions = existingSession ? currentSessions.map(session => {
|
|
2603
2829
|
if (session.id !== mockSessionId) {
|
|
2604
2830
|
return session;
|
|
2605
2831
|
}
|
|
@@ -2607,7 +2833,7 @@ const openMockSession = async (state, mockSessionId, mockChatMessages) => {
|
|
|
2607
2833
|
...session,
|
|
2608
2834
|
messages: mockChatMessages
|
|
2609
2835
|
};
|
|
2610
|
-
}) : [...
|
|
2836
|
+
}) : [...currentSessions, {
|
|
2611
2837
|
id: mockSessionId,
|
|
2612
2838
|
messages: mockChatMessages,
|
|
2613
2839
|
title: mockSessionId
|
|
@@ -2657,6 +2883,7 @@ const renderFocusContext = (oldState, newState) => {
|
|
|
2657
2883
|
return [SetFocusContext, newState.uid, FocusChatInput];
|
|
2658
2884
|
};
|
|
2659
2885
|
|
|
2886
|
+
const Actions = 'Actions';
|
|
2660
2887
|
const ChatActions = 'ChatActions';
|
|
2661
2888
|
const ChatName = 'ChatName';
|
|
2662
2889
|
const ChatSendArea = 'ChatSendArea';
|
|
@@ -2665,7 +2892,11 @@ const ChatSendAreaBottom = 'ChatSendAreaBottom';
|
|
|
2665
2892
|
const ChatSendAreaContent = 'ChatSendAreaContent';
|
|
2666
2893
|
const Chat = 'Chat';
|
|
2667
2894
|
const ChatHeader = 'ChatHeader';
|
|
2895
|
+
const Button = 'Button';
|
|
2896
|
+
const ButtonPrimary = 'ButtonPrimary';
|
|
2897
|
+
const ButtonSecondary = 'ButtonSecondary';
|
|
2668
2898
|
const IconButton = 'IconButton';
|
|
2899
|
+
const InputBox = 'InputBox';
|
|
2669
2900
|
const Label = 'Label';
|
|
2670
2901
|
const LabelDetail = 'LabelDetail';
|
|
2671
2902
|
const ChatList = 'ChatList';
|
|
@@ -2726,11 +2957,11 @@ const getSendButtonDom = isSendDisabled => {
|
|
|
2726
2957
|
childCount: 1,
|
|
2727
2958
|
className: sendButtonClassName,
|
|
2728
2959
|
disabled: isSendDisabled,
|
|
2729
|
-
name:
|
|
2960
|
+
name: Send,
|
|
2730
2961
|
onClick: HandleSubmit,
|
|
2731
|
-
role: Button$
|
|
2732
|
-
title: sendMessage,
|
|
2733
|
-
type: Button
|
|
2962
|
+
role: Button$2,
|
|
2963
|
+
title: sendMessage(),
|
|
2964
|
+
type: Button$1
|
|
2734
2965
|
}, {
|
|
2735
2966
|
childCount: 0,
|
|
2736
2967
|
className: 'MaskIcon MaskIconSend',
|
|
@@ -2787,10 +3018,10 @@ const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOvervie
|
|
|
2787
3018
|
}, {
|
|
2788
3019
|
childCount: 0,
|
|
2789
3020
|
className: MultilineInputBox,
|
|
2790
|
-
name:
|
|
3021
|
+
name: Composer,
|
|
2791
3022
|
onFocus: HandleFocus,
|
|
2792
3023
|
onInput: HandleInput,
|
|
2793
|
-
placeholder: composePlaceholder,
|
|
3024
|
+
placeholder: composePlaceholder(),
|
|
2794
3025
|
rows: 4,
|
|
2795
3026
|
type: TextArea,
|
|
2796
3027
|
value: composerValue
|
|
@@ -2801,7 +3032,7 @@ const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOvervie
|
|
|
2801
3032
|
}, {
|
|
2802
3033
|
childCount: models.length,
|
|
2803
3034
|
className: Select,
|
|
2804
|
-
name:
|
|
3035
|
+
name: Model,
|
|
2805
3036
|
onInput: HandleModelChange,
|
|
2806
3037
|
type: Select$1,
|
|
2807
3038
|
value: selectedModelId
|
|
@@ -2812,11 +3043,11 @@ const getBackButtonVirtualDom = () => {
|
|
|
2812
3043
|
return [{
|
|
2813
3044
|
childCount: 1,
|
|
2814
3045
|
className: IconButton,
|
|
2815
|
-
name:
|
|
3046
|
+
name: Back,
|
|
2816
3047
|
onClick: HandleClickBack,
|
|
2817
|
-
role: Button$
|
|
2818
|
-
title: backToChats,
|
|
2819
|
-
type: Button
|
|
3048
|
+
role: Button$2,
|
|
3049
|
+
title: backToChats(),
|
|
3050
|
+
type: Button$1
|
|
2820
3051
|
}, {
|
|
2821
3052
|
childCount: 0,
|
|
2822
3053
|
className: 'MaskIcon MaskIconArrowLeft',
|
|
@@ -2830,9 +3061,9 @@ const getHeaderActionVirtualDom = item => {
|
|
|
2830
3061
|
className: IconButton,
|
|
2831
3062
|
name: item.name,
|
|
2832
3063
|
onClick: item.onClick,
|
|
2833
|
-
role: Button$
|
|
3064
|
+
role: Button$2,
|
|
2834
3065
|
title: item.title,
|
|
2835
|
-
type: Button
|
|
3066
|
+
type: Button$1
|
|
2836
3067
|
}, {
|
|
2837
3068
|
childCount: 0,
|
|
2838
3069
|
className: item.icon,
|
|
@@ -2843,19 +3074,19 @@ const getHeaderActionVirtualDom = item => {
|
|
|
2843
3074
|
const getChatHeaderActionsDom = () => {
|
|
2844
3075
|
const items = [{
|
|
2845
3076
|
icon: 'MaskIcon MaskIconAdd',
|
|
2846
|
-
name:
|
|
3077
|
+
name: CreateSession,
|
|
2847
3078
|
onClick: HandleClickNew,
|
|
2848
|
-
title: newChat
|
|
3079
|
+
title: newChat()
|
|
2849
3080
|
}, {
|
|
2850
3081
|
icon: 'MaskIcon MaskIconSettingsGear',
|
|
2851
|
-
name:
|
|
3082
|
+
name: Settings,
|
|
2852
3083
|
onClick: HandleClickSettings,
|
|
2853
|
-
title: settings
|
|
3084
|
+
title: settings()
|
|
2854
3085
|
}, {
|
|
2855
3086
|
icon: 'MaskIcon MaskIconClose',
|
|
2856
|
-
name:
|
|
3087
|
+
name: CloseChat,
|
|
2857
3088
|
onClick: HandleClickClose,
|
|
2858
|
-
title: closeChat
|
|
3089
|
+
title: closeChat()
|
|
2859
3090
|
}];
|
|
2860
3091
|
return [{
|
|
2861
3092
|
childCount: items.length,
|
|
@@ -2880,47 +3111,96 @@ const getChatHeaderDomDetailMode = selectedSessionTitle => {
|
|
|
2880
3111
|
}, text(selectedSessionTitle), ...getChatHeaderActionsDom()];
|
|
2881
3112
|
};
|
|
2882
3113
|
|
|
2883
|
-
const
|
|
3114
|
+
const getMissingOpenRouterApiKeyDom = openRouterApiKeyInput => {
|
|
3115
|
+
return [{
|
|
3116
|
+
childCount: 2,
|
|
3117
|
+
type: Div
|
|
3118
|
+
}, {
|
|
3119
|
+
childCount: 0,
|
|
3120
|
+
className: InputBox,
|
|
3121
|
+
name: OpenRouterApiKeyInput,
|
|
3122
|
+
onInput: HandleInput,
|
|
3123
|
+
placeholder: openRouterApiKeyPlaceholder(),
|
|
3124
|
+
type: Input,
|
|
3125
|
+
value: openRouterApiKeyInput
|
|
3126
|
+
}, {
|
|
3127
|
+
childCount: 2,
|
|
3128
|
+
className: Actions,
|
|
3129
|
+
type: Div
|
|
3130
|
+
}, {
|
|
3131
|
+
childCount: 1,
|
|
3132
|
+
className: mergeClassNames(Button, ButtonPrimary),
|
|
3133
|
+
name: SaveOpenRouterApiKey,
|
|
3134
|
+
onClick: HandleClick,
|
|
3135
|
+
type: Button$1
|
|
3136
|
+
}, text(save()), {
|
|
3137
|
+
childCount: 1,
|
|
3138
|
+
className: mergeClassNames(Button, ButtonSecondary),
|
|
3139
|
+
name: OpenOpenRouterApiKeySettings,
|
|
3140
|
+
onClick: HandleClick,
|
|
3141
|
+
type: Button$1
|
|
3142
|
+
}, text(getOpenRouterApiKey())];
|
|
3143
|
+
};
|
|
3144
|
+
|
|
3145
|
+
const getOpenRouterRequestFailedDom = () => {
|
|
3146
|
+
return [{
|
|
3147
|
+
childCount: openRouterRequestFailureReasons.length,
|
|
3148
|
+
className: Markdown,
|
|
3149
|
+
type: Ol
|
|
3150
|
+
}, ...openRouterRequestFailureReasons.flatMap(reason => {
|
|
3151
|
+
return [{
|
|
3152
|
+
childCount: 1,
|
|
3153
|
+
type: Li
|
|
3154
|
+
}, text(reason)];
|
|
3155
|
+
})];
|
|
3156
|
+
};
|
|
3157
|
+
const getChatMessageDom = (message, openRouterApiKeyInput) => {
|
|
2884
3158
|
const roleClassName = message.role === 'user' ? MessageUser : MessageAssistant;
|
|
3159
|
+
const isOpenRouterApiKeyMissingMessage = message.role === 'assistant' && message.text === openRouterApiKeyRequiredMessage;
|
|
3160
|
+
const isOpenRouterRequestFailedMessage = message.role === 'assistant' && message.text === openRouterRequestFailedMessage;
|
|
3161
|
+
const extraChildCount = isOpenRouterApiKeyMissingMessage || isOpenRouterRequestFailedMessage ? 2 : 1;
|
|
2885
3162
|
return [{
|
|
2886
3163
|
childCount: 1,
|
|
2887
3164
|
className: mergeClassNames(Message, roleClassName),
|
|
2888
3165
|
type: Div
|
|
2889
3166
|
}, {
|
|
2890
|
-
childCount:
|
|
3167
|
+
childCount: extraChildCount,
|
|
2891
3168
|
className: ChatMessageContent,
|
|
2892
3169
|
type: Div
|
|
2893
3170
|
}, {
|
|
2894
3171
|
childCount: 1,
|
|
2895
3172
|
className: Markdown,
|
|
2896
3173
|
type: P
|
|
2897
|
-
}, text(message.text)];
|
|
3174
|
+
}, text(message.text), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : [])];
|
|
2898
3175
|
};
|
|
2899
3176
|
|
|
2900
|
-
const
|
|
3177
|
+
const getEmptyMessagesDom = () => {
|
|
3178
|
+
return [{
|
|
3179
|
+
childCount: 1,
|
|
3180
|
+
className: ChatWelcomeMessage,
|
|
3181
|
+
type: Div
|
|
3182
|
+
}, text(startConversation())];
|
|
3183
|
+
};
|
|
3184
|
+
const getMessagesDom = (messages, openRouterApiKeyInput) => {
|
|
2901
3185
|
if (messages.length === 0) {
|
|
2902
|
-
return
|
|
2903
|
-
childCount: 1,
|
|
2904
|
-
className: ChatWelcomeMessage,
|
|
2905
|
-
type: Div
|
|
2906
|
-
}, text(startConversation)];
|
|
3186
|
+
return getEmptyMessagesDom();
|
|
2907
3187
|
}
|
|
2908
3188
|
return [{
|
|
2909
3189
|
childCount: messages.length,
|
|
2910
3190
|
className: 'ChatMessages',
|
|
2911
3191
|
type: Div
|
|
2912
|
-
}, ...messages.flatMap(getChatMessageDom)];
|
|
3192
|
+
}, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput))];
|
|
2913
3193
|
};
|
|
2914
3194
|
|
|
2915
|
-
const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
|
|
3195
|
+
const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
|
|
2916
3196
|
const selectedSession = sessions.find(session => session.id === selectedSessionId);
|
|
2917
|
-
const selectedSessionTitle = selectedSession?.title || chatTitle;
|
|
3197
|
+
const selectedSessionTitle = selectedSession?.title || chatTitle();
|
|
2918
3198
|
const messages = selectedSession ? selectedSession.messages : [];
|
|
2919
3199
|
return [{
|
|
2920
3200
|
childCount: 3,
|
|
2921
3201
|
className: mergeClassNames(Viewlet, Chat),
|
|
2922
3202
|
type: Div
|
|
2923
|
-
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax)];
|
|
3203
|
+
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax)];
|
|
2924
3204
|
};
|
|
2925
3205
|
|
|
2926
3206
|
const getChatHeaderListModeDom = () => {
|
|
@@ -2932,7 +3212,7 @@ const getChatHeaderListModeDom = () => {
|
|
|
2932
3212
|
childCount: 1,
|
|
2933
3213
|
className: Label,
|
|
2934
3214
|
type: Span
|
|
2935
|
-
}, text(chats), ...getChatHeaderActionsDom()];
|
|
3215
|
+
}, text(chats()), ...getChatHeaderActionsDom()];
|
|
2936
3216
|
};
|
|
2937
3217
|
|
|
2938
3218
|
const getEmptyChatSessionsDom = () => {
|
|
@@ -2944,7 +3224,7 @@ const getEmptyChatSessionsDom = () => {
|
|
|
2944
3224
|
childCount: 1,
|
|
2945
3225
|
className: Label,
|
|
2946
3226
|
type: Div
|
|
2947
|
-
}, text(clickToOpenNewChat)];
|
|
3227
|
+
}, text(clickToOpenNewChat())];
|
|
2948
3228
|
};
|
|
2949
3229
|
|
|
2950
3230
|
const getSessionDom = session => {
|
|
@@ -2956,7 +3236,7 @@ const getSessionDom = session => {
|
|
|
2956
3236
|
}, {
|
|
2957
3237
|
childCount: 1,
|
|
2958
3238
|
className: ChatListItemLabel,
|
|
2959
|
-
name:
|
|
3239
|
+
name: getSessionInputName(session.id),
|
|
2960
3240
|
onContextMenu: HandleListContextMenu,
|
|
2961
3241
|
tabIndex: 0,
|
|
2962
3242
|
type: Div
|
|
@@ -2968,12 +3248,12 @@ const getSessionDom = session => {
|
|
|
2968
3248
|
childCount: 1,
|
|
2969
3249
|
className: IconButton,
|
|
2970
3250
|
'data-id': session.id,
|
|
2971
|
-
name:
|
|
3251
|
+
name: SessionDelete,
|
|
2972
3252
|
onClick: HandleClickDelete,
|
|
2973
|
-
role: Button$
|
|
3253
|
+
role: Button$2,
|
|
2974
3254
|
tabIndex: 0,
|
|
2975
|
-
title: deleteChatSession$1,
|
|
2976
|
-
type: Button
|
|
3255
|
+
title: deleteChatSession$1(),
|
|
3256
|
+
type: Button$1
|
|
2977
3257
|
}, text('🗑')];
|
|
2978
3258
|
};
|
|
2979
3259
|
|
|
@@ -3001,13 +3281,13 @@ const getChatModeUnsupportedVirtualDom = () => {
|
|
|
3001
3281
|
return [{
|
|
3002
3282
|
childCount: 1,
|
|
3003
3283
|
type: Div
|
|
3004
|
-
}, text(
|
|
3284
|
+
}, text(unknownViewMode())];
|
|
3005
3285
|
};
|
|
3006
3286
|
|
|
3007
|
-
const getChatVirtualDom = (sessions, selectedSessionId, composerValue, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
|
|
3287
|
+
const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
|
|
3008
3288
|
switch (viewMode) {
|
|
3009
3289
|
case 'detail':
|
|
3010
|
-
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
3290
|
+
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
3011
3291
|
case 'list':
|
|
3012
3292
|
return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
3013
3293
|
default:
|
|
@@ -3020,6 +3300,7 @@ const renderItems = (oldState, newState) => {
|
|
|
3020
3300
|
composerValue,
|
|
3021
3301
|
initial,
|
|
3022
3302
|
models,
|
|
3303
|
+
openRouterApiKeyInput,
|
|
3023
3304
|
selectedModelId,
|
|
3024
3305
|
selectedSessionId,
|
|
3025
3306
|
sessions,
|
|
@@ -3032,7 +3313,7 @@ const renderItems = (oldState, newState) => {
|
|
|
3032
3313
|
if (initial) {
|
|
3033
3314
|
return [SetDom2, uid, []];
|
|
3034
3315
|
}
|
|
3035
|
-
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
3316
|
+
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
|
|
3036
3317
|
return [SetDom2, uid, dom];
|
|
3037
3318
|
};
|
|
3038
3319
|
|
|
@@ -3047,7 +3328,7 @@ const renderValue = (oldState, newState) => {
|
|
|
3047
3328
|
const {
|
|
3048
3329
|
composerValue
|
|
3049
3330
|
} = newState;
|
|
3050
|
-
return [SetValueByName, newState.uid,
|
|
3331
|
+
return [SetValueByName, newState.uid, Composer, composerValue];
|
|
3051
3332
|
};
|
|
3052
3333
|
|
|
3053
3334
|
const getRenderer = diffType => {
|
|
@@ -3119,7 +3400,7 @@ const renderEventListeners = () => {
|
|
|
3119
3400
|
params: ['handleClickList', ClientX, ClientY]
|
|
3120
3401
|
}, {
|
|
3121
3402
|
name: HandleInput,
|
|
3122
|
-
params: ['handleInput', TargetValue]
|
|
3403
|
+
params: ['handleInput', TargetName, TargetValue]
|
|
3123
3404
|
}, {
|
|
3124
3405
|
name: HandleModelChange,
|
|
3125
3406
|
params: ['handleModelChange', TargetValue]
|
|
@@ -3144,6 +3425,7 @@ const reset = async state => {
|
|
|
3144
3425
|
return {
|
|
3145
3426
|
...state,
|
|
3146
3427
|
composerValue: '',
|
|
3428
|
+
openRouterApiKey: '',
|
|
3147
3429
|
selectedSessionId: '',
|
|
3148
3430
|
sessions: [],
|
|
3149
3431
|
viewMode: 'list'
|
|
@@ -3187,15 +3469,15 @@ const saveState = state => {
|
|
|
3187
3469
|
const dummySessions = [{
|
|
3188
3470
|
id: 'session-a',
|
|
3189
3471
|
messages: [],
|
|
3190
|
-
title: dummyChatA
|
|
3472
|
+
title: dummyChatA()
|
|
3191
3473
|
}, {
|
|
3192
3474
|
id: 'session-b',
|
|
3193
3475
|
messages: [],
|
|
3194
|
-
title: dummyChatB
|
|
3476
|
+
title: dummyChatB()
|
|
3195
3477
|
}, {
|
|
3196
3478
|
id: 'session-c',
|
|
3197
3479
|
messages: [],
|
|
3198
|
-
title: dummyChatC
|
|
3480
|
+
title: dummyChatC()
|
|
3199
3481
|
}];
|
|
3200
3482
|
const setChatList = state => {
|
|
3201
3483
|
return {
|
|
@@ -3213,6 +3495,7 @@ const commandMap = {
|
|
|
3213
3495
|
'Chat.enterNewLine': wrapCommand(handleNewline),
|
|
3214
3496
|
'Chat.getCommandIds': getCommandIds,
|
|
3215
3497
|
'Chat.getKeyBindings': getKeyBindings,
|
|
3498
|
+
'Chat.getSelectedSessionId': wrapGetter(getSelectedSessionId),
|
|
3216
3499
|
'Chat.handleChatListContextMenu': handleChatListContextMenu,
|
|
3217
3500
|
'Chat.handleClick': wrapCommand(handleClick),
|
|
3218
3501
|
'Chat.handleClickBack': wrapCommand(handleClickBack),
|
|
@@ -3237,6 +3520,7 @@ const commandMap = {
|
|
|
3237
3520
|
'Chat.resize': wrapCommand(resize),
|
|
3238
3521
|
'Chat.saveState': wrapGetter(saveState),
|
|
3239
3522
|
'Chat.setChatList': wrapCommand(setChatList),
|
|
3523
|
+
'Chat.setOpenRouterApiKey': wrapCommand(setOpenRouterApiKey),
|
|
3240
3524
|
'Chat.terminate': terminate
|
|
3241
3525
|
};
|
|
3242
3526
|
|