@lvce-editor/chat-view 1.20.0 → 2.1.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.
@@ -698,7 +698,7 @@ const getErrorResponse = (id, error, preparePrettyError, logError) => {
698
698
  const errorProperty = getErrorProperty(error, prettyError);
699
699
  return create$1$1(id, errorProperty);
700
700
  };
701
- const create$9 = (message, result) => {
701
+ const create$a = (message, result) => {
702
702
  return {
703
703
  id: message.id,
704
704
  jsonrpc: Two$1,
@@ -707,7 +707,7 @@ const create$9 = (message, result) => {
707
707
  };
708
708
  const getSuccessResponse = (message, result) => {
709
709
  const resultProperty = result ?? null;
710
- return create$9(message, resultProperty);
710
+ return create$a(message, resultProperty);
711
711
  };
712
712
  const getErrorResponseSimple = (id, error) => {
713
713
  return {
@@ -801,7 +801,7 @@ const handleJsonRpcMessage = async (...args) => {
801
801
 
802
802
  const Two = '2.0';
803
803
 
804
- const create$8 = (method, params) => {
804
+ const create$9 = (method, params) => {
805
805
  return {
806
806
  jsonrpc: Two,
807
807
  method,
@@ -809,7 +809,7 @@ const create$8 = (method, params) => {
809
809
  };
810
810
  };
811
811
 
812
- const create$7 = (id, method, params) => {
812
+ const create$8 = (id, method, params) => {
813
813
  const message = {
814
814
  id,
815
815
  jsonrpc: Two,
@@ -820,12 +820,12 @@ const create$7 = (id, method, params) => {
820
820
  };
821
821
 
822
822
  let id$1 = 0;
823
- const create$6 = () => {
823
+ const create$7 = () => {
824
824
  return ++id$1;
825
825
  };
826
826
 
827
827
  const registerPromise = map => {
828
- const id = create$6();
828
+ const id = create$7();
829
829
  const {
830
830
  promise,
831
831
  resolve
@@ -842,7 +842,7 @@ const invokeHelper = async (callbacks, ipc, method, params, useSendAndTransfer)
842
842
  id,
843
843
  promise
844
844
  } = registerPromise(callbacks);
845
- const message = create$7(id, method, params);
845
+ const message = create$8(id, method, params);
846
846
  if (useSendAndTransfer && ipc.sendAndTransfer) {
847
847
  ipc.sendAndTransfer(message);
848
848
  } else {
@@ -878,7 +878,7 @@ const createRpc = ipc => {
878
878
  * @deprecated
879
879
  */
880
880
  send(method, ...params) {
881
- const message = create$8(method, params);
881
+ const message = create$9(method, params);
882
882
  ipc.send(message);
883
883
  }
884
884
  };
@@ -918,7 +918,7 @@ const listen$1 = async (module, options) => {
918
918
  return ipc;
919
919
  };
920
920
 
921
- const create$5 = async ({
921
+ const create$6 = async ({
922
922
  commandMap,
923
923
  isMessagePortOpen = true,
924
924
  messagePort
@@ -936,7 +936,7 @@ const create$5 = async ({
936
936
  return rpc;
937
937
  };
938
938
 
939
- const create$4 = async ({
939
+ const create$5 = async ({
940
940
  commandMap,
941
941
  isMessagePortOpen,
942
942
  send
@@ -946,13 +946,55 @@ const create$4 = async ({
946
946
  port2
947
947
  } = new MessageChannel();
948
948
  await send(port1);
949
- return create$5({
949
+ return create$6({
950
950
  commandMap,
951
951
  isMessagePortOpen,
952
952
  messagePort: port2
953
953
  });
954
954
  };
955
955
 
956
+ const createSharedLazyRpc = factory => {
957
+ let rpcPromise;
958
+ const getOrCreate = () => {
959
+ if (!rpcPromise) {
960
+ rpcPromise = factory();
961
+ }
962
+ return rpcPromise;
963
+ };
964
+ return {
965
+ async dispose() {
966
+ const rpc = await getOrCreate();
967
+ await rpc.dispose();
968
+ },
969
+ async invoke(method, ...params) {
970
+ const rpc = await getOrCreate();
971
+ return rpc.invoke(method, ...params);
972
+ },
973
+ async invokeAndTransfer(method, ...params) {
974
+ const rpc = await getOrCreate();
975
+ return rpc.invokeAndTransfer(method, ...params);
976
+ },
977
+ async send(method, ...params) {
978
+ const rpc = await getOrCreate();
979
+ rpc.send(method, ...params);
980
+ }
981
+ };
982
+ };
983
+
984
+ const create$4 = async ({
985
+ commandMap,
986
+ isMessagePortOpen,
987
+ send
988
+ }) => {
989
+ return createSharedLazyRpc(() => {
990
+ return create$5({
991
+ commandMap,
992
+ isMessagePortOpen,
993
+ send
994
+ });
995
+ });
996
+ };
997
+
956
998
  const create$3 = async ({
957
999
  commandMap
958
1000
  }) => {
@@ -964,9 +1006,6 @@ const create$3 = async ({
964
1006
  return rpc;
965
1007
  };
966
1008
 
967
- const ExtensionHostWorker = 44;
968
- const RendererWorker = 1;
969
-
970
1009
  const createMockRpc = ({
971
1010
  commandMap
972
1011
  }) => {
@@ -988,7 +1027,7 @@ const createMockRpc = ({
988
1027
  };
989
1028
 
990
1029
  const rpcs = Object.create(null);
991
- const set$3 = (id, rpc) => {
1030
+ const set$4 = (id, rpc) => {
992
1031
  rpcs[id] = rpc;
993
1032
  };
994
1033
  const get$2 = id => {
@@ -1021,7 +1060,7 @@ const create$2 = rpcId => {
1021
1060
  const mockRpc = createMockRpc({
1022
1061
  commandMap
1023
1062
  });
1024
- set$3(rpcId, mockRpc);
1063
+ set$4(rpcId, mockRpc);
1025
1064
  // @ts-ignore
1026
1065
  mockRpc[Symbol.dispose] = () => {
1027
1066
  remove(rpcId);
@@ -1030,11 +1069,35 @@ const create$2 = rpcId => {
1030
1069
  return mockRpc;
1031
1070
  },
1032
1071
  set(rpc) {
1033
- set$3(rpcId, rpc);
1072
+ set$4(rpcId, rpc);
1034
1073
  }
1035
1074
  };
1036
1075
  };
1037
1076
 
1077
+ const {
1078
+ set: set$3
1079
+ } = create$2(6002);
1080
+
1081
+ const ClientX = 'event.clientX';
1082
+ const ClientY = 'event.clientY';
1083
+ const Key = 'event.key';
1084
+ const ShiftKey = 'event.shiftKey';
1085
+ const TargetName = 'event.target.name';
1086
+ const TargetValue = 'event.target.value';
1087
+
1088
+ const ExtensionHostWorker = 44;
1089
+ const RendererWorker = 1;
1090
+
1091
+ const FocusSelector = 'Viewlet.focusSelector';
1092
+ const SetCss = 'Viewlet.setCss';
1093
+ const SetDom2 = 'Viewlet.setDom2';
1094
+ const SetFocusContext = 'Viewlet.setFocusContext';
1095
+ const SetProperty = 'Viewlet.setProperty';
1096
+ const SetValueByName = 'Viewlet.setValueByName';
1097
+ const SetPatches = 'Viewlet.setPatches';
1098
+
1099
+ const FocusChatInput = 8000;
1100
+
1038
1101
  const {
1039
1102
  invoke: invoke$1,
1040
1103
  set: set$2
@@ -1251,6 +1314,9 @@ const chats = () => {
1251
1314
  const newChat = () => {
1252
1315
  return i18nString('New Chat');
1253
1316
  };
1317
+ const debug = () => {
1318
+ return i18nString('Debug');
1319
+ };
1254
1320
  const backToChats = () => {
1255
1321
  return i18nString('Back to chats');
1256
1322
  };
@@ -1528,23 +1594,6 @@ const diff2 = uid => {
1528
1594
  return result;
1529
1595
  };
1530
1596
 
1531
- const ClientX = 'event.clientX';
1532
- const ClientY = 'event.clientY';
1533
- const Key = 'event.key';
1534
- const ShiftKey = 'event.shiftKey';
1535
- const TargetName = 'event.target.name';
1536
- const TargetValue = 'event.target.value';
1537
-
1538
- const FocusSelector = 'Viewlet.focusSelector';
1539
- const SetCss = 'Viewlet.setCss';
1540
- const SetDom2 = 'Viewlet.setDom2';
1541
- const SetFocusContext = 'Viewlet.setFocusContext';
1542
- const SetProperty = 'Viewlet.setProperty';
1543
- const SetValueByName = 'Viewlet.setValueByName';
1544
- const SetPatches = 'Viewlet.setPatches';
1545
-
1546
- const FocusChatInput = 8000;
1547
-
1548
1597
  const Button$2 = 'button';
1549
1598
 
1550
1599
  const Button$1 = 1;
@@ -1934,29 +1983,6 @@ const requestToPromise = async createRequest => {
1934
1983
  return promise;
1935
1984
  };
1936
1985
 
1937
- const openSessionsDatabase = async (databaseName, databaseVersion, storeName) => {
1938
- const request = indexedDB.open(databaseName, databaseVersion);
1939
- request.addEventListener('upgradeneeded', () => {
1940
- const database = request.result;
1941
- if (!database.objectStoreNames.contains(storeName)) {
1942
- database.createObjectStore(storeName, {
1943
- keyPath: 'id'
1944
- });
1945
- }
1946
- });
1947
- return requestToPromise(() => request);
1948
- };
1949
-
1950
- const getDatabase = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName) => {
1951
- const existingDatabasePromise = getDatabasePromise();
1952
- if (existingDatabasePromise) {
1953
- return existingDatabasePromise;
1954
- }
1955
- const nextDatabasePromise = openSessionsDatabase(databaseName, databaseVersion, storeName);
1956
- setDatabasePromise(nextDatabasePromise);
1957
- return nextDatabasePromise;
1958
- };
1959
-
1960
1986
  const transactionToPromise = async createTransaction => {
1961
1987
  const transaction = createTransaction();
1962
1988
  const {
@@ -1976,97 +2002,522 @@ const transactionToPromise = async createTransaction => {
1976
2002
  return promise;
1977
2003
  };
1978
2004
 
1979
- const clear = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName) => {
1980
- const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
1981
- const transaction = database.transaction(storeName, 'readwrite');
1982
- const createTransaction = () => transaction;
1983
- const store = transaction.objectStore(storeName);
1984
- store.clear();
1985
- await transactionToPromise(createTransaction);
2005
+ const toChatViewEvent = event => {
2006
+ const {
2007
+ eventId,
2008
+ ...chatViewEvent
2009
+ } = event;
2010
+ return chatViewEvent;
1986
2011
  };
1987
-
1988
- const deleteSession$1 = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName, id) => {
1989
- const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
1990
- const transaction = database.transaction(storeName, 'readwrite');
1991
- const createTransaction = () => transaction;
1992
- const store = transaction.objectStore(storeName);
1993
- store.delete(id);
1994
- await transactionToPromise(createTransaction);
2012
+ const now$1 = () => {
2013
+ return new Date().toISOString();
1995
2014
  };
1996
-
1997
- const getSession = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName, id) => {
1998
- const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
1999
- const transaction = database.transaction(storeName, 'readonly');
2000
- const store = transaction.objectStore(storeName);
2001
- const result = await requestToPromise(() => store.get(id));
2002
- return result;
2015
+ const isSameMessage$1 = (a, b) => {
2016
+ return a.id === b.id && a.inProgress === b.inProgress && a.role === b.role && a.text === b.text && a.time === b.time && JSON.stringify(a.toolCalls || []) === JSON.stringify(b.toolCalls || []);
2003
2017
  };
2004
-
2005
- const listSessions = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName) => {
2006
- const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
2007
- const transaction = database.transaction(storeName, 'readonly');
2008
- const store = transaction.objectStore(storeName);
2009
- const result = await requestToPromise(() => store.getAll());
2010
- return result;
2018
+ const canAppendMessages$1 = (previousMessages, nextMessages) => {
2019
+ if (nextMessages.length < previousMessages.length) {
2020
+ return false;
2021
+ }
2022
+ return previousMessages.every((message, index) => isSameMessage$1(message, nextMessages[index]));
2011
2023
  };
2012
-
2013
- const setSession = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName, session) => {
2014
- const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
2015
- const transaction = database.transaction(storeName, 'readwrite');
2016
- const createTransaction = () => transaction;
2017
- const store = transaction.objectStore(storeName);
2018
- store.put(session);
2019
- await transactionToPromise(createTransaction);
2024
+ const canUpdateMessages$1 = (previousMessages, nextMessages) => {
2025
+ if (previousMessages.length !== nextMessages.length) {
2026
+ return false;
2027
+ }
2028
+ for (let i = 0; i < previousMessages.length; i += 1) {
2029
+ const previous = previousMessages[i];
2030
+ const next = nextMessages[i];
2031
+ if (previous.id !== next.id || previous.role !== next.role) {
2032
+ return false;
2033
+ }
2034
+ }
2035
+ return true;
2036
+ };
2037
+ const getMutationEvents$1 = (previous, next) => {
2038
+ const timestamp = now$1();
2039
+ const events = [];
2040
+ if (!previous) {
2041
+ events.push({
2042
+ sessionId: next.id,
2043
+ timestamp,
2044
+ title: next.title,
2045
+ type: 'chat-session-created'
2046
+ });
2047
+ for (const message of next.messages) {
2048
+ events.push({
2049
+ message,
2050
+ sessionId: next.id,
2051
+ timestamp,
2052
+ type: 'chat-message-added'
2053
+ });
2054
+ }
2055
+ return events;
2056
+ }
2057
+ if (previous.title !== next.title) {
2058
+ events.push({
2059
+ sessionId: next.id,
2060
+ timestamp,
2061
+ title: next.title,
2062
+ type: 'chat-session-title-updated'
2063
+ });
2064
+ }
2065
+ if (canAppendMessages$1(previous.messages, next.messages)) {
2066
+ for (let i = previous.messages.length; i < next.messages.length; i += 1) {
2067
+ events.push({
2068
+ message: next.messages[i],
2069
+ sessionId: next.id,
2070
+ timestamp,
2071
+ type: 'chat-message-added'
2072
+ });
2073
+ }
2074
+ return events;
2075
+ }
2076
+ if (canUpdateMessages$1(previous.messages, next.messages)) {
2077
+ for (let i = 0; i < previous.messages.length; i += 1) {
2078
+ const previousMessage = previous.messages[i];
2079
+ const nextMessage = next.messages[i];
2080
+ if (!isSameMessage$1(previousMessage, nextMessage)) {
2081
+ events.push({
2082
+ inProgress: nextMessage.inProgress,
2083
+ messageId: nextMessage.id,
2084
+ sessionId: next.id,
2085
+ text: nextMessage.text,
2086
+ time: nextMessage.time,
2087
+ timestamp,
2088
+ toolCalls: nextMessage.toolCalls,
2089
+ type: 'chat-message-updated'
2090
+ });
2091
+ }
2092
+ }
2093
+ return events;
2094
+ }
2095
+ events.push({
2096
+ messages: [...next.messages],
2097
+ sessionId: next.id,
2098
+ timestamp,
2099
+ type: 'chat-session-messages-replaced'
2100
+ });
2101
+ return events;
2102
+ };
2103
+ const replaySession$1 = (id, summary, events) => {
2104
+ let deleted = false;
2105
+ let title = summary?.title || '';
2106
+ let messages = summary?.messages ? [...summary.messages] : [];
2107
+ for (const event of events) {
2108
+ if (event.sessionId !== id) {
2109
+ continue;
2110
+ }
2111
+ if (event.type === 'chat-session-created') {
2112
+ const {
2113
+ title: eventTitle
2114
+ } = event;
2115
+ deleted = false;
2116
+ title = eventTitle;
2117
+ continue;
2118
+ }
2119
+ if (event.type === 'chat-session-deleted') {
2120
+ deleted = true;
2121
+ continue;
2122
+ }
2123
+ if (event.type === 'chat-session-title-updated') {
2124
+ const {
2125
+ title: eventTitle
2126
+ } = event;
2127
+ title = eventTitle;
2128
+ continue;
2129
+ }
2130
+ if (event.type === 'chat-message-added') {
2131
+ messages = [...messages, event.message];
2132
+ continue;
2133
+ }
2134
+ if (event.type === 'chat-message-updated') {
2135
+ messages = messages.map(message => {
2136
+ if (message.id !== event.messageId) {
2137
+ return message;
2138
+ }
2139
+ return {
2140
+ ...message,
2141
+ inProgress: event.inProgress,
2142
+ text: event.text,
2143
+ time: event.time,
2144
+ toolCalls: event.toolCalls
2145
+ };
2146
+ });
2147
+ continue;
2148
+ }
2149
+ if (event.type === 'chat-session-messages-replaced') {
2150
+ messages = [...event.messages];
2151
+ }
2152
+ }
2153
+ if (deleted || !title) {
2154
+ return undefined;
2155
+ }
2156
+ return {
2157
+ id,
2158
+ messages,
2159
+ title
2160
+ };
2020
2161
  };
2021
-
2022
2162
  class IndexedDbChatSessionStorage {
2023
2163
  constructor(options = {}) {
2024
2164
  this.state = {
2025
2165
  databaseName: options.databaseName || 'lvce-chat-view-sessions',
2026
2166
  databasePromise: undefined,
2027
- databaseVersion: options.databaseVersion || 1,
2167
+ databaseVersion: options.databaseVersion || 2,
2168
+ eventStoreName: options.eventStoreName || 'chat-view-events',
2028
2169
  storeName: options.storeName || 'chat-sessions'
2029
2170
  };
2030
2171
  }
2031
- getDatabasePromise = () => {
2032
- return this.state.databasePromise;
2033
- };
2034
- setDatabasePromise = databasePromise => {
2172
+ openDatabase = async () => {
2173
+ if (this.state.databasePromise) {
2174
+ return this.state.databasePromise;
2175
+ }
2176
+ const request = indexedDB.open(this.state.databaseName, this.state.databaseVersion);
2177
+ request.addEventListener('upgradeneeded', () => {
2178
+ const database = request.result;
2179
+ if (!database.objectStoreNames.contains(this.state.storeName)) {
2180
+ database.createObjectStore(this.state.storeName, {
2181
+ keyPath: 'id'
2182
+ });
2183
+ }
2184
+ if (database.objectStoreNames.contains(this.state.eventStoreName)) {
2185
+ const {
2186
+ transaction
2187
+ } = request;
2188
+ if (!transaction) {
2189
+ return;
2190
+ }
2191
+ const eventStore = transaction.objectStore(this.state.eventStoreName);
2192
+ if (!eventStore.indexNames.contains('sessionId')) {
2193
+ eventStore.createIndex('sessionId', 'sessionId', {
2194
+ unique: false
2195
+ });
2196
+ }
2197
+ } else {
2198
+ const eventStore = database.createObjectStore(this.state.eventStoreName, {
2199
+ autoIncrement: true,
2200
+ keyPath: 'eventId'
2201
+ });
2202
+ eventStore.createIndex('sessionId', 'sessionId', {
2203
+ unique: false
2204
+ });
2205
+ }
2206
+ });
2207
+ const databasePromise = requestToPromise(() => request);
2035
2208
  this.state.databasePromise = databasePromise;
2209
+ return databasePromise;
2210
+ };
2211
+ listSummaries = async () => {
2212
+ const database = await this.openDatabase();
2213
+ const transaction = database.transaction(this.state.storeName, 'readonly');
2214
+ const store = transaction.objectStore(this.state.storeName);
2215
+ const summaries = await requestToPromise(() => store.getAll());
2216
+ return summaries;
2217
+ };
2218
+ getSummary = async id => {
2219
+ const database = await this.openDatabase();
2220
+ const transaction = database.transaction(this.state.storeName, 'readonly');
2221
+ const store = transaction.objectStore(this.state.storeName);
2222
+ const summary = await requestToPromise(() => store.get(id));
2223
+ return summary;
2224
+ };
2225
+ getEventsBySessionId = async sessionId => {
2226
+ const database = await this.openDatabase();
2227
+ const transaction = database.transaction(this.state.eventStoreName, 'readonly');
2228
+ const store = transaction.objectStore(this.state.eventStoreName);
2229
+ const index = store.index('sessionId');
2230
+ const events = await requestToPromise(() => index.getAll(IDBKeyRange.only(sessionId)));
2231
+ return events.map(toChatViewEvent);
2232
+ };
2233
+ listEventsInternal = async () => {
2234
+ const database = await this.openDatabase();
2235
+ const transaction = database.transaction(this.state.eventStoreName, 'readonly');
2236
+ const store = transaction.objectStore(this.state.eventStoreName);
2237
+ const events = await requestToPromise(() => store.getAll());
2238
+ return events.map(toChatViewEvent);
2036
2239
  };
2240
+ appendEvents = async events => {
2241
+ if (events.length === 0) {
2242
+ return;
2243
+ }
2244
+ const database = await this.openDatabase();
2245
+ const transaction = database.transaction([this.state.storeName, this.state.eventStoreName], 'readwrite');
2246
+ const summaryStore = transaction.objectStore(this.state.storeName);
2247
+ const eventStore = transaction.objectStore(this.state.eventStoreName);
2248
+ for (const event of events) {
2249
+ eventStore.add(event);
2250
+ if (event.type === 'chat-session-created' || event.type === 'chat-session-title-updated') {
2251
+ summaryStore.put({
2252
+ id: event.sessionId,
2253
+ title: event.title
2254
+ });
2255
+ }
2256
+ if (event.type === 'chat-session-deleted') {
2257
+ summaryStore.delete(event.sessionId);
2258
+ }
2259
+ }
2260
+ await transactionToPromise(() => transaction);
2261
+ };
2262
+ async appendEvent(event) {
2263
+ await this.appendEvents([event]);
2264
+ }
2037
2265
  async clear() {
2038
- return clear(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName);
2266
+ const database = await this.openDatabase();
2267
+ const transaction = database.transaction([this.state.storeName, this.state.eventStoreName], 'readwrite');
2268
+ transaction.objectStore(this.state.storeName).clear();
2269
+ transaction.objectStore(this.state.eventStoreName).clear();
2270
+ await transactionToPromise(() => transaction);
2039
2271
  }
2040
2272
  async deleteSession(id) {
2041
- return deleteSession$1(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName, id);
2273
+ await this.appendEvent({
2274
+ sessionId: id,
2275
+ timestamp: now$1(),
2276
+ type: 'chat-session-deleted'
2277
+ });
2278
+ }
2279
+ async getEvents(sessionId) {
2280
+ if (sessionId) {
2281
+ return this.getEventsBySessionId(sessionId);
2282
+ }
2283
+ return this.listEventsInternal();
2042
2284
  }
2043
2285
  async getSession(id) {
2044
- return getSession(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName, id);
2286
+ const [summary, events] = await Promise.all([this.getSummary(id), this.getEventsBySessionId(id)]);
2287
+ return replaySession$1(id, summary, events);
2045
2288
  }
2046
2289
  async listSessions() {
2047
- return listSessions(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName);
2290
+ const summaries = await this.listSummaries();
2291
+ const sessions = [];
2292
+ for (const summary of summaries) {
2293
+ const events = await this.getEventsBySessionId(summary.id);
2294
+ const session = replaySession$1(summary.id, summary, events);
2295
+ if (!session) {
2296
+ continue;
2297
+ }
2298
+ sessions.push(session);
2299
+ }
2300
+ return sessions;
2048
2301
  }
2049
2302
  async setSession(session) {
2050
- return setSession(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName, session);
2303
+ const previous = await this.getSession(session.id);
2304
+ const events = getMutationEvents$1(previous, session);
2305
+ await this.appendEvents(events);
2306
+ if (events.length === 0) {
2307
+ const database = await this.openDatabase();
2308
+ const transaction = database.transaction(this.state.storeName, 'readwrite');
2309
+ const summaryStore = transaction.objectStore(this.state.storeName);
2310
+ summaryStore.put({
2311
+ id: session.id,
2312
+ title: session.title
2313
+ });
2314
+ await transactionToPromise(() => transaction);
2315
+ }
2051
2316
  }
2052
2317
  }
2053
2318
 
2319
+ const now = () => {
2320
+ return new Date().toISOString();
2321
+ };
2322
+ const isSameMessage = (a, b) => {
2323
+ return a.id === b.id && a.inProgress === b.inProgress && a.role === b.role && a.text === b.text && a.time === b.time && JSON.stringify(a.toolCalls || []) === JSON.stringify(b.toolCalls || []);
2324
+ };
2325
+ const canAppendMessages = (previousMessages, nextMessages) => {
2326
+ if (nextMessages.length < previousMessages.length) {
2327
+ return false;
2328
+ }
2329
+ return previousMessages.every((message, index) => isSameMessage(message, nextMessages[index]));
2330
+ };
2331
+ const canUpdateMessages = (previousMessages, nextMessages) => {
2332
+ if (previousMessages.length !== nextMessages.length) {
2333
+ return false;
2334
+ }
2335
+ for (let i = 0; i < previousMessages.length; i += 1) {
2336
+ const previous = previousMessages[i];
2337
+ const next = nextMessages[i];
2338
+ if (previous.id !== next.id || previous.role !== next.role) {
2339
+ return false;
2340
+ }
2341
+ }
2342
+ return true;
2343
+ };
2344
+ const getMutationEvents = (previous, next) => {
2345
+ const timestamp = now();
2346
+ const events = [];
2347
+ if (!previous) {
2348
+ events.push({
2349
+ sessionId: next.id,
2350
+ timestamp,
2351
+ title: next.title,
2352
+ type: 'chat-session-created'
2353
+ });
2354
+ for (const message of next.messages) {
2355
+ events.push({
2356
+ message,
2357
+ sessionId: next.id,
2358
+ timestamp,
2359
+ type: 'chat-message-added'
2360
+ });
2361
+ }
2362
+ return events;
2363
+ }
2364
+ if (previous.title !== next.title) {
2365
+ events.push({
2366
+ sessionId: next.id,
2367
+ timestamp,
2368
+ title: next.title,
2369
+ type: 'chat-session-title-updated'
2370
+ });
2371
+ }
2372
+ if (canAppendMessages(previous.messages, next.messages)) {
2373
+ for (let i = previous.messages.length; i < next.messages.length; i += 1) {
2374
+ events.push({
2375
+ message: next.messages[i],
2376
+ sessionId: next.id,
2377
+ timestamp,
2378
+ type: 'chat-message-added'
2379
+ });
2380
+ }
2381
+ return events;
2382
+ }
2383
+ if (canUpdateMessages(previous.messages, next.messages)) {
2384
+ for (let i = 0; i < previous.messages.length; i += 1) {
2385
+ const previousMessage = previous.messages[i];
2386
+ const nextMessage = next.messages[i];
2387
+ if (!isSameMessage(previousMessage, nextMessage)) {
2388
+ events.push({
2389
+ inProgress: nextMessage.inProgress,
2390
+ messageId: nextMessage.id,
2391
+ sessionId: next.id,
2392
+ text: nextMessage.text,
2393
+ time: nextMessage.time,
2394
+ timestamp,
2395
+ toolCalls: nextMessage.toolCalls,
2396
+ type: 'chat-message-updated'
2397
+ });
2398
+ }
2399
+ }
2400
+ return events;
2401
+ }
2402
+ events.push({
2403
+ messages: [...next.messages],
2404
+ sessionId: next.id,
2405
+ timestamp,
2406
+ type: 'chat-session-messages-replaced'
2407
+ });
2408
+ return events;
2409
+ };
2410
+ const replaySession = (id, title, events) => {
2411
+ let deleted = false;
2412
+ let currentTitle = title || '';
2413
+ let messages = [];
2414
+ for (const event of events) {
2415
+ if (event.sessionId !== id) {
2416
+ continue;
2417
+ }
2418
+ if (event.type === 'chat-session-created') {
2419
+ deleted = false;
2420
+ currentTitle = event.title;
2421
+ continue;
2422
+ }
2423
+ if (event.type === 'chat-session-deleted') {
2424
+ deleted = true;
2425
+ continue;
2426
+ }
2427
+ if (event.type === 'chat-session-title-updated') {
2428
+ currentTitle = event.title;
2429
+ continue;
2430
+ }
2431
+ if (event.type === 'chat-message-added') {
2432
+ messages = [...messages, event.message];
2433
+ continue;
2434
+ }
2435
+ if (event.type === 'chat-message-updated') {
2436
+ messages = messages.map(message => {
2437
+ if (message.id !== event.messageId) {
2438
+ return message;
2439
+ }
2440
+ return {
2441
+ ...message,
2442
+ inProgress: event.inProgress,
2443
+ text: event.text,
2444
+ time: event.time,
2445
+ toolCalls: event.toolCalls
2446
+ };
2447
+ });
2448
+ continue;
2449
+ }
2450
+ if (event.type === 'chat-session-messages-replaced') {
2451
+ messages = [...event.messages];
2452
+ }
2453
+ }
2454
+ if (deleted || !currentTitle) {
2455
+ return undefined;
2456
+ }
2457
+ return {
2458
+ id,
2459
+ messages,
2460
+ title: currentTitle
2461
+ };
2462
+ };
2054
2463
  class InMemoryChatSessionStorage {
2055
- sessions = new Map();
2464
+ events = [];
2465
+ summaries = new Map();
2466
+ async appendEvent(event) {
2467
+ this.events.push(event);
2468
+ if (event.type === 'chat-session-created' || event.type === 'chat-session-title-updated') {
2469
+ this.summaries.set(event.sessionId, event.title);
2470
+ return;
2471
+ }
2472
+ if (event.type === 'chat-session-deleted') {
2473
+ this.summaries.delete(event.sessionId);
2474
+ }
2475
+ }
2056
2476
  async clear() {
2057
- this.sessions.clear();
2477
+ this.events.length = 0;
2478
+ this.summaries.clear();
2058
2479
  }
2059
2480
  async deleteSession(id) {
2060
- this.sessions.delete(id);
2481
+ await this.appendEvent({
2482
+ sessionId: id,
2483
+ timestamp: now(),
2484
+ type: 'chat-session-deleted'
2485
+ });
2486
+ }
2487
+ async getEvents(sessionId) {
2488
+ if (!sessionId) {
2489
+ return [...this.events];
2490
+ }
2491
+ return this.events.filter(event => event.sessionId === sessionId);
2061
2492
  }
2062
2493
  async getSession(id) {
2063
- return this.sessions.get(id);
2494
+ return replaySession(id, this.summaries.get(id), this.events);
2064
2495
  }
2065
2496
  async listSessions() {
2066
- return [...this.sessions.values()];
2497
+ const ids = new Set();
2498
+ for (const id of this.summaries.keys()) {
2499
+ ids.add(id);
2500
+ }
2501
+ for (const event of this.events) {
2502
+ ids.add(event.sessionId);
2503
+ }
2504
+ const sessions = [];
2505
+ for (const id of ids) {
2506
+ const session = replaySession(id, this.summaries.get(id), this.events);
2507
+ if (!session) {
2508
+ continue;
2509
+ }
2510
+ sessions.push(session);
2511
+ }
2512
+ return sessions;
2067
2513
  }
2068
2514
  async setSession(session) {
2069
- this.sessions.set(session.id, session);
2515
+ const previous = await this.getSession(session.id);
2516
+ const events = getMutationEvents(previous, session);
2517
+ for (const event of events) {
2518
+ await this.appendEvent(event);
2519
+ }
2520
+ this.summaries.set(session.id, session.title);
2070
2521
  }
2071
2522
  }
2072
2523
 
@@ -2109,6 +2560,9 @@ const deleteChatSession = async id => {
2109
2560
  const clearChatSessions = async () => {
2110
2561
  await chatSessionStorage.clear();
2111
2562
  };
2563
+ const appendChatViewEvent = async event => {
2564
+ await chatSessionStorage.appendEvent(event);
2565
+ };
2112
2566
 
2113
2567
  const generateSessionId = () => {
2114
2568
  return crypto.randomUUID();
@@ -2601,6 +3055,20 @@ const getTextContent = content => {
2601
3055
  return textParts.join('\n');
2602
3056
  };
2603
3057
 
3058
+ const getOpenAiParams = (completionMessages, modelId, stream, includeObfuscation, tools) => {
3059
+ return {
3060
+ messages: completionMessages,
3061
+ model: modelId,
3062
+ ...(stream ? {
3063
+ stream: true
3064
+ } : {}),
3065
+ ...(includeObfuscation ? {
3066
+ include_obfuscation: true
3067
+ } : {}),
3068
+ tool_choice: 'auto',
3069
+ tools
3070
+ };
3071
+ };
2604
3072
  const getStreamChunkText = content => {
2605
3073
  if (typeof content === 'string') {
2606
3074
  return content;
@@ -2627,7 +3095,59 @@ const parseSseEvent = eventChunk => {
2627
3095
  }
2628
3096
  return dataLines;
2629
3097
  };
2630
- const parseOpenApiStream = async (response, onTextChunk) => {
3098
+ const updateToolCallAccumulator = (accumulator, chunk) => {
3099
+ let changed = false;
3100
+ const nextAccumulator = {
3101
+ ...accumulator
3102
+ };
3103
+ for (const item of chunk) {
3104
+ if (!item || typeof item !== 'object') {
3105
+ continue;
3106
+ }
3107
+ const index = Reflect.get(item, 'index');
3108
+ if (typeof index !== 'number') {
3109
+ continue;
3110
+ }
3111
+ const current = nextAccumulator[index] || {
3112
+ arguments: '',
3113
+ name: ''
3114
+ };
3115
+ const id = Reflect.get(item, 'id');
3116
+ const toolFunction = Reflect.get(item, 'function');
3117
+ let {
3118
+ name
3119
+ } = current;
3120
+ let args = current.arguments;
3121
+ if (toolFunction && typeof toolFunction === 'object') {
3122
+ const deltaName = Reflect.get(toolFunction, 'name');
3123
+ const deltaArguments = Reflect.get(toolFunction, 'arguments');
3124
+ if (typeof deltaName === 'string' && deltaName) {
3125
+ name = deltaName;
3126
+ }
3127
+ if (typeof deltaArguments === 'string') {
3128
+ args += deltaArguments;
3129
+ }
3130
+ }
3131
+ const next = {
3132
+ arguments: args,
3133
+ id: typeof id === 'string' ? id : current.id,
3134
+ name
3135
+ };
3136
+ if (JSON.stringify(next) !== JSON.stringify(current)) {
3137
+ nextAccumulator[index] = next;
3138
+ changed = true;
3139
+ }
3140
+ }
3141
+ if (!changed) {
3142
+ return undefined;
3143
+ }
3144
+ const toolCalls = Object.entries(nextAccumulator).toSorted((a, b) => Number(a[0]) - Number(b[0])).map(entry => entry[1]).filter(toolCall => !!toolCall.name);
3145
+ return {
3146
+ nextAccumulator,
3147
+ toolCalls
3148
+ };
3149
+ };
3150
+ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDataEvent, onEventStreamFinished) => {
2631
3151
  if (!response.body) {
2632
3152
  return {
2633
3153
  details: 'request-failed',
@@ -2639,6 +3159,7 @@ const parseOpenApiStream = async (response, onTextChunk) => {
2639
3159
  let remainder = '';
2640
3160
  let text = '';
2641
3161
  let done = false;
3162
+ let toolCallAccumulator = {};
2642
3163
  while (!done) {
2643
3164
  const {
2644
3165
  done: streamDone,
@@ -2664,6 +3185,9 @@ const parseOpenApiStream = async (response, onTextChunk) => {
2664
3185
  }
2665
3186
  for (const line of dataLines) {
2666
3187
  if (line === '[DONE]') {
3188
+ if (onEventStreamFinished) {
3189
+ await onEventStreamFinished();
3190
+ }
2667
3191
  done = true;
2668
3192
  break;
2669
3193
  }
@@ -2676,6 +3200,9 @@ const parseOpenApiStream = async (response, onTextChunk) => {
2676
3200
  if (!parsed || typeof parsed !== 'object') {
2677
3201
  continue;
2678
3202
  }
3203
+ if (onDataEvent) {
3204
+ await onDataEvent(parsed);
3205
+ }
2679
3206
  const choices = Reflect.get(parsed, 'choices');
2680
3207
  if (!Array.isArray(choices)) {
2681
3208
  continue;
@@ -2688,6 +3215,14 @@ const parseOpenApiStream = async (response, onTextChunk) => {
2688
3215
  if (!delta || typeof delta !== 'object') {
2689
3216
  continue;
2690
3217
  }
3218
+ const toolCalls = Reflect.get(delta, 'tool_calls');
3219
+ const updatedToolCallResult = Array.isArray(toolCalls) ? updateToolCallAccumulator(toolCallAccumulator, toolCalls) : undefined;
3220
+ if (updatedToolCallResult) {
3221
+ toolCallAccumulator = updatedToolCallResult.nextAccumulator;
3222
+ }
3223
+ if (updatedToolCallResult && onToolCallsChunk) {
3224
+ await onToolCallsChunk(updatedToolCallResult.toolCalls);
3225
+ }
2691
3226
  const content = Reflect.get(delta, 'content');
2692
3227
  const chunkText = getStreamChunkText(content);
2693
3228
  if (!chunkText) {
@@ -2704,6 +3239,9 @@ const parseOpenApiStream = async (response, onTextChunk) => {
2704
3239
  const dataLines = parseSseEvent(remainder);
2705
3240
  for (const line of dataLines) {
2706
3241
  if (line === '[DONE]') {
3242
+ if (onEventStreamFinished) {
3243
+ await onEventStreamFinished();
3244
+ }
2707
3245
  continue;
2708
3246
  }
2709
3247
  let parsed;
@@ -2715,6 +3253,9 @@ const parseOpenApiStream = async (response, onTextChunk) => {
2715
3253
  if (!parsed || typeof parsed !== 'object') {
2716
3254
  continue;
2717
3255
  }
3256
+ if (onDataEvent) {
3257
+ await onDataEvent(parsed);
3258
+ }
2718
3259
  const choices = Reflect.get(parsed, 'choices');
2719
3260
  if (!Array.isArray(choices)) {
2720
3261
  continue;
@@ -2727,6 +3268,14 @@ const parseOpenApiStream = async (response, onTextChunk) => {
2727
3268
  if (!delta || typeof delta !== 'object') {
2728
3269
  continue;
2729
3270
  }
3271
+ const toolCalls = Reflect.get(delta, 'tool_calls');
3272
+ const updatedToolCallResult = Array.isArray(toolCalls) ? updateToolCallAccumulator(toolCallAccumulator, toolCalls) : undefined;
3273
+ if (updatedToolCallResult) {
3274
+ toolCallAccumulator = updatedToolCallResult.nextAccumulator;
3275
+ }
3276
+ if (updatedToolCallResult && onToolCallsChunk) {
3277
+ await onToolCallsChunk(updatedToolCallResult.toolCalls);
3278
+ }
2730
3279
  const content = Reflect.get(delta, 'content');
2731
3280
  const chunkText = getStreamChunkText(content);
2732
3281
  if (!chunkText) {
@@ -2769,7 +3318,10 @@ const getOpenApiErrorDetails = async response => {
2769
3318
  const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApiApiBaseUrl, assetDir, platform, options) => {
2770
3319
  const {
2771
3320
  includeObfuscation = false,
3321
+ onDataEvent,
3322
+ onEventStreamFinished,
2772
3323
  onTextChunk,
3324
+ onToolCallsChunk,
2773
3325
  stream
2774
3326
  } = options ?? {
2775
3327
  stream: false
@@ -2784,18 +3336,7 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
2784
3336
  let response;
2785
3337
  try {
2786
3338
  response = await fetch(getOpenApiApiEndpoint(openApiApiBaseUrl, stream), {
2787
- body: JSON.stringify({
2788
- messages: completionMessages,
2789
- model: modelId,
2790
- ...(stream ? {
2791
- stream: true
2792
- } : {}),
2793
- ...(includeObfuscation ? {
2794
- include_obfuscation: true
2795
- } : {}),
2796
- tool_choice: 'auto',
2797
- tools
2798
- }),
3339
+ body: JSON.stringify(getOpenAiParams(completionMessages, modelId, stream, includeObfuscation, tools)),
2799
3340
  headers: {
2800
3341
  Authorization: `Bearer ${openApiApiKey}`,
2801
3342
  'Content-Type': 'application/json',
@@ -2825,7 +3366,7 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
2825
3366
  };
2826
3367
  }
2827
3368
  if (stream) {
2828
- return parseOpenApiStream(response, onTextChunk);
3369
+ return parseOpenApiStream(response, onTextChunk, onToolCallsChunk, onDataEvent, onEventStreamFinished);
2829
3370
  }
2830
3371
  let parsed;
2831
3372
  try {
@@ -3239,11 +3780,14 @@ const isOpenRouterModel = (selectedModelId, models) => {
3239
3780
 
3240
3781
  const getAiResponse = async ({
3241
3782
  assetDir,
3783
+ messageId,
3242
3784
  messages,
3243
3785
  mockApiCommandId,
3244
3786
  models,
3245
- nextMessageId,
3787
+ onDataEvent,
3788
+ onEventStreamFinished,
3246
3789
  onTextChunk,
3790
+ onToolCallsChunk,
3247
3791
  openApiApiBaseUrl,
3248
3792
  openApiApiKey,
3249
3793
  openRouterApiBaseUrl,
@@ -3272,7 +3816,10 @@ const getAiResponse = async ({
3272
3816
  } else if (openApiApiKey) {
3273
3817
  const result = await getOpenApiAssistantText(messages, getOpenApiModelId(selectedModelId), openApiApiKey, openApiApiBaseUrl, assetDir, platform, {
3274
3818
  includeObfuscation: passIncludeObfuscation,
3819
+ onDataEvent,
3820
+ onEventStreamFinished,
3275
3821
  onTextChunk,
3822
+ onToolCallsChunk,
3276
3823
  stream: streamingEnabled
3277
3824
  });
3278
3825
  if (result.type === 'success') {
@@ -3320,7 +3867,7 @@ const getAiResponse = async ({
3320
3867
  minute: '2-digit'
3321
3868
  });
3322
3869
  return {
3323
- id: `message-${nextMessageId}`,
3870
+ id: messageId || crypto.randomUUID(),
3324
3871
  role: 'assistant',
3325
3872
  text,
3326
3873
  time: assistantTime
@@ -3375,7 +3922,6 @@ const handleClickSaveOpenApiApiKey = async state => {
3375
3922
  messages: retryMessages,
3376
3923
  mockApiCommandId: updatedState.mockApiCommandId,
3377
3924
  models: updatedState.models,
3378
- nextMessageId: updatedState.nextMessageId,
3379
3925
  openApiApiBaseUrl: updatedState.openApiApiBaseUrl,
3380
3926
  openApiApiKey: updatedState.openApiApiKey,
3381
3927
  openRouterApiBaseUrl: updatedState.openRouterApiBaseUrl,
@@ -3455,7 +4001,6 @@ const handleClickSaveOpenRouterApiKey = async state => {
3455
4001
  messages: retryMessages,
3456
4002
  mockApiCommandId: updatedState.mockApiCommandId,
3457
4003
  models: updatedState.models,
3458
- nextMessageId: updatedState.nextMessageId,
3459
4004
  openApiApiBaseUrl: updatedState.openApiApiBaseUrl,
3460
4005
  openApiApiKey: updatedState.openApiApiKey,
3461
4006
  openRouterApiBaseUrl: updatedState.openRouterApiBaseUrl,
@@ -3512,6 +4057,25 @@ const updateMessageTextInSelectedSession = (sessions, selectedSessionId, message
3512
4057
  };
3513
4058
  });
3514
4059
  };
4060
+ const updateMessageToolCallsInSelectedSession = (sessions, selectedSessionId, messageId, toolCalls) => {
4061
+ return sessions.map(session => {
4062
+ if (session.id !== selectedSessionId) {
4063
+ return session;
4064
+ }
4065
+ return {
4066
+ ...session,
4067
+ messages: session.messages.map(message => {
4068
+ if (message.id !== messageId) {
4069
+ return message;
4070
+ }
4071
+ return {
4072
+ ...message,
4073
+ toolCalls
4074
+ };
4075
+ })
4076
+ };
4077
+ });
4078
+ };
3515
4079
  const handleTextChunkFunction = async (uid, assistantMessageId, chunk, handleTextChunkState) => {
3516
4080
  const selectedSession = handleTextChunkState.latestState.sessions.find(session => session.id === handleTextChunkState.latestState.selectedSessionId);
3517
4081
  if (!selectedSession) {
@@ -3527,6 +4091,13 @@ const handleTextChunkFunction = async (uid, assistantMessageId, chunk, handleTex
3527
4091
  previousState: handleTextChunkState.previousState
3528
4092
  };
3529
4093
  }
4094
+ await appendChatViewEvent({
4095
+ content: chunk,
4096
+ messageId: assistantMessageId,
4097
+ sessionId: handleTextChunkState.latestState.selectedSessionId,
4098
+ timestamp: new Date().toISOString(),
4099
+ type: 'handle-response-chunk'
4100
+ });
3530
4101
  const updatedText = assistantMessage.text + chunk;
3531
4102
  const updatedSessions = updateMessageTextInSelectedSession(handleTextChunkState.latestState.sessions, handleTextChunkState.latestState.selectedSessionId, assistantMessageId, updatedText, true);
3532
4103
  const nextState = {
@@ -3541,6 +4112,34 @@ const handleTextChunkFunction = async (uid, assistantMessageId, chunk, handleTex
3541
4112
  previousState: nextState
3542
4113
  };
3543
4114
  };
4115
+ const handleToolCallsChunkFunction = async (uid, assistantMessageId, toolCalls, handleTextChunkState) => {
4116
+ const selectedSession = handleTextChunkState.latestState.sessions.find(session => session.id === handleTextChunkState.latestState.selectedSessionId);
4117
+ if (!selectedSession) {
4118
+ return {
4119
+ latestState: handleTextChunkState.latestState,
4120
+ previousState: handleTextChunkState.previousState
4121
+ };
4122
+ }
4123
+ const assistantMessage = selectedSession.messages.find(message => message.id === assistantMessageId);
4124
+ if (!assistantMessage) {
4125
+ return {
4126
+ latestState: handleTextChunkState.latestState,
4127
+ previousState: handleTextChunkState.previousState
4128
+ };
4129
+ }
4130
+ const updatedSessions = updateMessageToolCallsInSelectedSession(handleTextChunkState.latestState.sessions, handleTextChunkState.latestState.selectedSessionId, assistantMessageId, toolCalls);
4131
+ const nextState = {
4132
+ ...handleTextChunkState.latestState,
4133
+ sessions: updatedSessions
4134
+ };
4135
+ set(uid, handleTextChunkState.previousState, nextState);
4136
+ // @ts-ignore
4137
+ await invoke('Chat.rerender');
4138
+ return {
4139
+ latestState: nextState,
4140
+ previousState: nextState
4141
+ };
4142
+ };
3544
4143
 
3545
4144
  const appendMessageToSelectedSession = (sessions, selectedSessionId, message) => {
3546
4145
  return sessions.map(session => {
@@ -3581,13 +4180,14 @@ const handleSubmit = async state => {
3581
4180
  hour: '2-digit',
3582
4181
  minute: '2-digit'
3583
4182
  });
4183
+ const userMessageId = crypto.randomUUID();
3584
4184
  const userMessage = {
3585
- id: `message-${nextMessageId}`,
4185
+ id: userMessageId,
3586
4186
  role: 'user',
3587
4187
  text: userText,
3588
4188
  time: userTime
3589
4189
  };
3590
- const assistantMessageId = `message-${nextMessageId + 1}`;
4190
+ const assistantMessageId = crypto.randomUUID();
3591
4191
  const assistantTime = new Date().toLocaleTimeString([], {
3592
4192
  hour: '2-digit',
3593
4193
  minute: '2-digit'
@@ -3614,6 +4214,12 @@ const handleSubmit = async state => {
3614
4214
  let optimisticState;
3615
4215
  if (viewMode === 'list') {
3616
4216
  const newSessionId = generateSessionId();
4217
+ await appendChatViewEvent({
4218
+ sessionId: newSessionId,
4219
+ timestamp: new Date().toISOString(),
4220
+ type: 'handle-submit',
4221
+ value: userText
4222
+ });
3617
4223
  const newSession = {
3618
4224
  id: newSessionId,
3619
4225
  messages: streamingEnabled ? [userMessage, inProgressAssistantMessage] : [userMessage],
@@ -3632,6 +4238,12 @@ const handleSubmit = async state => {
3632
4238
  viewMode: 'detail'
3633
4239
  });
3634
4240
  } else {
4241
+ await appendChatViewEvent({
4242
+ sessionId: selectedSessionId,
4243
+ timestamp: new Date().toISOString(),
4244
+ type: 'handle-submit',
4245
+ value: userText
4246
+ });
3635
4247
  const updatedWithUser = appendMessageToSelectedSession(workingSessions, selectedSessionId, userMessage);
3636
4248
  const updatedSessions = streamingEnabled ? appendMessageToSelectedSession(updatedWithUser, selectedSessionId, inProgressAssistantMessage) : updatedWithUser;
3637
4249
  const selectedSession = updatedSessions.find(session => session.id === selectedSessionId);
@@ -3662,11 +4274,30 @@ const handleSubmit = async state => {
3662
4274
  } : undefined;
3663
4275
  const assistantMessage = await getAiResponse({
3664
4276
  assetDir,
4277
+ messageId: assistantMessageId,
3665
4278
  messages,
3666
4279
  mockApiCommandId,
3667
4280
  models,
3668
- nextMessageId: optimisticState.nextMessageId,
4281
+ onDataEvent: async value => {
4282
+ await appendChatViewEvent({
4283
+ sessionId: optimisticState.selectedSessionId,
4284
+ timestamp: new Date().toISOString(),
4285
+ type: 'data-event',
4286
+ value
4287
+ });
4288
+ },
4289
+ onEventStreamFinished: async () => {
4290
+ await appendChatViewEvent({
4291
+ sessionId: optimisticState.selectedSessionId,
4292
+ timestamp: new Date().toISOString(),
4293
+ type: 'event-stream-finished',
4294
+ value: '[DONE]'
4295
+ });
4296
+ },
3669
4297
  onTextChunk: handleTextChunkFunctionRef,
4298
+ onToolCallsChunk: async toolCalls => {
4299
+ handleTextChunkState = await handleToolCallsChunkFunction(state.uid, assistantMessageId, toolCalls, handleTextChunkState);
4300
+ },
3670
4301
  openApiApiBaseUrl,
3671
4302
  openApiApiKey,
3672
4303
  openRouterApiBaseUrl,
@@ -3712,6 +4343,7 @@ const Send = 'send';
3712
4343
  const Back = 'back';
3713
4344
  const Model = 'model';
3714
4345
  const CreateSession = 'create-session';
4346
+ const SessionDebug = 'session-debug';
3715
4347
  const Settings = 'settings';
3716
4348
  const CloseChat = 'close-chat';
3717
4349
  const SessionDelete = 'SessionDelete';
@@ -3856,6 +4488,11 @@ const handleClickNew = async state => {
3856
4488
  return createSession(state);
3857
4489
  };
3858
4490
 
4491
+ const handleClickSessionDebug = async state => {
4492
+ await invoke('Main.openUri', `chat-debug://${state.selectedSessionId}`);
4493
+ return state;
4494
+ };
4495
+
3859
4496
  const handleClickSettings = async () => {
3860
4497
  // TODO
3861
4498
  await invoke('Main.openUri', 'app://settings.json');
@@ -3877,6 +4514,14 @@ const handleInput = async (state, name, value, inputSource = 'user') => {
3877
4514
  if (name !== Composer) {
3878
4515
  return state;
3879
4516
  }
4517
+ if (state.selectedSessionId) {
4518
+ await appendChatViewEvent({
4519
+ sessionId: state.selectedSessionId,
4520
+ timestamp: new Date().toISOString(),
4521
+ type: 'handle-input',
4522
+ value
4523
+ });
4524
+ }
3880
4525
  const composerHeight = await getComposerHeight(state, value);
3881
4526
  return {
3882
4527
  ...state,
@@ -3904,7 +4549,7 @@ const handleInputFocus = async (state, name) => {
3904
4549
  focused: true
3905
4550
  };
3906
4551
  }
3907
- if (name === CreateSession || name === Settings || name === CloseChat || name === Back) {
4552
+ if (name === CreateSession || name === SessionDebug || name === Settings || name === CloseChat || name === Back) {
3908
4553
  return {
3909
4554
  ...state,
3910
4555
  focus: 'header',
@@ -4016,7 +4661,7 @@ const sendMessagePortToExtensionHostWorker = async port => {
4016
4661
 
4017
4662
  const createExtensionHostRpc = async () => {
4018
4663
  try {
4019
- const rpc = await create$4({
4664
+ const rpc = await create$5({
4020
4665
  commandMap: {},
4021
4666
  send: sendMessagePortToExtensionHostWorker
4022
4667
  });
@@ -4393,6 +5038,7 @@ const HandleSubmit = 19;
4393
5038
  const HandleModelChange = 20;
4394
5039
  const HandleChatListScroll = 21;
4395
5040
  const HandleMessagesScroll = 22;
5041
+ const HandleClickSessionDebug = 23;
4396
5042
 
4397
5043
  const getModelLabel = model => {
4398
5044
  if (model.provider === 'openRouter') {
@@ -4540,6 +5186,11 @@ const getHeaderActionVirtualDom = item => {
4540
5186
 
4541
5187
  const getChatHeaderActionsDom = () => {
4542
5188
  const items = [{
5189
+ icon: 'MaskIcon MaskIconDebugPause',
5190
+ name: SessionDebug,
5191
+ onClick: HandleClickSessionDebug,
5192
+ title: debug()
5193
+ }, {
4543
5194
  icon: 'MaskIcon MaskIconAdd',
4544
5195
  name: CreateSession,
4545
5196
  onClick: HandleClickNew,
@@ -4644,6 +5295,126 @@ const getMissingOpenRouterApiKeyDom = (openRouterApiKeyInput, openRouterApiKeySt
4644
5295
  });
4645
5296
  };
4646
5297
 
5298
+ const orderedListItemRegex = /^\s*\d+\.\s+(.*)$/;
5299
+ const parseMessageContent = rawMessage => {
5300
+ if (rawMessage === '') {
5301
+ return [{
5302
+ text: '',
5303
+ type: 'text'
5304
+ }];
5305
+ }
5306
+ const lines = rawMessage.split(/\r?\n/);
5307
+ const nodes = [];
5308
+ let paragraphLines = [];
5309
+ let listItems = [];
5310
+ const flushParagraph = () => {
5311
+ if (paragraphLines.length === 0) {
5312
+ return;
5313
+ }
5314
+ nodes.push({
5315
+ text: paragraphLines.join('\n'),
5316
+ type: 'text'
5317
+ });
5318
+ paragraphLines = [];
5319
+ };
5320
+ const flushList = () => {
5321
+ if (listItems.length === 0) {
5322
+ return;
5323
+ }
5324
+ nodes.push({
5325
+ items: listItems,
5326
+ type: 'list'
5327
+ });
5328
+ listItems = [];
5329
+ };
5330
+ for (const line of lines) {
5331
+ if (!line.trim()) {
5332
+ flushList();
5333
+ flushParagraph();
5334
+ continue;
5335
+ }
5336
+ const match = line.match(orderedListItemRegex);
5337
+ if (match) {
5338
+ flushParagraph();
5339
+ listItems.push({
5340
+ text: match[1],
5341
+ type: 'list-item'
5342
+ });
5343
+ continue;
5344
+ }
5345
+ flushList();
5346
+ paragraphLines.push(line);
5347
+ }
5348
+ flushList();
5349
+ flushParagraph();
5350
+ return nodes.length === 0 ? [{
5351
+ text: '',
5352
+ type: 'text'
5353
+ }] : nodes;
5354
+ };
5355
+ const getMessageContentDom = nodes => {
5356
+ return nodes.flatMap(node => {
5357
+ if (node.type === 'text') {
5358
+ return [{
5359
+ childCount: 1,
5360
+ className: Markdown,
5361
+ type: P
5362
+ }, text(node.text)];
5363
+ }
5364
+ return [{
5365
+ childCount: node.items.length,
5366
+ className: ChatOrderedList,
5367
+ type: Ol
5368
+ }, ...node.items.flatMap(item => {
5369
+ return [{
5370
+ childCount: 1,
5371
+ className: ChatOrderedListItem,
5372
+ type: Li
5373
+ }, text(item.text)];
5374
+ })];
5375
+ });
5376
+ };
5377
+
5378
+ const getToolCallArgumentPreview = rawArguments => {
5379
+ if (!rawArguments.trim()) {
5380
+ return '""';
5381
+ }
5382
+ let parsed;
5383
+ try {
5384
+ parsed = JSON.parse(rawArguments);
5385
+ } catch {
5386
+ return rawArguments;
5387
+ }
5388
+ if (!parsed || typeof parsed !== 'object') {
5389
+ return rawArguments;
5390
+ }
5391
+ const path = Reflect.get(parsed, 'path');
5392
+ if (typeof path === 'string') {
5393
+ return `"${path}"`;
5394
+ }
5395
+ const keys = Object.keys(parsed);
5396
+ if (keys.length === 1) {
5397
+ const value = Reflect.get(parsed, keys[0]);
5398
+ if (typeof value === 'string') {
5399
+ return `"${value}"`;
5400
+ }
5401
+ }
5402
+ return rawArguments;
5403
+ };
5404
+ const getToolCallsDom = message => {
5405
+ if (message.role !== 'assistant' || !message.toolCalls || message.toolCalls.length === 0) {
5406
+ return [];
5407
+ }
5408
+ return message.toolCalls.flatMap(toolCall => {
5409
+ const argumentPreview = getToolCallArgumentPreview(toolCall.arguments);
5410
+ const label = `${toolCall.name} ${argumentPreview}`;
5411
+ return [{
5412
+ childCount: 1,
5413
+ className: Markdown,
5414
+ type: P
5415
+ }, text(label)];
5416
+ });
5417
+ };
4647
5418
  const getOpenRouterRequestFailedDom = () => {
4648
5419
  return [{
4649
5420
  childCount: openRouterRequestFailureReasons.length,
@@ -4676,7 +5447,10 @@ const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput =
4676
5447
  const isOpenRouterApiKeyMissingMessage = message.role === 'assistant' && message.text === openRouterApiKeyRequiredMessage;
4677
5448
  const isOpenRouterRequestFailedMessage = message.role === 'assistant' && message.text === openRouterRequestFailedMessage;
4678
5449
  const isOpenRouterTooManyRequestsMessage = message.role === 'assistant' && message.text.startsWith(openRouterTooManyRequestsMessage);
4679
- const extraChildCount = isOpenApiApiKeyMissingMessage || isOpenRouterApiKeyMissingMessage || isOpenRouterRequestFailedMessage || isOpenRouterTooManyRequestsMessage ? 2 : 1;
5450
+ const messageIntermediate = parseMessageContent(message.text);
5451
+ const messageDom = getMessageContentDom(messageIntermediate);
5452
+ const toolCallsDom = getToolCallsDom(message);
5453
+ const extraChildCount = isOpenApiApiKeyMissingMessage || isOpenRouterApiKeyMissingMessage || isOpenRouterRequestFailedMessage || isOpenRouterTooManyRequestsMessage ? messageIntermediate.length + 1 + toolCallsDom.length : messageIntermediate.length + toolCallsDom.length;
4680
5454
  return [{
4681
5455
  childCount: 1,
4682
5456
  className: mergeClassNames(Message, roleClassName),
@@ -4685,11 +5459,7 @@ const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput =
4685
5459
  childCount: extraChildCount,
4686
5460
  className: ChatMessageContent,
4687
5461
  type: Div
4688
- }, {
4689
- childCount: 1,
4690
- className: Markdown,
4691
- type: P
4692
- }, text(message.text), ...(isOpenApiApiKeyMissingMessage ? getMissingOpenApiApiKeyDom(openApiApiKeyInput) : []), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput, openRouterApiKeyState) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : []), ...(isOpenRouterTooManyRequestsMessage ? getOpenRouterTooManyRequestsDom() : [])];
5462
+ }, ...messageDom, ...toolCallsDom, ...(isOpenApiApiKeyMissingMessage ? getMissingOpenApiApiKeyDom(openApiApiKeyInput) : []), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput, openRouterApiKeyState) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : []), ...(isOpenRouterTooManyRequestsMessage ? getOpenRouterTooManyRequestsDom() : [])];
4693
5463
  };
4694
5464
 
4695
5465
  const getEmptyMessagesDom = () => {
@@ -4933,6 +5703,9 @@ const renderEventListeners = () => {
4933
5703
  }, {
4934
5704
  name: HandleClickNew,
4935
5705
  params: ['handleClickNew']
5706
+ }, {
5707
+ name: HandleClickSessionDebug,
5708
+ params: ['handleClickSessionDebug']
4936
5709
  }, {
4937
5710
  name: HandleClickBack,
4938
5711
  params: ['handleClickBack']
@@ -5080,6 +5853,7 @@ const commandMap = {
5080
5853
  'Chat.handleClickDelete': wrapCommand(handleClickDelete),
5081
5854
  'Chat.handleClickList': wrapCommand(handleClickList),
5082
5855
  'Chat.handleClickNew': wrapCommand(handleClickNew),
5856
+ 'Chat.handleClickSessionDebug': wrapCommand(handleClickSessionDebug),
5083
5857
  'Chat.handleClickSettings': handleClickSettings,
5084
5858
  'Chat.handleInput': wrapCommand(handleInput),
5085
5859
  'Chat.handleInputFocus': wrapCommand(handleInputFocus),
@@ -5107,12 +5881,24 @@ const commandMap = {
5107
5881
  'Chat.useMockApi': wrapCommand(useMockApi)
5108
5882
  };
5109
5883
 
5884
+ const send = port => {
5885
+ return invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToChatNetworkWorker', port, 'HandleMessagePort.handleMessagePort');
5886
+ };
5887
+ const initializeChatNetworkWorker = async () => {
5888
+ const rpc = await create$4({
5889
+ commandMap: {},
5890
+ send
5891
+ });
5892
+ set$3(rpc);
5893
+ };
5894
+
5110
5895
  const listen = async () => {
5111
5896
  registerCommands(commandMap);
5112
5897
  const rpc = await create$3({
5113
5898
  commandMap: commandMap
5114
5899
  });
5115
5900
  set$1(rpc);
5901
+ await initializeChatNetworkWorker();
5116
5902
  };
5117
5903
 
5118
5904
  const main = async () => {