@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.
@@ -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 = i18nString('Chat');
1195
- const chats = i18nString('Chats');
1196
- const newChat = i18nString('New Chat');
1197
- const backToChats = i18nString('Back to chats');
1198
- const settings = i18nString('Settings');
1199
- const closeChat = i18nString('Close Chat');
1200
- const clickToOpenNewChat = i18nString('Click the + button to open a new chat.');
1201
- const startConversation = i18nString('Start a conversation by typing below.');
1202
- i18nString('You');
1203
- i18nString('Assistant');
1204
- const composePlaceholder = i18nString('Type your message. Enter to send, Shift+Enter for newline.');
1205
- const sendMessage = i18nString('Send message');
1206
- i18nString('Send');
1207
- const deleteChatSession$1 = i18nString('Delete chat session');
1208
- const defaultSessionTitle = i18nString('Chat 1');
1209
- const dummyChatA = i18nString('Dummy Chat A');
1210
- const dummyChatB = i18nString('Dummy Chat B');
1211
- const dummyChatC = i18nString('Dummy Chat C');
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$1 = '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
- return new Promise((resolve, reject) => {
1749
- request.addEventListener('success', () => {
1750
- resolve(request.result);
1751
- });
1752
- request.addEventListener('error', () => {
1753
- reject(toError(request.error));
1754
- });
1827
+ const {
1828
+ promise,
1829
+ reject,
1830
+ resolve
1831
+ } = Promise.withResolvers();
1832
+ request.addEventListener('success', () => {
1833
+ resolve(request.result);
1755
1834
  });
1756
- };
1757
- const transactionToPromise = async createTransaction => {
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
- const openSessionsDatabase = async () => {
1772
- const request = indexedDB.open(LVCE_CHAT_SESSIONS_DB_NAME, LVCE_CHAT_SESSIONS_DB_VERSION);
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(LVCE_CHAT_SESSIONS_STORE)) {
1776
- database.createObjectStore(LVCE_CHAT_SESSIONS_STORE, {
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
- async getDatabase() {
1785
- if (!this.databasePromise) {
1786
- this.databasePromise = openSessionsDatabase();
1787
- }
1788
- return this.databasePromise;
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
- const database = await this.getDatabase();
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
- const database = await this.getDatabase();
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
- const database = await this.getDatabase();
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
- const database = await this.getDatabase();
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
- const database = await this.getDatabase();
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 focusInput = state => {
1960
- return {
1961
- ...state,
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
- const response = await fetch(getOpenRouterApiEndpoint(openRouterApiBaseUrl), {
2004
- body: JSON.stringify({
2005
- messages: [{
2006
- content: userText,
2007
- role: 'user'
2008
- }],
2009
- model: modelId
2010
- }),
2011
- headers: {
2012
- Authorization: `Bearer ${openRouterApiKey}`,
2013
- 'Content-Type': 'application/json'
2014
- },
2015
- method: 'POST'
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
- if (selectedModelId.startsWith('openRouter/')) {
2042
- return selectedModelId.slice('openRouter/'.length);
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
- if (selectedModel?.provider === 'openRouter') {
2188
+ const normalizedProvider = selectedModel?.provider?.toLowerCase();
2189
+ if (normalizedProvider === 'openrouter' || normalizedProvider === 'open-router') {
2050
2190
  return true;
2051
2191
  }
2052
- return selectedModelId.startsWith('openRouter/');
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 shouldUseOpenRouter = isOpenRouterModel(selectedModelId, models) && openRouterApiKey;
2058
- if (shouldUseOpenRouter) {
2059
- try {
2060
- text = await getOpenRouterAssistantText(userText, getOpenRouterModelId(selectedModelId), openRouterApiKey, openRouterApiBaseUrl);
2061
- } catch {
2062
- text = '';
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 === CREATE_SESSION) {
2507
+ if (name === CreateSession) {
2265
2508
  return createSession(state);
2266
2509
  }
2267
- if (name.startsWith(SESSION_PREFIX)) {
2268
- const id = name.slice(SESSION_PREFIX.length);
2269
- return selectSession(state, id);
2510
+ if (isSessionInputName(name)) {
2511
+ const sessionId = getSessionIdFromInputName(name);
2512
+ return selectSession(state, sessionId);
2270
2513
  }
2271
- if (name.startsWith(RENAME_PREFIX)) {
2272
- const id = name.slice(RENAME_PREFIX.length);
2273
- return startRename(state, id);
2514
+ if (isRenameInputName(name)) {
2515
+ const sessionId = getRenameIdFromInputName(name);
2516
+ return startRename(state, sessionId);
2274
2517
  }
2275
- if (name === SESSION_DELETE) {
2518
+ if (name === SessionDelete) {
2276
2519
  return deleteSession(state, id);
2277
2520
  }
2278
- if (name === SEND) {
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 === 'composer') {
2577
+ if (name === Composer) {
2320
2578
  return focusInput(state);
2321
2579
  }
2322
- if (name === 'send') {
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.startsWith('session:') || name === 'SessionDelete') {
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 === 'create-session' || name === 'settings' || name === 'close-chat' || name === 'back') {
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 = state.sessions.find(session => session.id === mockSessionId);
2602
- const sessions = existingSession ? state.sessions.map(session => {
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
- }) : [...state.sessions, {
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: 'send',
2960
+ name: Send,
2730
2961
  onClick: HandleSubmit,
2731
- role: Button$1,
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: 'composer',
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: 'model',
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: 'back',
3046
+ name: Back,
2816
3047
  onClick: HandleClickBack,
2817
- role: Button$1,
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$1,
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: 'create-session',
3077
+ name: CreateSession,
2847
3078
  onClick: HandleClickNew,
2848
- title: newChat
3079
+ title: newChat()
2849
3080
  }, {
2850
3081
  icon: 'MaskIcon MaskIconSettingsGear',
2851
- name: 'settings',
3082
+ name: Settings,
2852
3083
  onClick: HandleClickSettings,
2853
- title: settings
3084
+ title: settings()
2854
3085
  }, {
2855
3086
  icon: 'MaskIcon MaskIconClose',
2856
- name: 'close-chat',
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 getChatMessageDom = message => {
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: 1,
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 getMessagesDom = messages => {
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: `session:${session.id}`,
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: 'SessionDelete',
3251
+ name: SessionDelete,
2972
3252
  onClick: HandleClickDelete,
2973
- role: Button$1,
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('Unknown view mode')];
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, 'composer', composerValue];
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",