# Web Frontend (packages/web) ## Overview `@memoh/web` is the management UI for Memoh, built with Vue 3 + Vite. It provides visual configuration for bots, models, channels, memory, and more. ## Tech Stack | Category | Technology | |----------|-----------| | Framework | Vue 3 (Composition API, ` ``` ### Icon Usage - **FontAwesome** (primary): Global ``, icons registered in `main.ts` - **Lucide** (secondary): Direct imports ``, ``, used for theme toggle ### Notification Pattern ```typescript import { toast } from 'vue-sonner' toast.success(t('common.saved')) toast.error(resolveApiErrorMessage(error, 'Failed')) ``` ## Data Fetching ### API Client Setup (`lib/api-client.ts`) - SDK: `@memoh/sdk` auto-generated from OpenAPI via `@hey-api/openapi-ts` - Base URL: `VITE_API_URL` env var (defaults to `/api`, proxied by Vite dev server to backend) - Auth: Request interceptor attaches `Authorization: Bearer ${token}` from localStorage - 401 handling: Response interceptor removes token and redirects to `/login` ### Pinia Colada (Server State) Primary data fetching mechanism for CRUD operations: ```typescript // Query — auto-generated from SDK const { data, isLoading } = useQuery(getBotsQuery()) // Custom query with dynamic key const { data } = useQuery({ key: () => ['bot-settings', botId.value], query: async () => { const { data } = await getBotsByBotIdSettings({ path: { bot_id: botId.value }, throwOnError: true, }) return data }, enabled: () => !!botId.value, }) // Mutation with cache invalidation const queryCache = useQueryCache() const { mutateAsync } = useMutation({ mutation: async (body) => { const { data } = await putBotsByBotIdSettings({ path: { bot_id: botId.value }, body, throwOnError: true, }) return data }, onSettled: () => queryCache.invalidateQueries({ key: ['bot-settings', botId.value], }), }) ``` SDK also generates colada helpers: `getBotsQuery()`, `postBotsMutation()`, query key factories. ### Pinia Stores (Client State) | Store | Purpose | |-------|---------| | `user` | JWT token (`useLocalStorage`), user info, login/logout | | `settings` | Theme (dark/light), language (en/zh), persisted | | `capabilities` | Server feature flags (container backend, snapshot support) | | `chat-list` | Chat messages, streaming state, SSE event processing | Stores use Composition API style (`defineStore(() => { ... })`), with persistence via `pinia-plugin-persistedstate`. ### SSE Streaming (Chat) Chat responses are streamed via Server-Sent Events: - **Endpoints**: `/bots/{bot_id}/web/stream` (chat), `/bots/{bot_id}/messages/events` (real-time updates) - **Parsing**: `composables/api/useChat.sse.ts` reads `ReadableStream` and parses SSE `data:` lines - **Events**: `text_delta`, `reasoning_delta`, `tool_call_start/end`, `attachment_delta`, `processing_completed/failed` - **Retry**: `useRetryingStream` composable provides exponential backoff for reconnection - **State**: `store/chat-list.ts` processes streaming events into reactive message blocks in real-time - **Abort**: Stream cancellation via `AbortSignal` ### Error Handling - **Global**: `utils/api-error.ts` — `resolveApiErrorMessage()` extracts error from `message`, `error`, `detail` fields - **Mutations**: `useDialogMutation` composable wraps mutations with automatic `toast.error()` on failure - **SDK**: All calls use `throwOnError: true`; try/catch at component level - **Streams**: `processing_failed` / `error` events appended to message blocks ## i18n - Plugin: vue-i18n (Composition API, `legacy: false`) - Locales: `en` (English, default), `zh` (Chinese) - Files: `src/i18n/locales/en.json`, `src/i18n/locales/zh.json` - Usage: `const { t } = useI18n()` → `t('bots.title')` - Key namespaces: `common`, `auth`, `sidebar`, `settings`, `chat`, `models`, `provider`, `searchProvider`, `emailProvider`, `mcp`, `bots`, `home` ## Vite Configuration - Dev server port: 8082 (from `config.toml`) - Proxy: `/api` → backend (default `http://localhost:8080`) - Aliases: `@` → `./src`, `#` → `../ui/src` - Config: reads from `../../config.toml` via `@memoh/config` ## Development Rules - Use Vue 3 Composition API with `