@lvce-editor/chat-storage-worker 1.0.0 → 1.2.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.
@@ -888,6 +888,631 @@ const create = async ({
888
888
  return rpc;
889
889
  };
890
890
 
891
+ const toError = error => {
892
+ if (error instanceof Error) {
893
+ return error;
894
+ }
895
+ return new Error('IndexedDB request failed');
896
+ };
897
+
898
+ const requestToPromise = async createRequest => {
899
+ const request = createRequest();
900
+ const {
901
+ promise,
902
+ reject,
903
+ resolve
904
+ } = Promise.withResolvers();
905
+ request.addEventListener('success', () => {
906
+ resolve(request.result);
907
+ });
908
+ request.addEventListener('error', () => {
909
+ reject(toError(request.error));
910
+ });
911
+ return promise;
912
+ };
913
+
914
+ const transactionToPromise = async createTransaction => {
915
+ const transaction = createTransaction();
916
+ const {
917
+ promise,
918
+ reject,
919
+ resolve
920
+ } = Promise.withResolvers();
921
+ transaction.addEventListener('complete', () => {
922
+ resolve();
923
+ });
924
+ transaction.addEventListener('error', () => {
925
+ reject(toError(transaction.error));
926
+ });
927
+ transaction.addEventListener('abort', () => {
928
+ reject(toError(transaction.error));
929
+ });
930
+ return promise;
931
+ };
932
+
933
+ const toChatViewEvent = event => {
934
+ const {
935
+ eventId,
936
+ ...chatViewEvent
937
+ } = event;
938
+ return chatViewEvent;
939
+ };
940
+ const now$1 = () => {
941
+ return new Date().toISOString();
942
+ };
943
+ const isSameMessage$1 = (a, b) => {
944
+ 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 || []);
945
+ };
946
+ const canAppendMessages$1 = (previousMessages, nextMessages) => {
947
+ if (nextMessages.length < previousMessages.length) {
948
+ return false;
949
+ }
950
+ return previousMessages.every((message, index) => isSameMessage$1(message, nextMessages[index]));
951
+ };
952
+ const canUpdateMessages$1 = (previousMessages, nextMessages) => {
953
+ if (previousMessages.length !== nextMessages.length) {
954
+ return false;
955
+ }
956
+ for (let i = 0; i < previousMessages.length; i += 1) {
957
+ const previous = previousMessages[i];
958
+ const next = nextMessages[i];
959
+ if (previous.id !== next.id || previous.role !== next.role) {
960
+ return false;
961
+ }
962
+ }
963
+ return true;
964
+ };
965
+ const getMutationEvents$1 = (previous, next) => {
966
+ const timestamp = now$1();
967
+ const events = [];
968
+ if (!previous) {
969
+ events.push({
970
+ sessionId: next.id,
971
+ timestamp,
972
+ title: next.title,
973
+ type: 'chat-session-created'
974
+ });
975
+ for (const message of next.messages) {
976
+ events.push({
977
+ message,
978
+ sessionId: next.id,
979
+ timestamp,
980
+ type: 'chat-message-added'
981
+ });
982
+ }
983
+ return events;
984
+ }
985
+ if (previous.title !== next.title) {
986
+ events.push({
987
+ sessionId: next.id,
988
+ timestamp,
989
+ title: next.title,
990
+ type: 'chat-session-title-updated'
991
+ });
992
+ }
993
+ if (canAppendMessages$1(previous.messages, next.messages)) {
994
+ for (let i = previous.messages.length; i < next.messages.length; i += 1) {
995
+ events.push({
996
+ message: next.messages[i],
997
+ sessionId: next.id,
998
+ timestamp,
999
+ type: 'chat-message-added'
1000
+ });
1001
+ }
1002
+ return events;
1003
+ }
1004
+ if (canUpdateMessages$1(previous.messages, next.messages)) {
1005
+ for (let i = 0; i < previous.messages.length; i += 1) {
1006
+ const previousMessage = previous.messages[i];
1007
+ const nextMessage = next.messages[i];
1008
+ if (!isSameMessage$1(previousMessage, nextMessage)) {
1009
+ events.push({
1010
+ inProgress: nextMessage.inProgress,
1011
+ messageId: nextMessage.id,
1012
+ sessionId: next.id,
1013
+ text: nextMessage.text,
1014
+ time: nextMessage.time,
1015
+ timestamp,
1016
+ toolCalls: nextMessage.toolCalls,
1017
+ type: 'chat-message-updated'
1018
+ });
1019
+ }
1020
+ }
1021
+ return events;
1022
+ }
1023
+ events.push({
1024
+ messages: [...next.messages],
1025
+ sessionId: next.id,
1026
+ timestamp,
1027
+ type: 'chat-session-messages-replaced'
1028
+ });
1029
+ return events;
1030
+ };
1031
+ const replaySession$1 = (id, summary, events) => {
1032
+ let deleted = false;
1033
+ let title = summary?.title || '';
1034
+ let messages = summary?.messages ? [...summary.messages] : [];
1035
+ for (const event of events) {
1036
+ if (event.sessionId !== id) {
1037
+ continue;
1038
+ }
1039
+ if (event.type === 'chat-session-created') {
1040
+ const {
1041
+ title: eventTitle
1042
+ } = event;
1043
+ deleted = false;
1044
+ title = eventTitle;
1045
+ continue;
1046
+ }
1047
+ if (event.type === 'chat-session-deleted') {
1048
+ deleted = true;
1049
+ continue;
1050
+ }
1051
+ if (event.type === 'chat-session-title-updated') {
1052
+ const {
1053
+ title: eventTitle
1054
+ } = event;
1055
+ title = eventTitle;
1056
+ continue;
1057
+ }
1058
+ if (event.type === 'chat-message-added') {
1059
+ messages = [...messages, event.message];
1060
+ continue;
1061
+ }
1062
+ if (event.type === 'chat-message-updated') {
1063
+ messages = messages.map(message => {
1064
+ if (message.id !== event.messageId) {
1065
+ return message;
1066
+ }
1067
+ return {
1068
+ ...message,
1069
+ ...(event.inProgress === undefined ? {} : {
1070
+ inProgress: event.inProgress
1071
+ }),
1072
+ text: event.text,
1073
+ time: event.time,
1074
+ ...(event.toolCalls === undefined ? {} : {
1075
+ toolCalls: event.toolCalls
1076
+ })
1077
+ };
1078
+ });
1079
+ continue;
1080
+ }
1081
+ if (event.type === 'chat-session-messages-replaced') {
1082
+ messages = [...event.messages];
1083
+ }
1084
+ }
1085
+ if (deleted || !title) {
1086
+ return undefined;
1087
+ }
1088
+ return {
1089
+ id,
1090
+ messages,
1091
+ title
1092
+ };
1093
+ };
1094
+ class IndexedDbChatSessionStorage {
1095
+ constructor(options = {}) {
1096
+ this.state = {
1097
+ databaseName: options.databaseName || 'lvce-chat-view-sessions',
1098
+ databasePromise: undefined,
1099
+ databaseVersion: options.databaseVersion || 2,
1100
+ eventStoreName: options.eventStoreName || 'chat-view-events',
1101
+ storeName: options.storeName || 'chat-sessions'
1102
+ };
1103
+ }
1104
+ openDatabase = async () => {
1105
+ if (this.state.databasePromise) {
1106
+ return this.state.databasePromise;
1107
+ }
1108
+ const request = indexedDB.open(this.state.databaseName, this.state.databaseVersion);
1109
+ request.addEventListener('upgradeneeded', () => {
1110
+ const database = request.result;
1111
+ if (!database.objectStoreNames.contains(this.state.storeName)) {
1112
+ database.createObjectStore(this.state.storeName, {
1113
+ keyPath: 'id'
1114
+ });
1115
+ }
1116
+ if (database.objectStoreNames.contains(this.state.eventStoreName)) {
1117
+ const {
1118
+ transaction
1119
+ } = request;
1120
+ if (!transaction) {
1121
+ return;
1122
+ }
1123
+ const eventStore = transaction.objectStore(this.state.eventStoreName);
1124
+ if (!eventStore.indexNames.contains('sessionId')) {
1125
+ eventStore.createIndex('sessionId', 'sessionId', {
1126
+ unique: false
1127
+ });
1128
+ }
1129
+ } else {
1130
+ const eventStore = database.createObjectStore(this.state.eventStoreName, {
1131
+ autoIncrement: true,
1132
+ keyPath: 'eventId'
1133
+ });
1134
+ eventStore.createIndex('sessionId', 'sessionId', {
1135
+ unique: false
1136
+ });
1137
+ }
1138
+ });
1139
+ const databasePromise = requestToPromise(() => request);
1140
+ this.state.databasePromise = databasePromise;
1141
+ return databasePromise;
1142
+ };
1143
+ listSummaries = async () => {
1144
+ const database = await this.openDatabase();
1145
+ const transaction = database.transaction(this.state.storeName, 'readonly');
1146
+ const store = transaction.objectStore(this.state.storeName);
1147
+ const summaries = await requestToPromise(() => store.getAll());
1148
+ return summaries;
1149
+ };
1150
+ getSummary = async id => {
1151
+ const database = await this.openDatabase();
1152
+ const transaction = database.transaction(this.state.storeName, 'readonly');
1153
+ const store = transaction.objectStore(this.state.storeName);
1154
+ const summary = await requestToPromise(() => store.get(id));
1155
+ return summary;
1156
+ };
1157
+ getEventsBySessionId = async sessionId => {
1158
+ const database = await this.openDatabase();
1159
+ const transaction = database.transaction(this.state.eventStoreName, 'readonly');
1160
+ const store = transaction.objectStore(this.state.eventStoreName);
1161
+ const index = store.index('sessionId');
1162
+ const events = await requestToPromise(() => index.getAll(IDBKeyRange.only(sessionId)));
1163
+ return events.map(toChatViewEvent);
1164
+ };
1165
+ listEventsInternal = async () => {
1166
+ const database = await this.openDatabase();
1167
+ const transaction = database.transaction(this.state.eventStoreName, 'readonly');
1168
+ const store = transaction.objectStore(this.state.eventStoreName);
1169
+ const events = await requestToPromise(() => store.getAll());
1170
+ return events.map(toChatViewEvent);
1171
+ };
1172
+ appendEvents = async events => {
1173
+ if (events.length === 0) {
1174
+ return;
1175
+ }
1176
+ const database = await this.openDatabase();
1177
+ const transaction = database.transaction([this.state.storeName, this.state.eventStoreName], 'readwrite');
1178
+ const summaryStore = transaction.objectStore(this.state.storeName);
1179
+ const eventStore = transaction.objectStore(this.state.eventStoreName);
1180
+ for (const event of events) {
1181
+ eventStore.add(event);
1182
+ if (event.type === 'chat-session-created' || event.type === 'chat-session-title-updated') {
1183
+ summaryStore.put({
1184
+ id: event.sessionId,
1185
+ title: event.title
1186
+ });
1187
+ }
1188
+ if (event.type === 'chat-session-deleted') {
1189
+ summaryStore.delete(event.sessionId);
1190
+ }
1191
+ }
1192
+ await transactionToPromise(() => transaction);
1193
+ };
1194
+ async appendEvent(event) {
1195
+ await this.appendEvents([event]);
1196
+ }
1197
+ async clear() {
1198
+ const database = await this.openDatabase();
1199
+ const transaction = database.transaction([this.state.storeName, this.state.eventStoreName], 'readwrite');
1200
+ transaction.objectStore(this.state.storeName).clear();
1201
+ transaction.objectStore(this.state.eventStoreName).clear();
1202
+ await transactionToPromise(() => transaction);
1203
+ }
1204
+ async deleteSession(id) {
1205
+ await this.appendEvent({
1206
+ sessionId: id,
1207
+ timestamp: now$1(),
1208
+ type: 'chat-session-deleted'
1209
+ });
1210
+ }
1211
+ async getEvents(sessionId) {
1212
+ if (sessionId) {
1213
+ return this.getEventsBySessionId(sessionId);
1214
+ }
1215
+ return this.listEventsInternal();
1216
+ }
1217
+ async getSession(id) {
1218
+ const [summary, events] = await Promise.all([this.getSummary(id), this.getEventsBySessionId(id)]);
1219
+ return replaySession$1(id, summary, events);
1220
+ }
1221
+ async listSessions() {
1222
+ const summaries = await this.listSummaries();
1223
+ const sessions = [];
1224
+ for (const summary of summaries) {
1225
+ const events = await this.getEventsBySessionId(summary.id);
1226
+ const session = replaySession$1(summary.id, summary, events);
1227
+ if (!session) {
1228
+ continue;
1229
+ }
1230
+ sessions.push(session);
1231
+ }
1232
+ return sessions;
1233
+ }
1234
+ async setSession(session) {
1235
+ const previous = await this.getSession(session.id);
1236
+ const events = getMutationEvents$1(previous, session);
1237
+ await this.appendEvents(events);
1238
+ if (events.length === 0) {
1239
+ const database = await this.openDatabase();
1240
+ const transaction = database.transaction(this.state.storeName, 'readwrite');
1241
+ const summaryStore = transaction.objectStore(this.state.storeName);
1242
+ summaryStore.put({
1243
+ id: session.id,
1244
+ title: session.title
1245
+ });
1246
+ await transactionToPromise(() => transaction);
1247
+ }
1248
+ }
1249
+ }
1250
+
1251
+ const now = () => {
1252
+ return new Date().toISOString();
1253
+ };
1254
+ const isSameMessage = (a, b) => {
1255
+ 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 || []);
1256
+ };
1257
+ const canAppendMessages = (previousMessages, nextMessages) => {
1258
+ if (nextMessages.length < previousMessages.length) {
1259
+ return false;
1260
+ }
1261
+ return previousMessages.every((message, index) => isSameMessage(message, nextMessages[index]));
1262
+ };
1263
+ const canUpdateMessages = (previousMessages, nextMessages) => {
1264
+ if (previousMessages.length !== nextMessages.length) {
1265
+ return false;
1266
+ }
1267
+ for (let i = 0; i < previousMessages.length; i += 1) {
1268
+ const previous = previousMessages[i];
1269
+ const next = nextMessages[i];
1270
+ if (previous.id !== next.id || previous.role !== next.role) {
1271
+ return false;
1272
+ }
1273
+ }
1274
+ return true;
1275
+ };
1276
+ const getMutationEvents = (previous, next) => {
1277
+ const timestamp = now();
1278
+ const events = [];
1279
+ if (!previous) {
1280
+ events.push({
1281
+ sessionId: next.id,
1282
+ timestamp,
1283
+ title: next.title,
1284
+ type: 'chat-session-created'
1285
+ });
1286
+ for (const message of next.messages) {
1287
+ events.push({
1288
+ message,
1289
+ sessionId: next.id,
1290
+ timestamp,
1291
+ type: 'chat-message-added'
1292
+ });
1293
+ }
1294
+ return events;
1295
+ }
1296
+ if (previous.title !== next.title) {
1297
+ events.push({
1298
+ sessionId: next.id,
1299
+ timestamp,
1300
+ title: next.title,
1301
+ type: 'chat-session-title-updated'
1302
+ });
1303
+ }
1304
+ if (canAppendMessages(previous.messages, next.messages)) {
1305
+ for (let i = previous.messages.length; i < next.messages.length; i += 1) {
1306
+ events.push({
1307
+ message: next.messages[i],
1308
+ sessionId: next.id,
1309
+ timestamp,
1310
+ type: 'chat-message-added'
1311
+ });
1312
+ }
1313
+ return events;
1314
+ }
1315
+ if (canUpdateMessages(previous.messages, next.messages)) {
1316
+ for (let i = 0; i < previous.messages.length; i += 1) {
1317
+ const previousMessage = previous.messages[i];
1318
+ const nextMessage = next.messages[i];
1319
+ if (!isSameMessage(previousMessage, nextMessage)) {
1320
+ events.push({
1321
+ inProgress: nextMessage.inProgress,
1322
+ messageId: nextMessage.id,
1323
+ sessionId: next.id,
1324
+ text: nextMessage.text,
1325
+ time: nextMessage.time,
1326
+ timestamp,
1327
+ toolCalls: nextMessage.toolCalls,
1328
+ type: 'chat-message-updated'
1329
+ });
1330
+ }
1331
+ }
1332
+ return events;
1333
+ }
1334
+ events.push({
1335
+ messages: [...next.messages],
1336
+ sessionId: next.id,
1337
+ timestamp,
1338
+ type: 'chat-session-messages-replaced'
1339
+ });
1340
+ return events;
1341
+ };
1342
+ const replaySession = (id, title, events) => {
1343
+ let deleted = false;
1344
+ let currentTitle = title || '';
1345
+ let messages = [];
1346
+ for (const event of events) {
1347
+ if (event.sessionId !== id) {
1348
+ continue;
1349
+ }
1350
+ if (event.type === 'chat-session-created') {
1351
+ deleted = false;
1352
+ currentTitle = event.title;
1353
+ continue;
1354
+ }
1355
+ if (event.type === 'chat-session-deleted') {
1356
+ deleted = true;
1357
+ continue;
1358
+ }
1359
+ if (event.type === 'chat-session-title-updated') {
1360
+ currentTitle = event.title;
1361
+ continue;
1362
+ }
1363
+ if (event.type === 'chat-message-added') {
1364
+ messages = [...messages, event.message];
1365
+ continue;
1366
+ }
1367
+ if (event.type === 'chat-message-updated') {
1368
+ messages = messages.map(message => {
1369
+ if (message.id !== event.messageId) {
1370
+ return message;
1371
+ }
1372
+ return {
1373
+ ...message,
1374
+ ...(event.inProgress === undefined ? {} : {
1375
+ inProgress: event.inProgress
1376
+ }),
1377
+ text: event.text,
1378
+ time: event.time,
1379
+ ...(event.toolCalls === undefined ? {} : {
1380
+ toolCalls: event.toolCalls
1381
+ })
1382
+ };
1383
+ });
1384
+ continue;
1385
+ }
1386
+ if (event.type === 'chat-session-messages-replaced') {
1387
+ messages = [...event.messages];
1388
+ }
1389
+ }
1390
+ if (deleted || !currentTitle) {
1391
+ return undefined;
1392
+ }
1393
+ return {
1394
+ id,
1395
+ messages,
1396
+ title: currentTitle
1397
+ };
1398
+ };
1399
+ class InMemoryChatSessionStorage {
1400
+ events = [];
1401
+ summaries = new Map();
1402
+ async appendEvent(event) {
1403
+ this.events.push(event);
1404
+ if (event.type === 'chat-session-created' || event.type === 'chat-session-title-updated') {
1405
+ this.summaries.set(event.sessionId, event.title);
1406
+ return;
1407
+ }
1408
+ if (event.type === 'chat-session-deleted') {
1409
+ this.summaries.delete(event.sessionId);
1410
+ }
1411
+ }
1412
+ async clear() {
1413
+ this.events.length = 0;
1414
+ this.summaries.clear();
1415
+ }
1416
+ async deleteSession(id) {
1417
+ await this.appendEvent({
1418
+ sessionId: id,
1419
+ timestamp: now(),
1420
+ type: 'chat-session-deleted'
1421
+ });
1422
+ }
1423
+ async getEvents(sessionId) {
1424
+ if (!sessionId) {
1425
+ return [...this.events];
1426
+ }
1427
+ return this.events.filter(event => event.sessionId === sessionId);
1428
+ }
1429
+ async getSession(id) {
1430
+ return replaySession(id, this.summaries.get(id), this.events);
1431
+ }
1432
+ async listSessions() {
1433
+ const ids = new Set();
1434
+ for (const id of this.summaries.keys()) {
1435
+ ids.add(id);
1436
+ }
1437
+ for (const event of this.events) {
1438
+ ids.add(event.sessionId);
1439
+ }
1440
+ const sessions = [];
1441
+ for (const id of ids) {
1442
+ const session = replaySession(id, this.summaries.get(id), this.events);
1443
+ if (!session) {
1444
+ continue;
1445
+ }
1446
+ sessions.push(session);
1447
+ }
1448
+ return sessions;
1449
+ }
1450
+ async setSession(session) {
1451
+ const previous = await this.getSession(session.id);
1452
+ const events = getMutationEvents(previous, session);
1453
+ for (const event of events) {
1454
+ await this.appendEvent(event);
1455
+ }
1456
+ this.summaries.set(session.id, session.title);
1457
+ }
1458
+ }
1459
+
1460
+ const createDefaultStorage = () => {
1461
+ if (typeof indexedDB === 'undefined') {
1462
+ return new InMemoryChatSessionStorage();
1463
+ }
1464
+ return new IndexedDbChatSessionStorage();
1465
+ };
1466
+ let chatSessionStorage = createDefaultStorage();
1467
+ const setSession = async session => {
1468
+ await chatSessionStorage.setSession(session);
1469
+ };
1470
+ const listChatSessions = async () => {
1471
+ const sessions = await chatSessionStorage.listSessions();
1472
+ return sessions.map(session => {
1473
+ const summary = {
1474
+ id: session.id,
1475
+ messages: [],
1476
+ title: session.title
1477
+ };
1478
+ if (!session.projectId) {
1479
+ return summary;
1480
+ }
1481
+ return {
1482
+ ...summary,
1483
+ projectId: session.projectId
1484
+ };
1485
+ });
1486
+ };
1487
+ const getChatSession = async id => {
1488
+ const session = await chatSessionStorage.getSession(id);
1489
+ if (!session) {
1490
+ return undefined;
1491
+ }
1492
+ const resultBase = {
1493
+ id: session.id,
1494
+ messages: [...session.messages],
1495
+ title: session.title
1496
+ };
1497
+ const result = session.projectId ? {
1498
+ ...resultBase,
1499
+ projectId: session.projectId
1500
+ } : resultBase;
1501
+ return result;
1502
+ };
1503
+ const deleteChatSession = async id => {
1504
+ await chatSessionStorage.deleteSession(id);
1505
+ };
1506
+ const clearChatSessions = async () => {
1507
+ await chatSessionStorage.clear();
1508
+ };
1509
+ const appendChatViewEvent = async event => {
1510
+ await chatSessionStorage.appendEvent(event);
1511
+ };
1512
+ const getChatViewEvents = async sessionId => {
1513
+ return chatSessionStorage.getEvents(sessionId);
1514
+ };
1515
+
891
1516
  const handleMessagePort = async port => {
892
1517
  await create$1({
893
1518
  commandMap: commandMap,
@@ -896,6 +1521,13 @@ const handleMessagePort = async port => {
896
1521
  };
897
1522
 
898
1523
  const commandMap = {
1524
+ 'ChatStorage.appendEvent': appendChatViewEvent,
1525
+ 'ChatStorage.clear': clearChatSessions,
1526
+ 'ChatStorage.deleteSession': deleteChatSession,
1527
+ 'ChatStorage.getEvents': getChatViewEvents,
1528
+ 'ChatStorage.getSession': getChatSession,
1529
+ 'ChatStorage.listSessions': listChatSessions,
1530
+ 'ChatStorage.setSession': setSession,
899
1531
  'HandleMessagePort.handleMessagePort': handleMessagePort
900
1532
  };
901
1533
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-storage-worker",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Chat Storage Worker",
5
5
  "repository": {
6
6
  "type": "git",