From 351c861777aa88885ef226cd1928276bdd5b7e05 Mon Sep 17 00:00:00 2001 From: ekko Date: Thu, 16 Apr 2026 08:38:18 +0800 Subject: [PATCH] refactor: restructure project for multi-agent extensibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Migrate source to packages/client and packages/server directories - Namespace all Hermes-specific code under hermes/ subdirectories (api/hermes/, components/hermes/, views/hermes/, stores/hermes/) - Add hermes.* route names and /hermes/* path prefixes - Upgrade @koa/router to v15, adapt path-to-regexp v8 syntax - Fix proxy path rewriting: /api/hermes/v1/* → /v1/*, /api/hermes/* → /api/* - Fix frontend API paths to match backend /api/hermes/* routes - Fix WebSocket terminal path to /api/hermes/terminal - Add proxyMiddleware for reliable unmatched route proxying - Add profiles route module and hermes-cli profile commands - Update CLAUDE.md development guide with new architecture - Add Chinese README (README_zh.md) - Add Web Terminal feature to README Co-Authored-By: Claude Opus 4.6 --- .gitignore | 5 +- CLAUDE.md | 452 ++++++++++++++++++ README.md | 20 +- README_zh.md | 213 +++++++++ output.gif | Bin 133903 -> 0 bytes package.json | 10 +- index.html => packages/client/index.html | 0 .../client/public}/favicon.ico | Bin .../client/public}/favicon.svg | 0 {public => packages/client/public}/icons.svg | 0 {assets => packages/client/public}/logo.png | Bin {src => packages/client/src}/App.vue | 2 +- {src => packages/client/src}/api/client.ts | 0 .../client/src/api/hermes}/chat.ts | 8 +- .../client/src/api/hermes}/config.ts | 14 +- .../client/src/api/hermes}/jobs.ts | 18 +- .../client/src/api/hermes}/logs.ts | 6 +- .../client/src/api/hermes}/sessions.ts | 10 +- .../client/src/api/hermes}/skills.ts | 14 +- .../client/src/api/hermes}/system.ts | 27 +- {src => packages/client/src}/assets/dance.mp4 | Bin {src => packages/client/src}/assets/hero.png | Bin .../client/src/assets}/logo.png | Bin .../client/src}/assets/output.gif | Bin .../client/src}/assets/thinking.mp4 | Bin {src => packages/client/src}/assets/video.mp4 | Bin {src => packages/client/src}/assets/vite.svg | 0 .../src/components/hermes}/chat/ChatInput.vue | 4 +- .../src/components/hermes}/chat/ChatPanel.vue | 4 +- .../hermes}/chat/MarkdownRenderer.vue | 0 .../components/hermes}/chat/MessageItem.vue | 2 +- .../components/hermes}/chat/MessageList.vue | 2 +- .../src/components/hermes}/jobs/JobCard.vue | 4 +- .../components/hermes}/jobs/JobFormModal.vue | 4 +- .../src/components/hermes}/jobs/JobsPanel.vue | 2 +- .../hermes}/models/ProviderCard.vue | 4 +- .../hermes}/models/ProviderFormModal.vue | 2 +- .../hermes}/models/ProvidersPanel.vue | 2 +- .../hermes}/settings/AgentSettings.vue | 2 +- .../hermes}/settings/DisplaySettings.vue | 2 +- .../hermes}/settings/MemorySettings.vue | 2 +- .../hermes}/settings/PlatformCard.vue | 0 .../hermes}/settings/PlatformSettings.vue | 4 +- .../hermes}/settings/PrivacySettings.vue | 2 +- .../hermes}/settings/SessionSettings.vue | 2 +- .../hermes}/settings/SettingRow.vue | 0 .../components/hermes}/skills/SkillDetail.vue | 4 +- .../components/hermes}/skills/SkillList.vue | 4 +- .../components/hermes}/usage/DailyTrend.vue | 2 +- .../hermes}/usage/ModelBreakdown.vue | 2 +- .../components/hermes}/usage/StatCards.vue | 2 +- .../src}/components/layout/AppSidebar.vue | 44 +- .../src}/components/layout/LanguageSwitch.vue | 0 .../src}/components/layout/ModelSelector.vue | 2 +- .../client/src}/composables/useKeyboard.ts | 4 +- {src => packages/client/src}/env.d.ts | 0 {src => packages/client/src}/i18n/index.ts | 0 .../client/src}/i18n/locales/en.ts | 0 .../client/src}/i18n/locales/zh.ts | 0 {src => packages/client/src}/main.ts | 0 packages/client/src/router/index.ts | 87 ++++ .../client}/src/shared/providers.ts | 0 .../client/src/stores/hermes}/app.ts | 2 +- .../client/src/stores/hermes}/chat.ts | 4 +- .../client/src/stores/hermes}/jobs.ts | 4 +- .../client/src/stores/hermes}/models.ts | 4 +- .../client/src/stores/hermes}/settings.ts | 4 +- .../client/src/stores/hermes}/usage.ts | 2 +- .../client/src}/styles/global.scss | 0 {src => packages/client/src}/styles/theme.ts | 0 .../client/src}/styles/variables.scss | 0 .../client/src}/views/LoginView.vue | 4 +- .../client/src/views/hermes}/ChannelsView.vue | 4 +- .../client/src/views/hermes}/ChatView.vue | 6 +- .../client/src/views/hermes}/JobsView.vue | 6 +- .../client/src/views/hermes}/LogsView.vue | 2 +- .../client/src/views/hermes}/MemoryView.vue | 4 +- .../client/src/views/hermes}/ModelsView.vue | 6 +- .../client/src/views/hermes}/SettingsView.vue | 14 +- .../client/src/views/hermes}/SkillsView.vue | 6 +- .../client/src/views/hermes}/TerminalView.vue | 4 +- .../client/src/views/hermes}/UsageView.vue | 8 +- {server => packages/server}/src/config.ts | 0 {server => packages/server}/src/index.ts | 20 +- .../server/src/routes/hermes}/config.ts | 8 +- .../server/src/routes/hermes}/filesystem.ts | 28 +- packages/server/src/routes/hermes/index.ts | 21 + .../server/src/routes/hermes}/logs.ts | 6 +- packages/server/src/routes/hermes/profiles.ts | 190 ++++++++ .../src/routes/hermes}/proxy-handler.ts | 8 +- packages/server/src/routes/hermes/proxy.ts | 17 + .../server/src/routes/hermes}/sessions.ts | 10 +- .../server/src/routes/hermes}/terminal.ts | 10 +- .../server/src/routes/hermes}/weixin.ts | 8 +- .../server}/src/routes/upload.ts | 0 .../server}/src/routes/webhook.ts | 0 .../server}/src/services/auth.ts | 0 .../server}/src/services/hermes-cli.ts | 214 +++++++++ .../server}/src/services/hermes.ts | 0 .../server/src}/shared/providers.ts | 0 {server => packages/server}/tsconfig.json | 2 +- server/src/routes/proxy.ts | 8 - src/assets/logo.png | Bin 1319989 -> 0 bytes src/router/index.ts | 87 ---- tsconfig.app.json | 4 +- vite.config.ts | 7 +- 106 files changed, 1409 insertions(+), 317 deletions(-) create mode 100644 CLAUDE.md create mode 100644 README_zh.md delete mode 100644 output.gif rename index.html => packages/client/index.html (100%) rename {public => packages/client/public}/favicon.ico (100%) rename {public => packages/client/public}/favicon.svg (100%) rename {public => packages/client/public}/icons.svg (100%) rename {assets => packages/client/public}/logo.png (100%) rename {src => packages/client/src}/App.vue (97%) rename {src => packages/client/src}/api/client.ts (100%) rename {src/api => packages/client/src/api/hermes}/chat.ts (86%) rename {src/api => packages/client/src/api/hermes}/config.ts (85%) rename {src/api => packages/client/src/api/hermes}/jobs.ts (73%) rename {src/api => packages/client/src/api/hermes}/logs.ts (84%) rename {src/api => packages/client/src/api/hermes}/sessions.ts (88%) rename {src/api => packages/client/src/api/hermes}/skills.ts (72%) rename {src/api => packages/client/src/api/hermes}/system.ts (72%) rename {src => packages/client/src}/assets/dance.mp4 (100%) rename {src => packages/client/src}/assets/hero.png (100%) rename {public => packages/client/src/assets}/logo.png (100%) rename {src => packages/client/src}/assets/output.gif (100%) rename {src => packages/client/src}/assets/thinking.mp4 (100%) rename {src => packages/client/src}/assets/video.mp4 (100%) rename {src => packages/client/src}/assets/vite.svg (100%) rename {src/components => packages/client/src/components/hermes}/chat/ChatInput.vue (98%) rename {src/components => packages/client/src/components/hermes}/chat/ChatPanel.vue (99%) rename {src/components => packages/client/src/components/hermes}/chat/MarkdownRenderer.vue (100%) rename {src/components => packages/client/src/components/hermes}/chat/MessageItem.vue (99%) rename {src/components => packages/client/src/components/hermes}/chat/MessageList.vue (98%) rename {src/components => packages/client/src/components/hermes}/jobs/JobCard.vue (98%) rename {src/components => packages/client/src/components/hermes}/jobs/JobFormModal.vue (97%) rename {src/components => packages/client/src/components/hermes}/jobs/JobsPanel.vue (96%) rename {src/components => packages/client/src/components/hermes}/models/ProviderCard.vue (96%) rename {src/components => packages/client/src/components/hermes}/models/ProviderFormModal.vue (99%) rename {src/components => packages/client/src/components/hermes}/models/ProvidersPanel.vue (95%) rename {src/components => packages/client/src/components/hermes}/settings/AgentSettings.vue (97%) rename {src/components => packages/client/src/components/hermes}/settings/DisplaySettings.vue (97%) rename {src/components => packages/client/src/components/hermes}/settings/MemorySettings.vue (96%) rename {src/components => packages/client/src/components/hermes}/settings/PlatformCard.vue (100%) rename {src/components => packages/client/src/components/hermes}/settings/PlatformSettings.vue (99%) rename {src/components => packages/client/src/components/hermes}/settings/PrivacySettings.vue (93%) rename {src/components => packages/client/src/components/hermes}/settings/SessionSettings.vue (96%) rename {src/components => packages/client/src/components/hermes}/settings/SettingRow.vue (100%) rename {src/components => packages/client/src/components/hermes}/skills/SkillDetail.vue (98%) rename {src/components => packages/client/src/components/hermes}/skills/SkillList.vue (98%) rename {src/components => packages/client/src/components/hermes}/usage/DailyTrend.vue (98%) rename {src/components => packages/client/src/components/hermes}/usage/ModelBreakdown.vue (97%) rename {src/components => packages/client/src/components/hermes}/usage/StatCards.vue (97%) rename {src => packages/client/src}/components/layout/AppSidebar.vue (90%) rename {src => packages/client/src}/components/layout/LanguageSwitch.vue (100%) rename {src => packages/client/src}/components/layout/ModelSelector.vue (95%) rename {src => packages/client/src}/composables/useKeyboard.ts (90%) rename {src => packages/client/src}/env.d.ts (100%) rename {src => packages/client/src}/i18n/index.ts (100%) rename {src => packages/client/src}/i18n/locales/en.ts (100%) rename {src => packages/client/src}/i18n/locales/zh.ts (100%) rename {src => packages/client/src}/main.ts (100%) create mode 100644 packages/client/src/router/index.ts rename {server => packages/client}/src/shared/providers.ts (100%) rename {src/stores => packages/client/src/stores/hermes}/app.ts (97%) rename {src/stores => packages/client/src/stores/hermes}/chat.ts (99%) rename {src/stores => packages/client/src/stores/hermes}/jobs.ts (96%) rename {src/stores => packages/client/src/stores/hermes}/models.ts (96%) rename {src/stores => packages/client/src/stores/hermes}/settings.ts (97%) rename {src/stores => packages/client/src/stores/hermes}/usage.ts (98%) rename {src => packages/client/src}/styles/global.scss (100%) rename {src => packages/client/src}/styles/theme.ts (100%) rename {src => packages/client/src}/styles/variables.scss (100%) rename {src => packages/client/src}/views/LoginView.vue (98%) rename {src/views => packages/client/src/views/hermes}/ChannelsView.vue (86%) rename {src/views => packages/client/src/views/hermes}/ChatView.vue (71%) rename {src/views => packages/client/src/views/hermes}/JobsView.vue (90%) rename {src/views => packages/client/src/views/hermes}/LogsView.vue (98%) rename {src/views => packages/client/src/views/hermes}/MemoryView.vue (98%) rename {src/views => packages/client/src/views/hermes}/ModelsView.vue (87%) rename {src/views => packages/client/src/views/hermes}/SettingsView.vue (88%) rename {src/views => packages/client/src/views/hermes}/SkillsView.vue (96%) rename {src/views => packages/client/src/views/hermes}/TerminalView.vue (98%) rename {src/views => packages/client/src/views/hermes}/UsageView.vue (85%) rename {server => packages/server}/src/config.ts (100%) rename {server => packages/server}/src/index.ts (90%) rename {server/src/routes => packages/server/src/routes/hermes}/config.ts (97%) rename {server/src/routes => packages/server/src/routes/hermes}/filesystem.ts (95%) create mode 100644 packages/server/src/routes/hermes/index.ts rename {server/src/routes => packages/server/src/routes/hermes}/logs.ts (90%) create mode 100644 packages/server/src/routes/hermes/profiles.ts rename {server/src/routes => packages/server/src/routes/hermes}/proxy-handler.ts (85%) create mode 100644 packages/server/src/routes/hermes/proxy.ts rename {server/src/routes => packages/server/src/routes/hermes}/sessions.ts (79%) rename {server/src/routes => packages/server/src/routes/hermes}/terminal.ts (97%) rename {server/src/routes => packages/server/src/routes/hermes}/weixin.ts (93%) rename {server => packages/server}/src/routes/upload.ts (100%) rename {server => packages/server}/src/routes/webhook.ts (100%) rename {server => packages/server}/src/services/auth.ts (100%) rename {server => packages/server}/src/services/hermes-cli.ts (60%) rename {server => packages/server}/src/services/hermes.ts (100%) rename {src => packages/server/src}/shared/providers.ts (100%) rename {server => packages/server}/tsconfig.json (88%) delete mode 100644 server/src/routes/proxy.ts delete mode 100644 src/assets/logo.png delete mode 100644 src/router/index.ts diff --git a/.gitignore b/.gitignore index 5d42b0d..6d8608f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,11 +11,12 @@ node_modules dist dist-ssr server/dist +packages/server/dist *.local ROADMAP.md # Server data -server/data/ -server/node_modules/ +packages/server/data/ +packages/server/node_modules/ .hermes-web-ui/ # Editor directories and files diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..10f833a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,452 @@ +# CLAUDE.md — Hermes Web UI Development Guide + +## Project Overview + +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. + +**Tech stack:** + +- **Frontend:** Vue 3 (Composition API, ` + + + + +``` + +Key patterns: +- 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' + +export const useMyStore = defineStore('myStore', () => { + const items = ref([]) + const loading = ref(false) + + async function fetchItems() { + loading.value = true + try { + items.value = await apiCall() + } 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(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` + +```ts +// packages/client/src/api/hermes/sessions.ts +import { request } from '../client' + +export async function fetchSessions(source?: string, limit?: number): Promise { + const params = new URLSearchParams() + if (source) params.set('source', source) + if (limit) params.set('limit', String(limit)) + const query = params.toString() + const res = await request<{ sessions: SessionSummary[] }>(`/api/hermes/sessions${query ? `?${query}` : ''}`) + return res.sessions +} +``` + +**API path rules:** +- Local BFF endpoints: `/api/hermes/{resource}` — handled by Koa routes, call Hermes CLI directly +- Gateway proxy endpoints: `/api/hermes/v1/*`, `/api/hermes/jobs/*` — forwarded to upstream Hermes gateway +- 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 +export default { + 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 `