Valeriy Selitskiy
Release v1.2.1 adaptive chat workspace
6c62075
Raw
History Blame Contribute Delete
5.35 kB
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();
}