In Hermes mode JBOT is a pure voice UI — memory is owned by the Agent:
- Backend: sys prompt excludes local memories when backend_mode=hermes
- Backend: config_view now returns backendMode so the frontend can adapt
- Frontend: App.jsx loads backendMode on mount via apiConfig()
- Frontend: memories not passed to apiChat() when isHermesMode
- Frontend: memory extraction (apiExtractMemory) skipped when isHermesMode
- Frontend: MemoryPanel hidden in Hermes mode; replaced by subtle
'🧠 Hermes Agent 模式 · 记忆由 Agent 管理' badge
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- New DB fields: hermes_url, hermes_api_key, backend_mode (openrouter/hermes)
- Migration 0002 adds the three fields to JbotApiConfig
- _do_hermes_chat(): streams /v1/chat/completions from Hermes Agent,
collects hermes.tool.progress SSE events, returns (final_text, tool_calls)
- chat_view branches on backend_mode; Hermes response includes toolCalls list
- Settings page: new '🧠 AI 后端模式' section — radio to toggle mode,
conditional Hermes URL + API key fields, LLM model field moved under
OpenRouter mode
- ChatPanel: ToolCallsList component renders collapsible '🔧 Hermes 使用了
N 个工具' widget before the bot reply — tool calls are NOT spoken via TTS
- CSS: .tool-calls, .tool-calls-* styles for the tool progress widget
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
right-footer served no purpose after hint text was removed. vbtn-wrap
is now a direct flex child of right-col: white bar at the bottom on
desktop, position:fixed on mobile — no stray white boxes anywhere.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Merged hint text + VoiceButton into a single .right-footer div
(flex column, white bg, border-top) so they form one solid footer bar
visually anchored to the bottom of the right column — no more floating
- .right-col gets min-height:0 so flex children can shrink properly
- .vbtn-wrap is now a transparent width:100% wrapper inside right-footer
(desktop); mobile keeps its own fixed-position overlay
- .right-footer { display: none } on mobile (fixed vbtn-wrap takes over)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove 'overflow:hidden' from html/body in index.css — it was blocking
scroll on all pages (Settings, History). The chat page is self-contained
via .app {height:100dvh; overflow:hidden} so body lock is unnecessary.
.app reverted back to height:100dvh (self-sufficient, not body-relative)
- Remove duplicate 历史记录 link from the right-column hint bar; it
already appears in the ChatPanel header, so three links was redundant
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Move MemoryPanel from bottom of left column to top of right column
(above chat messages, with a clean border-bottom separator)
- Move hint text ('按住 Space 或按钮说话 · 音色') from left column
to a compact bar on the right column between chat and voice button;
hidden on mobile to save space
- Remove 切换间隔 slider from left column; add it to Settings page
under 🤖 机器人身份 section with proper slider+value layout
- Add switchIntervalMs:10000 to DEFAULT_SETTINGS in settingsStore so
the value persists across sessions; App.jsx reads it on mount
- Left column is now much leaner: face + interaction buttons + micro
expression grid + auto-rotate toggle only
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Left column: scrollbar-width:none + ::-webkit-scrollbar:none to
hide the scrollbar chrome while keeping overflow-y:auto for tall
viewports; tightened padding (14→10px top, 20→14px bottom) and
panel/section gaps (14→10px, 12→8px, h3 margin 7→5px) to help
content fit without scrolling on typical laptop heights
- Header: added 🏠 home anchor linking to '/' (Links homepage) next
to the JBOT logo; styled as a small icon button
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- index.css targeted #root but app mounts to #jbot-root — viewport
height was never constrained, causing full page scroll
- Fix: rename #root → #jbot-root, add html/body {height:100%;overflow:hidden}
- .app now uses height:100% relative to #jbot-root instead of
duplicate 100dvh, so the layout is a true single-screen SPA
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
.chat-panel was using height:100% which consumed all right-col space,
pushing VoiceButton out of view (clipped by overflow:hidden).
Fix: switch .chat-panel to flex:1 + min-height:0 so it participates
in flex layout and leaves room for the VoiceButton below it.
Also moved VoiceButton component outside App() to avoid React
component-identity churn on every render.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Added VoiceButton component (inline in App.jsx) with full
mousedown/mouseup/mouseleave + touchstart/touchend/touchcancel support
- Shows context-aware label: 🎙 按住说话 / ⏳ 准备中 / 🔴 松开发送 / ⚙ 思考中 / 🔊 播放中
- Pulsing red animation while recording, amber pulse while acquiring mic
- Disabled (gray) while AI is thinking or speaking
- Desktop: pill button docked at bottom of chat panel (max-width 380px)
- Mobile: fixed to bottom of viewport with safe-area-inset support,
height 60px for easy thumb tap; right-col padded so messages aren't hidden
- Updated hint text and listening status to be input-agnostic
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Volcengine ASR server sends a WS close frame (opcode=8, code 1000,
'finish last sequence') after receiving the final audio chunk.
websocket-client fires on_error with this frame data instead of
(or in addition to) on_close — treat opcode=8 and 'already closed'
as normal termination, not an error.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WebSocketApp callbacks receive the WebSocketApp object, not WebSocket.
WebSocketApp has no send_binary() method — must use:
ws.send(data, ws_module.ABNF.OPCODE_BINARY)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- jbot/index.html: <script type="module"> required for Vite ES module output
(jbot.js uses import statements for the shared client chunk)
- base.html: add JBOT menu item with robot icon after Invest in dropdown
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add JbotApiConfig Django model (DB singleton) for storing
Volcengine/OpenRouter credentials set via Settings UI
- New endpoint GET/POST /api/jbot/api-config/ — tokens masked as ***
on read; only non-placeholder values are updated on write
- views.py: _get_cfg() helper reads DB first, env vars as fallback
- Settings.jsx: new 🔑 API 配置 section with show/hide token fields
for VOLC App ID, Access Token, OpenRouter API Key, LLM Model, ASR Resource
- api.js: apiGetApiConfig() / apiSaveApiConfig() client helpers
- Dockerfile: COPY jbot/ in both builder + production stages (was missing)
- entrypoint.sh: runs migrate --noinput before gunicorn (auto-creates table)
- k8s/manifest.yaml: remove jbot-credentials secretKeyRef (no Secret needed);
keep LLM_MODEL/VOLC_TTS_VOICE/VOLC_ASR_RESOURCE as optional env defaults
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The serializers.py file had two issues:
1. A second definition of TransactionSerializer that included a
non-existent 'price_per_share' field, overriding the correct one
and causing 500 errors on GET/POST /api/invest/transactions/.
2. Duplicate AIHoldingInputSerializer and AIUpdateSerializer blocks
that were redundant (same content as the first definitions).
Removed all duplicate class definitions, leaving only the correct
single definitions that match the Transaction model fields.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>