@lvce-editor/chat-view 1.8.0 โ†’ 1.10.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.
@@ -1196,12 +1196,12 @@ const settings = i18nString('Settings');
1196
1196
  const closeChat = i18nString('Close Chat');
1197
1197
  const clickToOpenNewChat = i18nString('Click the + button to open a new chat.');
1198
1198
  const startConversation = i18nString('Start a conversation by typing below.');
1199
- const you = i18nString('You');
1200
- const assistant = i18nString('Assistant');
1199
+ i18nString('You');
1200
+ i18nString('Assistant');
1201
1201
  const composePlaceholder = i18nString('Type your message. Enter to send, Shift+Enter for newline.');
1202
1202
  const sendMessage = i18nString('Send message');
1203
1203
  const send = i18nString('Send');
1204
- const deleteChatSession = i18nString('Delete chat session');
1204
+ const deleteChatSession$1 = i18nString('Delete chat session');
1205
1205
  const defaultSessionTitle = i18nString('Chat 1');
1206
1206
  const dummyChatA = i18nString('Dummy Chat A');
1207
1207
  const dummyChatB = i18nString('Dummy Chat B');
@@ -1209,6 +1209,7 @@ const dummyChatC = i18nString('Dummy Chat C');
1209
1209
 
1210
1210
  const createDefaultState = () => {
1211
1211
  const defaultSessionId = 'session-1';
1212
+ const defaultModelId = 'test';
1212
1213
  return {
1213
1214
  assetDir: '',
1214
1215
  composerValue: '',
@@ -1221,16 +1222,33 @@ const createDefaultState = () => {
1221
1222
  inputSource: 'script',
1222
1223
  lastSubmittedSessionId: '',
1223
1224
  listItemHeight: 40,
1225
+ models: [{
1226
+ id: defaultModelId,
1227
+ name: 'test'
1228
+ }, {
1229
+ id: 'codex-5.3',
1230
+ name: 'Codex 5.3'
1231
+ }, {
1232
+ id: 'claude-code',
1233
+ name: 'Claude Code'
1234
+ }, {
1235
+ id: 'claude-haiku',
1236
+ name: 'Claude Haiku'
1237
+ }],
1224
1238
  nextMessageId: 1,
1225
1239
  platform: 0,
1226
1240
  renamingSessionId: '',
1241
+ selectedModelId: defaultModelId,
1227
1242
  selectedSessionId: defaultSessionId,
1228
1243
  sessions: [{
1229
1244
  id: defaultSessionId,
1230
1245
  messages: [],
1231
1246
  title: defaultSessionTitle
1232
1247
  }],
1248
+ tokensMax: 0,
1249
+ tokensUsed: 0,
1233
1250
  uid: 0,
1251
+ usageOverviewEnabled: false,
1234
1252
  viewMode: 'list',
1235
1253
  warningCount: 0,
1236
1254
  width: 0,
@@ -1274,7 +1292,7 @@ const diffFocus = (oldState, newState) => {
1274
1292
  };
1275
1293
 
1276
1294
  const isEqual = (oldState, newState) => {
1277
- return oldState.composerValue === newState.composerValue && oldState.initial === newState.initial && oldState.renamingSessionId === newState.renamingSessionId && oldState.selectedSessionId === newState.selectedSessionId && oldState.sessions === newState.sessions && oldState.viewMode === newState.viewMode;
1295
+ return oldState.composerValue === newState.composerValue && oldState.initial === newState.initial && oldState.renamingSessionId === newState.renamingSessionId && oldState.selectedModelId === newState.selectedModelId && oldState.selectedSessionId === newState.selectedSessionId && oldState.sessions === newState.sessions && oldState.tokensMax === newState.tokensMax && oldState.tokensUsed === newState.tokensUsed && oldState.usageOverviewEnabled === newState.usageOverviewEnabled && oldState.viewMode === newState.viewMode;
1278
1296
  };
1279
1297
 
1280
1298
  const RenderItems = 4;
@@ -1338,6 +1356,8 @@ const Span = 8;
1338
1356
  const Text = 12;
1339
1357
  const P = 50;
1340
1358
  const TextArea = 62;
1359
+ const Select$1 = 63;
1360
+ const Option$1 = 64;
1341
1361
  const Reference = 100;
1342
1362
 
1343
1363
  const Enter = 3;
@@ -1667,17 +1687,170 @@ const handleChatListContextMenu = async (name, x, y) => {
1667
1687
  await invoke('ContextMenu.show', x, y, CHAT_LIST_ITEM_CONTEXT_MENU, sessionId);
1668
1688
  };
1669
1689
 
1690
+ const LVCE_CHAT_SESSIONS_DB_NAME = 'lvce-chat-view-sessions';
1691
+ const LVCE_CHAT_SESSIONS_DB_VERSION = 1;
1692
+ const LVCE_CHAT_SESSIONS_STORE = 'chat-sessions';
1693
+ const toError = error => {
1694
+ if (error instanceof Error) {
1695
+ return error;
1696
+ }
1697
+ return new Error('IndexedDB request failed');
1698
+ };
1699
+ const requestToPromise = async createRequest => {
1700
+ const request = createRequest();
1701
+ return new Promise((resolve, reject) => {
1702
+ request.addEventListener('success', () => {
1703
+ resolve(request.result);
1704
+ });
1705
+ request.addEventListener('error', () => {
1706
+ reject(toError(request.error));
1707
+ });
1708
+ });
1709
+ };
1710
+ const transactionToPromise = async createTransaction => {
1711
+ const transaction = createTransaction();
1712
+ return new Promise((resolve, reject) => {
1713
+ transaction.addEventListener('complete', () => {
1714
+ resolve();
1715
+ });
1716
+ transaction.addEventListener('error', () => {
1717
+ reject(toError(transaction.error));
1718
+ });
1719
+ transaction.addEventListener('abort', () => {
1720
+ reject(toError(transaction.error));
1721
+ });
1722
+ });
1723
+ };
1724
+ const openSessionsDatabase = async () => {
1725
+ const request = indexedDB.open(LVCE_CHAT_SESSIONS_DB_NAME, LVCE_CHAT_SESSIONS_DB_VERSION);
1726
+ request.addEventListener('upgradeneeded', () => {
1727
+ const database = request.result;
1728
+ if (!database.objectStoreNames.contains(LVCE_CHAT_SESSIONS_STORE)) {
1729
+ database.createObjectStore(LVCE_CHAT_SESSIONS_STORE, {
1730
+ keyPath: 'id'
1731
+ });
1732
+ }
1733
+ });
1734
+ return requestToPromise(() => request);
1735
+ };
1736
+ class IndexedDbChatSessionStorage {
1737
+ async getDatabase() {
1738
+ if (!this.databasePromise) {
1739
+ this.databasePromise = openSessionsDatabase();
1740
+ }
1741
+ return this.databasePromise;
1742
+ }
1743
+ async clear() {
1744
+ const database = await this.getDatabase();
1745
+ const transaction = database.transaction(LVCE_CHAT_SESSIONS_STORE, 'readwrite');
1746
+ const createTransaction = () => transaction;
1747
+ const store = transaction.objectStore(LVCE_CHAT_SESSIONS_STORE);
1748
+ store.clear();
1749
+ await transactionToPromise(createTransaction);
1750
+ }
1751
+ async deleteSession(id) {
1752
+ const database = await this.getDatabase();
1753
+ const transaction = database.transaction(LVCE_CHAT_SESSIONS_STORE, 'readwrite');
1754
+ const createTransaction = () => transaction;
1755
+ const store = transaction.objectStore(LVCE_CHAT_SESSIONS_STORE);
1756
+ store.delete(id);
1757
+ await transactionToPromise(createTransaction);
1758
+ }
1759
+ async getSession(id) {
1760
+ const database = await this.getDatabase();
1761
+ const transaction = database.transaction(LVCE_CHAT_SESSIONS_STORE, 'readonly');
1762
+ const store = transaction.objectStore(LVCE_CHAT_SESSIONS_STORE);
1763
+ const result = await requestToPromise(() => store.get(id));
1764
+ return result;
1765
+ }
1766
+ async listSessions() {
1767
+ const database = await this.getDatabase();
1768
+ const transaction = database.transaction(LVCE_CHAT_SESSIONS_STORE, 'readonly');
1769
+ const store = transaction.objectStore(LVCE_CHAT_SESSIONS_STORE);
1770
+ const result = await requestToPromise(() => store.getAll());
1771
+ return result;
1772
+ }
1773
+ async setSession(session) {
1774
+ const database = await this.getDatabase();
1775
+ const transaction = database.transaction(LVCE_CHAT_SESSIONS_STORE, 'readwrite');
1776
+ const createTransaction = () => transaction;
1777
+ const store = transaction.objectStore(LVCE_CHAT_SESSIONS_STORE);
1778
+ store.put(session);
1779
+ await transactionToPromise(createTransaction);
1780
+ }
1781
+ }
1782
+
1783
+ class InMemoryChatSessionStorage {
1784
+ sessions = new Map();
1785
+ async clear() {
1786
+ this.sessions.clear();
1787
+ }
1788
+ async deleteSession(id) {
1789
+ this.sessions.delete(id);
1790
+ }
1791
+ async getSession(id) {
1792
+ return this.sessions.get(id);
1793
+ }
1794
+ async listSessions() {
1795
+ return [...this.sessions.values()];
1796
+ }
1797
+ async setSession(session) {
1798
+ this.sessions.set(session.id, session);
1799
+ }
1800
+ }
1801
+
1802
+ const createDefaultStorage = () => {
1803
+ if (typeof indexedDB === 'undefined') {
1804
+ return new InMemoryChatSessionStorage();
1805
+ }
1806
+ return new IndexedDbChatSessionStorage();
1807
+ };
1808
+ let chatSessionStorage = createDefaultStorage();
1809
+ const listChatSessions = async () => {
1810
+ const sessions = await chatSessionStorage.listSessions();
1811
+ return sessions.map(session => ({
1812
+ id: session.id,
1813
+ messages: [],
1814
+ title: session.title
1815
+ }));
1816
+ };
1817
+ const getChatSession = async id => {
1818
+ const session = await chatSessionStorage.getSession(id);
1819
+ if (!session) {
1820
+ return undefined;
1821
+ }
1822
+ return {
1823
+ id: session.id,
1824
+ messages: [...session.messages],
1825
+ title: session.title
1826
+ };
1827
+ };
1828
+ const saveChatSession = async session => {
1829
+ await chatSessionStorage.setSession({
1830
+ id: session.id,
1831
+ messages: [...session.messages],
1832
+ title: session.title
1833
+ });
1834
+ };
1835
+ const deleteChatSession = async id => {
1836
+ await chatSessionStorage.deleteSession(id);
1837
+ };
1838
+ const clearChatSessions = async () => {
1839
+ await chatSessionStorage.clear();
1840
+ };
1841
+
1670
1842
  const generateSessionId = () => {
1671
1843
  return crypto.randomUUID();
1672
1844
  };
1673
1845
 
1674
- const createSession = state => {
1846
+ const createSession = async state => {
1675
1847
  const id = generateSessionId();
1676
1848
  const session = {
1677
1849
  id,
1678
1850
  messages: [],
1679
1851
  title: `Chat ${state.sessions.length + 1}`
1680
1852
  };
1853
+ await saveChatSession(session);
1681
1854
  return {
1682
1855
  ...state,
1683
1856
  renamingSessionId: '',
@@ -1698,7 +1871,7 @@ const getNextSelectedSessionId = (sessions, deletedId) => {
1698
1871
  return sessions[nextIndex].id;
1699
1872
  };
1700
1873
 
1701
- const deleteSession = (state, id) => {
1874
+ const deleteSession = async (state, id) => {
1702
1875
  const {
1703
1876
  renamingSessionId,
1704
1877
  sessions
@@ -1707,6 +1880,7 @@ const deleteSession = (state, id) => {
1707
1880
  if (filtered.length === sessions.length) {
1708
1881
  return state;
1709
1882
  }
1883
+ await deleteChatSession(id);
1710
1884
  if (filtered.length === 0) {
1711
1885
  return {
1712
1886
  ...state,
@@ -1716,11 +1890,22 @@ const deleteSession = (state, id) => {
1716
1890
  viewMode: 'list'
1717
1891
  };
1718
1892
  }
1893
+ const nextSelectedSessionId = getNextSelectedSessionId(filtered, id);
1894
+ const loadedSession = await getChatSession(nextSelectedSessionId);
1895
+ const hydratedSessions = filtered.map(session => {
1896
+ if (session.id !== nextSelectedSessionId) {
1897
+ return session;
1898
+ }
1899
+ if (!loadedSession) {
1900
+ return session;
1901
+ }
1902
+ return loadedSession;
1903
+ });
1719
1904
  return {
1720
1905
  ...state,
1721
1906
  renamingSessionId: renamingSessionId === id ? '' : renamingSessionId,
1722
- selectedSessionId: getNextSelectedSessionId(filtered, id),
1723
- sessions: filtered
1907
+ selectedSessionId: nextSelectedSessionId,
1908
+ sessions: hydratedSessions
1724
1909
  };
1725
1910
  };
1726
1911
 
@@ -1774,14 +1959,27 @@ const handleSubmit = async state => {
1774
1959
  text: userText,
1775
1960
  time: userTime
1776
1961
  };
1962
+ let workingSessions = sessions;
1963
+ if (viewMode === 'detail') {
1964
+ const loadedSession = await getChatSession(selectedSessionId);
1965
+ if (loadedSession) {
1966
+ workingSessions = sessions.map(session => {
1967
+ if (session.id !== selectedSessionId) {
1968
+ return session;
1969
+ }
1970
+ return loadedSession;
1971
+ });
1972
+ }
1973
+ }
1777
1974
  let optimisticState;
1778
1975
  if (viewMode === 'list') {
1779
1976
  const newSessionId = generateSessionId();
1780
1977
  const newSession = {
1781
1978
  id: newSessionId,
1782
1979
  messages: [userMessage],
1783
- title: `Chat ${sessions.length + 1}`
1980
+ title: `Chat ${workingSessions.length + 1}`
1784
1981
  };
1982
+ await saveChatSession(newSession);
1785
1983
  optimisticState = focusInput({
1786
1984
  ...state,
1787
1985
  composerValue: '',
@@ -1789,11 +1987,11 @@ const handleSubmit = async state => {
1789
1987
  lastSubmittedSessionId: newSessionId,
1790
1988
  nextMessageId: nextMessageId + 1,
1791
1989
  selectedSessionId: newSessionId,
1792
- sessions: [...sessions, newSession],
1990
+ sessions: [...workingSessions, newSession],
1793
1991
  viewMode: 'detail'
1794
1992
  });
1795
1993
  } else {
1796
- const updatedSessions = sessions.map(session => {
1994
+ const updatedSessions = workingSessions.map(session => {
1797
1995
  if (session.id !== selectedSessionId) {
1798
1996
  return session;
1799
1997
  }
@@ -1802,6 +2000,10 @@ const handleSubmit = async state => {
1802
2000
  messages: [...session.messages, userMessage]
1803
2001
  };
1804
2002
  });
2003
+ const selectedSession = updatedSessions.find(session => session.id === selectedSessionId);
2004
+ if (selectedSession) {
2005
+ await saveChatSession(selectedSession);
2006
+ }
1805
2007
  optimisticState = focusInput({
1806
2008
  ...state,
1807
2009
  composerValue: '',
@@ -1824,6 +2026,10 @@ const handleSubmit = async state => {
1824
2026
  messages: [...session.messages, assistantMessage]
1825
2027
  };
1826
2028
  });
2029
+ const selectedSession = updatedSessions.find(session => session.id === optimisticState.selectedSessionId);
2030
+ if (selectedSession) {
2031
+ await saveChatSession(selectedSession);
2032
+ }
1827
2033
  return focusInput({
1828
2034
  ...optimisticState,
1829
2035
  nextMessageId: optimisticState.nextMessageId + 1,
@@ -1845,15 +2051,26 @@ const handleClickSend = async state => {
1845
2051
  return handleSubmit(submitState);
1846
2052
  };
1847
2053
 
1848
- const selectSession = (state, id) => {
2054
+ const selectSession = async (state, id) => {
1849
2055
  const exists = state.sessions.some(session => session.id === id);
1850
2056
  if (!exists) {
1851
2057
  return state;
1852
2058
  }
2059
+ const loadedSession = await getChatSession(id);
2060
+ const sessions = state.sessions.map(session => {
2061
+ if (session.id !== id) {
2062
+ return session;
2063
+ }
2064
+ if (!loadedSession) {
2065
+ return session;
2066
+ }
2067
+ return loadedSession;
2068
+ });
1853
2069
  return {
1854
2070
  ...state,
1855
2071
  renamingSessionId: '',
1856
2072
  selectedSessionId: id,
2073
+ sessions,
1857
2074
  viewMode: 'detail'
1858
2075
  };
1859
2076
  };
@@ -2007,7 +2224,7 @@ const handleInputFocus = async (state, name) => {
2007
2224
  };
2008
2225
  };
2009
2226
 
2010
- const submitRename = state => {
2227
+ const submitRename = async state => {
2011
2228
  const {
2012
2229
  composerValue,
2013
2230
  renamingSessionId,
@@ -2029,6 +2246,10 @@ const submitRename = state => {
2029
2246
  title
2030
2247
  };
2031
2248
  });
2249
+ const renamedSession = updatedSessions.find(session => session.id === renamingSessionId);
2250
+ if (renamedSession) {
2251
+ await saveChatSession(renamedSession);
2252
+ }
2032
2253
  return {
2033
2254
  ...state,
2034
2255
  composerValue: '',
@@ -2061,6 +2282,13 @@ const handleKeyDown = async (state, key, shiftKey) => {
2061
2282
  return handleSubmit(submitState);
2062
2283
  };
2063
2284
 
2285
+ const handleModelChange = async (state, value) => {
2286
+ return {
2287
+ ...state,
2288
+ selectedModelId: value
2289
+ };
2290
+ };
2291
+
2064
2292
  const handleNewline = async state => {
2065
2293
  return handleInput(state, `${state.composerValue}\n`);
2066
2294
  };
@@ -2121,6 +2349,19 @@ const getSavedBounds = savedState => {
2121
2349
  };
2122
2350
  };
2123
2351
 
2352
+ const getSavedSelectedModelId = savedState => {
2353
+ if (!isObject(savedState)) {
2354
+ return undefined;
2355
+ }
2356
+ const {
2357
+ selectedModelId
2358
+ } = savedState;
2359
+ if (typeof selectedModelId !== 'string') {
2360
+ return undefined;
2361
+ }
2362
+ return selectedModelId;
2363
+ };
2364
+
2124
2365
  const getSavedSelectedSessionId = savedState => {
2125
2366
  if (!isObject(savedState)) {
2126
2367
  return undefined;
@@ -2147,23 +2388,79 @@ const getSavedSessions = savedState => {
2147
2388
  return sessions;
2148
2389
  };
2149
2390
 
2391
+ const getSavedViewMode = savedState => {
2392
+ if (!isObject(savedState)) {
2393
+ return undefined;
2394
+ }
2395
+ const {
2396
+ viewMode
2397
+ } = savedState;
2398
+ if (viewMode !== 'list' && viewMode !== 'detail') {
2399
+ return undefined;
2400
+ }
2401
+ return viewMode;
2402
+ };
2403
+
2404
+ const toSummarySession = session => {
2405
+ return {
2406
+ id: session.id,
2407
+ messages: [],
2408
+ title: session.title
2409
+ };
2410
+ };
2411
+ const loadSelectedSessionMessages = async (sessions, selectedSessionId) => {
2412
+ if (!selectedSessionId) {
2413
+ return sessions;
2414
+ }
2415
+ const loadedSession = await getChatSession(selectedSessionId);
2416
+ if (!loadedSession) {
2417
+ return sessions;
2418
+ }
2419
+ return sessions.map(session => {
2420
+ if (session.id !== selectedSessionId) {
2421
+ return session;
2422
+ }
2423
+ return loadedSession;
2424
+ });
2425
+ };
2150
2426
  const loadContent = async (state, savedState) => {
2151
2427
  const savedBounds = getSavedBounds(savedState);
2152
- const sessions = getSavedSessions(savedState) || state.sessions;
2428
+ const savedSelectedModelId = getSavedSelectedModelId(savedState);
2429
+ const savedViewMode = getSavedViewMode(savedState);
2430
+ const legacySavedSessions = getSavedSessions(savedState);
2431
+ const storedSessions = await listChatSessions();
2432
+ let sessions = storedSessions;
2433
+ if (sessions.length === 0 && legacySavedSessions && legacySavedSessions.length > 0) {
2434
+ for (const session of legacySavedSessions) {
2435
+ await saveChatSession(session);
2436
+ }
2437
+ sessions = legacySavedSessions.map(toSummarySession);
2438
+ }
2439
+ if (sessions.length === 0 && state.sessions.length > 0) {
2440
+ for (const session of state.sessions) {
2441
+ await saveChatSession(session);
2442
+ }
2443
+ sessions = state.sessions.map(toSummarySession);
2444
+ }
2153
2445
  const preferredSessionId = getSavedSelectedSessionId(savedState) || state.selectedSessionId;
2446
+ const preferredModelId = savedSelectedModelId || state.selectedModelId;
2447
+ const selectedModelId = state.models.some(model => model.id === preferredModelId) ? preferredModelId : state.models[0]?.id || '';
2154
2448
  const selectedSessionId = sessions.some(session => session.id === preferredSessionId) ? preferredSessionId : sessions[0]?.id || '';
2155
- const viewMode = sessions.length === 0 ? 'list' : state.viewMode === 'detail' ? 'detail' : 'list';
2449
+ sessions = await loadSelectedSessionMessages(sessions, selectedSessionId);
2450
+ const preferredViewMode = savedViewMode || state.viewMode;
2451
+ const viewMode = sessions.length === 0 || !selectedSessionId ? 'list' : preferredViewMode === 'detail' ? 'detail' : 'list';
2156
2452
  return {
2157
2453
  ...state,
2158
2454
  ...savedBounds,
2159
2455
  initial: false,
2456
+ selectedModelId,
2160
2457
  selectedSessionId,
2161
2458
  sessions,
2162
2459
  viewMode
2163
2460
  };
2164
2461
  };
2165
2462
 
2166
- const openMockSession = (state, mockSessionId, mockChatMessages) => {
2463
+ const openMockSession = async (state, mockSessionId, mockChatMessages) => {
2167
2464
  if (!mockSessionId) {
2168
2465
  return state;
2169
2466
  }
@@ -2181,6 +2478,10 @@ const openMockSession = (state, mockSessionId, mockChatMessages) => {
2181
2478
  messages: mockChatMessages,
2182
2479
  title: mockSessionId
2183
2480
  }];
2481
+ const selectedSession = sessions.find(session => session.id === mockSessionId);
2482
+ if (selectedSession) {
2483
+ await saveChatSession(selectedSession);
2484
+ }
2184
2485
  return {
2185
2486
  ...state,
2186
2487
  renamingSessionId: '',
@@ -2226,6 +2527,8 @@ const renderFocusContext = (oldState, newState) => {
2226
2527
  const ChatActions = 'ChatActions';
2227
2528
  const ChatName = 'ChatName';
2228
2529
  const ChatSendArea = 'ChatSendArea';
2530
+ const ChatSendAreaBottom = 'ChatSendAreaBottom';
2531
+ const ChatSendAreaContent = 'ChatSendAreaContent';
2229
2532
  const Chat = 'Chat';
2230
2533
  const ChatHeader = 'ChatHeader';
2231
2534
  const Button = 'Button';
@@ -2233,12 +2536,22 @@ const ButtonDisabled = 'ButtonDisabled';
2233
2536
  const ButtonPrimary = 'ButtonPrimary';
2234
2537
  const IconButton = 'IconButton';
2235
2538
  const Label = 'Label';
2539
+ const LabelDetail = 'LabelDetail';
2236
2540
  const ChatList = 'ChatList';
2541
+ const ChatListEmpty = 'ChatListEmpty';
2237
2542
  const ChatListItem = 'ChatListItem';
2238
2543
  const ChatListItemLabel = 'ChatListItemLabel';
2239
2544
  const Markdown = 'Markdown';
2240
2545
  const Message = 'Message';
2546
+ const ChatMessageContent = 'ChatMessageContent';
2547
+ const MessageUser = 'MessageUser';
2548
+ const MessageAssistant = 'MessageAssistant';
2241
2549
  const MultilineInputBox = 'MultilineInputBox';
2550
+ const Option = 'Option';
2551
+ const TokenUsageOverview = 'TokenUsageOverview';
2552
+ const TokenUsageRing = 'TokenUsageRing';
2553
+ const TokenUsageRingInner = 'TokenUsageRingInner';
2554
+ const Select = 'Select';
2242
2555
  const Viewlet = 'Viewlet';
2243
2556
  const ChatWelcomeMessage = 'ChatWelcomeMessage';
2244
2557
 
@@ -2254,14 +2567,78 @@ const HandleClickBack = 16;
2254
2567
  const HandleClickList = 17;
2255
2568
  const HandleClickDelete = 18;
2256
2569
  const HandleSubmit = 19;
2570
+ const HandleModelChange = 20;
2257
2571
 
2258
- const getChatSendAreaDom = composerValue => {
2259
- const isSendDisabled = composerValue.trim() === '';
2572
+ const getModelOptionDOm = (model, selectedModelId) => {
2573
+ return [{
2574
+ childCount: 1,
2575
+ className: Option,
2576
+ selected: model.id === selectedModelId,
2577
+ type: Option$1,
2578
+ value: model.id
2579
+ }, text(model.name)];
2580
+ };
2581
+
2582
+ const getSendButtonDom = isSendDisabled => {
2260
2583
  const sendButtonClassName = isSendDisabled ? `${Button} ${ButtonPrimary} ${ButtonDisabled}` : `${Button} ${ButtonPrimary}`;
2261
2584
  return [{
2262
- childCount: 2,
2585
+ childCount: 1,
2586
+ className: sendButtonClassName,
2587
+ disabled: isSendDisabled,
2588
+ name: 'send',
2589
+ onClick: HandleSubmit,
2590
+ role: Button$2,
2591
+ title: sendMessage,
2592
+ type: Button$1
2593
+ }, text(send)];
2594
+ };
2595
+
2596
+ const clampToPercentage = (tokensUsed, tokensMax) => {
2597
+ if (tokensMax <= 0) {
2598
+ return 0;
2599
+ }
2600
+ const percentage = tokensUsed / tokensMax * 100;
2601
+ return Math.max(0, Math.min(100, percentage));
2602
+ };
2603
+
2604
+ const getUsageOverviewDom = (tokensUsed, tokensMax) => {
2605
+ const usagePercent = clampToPercentage(tokensUsed, tokensMax);
2606
+ const usageLabel = `${tokensUsed} / ${tokensMax}`;
2607
+ const usageTitle = `${tokensUsed} of ${tokensMax} tokens used (${Math.round(usagePercent)}%)`;
2608
+ return [{
2609
+ childCount: 3,
2610
+ className: TokenUsageOverview,
2611
+ type: Div
2612
+ }, {
2613
+ childCount: 1,
2614
+ className: TokenUsageRing,
2615
+ style: `background: conic-gradient(var(--vscode-button-background) ${usagePercent}%, var(--vscode-editorWidget-border) 0);`,
2616
+ title: usageTitle,
2617
+ type: Div
2618
+ }, {
2619
+ childCount: 0,
2620
+ className: TokenUsageRingInner,
2621
+ style: 'background: var(--vscode-editor-background);',
2622
+ type: Div
2623
+ }, {
2624
+ childCount: 1,
2625
+ className: LabelDetail,
2626
+ title: usageTitle,
2627
+ type: Span
2628
+ }, text(usageLabel)];
2629
+ };
2630
+
2631
+ const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
2632
+ const isSendDisabled = composerValue.trim() === '';
2633
+ const modelOptions = models.flatMap(model => getModelOptionDOm(model, selectedModelId));
2634
+ return [{
2635
+ childCount: 1,
2263
2636
  className: ChatSendArea,
2264
2637
  type: Div
2638
+ }, {
2639
+ childCount: 2,
2640
+ className: ChatSendAreaContent,
2641
+ type: Div
2265
2642
  }, {
2266
2643
  childCount: 0,
2267
2644
  className: MultilineInputBox,
@@ -2272,15 +2649,17 @@ const getChatSendAreaDom = composerValue => {
2272
2649
  type: TextArea,
2273
2650
  value: composerValue
2274
2651
  }, {
2275
- childCount: 1,
2276
- className: sendButtonClassName,
2277
- disabled: isSendDisabled,
2278
- name: 'send',
2279
- onClick: HandleSubmit,
2280
- role: Button$2,
2281
- title: sendMessage,
2282
- type: Button$1
2283
- }, text(send)];
2652
+ childCount: usageOverviewEnabled ? 3 : 2,
2653
+ className: ChatSendAreaBottom,
2654
+ type: Div
2655
+ }, {
2656
+ childCount: models.length,
2657
+ className: Select,
2658
+ name: 'model',
2659
+ onInput: HandleModelChange,
2660
+ type: Select$1,
2661
+ value: selectedModelId
2662
+ }, ...modelOptions, ...(usageOverviewEnabled ? getUsageOverviewDom(tokensUsed, tokensMax) : []), ...getSendButtonDom(isSendDisabled)];
2284
2663
  };
2285
2664
 
2286
2665
  const getHeaderActionVirtualDom = item => {
@@ -2328,7 +2707,11 @@ const getChatHeaderBackButtonVirtualDom = () => {
2328
2707
  role: Button$2,
2329
2708
  title: backToChats,
2330
2709
  type: Button$1
2331
- }, text('โ†')];
2710
+ }, {
2711
+ childCount: 0,
2712
+ className: 'MaskIcon MaskIconArrowLeft',
2713
+ type: Div
2714
+ }];
2332
2715
  };
2333
2716
 
2334
2717
  const getChatHeaderDomDetailMode = selectedSessionTitle => {
@@ -2348,15 +2731,16 @@ const getChatHeaderDomDetailMode = selectedSessionTitle => {
2348
2731
  };
2349
2732
 
2350
2733
  const getChatMessageDom = message => {
2734
+ const roleClassName = message.role === 'user' ? MessageUser : MessageAssistant;
2351
2735
  return [{
2352
- childCount: 2,
2353
- className: Message,
2736
+ childCount: 1,
2737
+ className: mergeClassNames(Message, roleClassName),
2354
2738
  type: Div
2355
2739
  }, {
2356
2740
  childCount: 1,
2357
- className: Label,
2741
+ className: ChatMessageContent,
2358
2742
  type: Div
2359
- }, text(`${message.role === 'user' ? you : assistant} ยท ${message.time}`), {
2743
+ }, {
2360
2744
  childCount: 1,
2361
2745
  className: Markdown,
2362
2746
  type: P
@@ -2378,7 +2762,7 @@ const getMessagesDom = messages => {
2378
2762
  }, ...messages.flatMap(getChatMessageDom)];
2379
2763
  };
2380
2764
 
2381
- const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue) => {
2765
+ const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
2382
2766
  const selectedSession = sessions.find(session => session.id === selectedSessionId);
2383
2767
  const selectedSessionTitle = selectedSession?.title || chatTitle;
2384
2768
  const messages = selectedSession ? selectedSession.messages : [];
@@ -2386,7 +2770,7 @@ const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue)
2386
2770
  childCount: 3,
2387
2771
  className: mergeClassNames(Viewlet, Chat),
2388
2772
  type: Div
2389
- }, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages), ...getChatSendAreaDom(composerValue)];
2773
+ }, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax)];
2390
2774
  };
2391
2775
 
2392
2776
  const getChatHeaderListModeDom = () => {
@@ -2404,7 +2788,7 @@ const getChatHeaderListModeDom = () => {
2404
2788
  const getEmptyChatSessionsDom = () => {
2405
2789
  return [{
2406
2790
  childCount: 1,
2407
- className: ChatList,
2791
+ className: ChatListEmpty,
2408
2792
  type: Div
2409
2793
  }, {
2410
2794
  childCount: 1,
@@ -2438,7 +2822,7 @@ const getSessionDom = session => {
2438
2822
  onClick: HandleClickDelete,
2439
2823
  role: Button$2,
2440
2824
  tabIndex: 0,
2441
- title: deleteChatSession,
2825
+ title: deleteChatSession$1,
2442
2826
  type: Button$1
2443
2827
  }, text('๐Ÿ—‘')];
2444
2828
  };
@@ -2455,12 +2839,12 @@ const getChatListDom = (sessions, selectedSessionId) => {
2455
2839
  }, ...sessions.flatMap(getSessionDom)];
2456
2840
  };
2457
2841
 
2458
- const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue) => {
2842
+ const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
2459
2843
  return [{
2460
2844
  childCount: 3,
2461
2845
  className: mergeClassNames(Viewlet, Chat),
2462
2846
  type: Div
2463
- }, ...getChatHeaderListModeDom(), ...getChatListDom(sessions), ...getChatSendAreaDom(composerValue)];
2847
+ }, ...getChatHeaderListModeDom(), ...getChatListDom(sessions), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax)];
2464
2848
  };
2465
2849
 
2466
2850
  const getChatModeUnsupportedVirtualDom = () => {
@@ -2470,12 +2854,12 @@ const getChatModeUnsupportedVirtualDom = () => {
2470
2854
  }, text('Unknown view mode')];
2471
2855
  };
2472
2856
 
2473
- const getChatVirtualDom = (sessions, selectedSessionId, composerValue, viewMode) => {
2857
+ const getChatVirtualDom = (sessions, selectedSessionId, composerValue, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
2474
2858
  switch (viewMode) {
2475
2859
  case 'detail':
2476
- return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue);
2860
+ return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
2477
2861
  case 'list':
2478
- return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue);
2862
+ return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
2479
2863
  default:
2480
2864
  return getChatModeUnsupportedVirtualDom();
2481
2865
  }
@@ -2485,15 +2869,20 @@ const renderItems = (oldState, newState) => {
2485
2869
  const {
2486
2870
  composerValue,
2487
2871
  initial,
2872
+ models,
2873
+ selectedModelId,
2488
2874
  selectedSessionId,
2489
2875
  sessions,
2876
+ tokensMax,
2877
+ tokensUsed,
2490
2878
  uid,
2879
+ usageOverviewEnabled,
2491
2880
  viewMode
2492
2881
  } = newState;
2493
2882
  if (initial) {
2494
2883
  return [SetDom2, uid, []];
2495
2884
  }
2496
- const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, viewMode);
2885
+ const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
2497
2886
  return [SetDom2, uid, dom];
2498
2887
  };
2499
2888
 
@@ -2581,6 +2970,9 @@ const renderEventListeners = () => {
2581
2970
  }, {
2582
2971
  name: HandleInput,
2583
2972
  params: ['handleInput', TargetValue]
2973
+ }, {
2974
+ name: HandleModelChange,
2975
+ params: ['handleModelChange', TargetValue]
2584
2976
  }, {
2585
2977
  name: HandleFocus,
2586
2978
  params: ['handleInputFocus', TargetName]
@@ -2598,6 +2990,7 @@ const rerender = state => {
2598
2990
  };
2599
2991
 
2600
2992
  const reset = async state => {
2993
+ await clearChatSessions();
2601
2994
  return {
2602
2995
  ...state,
2603
2996
  composerValue: '',
@@ -2620,8 +3013,8 @@ const saveState = state => {
2620
3013
  height,
2621
3014
  nextMessageId,
2622
3015
  renamingSessionId,
3016
+ selectedModelId,
2623
3017
  selectedSessionId,
2624
- sessions,
2625
3018
  viewMode,
2626
3019
  width,
2627
3020
  x,
@@ -2632,8 +3025,8 @@ const saveState = state => {
2632
3025
  height,
2633
3026
  nextMessageId,
2634
3027
  renamingSessionId,
3028
+ selectedModelId,
2635
3029
  selectedSessionId,
2636
- sessions,
2637
3030
  viewMode,
2638
3031
  width,
2639
3032
  x,
@@ -2681,6 +3074,7 @@ const commandMap = {
2681
3074
  'Chat.handleInput': wrapCommand(handleInput),
2682
3075
  'Chat.handleInputFocus': wrapCommand(handleInputFocus),
2683
3076
  'Chat.handleKeyDown': wrapCommand(handleKeyDown),
3077
+ 'Chat.handleModelChange': wrapCommand(handleModelChange),
2684
3078
  'Chat.handleSubmit': wrapCommand(handleSubmit),
2685
3079
  'Chat.initialize': initialize,
2686
3080
  'Chat.loadContent': wrapCommand(loadContent),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",