feat(hermes): Batch 1 §E - Sessions 导出(走 dashboard /api/sessions/{id}/messages)

校对稿订正:不走 CLI `hermes sessions export`,直接调 dashboard 9119 HTTP API。

## 后端
- 新 Tauri 命令 hermes_session_export(session_id):
  · GET http://127.0.0.1:{dashboard_port}/api/sessions/{id}/messages
  · 拿原始 JSON 返回前端
  · 错误提示「请先启动 Dashboard」(dashboard server 必须运行)

## 前端
- tauri-api.js: hermesSessionExport wrapper
- sessions.js: 详情面板「打开会话 / Pin / 导出 / 删除」并列布局
  · 点导出 → Blob + URL.createObjectURL + a.download 浏览器下载 hermes-session-{id}.json
  · toast 成功/失败
- dev-api.js: Web 模式 handler 同步调 dashboard 端口

## i18n
- sessionsExport / sessionsExportSuccess / sessionsExportFailed × 3 语言
This commit is contained in:
晴天
2026-05-14 04:54:25 +08:00
parent 832bb9a6ef
commit 112963b2b7
6 changed files with 83 additions and 0 deletions

View File

@@ -249,6 +249,7 @@ export function render() {
<div class="hm-session-detail-actions">
<button class="hm-sessions-btn" id="hm-session-open-chat">${icon('message-circle', 14)}${escHtml(t('engine.sessionsOpenChat'))}</button>
${canPin ? `<button class="hm-sessions-btn" id="hm-session-pin">${icon(store.state.pinned.has(session.id) ? 'crown' : 'target', 14)}${escHtml(store.state.pinned.has(session.id) ? t('engine.sessionsUnpin') : t('engine.sessionsPin'))}</button>` : ''}
<button class="hm-sessions-btn" id="hm-session-export" data-session-id="${escAttr(session.id)}">${icon('download', 14)}${escHtml(t('engine.sessionsExport'))}</button>
<button class="hm-sessions-btn is-danger" id="hm-session-delete" data-session-key="${escAttr(key)}">${icon('trash', 14)}${escHtml(t('engine.chatDeleteSession'))}</button>
</div>
</div>
@@ -454,6 +455,31 @@ export function render() {
el.querySelector('#hm-session-delete')?.addEventListener('click', async () => {
await deleteOne(currentSession())
})
// Batch 1 §E: 会话导出
el.querySelector('#hm-session-export')?.addEventListener('click', async (e) => {
const sid = e.currentTarget.dataset.sessionId
if (!sid) return
const btn = e.currentTarget
btn.disabled = true
try {
const data = await api.hermesSessionExport(sid)
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `hermes-session-${sid}.json`
document.body.appendChild(a)
a.click()
a.remove()
URL.revokeObjectURL(url)
toast(t('engine.sessionsExportSuccess'), 'success')
} catch (err) {
toast(t('engine.sessionsExportFailed') + ': ' + (err?.message || err), 'error')
} finally {
btn.disabled = false
}
})
}
async function init() {

View File

@@ -478,6 +478,8 @@ export const api = {
// Batch 1 §D + §C-bis: 真正中断 + Approval Flow用 run_id
hermesRunStop: (runId) => invoke('hermes_run_stop', { runId }),
hermesRunApproval: (runId, choice) => invoke('hermes_run_approval', { runId, choice }),
// Batch 1 §E: 会话消息导出(走 dashboard /api/sessions/{id}/messages
hermesSessionExport: (sessionId) => invoke('hermes_session_export', { sessionId }),
hermesReadConfig: () => invoke('hermes_read_config'),
hermesReadConfigFull: () => invoke('hermes_read_config_full'),
hermesLazyDepsFeatures: () => cachedInvoke('hermes_lazy_deps_features', {}, 600000),

View File

@@ -437,6 +437,10 @@ export default {
hermesConfigStatusSaving: _('保存中…', 'Saving…', '儲存中…'),
hermesConfigStatusLoading: _('加载中…', 'Loading…', '載入中…'),
hermesConfigStatusReady: _('raw yaml 编辑器', 'raw yaml editor', 'raw yaml 編輯器'),
// Batch 1 §E: 会话导出
sessionsExport: _('导出', 'Export', '匯出'),
sessionsExportSuccess: _('已导出', 'Exported', '已匯出'),
sessionsExportFailed: _('导出失败', 'Export failed', '匯出失敗'),
// 停止流式
chatStop: _('停止', 'Stop', '停止'),
chatStopped: _('已停止当前回复', 'Run stopped', '已停止目前回覆'),