mirror of
https://github.com/hanzoai/browser.git
synced 2026-10-30 18:46:34 +00:00
256 lines
8.4 KiB
TypeScript
256 lines
8.4 KiB
TypeScript
import { createStorage } from '../base/base';
|
|
import { StorageEnum } from '../base/enums';
|
|
import type {
|
|
ChatSession,
|
|
ChatMessage,
|
|
ChatHistoryStorage,
|
|
Message,
|
|
ChatSessionMetadata,
|
|
ChatAgentStepHistory,
|
|
} from './types';
|
|
|
|
// Key for storing chat session metadata
|
|
const CHAT_SESSIONS_META_KEY = 'chat_sessions_meta';
|
|
|
|
// Create storage for session metadata
|
|
const chatSessionsMetaStorage = createStorage<ChatSessionMetadata[]>(CHAT_SESSIONS_META_KEY, [], {
|
|
storageEnum: StorageEnum.Local,
|
|
liveUpdate: true,
|
|
});
|
|
|
|
// Helper function to get storage key for a specific session's messages
|
|
const getSessionMessagesKey = (sessionId: string) => `chat_messages_${sessionId}`;
|
|
|
|
// Helper function to create storage for a specific session's messages
|
|
const getSessionMessagesStorage = (sessionId: string) => {
|
|
return createStorage<ChatMessage[]>(getSessionMessagesKey(sessionId), [], {
|
|
storageEnum: StorageEnum.Local,
|
|
liveUpdate: true,
|
|
});
|
|
};
|
|
|
|
// Helper function to get storage key for a specific session's agent state history
|
|
const getSessionAgentStepHistoryKey = (sessionId: string) => `chat_agent_step_${sessionId}`;
|
|
|
|
// Helper function to get storage for a specific session's agent state history
|
|
const getSessionAgentStepHistoryStorage = (sessionId: string) => {
|
|
return createStorage<ChatAgentStepHistory>(
|
|
getSessionAgentStepHistoryKey(sessionId),
|
|
{
|
|
task: '',
|
|
history: '',
|
|
timestamp: 0,
|
|
},
|
|
{
|
|
storageEnum: StorageEnum.Local,
|
|
liveUpdate: true,
|
|
},
|
|
);
|
|
};
|
|
|
|
// Helper function to get current timestamp in milliseconds
|
|
const getCurrentTimestamp = (): number => Date.now();
|
|
|
|
/**
|
|
* Creates a chat history storage instance with optimized operations
|
|
*/
|
|
export function createChatHistoryStorage(): ChatHistoryStorage {
|
|
return {
|
|
getAllSessions: async (): Promise<ChatSession[]> => {
|
|
const sessionsMeta = await chatSessionsMetaStorage.get();
|
|
|
|
// For listing purposes, we can return sessions without loading messages
|
|
// This makes the list view very fast
|
|
return sessionsMeta.map(meta => ({
|
|
...meta,
|
|
messages: [], // Empty array as we don't load messages for listing
|
|
}));
|
|
},
|
|
|
|
clearAllSessions: async (): Promise<void> => {
|
|
const sessionsMeta = await chatSessionsMetaStorage.get();
|
|
for (const sessionMeta of sessionsMeta) {
|
|
const messagesStorage = getSessionMessagesStorage(sessionMeta.id);
|
|
await messagesStorage.set([]);
|
|
}
|
|
await chatSessionsMetaStorage.set([]);
|
|
},
|
|
|
|
// Get session metadata without messages (for UI listing)
|
|
getSessionsMetadata: async (): Promise<ChatSessionMetadata[]> => {
|
|
return await chatSessionsMetaStorage.get();
|
|
},
|
|
|
|
getSession: async (sessionId: string): Promise<ChatSession | null> => {
|
|
const sessionsMeta = await chatSessionsMetaStorage.get();
|
|
const sessionMeta = sessionsMeta.find(session => session.id === sessionId);
|
|
|
|
if (!sessionMeta) return null;
|
|
|
|
// Load messages only when a specific session is requested
|
|
const messagesStorage = getSessionMessagesStorage(sessionId);
|
|
const messages = await messagesStorage.get();
|
|
|
|
return {
|
|
...sessionMeta,
|
|
messages,
|
|
};
|
|
},
|
|
|
|
createSession: async (title: string): Promise<ChatSession> => {
|
|
const newSessionId = crypto.randomUUID();
|
|
const currentTime = getCurrentTimestamp();
|
|
const newSessionMeta: ChatSessionMetadata = {
|
|
id: newSessionId,
|
|
title,
|
|
createdAt: currentTime,
|
|
updatedAt: currentTime,
|
|
messageCount: 0,
|
|
};
|
|
|
|
// Create empty messages array for the new session
|
|
const messagesStorage = getSessionMessagesStorage(newSessionId);
|
|
await messagesStorage.set([]);
|
|
|
|
// Add session metadata to the index
|
|
await chatSessionsMetaStorage.set(prevSessions => [...prevSessions, newSessionMeta]);
|
|
|
|
return {
|
|
...newSessionMeta,
|
|
messages: [],
|
|
};
|
|
},
|
|
|
|
updateTitle: async (sessionId: string, title: string): Promise<ChatSessionMetadata> => {
|
|
let updatedSessionMeta: ChatSessionMetadata | undefined;
|
|
|
|
// Update the title and capture the updated session in a single pass
|
|
await chatSessionsMetaStorage.set(prevSessions => {
|
|
return prevSessions.map(session => {
|
|
if (session.id === sessionId) {
|
|
// Create the updated session
|
|
const updated = {
|
|
...session,
|
|
title,
|
|
updatedAt: getCurrentTimestamp(),
|
|
};
|
|
|
|
// Capture it for return value
|
|
updatedSessionMeta = updated;
|
|
|
|
return updated;
|
|
}
|
|
return session;
|
|
});
|
|
});
|
|
|
|
// Check if we found and updated the session
|
|
if (!updatedSessionMeta) {
|
|
throw new Error('Session not found');
|
|
}
|
|
|
|
// Return the already captured metadata
|
|
return updatedSessionMeta;
|
|
},
|
|
|
|
deleteSession: async (sessionId: string): Promise<void> => {
|
|
// Remove session from metadata
|
|
await chatSessionsMetaStorage.set(prevSessions => prevSessions.filter(session => session.id !== sessionId));
|
|
|
|
// Remove the session's messages
|
|
const messagesStorage = getSessionMessagesStorage(sessionId);
|
|
await messagesStorage.set([]);
|
|
},
|
|
|
|
addMessage: async (sessionId: string, message: Message): Promise<ChatMessage> => {
|
|
const newMessage: ChatMessage = {
|
|
...message,
|
|
id: crypto.randomUUID(),
|
|
};
|
|
|
|
// First check if session exists and update metadata in a single operation
|
|
let sessionFound = false;
|
|
|
|
await chatSessionsMetaStorage.set(prevSessions => {
|
|
return prevSessions.map(session => {
|
|
if (session.id === sessionId) {
|
|
sessionFound = true;
|
|
return {
|
|
...session,
|
|
updatedAt: getCurrentTimestamp(),
|
|
messageCount: session.messageCount + 1,
|
|
};
|
|
}
|
|
return session;
|
|
});
|
|
});
|
|
|
|
// Throw error if session wasn't found
|
|
if (!sessionFound) {
|
|
throw new Error(`Session with ID ${sessionId} not found`);
|
|
}
|
|
|
|
// Only add the message if the session exists
|
|
const messagesStorage = getSessionMessagesStorage(sessionId);
|
|
await messagesStorage.set(prevMessages => [...prevMessages, newMessage]);
|
|
|
|
return newMessage;
|
|
},
|
|
|
|
deleteMessage: async (sessionId: string, messageId: string): Promise<void> => {
|
|
// Get the messages storage for this session
|
|
const messagesStorage = getSessionMessagesStorage(sessionId);
|
|
|
|
// Get current messages to calculate the new count
|
|
const currentMessages = await messagesStorage.get();
|
|
const messageToDelete = currentMessages.find(msg => msg.id === messageId);
|
|
|
|
if (!messageToDelete) return; // Message not found
|
|
|
|
// Remove the message directly from the messages storage
|
|
await messagesStorage.set(prevMessages => prevMessages.filter(msg => msg.id !== messageId));
|
|
|
|
// Update the session's metadata (updatedAt timestamp and messageCount)
|
|
await chatSessionsMetaStorage.set(prevSessions => {
|
|
return prevSessions.map(session => {
|
|
if (session.id === sessionId) {
|
|
return {
|
|
...session,
|
|
updatedAt: getCurrentTimestamp(),
|
|
messageCount: Math.max(0, session.messageCount - 1),
|
|
};
|
|
}
|
|
return session;
|
|
});
|
|
});
|
|
},
|
|
|
|
storeAgentStepHistory: async (sessionId: string, task: string, history: string): Promise<void> => {
|
|
// Check if session exists
|
|
const sessionsMeta = await chatSessionsMetaStorage.get();
|
|
const sessionMeta = sessionsMeta.find(session => session.id === sessionId);
|
|
if (!sessionMeta) {
|
|
throw new Error(`Session with ID ${sessionId} not found`);
|
|
}
|
|
|
|
const agentStepHistoryStorage = getSessionAgentStepHistoryStorage(sessionId);
|
|
await agentStepHistoryStorage.set({
|
|
task,
|
|
history,
|
|
timestamp: getCurrentTimestamp(),
|
|
});
|
|
},
|
|
|
|
loadAgentStepHistory: async (sessionId: string): Promise<ChatAgentStepHistory | null> => {
|
|
const agentStepHistoryStorage = getSessionAgentStepHistoryStorage(sessionId);
|
|
const history = await agentStepHistoryStorage.get();
|
|
if (!history || !history.task || !history.timestamp || history.history === '' || history.history === '[]')
|
|
return null;
|
|
return history;
|
|
},
|
|
};
|
|
}
|
|
|
|
// Export the storage instance for direct use
|
|
export const chatHistoryStore = createChatHistoryStorage();
|