| | import Dexie, { type EntityTable } from 'dexie';
|
| | import { findDescendantMessages } from '$lib/utils';
|
| |
|
| | class LlamacppDatabase extends Dexie {
|
| | conversations!: EntityTable<DatabaseConversation, string>;
|
| | messages!: EntityTable<DatabaseMessage, string>;
|
| |
|
| | constructor() {
|
| | super('LlamacppWebui');
|
| |
|
| | this.version(1).stores({
|
| | conversations: 'id, lastModified, currNode, name',
|
| | messages: 'id, convId, type, role, timestamp, parent, children'
|
| | });
|
| | }
|
| | }
|
| |
|
| | const db = new LlamacppDatabase();
|
| | import { v4 as uuid } from 'uuid';
|
| | import { MessageRole } from '$lib/enums';
|
| |
|
| | export class DatabaseService {
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | static async createConversation(name: string): Promise<DatabaseConversation> {
|
| | const conversation: DatabaseConversation = {
|
| | id: uuid(),
|
| | name,
|
| | lastModified: Date.now(),
|
| | currNode: ''
|
| | };
|
| |
|
| | await db.conversations.add(conversation);
|
| | return conversation;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | static async createMessageBranch(
|
| | message: Omit<DatabaseMessage, 'id'>,
|
| | parentId: string | null
|
| | ): Promise<DatabaseMessage> {
|
| | return await db.transaction('rw', [db.conversations, db.messages], async () => {
|
| |
|
| | if (parentId !== null) {
|
| | const parentMessage = await db.messages.get(parentId);
|
| | if (!parentMessage) {
|
| | throw new Error(`Parent message ${parentId} not found`);
|
| | }
|
| | }
|
| |
|
| | const newMessage: DatabaseMessage = {
|
| | ...message,
|
| | id: uuid(),
|
| | parent: parentId,
|
| | toolCalls: message.toolCalls ?? '',
|
| | children: []
|
| | };
|
| |
|
| | await db.messages.add(newMessage);
|
| |
|
| |
|
| | if (parentId !== null) {
|
| | const parentMessage = await db.messages.get(parentId);
|
| | if (parentMessage) {
|
| | await db.messages.update(parentId, {
|
| | children: [...parentMessage.children, newMessage.id]
|
| | });
|
| | }
|
| | }
|
| |
|
| | await this.updateConversation(message.convId, {
|
| | currNode: newMessage.id
|
| | });
|
| |
|
| | return newMessage;
|
| | });
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | static async createRootMessage(convId: string): Promise<string> {
|
| | const rootMessage: DatabaseMessage = {
|
| | id: uuid(),
|
| | convId,
|
| | type: 'root',
|
| | timestamp: Date.now(),
|
| | role: MessageRole.SYSTEM,
|
| | content: '',
|
| | parent: null,
|
| | toolCalls: '',
|
| | children: []
|
| | };
|
| |
|
| | await db.messages.add(rootMessage);
|
| | return rootMessage.id;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | static async createSystemMessage(
|
| | convId: string,
|
| | systemPrompt: string,
|
| | parentId: string
|
| | ): Promise<DatabaseMessage> {
|
| | const trimmedPrompt = systemPrompt.trim();
|
| | if (!trimmedPrompt) {
|
| | throw new Error('Cannot create system message with empty content');
|
| | }
|
| |
|
| | const systemMessage: DatabaseMessage = {
|
| | id: uuid(),
|
| | convId,
|
| | type: MessageRole.SYSTEM,
|
| | timestamp: Date.now(),
|
| | role: MessageRole.SYSTEM,
|
| | content: trimmedPrompt,
|
| | parent: parentId,
|
| | children: []
|
| | };
|
| |
|
| | await db.messages.add(systemMessage);
|
| |
|
| | const parentMessage = await db.messages.get(parentId);
|
| | if (parentMessage) {
|
| | await db.messages.update(parentId, {
|
| | children: [...parentMessage.children, systemMessage.id]
|
| | });
|
| | }
|
| |
|
| | return systemMessage;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| | static async deleteConversation(id: string): Promise<void> {
|
| | await db.transaction('rw', [db.conversations, db.messages], async () => {
|
| | await db.conversations.delete(id);
|
| | await db.messages.where('convId').equals(id).delete();
|
| | });
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| | static async deleteMessage(messageId: string): Promise<void> {
|
| | await db.transaction('rw', db.messages, async () => {
|
| | const message = await db.messages.get(messageId);
|
| | if (!message) return;
|
| |
|
| |
|
| | if (message.parent) {
|
| | const parent = await db.messages.get(message.parent);
|
| | if (parent) {
|
| | parent.children = parent.children.filter((childId: string) => childId !== messageId);
|
| | await db.messages.put(parent);
|
| | }
|
| | }
|
| |
|
| |
|
| | await db.messages.delete(messageId);
|
| | });
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | static async deleteMessageCascading(
|
| | conversationId: string,
|
| | messageId: string
|
| | ): Promise<string[]> {
|
| | return await db.transaction('rw', db.messages, async () => {
|
| |
|
| | const allMessages = await db.messages.where('convId').equals(conversationId).toArray();
|
| |
|
| |
|
| | const descendants = findDescendantMessages(allMessages, messageId);
|
| | const allToDelete = [messageId, ...descendants];
|
| |
|
| |
|
| | const message = await db.messages.get(messageId);
|
| | if (message && message.parent) {
|
| | const parent = await db.messages.get(message.parent);
|
| | if (parent) {
|
| | parent.children = parent.children.filter((childId: string) => childId !== messageId);
|
| | await db.messages.put(parent);
|
| | }
|
| | }
|
| |
|
| |
|
| | await db.messages.bulkDelete(allToDelete);
|
| |
|
| | return allToDelete;
|
| | });
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| | static async getAllConversations(): Promise<DatabaseConversation[]> {
|
| | return await db.conversations.orderBy('lastModified').reverse().toArray();
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | static async getConversation(id: string): Promise<DatabaseConversation | undefined> {
|
| | return await db.conversations.get(id);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | static async getConversationMessages(convId: string): Promise<DatabaseMessage[]> {
|
| | return await db.messages.where('convId').equals(convId).sortBy('timestamp');
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | static async updateConversation(
|
| | id: string,
|
| | updates: Partial<Omit<DatabaseConversation, 'id'>>
|
| | ): Promise<void> {
|
| | await db.conversations.update(id, {
|
| | ...updates,
|
| | lastModified: Date.now()
|
| | });
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | static async updateCurrentNode(convId: string, nodeId: string): Promise<void> {
|
| | await this.updateConversation(convId, {
|
| | currNode: nodeId
|
| | });
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | static async updateMessage(
|
| | id: string,
|
| | updates: Partial<Omit<DatabaseMessage, 'id'>>
|
| | ): Promise<void> {
|
| | await db.messages.update(id, updates);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | static async importConversations(
|
| | data: { conv: DatabaseConversation; messages: DatabaseMessage[] }[]
|
| | ): Promise<{ imported: number; skipped: number }> {
|
| | let importedCount = 0;
|
| | let skippedCount = 0;
|
| |
|
| | return await db.transaction('rw', [db.conversations, db.messages], async () => {
|
| | for (const item of data) {
|
| | const { conv, messages } = item;
|
| |
|
| | const existing = await db.conversations.get(conv.id);
|
| | if (existing) {
|
| | console.warn(`Conversation "${conv.name}" already exists, skipping...`);
|
| | skippedCount++;
|
| | continue;
|
| | }
|
| |
|
| | await db.conversations.add(conv);
|
| | for (const msg of messages) {
|
| | await db.messages.put(msg);
|
| | }
|
| |
|
| | importedCount++;
|
| | }
|
| |
|
| | return { imported: importedCount, skipped: skippedCount };
|
| | });
|
| | }
|
| | }
|
| |
|