Architecture
Data model (Dexie v7 schema)
All persistent data lives in IndexedDB via Dexie 4.4. The schema is declared in src/utils/db.ts:
| Store | Indexes | Shape |
|---|---|---|
workspaces | id, type, parentId | { id, name, avatar, type: 'workspace'|'folder', parentId, vars, indexContent, defaultAssistantId?, lastDialogId?, listOpen: {assistants,artifacts,dialogs} } |
dialogs | id, workspaceId | { id, name, workspaceId, assistantId?, msgTree, msgRoute, inputVars, modelOverride? } |
messages | id, type, dialogId | { id, type: 'user'|'assistant', assistantId?, dialogId, contents, status, generatingSession?, error?, warnings?, usage?, modelName? } |
assistants | id, workspaceId | { id, name, avatar, prompt, promptVars, promptTemplate, provider, model?, modelSettings, workspaceId, plugins, promptRole, contextNum?, stream, description?, author?, homepage? } |
artifacts | id, workspaceId | { id, name, workspaceId, versions: ArtifactVersion[], currIndex, readable, writable, open } |
installedPluginsV2 | key, id | Plugin install records (lobechat / gradio / mcp) |
reactives | key | UI reactive state per key (settings, current selection, etc.) |
avatarImages | id | Cached generated avatars (Material color palette) |
imageCache | url | Fetched image cache |
items | id, type, dialogId | Tool-call result items (images, files, search hits) referenced from messages |
providers | id | User-defined custom providers (mix-and-match subproviders) |
The canvases store is in the schema but deprecated — it was the old canvas API, replaced by artifacts. Migration preserves old data but new writes go to artifacts.
Branching chat — the tree structure
Each dialog stores messages in a tree, not a list. The two key fields on the dialog are:
msgTree: Record<string, string[]>— maps each message id to its child message ids. Always starts with$root.msgRoute: number[]— the currently displayed path, as indices into each level's children array.
When you click "edit" on a past message, the assistant message that follows is duplicated, the new branch is added under the edited user message, and msgRoute shifts to point at the new index. The tree can branch at every user message; older branches remain accessible via the chat router UI.
This logic lives in src/composables/use-dialog-chain.ts (functions getChain, switchChain, deleteBranch, appendMessage).
Routing
Defined in src/router/routes.ts:
| Route | Page | Notes |
|---|---|---|
/workspaces/:wid/ | WorkspacePage + drawer | Workspace home (left drawer with assistants / dialogs) |
/workspaces/:wid/settings | WorkspaceSettings | Default assistant, home content, vars |
/workspaces/:wid/dialogs/:did | DialogView | Chat with branching tree |
/workspaces/:wid/assistants/:aid | AssistantView | Edit assistant config |
/workspaces/:wid/assistants/:aid/plugins/:pid | PluginAdjust | Per-assistant plugin config |
/settings/ | SettingsView | Providers, features, shortcuts, theme |
/settings/shortcut-keys | ShortcutKeys | Key binding editor |
/settings/providers/:id | CustomProvider | Edit custom provider |
/plugins/ | PluginsMarket | Installed + market plugins |
/plugins/:pid | PluginSettings | Per-plugin settings |
/assistants/ | AssistantsMarket | Browse / import assistants |
/assistants/:aid | AssistantView (same editor) | |
/set-provider | SetProvider | Onboarding wizard |
/account | AccountPage | Only if DexieDBURL is set (fork hides this) |
/model-pricing | ModelPricing | Only if both DexieDBURL and LitellmBaseURL are set |
All routes share MainLayout. Catch-all 404 lands on ErrorNotFound.
Pinia stores
In src/stores/:
workspaces.ts— CRUD for workspaces, folders, dialogs (cascading delete)assistants.ts— CRUD for assistantsproviders.ts— built-in + custom provider factory; supports subprovider fallbackplugins.ts— 8 builtin + 6 always-on + 3 installed kinds (lobechat / gradio / mcp); MCP uses@modelcontextprotocol/sdkwith stdio/http/sse transportsuser-data.ts— export / import all IndexedDB stores to/from JSONuser-perfs.ts— 38 user preferences persisted viapersistentReactiveui-state.ts— transient UI state (drawers, modals)
i18n
Three locales: en-US, zh-CN, zh-TW. Source under src/i18n/<locale>/ as TypeScript modules. Auto-detection: navigator.language → matching locale, fallback to en-US. Quasar's own language pack switches to match. The selection is persisted in localData (browser localStorage).
Adding a language requires creating a new folder under src/i18n/ and adding its name to the languages set in src/boot/i18n.ts.
Runtime platform detection
src/utils/platform-api.ts exports IsTauri, IsCapacitor, IsWeb, CapacitorPlatform, TauriPlatform. All platform-specific code paths branch on these:
fetch— uses Tauri HTTP plugin on desktop, Capacitor stream-fetch on Android, nativefetchon iOS/webclipboardReadText— Tauri clipboard plugin / Capacitor Clipboard / web Clipboard APIexportFile— direct write toDocuments/AiaW/on Android, Quasar'sexportFileon web