Hermes Web UI is a web dashboard for [Hermes Agent](https://github.com/EKKOLearnAI/hermes-web-ui), a multi-platform AI chat system. It provides session management, scheduled jobs, usage analytics, model configuration, channel management (Telegram, Discord, Slack, WhatsApp, etc.), an integrated terminal, and a streaming chat interface.
The project is designed for **multi-agent extensibility** — Hermes is the first agent integration. All agent-specific code is namespaced under `hermes/` directories, so future agents can be added alongside without conflicts.
- Import Naive UI components directly from `naive-ui`
- Use `useMessage()` for toast notifications
- Use `useI18n()` for translations, access via `t('key.path')`
- Scoped SCSS with `@use '@/styles/variables' as *`
### Pinia Stores
Use setup store syntax (function passed to `defineStore`):
```ts
import{defineStore}from'pinia'
import{ref}from'vue'
exportconstuseMyStore=defineStore('myStore',()=>{
constitems=ref<Item[]>([])
constloading=ref(false)
asyncfunctionfetchItems() {
loading.value=true
try{
items.value=awaitapiCall()
}finally{
loading.value=false
}
}
return{items,loading,fetchItems}
})
```
Existing stores in `packages/client/src/stores/hermes/`: `app`, `chat`, `jobs`, `models`, `settings`, `usage`.
### API Layer
Agent-specific API modules live in `api/{agent}/`. The shared base `api/client.ts` provides:
-`request<T>(path, options)` — typed fetch wrapper with automatic `Authorization: Bearer` header and global 401 handling (clears token, redirects to login)
-`getApiKey()` / `setApiKey()` / `clearApiKey()` — token management via `localStorage`
-`getBaseUrlValue()` — configurable server URL from `localStorage`
- Shared endpoints: `/health`, `/upload`, `/webhook` — no agent prefix
### i18n
Two locales: `en.ts` and `zh.ts` in `packages/client/src/i18n/locales/`. Flat nested object structure organized by feature section:
```ts
// en.ts
exportdefault{
chat:{
emptyState:'Start a conversation with Hermes Agent',
inputPlaceholder:'Type a message...',
sessions:'Sessions',
// ...
},
common:{
save:'Save',
cancel:'Cancel',
delete:'Delete',
// ...
},
}
```
When adding new strings, always add to both `en.ts` and `zh.ts`.
### SCSS Styling
- Global variables in `packages/client/src/styles/variables.scss` — import with `@use '@/styles/variables' as *`
- Theme: "Pure Ink" (monochrome black/white/gray), no color accent
- Mobile breakpoint: `$breakpoint-mobile: 768px`
- Global resets and shared classes in `packages/client/src/styles/global.scss`
- Component styles are always `<style scoped lang="scss">`
### Router
Hash-based routing (`createWebHashHistory`). All routes use lazy imports. Auth guard in `router.beforeEach` redirects unauthenticated users to `/` (login). Public routes use `meta: { public: true }`.
- Parameters: `:id` (single segment) or `{*path}` (wildcard, matches `/`)
- No regex groups `(.*)` — use `{*name}` instead
- No modifiers `:id+` or `:id*` — use `{*name}`
### Reverse Proxy
Unmatched `/api/hermes/*` and `/v1/*` requests are forwarded to the upstream Hermes gateway (`http://127.0.0.1:8642`). Path rewriting in `proxy-handler.ts`:
The proxy is implemented as both a route (`proxyRoutes.all('/api/hermes/{*any}', proxy)`) and a middleware (`proxyMiddleware`) registered on the main app to catch any requests that slip through route matching.
**Important:** Custom API endpoints handled locally (not proxied) must be registered **before**`hermesRoutes.routes()` in `bootstrap()`. The proxy route `proxyRoutes.all('/api/hermes/{*any}')` matches all `/api/hermes/*` paths, so any middleware registered after it will never be reached. See the `update` middleware in `index.ts` for an example.
1. Create view component in `packages/client/src/views/hermes/MyView.vue`
2. Add route in `packages/client/src/router/index.ts` with name `hermes.myPage` and path `/hermes/my-page`
3. Add sidebar entry in `packages/client/src/components/layout/AppSidebar.vue` with `handleNav('hermes.myPage')`
4. Add i18n keys to both `en.ts` and `zh.ts`
### Add a new Hermes API endpoint
1. Add the route handler in `packages/server/src/routes/hermes/` (new or existing module)
2. If it calls Hermes CLI, add a wrapper function in `packages/server/src/services/hermes-cli.ts`
3. Register the route in `packages/server/src/routes/hermes/index.ts` via `hermesRoutes.use(myRoutes.routes())`
4. Add the frontend API function in `packages/client/src/api/hermes/`
5. If the endpoint should be proxied to the upstream gateway (not handled locally), ensure the path starts with `/api/hermes/` — the `proxyMiddleware` will catch it automatically
### Add a new Hermes Pinia store
1. Create `packages/client/src/stores/hermes/myFeature.ts` using setup syntax