From 3ba76ad19bb4532fb5eccf7b1a3d048533ab6996 Mon Sep 17 00:00:00 2001 From: ekko <152005280+EKKOLearnAI@users.noreply.github.com> Date: Fri, 1 May 2026 11:27:43 +0800 Subject: [PATCH] feat: add History page for browsing Hermes sessions (v0.5.5) (#370) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Features: - Add dedicated History page for browsing Hermes session history - Independent session state (does not interfere with active chat) - Auto-select first CLI session on page load - Filter out api_server and cron sources Components: - New HistoryView.vue with isolated state management - New HistoryMessageList.vue with session prop support - Filters empty content and tool messages without toolName Backend: - Add GET /api/hermes/sessions/hermes endpoint (excludes api_server) - Add GET /api/hermes/sessions/hermes/:id endpoint (404s for api_server) - Add fetchHermesSessions() and fetchHermesSession() API functions Cleanup: - Remove localStorage session caching - Simplify profile switching cache management - Clean up废弃 cache cleanup calls i18n: - Add "History" translation to all 8 locales - Add v0.5.5 changelog entries in all languages - 🎉 Happy Labor Day! Co-authored-by: Claude Sonnet 4.6 --- package.json | 2 +- packages/client/src/api/hermes/sessions.ts | 24 + .../hermes/chat/HistoryMessageList.vue | 427 +++++++++ .../src/components/layout/AppSidebar.vue | 7 + packages/client/src/data/changelog.ts | 14 + packages/client/src/i18n/locales/de.ts | 12 +- packages/client/src/i18n/locales/en.ts | 22 +- packages/client/src/i18n/locales/es.ts | 12 +- packages/client/src/i18n/locales/fr.ts | 12 +- packages/client/src/i18n/locales/ja.ts | 12 +- packages/client/src/i18n/locales/ko.ts | 12 +- packages/client/src/i18n/locales/pt.ts | 12 +- packages/client/src/i18n/locales/zh.ts | 22 +- packages/client/src/router/index.ts | 5 + packages/client/src/stores/hermes/chat.ts | 26 +- packages/client/src/stores/hermes/profiles.ts | 27 +- .../client/src/views/hermes/HistoryView.vue | 829 ++++++++++++++++++ .../server/src/controllers/hermes/sessions.ts | 41 + packages/server/src/routes/hermes/sessions.ts | 2 + 19 files changed, 1473 insertions(+), 47 deletions(-) create mode 100644 packages/client/src/components/hermes/chat/HistoryMessageList.vue create mode 100644 packages/client/src/views/hermes/HistoryView.vue diff --git a/package.json b/package.json index 67006bd..60a34a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hermes-web-ui", - "version": "0.5.4", + "version": "0.5.5", "description": "Self-hosted AI chat dashboard for Hermes Agent — multi-model (Claude, GPT, Gemini, DeepSeek) web UI with Telegram, Discord, Slack, WhatsApp integration", "repository": { "type": "git", diff --git a/packages/client/src/api/hermes/sessions.ts b/packages/client/src/api/hermes/sessions.ts index d47bab2..785f0aa 100644 --- a/packages/client/src/api/hermes/sessions.ts +++ b/packages/client/src/api/hermes/sessions.ts @@ -56,6 +56,18 @@ 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/hermes${query ? `?${query}` : ''}`) + return res.sessions +} + export async function searchSessions(q: string, source?: string, limit?: number): Promise { const params = new URLSearchParams() params.set('q', q) @@ -75,6 +87,18 @@ export async function fetchSession(id: string): Promise { } } +/** + * Fetch Hermes session detail only (exclude api_server source) + */ +export async function fetchHermesSession(id: string): Promise { + try { + const res = await request<{ session: SessionDetail }>(`/api/hermes/sessions/hermes/${id}`) + return res.session + } catch { + return null + } +} + export async function deleteSession(id: string): Promise { try { await request(`/api/hermes/sessions/${id}`, { method: 'DELETE' }) diff --git a/packages/client/src/components/hermes/chat/HistoryMessageList.vue b/packages/client/src/components/hermes/chat/HistoryMessageList.vue new file mode 100644 index 0000000..83531ba --- /dev/null +++ b/packages/client/src/components/hermes/chat/HistoryMessageList.vue @@ -0,0 +1,427 @@ + + + + + diff --git a/packages/client/src/components/layout/AppSidebar.vue b/packages/client/src/components/layout/AppSidebar.vue index 53ec46b..022dc1c 100644 --- a/packages/client/src/components/layout/AppSidebar.vue +++ b/packages/client/src/components/layout/AppSidebar.vue @@ -80,6 +80,13 @@ function openChangelog() { {{ t("sidebar.chat") }} + + + +
+
{{ t('common.loading') }}
+
{{ t('chat.noSessions') }}
+ + + + +
+ + + + + + + + + + + + +
+
+
+ + + + {{ activeSessionTitle }} + {{ getSourceLabel(activeSessionSource) }} + 📁 {{ historySession.workspace.split('/').pop() || historySession.workspace }} +
+
+ + + {{ t('chat.copySessionId') }} + +
+
+ + +
+ + + + diff --git a/packages/server/src/controllers/hermes/sessions.ts b/packages/server/src/controllers/hermes/sessions.ts index ff92746..ac00737 100644 --- a/packages/server/src/controllers/hermes/sessions.ts +++ b/packages/server/src/controllers/hermes/sessions.ts @@ -160,6 +160,26 @@ export async function list(ctx: any) { ctx.body = { sessions: filterPendingDeletedSessions(sessions) } } +/** + * List Hermes sessions only (exclude api_server source) + * GET /api/hermes/sessions/hermes?source=&limit= + */ +export async function listHermesSessions(ctx: any) { + const source = (ctx.query.source as string) || undefined + const limit = ctx.query.limit ? parseInt(ctx.query.limit as string, 10) : undefined + + try { + const sessions = await listSessionSummaries(source, limit && limit > 0 ? limit : 2000) + ctx.body = { sessions: filterPendingDeletedSessions(sessions.filter(s => s.source !== 'api_server' && s.source !== 'cron')) } + return + } catch (err) { + logger.warn(err, 'Hermes Session DB: summary query failed, falling back to CLI') + } + + const sessions = await hermesCli.listSessions(source, limit) + ctx.body = { sessions: filterPendingDeletedSessions(sessions.filter(s => s.source !== 'api_server')) } +} + export async function search(ctx: any) { if (useLocalSessionStore()) { const q = typeof ctx.query.q === 'string' ? ctx.query.q : '' @@ -207,6 +227,27 @@ export async function get(ctx: any) { ctx.body = { session } } +/** + * Get Hermes session detail only (exclude api_server source) + * GET /api/hermes/sessions/hermes/:id + */ +export async function getHermesSession(ctx: any) { + + const session = await hermesCli.getSession(ctx.params.id) + if (!session) { + ctx.status = 404 + ctx.body = { error: 'Session not found' } + return + } + // Filter out api_server sessions + if (session.source === 'api_server') { + ctx.status = 404 + ctx.body = { error: 'Session not found' } + return + } + ctx.body = { session } +} + export async function remove(ctx: any) { if (useLocalSessionStore()) { const sessionId = ctx.params.id diff --git a/packages/server/src/routes/hermes/sessions.ts b/packages/server/src/routes/hermes/sessions.ts index f8180de..d67dc22 100644 --- a/packages/server/src/routes/hermes/sessions.ts +++ b/packages/server/src/routes/hermes/sessions.ts @@ -7,6 +7,8 @@ sessionRoutes.get('/api/hermes/sessions/conversations', ctrl.listConversations) sessionRoutes.get('/api/hermes/sessions/conversations/:id/messages', ctrl.getConversationMessages) sessionRoutes.get('/api/hermes/sessions/conversations/:id/messages/paginated', ctrl.getConversationMessagesPaginated) sessionRoutes.get('/api/hermes/sessions', ctrl.list) +sessionRoutes.get('/api/hermes/sessions/hermes', ctrl.listHermesSessions) +sessionRoutes.get('/api/hermes/sessions/hermes/:id', ctrl.getHermesSession) sessionRoutes.get('/api/hermes/search/sessions', ctrl.search) sessionRoutes.get('/api/hermes/sessions/search', ctrl.search) sessionRoutes.get('/api/hermes/sessions/usage', ctrl.usageBatch)