Skip to content

Frontend Architecture

frontend/src/
├── app.html # HTML shell
├── app.css # Global styles
├── app.d.ts # TypeScript declarations
├── hooks.server.ts # Server hooks (Supabase SSR setup)
├── lib/
│ ├── api.ts # REST API client (apiFetch, api object)
│ ├── ws.ts # WebSocket client (createChatWebSocket)
│ ├── supabase.ts # Supabase client factories
│ ├── index.ts # Barrel exports
│ ├── stores/
│ │ ├── auth.ts # Auth state (user, session, loading)
│ │ ├── session.ts # Session/chat state (sessions, activeSession)
│ │ └── models.ts # Available models store
│ ├── components/
│ │ ├── chat/
│ │ │ ├── ChatArea.svelte # Main chat area (manages tabs)
│ │ │ ├── ChatPanel.svelte # Single chat panel with messages
│ │ │ ├── ChatInput.svelte # Message input with send button
│ │ │ ├── ChatMessage.svelte # Single message bubble
│ │ │ ├── ChatTab.svelte # Tab header for a chat
│ │ │ ├── AddChatDialog.svelte # Dialog to add a new chat
│ │ │ ├── ModelPicker.svelte # Model selection dropdown
│ │ │ └── StreamingIndicator.svelte # Typing indicator
│ │ ├── context/
│ │ │ └── ContextPicker.svelte # Context item search and selection
│ │ ├── layout/
│ │ │ ├── DashboardLayout.svelte # Main layout with sidebar
│ │ │ └── NavBar.svelte # Top navigation bar
│ │ ├── session/
│ │ │ └── SessionList.svelte # Sidebar session list
│ │ └── ui/
│ │ └── Toast.svelte # Notification toast
│ └── assets/
│ └── favicon.svg
└── routes/
├── +layout.svelte # Root layout (auth initialization)
├── +page.svelte # Landing/redirect page
├── (auth)/
│ ├── login/+page.svelte # Google OAuth login page
│ └── callback/+page.server.ts # OAuth callback handler
└── (app)/
└── dashboard/
├── +page.server.ts # Auth guard, load sessions
└── +page.svelte # Main dashboard page

The frontend uses SvelteKit route groups to separate auth and app concerns:

  • (auth)/ — Login and OAuth callback pages. No auth guard.
  • (app)/ — Protected pages. The +page.server.ts load function verifies the Supabase session and redirects to login if not authenticated.

Holds the current Supabase user and session:

interface AuthState {
user: User | null;
session: Session | null;
loading: boolean;
}

Manages chat sessions and the active selection:

export const sessions = writable<ChatSession[]>([]);
export const activeSession = writable<ChatSession | null>(null);
export const activeTabIndex = writable<number>(0);

A ChatSession contains an array of Chat objects, each with its own messages array and model_name.

Lists available LLM models fetched from the backend:

export const availableModels = writable<ModelInfo[]>([]);

The api.ts module provides a typed wrapper around fetch:

export const api = {
sessions: { list, create, delete, updateMaster },
chats: { add, remove, query, sync, import },
models: { list },
contextItems: { list, search, create },
suggestions: { search },
};

All methods accept a token parameter (the Supabase access token) which is sent as a Bearer token. The base URL is constructed from window.location.origin + the configured base path.

The ws.ts module exports createChatWebSocket(sessionId, accessToken), which returns:

PropertyTypeDescription
sendQuery(chatId, content) => voidSend a message to one chat
sendQueryAll(content, chatIds[]) => voidSend the same message to multiple chats
tokensWritable<{[chatId]: string}>Accumulated streamed tokens per chat
doneWritable<{[chatId]: boolean}>Whether streaming is complete per chat
errorsWritable<{[chatId]: string}>Error messages per chat
connectedWritable<boolean>WebSocket connection status
close() => voidClose the connection
resetChat(chatId) => voidClear tokens/done/error for a chat

The client automatically reconnects after 3 seconds on disconnect.

DashboardLayout
├── NavBar
├── SessionList (sidebar)
└── ChatArea
├── ChatTab (one per chat, horizontal tabs)
├── AddChatDialog
│ ├── ModelPicker
│ └── ContextPicker
└── ChatPanel (active chat)
├── ChatMessage (repeated)
├── StreamingIndicator
└── ChatInput

The ChatArea component manages the tab bar and creates a ChatPanel for the active tab. When a user sends a message, it calls ws.sendQuery() for single-chat mode or ws.sendQueryAll() for multi-link mode, and subscribes to the tokens store to render the streaming response.