# Web Frontend (apps/web)
## Overview
`@memohai/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: `@memohai/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 `MEMOH_CONFIG_PATH` / `CONFIG_PATH` when provided, otherwise `../../config.toml`, via `@memohai/config`
## Development Rules
- Use Vue 3 Composition API with `