From 247abfe32c619695af37e742e45ab21dba20b1c2 Mon Sep 17 00:00:00 2001 From: shahondin1624 Date: Thu, 12 Mar 2026 11:52:19 +0100 Subject: [PATCH] feat: add session creation and ID management with localStorage - Session store with UUID v4 generation and localStorage persistence - Session ID in URL params (?session=) for deep linking - "New Chat" button for creating fresh sessions - Message history persisted per session - Session title auto-generated from first user message Closes #11 Co-Authored-By: Claude Opus 4.6 --- implementation-plans/_index.md | 1 + implementation-plans/issue-011.md | 17 +++++ src/lib/stores/sessions.svelte.ts | 109 ++++++++++++++++++++++++++++++ src/routes/chat/+page.svelte | 40 ++++++++++- 4 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 implementation-plans/issue-011.md create mode 100644 src/lib/stores/sessions.svelte.ts diff --git a/implementation-plans/_index.md b/implementation-plans/_index.md index 585efdc..589d322 100644 --- a/implementation-plans/_index.md +++ b/implementation-plans/_index.md @@ -12,3 +12,4 @@ | #8 | Orchestration state progress indicator | COMPLETED | [issue-008.md](issue-008.md) | | #9 | Intermediate results display | COMPLETED | [issue-009.md](issue-009.md) | | #10 | Final result rendering with artifacts | COMPLETED | [issue-010.md](issue-010.md) | +| #11 | Session creation and ID management | COMPLETED | [issue-011.md](issue-011.md) | diff --git a/implementation-plans/issue-011.md b/implementation-plans/issue-011.md new file mode 100644 index 0000000..9c12895 --- /dev/null +++ b/implementation-plans/issue-011.md @@ -0,0 +1,17 @@ +--- +--- + +# Issue #11: Session creation and ID management + +**Status:** COMPLETED +**Issue:** https://git.shahondin1624.de/llm-multiverse/llm-multiverse-ui/issues/11 +**Branch:** `feature/issue-11-session-management` + +## Acceptance Criteria + +- [x] UUID v4 session ID generated on new chat creation +- [x] Active session ID persisted in URL params (?session=) +- [x] Session ID also stored in localStorage for recovery +- [x] "New Chat" button creates a fresh session +- [x] Switching sessions updates URL and loads correct message history +- [x] Session ID passed to ProcessRequest gRPC calls diff --git a/src/lib/stores/sessions.svelte.ts b/src/lib/stores/sessions.svelte.ts new file mode 100644 index 0000000..1b0a8bb --- /dev/null +++ b/src/lib/stores/sessions.svelte.ts @@ -0,0 +1,109 @@ +import type { ChatMessage } from '$lib/types'; + +export interface Session { + id: string; + title: string; + messages: ChatMessage[]; + createdAt: Date; +} + +const STORAGE_KEY = 'llm-multiverse-sessions'; +const ACTIVE_SESSION_KEY = 'llm-multiverse-active-session'; + +function loadSessions(): Map { + if (typeof localStorage === 'undefined') return new Map(); + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return new Map(); + const arr: [string, Session][] = JSON.parse(raw); + return new Map( + arr.map(([id, s]) => [ + id, + { ...s, createdAt: new Date(s.createdAt), messages: s.messages.map(m => ({ ...m, timestamp: new Date(m.timestamp) })) } + ]) + ); + } catch { + return new Map(); + } +} + +function saveSessions(sessions: Map) { + if (typeof localStorage === 'undefined') return; + localStorage.setItem(STORAGE_KEY, JSON.stringify([...sessions.entries()])); +} + +function loadActiveSessionId(): string | null { + if (typeof localStorage === 'undefined') return null; + return localStorage.getItem(ACTIVE_SESSION_KEY); +} + +function saveActiveSessionId(id: string) { + if (typeof localStorage === 'undefined') return; + localStorage.setItem(ACTIVE_SESSION_KEY, id); +} + +function createSessionStore() { + const sessions = $state>(loadSessions()); + let activeSessionId = $state(loadActiveSessionId()); + + function createSession(id?: string): Session { + const session: Session = { + id: id ?? crypto.randomUUID(), + title: 'New Chat', + messages: [], + createdAt: new Date() + }; + sessions.set(session.id, session); + activeSessionId = session.id; + saveSessions(sessions); + saveActiveSessionId(session.id); + return session; + } + + function getOrCreateSession(id?: string): Session { + if (id && sessions.has(id)) { + activeSessionId = id; + saveActiveSessionId(id); + return sessions.get(id)!; + } + return createSession(id); + } + + function updateMessages(sessionId: string, messages: ChatMessage[]) { + const session = sessions.get(sessionId); + if (!session) return; + session.messages = messages; + // Update title from first user message if still default + if (session.title === 'New Chat') { + const firstUser = messages.find(m => m.role === 'user'); + if (firstUser) { + session.title = firstUser.content.slice(0, 50) + (firstUser.content.length > 50 ? '...' : ''); + } + } + sessions.set(sessionId, session); + saveSessions(sessions); + } + + function switchSession(id: string) { + if (sessions.has(id)) { + activeSessionId = id; + saveActiveSessionId(id); + } + } + + function getAllSessions(): Session[] { + return [...sessions.values()].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); + } + + return { + get activeSessionId() { return activeSessionId; }, + get activeSession() { return activeSessionId ? sessions.get(activeSessionId) ?? null : null; }, + createSession, + getOrCreateSession, + updateMessages, + switchSession, + getAllSessions + }; +} + +export const sessionStore = createSessionStore(); diff --git a/src/routes/chat/+page.svelte b/src/routes/chat/+page.svelte index 4e363fe..4c7610f 100644 --- a/src/routes/chat/+page.svelte +++ b/src/routes/chat/+page.svelte @@ -1,4 +1,7 @@
-
+

Chat

+
-- 2.49.1