No description
  • Vue 59.8%
  • JavaScript 39.3%
  • CSS 0.6%
  • HTML 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-16 11:23:38 +08:00
public init 2026-09-13 15:57:18 +08:00
src 调整气泡渲染逻辑 2026-09-16 11:23:38 +08:00
.env.development init 2026-09-13 15:57:18 +08:00
.env.example init 2026-09-13 15:57:18 +08:00
.env.production init 2026-09-13 15:57:18 +08:00
.gitignore init 2026-09-13 15:57:18 +08:00
eslint.config.js init 2026-09-13 15:57:18 +08:00
index.html init 2026-09-13 15:57:18 +08:00
jsconfig.json init 2026-09-13 15:57:18 +08:00
package-lock.json init 2026-09-13 15:57:18 +08:00
package.json init 2026-09-13 15:57:18 +08:00
README.md 空的 message.delta / message.complete不再渲染出气泡 2026-09-15 17:07:35 +08:00
uno.config.js init 2026-09-13 15:57:18 +08:00
vite.config.js init 2026-09-13 15:57:18 +08:00

hermes_chat_spa

类比 Hermes Dashboard 的轻量对话壳:直连 Dashboard /api/ws(不经能碳转发聊天流)。
供能碳管理端 iframe 嵌入(entry=embedded)或独立打开(standalone)。

大屏对话已内嵌在 yuanzhou_ui 大屏页,经 unicode → BFF handoff → TUI,不再走本 SPA。

入口

模式 何时用 行为
standalone(默认) 本机直接打开 SPA 登录页输账号密码 → 左侧列出全部 profile → 用户点选后开聊
embedded 内嵌能碳管理端 能碳 BFF handoff 注入,自动开聊
# .env
VITE_HERMES_ENTRY_MODE=standalone   # 或 embedded

本地启动(standalone)

cd hermes_chat_spa
cp .env.example .env.development
npm install
npm run dev
# http://localhost:3210/login

只需配置 Dashboard 代理目标,不要把账号密码写进 .env。

能碳内嵌(embedded,后续)

能碳 application-*.yml 配置账号/密码/锁定 profile,构建或运行 SPA 时注入:

VITE_HERMES_ENTRY_MODE=embedded
VITE_HERMES_USERNAME=...
VITE_HERMES_PASSWORD=...
VITE_HERMES_PROFILE=yzzf-admin

iframe 打开 /chat 即可。

按住语音识别(STT)

SPA:按住麦克风 → MediaRecorder → POST /__hermes/api/audio/transcribe(Dashboard Cookie)→ 文本填入输入框。

安全上下文:浏览器只允许在 HTTPS 或 localhost 调用麦克风。

环境 怎么打开 说明
本机开发 http://localhost:3210 localhost 即为安全上下文
工控机 / 测试 SPA https://spa.bingjun360.com 工控机 nginx 已配 SSL;HTTP 会 301 到 HTTPS
纯 http://IP 或未开 TLS 的域名 不可用 会报「语音识别需要 HTTPS」,前端无法绕过

nginx 参考配置:deploy/nginx/spa.bingjun360.com.conf(80 跳转 + 443;证书路径以工控机实际为准)。

鉴权:依赖 Dashboard 登录 Cookie(standalone 登录后可用)。能碳嵌入若只有一次性 ticket、无 Cookie,会 401——需独立站先登录,或后续加 BFF 代转录。

Hermes 侧配置(Dashboard 进程所用 profile 的 config.yaml)

stt:
  enabled: true
  # 中文务必设 zh;默认 en 容易把短句听成英文
  language: "zh"
  # 推荐其一:local | groq | openai
  provider: "groq"          # 或 local / openai
  local:
    model: "base"           # 仅 provider=local
    language: "zh"
    vad: true
  groq:
    language: "zh"
  openai:
    model: "whisper-1"
    language: "zh"

对应 .env(与 provider 匹配):

# groq
GROQ_API_KEY=...

# openai(语音专用优先)
VOICE_TOOLS_OPENAI_KEY=...
# 或 OPENAI_API_KEY=...

# local
# pip install faster-whisper

改完后重启 hermes-dashboard(跑 /api/audio/transcribe 的进程)。可用:

curl -sS -b 'cookie.txt' -H 'Content-Type: application/json' \
  -d '{"data_url":"data:audio/webm;base64,...","mime_type":"audio/webm"}' \
  http://127.0.0.1:9119/api/audio/transcribe

nginx 的 /__hermes/ 已反代到 9119 即可覆盖该路径。

产物下载(MEDIA)

助手按 SOUL 约定在最终回复中单独一行写出:

MEDIA:/opt/data/profiles/<profile>/outputs/report.docx

或单独一行带扩展名的绝对路径。SPA 会剥离该行并显示「下载」按钮,请求:

GET /__hermes/api/files/download?path=...(Dashboard Cookie)。

Docker / 路径要点

角色 路径含义
Agent 落盘 容器内 HERMES_HOME=/opt/data → /opt/data/profiles/<name>/outputs/...
宿主机卷 ${HERMES_DATA_DIR}(如 /opt/data/yuanzhou-hermes)挂到容器 /opt/data
SPA / nginx 不要用宿主机路径;下载 API 跑在 Dashboard 进程里,只认容器可见路径

因此 MEDIA: 必须发 /opt/data/...,不能发 /opt/data/yuanzhou-hermes/...。Dashboard 与 gateway 需共用同一 /opt/data 挂载(同容器或同卷),否则 404。

鉴权:与 STT 相同,依赖 Dashboard Cookie;嵌入态仅 ticket 时会 401。

工控机部署(standalone SPA)

  1. npm run build(VITE_PUBLIC_PATH=/),同步 dist/* → /home/webapps/hermes-chat-spa/dist/(或站点 root 约定目录)
  2. nginx:宿主机 spa.conf 将 /__hermes/ 反代到本机 Dashboard :9119(与独立 hermes-dashboard 容器一致;Gateway 仍为 :8643)
  3. HTTPS:浏览器须以 HTTPS 打开 SPA(语音依赖安全上下文)。工控机测试常见为边缘/公网 Nginx 终结 TLS,内网只听 80;生产同机终结见 deploy/nginx/spa.bingjun360.com.conf 与变更文档
  4. 生产变更逐步指令:deploy/docs/生产环境-Hermes-Dashboard独立与SPA-HTTPS变更.md
  5. 改证书或站点后:nginx -t && nginx -s reload

对话循环

  1. POST /auth/password-login(Cookie)
  2. GET /api/profiles(standalone 选 profile)
  3. POST /api/auth/ws-ticket → ws://…/api/ws?ticket=
  4. session.create({ profile }) → prompt.submit

压缩后续接:live session_id 不变;stored_session_id 由 session.info 更新。