Skip to content

Commit b1c015f

Browse files
committed
feat: add global modAI button with IndexedDB as persist layer
1 parent 766b794 commit b1c015f

File tree

13 files changed

+663
-79
lines changed

13 files changed

+663
-79
lines changed

_build/js/src/chatHistory.ts

Lines changed: 159 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
import {
2+
saveMessage,
3+
deleteAllMessages,
4+
deleteMessagesAfter,
5+
getMessages,
6+
updateMessage,
7+
} from './db';
8+
19
import type { ToolResponseContent, ToolCalls, ServiceResponse, Metadata } from './executor/types';
210

311
export type AssistantMessageContentType = 'text' | 'image';
@@ -18,13 +26,14 @@ export type UserAttachment = {
1826
value: string;
1927
};
2028

21-
type BaseMessage = {
29+
export type BaseMessage = {
2230
id: string;
2331
hidden: boolean;
2432
ctx: Record<string, unknown>;
2533
toolCalls?: undefined;
2634
contexts?: undefined;
2735
attachments?: undefined;
36+
init?: boolean | undefined;
2837
};
2938

3039
export type ToolResponseMessage = BaseMessage & {
@@ -58,6 +67,7 @@ export type Message = UserMessage | AssistantMessage | ToolResponseMessage;
5867
type Namespace = {
5968
history: Message[];
6069
idRef: Record<string, Message>;
70+
persist: boolean;
6171
onAddMessage: <M extends Message = Message>(msg: M) => UpdatableHTMLElement<M> | undefined;
6272
};
6373

@@ -95,6 +105,10 @@ const addUserMessage = (
95105
}
96106

97107
msgObject.el = namespace.onAddMessage(msgObject);
108+
109+
if (namespace.persist) {
110+
void saveMessage(key, msgObject);
111+
}
98112
};
99113

100114
const addToolResponseMessage = (
@@ -123,6 +137,10 @@ const addToolResponseMessage = (
123137
}
124138

125139
msgObject.el = namespace.onAddMessage(msgObject);
140+
141+
if (namespace.persist) {
142+
void saveMessage(key, msgObject);
143+
}
126144
};
127145

128146
const addAssistantMessage = (key: string, data: ServiceResponse, hidden: boolean = false) => {
@@ -156,6 +174,10 @@ const addAssistantMessage = (key: string, data: ServiceResponse, hidden: boolean
156174
}
157175

158176
msgObject.el = namespace.onAddMessage(msgObject);
177+
178+
if (namespace.persist) {
179+
void saveMessage(key, msgObject);
180+
}
159181
};
160182

161183
const updateAssistantMessage = (key: string, data: ServiceResponse) => {
@@ -180,6 +202,10 @@ const updateAssistantMessage = (key: string, data: ServiceResponse) => {
180202
if (msg.__type === 'AssistantMessage' && msg.el && msg.el.update) {
181203
msg.el.update(msg);
182204
}
205+
206+
if (namespace.persist) {
207+
void updateMessage(msg);
208+
}
183209
};
184210

185211
const getMessage = (key: string, id: string) => {
@@ -191,6 +217,91 @@ const getMessage = (key: string, id: string) => {
191217
return namespace.idRef[id];
192218
};
193219

220+
const loadFromDB = async (key: string, onInitDone?: () => void) => {
221+
const messages = await getMessages(key);
222+
for (const message of messages) {
223+
const namespace = _namespace[message.category];
224+
if (!namespace) {
225+
continue;
226+
}
227+
228+
if (message.__type === 'UserMessage') {
229+
const msgObject: UserMessage = {
230+
init: true,
231+
__type: 'UserMessage',
232+
content: message.content as string,
233+
contexts: message.contexts,
234+
attachments: message.attachments,
235+
role: 'user',
236+
id: message.id,
237+
hidden: message.hidden,
238+
ctx: {},
239+
};
240+
241+
const index = namespace.history.push(msgObject) - 1;
242+
namespace.idRef[message.id] = namespace.history[index];
243+
244+
msgObject.el = namespace.onAddMessage(msgObject);
245+
246+
continue;
247+
}
248+
249+
if (message.__type === 'AssistantMessage') {
250+
const msgObject: AssistantMessage = {
251+
init: true,
252+
__type: 'AssistantMessage',
253+
content: undefined,
254+
toolCalls: undefined,
255+
contentType: 'text',
256+
role: 'assistant',
257+
id: message.id,
258+
metadata: message.metadata,
259+
hidden: message.hidden,
260+
ctx: message.ctx,
261+
attachments: message.attachments,
262+
contexts: message.contexts,
263+
};
264+
265+
if (message.contentType === 'image') {
266+
msgObject.content = message.content;
267+
msgObject.contentType = 'image';
268+
} else {
269+
msgObject.content = message.content;
270+
msgObject.toolCalls = message.toolCalls;
271+
}
272+
273+
const index = namespace.history.push(msgObject) - 1;
274+
namespace.idRef[message.id] = namespace.history[index];
275+
276+
msgObject.el = namespace.onAddMessage(msgObject);
277+
continue;
278+
}
279+
280+
if (message.__type === 'ToolResponseMessage') {
281+
const msgObject: ToolResponseMessage = {
282+
init: true,
283+
__type: 'ToolResponseMessage',
284+
content: message.content,
285+
role: 'tool',
286+
id: message.id,
287+
hidden: message.hidden,
288+
ctx: {},
289+
};
290+
291+
const index = namespace.history.push(msgObject) - 1;
292+
if (message.id) {
293+
namespace.idRef[message.id] = namespace.history[index];
294+
}
295+
296+
msgObject.el = namespace.onAddMessage(msgObject);
297+
}
298+
}
299+
300+
if (onInitDone) {
301+
onInitDone();
302+
}
303+
};
304+
194305
export type ChatHistory = {
195306
addUserMessage: (
196307
content:
@@ -206,6 +317,7 @@ export type ChatHistory = {
206317
hidden?: boolean,
207318
) => void;
208319
updateAssistantMessage: (data: ServiceResponse) => void;
320+
syncMessage: (id: string) => void;
209321
getAssistantMessage: (id: string) => Message | undefined;
210322
getMessages: () => Message[];
211323
getMessagesHistory: () => Pick<
@@ -217,31 +329,41 @@ export type ChatHistory = {
217329
};
218330

219331
export const chatHistory = {
220-
init: (key: string, onAddMessage: Namespace['onAddMessage']): ChatHistory => {
221-
if (!_namespace[key]) {
222-
_namespace[key] = {
332+
init: (config: {
333+
key: string;
334+
persist?: boolean;
335+
onAddMessage: Namespace['onAddMessage'];
336+
onInitDone?: () => void;
337+
}): ChatHistory => {
338+
if (!_namespace[config.key]) {
339+
_namespace[config.key] = {
223340
history: [],
224341
idRef: {},
225-
onAddMessage,
342+
persist: config.persist ?? false,
343+
onAddMessage: config.onAddMessage,
226344
};
345+
346+
if (config.persist) {
347+
void loadFromDB(config.key, config.onInitDone);
348+
}
227349
}
228350

229-
if (onAddMessage) {
230-
_namespace[key].onAddMessage = onAddMessage;
351+
if (config.onAddMessage) {
352+
_namespace[config.key].onAddMessage = config.onAddMessage;
231353
}
232354

233355
return {
234356
addUserMessage: (content, hidden = false) => {
235357
const id = 'user-msg-' + Date.now() + Math.round(Math.random() * 1000);
236-
addUserMessage(key, id, content, hidden);
358+
addUserMessage(config.key, id, content, hidden);
237359
},
238360
addAssistantMessage: (data, hidden = false) => {
239-
addAssistantMessage(key, data, hidden);
361+
addAssistantMessage(config.key, data, hidden);
240362
},
241363

242364
addToolCallsMessage: (toolCalls, hidden = false) => {
243365
addAssistantMessage(
244-
key,
366+
config.key,
245367
{
246368
__type: 'ToolsData',
247369
id: crypto.randomUUID(),
@@ -256,19 +378,31 @@ export const chatHistory = {
256378
);
257379
},
258380
addToolResponseMessage: (id, content, hidden = false) => {
259-
addToolResponseMessage(key, id, content, hidden);
381+
addToolResponseMessage(config.key, id, content, hidden);
260382
},
261383
updateAssistantMessage: (data) => {
262-
updateAssistantMessage(key, data);
384+
updateAssistantMessage(config.key, data);
385+
},
386+
syncMessage: (id) => {
387+
const namespace = _namespace[config.key];
388+
if (!namespace) {
389+
return;
390+
}
391+
392+
if (!namespace.idRef[id]) {
393+
return;
394+
}
395+
396+
void updateMessage(namespace.idRef[id]);
263397
},
264398
getAssistantMessage: (id) => {
265-
return getMessage(key, id);
399+
return getMessage(config.key, id);
266400
},
267401
getMessages: () => {
268-
return _namespace[key].history;
402+
return _namespace[config.key].history;
269403
},
270404
getMessagesHistory: () => {
271-
return _namespace[key].history.map((m) => ({
405+
return _namespace[config.key].history.map((m) => ({
272406
role: m.role,
273407
content: m.content,
274408
toolCalls: m.toolCalls,
@@ -277,22 +411,26 @@ export const chatHistory = {
277411
}));
278412
},
279413
clearHistory: () => {
280-
_namespace[key].history.forEach((msg) => {
414+
_namespace[config.key].history.forEach((msg) => {
281415
msg.el?.remove();
282416
});
283-
_namespace[key].history = [];
417+
_namespace[config.key].history = [];
418+
419+
void deleteAllMessages(config.key);
284420
},
285421
clearHistoryFrom: (id: string) => {
286-
const startIndex = _namespace[key].history.findIndex((obj) => obj.id === id);
422+
const startIndex = _namespace[config.key].history.findIndex((obj) => obj.id === id);
287423

288424
if (startIndex !== -1) {
289-
for (let i = startIndex; i < _namespace[key].history.length; i++) {
290-
const obj = _namespace[key].history[i];
425+
for (let i = startIndex; i < _namespace[config.key].history.length; i++) {
426+
const obj = _namespace[config.key].history[i];
291427
obj.el?.remove();
292428
}
293429

294-
_namespace[key].history.splice(startIndex);
430+
_namespace[config.key].history.splice(startIndex);
295431
}
432+
433+
void deleteMessagesAfter(config.key, id);
296434
},
297435
};
298436
},

0 commit comments

Comments
 (0)