Spaces:
Running
Running
File size: 5,351 Bytes
0ed8124 6c62075 0ed8124 6c62075 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | const DATABASE_NAME = 'bonsai-webgpu-chat';
const DATABASE_VERSION = 1;
const MEMORY_STORE = 'memory';
const CONVERSATION_STORE = 'conversations';
export interface MemoryRecord {
key: string;
value: string;
updatedAt: number;
}
export interface ConversationRecord<TMessage = unknown> {
id: string;
title: string;
messages: TMessage[];
modelId: string;
systemPrompt?: string;
updatedAt: number;
}
function requestResult<T>(request: IDBRequest<T>): Promise<T> {
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error ?? new Error('IndexedDB request failed'));
});
}
function transactionDone(transaction: IDBTransaction): Promise<void> {
return new Promise((resolve, reject) => {
transaction.oncomplete = () => resolve();
transaction.onerror = () => reject(transaction.error ?? new Error('IndexedDB transaction failed'));
transaction.onabort = () => reject(transaction.error ?? new Error('IndexedDB transaction aborted'));
});
}
async function openDatabase(): Promise<IDBDatabase> {
if (!('indexedDB' in globalThis)) throw new Error('IndexedDB is unavailable in this browser');
const request = indexedDB.open(DATABASE_NAME, DATABASE_VERSION);
request.onupgradeneeded = () => {
const database = request.result;
if (!database.objectStoreNames.contains(MEMORY_STORE)) {
database.createObjectStore(MEMORY_STORE, { keyPath: 'key' });
}
if (!database.objectStoreNames.contains(CONVERSATION_STORE)) {
const store = database.createObjectStore(CONVERSATION_STORE, { keyPath: 'id' });
store.createIndex('updatedAt', 'updatedAt');
}
};
return requestResult(request);
}
function validateIdentifier(value: string, label: string): string {
const normalized = value.trim();
if (!normalized || normalized.length > 256) throw new Error(`${label} must contain 1–256 characters`);
return normalized;
}
export async function putMemory(key: string, value: string): Promise<MemoryRecord> {
const record = { key: validateIdentifier(key, 'memory key'), value: value.slice(0, 64 * 1024), updatedAt: Date.now() };
const database = await openDatabase();
const transaction = database.transaction(MEMORY_STORE, 'readwrite');
transaction.objectStore(MEMORY_STORE).put(record);
await transactionDone(transaction);
database.close();
return record;
}
export async function getMemory(key: string): Promise<MemoryRecord | undefined> {
const database = await openDatabase();
const transaction = database.transaction(MEMORY_STORE, 'readonly');
const result = await requestResult(transaction.objectStore(MEMORY_STORE).get(validateIdentifier(key, 'memory key')));
await transactionDone(transaction);
database.close();
return result as MemoryRecord | undefined;
}
export async function listMemory(): Promise<MemoryRecord[]> {
const database = await openDatabase();
const transaction = database.transaction(MEMORY_STORE, 'readonly');
const result = await requestResult(transaction.objectStore(MEMORY_STORE).getAll()) as MemoryRecord[];
await transactionDone(transaction);
database.close();
return result.sort((left, right) => right.updatedAt - left.updatedAt);
}
export async function deleteMemory(key: string): Promise<void> {
const database = await openDatabase();
const transaction = database.transaction(MEMORY_STORE, 'readwrite');
transaction.objectStore(MEMORY_STORE).delete(validateIdentifier(key, 'memory key'));
await transactionDone(transaction);
database.close();
}
export async function saveConversation<TMessage>(record: ConversationRecord<TMessage>): Promise<void> {
const id = validateIdentifier(record.id, 'conversation id');
const database = await openDatabase();
const transaction = database.transaction(CONVERSATION_STORE, 'readwrite');
transaction.objectStore(CONVERSATION_STORE).put({ ...record, id, title: record.title.slice(0, 256), updatedAt: Date.now() });
await transactionDone(transaction);
database.close();
}
export async function loadConversation<TMessage>(id: string): Promise<ConversationRecord<TMessage> | undefined> {
const database = await openDatabase();
const transaction = database.transaction(CONVERSATION_STORE, 'readonly');
const result = await requestResult(transaction.objectStore(CONVERSATION_STORE).get(validateIdentifier(id, 'conversation id')));
await transactionDone(transaction);
database.close();
return result as ConversationRecord<TMessage> | undefined;
}
export async function listConversations<TMessage>(): Promise<ConversationRecord<TMessage>[]> {
const database = await openDatabase();
const transaction = database.transaction(CONVERSATION_STORE, 'readonly');
const result = await requestResult(transaction.objectStore(CONVERSATION_STORE).getAll()) as ConversationRecord<TMessage>[];
await transactionDone(transaction);
database.close();
return result.sort((left, right) => right.updatedAt - left.updatedAt);
}
export async function deleteConversation(id: string): Promise<void> {
const database = await openDatabase();
const transaction = database.transaction(CONVERSATION_STORE, 'readwrite');
transaction.objectStore(CONVERSATION_STORE).delete(validateIdentifier(id, 'conversation id'));
await transactionDone(transaction);
database.close();
}
|