2026-04-08 14:55:03 +00:00
// ── i18n: locale bundles and t() helper ──────────────────────────────────────
// To add a new language: add an entry to LOCALES below with all keys translated.
// The language code must match a valid BCP 47 tag (used for speech recognition).
// Keys missing in a non-English locale fall back to English automatically.
const LOCALES = {
en : {
_lang : 'en' ,
_label : 'English' ,
_speech : 'en-US' ,
// boot.js
cancelling : 'Cancelling\u2026' ,
cancel _failed : 'Cancel failed: ' ,
mic _denied : 'Microphone access denied. Check browser permissions.' ,
mic _no _speech : 'No speech detected. Try again.' ,
mic _network : 'Speech recognition unavailable.' ,
mic _error : 'Voice input error: ' ,
session _imported : 'Session imported' ,
import _failed : 'Import failed: ' ,
import _invalid _json : 'Invalid JSON' ,
image _pasted : 'Image pasted: ' ,
// messages.js
edit _message : 'Edit message' ,
regenerate : 'Regenerate response' ,
copy : 'Copy' ,
copied : 'Copied!' ,
you : 'You' ,
thinking : 'Thinking' ,
expand _all : 'Expand all' ,
collapse _all : 'Collapse all' ,
edit _failed : 'Edit failed: ' ,
regen _failed : 'Regenerate failed: ' ,
reconnect _active : 'A response is still being generated. Reload when ready?' ,
reconnect _finished : 'A response was in progress when you last left. Messages may have updated.' ,
fix: surface approval prompt in UI instead of getting stuck in Thinking (#187)
* fix: surface approval prompt in UI instead of getting stuck in Thinking
When a dangerous command was detected during streaming, the approval system
would call submit_pending() but no SSE 'approval' event would be emitted to
the frontend. The agent thread either blocked indefinitely (gateway path) or
returned an approval_required status the UI never saw (EXEC_ASK path). Either
way the chat UI stayed stuck in 'Thinking...' with no prompt shown.
Root cause: streaming.py used HERMES_EXEC_ASK=1 but never registered a
register_gateway_notify() callback. Without it, check_all_command_guards()
fell back to the legacy polling path (submit_pending only), which relies on
on_tool() polling -- but on_tool() fires *before* the tool runs, so by the
time the terminal tool detected the dangerous command and called submit_pending,
the approval event had already missed its window.
Fix (streaming.py):
- Register a gateway-style notify_cb via register_gateway_notify() before the
agent runs. The callback calls put('approval', ...) to emit the SSE event
the moment a dangerous command is detected, regardless of on_tool() timing.
- Unregister via unregister_gateway_notify() in the finally block to unblock
any threads still waiting if the stream ends or is cancelled mid-approval.
- Keep the on_tool() fallback poll for older approval module versions.
Fix (routes.py):
- Import and call resolve_gateway_approval() in _handle_approval_respond().
This unblocks the agent thread parked in entry.event.wait() when the user
clicks Allow or Deny in the UI. Without this call the thread would block
until the 5-minute gateway timeout.
Tests (tests/test_approval_unblock.py):
- 16 new tests covering: resolve_gateway_approval() event signalling, deny/
session/once choices, resolve_all, notify_cb registration/firing/cleanup,
unregister signals blocked entries, full end-to-end streaming simulation,
module symbol exports, and HTTP endpoint regressions.
515 tests pass (499 existing + 16 new).
* feat: full approval UI — i18n buttons, keyboard shortcut, loading state, scoping fix
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-09 03:16:22 +00:00
// approval card
approval _heading : 'Approval required' ,
approval _desc _prefix : 'Dangerous command detected' ,
approval _btn _once : 'Allow once' ,
approval _btn _once _title : 'Allow this one command (Enter)' ,
approval _btn _session : 'Allow session' ,
approval _btn _session _title : 'Allow for this conversation session' ,
approval _btn _always : 'Always allow' ,
approval _btn _always _title : 'Always allow this command pattern' ,
approval _btn _deny : 'Deny' ,
approval _btn _deny _title : 'Deny — do not run this command' ,
approval _responding : 'Responding\u2026' ,
2026-04-08 14:55:03 +00:00
untitled : 'Untitled' ,
n _messages : ( n ) => ` ${ n } messages ` ,
model _unavailable : ' (unavailable)' ,
model _unavailable _title : 'This model is no longer in your current provider list' ,
fix: warn on provider/model mismatch, surface auth errors (#266)
* fix: warn on provider/model mismatch, surface auth errors (#266)
Fixes #266 — WebUI silently ignores provider/model selection mismatch.
The problem: selecting an OpenRouter (or Anthropic/OpenAI) model while
Hermes is configured for a different provider (e.g. local Ollama) sends
the request to the wrong endpoint, which returns a 401 Unauthorized error
with no UI indication of why.
Three-layer fix:
1. api/streaming.py — detect 401/auth errors explicitly
Added is_auth_error detection covering '401', 'AuthenticationError',
'authentication', 'unauthorized', 'invalid api key', and the specific
Ollama error string 'no cookie auth credentials'. Auth errors emit
apperror with type='auth_mismatch' and a hint pointing to 'hermes model'.
2. static/ui.js — expose active_provider and warn on selection
- populateModelDropdown() stores data.active_provider from /api/models
as window._activeProvider (the field was already in the response but
the frontend never used it)
- New _checkProviderMismatch(modelId) helper: compares the selected
model's slash-prefix (e.g. 'openai/' from 'openai/gpt-4o') against
the active provider. Skips the check for 'openrouter' and 'custom'
to avoid false positives on configs that legitimately route any model.
3. static/boot.js — warn on model dropdown change
modelSelect.onchange calls _checkProviderMismatch() and shows a toast
when the selected model looks incompatible with the configured provider.
4. static/messages.js — distinct UI label for auth errors
apperror handler now distinguishes type='auth_mismatch' and shows
'Provider mismatch' as the error label instead of 'Error'.
5. static/i18n.js — provider_mismatch_warning and provider_mismatch_label
keys added to all 5 locales (en, es, de, zh-Hans, zh-Hant).
Tests: 21 new tests in tests/test_provider_mismatch.py covering all
five change areas. 679/679 total pass (658 baseline + 21 new).
* fix: t() call args spread + use i18n label for auth mismatch
1. ui.js: _checkProviderMismatch passed [modelId, ap] as a single
array arg to t(). Since t(key, ...args) spreads, the function
received the array as m and undefined as p. Fixed to pass as
separate args: t('provider_mismatch_warning', modelId, ap).
2. messages.js: 'Provider mismatch' label was hardcoded instead of
using t('provider_mismatch_label'). Now uses the i18n key with
fallback for when t() isn't available.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 04:25:18 +00:00
provider _mismatch _warning : ( m , p ) => ` " ${ m } " may not work with your configured provider ( ${ p } ). Send anyway, or run \` hermes model \` in your terminal to switch. ` ,
provider _mismatch _label : 'Provider mismatch' ,
2026-04-14 20:56:56 +00:00
model _custom _label : 'Custom model ID' ,
model _custom _placeholder : 'e.g. openai/gpt-5.4' ,
2026-04-08 14:55:03 +00:00
// commands.js
cmd _help : 'List available commands' ,
cmd _clear : 'Clear conversation messages' ,
cmd _compact : 'Compress conversation context' ,
cmd _model : 'Switch model (e.g. /model gpt-4o)' ,
cmd _workspace : 'Switch workspace by name' ,
cmd _new : 'Start a new chat session' ,
cmd _usage : 'Toggle token usage display on/off' ,
cmd _theme : 'Switch theme (dark/light/slate/solarized/monokai/nord/oled)' ,
cmd _personality : 'Switch agent personality' ,
v0.47.0: dialogs, session menu, /skills, mobile fixes, mobile QA suite
* fix: custom provider with slash model name no longer rerouted to OpenRouter (#255)
When base_url is configured in config.yaml, resolve_model_provider() now
trusts the configured provider/base_url entirely and skips the slash-based
OpenRouter heuristic. Fixes google/gemma-4-26b-a4b with provider:custom
being silently routed to OpenRouter, resulting in 401 errors.
Fixes #230
* test: mobile layout regression suite — 14 tests for every QA run (#254)
Adds tests/test_mobile_layout.py with 14 static regression tests that run
on every QA pass to catch mobile layout breakage before it reaches prod.
Covers: breakpoints at 900px/640px, right panel slide-over CSS, mobile
overlay, bottom nav, files button, profile dropdown z-index, chip overflow,
workspace close, 100dvh, 44px touch targets, 16px font-size on textarea.
* feat: /skills slash command lists and filters available Hermes skills (#257)
Adds /skills [query] command to commands.js. Fetches from /api/skills,
groups by category (alphabetically sorted), displays as a formatted
assistant message. Optional query filters by name, description, or category.
i18n keys added for en, de, zh, zh-Hant. 1 regression test added.
Fixes #248
* feat: shared app dialogs replace native confirm()/prompt() calls (#251)
Adds showConfirmDialog() and showPromptDialog() helpers to ui.js, backed
by a themed #appDialogOverlay. Replaces all 11 native browser confirm/prompt
call sites across panels.js, sessions.js, ui.js, workspace.js.
Supports: danger mode, keyboard focus trap (Tab/Escape/Enter), focus restore,
ARIA roles, mobile-responsive stacked buttons at 640px. i18n for en/de/zh/zh-Hant.
5 new tests in test_sprint33.py verify markup, CSS, helpers, and absence of
native dialog calls.
Extracted from PR #242.
* fix: Android Chrome mobile — workspace panel close + profile dropdown (#256)
Fix #247: toggleMobileFiles() now shows/hides the mobile overlay when
toggling the right workspace panel. New closeMobileFiles() helper closes
the panel with correct overlay state tracking. Overlay onclick calls both
closeMobileSidebar() and closeMobileFiles(). Mobile-only close button (x)
added to workspace panel header.
Fix #246: profile dropdown uses position:fixed;top:56px;right:8px at
max-width:900px, escaping the overflow-x:auto stacking context that was
clipping it on Android Chrome.
Fix applied during review: closeMobileSidebar() now checks if the right
panel is still open before hiding the overlay, preventing the overlay from
disappearing when only the sidebar is closed.
Fixes #247 Fixes #246
* feat: session ⋯ action dropdown replaces per-row buttons (#252)
Replaces the 5 per-row hover action buttons (pin/move/archive/duplicate/trash)
with a single ⋯ trigger that opens a positioned dropdown menu. Menu has full
keyboard (Escape), click-outside, scroll, and resize-reposition handling.
Position:fixed prevents sidebar clipping.
5 actions: Pin/Unpin, Move to project, Archive/Unarchive, Duplicate, Delete
(danger style). Each with icon and descriptive subtitle.
Updated test_sprint16.py: test_sessions_js_uses_action_menu_not_per_row_buttons
asserts the new trigger and menu functions exist, old per-row classes are gone.
Extracted from PR #242.
* docs: v0.47.0 release notes, bump version, update test counts (645)
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 19:19:12 +00:00
cmd _skills : 'List available Hermes skills' ,
2026-04-08 14:55:03 +00:00
available _commands : 'Available commands:' ,
type _slash : 'Type / to see commands' ,
conversation _cleared : 'Conversation cleared' ,
model _usage : 'Usage: /model <name>' ,
no _model _match : 'No model matching "' ,
switched _to : 'Switched to ' ,
workspace _usage : 'Usage: /workspace <name>' ,
no _workspace _match : 'No workspace matching "' ,
switched _workspace : 'Switched to workspace: ' ,
workspace _switch _failed : 'Workspace switch failed: ' ,
new _session : 'New session created' ,
compressing : 'Requesting context compression...' ,
token _usage _on : 'Token usage on' ,
token _usage _off : 'Token usage off' ,
theme _usage : 'Usage: /theme ' ,
theme _set : 'Theme: ' ,
no _active _session : 'No active session' ,
no _personalities : 'No personalities found (add them to ~/.hermes/personalities/)' ,
available _personalities : 'Available personalities:' ,
personality _switch _hint : '\n\nUse `/personality <name>` to switch, or `/personality none` to clear.' ,
personalities _load _failed : 'Failed to load personalities' ,
personality _cleared : 'Personality cleared' ,
personality _set : 'Personality: ' ,
failed _colon : 'Failed: ' ,
// ui.js
no _workspace : 'No workspace' ,
feat: composer-centric UI refresh + Hermes Control Center (v0.50.0, closes #242)
* Polish workspace panel behavior and app dialogs
* Replace remaining emoji UI glyphs with Lucide icons
* Redesign composer footer around model and context controls
Move the model selector into the composer footer, replace the linear context pill with a compact circular badge plus tooltip, and remove the redundant topbar model pill.
Design credit and inspiration: Theo / T3 Code.
Reference implementation: https://github.com/pingdotgg/t3code/
* Remove obsolete activity bar
Drop the old activity bar, keep turn-scoped state in the composer footer, and route remaining non-chat status messages through toasts.
This leaves live tool cards and the message timeline as the primary progress UI, with the composer owning stop/cancel and brief turn status.
* Move workspace and model switching into composer footer
* Move profile switching into composer footer
* Refactor Hermes control center UI
* Redesign control center settings modal layout
Widen the modal to 860px, simplify the tab list to icon+label rows,
stretch the tab column's divider to full height, lock the panel to a
fixed height so switching tabs no longer resizes the outer shell, and
always open on the Conversation tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Put session item actions in a dropdown
* Use Hermes mark in sidebar control button
* Reset control center section on close
* Drop session-item left border indicator
Remove the left-border accent used for active, CLI, and project rows —
each state already has a dedicated cue (gold fill, cli badge, project
dot), so the border was redundant. Fully round the row, add 2px
bottom spacing between rows, and strip the matching JS/CSS overrides.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Increase session search input vertical padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Normalise odd pixel values across UI
Snap padding, gap, and border-radius values to the 2/4/6/8/10/12 grid
across composer chips, sidebar panels, cron list, settings, approval
buttons, dropdowns, and inline message edit — eliminating the 7/9/11px
drift that was making sibling elements feel subtly misaligned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add missing #btnMobileFiles button and .mobile-files-btn CSS (for mobile QA suite)
The mobile layout regression suite (test_mobile_layout.py) requires:
- #btnMobileFiles onclick=toggleMobileFiles() in topbar chips
- .mobile-files-btn CSS rules for responsive show/hide at 640/900px breakpoints
Also adds max-width guard to .profile-dropdown to prevent clipping at narrow viewports.
* Improve composer footer mobile responsiveness and UX
- Collapse composer chips to icon-only at <=400px viewports
- Add model chip icon (CPU) so it remains tappable when labels are hidden
- Show send button always (disabled state when empty, hidden during streaming)
- Show context usage indicator on session load, not just after streaming
- Add cancel status fallback timeout to prevent stale "Cancelling..." text
- Update tests to match new send button and busy state behavior
* Fix duplicate files button and broken workspace close on mobile
Remove redundant #btnMobileFiles button that duplicated #btnWorkspacePanelToggle
in the mobile topbar. Fix workspace panel close button calling undefined
closeMobileFiles() — now calls closeWorkspacePanel().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix model chip icon vertical alignment in composer footer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix workspace toggle button hidden on desktop by conflicting CSS class
Remove mobile-files-btn class from #btnWorkspacePanelToggle — its
display:none!important rule was overriding workspace-toggle-btn visibility
on non-mobile viewports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix session actions dots button inaccessible on mobile sidebar
Always show the session actions trigger on mobile (no hover state on
touch devices) and restore right padding so text truncates with
ellipsis before the dots icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer footer manage links not opening sidebar panel
The "Manage profiles" and "Manage workspaces" links in the composer
footer dropdowns called switchPanel() which only changes the active
panel content but doesn't open the sidebar. Replaced with
mobileSwitchPanel() which also opens the sidebar so the panel is
actually visible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Widen icon-only composer chips breakpoint from 400px to 768px
Move the icon-only chip styling up into the existing max-width:768px
media query so chips collapse to icon-only on tablets too, preventing
composer footer overflow on mid-size screens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer-left vertical scrollbar by setting overflow-y:hidden
When overflow-x is set to auto, the CSS spec implicitly changes
overflow-y from visible to auto, allowing a vertical scrollbar to
appear from slight chip padding/border overflow. Explicitly set
overflow-y:hidden to prevent this.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve rebase conflicts and fix control center test assertions
- Resolved 4 conflicts during rebase onto master (workspace.js,
boot.js, index.html, test_sprint34.py)
- Fixed test_sprint34.py: _controlSection -> _settingsSection,
cc-tab -> settings-tabs (matching actual implementation)
- Fixed quoting syntax error in test assertion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update version badge in System tab to v0.49.4
* docs: update README and CHANGELOG for v0.50.0 UI refresh, bump version badge
---------
Co-authored-by: Aron Prins <pwf.aron@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 18:55:40 +00:00
dialog _confirm _title : 'Confirm action' ,
dialog _prompt _title : 'Enter a value' ,
dialog _confirm _btn : 'Confirm' ,
2026-04-08 14:55:03 +00:00
// workspace.js
unsaved _confirm : 'You have unsaved changes in the preview. Discard and navigate?' ,
feat: composer-centric UI refresh + Hermes Control Center (v0.50.0, closes #242)
* Polish workspace panel behavior and app dialogs
* Replace remaining emoji UI glyphs with Lucide icons
* Redesign composer footer around model and context controls
Move the model selector into the composer footer, replace the linear context pill with a compact circular badge plus tooltip, and remove the redundant topbar model pill.
Design credit and inspiration: Theo / T3 Code.
Reference implementation: https://github.com/pingdotgg/t3code/
* Remove obsolete activity bar
Drop the old activity bar, keep turn-scoped state in the composer footer, and route remaining non-chat status messages through toasts.
This leaves live tool cards and the message timeline as the primary progress UI, with the composer owning stop/cancel and brief turn status.
* Move workspace and model switching into composer footer
* Move profile switching into composer footer
* Refactor Hermes control center UI
* Redesign control center settings modal layout
Widen the modal to 860px, simplify the tab list to icon+label rows,
stretch the tab column's divider to full height, lock the panel to a
fixed height so switching tabs no longer resizes the outer shell, and
always open on the Conversation tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Put session item actions in a dropdown
* Use Hermes mark in sidebar control button
* Reset control center section on close
* Drop session-item left border indicator
Remove the left-border accent used for active, CLI, and project rows —
each state already has a dedicated cue (gold fill, cli badge, project
dot), so the border was redundant. Fully round the row, add 2px
bottom spacing between rows, and strip the matching JS/CSS overrides.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Increase session search input vertical padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Normalise odd pixel values across UI
Snap padding, gap, and border-radius values to the 2/4/6/8/10/12 grid
across composer chips, sidebar panels, cron list, settings, approval
buttons, dropdowns, and inline message edit — eliminating the 7/9/11px
drift that was making sibling elements feel subtly misaligned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add missing #btnMobileFiles button and .mobile-files-btn CSS (for mobile QA suite)
The mobile layout regression suite (test_mobile_layout.py) requires:
- #btnMobileFiles onclick=toggleMobileFiles() in topbar chips
- .mobile-files-btn CSS rules for responsive show/hide at 640/900px breakpoints
Also adds max-width guard to .profile-dropdown to prevent clipping at narrow viewports.
* Improve composer footer mobile responsiveness and UX
- Collapse composer chips to icon-only at <=400px viewports
- Add model chip icon (CPU) so it remains tappable when labels are hidden
- Show send button always (disabled state when empty, hidden during streaming)
- Show context usage indicator on session load, not just after streaming
- Add cancel status fallback timeout to prevent stale "Cancelling..." text
- Update tests to match new send button and busy state behavior
* Fix duplicate files button and broken workspace close on mobile
Remove redundant #btnMobileFiles button that duplicated #btnWorkspacePanelToggle
in the mobile topbar. Fix workspace panel close button calling undefined
closeMobileFiles() — now calls closeWorkspacePanel().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix model chip icon vertical alignment in composer footer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix workspace toggle button hidden on desktop by conflicting CSS class
Remove mobile-files-btn class from #btnWorkspacePanelToggle — its
display:none!important rule was overriding workspace-toggle-btn visibility
on non-mobile viewports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix session actions dots button inaccessible on mobile sidebar
Always show the session actions trigger on mobile (no hover state on
touch devices) and restore right padding so text truncates with
ellipsis before the dots icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer footer manage links not opening sidebar panel
The "Manage profiles" and "Manage workspaces" links in the composer
footer dropdowns called switchPanel() which only changes the active
panel content but doesn't open the sidebar. Replaced with
mobileSwitchPanel() which also opens the sidebar so the panel is
actually visible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Widen icon-only composer chips breakpoint from 400px to 768px
Move the icon-only chip styling up into the existing max-width:768px
media query so chips collapse to icon-only on tablets too, preventing
composer footer overflow on mid-size screens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer-left vertical scrollbar by setting overflow-y:hidden
When overflow-x is set to auto, the CSS spec implicitly changes
overflow-y from visible to auto, allowing a vertical scrollbar to
appear from slight chip padding/border overflow. Explicitly set
overflow-y:hidden to prevent this.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve rebase conflicts and fix control center test assertions
- Resolved 4 conflicts during rebase onto master (workspace.js,
boot.js, index.html, test_sprint34.py)
- Fixed test_sprint34.py: _controlSection -> _settingsSection,
cc-tab -> settings-tabs (matching actual implementation)
- Fixed quoting syntax error in test assertion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update version badge in System tab to v0.49.4
* docs: update README and CHANGELOG for v0.50.0 UI refresh, bump version badge
---------
Co-authored-by: Aron Prins <pwf.aron@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 18:55:40 +00:00
discard : 'Discard' ,
2026-04-08 14:55:03 +00:00
save : 'Save' ,
edit : 'Edit' ,
feat: composer-centric UI refresh + Hermes Control Center (v0.50.0, closes #242)
* Polish workspace panel behavior and app dialogs
* Replace remaining emoji UI glyphs with Lucide icons
* Redesign composer footer around model and context controls
Move the model selector into the composer footer, replace the linear context pill with a compact circular badge plus tooltip, and remove the redundant topbar model pill.
Design credit and inspiration: Theo / T3 Code.
Reference implementation: https://github.com/pingdotgg/t3code/
* Remove obsolete activity bar
Drop the old activity bar, keep turn-scoped state in the composer footer, and route remaining non-chat status messages through toasts.
This leaves live tool cards and the message timeline as the primary progress UI, with the composer owning stop/cancel and brief turn status.
* Move workspace and model switching into composer footer
* Move profile switching into composer footer
* Refactor Hermes control center UI
* Redesign control center settings modal layout
Widen the modal to 860px, simplify the tab list to icon+label rows,
stretch the tab column's divider to full height, lock the panel to a
fixed height so switching tabs no longer resizes the outer shell, and
always open on the Conversation tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Put session item actions in a dropdown
* Use Hermes mark in sidebar control button
* Reset control center section on close
* Drop session-item left border indicator
Remove the left-border accent used for active, CLI, and project rows —
each state already has a dedicated cue (gold fill, cli badge, project
dot), so the border was redundant. Fully round the row, add 2px
bottom spacing between rows, and strip the matching JS/CSS overrides.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Increase session search input vertical padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Normalise odd pixel values across UI
Snap padding, gap, and border-radius values to the 2/4/6/8/10/12 grid
across composer chips, sidebar panels, cron list, settings, approval
buttons, dropdowns, and inline message edit — eliminating the 7/9/11px
drift that was making sibling elements feel subtly misaligned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add missing #btnMobileFiles button and .mobile-files-btn CSS (for mobile QA suite)
The mobile layout regression suite (test_mobile_layout.py) requires:
- #btnMobileFiles onclick=toggleMobileFiles() in topbar chips
- .mobile-files-btn CSS rules for responsive show/hide at 640/900px breakpoints
Also adds max-width guard to .profile-dropdown to prevent clipping at narrow viewports.
* Improve composer footer mobile responsiveness and UX
- Collapse composer chips to icon-only at <=400px viewports
- Add model chip icon (CPU) so it remains tappable when labels are hidden
- Show send button always (disabled state when empty, hidden during streaming)
- Show context usage indicator on session load, not just after streaming
- Add cancel status fallback timeout to prevent stale "Cancelling..." text
- Update tests to match new send button and busy state behavior
* Fix duplicate files button and broken workspace close on mobile
Remove redundant #btnMobileFiles button that duplicated #btnWorkspacePanelToggle
in the mobile topbar. Fix workspace panel close button calling undefined
closeMobileFiles() — now calls closeWorkspacePanel().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix model chip icon vertical alignment in composer footer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix workspace toggle button hidden on desktop by conflicting CSS class
Remove mobile-files-btn class from #btnWorkspacePanelToggle — its
display:none!important rule was overriding workspace-toggle-btn visibility
on non-mobile viewports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix session actions dots button inaccessible on mobile sidebar
Always show the session actions trigger on mobile (no hover state on
touch devices) and restore right padding so text truncates with
ellipsis before the dots icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer footer manage links not opening sidebar panel
The "Manage profiles" and "Manage workspaces" links in the composer
footer dropdowns called switchPanel() which only changes the active
panel content but doesn't open the sidebar. Replaced with
mobileSwitchPanel() which also opens the sidebar so the panel is
actually visible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Widen icon-only composer chips breakpoint from 400px to 768px
Move the icon-only chip styling up into the existing max-width:768px
media query so chips collapse to icon-only on tablets too, preventing
composer footer overflow on mid-size screens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer-left vertical scrollbar by setting overflow-y:hidden
When overflow-x is set to auto, the CSS spec implicitly changes
overflow-y from visible to auto, allowing a vertical scrollbar to
appear from slight chip padding/border overflow. Explicitly set
overflow-y:hidden to prevent this.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve rebase conflicts and fix control center test assertions
- Resolved 4 conflicts during rebase onto master (workspace.js,
boot.js, index.html, test_sprint34.py)
- Fixed test_sprint34.py: _controlSection -> _settingsSection,
cc-tab -> settings-tabs (matching actual implementation)
- Fixed quoting syntax error in test assertion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update version badge in System tab to v0.49.4
* docs: update README and CHANGELOG for v0.50.0 UI refresh, bump version badge
---------
Co-authored-by: Aron Prins <pwf.aron@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 18:55:40 +00:00
clear : 'Clear' ,
create : 'Create' ,
remove : 'Remove' ,
2026-04-08 14:55:03 +00:00
save _title : 'Save changes' ,
edit _title : 'Edit this file' ,
saved : 'Saved' ,
save _failed : 'Save failed: ' ,
image _load _failed : 'Could not load image' ,
file _open _failed : 'Could not open file' ,
downloading : ( name ) => ` Downloading ${ name } \u 2026 ` ,
double _click _rename : 'Double-click to rename' ,
renamed _to : 'Renamed to ' ,
rename _failed : 'Rename failed: ' ,
delete _title : 'Delete' ,
delete _confirm : ( name ) => ` Delete ${ name } ? ` ,
deleted : 'Deleted ' ,
delete _failed : 'Delete failed: ' ,
new _file _prompt : 'New file name (e.g. notes.md):' ,
feat: composer-centric UI refresh + Hermes Control Center (v0.50.0, closes #242)
* Polish workspace panel behavior and app dialogs
* Replace remaining emoji UI glyphs with Lucide icons
* Redesign composer footer around model and context controls
Move the model selector into the composer footer, replace the linear context pill with a compact circular badge plus tooltip, and remove the redundant topbar model pill.
Design credit and inspiration: Theo / T3 Code.
Reference implementation: https://github.com/pingdotgg/t3code/
* Remove obsolete activity bar
Drop the old activity bar, keep turn-scoped state in the composer footer, and route remaining non-chat status messages through toasts.
This leaves live tool cards and the message timeline as the primary progress UI, with the composer owning stop/cancel and brief turn status.
* Move workspace and model switching into composer footer
* Move profile switching into composer footer
* Refactor Hermes control center UI
* Redesign control center settings modal layout
Widen the modal to 860px, simplify the tab list to icon+label rows,
stretch the tab column's divider to full height, lock the panel to a
fixed height so switching tabs no longer resizes the outer shell, and
always open on the Conversation tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Put session item actions in a dropdown
* Use Hermes mark in sidebar control button
* Reset control center section on close
* Drop session-item left border indicator
Remove the left-border accent used for active, CLI, and project rows —
each state already has a dedicated cue (gold fill, cli badge, project
dot), so the border was redundant. Fully round the row, add 2px
bottom spacing between rows, and strip the matching JS/CSS overrides.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Increase session search input vertical padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Normalise odd pixel values across UI
Snap padding, gap, and border-radius values to the 2/4/6/8/10/12 grid
across composer chips, sidebar panels, cron list, settings, approval
buttons, dropdowns, and inline message edit — eliminating the 7/9/11px
drift that was making sibling elements feel subtly misaligned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add missing #btnMobileFiles button and .mobile-files-btn CSS (for mobile QA suite)
The mobile layout regression suite (test_mobile_layout.py) requires:
- #btnMobileFiles onclick=toggleMobileFiles() in topbar chips
- .mobile-files-btn CSS rules for responsive show/hide at 640/900px breakpoints
Also adds max-width guard to .profile-dropdown to prevent clipping at narrow viewports.
* Improve composer footer mobile responsiveness and UX
- Collapse composer chips to icon-only at <=400px viewports
- Add model chip icon (CPU) so it remains tappable when labels are hidden
- Show send button always (disabled state when empty, hidden during streaming)
- Show context usage indicator on session load, not just after streaming
- Add cancel status fallback timeout to prevent stale "Cancelling..." text
- Update tests to match new send button and busy state behavior
* Fix duplicate files button and broken workspace close on mobile
Remove redundant #btnMobileFiles button that duplicated #btnWorkspacePanelToggle
in the mobile topbar. Fix workspace panel close button calling undefined
closeMobileFiles() — now calls closeWorkspacePanel().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix model chip icon vertical alignment in composer footer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix workspace toggle button hidden on desktop by conflicting CSS class
Remove mobile-files-btn class from #btnWorkspacePanelToggle — its
display:none!important rule was overriding workspace-toggle-btn visibility
on non-mobile viewports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix session actions dots button inaccessible on mobile sidebar
Always show the session actions trigger on mobile (no hover state on
touch devices) and restore right padding so text truncates with
ellipsis before the dots icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer footer manage links not opening sidebar panel
The "Manage profiles" and "Manage workspaces" links in the composer
footer dropdowns called switchPanel() which only changes the active
panel content but doesn't open the sidebar. Replaced with
mobileSwitchPanel() which also opens the sidebar so the panel is
actually visible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Widen icon-only composer chips breakpoint from 400px to 768px
Move the icon-only chip styling up into the existing max-width:768px
media query so chips collapse to icon-only on tablets too, preventing
composer footer overflow on mid-size screens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer-left vertical scrollbar by setting overflow-y:hidden
When overflow-x is set to auto, the CSS spec implicitly changes
overflow-y from visible to auto, allowing a vertical scrollbar to
appear from slight chip padding/border overflow. Explicitly set
overflow-y:hidden to prevent this.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve rebase conflicts and fix control center test assertions
- Resolved 4 conflicts during rebase onto master (workspace.js,
boot.js, index.html, test_sprint34.py)
- Fixed test_sprint34.py: _controlSection -> _settingsSection,
cc-tab -> settings-tabs (matching actual implementation)
- Fixed quoting syntax error in test assertion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update version badge in System tab to v0.49.4
* docs: update README and CHANGELOG for v0.50.0 UI refresh, bump version badge
---------
Co-authored-by: Aron Prins <pwf.aron@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 18:55:40 +00:00
project _name _prompt : 'Project name:' ,
2026-04-08 14:55:03 +00:00
created : 'Created ' ,
create _failed : 'Create failed: ' ,
new _folder _prompt : 'New folder name:' ,
folder _created : 'Created folder ' ,
folder _create _failed : 'Create folder failed: ' ,
remove _title : 'Remove' ,
empty _dir : '(empty)' ,
upload _failed : 'Upload failed: ' ,
all _uploads _failed : ( n ) => ` All ${ n } upload(s) failed ` ,
// settings panel
settings _title : 'Settings' ,
settings _save _btn : 'Save Settings' ,
settings _label _model : 'Default Model' ,
settings _label _send _key : 'Send Key' ,
settings _label _theme : 'Theme' ,
settings _label _language : 'Language' ,
settings _label _token _usage : 'Show token usage' ,
2026-04-14 04:42:01 +00:00
settings _label _bubble _layout : 'Chat bubble layout' ,
feat: real-time gateway session sync (Phase 1) (#274)
* feat: add real-time gateway session sync (Phase 1)
- Add gateway_watcher.py: background daemon polling state.db every 5s
for gateway session changes (telegram, discord, slack, etc.)
- Extend get_cli_sessions() to include all non-webui sources
- Add SSE endpoint /api/sessions/gateway/stream for real-time push
- Add dynamic source badges (telegram=blue, discord=purple, slack=dark purple)
- Rename 'Show CLI sessions' to 'Show agent sessions'
- Wire watcher lifecycle into server start/stop
- 10 tests covering metadata, filtering, SSE, and watcher lifecycle
- Activated via the same checkbox as CLI session import
Addresses GitHub issue #272
* fix: SSE event name mismatch, TLS attribute, remove PLAN.md
- Fix critical SSE bug: frontend listened for 'gateway_session_update'
but backend sends 'sessions_changed' -- events were silently dropped
- Fix frontend field check: data.changed -> data.sessions (matches
the actual payload structure from gateway_watcher)
- Fix TLS: ssl.TLSv1_2 -> ssl.TLSVersion.TLSv1_2 (the bare attribute
does not exist, would crash TLS setup and silently fall back to HTTP)
- Remove PLAN.md: implementation plan should not be committed to repo
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: test isolation and slow-consumer sentinel in gateway sync
tests/test_gateway_sync.py:
- Fix _get_test_state_dir() path mismatch: the function was computing
HERMES_HOME/webui-mvp-test but conftest.py sets HERMES_HOME=TEST_STATE_DIR,
so state.db was written to a double-nested path the server never read.
Now uses HERMES_WEBUI_STATE_DIR first (which conftest sets directly to
TEST_STATE_DIR), fixing the 7/10 test failures in full-suite ordering.
- Fix conn cleanup: removed conn.close() from inside try blocks so the
connection stays valid for _remove_test_sessions() in the finally block.
Previously the closed conn caused ProgrammingError in finally (swallowed
by bare except), leaving ghost sessions in state.db on test failure.
api/gateway_watcher.py:
- Fix slow-consumer queue eviction: when a subscriber queue fills (>10 events)
and is removed from _subscribers, now puts a None sentinel into it so the
SSE handler unblocks and closes the connection, letting EventSource
auto-reconnect. Without this the connection stayed open but received no
further events.
* fix: test isolation — set HERMES_WEBUI_TEST_STATE_DIR in conftest
The gateway sync tests write directly to state.db and must use the same
path the test server reads from. Previously they computed the path
independently, which broke when test_auth_sessions.py set a different
HERMES_WEBUI_STATE_DIR in the test-process environment at import time.
tests/conftest.py:
- Set HERMES_WEBUI_TEST_STATE_DIR=TEST_STATE_DIR in the test process's
os.environ (via setdefault) so gateway tests can read it reliably.
Using setdefault preserves any explicit override the caller may pass.
tests/test_gateway_sync.py:
- Simplify _get_test_state_dir(): check HERMES_WEBUI_TEST_STATE_DIR first
(now reliably set by conftest), fall back to HERMES_HOME/webui-mvp-test.
Remove the workaround that tried to snapshot HERMES_HOME at import time.
Result: 658/658 tests pass in full-suite ordering (was 651 pass / 7 fail).
---------
Co-authored-by: bergeouss <bergeouss@users.noreply.github.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 03:53:12 +00:00
settings _label _cli _sessions : 'Show agent sessions' ,
2026-04-08 14:55:03 +00:00
settings _label _sync _insights : 'Sync to insights' ,
settings _label _check _updates : 'Check for updates' ,
settings _label _bot _name : 'Assistant Name' ,
settings _label _password : 'Access Password' ,
settings _saved : 'Settings saved' ,
settings _save _failed : 'Save failed: ' ,
settings _load _failed : 'Failed to load settings: ' ,
2026-04-14 12:51:19 +00:00
settings _saved _pw : 'Settings saved — password protection enabled and this browser stays signed in' ,
settings _saved _pw _updated : 'Settings saved — password updated' ,
2026-04-08 14:55:03 +00:00
// login page (used server-side via /api/i18n/login endpoint)
login _title : 'Sign in' ,
login _subtitle : 'Enter your password to continue' ,
login _placeholder : 'Password' ,
login _btn : 'Sign in' ,
login _invalid _pw : 'Invalid password' ,
login _conn _failed : 'Connection failed' ,
v0.47.0: dialogs, session menu, /skills, mobile fixes, mobile QA suite
* fix: custom provider with slash model name no longer rerouted to OpenRouter (#255)
When base_url is configured in config.yaml, resolve_model_provider() now
trusts the configured provider/base_url entirely and skips the slash-based
OpenRouter heuristic. Fixes google/gemma-4-26b-a4b with provider:custom
being silently routed to OpenRouter, resulting in 401 errors.
Fixes #230
* test: mobile layout regression suite — 14 tests for every QA run (#254)
Adds tests/test_mobile_layout.py with 14 static regression tests that run
on every QA pass to catch mobile layout breakage before it reaches prod.
Covers: breakpoints at 900px/640px, right panel slide-over CSS, mobile
overlay, bottom nav, files button, profile dropdown z-index, chip overflow,
workspace close, 100dvh, 44px touch targets, 16px font-size on textarea.
* feat: /skills slash command lists and filters available Hermes skills (#257)
Adds /skills [query] command to commands.js. Fetches from /api/skills,
groups by category (alphabetically sorted), displays as a formatted
assistant message. Optional query filters by name, description, or category.
i18n keys added for en, de, zh, zh-Hant. 1 regression test added.
Fixes #248
* feat: shared app dialogs replace native confirm()/prompt() calls (#251)
Adds showConfirmDialog() and showPromptDialog() helpers to ui.js, backed
by a themed #appDialogOverlay. Replaces all 11 native browser confirm/prompt
call sites across panels.js, sessions.js, ui.js, workspace.js.
Supports: danger mode, keyboard focus trap (Tab/Escape/Enter), focus restore,
ARIA roles, mobile-responsive stacked buttons at 640px. i18n for en/de/zh/zh-Hant.
5 new tests in test_sprint33.py verify markup, CSS, helpers, and absence of
native dialog calls.
Extracted from PR #242.
* fix: Android Chrome mobile — workspace panel close + profile dropdown (#256)
Fix #247: toggleMobileFiles() now shows/hides the mobile overlay when
toggling the right workspace panel. New closeMobileFiles() helper closes
the panel with correct overlay state tracking. Overlay onclick calls both
closeMobileSidebar() and closeMobileFiles(). Mobile-only close button (x)
added to workspace panel header.
Fix #246: profile dropdown uses position:fixed;top:56px;right:8px at
max-width:900px, escaping the overflow-x:auto stacking context that was
clipping it on Android Chrome.
Fix applied during review: closeMobileSidebar() now checks if the right
panel is still open before hiding the overlay, preventing the overlay from
disappearing when only the sidebar is closed.
Fixes #247 Fixes #246
* feat: session ⋯ action dropdown replaces per-row buttons (#252)
Replaces the 5 per-row hover action buttons (pin/move/archive/duplicate/trash)
with a single ⋯ trigger that opens a positioned dropdown menu. Menu has full
keyboard (Escape), click-outside, scroll, and resize-reposition handling.
Position:fixed prevents sidebar clipping.
5 actions: Pin/Unpin, Move to project, Archive/Unarchive, Duplicate, Delete
(danger style). Each with icon and descriptive subtitle.
Updated test_sprint16.py: test_sessions_js_uses_action_menu_not_per_row_buttons
asserts the new trigger and menu functions exist, old per-row classes are gone.
Extracted from PR #242.
* docs: v0.47.0 release notes, bump version, update test counts (645)
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 19:19:12 +00:00
dialog _confirm _title : 'Confirm action' ,
dialog _prompt _title : 'Enter a value' ,
dialog _confirm _btn : 'Confirm' ,
discard : 'Discard' ,
clear : 'Clear' ,
create : 'Create' ,
remove : 'Remove' ,
project _name _prompt : 'Project name:' ,
2026-04-10 01:35:23 +00:00
// Sidebar & Tabs
tab _chat : 'Chat' ,
tab _tasks : 'Tasks' ,
tab _skills : 'Skills' ,
tab _memory : 'Memory' ,
tab _workspaces : 'Spaces' ,
tab _profiles : 'Profiles' ,
tab _todos : 'Todos' ,
new _conversation : 'New conversation' ,
filter _conversations : 'Filter conversations...' ,
2026-04-14 05:26:05 +00:00
session _time _unknown : 'Unknown' ,
session _time _just _now : 'just now' ,
session _time _minutes _ago : ( n ) => ` ${ n } minute ${ n === 1 ? '' : 's' } ago ` ,
session _time _hours _ago : ( n ) => ` ${ n } hour ${ n === 1 ? '' : 's' } ago ` ,
session _time _days _ago : ( n ) => ` ${ n } day ${ n === 1 ? '' : 's' } ago ` ,
session _time _last _week : 'last week' ,
session _time _bucket _today : 'Today' ,
session _time _bucket _yesterday : 'Yesterday' ,
session _time _bucket _this _week : 'This week' ,
session _time _bucket _last _week : 'Last week' ,
session _time _bucket _older : 'Older' ,
2026-04-10 01:35:23 +00:00
scheduled _jobs : 'Scheduled jobs' ,
new _job : 'New job' ,
loading : 'Loading...' ,
search _skills : 'Search skills...' ,
new _skill : 'New skill' ,
personal _memory : 'Personal memory' ,
current _task _list : 'Current task list' ,
workspace _desc : 'Add and switch workspaces for your sessions.' ,
new _profile : 'New profile' ,
transcript : 'Transcript' ,
download _transcript : 'Download as Markdown' ,
import : 'Import' ,
// Settings detail
settings _label _sound : 'Notification sound' ,
settings _desc _sound : 'Play a sound when the assistant finishes a response.' ,
settings _label _notifications : 'Browser notifications' ,
settings _desc _notifications : 'Show a system notification when a response completes while the tab is in the background.' ,
settings _desc _token _usage : 'Displays input/output token count below each assistant reply. Also toggled with /usage.' ,
2026-04-14 04:42:01 +00:00
settings _desc _bubble _layout : 'Right-align user messages and left-align assistant replies. Off by default to keep code blocks and tool output full-width.' ,
2026-04-10 01:35:23 +00:00
settings _desc _cli _sessions : 'Merges sessions from the Hermes CLI (state.db) into the session list. Click a CLI session to import it and continue the conversation.' ,
settings _desc _sync _insights : 'Mirrors WebUI token usage to state.db so hermes /insights includes browser session data. Off by default.' ,
settings _desc _check _updates : 'Show a banner when newer versions of the WebUI or Agent are available. Runs a background git fetch periodically.' ,
settings _desc _bot _name : 'Display name for the assistant throughout the UI. Defaults to Hermes.' ,
settings _desc _password : 'Enter a new password to set or change it. Leave blank to keep current setting.' ,
password _placeholder : 'Enter new password…' ,
disable _auth : 'Disable Auth' ,
sign _out : 'Sign Out' ,
cancel : 'Cancel' ,
create _job : 'Create job' ,
save _skill : 'Save skill' ,
editing : 'Editing' ,
// Empty state
empty _title : 'What can I help with?' ,
empty _subtitle : 'Ask anything, run commands, explore files, or manage your scheduled tasks.' ,
suggest _files : 'What files are in this workspace?' ,
suggest _schedule : "What's on my schedule today?" ,
suggest _plan : 'Help me plan a small project.' ,
2026-04-12 07:11:41 +00:00
// onboarding
onboarding _badge : 'FIRST RUN' ,
onboarding _title : 'Welcome to Hermes Web UI' ,
onboarding _lead : 'A quick guided setup will verify Hermes, save a real provider configuration, choose a workspace and model, and optionally protect the app with a password.' ,
onboarding _back : 'Back' ,
onboarding _continue : 'Continue' ,
2026-04-14 16:43:37 +00:00
onboarding _skip : 'Skip setup' ,
onboarding _skipped : 'Setup skipped — using existing config.' ,
2026-04-12 07:11:41 +00:00
onboarding _open : 'Open Hermes' ,
onboarding _step _system _title : 'System check' ,
onboarding _step _system _desc : 'Verify Hermes Agent and config visibility.' ,
onboarding _step _setup _title : 'Provider setup' ,
onboarding _step _setup _desc : 'Save the minimum Hermes provider config.' ,
onboarding _step _workspace _title : 'Workspace + model' ,
onboarding _step _workspace _desc : 'Pick defaults for new sessions and chat.' ,
onboarding _step _password _title : 'Optional password' ,
onboarding _step _password _desc : 'Protect the Web UI before sharing it.' ,
onboarding _step _finish _title : 'Finish' ,
onboarding _step _finish _desc : 'Review and enter the app.' ,
onboarding _notice _system _ready : 'Hermes Agent looks reachable from the Web UI.' ,
onboarding _notice _system _unavailable : 'Hermes Agent is not fully available yet. Bootstrap can install it, but provider setup may still require a terminal.' ,
onboarding _check _agent : 'Hermes Agent' ,
onboarding _check _agent _ready : 'Detected and importable' ,
onboarding _check _agent _missing : 'Missing or partially importable' ,
onboarding _check _password : 'Password' ,
onboarding _check _password _enabled : 'Already enabled' ,
onboarding _check _password _disabled : 'Not enabled yet' ,
onboarding _check _provider : 'Provider config' ,
onboarding _check _provider _ready : 'Ready to chat' ,
onboarding _check _provider _partial : 'Saved but incomplete' ,
onboarding _check _provider _pending : 'Needs verification' ,
onboarding _config _file : 'Config file:' ,
onboarding _env _file : '.env file:' ,
onboarding _unknown : 'Unknown' ,
onboarding _current _provider : 'Current setup:' ,
onboarding _missing _imports : 'Missing imports:' ,
onboarding _notice _setup _required : 'Choose a simple provider path here. Advanced OAuth flows still belong in the Hermes CLI for now.' ,
onboarding _notice _setup _already _ready : 'A working Hermes provider setup is already detected. You can keep it or replace it here.' ,
2026-04-12 21:28:16 +00:00
onboarding _oauth _provider _ready _title : 'Provider already authenticated' ,
onboarding _oauth _provider _ready _body : 'This instance is configured to use an OAuth provider (<strong>{provider}</strong>) that was set up via the Hermes CLI. No API key is needed here — click Continue to finish setup.' ,
onboarding _oauth _provider _not _ready _title : 'OAuth provider not yet authenticated' ,
onboarding _oauth _provider _not _ready _body : 'This instance is configured to use <strong>{provider}</strong>, which uses OAuth rather than an API key. Run <code>hermes auth</code> or <code>hermes model</code> in a terminal to authenticate, then reload the Web UI.' ,
onboarding _oauth _switch _hint : 'Or choose a different provider below to switch to an API-key setup:' ,
2026-04-12 07:11:41 +00:00
onboarding _notice _workspace : 'These values reuse the same settings APIs as the normal app.' ,
onboarding _workspace _label : 'Workspace' ,
onboarding _workspace _or _path : 'Or enter a workspace path' ,
onboarding _workspace _placeholder : '/home/you/workspace' ,
onboarding _provider _label : 'Setup mode' ,
onboarding _quick _setup _badge : 'quick setup' ,
onboarding _api _key _label : 'API key' ,
onboarding _api _key _placeholder : 'Leave blank to keep an existing saved key' ,
onboarding _api _key _help _prefix : 'Saved as a secret in your Hermes .env file using' ,
onboarding _base _url _label : 'Base URL' ,
onboarding _base _url _placeholder : 'https://your-endpoint.example/v1' ,
onboarding _base _url _help : 'Use this for OpenAI-compatible routers, self-hosted servers, LiteLLM, Ollama, LM Studio, vLLM, or similar endpoints.' ,
onboarding _model _label : 'Default model' ,
onboarding _workspace _help : 'Pick the model Hermes should use for new chats after setup completes.' ,
onboarding _custom _model _placeholder : 'your-model-name' ,
onboarding _custom _model _help : 'For custom endpoints, enter the exact model ID your server expects.' ,
onboarding _notice _password _enabled : 'A password is already configured. Enter a new one only if you want to replace it.' ,
onboarding _notice _password _recommended : 'Optional but recommended if you will expose the UI beyond localhost.' ,
onboarding _password _label : 'Password (optional)' ,
onboarding _password _placeholder : 'Leave blank to skip' ,
onboarding _password _help : 'Passwords are stored through the existing settings API and hashed server-side.' ,
onboarding _notice _finish : 'You can reopen Settings later to change any of this.' ,
onboarding _not _set : 'Not set' ,
onboarding _password _will _enable : 'Will be enabled' ,
2026-04-14 12:51:19 +00:00
onboarding _password _will _replace : 'Will be replaced' ,
onboarding _password _keep _existing : 'Keep current password' ,
onboarding _password _remains _disabled : 'Will remain disabled' ,
2026-04-12 07:11:41 +00:00
onboarding _password _skipped : 'Skipped for now' ,
onboarding _finish _help : 'Finishing stores <code>onboarding_completed</code> in settings and drops you into the normal app.' ,
onboarding _error _choose _workspace : 'Choose a workspace before continuing.' ,
onboarding _error _choose _model : 'Choose a model before continuing.' ,
onboarding _error _provider _required : 'Choose a setup mode before continuing.' ,
onboarding _error _base _url _required : 'Base URL is required for custom endpoints.' ,
onboarding _error _workspace _required : 'Workspace is required.' ,
onboarding _error _model _required : 'Model is required.' ,
onboarding _complete : 'Onboarding complete' ,
2026-04-14 17:14:01 +00:00
// panel/runtime i18n
error _prefix : 'Error: ' ,
not _available : 'N/A' ,
never : 'never' ,
add : 'Add' ,
add _failed : 'Add failed: ' ,
remove _failed : 'Remove failed: ' ,
switch _failed : 'Switch failed: ' ,
name _required : 'Name is required' ,
content _required : 'Content is required' ,
view : 'View' ,
dismiss : 'Dismiss' ,
disable : 'Disable' ,
cron _no _jobs : 'No scheduled jobs found.' ,
cron _status _off : 'off' ,
cron _status _paused : 'paused' ,
cron _status _error : 'error' ,
cron _status _active : 'active' ,
cron _next : 'Next' ,
cron _last : 'Last' ,
cron _run _now : 'Run now' ,
cron _pause : 'Pause' ,
cron _resume : 'Resume' ,
cron _job _name _placeholder : 'Job name' ,
cron _schedule _placeholder : 'Schedule' ,
cron _prompt _placeholder : 'Prompt' ,
cron _last _output : 'Last output' ,
cron _all _runs : 'All runs' ,
cron _hide _runs : 'Hide runs' ,
cron _no _runs _yet : '(no runs yet)' ,
cron _schedule _required _example : 'Schedule is required (e.g. "0 9 * * *" or "every 1h")' ,
cron _schedule _required : 'Schedule is required' ,
cron _prompt _required : 'Prompt is required' ,
cron _job _created : 'Job created' ,
cron _job _triggered : 'Job triggered' ,
cron _job _paused : 'Job paused' ,
cron _job _resumed : 'Job resumed' ,
cron _job _updated : 'Job updated' ,
cron _delete _confirm _title : 'Delete cron job' ,
cron _delete _confirm _message : 'This cannot be undone.' ,
cron _job _deleted : 'Job deleted' ,
cron _completion _status : ( name , status ) => ` Cron " ${ name } " ${ status } ` ,
status _failed : 'failed' ,
status _completed : 'completed' ,
todos _no _active : 'No active task list in this session.' ,
clear _conversation _title : 'Clear conversation' ,
clear _conversation _message : 'Clear all messages? This cannot be undone.' ,
clear _failed : 'Clear failed: ' ,
skills _no _match : 'No skills match.' ,
linked _files : 'Linked Files' ,
skill _load _failed : 'Could not load skill: ' ,
skill _file _load _failed : 'Could not load file: ' ,
skill _name _required : 'Skill name is required' ,
skill _updated : 'Skill updated' ,
skill _created : 'Skill created' ,
memory _notes _label : 'memory (notes)' ,
memory _saved : 'Memory saved' ,
my _notes : 'My Notes' ,
user _profile : 'User Profile' ,
no _notes _yet : 'No notes yet.' ,
no _profile _yet : 'No profile yet.' ,
workspace _choose _path : 'Choose workspace path' ,
workspace _choose _path _meta : 'Add a validated path and switch this conversation' ,
workspace _manage : 'Manage workspaces' ,
workspace _manage _meta : 'Open the Spaces panel' ,
workspace _use _title : 'Use in current session' ,
workspace _use : 'Use' ,
workspace _add _path _placeholder : 'Add workspace path (e.g. /home/user/my-project)' ,
workspace _paths _validated _hint : 'Paths are validated as existing directories before saving.' ,
workspace _added : 'Workspace added' ,
workspace _remove _confirm _title : 'Remove workspace' ,
workspace _remove _confirm _message : ( path ) => ` Remove " ${ path } "? ` ,
workspace _removed : 'Workspace removed' ,
workspace _switch _prompt _title : 'Switch workspace' ,
workspace _switch _prompt _message : 'Enter an absolute workspace path to add and switch this conversation to.' ,
workspace _switch _prompt _confirm : 'Switch' ,
workspace _switch _prompt _placeholder : '/Users/you/project' ,
workspace _not _added : 'Workspace was not added' ,
workspace _already _saved : 'Workspace already saved — choose it from the list' ,
workspace _busy _switch : 'Cannot switch workspace while agent is running' ,
discard _file _edits _title : 'Discard file edits?' ,
discard _file _edits _message : 'Switching workspaces will discard unsaved file edits in the preview.' ,
workspace _switched _to : ( name ) => ` Switched to ${ name } ` ,
profiles _no _profiles : 'No profiles found.' ,
profile _api _keys _configured : 'API keys configured' ,
profile _gateway _running : 'Gateway running' ,
profile _gateway _stopped : 'Gateway stopped' ,
profile _active : 'ACTIVE' ,
profile _no _configuration : 'No configuration' ,
2026-04-14 17:14:01 +00:00
profile _skill _count : ( count ) => ` ${ count } skill ${ count === 1 ? '' : 's' } ` ,
2026-04-14 17:14:01 +00:00
profile _use : 'Use' ,
profile _switch _title : 'Switch to this profile' ,
2026-04-14 17:14:01 +00:00
profile _delete _title : 'Delete this profile' ,
2026-04-14 17:14:01 +00:00
manage _profiles : 'Manage profiles' ,
profiles _load _failed : 'Failed to load profiles' ,
profiles _busy _switch : 'Cannot switch profiles while agent is running' ,
profile _switched _new _conversation : ( name ) => ` Switched to profile: ${ name } — new conversation started ` ,
profile _switched : ( name ) => ` Switched to profile: ${ name } ` ,
profile _name _rule : 'Lowercase letters, numbers, hyphens, underscores only' ,
profile _base _url _rule : 'Base URL must start with http:// or https://' ,
profile _created : ( name ) => ` Profile created: ${ name } ` ,
profile _delete _confirm _title : ( name ) => ` Delete profile " ${ name } "? ` ,
profile _delete _confirm _message : 'This removes all config, skills, memory, and sessions for this profile.' ,
profile _deleted : ( name ) => ` Profile deleted: ${ name } ` ,
active _conversation _none : 'No active conversation selected.' ,
active _conversation _meta : ( title , count ) => ` ${ title } · ${ count } message ${ count === 1 ? '' : 's' } ` ,
settings _unsaved _changes : 'You have unsaved changes.' ,
sign _out _failed : 'Sign out failed: ' ,
disable _auth _confirm _title : 'Disable password protection' ,
disable _auth _confirm _message : 'Anyone will be able to access this instance.' ,
auth _disabled : 'Auth disabled — password protection removed' ,
disable _auth _failed : 'Failed to disable auth: ' ,
bg _error _single : ( title ) => ` " ${ title } " has encountered an error ` ,
bg _error _multi : ( count ) => ` ${ count } sessions have encountered an error ` ,
2026-04-10 01:35:23 +00:00
} ,
2026-04-12 03:06:37 +00:00
es : {
_lang : 'es' ,
_label : 'Español' ,
_speech : 'es-ES' ,
// boot.js
cancelling : 'Cancelando…' ,
cancel _failed : 'Error al cancelar: ' ,
mic _denied : 'Acceso al micrófono denegado. Revisa los permisos del navegador.' ,
mic _no _speech : 'No se detectó voz. Inténtalo de nuevo.' ,
mic _network : 'El reconocimiento de voz no está disponible.' ,
mic _error : 'Error de entrada por voz: ' ,
session _imported : 'Sesión importada' ,
import _failed : 'Error al importar: ' ,
import _invalid _json : 'JSON inválido' ,
image _pasted : 'Imagen pegada: ' ,
// messages.js
edit _message : 'Editar mensaje' ,
regenerate : 'Regenerar respuesta' ,
copy : 'Copiar' ,
copied : '¡Copiado!' ,
you : 'Tú' ,
thinking : 'Pensando' ,
expand _all : 'Expandir todo' ,
collapse _all : 'Contraer todo' ,
edit _failed : 'Error al editar: ' ,
regen _failed : 'Error al regenerar: ' ,
reconnect _active : 'Todavía se está generando una respuesta. ¿Recargar cuando termine?' ,
reconnect _finished : 'Había una respuesta en curso cuando te fuiste. Puede que los mensajes se hayan actualizado.' ,
// approval card
approval _heading : 'Se requiere aprobación' ,
approval _desc _prefix : 'Se detectó un comando peligroso' ,
approval _btn _once : 'Permitir una vez' ,
approval _btn _once _title : 'Permitir solo este comando (Enter)' ,
approval _btn _session : 'Permitir en la sesión' ,
approval _btn _session _title : 'Permitir durante esta sesión de conversación' ,
approval _btn _always : 'Permitir siempre' ,
approval _btn _always _title : 'Permitir siempre este patrón de comando' ,
approval _btn _deny : 'Denegar' ,
approval _btn _deny _title : 'Denegar — no ejecutar este comando' ,
approval _responding : 'Respondiendo…' ,
untitled : 'Sin título' ,
n _messages : ( n ) => ` ${ n } mensajes ` ,
model _unavailable : ' (no disponible)' ,
model _unavailable _title : 'Este modelo ya no está en tu lista actual de proveedores' ,
fix: warn on provider/model mismatch, surface auth errors (#266)
* fix: warn on provider/model mismatch, surface auth errors (#266)
Fixes #266 — WebUI silently ignores provider/model selection mismatch.
The problem: selecting an OpenRouter (or Anthropic/OpenAI) model while
Hermes is configured for a different provider (e.g. local Ollama) sends
the request to the wrong endpoint, which returns a 401 Unauthorized error
with no UI indication of why.
Three-layer fix:
1. api/streaming.py — detect 401/auth errors explicitly
Added is_auth_error detection covering '401', 'AuthenticationError',
'authentication', 'unauthorized', 'invalid api key', and the specific
Ollama error string 'no cookie auth credentials'. Auth errors emit
apperror with type='auth_mismatch' and a hint pointing to 'hermes model'.
2. static/ui.js — expose active_provider and warn on selection
- populateModelDropdown() stores data.active_provider from /api/models
as window._activeProvider (the field was already in the response but
the frontend never used it)
- New _checkProviderMismatch(modelId) helper: compares the selected
model's slash-prefix (e.g. 'openai/' from 'openai/gpt-4o') against
the active provider. Skips the check for 'openrouter' and 'custom'
to avoid false positives on configs that legitimately route any model.
3. static/boot.js — warn on model dropdown change
modelSelect.onchange calls _checkProviderMismatch() and shows a toast
when the selected model looks incompatible with the configured provider.
4. static/messages.js — distinct UI label for auth errors
apperror handler now distinguishes type='auth_mismatch' and shows
'Provider mismatch' as the error label instead of 'Error'.
5. static/i18n.js — provider_mismatch_warning and provider_mismatch_label
keys added to all 5 locales (en, es, de, zh-Hans, zh-Hant).
Tests: 21 new tests in tests/test_provider_mismatch.py covering all
five change areas. 679/679 total pass (658 baseline + 21 new).
* fix: t() call args spread + use i18n label for auth mismatch
1. ui.js: _checkProviderMismatch passed [modelId, ap] as a single
array arg to t(). Since t(key, ...args) spreads, the function
received the array as m and undefined as p. Fixed to pass as
separate args: t('provider_mismatch_warning', modelId, ap).
2. messages.js: 'Provider mismatch' label was hardcoded instead of
using t('provider_mismatch_label'). Now uses the i18n key with
fallback for when t() isn't available.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 04:25:18 +00:00
provider _mismatch _warning : ( m , p ) => ` " ${ m } " puede no funcionar con tu proveedor configurado ( ${ p } ). Envía de todas formas, o ejecuta \` hermes model \` en la terminal para cambiar. ` ,
provider _mismatch _label : 'Proveedor incompatible' ,
2026-04-14 20:56:56 +00:00
model _custom _label : 'ID de modelo personalizado' ,
model _custom _placeholder : 'p. ej. openai/gpt-5.4' ,
2026-04-12 03:06:37 +00:00
// commands.js
cmd _help : 'Listar los comandos disponibles' ,
cmd _clear : 'Borrar los mensajes de la conversación' ,
cmd _compact : 'Comprimir el contexto de la conversación' ,
cmd _model : 'Cambiar de modelo (p. ej. /model gpt-4o)' ,
cmd _workspace : 'Cambiar de espacio de trabajo por nombre' ,
cmd _new : 'Iniciar una nueva sesión de chat' ,
cmd _usage : 'Activar o desactivar el uso de tokens' ,
cmd _theme : 'Cambiar tema (dark/light/slate/solarized/monokai/nord/oled)' ,
cmd _personality : 'Cambiar la personalidad del agente' ,
cmd _skills : 'Listar las skills de Hermes disponibles' ,
available _commands : 'Comandos disponibles:' ,
type _slash : 'Escribe / para ver los comandos' ,
conversation _cleared : 'Conversación borrada' ,
model _usage : 'Uso: /model <name>' ,
no _model _match : 'No hay ningún modelo que coincida con "' ,
switched _to : 'Se cambió a ' ,
workspace _usage : 'Uso: /workspace <name>' ,
no _workspace _match : 'No hay ningún espacio de trabajo que coincida con "' ,
switched _workspace : 'Se cambió al espacio de trabajo: ' ,
workspace _switch _failed : 'Error al cambiar de espacio de trabajo: ' ,
new _session : 'Nueva sesión creada' ,
compressing : 'Solicitando compresión del contexto...' ,
token _usage _on : 'Uso de tokens activado' ,
token _usage _off : 'Uso de tokens desactivado' ,
theme _usage : 'Uso: /theme ' ,
theme _set : 'Tema: ' ,
no _active _session : 'No hay ninguna sesión activa' ,
no _personalities : 'No se encontraron personalidades (añádelas a ~/.hermes/personalities/)' ,
available _personalities : 'Personalidades disponibles:' ,
personality _switch _hint : '\n\nUsa `/personality <name>` para cambiar, o `/personality none` para limpiar.' ,
personalities _load _failed : 'No se pudieron cargar las personalidades' ,
personality _cleared : 'Personalidad borrada' ,
personality _set : 'Personalidad: ' ,
failed _colon : 'Error: ' ,
// ui.js
no _workspace : 'Sin espacio de trabajo' ,
// workspace.js
unsaved _confirm : 'Tienes cambios sin guardar en la vista previa. ¿Descartar y navegar?' ,
save : 'Guardar' ,
edit : 'Editar' ,
save _title : 'Guardar cambios' ,
edit _title : 'Editar este archivo' ,
saved : 'Guardado' ,
save _failed : 'Error al guardar: ' ,
image _load _failed : 'No se pudo cargar la imagen' ,
file _open _failed : 'No se pudo abrir el archivo' ,
downloading : ( name ) => ` Descargando ${ name } … ` ,
double _click _rename : 'Haz doble clic para renombrar' ,
renamed _to : 'Renombrado a ' ,
rename _failed : 'Error al renombrar: ' ,
delete _title : 'Eliminar' ,
delete _confirm : ( name ) => ` ¿Eliminar ${ name } ? ` ,
deleted : 'Eliminado ' ,
delete _failed : 'Error al eliminar: ' ,
new _file _prompt : 'Nombre del archivo nuevo (p. ej. notes.md):' ,
created : 'Creado ' ,
create _failed : 'Error al crear: ' ,
new _folder _prompt : 'Nombre de la carpeta nueva:' ,
folder _created : 'Carpeta creada ' ,
folder _create _failed : 'Error al crear la carpeta: ' ,
remove _title : 'Quitar' ,
empty _dir : '(vacío)' ,
upload _failed : 'Error al subir: ' ,
all _uploads _failed : ( n ) => ` Fallaron las ${ n } subida(s) ` ,
// settings panel
settings _title : 'Configuración' ,
settings _save _btn : 'Guardar configuración' ,
settings _label _model : 'Modelo predeterminado' ,
settings _label _send _key : 'Tecla de envío' ,
settings _label _theme : 'Tema' ,
settings _label _language : 'Idioma' ,
settings _label _token _usage : 'Mostrar uso de tokens' ,
2026-04-14 04:42:01 +00:00
settings _label _bubble _layout : 'Disposición en burbujas' ,
2026-04-12 03:06:37 +00:00
settings _label _cli _sessions : 'Mostrar sesiones de CLI' ,
settings _label _sync _insights : 'Sincronizar con insights' ,
settings _label _check _updates : 'Buscar actualizaciones' ,
settings _label _bot _name : 'Nombre del asistente' ,
settings _label _password : 'Contraseña de acceso' ,
settings _saved : 'Configuración guardada' ,
settings _save _failed : 'Error al guardar: ' ,
settings _load _failed : 'Error al cargar la configuración: ' ,
2026-04-14 12:51:19 +00:00
settings _saved _pw : 'Configuración guardada — la contraseña queda activada y este navegador sigue autenticado' ,
settings _saved _pw _updated : 'Configuración guardada — contraseña actualizada' ,
2026-04-12 03:06:37 +00:00
// login page (used server-side via /api/i18n/login endpoint)
login _title : 'Iniciar sesión' ,
login _subtitle : 'Introduce tu contraseña para continuar' ,
login _placeholder : 'Contraseña' ,
login _btn : 'Entrar' ,
login _invalid _pw : 'Contraseña inválida' ,
login _conn _failed : 'Error de conexión' ,
dialog _confirm _title : 'Confirmar acción' ,
dialog _prompt _title : 'Introduce un valor' ,
dialog _confirm _btn : 'Confirmar' ,
discard : 'Descartar' ,
clear : 'Borrar' ,
create : 'Crear' ,
remove : 'Quitar' ,
project _name _prompt : 'Nombre del proyecto:' ,
// Sidebar & Tabs
tab _chat : 'Chat' ,
tab _tasks : 'Tareas' ,
tab _skills : 'Habilidades' ,
tab _memory : 'Memoria' ,
tab _workspaces : 'Espacios' ,
tab _profiles : 'Perfiles' ,
tab _todos : 'Todos' ,
new _conversation : 'Nueva conversación' ,
filter _conversations : 'Filtrar conversaciones...' ,
2026-04-14 05:26:05 +00:00
session _time _unknown : 'Desconocido' ,
session _time _just _now : 'justo ahora' ,
session _time _minutes _ago : ( n ) => ` hace ${ n } minuto ${ n === 1 ? '' : 's' } ` ,
session _time _hours _ago : ( n ) => ` hace ${ n } hora ${ n === 1 ? '' : 's' } ` ,
session _time _days _ago : ( n ) => ` hace ${ n } día ${ n === 1 ? '' : 's' } ` ,
session _time _last _week : 'la semana pasada' ,
session _time _bucket _today : 'Hoy' ,
session _time _bucket _yesterday : 'Ayer' ,
session _time _bucket _this _week : 'Esta semana' ,
session _time _bucket _last _week : 'La semana pasada' ,
session _time _bucket _older : 'Más antiguo' ,
2026-04-12 03:06:37 +00:00
scheduled _jobs : 'Tareas programadas' ,
new _job : 'Nueva tarea' ,
loading : 'Cargando...' ,
search _skills : 'Buscar skills...' ,
new _skill : 'Nueva skill' ,
personal _memory : 'Memoria personal' ,
current _task _list : 'Lista de tareas actual' ,
workspace _desc : 'Añade y cambia espacios de trabajo para tus sesiones.' ,
new _profile : 'Nuevo perfil' ,
transcript : 'Transcripción' ,
download _transcript : 'Descargar como Markdown' ,
import : 'Importar' ,
// Settings detail
settings _label _sound : 'Sonido de notificación' ,
settings _desc _sound : 'Reproduce un sonido cuando el asistente termina una respuesta.' ,
settings _label _notifications : 'Notificaciones del navegador' ,
settings _desc _notifications : 'Muestra una notificación del sistema cuando una respuesta termina mientras la pestaña está en segundo plano.' ,
settings _desc _token _usage : 'Muestra el conteo de tokens de entrada/salida debajo de cada respuesta del asistente. También se puede alternar con /usage.' ,
2026-04-14 04:42:01 +00:00
settings _desc _bubble _layout : 'Alinea los mensajes del usuario a la derecha y las respuestas del asistente a la izquierda. Desactivado por defecto para mantener los bloques de código y la salida de herramientas a ancho completo.' ,
2026-04-12 03:06:37 +00:00
settings _desc _cli _sessions : 'Fusiona las sesiones del CLI de Hermes (state.db) en la lista de sesiones. Haz clic en una sesión de CLI para importarla y continuar la conversación.' ,
settings _desc _sync _insights : 'Refleja el uso de tokens de la WebUI en state.db para que hermes /insights incluya datos de sesiones del navegador. Desactivado por defecto.' ,
settings _desc _check _updates : 'Muestra un banner cuando haya versiones más nuevas de la WebUI o del Agent. Ejecuta periódicamente un git fetch en segundo plano.' ,
settings _desc _bot _name : 'Nombre visible del asistente en toda la UI. Por defecto es Hermes.' ,
settings _desc _password : 'Introduce una nueva contraseña para establecerla o cambiarla. Déjalo en blanco para mantener la configuración actual.' ,
password _placeholder : 'Introduce una contraseña nueva…' ,
disable _auth : 'Desactivar autenticación' ,
sign _out : 'Cerrar sesión' ,
cancel : 'Cancelar' ,
create _job : 'Crear tarea' ,
save _skill : 'Guardar skill' ,
editing : 'Editando' ,
// Empty state
empty _title : '¿En qué puedo ayudarte?' ,
empty _subtitle : 'Pregunta lo que quieras, ejecuta comandos, explora archivos o gestiona tus tareas programadas.' ,
suggest _files : '¿Qué archivos hay en este espacio de trabajo?' ,
suggest _schedule : '¿Qué tengo hoy en mi agenda?' ,
suggest _plan : 'Ayúdame a planificar un proyecto pequeño.' ,
2026-04-12 07:11:41 +00:00
// onboarding
onboarding _badge : 'PRIMER USO' ,
onboarding _title : 'Bienvenido a Hermes Web UI' ,
onboarding _lead : 'Una guía rápida verificará Hermes, guardará una configuración real del proveedor, elegirá un espacio de trabajo y un modelo, y opcionalmente protegerá la app con una contraseña.' ,
onboarding _back : 'Atrás' ,
onboarding _continue : 'Continuar' ,
2026-04-14 16:43:37 +00:00
onboarding _skip : 'Omitir configuración' ,
onboarding _skipped : 'Configuración omitida — se usa la configuración existente.' ,
2026-04-12 07:11:41 +00:00
onboarding _open : 'Abrir Hermes' ,
onboarding _step _system _title : 'Comprobación del sistema' ,
onboarding _step _system _desc : 'Verifica Hermes Agent y la visibilidad de la configuración.' ,
onboarding _step _setup _title : 'Configuración del proveedor' ,
onboarding _step _setup _desc : 'Guarda la configuración mínima real de Hermes.' ,
onboarding _step _workspace _title : 'Espacio de trabajo + modelo' ,
onboarding _step _workspace _desc : 'Elige los valores predeterminados para nuevas sesiones y chats.' ,
onboarding _step _password _title : 'Contraseña opcional' ,
onboarding _step _password _desc : 'Protege la Web UI antes de compartirla.' ,
onboarding _step _finish _title : 'Finalizar' ,
onboarding _step _finish _desc : 'Revisa todo y entra en la app.' ,
onboarding _notice _system _ready : 'Parece que Hermes Agent está accesible desde la Web UI.' ,
onboarding _notice _system _unavailable : 'Hermes Agent todavía no está totalmente disponible. Bootstrap puede instalarlo, pero la configuración del proveedor quizá aún requiera una terminal.' ,
onboarding _check _agent : 'Hermes Agent' ,
onboarding _check _agent _ready : 'Detectado e importable' ,
onboarding _check _agent _missing : 'Falta o solo es parcialmente importable' ,
onboarding _check _password : 'Contraseña' ,
onboarding _check _password _enabled : 'Ya está activada' ,
onboarding _check _password _disabled : 'Todavía no está activada' ,
onboarding _check _provider : 'Configuración del proveedor' ,
onboarding _check _provider _ready : 'Listo para chatear' ,
onboarding _check _provider _partial : 'Guardado pero incompleto' ,
onboarding _check _provider _pending : 'Necesita verificación' ,
onboarding _config _file : 'Archivo de configuración:' ,
onboarding _env _file : 'Archivo .env:' ,
onboarding _unknown : 'Desconocido' ,
onboarding _current _provider : 'Configuración actual:' ,
onboarding _missing _imports : 'Importaciones faltantes:' ,
onboarding _notice _setup _required : 'Elige aquí una ruta simple de proveedor. Los flujos OAuth avanzados siguen siendo del CLI de Hermes por ahora.' ,
onboarding _notice _setup _already _ready : 'Ya se detectó una configuración funcional del proveedor de Hermes. Puedes conservarla o reemplazarla aquí.' ,
2026-04-12 21:28:16 +00:00
onboarding _oauth _provider _ready _title : 'Proveedor ya autenticado' ,
onboarding _oauth _provider _ready _body : 'Esta instancia está configurada para usar un proveedor OAuth (<strong>{provider}</strong>) configurado mediante la CLI de Hermes. No se necesita clave API aquí — haz clic en Continuar para finalizar la configuración.' ,
onboarding _oauth _provider _not _ready _title : 'Proveedor OAuth no autenticado aún' ,
onboarding _oauth _provider _not _ready _body : 'Esta instancia está configurada para usar <strong>{provider}</strong>, que utiliza OAuth en lugar de una clave API. Ejecuta <code>hermes auth</code> o <code>hermes model</code> en una terminal para autenticarte y recarga la interfaz web.' ,
onboarding _oauth _switch _hint : 'O elige un proveedor diferente a continuación para cambiar a la configuración con clave API:' ,
2026-04-12 07:11:41 +00:00
onboarding _notice _workspace : 'Estos valores reutilizan las mismas APIs de configuración que la app normal.' ,
onboarding _workspace _label : 'Espacio de trabajo' ,
onboarding _workspace _or _path : 'O introduce la ruta de un espacio de trabajo' ,
onboarding _workspace _placeholder : '/home/you/workspace' ,
onboarding _provider _label : 'Modo de configuración' ,
onboarding _quick _setup _badge : 'configuración rápida' ,
onboarding _api _key _label : 'API key' ,
onboarding _api _key _placeholder : 'Déjala en blanco para conservar una key ya guardada' ,
onboarding _api _key _help _prefix : 'Se guarda como secreto en tu archivo .env de Hermes usando' ,
onboarding _base _url _label : 'Base URL' ,
onboarding _base _url _placeholder : 'https://tu-endpoint.example/v1' ,
onboarding _base _url _help : 'Úsalo para routers OpenAI-compatible, servidores autoalojados, LiteLLM, Ollama, LM Studio, vLLM o endpoints parecidos.' ,
onboarding _model _label : 'Modelo predeterminado' ,
onboarding _workspace _help : 'Elige el modelo que Hermes debe usar para nuevos chats cuando termine la configuración.' ,
onboarding _custom _model _placeholder : 'tu-modelo' ,
onboarding _custom _model _help : 'Para endpoints personalizados, introduce el identificador exacto del modelo que espera tu servidor.' ,
onboarding _notice _password _enabled : 'Ya hay una contraseña configurada. Introduce una nueva solo si quieres reemplazarla.' ,
onboarding _notice _password _recommended : 'Es opcional, pero recomendable si vas a exponer la UI más allá de localhost.' ,
onboarding _password _label : 'Contraseña (opcional)' ,
onboarding _password _placeholder : 'Déjala en blanco para omitirla' ,
onboarding _password _help : 'Las contraseñas se guardan mediante la API de configuración existente y se hashean en el servidor.' ,
onboarding _notice _finish : 'Puedes volver a abrir Configuración más tarde para cambiar cualquiera de estos valores.' ,
onboarding _not _set : 'Sin definir' ,
onboarding _password _will _enable : 'Se activará' ,
2026-04-14 12:51:19 +00:00
onboarding _password _will _replace : 'Se reemplazará' ,
onboarding _password _keep _existing : 'Mantener la contraseña actual' ,
onboarding _password _remains _disabled : 'Seguirá desactivada' ,
2026-04-12 07:11:41 +00:00
onboarding _password _skipped : 'Se omitirá por ahora' ,
onboarding _finish _help : 'Al finalizar se guarda <code>onboarding_completed</code> en la configuración y entras en la app normal.' ,
onboarding _error _choose _workspace : 'Elige un espacio de trabajo antes de continuar.' ,
onboarding _error _choose _model : 'Elige un modelo antes de continuar.' ,
onboarding _error _provider _required : 'Elige un modo de configuración antes de continuar.' ,
onboarding _error _base _url _required : 'La base URL es obligatoria para endpoints personalizados.' ,
onboarding _error _workspace _required : 'El espacio de trabajo es obligatorio.' ,
onboarding _error _model _required : 'El modelo es obligatorio.' ,
onboarding _complete : 'Onboarding completado' ,
2026-04-14 17:14:01 +00:00
// panel/runtime i18n
error _prefix : 'Error: ' ,
not _available : 'N/A' ,
never : 'never' ,
add : 'Add' ,
add _failed : 'Add failed: ' ,
remove _failed : 'Remove failed: ' ,
switch _failed : 'Switch failed: ' ,
name _required : 'Name is required' ,
content _required : 'Content is required' ,
view : 'View' ,
dismiss : 'Dismiss' ,
disable : 'Disable' ,
cron _no _jobs : 'No scheduled jobs found.' ,
cron _status _off : 'off' ,
cron _status _paused : 'paused' ,
cron _status _error : 'error' ,
cron _status _active : 'active' ,
cron _next : 'Next' ,
cron _last : 'Last' ,
cron _run _now : 'Run now' ,
cron _pause : 'Pause' ,
cron _resume : 'Resume' ,
cron _job _name _placeholder : 'Job name' ,
cron _schedule _placeholder : 'Schedule' ,
cron _prompt _placeholder : 'Prompt' ,
cron _last _output : 'Last output' ,
cron _all _runs : 'All runs' ,
cron _hide _runs : 'Hide runs' ,
cron _no _runs _yet : '(no runs yet)' ,
cron _schedule _required _example : 'Schedule is required (e.g. "0 9 * * *" or "every 1h")' ,
cron _schedule _required : 'Schedule is required' ,
cron _prompt _required : 'Prompt is required' ,
cron _job _created : 'Job created' ,
cron _job _triggered : 'Job triggered' ,
cron _job _paused : 'Job paused' ,
cron _job _resumed : 'Job resumed' ,
cron _job _updated : 'Job updated' ,
cron _delete _confirm _title : 'Delete cron job' ,
cron _delete _confirm _message : 'This cannot be undone.' ,
cron _job _deleted : 'Job deleted' ,
cron _completion _status : ( name , status ) => ` Cron " ${ name } " ${ status } ` ,
status _failed : 'failed' ,
status _completed : 'completed' ,
todos _no _active : 'No active task list in this session.' ,
clear _conversation _title : 'Clear conversation' ,
clear _conversation _message : 'Clear all messages? This cannot be undone.' ,
clear _failed : 'Clear failed: ' ,
skills _no _match : 'No skills match.' ,
linked _files : 'Linked Files' ,
skill _load _failed : 'Could not load skill: ' ,
skill _file _load _failed : 'Could not load file: ' ,
skill _name _required : 'Skill name is required' ,
skill _updated : 'Skill updated' ,
skill _created : 'Skill created' ,
memory _notes _label : 'memory (notes)' ,
memory _saved : 'Memory saved' ,
my _notes : 'My Notes' ,
user _profile : 'User Profile' ,
no _notes _yet : 'No notes yet.' ,
no _profile _yet : 'No profile yet.' ,
workspace _choose _path : 'Choose workspace path' ,
workspace _choose _path _meta : 'Add a validated path and switch this conversation' ,
workspace _manage : 'Manage workspaces' ,
workspace _manage _meta : 'Open the Spaces panel' ,
workspace _use _title : 'Use in current session' ,
workspace _use : 'Use' ,
workspace _add _path _placeholder : 'Add workspace path (e.g. /home/user/my-project)' ,
workspace _paths _validated _hint : 'Paths are validated as existing directories before saving.' ,
workspace _added : 'Workspace added' ,
workspace _remove _confirm _title : 'Remove workspace' ,
workspace _remove _confirm _message : ( path ) => ` Remove " ${ path } "? ` ,
workspace _removed : 'Workspace removed' ,
workspace _switch _prompt _title : 'Switch workspace' ,
workspace _switch _prompt _message : 'Enter an absolute workspace path to add and switch this conversation to.' ,
workspace _switch _prompt _confirm : 'Switch' ,
workspace _switch _prompt _placeholder : '/Users/you/project' ,
workspace _not _added : 'Workspace was not added' ,
workspace _already _saved : 'Workspace already saved — choose it from the list' ,
workspace _busy _switch : 'Cannot switch workspace while agent is running' ,
discard _file _edits _title : 'Discard file edits?' ,
discard _file _edits _message : 'Switching workspaces will discard unsaved file edits in the preview.' ,
workspace _switched _to : ( name ) => ` Switched to ${ name } ` ,
profiles _no _profiles : 'No profiles found.' ,
profile _api _keys _configured : 'API keys configured' ,
profile _gateway _running : 'Gateway running' ,
profile _gateway _stopped : 'Gateway stopped' ,
profile _active : 'ACTIVE' ,
profile _no _configuration : 'No configuration' ,
2026-04-14 17:14:01 +00:00
profile _skill _count : ( count ) => ` ${ count } habilidad ${ count === 1 ? '' : 'es' } ` ,
2026-04-14 17:14:01 +00:00
profile _use : 'Use' ,
profile _switch _title : 'Switch to this profile' ,
2026-04-14 17:14:01 +00:00
profile _delete _title : 'Eliminar este perfil' ,
2026-04-14 17:14:01 +00:00
manage _profiles : 'Manage profiles' ,
profiles _load _failed : 'Failed to load profiles' ,
profiles _busy _switch : 'Cannot switch profiles while agent is running' ,
profile _switched _new _conversation : ( name ) => ` Switched to profile: ${ name } — new conversation started ` ,
profile _switched : ( name ) => ` Switched to profile: ${ name } ` ,
profile _name _rule : 'Lowercase letters, numbers, hyphens, underscores only' ,
profile _base _url _rule : 'Base URL must start with http:// or https://' ,
profile _created : ( name ) => ` Profile created: ${ name } ` ,
profile _delete _confirm _title : ( name ) => ` Delete profile " ${ name } "? ` ,
profile _delete _confirm _message : 'This removes all config, skills, memory, and sessions for this profile.' ,
profile _deleted : ( name ) => ` Profile deleted: ${ name } ` ,
active _conversation _none : 'No active conversation selected.' ,
active _conversation _meta : ( title , count ) => ` ${ title } · ${ count } message ${ count === 1 ? '' : 's' } ` ,
settings _unsaved _changes : 'You have unsaved changes.' ,
sign _out _failed : 'Sign out failed: ' ,
disable _auth _confirm _title : 'Disable password protection' ,
disable _auth _confirm _message : 'Anyone will be able to access this instance.' ,
auth _disabled : 'Auth disabled — password protection removed' ,
disable _auth _failed : 'Failed to disable auth: ' ,
bg _error _single : ( title ) => ` " ${ title } " has encountered an error ` ,
bg _error _multi : ( count ) => ` ${ count } sessions have encountered an error ` ,
2026-04-12 03:06:37 +00:00
} ,
2026-04-10 01:35:23 +00:00
de : {
_lang : 'de' ,
_label : 'Deutsch' ,
_speech : 'de-DE' ,
// boot.js
2026-04-10 02:04:48 +00:00
cancelling : 'Wird abgebrochen\u2026' ,
2026-04-10 01:35:23 +00:00
cancel _failed : 'Abbrechen fehlgeschlagen: ' ,
mic _denied : 'Mikrofonzugriff verweigert. Überprüfen Sie die Browserberechtigungen.' ,
mic _no _speech : 'Keine Sprache erkannt. Versuchen Sie es erneut.' ,
mic _network : 'Spracherkennung nicht verfügbar.' ,
mic _error : 'Spracheingabefehler: ' ,
session _imported : 'Sitzung importiert' ,
import _failed : 'Import fehlgeschlagen: ' ,
import _invalid _json : 'Ungültiges JSON' ,
image _pasted : 'Bild eingefügt: ' ,
// messages.js
edit _message : 'Nachricht bearbeiten' ,
regenerate : 'Antwort regenerieren' ,
copy : 'Kopieren' ,
copied : 'Kopiert!' ,
you : 'Du' ,
thinking : 'Nachdenken' ,
expand _all : 'Alle ausklappen' ,
collapse _all : 'Alle einklappen' ,
edit _failed : 'Bearbeiten fehlgeschlagen: ' ,
regen _failed : 'Regeneration fehlgeschlagen: ' ,
reconnect _active : 'Eine Antwort wird noch generiert. Neu laden, wenn bereit?' ,
reconnect _finished : 'Eine Antwort war in Arbeit, als Sie zuletzt gegangen sind. Nachrichten könnten aktualisiert worden sein.' ,
// approval card
approval _heading : 'Genehmigung erforderlich' ,
approval _desc _prefix : 'Gefährlicher Befehl erkannt' ,
approval _btn _once : 'Einmal zulassen' ,
approval _btn _once _title : 'Diesen einen Befehl zulassen (Enter)' ,
approval _btn _session : 'Sitzung zulassen' ,
approval _btn _session _title : 'Für diese Konversationssitzung zulassen' ,
approval _btn _always : 'Immer zulassen' ,
approval _btn _always _title : 'Dieses Befehlsmuster immer zulassen' ,
approval _btn _deny : 'Ablehnen' ,
approval _btn _deny _title : 'Ablehnen \u2014 diesen Befehl nicht ausführen' ,
approval _responding : 'Antwortet\u2026' ,
untitled : 'Unbenannt' ,
n _messages : ( n ) => ` ${ n } Nachrichten ` ,
model _unavailable : ' (nicht verfügbar)' ,
model _unavailable _title : 'Dieses Modell ist nicht mehr in Ihrer aktuellen Provider-Liste' ,
fix: warn on provider/model mismatch, surface auth errors (#266)
* fix: warn on provider/model mismatch, surface auth errors (#266)
Fixes #266 — WebUI silently ignores provider/model selection mismatch.
The problem: selecting an OpenRouter (or Anthropic/OpenAI) model while
Hermes is configured for a different provider (e.g. local Ollama) sends
the request to the wrong endpoint, which returns a 401 Unauthorized error
with no UI indication of why.
Three-layer fix:
1. api/streaming.py — detect 401/auth errors explicitly
Added is_auth_error detection covering '401', 'AuthenticationError',
'authentication', 'unauthorized', 'invalid api key', and the specific
Ollama error string 'no cookie auth credentials'. Auth errors emit
apperror with type='auth_mismatch' and a hint pointing to 'hermes model'.
2. static/ui.js — expose active_provider and warn on selection
- populateModelDropdown() stores data.active_provider from /api/models
as window._activeProvider (the field was already in the response but
the frontend never used it)
- New _checkProviderMismatch(modelId) helper: compares the selected
model's slash-prefix (e.g. 'openai/' from 'openai/gpt-4o') against
the active provider. Skips the check for 'openrouter' and 'custom'
to avoid false positives on configs that legitimately route any model.
3. static/boot.js — warn on model dropdown change
modelSelect.onchange calls _checkProviderMismatch() and shows a toast
when the selected model looks incompatible with the configured provider.
4. static/messages.js — distinct UI label for auth errors
apperror handler now distinguishes type='auth_mismatch' and shows
'Provider mismatch' as the error label instead of 'Error'.
5. static/i18n.js — provider_mismatch_warning and provider_mismatch_label
keys added to all 5 locales (en, es, de, zh-Hans, zh-Hant).
Tests: 21 new tests in tests/test_provider_mismatch.py covering all
five change areas. 679/679 total pass (658 baseline + 21 new).
* fix: t() call args spread + use i18n label for auth mismatch
1. ui.js: _checkProviderMismatch passed [modelId, ap] as a single
array arg to t(). Since t(key, ...args) spreads, the function
received the array as m and undefined as p. Fixed to pass as
separate args: t('provider_mismatch_warning', modelId, ap).
2. messages.js: 'Provider mismatch' label was hardcoded instead of
using t('provider_mismatch_label'). Now uses the i18n key with
fallback for when t() isn't available.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 04:25:18 +00:00
provider _mismatch _warning : ( m , p ) => ` " ${ m } " funktioniert möglicherweise nicht mit Ihrem konfigurierten Provider ( ${ p } ). Trotzdem senden, oder \` hermes model \` im Terminal ausführen. ` ,
provider _mismatch _label : 'Provider-Konflikt' ,
2026-04-10 01:35:23 +00:00
// commands.js
cmd _help : 'Verfügbare Befehle auflisten' ,
cmd _clear : 'Konversationsverlauf löschen' ,
cmd _compact : 'Kontext komprimieren' ,
cmd _model : 'Modell wechseln (z.B. /model gpt-4o)' ,
cmd _workspace : 'Workspace nach Namen wechseln' ,
cmd _new : 'Neue Chat-Sitzung starten' ,
cmd _usage : 'Token-Verbrauchsanzeige umschalten' ,
cmd _theme : 'Theme wechseln (dark/light/slate/solarized/monokai/nord/oled)' ,
cmd _personality : 'Agenten-Persönlichkeit wechseln' ,
v0.47.0: dialogs, session menu, /skills, mobile fixes, mobile QA suite
* fix: custom provider with slash model name no longer rerouted to OpenRouter (#255)
When base_url is configured in config.yaml, resolve_model_provider() now
trusts the configured provider/base_url entirely and skips the slash-based
OpenRouter heuristic. Fixes google/gemma-4-26b-a4b with provider:custom
being silently routed to OpenRouter, resulting in 401 errors.
Fixes #230
* test: mobile layout regression suite — 14 tests for every QA run (#254)
Adds tests/test_mobile_layout.py with 14 static regression tests that run
on every QA pass to catch mobile layout breakage before it reaches prod.
Covers: breakpoints at 900px/640px, right panel slide-over CSS, mobile
overlay, bottom nav, files button, profile dropdown z-index, chip overflow,
workspace close, 100dvh, 44px touch targets, 16px font-size on textarea.
* feat: /skills slash command lists and filters available Hermes skills (#257)
Adds /skills [query] command to commands.js. Fetches from /api/skills,
groups by category (alphabetically sorted), displays as a formatted
assistant message. Optional query filters by name, description, or category.
i18n keys added for en, de, zh, zh-Hant. 1 regression test added.
Fixes #248
* feat: shared app dialogs replace native confirm()/prompt() calls (#251)
Adds showConfirmDialog() and showPromptDialog() helpers to ui.js, backed
by a themed #appDialogOverlay. Replaces all 11 native browser confirm/prompt
call sites across panels.js, sessions.js, ui.js, workspace.js.
Supports: danger mode, keyboard focus trap (Tab/Escape/Enter), focus restore,
ARIA roles, mobile-responsive stacked buttons at 640px. i18n for en/de/zh/zh-Hant.
5 new tests in test_sprint33.py verify markup, CSS, helpers, and absence of
native dialog calls.
Extracted from PR #242.
* fix: Android Chrome mobile — workspace panel close + profile dropdown (#256)
Fix #247: toggleMobileFiles() now shows/hides the mobile overlay when
toggling the right workspace panel. New closeMobileFiles() helper closes
the panel with correct overlay state tracking. Overlay onclick calls both
closeMobileSidebar() and closeMobileFiles(). Mobile-only close button (x)
added to workspace panel header.
Fix #246: profile dropdown uses position:fixed;top:56px;right:8px at
max-width:900px, escaping the overflow-x:auto stacking context that was
clipping it on Android Chrome.
Fix applied during review: closeMobileSidebar() now checks if the right
panel is still open before hiding the overlay, preventing the overlay from
disappearing when only the sidebar is closed.
Fixes #247 Fixes #246
* feat: session ⋯ action dropdown replaces per-row buttons (#252)
Replaces the 5 per-row hover action buttons (pin/move/archive/duplicate/trash)
with a single ⋯ trigger that opens a positioned dropdown menu. Menu has full
keyboard (Escape), click-outside, scroll, and resize-reposition handling.
Position:fixed prevents sidebar clipping.
5 actions: Pin/Unpin, Move to project, Archive/Unarchive, Duplicate, Delete
(danger style). Each with icon and descriptive subtitle.
Updated test_sprint16.py: test_sessions_js_uses_action_menu_not_per_row_buttons
asserts the new trigger and menu functions exist, old per-row classes are gone.
Extracted from PR #242.
* docs: v0.47.0 release notes, bump version, update test counts (645)
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 19:19:12 +00:00
cmd _skills : 'Verfügbare Hermes-Skills auflisten' ,
2026-04-10 01:35:23 +00:00
available _commands : 'Verfügbare Befehle:' ,
type _slash : 'Tippe / für Befehle' ,
conversation _cleared : 'Konversation gelöscht' ,
model _usage : 'Nutzung: /model <name>' ,
no _model _match : 'Kein Modell gefunden für "' ,
switched _to : 'Gewechselt zu ' ,
workspace _usage : 'Nutzung: /workspace <name>' ,
no _workspace _match : 'Kein Workspace gefunden für "' ,
switched _workspace : 'Gewechselt zu Workspace: ' ,
workspace _switch _failed : 'Workspace-Wechsel fehlgeschlagen: ' ,
new _session : 'Neue Sitzung erstellt' ,
compressing : 'Kontext-Komprimierung wird angefordert...' ,
token _usage _on : 'Token-Verbrauch an' ,
token _usage _off : 'Token-Verbrauch aus' ,
theme _usage : 'Nutzung: /theme ' ,
theme _set : 'Theme: ' ,
no _active _session : 'Keine aktive Sitzung' ,
no _personalities : 'Keine Persönlichkeiten gefunden (füge sie in ~/.hermes/personalities/ hinzu)' ,
available _personalities : 'Verfügbare Persönlichkeiten:' ,
personality _switch _hint : '\n\nNutze `/personality <name>` zum Wechseln, oder `/personality none` zum Löschen.' ,
personalities _load _failed : 'Fehler beim Laden der Persönlichkeiten' ,
personality _cleared : 'Persönlichkeit gelöscht' ,
personality _set : 'Persönlichkeit: ' ,
failed _colon : 'Fehlgeschlagen: ' ,
// ui.js
no _workspace : 'Kein Workspace' ,
feat: composer-centric UI refresh + Hermes Control Center (v0.50.0, closes #242)
* Polish workspace panel behavior and app dialogs
* Replace remaining emoji UI glyphs with Lucide icons
* Redesign composer footer around model and context controls
Move the model selector into the composer footer, replace the linear context pill with a compact circular badge plus tooltip, and remove the redundant topbar model pill.
Design credit and inspiration: Theo / T3 Code.
Reference implementation: https://github.com/pingdotgg/t3code/
* Remove obsolete activity bar
Drop the old activity bar, keep turn-scoped state in the composer footer, and route remaining non-chat status messages through toasts.
This leaves live tool cards and the message timeline as the primary progress UI, with the composer owning stop/cancel and brief turn status.
* Move workspace and model switching into composer footer
* Move profile switching into composer footer
* Refactor Hermes control center UI
* Redesign control center settings modal layout
Widen the modal to 860px, simplify the tab list to icon+label rows,
stretch the tab column's divider to full height, lock the panel to a
fixed height so switching tabs no longer resizes the outer shell, and
always open on the Conversation tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Put session item actions in a dropdown
* Use Hermes mark in sidebar control button
* Reset control center section on close
* Drop session-item left border indicator
Remove the left-border accent used for active, CLI, and project rows —
each state already has a dedicated cue (gold fill, cli badge, project
dot), so the border was redundant. Fully round the row, add 2px
bottom spacing between rows, and strip the matching JS/CSS overrides.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Increase session search input vertical padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Normalise odd pixel values across UI
Snap padding, gap, and border-radius values to the 2/4/6/8/10/12 grid
across composer chips, sidebar panels, cron list, settings, approval
buttons, dropdowns, and inline message edit — eliminating the 7/9/11px
drift that was making sibling elements feel subtly misaligned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add missing #btnMobileFiles button and .mobile-files-btn CSS (for mobile QA suite)
The mobile layout regression suite (test_mobile_layout.py) requires:
- #btnMobileFiles onclick=toggleMobileFiles() in topbar chips
- .mobile-files-btn CSS rules for responsive show/hide at 640/900px breakpoints
Also adds max-width guard to .profile-dropdown to prevent clipping at narrow viewports.
* Improve composer footer mobile responsiveness and UX
- Collapse composer chips to icon-only at <=400px viewports
- Add model chip icon (CPU) so it remains tappable when labels are hidden
- Show send button always (disabled state when empty, hidden during streaming)
- Show context usage indicator on session load, not just after streaming
- Add cancel status fallback timeout to prevent stale "Cancelling..." text
- Update tests to match new send button and busy state behavior
* Fix duplicate files button and broken workspace close on mobile
Remove redundant #btnMobileFiles button that duplicated #btnWorkspacePanelToggle
in the mobile topbar. Fix workspace panel close button calling undefined
closeMobileFiles() — now calls closeWorkspacePanel().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix model chip icon vertical alignment in composer footer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix workspace toggle button hidden on desktop by conflicting CSS class
Remove mobile-files-btn class from #btnWorkspacePanelToggle — its
display:none!important rule was overriding workspace-toggle-btn visibility
on non-mobile viewports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix session actions dots button inaccessible on mobile sidebar
Always show the session actions trigger on mobile (no hover state on
touch devices) and restore right padding so text truncates with
ellipsis before the dots icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer footer manage links not opening sidebar panel
The "Manage profiles" and "Manage workspaces" links in the composer
footer dropdowns called switchPanel() which only changes the active
panel content but doesn't open the sidebar. Replaced with
mobileSwitchPanel() which also opens the sidebar so the panel is
actually visible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Widen icon-only composer chips breakpoint from 400px to 768px
Move the icon-only chip styling up into the existing max-width:768px
media query so chips collapse to icon-only on tablets too, preventing
composer footer overflow on mid-size screens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer-left vertical scrollbar by setting overflow-y:hidden
When overflow-x is set to auto, the CSS spec implicitly changes
overflow-y from visible to auto, allowing a vertical scrollbar to
appear from slight chip padding/border overflow. Explicitly set
overflow-y:hidden to prevent this.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve rebase conflicts and fix control center test assertions
- Resolved 4 conflicts during rebase onto master (workspace.js,
boot.js, index.html, test_sprint34.py)
- Fixed test_sprint34.py: _controlSection -> _settingsSection,
cc-tab -> settings-tabs (matching actual implementation)
- Fixed quoting syntax error in test assertion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update version badge in System tab to v0.49.4
* docs: update README and CHANGELOG for v0.50.0 UI refresh, bump version badge
---------
Co-authored-by: Aron Prins <pwf.aron@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 18:55:40 +00:00
dialog _confirm _title : 'Aktion bestätigen' ,
dialog _prompt _title : 'Wert eingeben' ,
dialog _confirm _btn : 'Bestätigen' ,
2026-04-10 01:35:23 +00:00
// workspace.js
unsaved _confirm : 'Sie haben ungespeicherte Änderungen in der Vorschau. Verwerfen und fortfahren?' ,
feat: composer-centric UI refresh + Hermes Control Center (v0.50.0, closes #242)
* Polish workspace panel behavior and app dialogs
* Replace remaining emoji UI glyphs with Lucide icons
* Redesign composer footer around model and context controls
Move the model selector into the composer footer, replace the linear context pill with a compact circular badge plus tooltip, and remove the redundant topbar model pill.
Design credit and inspiration: Theo / T3 Code.
Reference implementation: https://github.com/pingdotgg/t3code/
* Remove obsolete activity bar
Drop the old activity bar, keep turn-scoped state in the composer footer, and route remaining non-chat status messages through toasts.
This leaves live tool cards and the message timeline as the primary progress UI, with the composer owning stop/cancel and brief turn status.
* Move workspace and model switching into composer footer
* Move profile switching into composer footer
* Refactor Hermes control center UI
* Redesign control center settings modal layout
Widen the modal to 860px, simplify the tab list to icon+label rows,
stretch the tab column's divider to full height, lock the panel to a
fixed height so switching tabs no longer resizes the outer shell, and
always open on the Conversation tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Put session item actions in a dropdown
* Use Hermes mark in sidebar control button
* Reset control center section on close
* Drop session-item left border indicator
Remove the left-border accent used for active, CLI, and project rows —
each state already has a dedicated cue (gold fill, cli badge, project
dot), so the border was redundant. Fully round the row, add 2px
bottom spacing between rows, and strip the matching JS/CSS overrides.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Increase session search input vertical padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Normalise odd pixel values across UI
Snap padding, gap, and border-radius values to the 2/4/6/8/10/12 grid
across composer chips, sidebar panels, cron list, settings, approval
buttons, dropdowns, and inline message edit — eliminating the 7/9/11px
drift that was making sibling elements feel subtly misaligned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add missing #btnMobileFiles button and .mobile-files-btn CSS (for mobile QA suite)
The mobile layout regression suite (test_mobile_layout.py) requires:
- #btnMobileFiles onclick=toggleMobileFiles() in topbar chips
- .mobile-files-btn CSS rules for responsive show/hide at 640/900px breakpoints
Also adds max-width guard to .profile-dropdown to prevent clipping at narrow viewports.
* Improve composer footer mobile responsiveness and UX
- Collapse composer chips to icon-only at <=400px viewports
- Add model chip icon (CPU) so it remains tappable when labels are hidden
- Show send button always (disabled state when empty, hidden during streaming)
- Show context usage indicator on session load, not just after streaming
- Add cancel status fallback timeout to prevent stale "Cancelling..." text
- Update tests to match new send button and busy state behavior
* Fix duplicate files button and broken workspace close on mobile
Remove redundant #btnMobileFiles button that duplicated #btnWorkspacePanelToggle
in the mobile topbar. Fix workspace panel close button calling undefined
closeMobileFiles() — now calls closeWorkspacePanel().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix model chip icon vertical alignment in composer footer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix workspace toggle button hidden on desktop by conflicting CSS class
Remove mobile-files-btn class from #btnWorkspacePanelToggle — its
display:none!important rule was overriding workspace-toggle-btn visibility
on non-mobile viewports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix session actions dots button inaccessible on mobile sidebar
Always show the session actions trigger on mobile (no hover state on
touch devices) and restore right padding so text truncates with
ellipsis before the dots icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer footer manage links not opening sidebar panel
The "Manage profiles" and "Manage workspaces" links in the composer
footer dropdowns called switchPanel() which only changes the active
panel content but doesn't open the sidebar. Replaced with
mobileSwitchPanel() which also opens the sidebar so the panel is
actually visible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Widen icon-only composer chips breakpoint from 400px to 768px
Move the icon-only chip styling up into the existing max-width:768px
media query so chips collapse to icon-only on tablets too, preventing
composer footer overflow on mid-size screens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer-left vertical scrollbar by setting overflow-y:hidden
When overflow-x is set to auto, the CSS spec implicitly changes
overflow-y from visible to auto, allowing a vertical scrollbar to
appear from slight chip padding/border overflow. Explicitly set
overflow-y:hidden to prevent this.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve rebase conflicts and fix control center test assertions
- Resolved 4 conflicts during rebase onto master (workspace.js,
boot.js, index.html, test_sprint34.py)
- Fixed test_sprint34.py: _controlSection -> _settingsSection,
cc-tab -> settings-tabs (matching actual implementation)
- Fixed quoting syntax error in test assertion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update version badge in System tab to v0.49.4
* docs: update README and CHANGELOG for v0.50.0 UI refresh, bump version badge
---------
Co-authored-by: Aron Prins <pwf.aron@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 18:55:40 +00:00
discard : 'Verwerfen' ,
2026-04-10 01:35:23 +00:00
save : 'Speichern' ,
edit : 'Bearbeiten' ,
feat: composer-centric UI refresh + Hermes Control Center (v0.50.0, closes #242)
* Polish workspace panel behavior and app dialogs
* Replace remaining emoji UI glyphs with Lucide icons
* Redesign composer footer around model and context controls
Move the model selector into the composer footer, replace the linear context pill with a compact circular badge plus tooltip, and remove the redundant topbar model pill.
Design credit and inspiration: Theo / T3 Code.
Reference implementation: https://github.com/pingdotgg/t3code/
* Remove obsolete activity bar
Drop the old activity bar, keep turn-scoped state in the composer footer, and route remaining non-chat status messages through toasts.
This leaves live tool cards and the message timeline as the primary progress UI, with the composer owning stop/cancel and brief turn status.
* Move workspace and model switching into composer footer
* Move profile switching into composer footer
* Refactor Hermes control center UI
* Redesign control center settings modal layout
Widen the modal to 860px, simplify the tab list to icon+label rows,
stretch the tab column's divider to full height, lock the panel to a
fixed height so switching tabs no longer resizes the outer shell, and
always open on the Conversation tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Put session item actions in a dropdown
* Use Hermes mark in sidebar control button
* Reset control center section on close
* Drop session-item left border indicator
Remove the left-border accent used for active, CLI, and project rows —
each state already has a dedicated cue (gold fill, cli badge, project
dot), so the border was redundant. Fully round the row, add 2px
bottom spacing between rows, and strip the matching JS/CSS overrides.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Increase session search input vertical padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Normalise odd pixel values across UI
Snap padding, gap, and border-radius values to the 2/4/6/8/10/12 grid
across composer chips, sidebar panels, cron list, settings, approval
buttons, dropdowns, and inline message edit — eliminating the 7/9/11px
drift that was making sibling elements feel subtly misaligned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add missing #btnMobileFiles button and .mobile-files-btn CSS (for mobile QA suite)
The mobile layout regression suite (test_mobile_layout.py) requires:
- #btnMobileFiles onclick=toggleMobileFiles() in topbar chips
- .mobile-files-btn CSS rules for responsive show/hide at 640/900px breakpoints
Also adds max-width guard to .profile-dropdown to prevent clipping at narrow viewports.
* Improve composer footer mobile responsiveness and UX
- Collapse composer chips to icon-only at <=400px viewports
- Add model chip icon (CPU) so it remains tappable when labels are hidden
- Show send button always (disabled state when empty, hidden during streaming)
- Show context usage indicator on session load, not just after streaming
- Add cancel status fallback timeout to prevent stale "Cancelling..." text
- Update tests to match new send button and busy state behavior
* Fix duplicate files button and broken workspace close on mobile
Remove redundant #btnMobileFiles button that duplicated #btnWorkspacePanelToggle
in the mobile topbar. Fix workspace panel close button calling undefined
closeMobileFiles() — now calls closeWorkspacePanel().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix model chip icon vertical alignment in composer footer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix workspace toggle button hidden on desktop by conflicting CSS class
Remove mobile-files-btn class from #btnWorkspacePanelToggle — its
display:none!important rule was overriding workspace-toggle-btn visibility
on non-mobile viewports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix session actions dots button inaccessible on mobile sidebar
Always show the session actions trigger on mobile (no hover state on
touch devices) and restore right padding so text truncates with
ellipsis before the dots icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer footer manage links not opening sidebar panel
The "Manage profiles" and "Manage workspaces" links in the composer
footer dropdowns called switchPanel() which only changes the active
panel content but doesn't open the sidebar. Replaced with
mobileSwitchPanel() which also opens the sidebar so the panel is
actually visible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Widen icon-only composer chips breakpoint from 400px to 768px
Move the icon-only chip styling up into the existing max-width:768px
media query so chips collapse to icon-only on tablets too, preventing
composer footer overflow on mid-size screens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer-left vertical scrollbar by setting overflow-y:hidden
When overflow-x is set to auto, the CSS spec implicitly changes
overflow-y from visible to auto, allowing a vertical scrollbar to
appear from slight chip padding/border overflow. Explicitly set
overflow-y:hidden to prevent this.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve rebase conflicts and fix control center test assertions
- Resolved 4 conflicts during rebase onto master (workspace.js,
boot.js, index.html, test_sprint34.py)
- Fixed test_sprint34.py: _controlSection -> _settingsSection,
cc-tab -> settings-tabs (matching actual implementation)
- Fixed quoting syntax error in test assertion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update version badge in System tab to v0.49.4
* docs: update README and CHANGELOG for v0.50.0 UI refresh, bump version badge
---------
Co-authored-by: Aron Prins <pwf.aron@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 18:55:40 +00:00
clear : 'Leeren' ,
create : 'Erstellen' ,
remove : 'Entfernen' ,
2026-04-10 01:35:23 +00:00
save _title : 'Änderungen speichern' ,
edit _title : 'Diese Datei bearbeiten' ,
saved : 'Gespeichert' ,
save _failed : 'Speichern fehlgeschlagen: ' ,
image _load _failed : 'Bild konnte nicht geladen werden' ,
file _open _failed : 'Datei konnte nicht geöffnet werden' ,
downloading : ( name ) => ` Lade ${ name } herunter \u 2026 ` ,
double _click _rename : 'Doppelklick zum Umbenennen' ,
renamed _to : 'Umbenannt in ' ,
rename _failed : 'Umbenennen fehlgeschlagen: ' ,
delete _title : 'Löschen' ,
delete _confirm : ( name ) => ` ${ name } löschen? ` ,
deleted : 'Gelöscht ' ,
delete _failed : 'Löschen fehlgeschlagen: ' ,
new _file _prompt : 'Neuer Dateiname (z.B. notes.md):' ,
feat: composer-centric UI refresh + Hermes Control Center (v0.50.0, closes #242)
* Polish workspace panel behavior and app dialogs
* Replace remaining emoji UI glyphs with Lucide icons
* Redesign composer footer around model and context controls
Move the model selector into the composer footer, replace the linear context pill with a compact circular badge plus tooltip, and remove the redundant topbar model pill.
Design credit and inspiration: Theo / T3 Code.
Reference implementation: https://github.com/pingdotgg/t3code/
* Remove obsolete activity bar
Drop the old activity bar, keep turn-scoped state in the composer footer, and route remaining non-chat status messages through toasts.
This leaves live tool cards and the message timeline as the primary progress UI, with the composer owning stop/cancel and brief turn status.
* Move workspace and model switching into composer footer
* Move profile switching into composer footer
* Refactor Hermes control center UI
* Redesign control center settings modal layout
Widen the modal to 860px, simplify the tab list to icon+label rows,
stretch the tab column's divider to full height, lock the panel to a
fixed height so switching tabs no longer resizes the outer shell, and
always open on the Conversation tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Put session item actions in a dropdown
* Use Hermes mark in sidebar control button
* Reset control center section on close
* Drop session-item left border indicator
Remove the left-border accent used for active, CLI, and project rows —
each state already has a dedicated cue (gold fill, cli badge, project
dot), so the border was redundant. Fully round the row, add 2px
bottom spacing between rows, and strip the matching JS/CSS overrides.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Increase session search input vertical padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Normalise odd pixel values across UI
Snap padding, gap, and border-radius values to the 2/4/6/8/10/12 grid
across composer chips, sidebar panels, cron list, settings, approval
buttons, dropdowns, and inline message edit — eliminating the 7/9/11px
drift that was making sibling elements feel subtly misaligned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add missing #btnMobileFiles button and .mobile-files-btn CSS (for mobile QA suite)
The mobile layout regression suite (test_mobile_layout.py) requires:
- #btnMobileFiles onclick=toggleMobileFiles() in topbar chips
- .mobile-files-btn CSS rules for responsive show/hide at 640/900px breakpoints
Also adds max-width guard to .profile-dropdown to prevent clipping at narrow viewports.
* Improve composer footer mobile responsiveness and UX
- Collapse composer chips to icon-only at <=400px viewports
- Add model chip icon (CPU) so it remains tappable when labels are hidden
- Show send button always (disabled state when empty, hidden during streaming)
- Show context usage indicator on session load, not just after streaming
- Add cancel status fallback timeout to prevent stale "Cancelling..." text
- Update tests to match new send button and busy state behavior
* Fix duplicate files button and broken workspace close on mobile
Remove redundant #btnMobileFiles button that duplicated #btnWorkspacePanelToggle
in the mobile topbar. Fix workspace panel close button calling undefined
closeMobileFiles() — now calls closeWorkspacePanel().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix model chip icon vertical alignment in composer footer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix workspace toggle button hidden on desktop by conflicting CSS class
Remove mobile-files-btn class from #btnWorkspacePanelToggle — its
display:none!important rule was overriding workspace-toggle-btn visibility
on non-mobile viewports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix session actions dots button inaccessible on mobile sidebar
Always show the session actions trigger on mobile (no hover state on
touch devices) and restore right padding so text truncates with
ellipsis before the dots icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer footer manage links not opening sidebar panel
The "Manage profiles" and "Manage workspaces" links in the composer
footer dropdowns called switchPanel() which only changes the active
panel content but doesn't open the sidebar. Replaced with
mobileSwitchPanel() which also opens the sidebar so the panel is
actually visible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Widen icon-only composer chips breakpoint from 400px to 768px
Move the icon-only chip styling up into the existing max-width:768px
media query so chips collapse to icon-only on tablets too, preventing
composer footer overflow on mid-size screens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer-left vertical scrollbar by setting overflow-y:hidden
When overflow-x is set to auto, the CSS spec implicitly changes
overflow-y from visible to auto, allowing a vertical scrollbar to
appear from slight chip padding/border overflow. Explicitly set
overflow-y:hidden to prevent this.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve rebase conflicts and fix control center test assertions
- Resolved 4 conflicts during rebase onto master (workspace.js,
boot.js, index.html, test_sprint34.py)
- Fixed test_sprint34.py: _controlSection -> _settingsSection,
cc-tab -> settings-tabs (matching actual implementation)
- Fixed quoting syntax error in test assertion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update version badge in System tab to v0.49.4
* docs: update README and CHANGELOG for v0.50.0 UI refresh, bump version badge
---------
Co-authored-by: Aron Prins <pwf.aron@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 18:55:40 +00:00
project _name _prompt : 'Projektname:' ,
2026-04-10 01:35:23 +00:00
created : 'Erstellt ' ,
create _failed : 'Erstellen fehlgeschlagen: ' ,
new _folder _prompt : 'Neuer Ordnername:' ,
folder _created : 'Ordner erstellt ' ,
folder _create _failed : 'Ordner erstellen fehlgeschlagen: ' ,
remove _title : 'Entfernen' ,
empty _dir : '(leer)' ,
upload _failed : 'Upload fehlgeschlagen: ' ,
all _uploads _failed : ( n ) => ` Alle ${ n } Upload(s) fehlgeschlagen ` ,
// settings panel
settings _title : 'Einstellungen' ,
settings _save _btn : 'Einstellungen speichern' ,
settings _label _model : 'Standard-Modell' ,
settings _label _send _key : 'Sende-Taste' ,
settings _label _theme : 'Theme' ,
settings _label _language : 'Sprache' ,
settings _label _token _usage : 'Token-Verbrauch anzeigen' ,
feat: real-time gateway session sync (Phase 1) (#274)
* feat: add real-time gateway session sync (Phase 1)
- Add gateway_watcher.py: background daemon polling state.db every 5s
for gateway session changes (telegram, discord, slack, etc.)
- Extend get_cli_sessions() to include all non-webui sources
- Add SSE endpoint /api/sessions/gateway/stream for real-time push
- Add dynamic source badges (telegram=blue, discord=purple, slack=dark purple)
- Rename 'Show CLI sessions' to 'Show agent sessions'
- Wire watcher lifecycle into server start/stop
- 10 tests covering metadata, filtering, SSE, and watcher lifecycle
- Activated via the same checkbox as CLI session import
Addresses GitHub issue #272
* fix: SSE event name mismatch, TLS attribute, remove PLAN.md
- Fix critical SSE bug: frontend listened for 'gateway_session_update'
but backend sends 'sessions_changed' -- events were silently dropped
- Fix frontend field check: data.changed -> data.sessions (matches
the actual payload structure from gateway_watcher)
- Fix TLS: ssl.TLSv1_2 -> ssl.TLSVersion.TLSv1_2 (the bare attribute
does not exist, would crash TLS setup and silently fall back to HTTP)
- Remove PLAN.md: implementation plan should not be committed to repo
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: test isolation and slow-consumer sentinel in gateway sync
tests/test_gateway_sync.py:
- Fix _get_test_state_dir() path mismatch: the function was computing
HERMES_HOME/webui-mvp-test but conftest.py sets HERMES_HOME=TEST_STATE_DIR,
so state.db was written to a double-nested path the server never read.
Now uses HERMES_WEBUI_STATE_DIR first (which conftest sets directly to
TEST_STATE_DIR), fixing the 7/10 test failures in full-suite ordering.
- Fix conn cleanup: removed conn.close() from inside try blocks so the
connection stays valid for _remove_test_sessions() in the finally block.
Previously the closed conn caused ProgrammingError in finally (swallowed
by bare except), leaving ghost sessions in state.db on test failure.
api/gateway_watcher.py:
- Fix slow-consumer queue eviction: when a subscriber queue fills (>10 events)
and is removed from _subscribers, now puts a None sentinel into it so the
SSE handler unblocks and closes the connection, letting EventSource
auto-reconnect. Without this the connection stayed open but received no
further events.
* fix: test isolation — set HERMES_WEBUI_TEST_STATE_DIR in conftest
The gateway sync tests write directly to state.db and must use the same
path the test server reads from. Previously they computed the path
independently, which broke when test_auth_sessions.py set a different
HERMES_WEBUI_STATE_DIR in the test-process environment at import time.
tests/conftest.py:
- Set HERMES_WEBUI_TEST_STATE_DIR=TEST_STATE_DIR in the test process's
os.environ (via setdefault) so gateway tests can read it reliably.
Using setdefault preserves any explicit override the caller may pass.
tests/test_gateway_sync.py:
- Simplify _get_test_state_dir(): check HERMES_WEBUI_TEST_STATE_DIR first
(now reliably set by conftest), fall back to HERMES_HOME/webui-mvp-test.
Remove the workaround that tried to snapshot HERMES_HOME at import time.
Result: 658/658 tests pass in full-suite ordering (was 651 pass / 7 fail).
---------
Co-authored-by: bergeouss <bergeouss@users.noreply.github.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 03:53:12 +00:00
settings _label _cli _sessions : 'Agent-Sitzungen anzeigen' ,
2026-04-10 01:35:23 +00:00
settings _label _sync _insights : 'Mit Insights synchronisieren' ,
settings _label _check _updates : 'Nach Updates suchen' ,
settings _label _bot _name : 'Assistenten-Name' ,
settings _label _password : 'Zugangspasswort' ,
settings _saved : 'Einstellungen gespeichert' ,
settings _save _failed : 'Speichern fehlgeschlagen: ' ,
settings _load _failed : 'Laden der Einstellungen fehlgeschlagen: ' ,
2026-04-14 12:51:19 +00:00
settings _saved _pw : 'Einstellungen gespeichert — Passwortschutz aktiviert und dieser Browser bleibt angemeldet' ,
settings _saved _pw _updated : 'Einstellungen gespeichert — Passwort aktualisiert' ,
2026-04-10 01:35:23 +00:00
// login page
login _title : 'Anmelden' ,
login _subtitle : 'Geben Sie Ihr Passwort ein, um fortzufahren' ,
login _placeholder : 'Passwort' ,
login _btn : 'Anmelden' ,
login _invalid _pw : 'Ungültiges Passwort' ,
login _conn _failed : 'Verbindung fehlgeschlagen' ,
v0.47.0: dialogs, session menu, /skills, mobile fixes, mobile QA suite
* fix: custom provider with slash model name no longer rerouted to OpenRouter (#255)
When base_url is configured in config.yaml, resolve_model_provider() now
trusts the configured provider/base_url entirely and skips the slash-based
OpenRouter heuristic. Fixes google/gemma-4-26b-a4b with provider:custom
being silently routed to OpenRouter, resulting in 401 errors.
Fixes #230
* test: mobile layout regression suite — 14 tests for every QA run (#254)
Adds tests/test_mobile_layout.py with 14 static regression tests that run
on every QA pass to catch mobile layout breakage before it reaches prod.
Covers: breakpoints at 900px/640px, right panel slide-over CSS, mobile
overlay, bottom nav, files button, profile dropdown z-index, chip overflow,
workspace close, 100dvh, 44px touch targets, 16px font-size on textarea.
* feat: /skills slash command lists and filters available Hermes skills (#257)
Adds /skills [query] command to commands.js. Fetches from /api/skills,
groups by category (alphabetically sorted), displays as a formatted
assistant message. Optional query filters by name, description, or category.
i18n keys added for en, de, zh, zh-Hant. 1 regression test added.
Fixes #248
* feat: shared app dialogs replace native confirm()/prompt() calls (#251)
Adds showConfirmDialog() and showPromptDialog() helpers to ui.js, backed
by a themed #appDialogOverlay. Replaces all 11 native browser confirm/prompt
call sites across panels.js, sessions.js, ui.js, workspace.js.
Supports: danger mode, keyboard focus trap (Tab/Escape/Enter), focus restore,
ARIA roles, mobile-responsive stacked buttons at 640px. i18n for en/de/zh/zh-Hant.
5 new tests in test_sprint33.py verify markup, CSS, helpers, and absence of
native dialog calls.
Extracted from PR #242.
* fix: Android Chrome mobile — workspace panel close + profile dropdown (#256)
Fix #247: toggleMobileFiles() now shows/hides the mobile overlay when
toggling the right workspace panel. New closeMobileFiles() helper closes
the panel with correct overlay state tracking. Overlay onclick calls both
closeMobileSidebar() and closeMobileFiles(). Mobile-only close button (x)
added to workspace panel header.
Fix #246: profile dropdown uses position:fixed;top:56px;right:8px at
max-width:900px, escaping the overflow-x:auto stacking context that was
clipping it on Android Chrome.
Fix applied during review: closeMobileSidebar() now checks if the right
panel is still open before hiding the overlay, preventing the overlay from
disappearing when only the sidebar is closed.
Fixes #247 Fixes #246
* feat: session ⋯ action dropdown replaces per-row buttons (#252)
Replaces the 5 per-row hover action buttons (pin/move/archive/duplicate/trash)
with a single ⋯ trigger that opens a positioned dropdown menu. Menu has full
keyboard (Escape), click-outside, scroll, and resize-reposition handling.
Position:fixed prevents sidebar clipping.
5 actions: Pin/Unpin, Move to project, Archive/Unarchive, Duplicate, Delete
(danger style). Each with icon and descriptive subtitle.
Updated test_sprint16.py: test_sessions_js_uses_action_menu_not_per_row_buttons
asserts the new trigger and menu functions exist, old per-row classes are gone.
Extracted from PR #242.
* docs: v0.47.0 release notes, bump version, update test counts (645)
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 19:19:12 +00:00
dialog _confirm _title : 'Aktion bestätigen' ,
dialog _prompt _title : 'Wert eingeben' ,
dialog _confirm _btn : 'Bestätigen' ,
discard : 'Verwerfen' ,
clear : 'Leeren' ,
create : 'Erstellen' ,
remove : 'Entfernen' ,
project _name _prompt : 'Projektname:' ,
2026-04-10 01:35:23 +00:00
// Sidebar & Tabs
tab _chat : 'Chat' ,
tab _tasks : 'Aufgaben' ,
tab _skills : 'Skills' ,
tab _memory : 'Gedächtnis' ,
tab _workspaces : 'Spaces' ,
tab _profiles : 'Profile' ,
tab _todos : 'Todos' ,
new _conversation : 'Neuer Chat' ,
filter _conversations : 'Chats filtern...' ,
scheduled _jobs : 'Geplante Aufgaben' ,
new _job : 'Neuer Job' ,
loading : 'Lädt...' ,
search _skills : 'Skills suchen...' ,
new _skill : 'Neuer Skill' ,
personal _memory : 'Persönliches Gedächtnis' ,
current _task _list : 'Aktuelle Aufgabenliste' ,
workspace _desc : 'Workspaces hinzufügen und wechseln.' ,
new _profile : 'Neues Profil' ,
transcript : 'Protokoll' ,
download _transcript : 'Als Markdown herunterladen' ,
import : 'Importieren' ,
// Settings detail
settings _label _sound : 'Benachrichtigungston' ,
settings _desc _sound : 'Spielt einen Ton ab, wenn der Assistent eine Antwort beendet.' ,
settings _label _notifications : 'Browser-Benachrichtigungen' ,
settings _desc _notifications : 'Zeigt eine Systembenachrichtigung an, wenn eine Antwort fertiggestellt wird, während der Tab im Hintergrund ist.' ,
settings _desc _token _usage : 'Zeigt die Anzahl der Input/Output-Token unter jeder Antwort des Assistenten an. Auch umschaltbar mit /usage.' ,
2026-04-10 02:04:48 +00:00
settings _desc _cli _sessions : 'Fügt Sitzungen aus der Hermes CLI (state.db) in die Sitzungsliste ein. Klicken Sie auf eine CLI-Sitzung, um sie zu importieren und das Gespräch fortzusetzen.' ,
2026-04-10 01:35:23 +00:00
settings _desc _sync _insights : 'Spiegelt den WebUI-Token-Verbrauch in die state.db, sodass hermes /insights Browser-Sitzungsdaten enthält. Standardmäßig aus.' ,
2026-04-10 02:04:48 +00:00
settings _desc _check _updates : 'Zeigt ein Banner an, wenn neuere Versionen der WebUI oder des Agenten verfügbar sind. Führt regelmäßig einen Git-Fetch im Hintergrund aus.' ,
2026-04-10 01:35:23 +00:00
settings _desc _bot _name : 'Anzeigename für den Assistenten in der UI. Standardmäßig Hermes.' ,
settings _desc _password : 'Geben Sie ein neues Passwort ein, um es zu setzen oder zu ändern. Leer lassen, um die aktuelle Einstellung beizubehalten.' ,
password _placeholder : 'Neues Passwort eingeben…' ,
disable _auth : 'Authentifizierung deaktivieren' ,
sign _out : 'Abmelden' ,
cancel : 'Abbrechen' ,
create _job : 'Job erstellen' ,
save _skill : 'Skill speichern' ,
2026-04-10 02:04:48 +00:00
editing : 'Bearbeitung' ,
2026-04-10 01:35:23 +00:00
// Empty state
empty _title : 'Wie kann ich helfen?' ,
2026-04-10 02:04:48 +00:00
empty _subtitle : 'Frage mich alles, führe Befehle aus, erkunde Dateien oder verwalte deine Aufgaben.' ,
2026-04-10 01:35:23 +00:00
suggest _files : 'Welche Dateien sind in diesem Workspace?' ,
suggest _schedule : 'Was steht heute auf meinem Plan?' ,
suggest _plan : 'Hilf mir, ein kleines Projekt zu planen.' ,
2026-04-14 12:51:19 +00:00
onboarding _password _will _enable : 'Wird aktiviert' ,
onboarding _password _will _replace : 'Wird ersetzt' ,
onboarding _password _keep _existing : 'Aktuelles Passwort beibehalten' ,
onboarding _password _remains _disabled : 'Bleibt deaktiviert' ,
2026-04-08 14:55:03 +00:00
} ,
zh : {
_lang : 'zh' ,
v0.46.0: security, Docker UID/GID, model discovery, i18n, cancel fix
* fix: decode HTML entities before markdown processing + zh/zh-Hant translations (#239)
Adds decode() helper in renderMd() to fix double-escaping of HTML entities
from LLM output (e.g. <code> becoming &lt;code&gt; instead
of rendering). XSS-safe: decode runs before esc(), only 5 entity patterns.
Also adds 40+ missing zh (Simplified Chinese) translation keys and a new
zh-Hant (Traditional Chinese) locale with 163 keys.
Fix applied: removed duplicate settings_label_notifications key in both
zh and zh-Hant locales.
Fixes #240
* fix: restore custom model list discovery with config api key (#238)
get_available_models() now reads api_key from config.yaml before env vars:
1. model.api_key
2. providers.<active>.api_key / providers.custom.api_key
3. env var fallbacks (HERMES_API_KEY, OPENAI_API_KEY, etc.)
Also adds OpenAI/Python User-Agent header and a regression test covering
authenticated /v1/models discovery.
Fixes users with LM Studio / Ollama custom endpoints configured in
config.yaml whose model picker silently collapsed to the default model.
* feat: Docker UID/GID matching to avoid root-owned .hermes files (#237)
Adds docker_init.bash with hermeswebuitoo/hermeswebui user pattern so
container files match the host user UID/GID. Prevents .hermes volume
mounts from being owned by root when using a non-root host user.
Configure via WANTED_UID and WANTED_GID env vars (default 1000/1000).
Readme updated with setup instructions.
Fix applied: removed duplicate WANTED_GID=1000 line in docker-compose.yml
that was overriding the ${GID:-1000} variable expansion.
* security: redact credentials from API responses and fix credential file permissions (#243)
Adds response-layer credential redaction to three endpoints:
- GET /api/session — messages[], tool_calls[], and title
- GET /api/session/export — download also redacted
- SSE done event — session payload in stream
- GET /api/memory — MEMORY.md and USER.md content
Adds api/startup.py with fix_credential_permissions() at server startup.
Adds 13 tests in tests/test_security_redaction.py.
Merged with #237 container detection changes in server.py.
* fix: cancel button now interrupts agent and cleans up UI state (#244)
Wires agent.interrupt() into cancel_stream() so the backend actually
stops tool execution when the user clicks Cancel, rather than only
stopping the SSE stream while the agent keeps running.
Changes:
- api/config.py: adds AGENT_INSTANCES dict (stream_id -> AIAgent)
- api/streaming.py: stores agent in AGENT_INSTANCES after creation,
checks CANCEL_FLAGS immediately after store (race condition fix),
calls agent.interrupt() in cancel_stream(), cleans up in finally block
- static/boot.js: removes stale setStatus(cancelling) call
- static/messages.js: setBusy(false)/setStatus('') unconditionally on cancel
Race condition fix: after storing agent in AGENT_INSTANCES, immediately
checks if CANCEL_FLAGS[stream_id] is already set (cancel arrived during
agent init) and interrupts before starting. Check is inside the same
STREAMS_LOCK acquisition, making it atomic.
New test file: tests/test_cancel_interrupt.py with 6 unit tests.
* docs: v0.46.0 release notes, bump version, update test counts
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 17:17:52 +00:00
_label : '\u7b80\u4f53\u4e2d\u6587' ,
2026-04-08 14:55:03 +00:00
_speech : 'zh-CN' ,
// boot.js
cancelling : '\u6b63\u5728\u53d6\u6d88...' ,
cancel _failed : '\u53d6\u6d88\u5931\u8d25\uff1a' ,
mic _denied : '\u9ea6\u514b\u98ce\u8bbf\u95ee\u88ab\u62d2\u7edd\uff0c\u8bf7\u68c0\u67e5\u6d4f\u89c8\u5668\u6743\u9650\u3002' ,
mic _no _speech : '\u6ca1\u6709\u68c0\u6d4b\u5230\u8bed\u97f3\uff0c\u8bf7\u518d\u8bd5\u4e00\u6b21\u3002' ,
mic _network : '\u8bed\u97f3\u8bc6\u522b\u5f53\u524d\u4e0d\u53ef\u7528\u3002' ,
mic _error : '\u8bed\u97f3\u8f93\u5165\u51fa\u9519\uff1a' ,
session _imported : '\u4f1a\u8bdd\u5df2\u5bfc\u5165' ,
import _failed : '\u5bfc\u5165\u5931\u8d25\uff1a' ,
import _invalid _json : 'JSON \u65e0\u6548' ,
image _pasted : '\u5df2\u7c98\u8d34\u56fe\u7247\uff1a' ,
// messages.js
edit _message : '\u7f16\u8f91\u6d88\u606f' ,
regenerate : '\u91cd\u65b0\u751f\u6210\u56de\u590d' ,
copy : '\u590d\u5236' ,
copied : '\u5df2\u590d\u5236' ,
you : '\u4f60' ,
thinking : '\u601d\u8003\u8fc7\u7a0b' ,
expand _all : '\u5168\u90e8\u5c55\u5f00' ,
collapse _all : '\u5168\u90e8\u6298\u53e0' ,
edit _failed : '\u7f16\u8f91\u5931\u8d25\uff1a' ,
regen _failed : '\u91cd\u65b0\u751f\u6210\u5931\u8d25\uff1a' ,
reconnect _active : '\u56de\u590d\u4ecd\u5728\u751f\u6210\u4e2d\uff0c\u51c6\u5907\u597d\u540e\u8981\u91cd\u65b0\u52a0\u8f7d\u5417\uff1f' ,
reconnect _finished : '\u4f60\u79bb\u5f00\u65f6\u6709\u56de\u590d\u6b63\u5728\u751f\u6210\uff0c\u6d88\u606f\u5185\u5bb9\u53ef\u80fd\u5df2\u7ecf\u66f4\u65b0\u3002' ,
fix: surface approval prompt in UI instead of getting stuck in Thinking (#187)
* fix: surface approval prompt in UI instead of getting stuck in Thinking
When a dangerous command was detected during streaming, the approval system
would call submit_pending() but no SSE 'approval' event would be emitted to
the frontend. The agent thread either blocked indefinitely (gateway path) or
returned an approval_required status the UI never saw (EXEC_ASK path). Either
way the chat UI stayed stuck in 'Thinking...' with no prompt shown.
Root cause: streaming.py used HERMES_EXEC_ASK=1 but never registered a
register_gateway_notify() callback. Without it, check_all_command_guards()
fell back to the legacy polling path (submit_pending only), which relies on
on_tool() polling -- but on_tool() fires *before* the tool runs, so by the
time the terminal tool detected the dangerous command and called submit_pending,
the approval event had already missed its window.
Fix (streaming.py):
- Register a gateway-style notify_cb via register_gateway_notify() before the
agent runs. The callback calls put('approval', ...) to emit the SSE event
the moment a dangerous command is detected, regardless of on_tool() timing.
- Unregister via unregister_gateway_notify() in the finally block to unblock
any threads still waiting if the stream ends or is cancelled mid-approval.
- Keep the on_tool() fallback poll for older approval module versions.
Fix (routes.py):
- Import and call resolve_gateway_approval() in _handle_approval_respond().
This unblocks the agent thread parked in entry.event.wait() when the user
clicks Allow or Deny in the UI. Without this call the thread would block
until the 5-minute gateway timeout.
Tests (tests/test_approval_unblock.py):
- 16 new tests covering: resolve_gateway_approval() event signalling, deny/
session/once choices, resolve_all, notify_cb registration/firing/cleanup,
unregister signals blocked entries, full end-to-end streaming simulation,
module symbol exports, and HTTP endpoint regressions.
515 tests pass (499 existing + 16 new).
* feat: full approval UI — i18n buttons, keyboard shortcut, loading state, scoping fix
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-09 03:16:22 +00:00
// approval card
approval _heading : '需要审批' ,
approval _desc _prefix : '检测到危险命令' ,
approval _btn _once : '允许一次' ,
approval _btn _once _title : '允许执行此命令一次( Enter) ' ,
approval _btn _session : '本次允许' ,
approval _btn _session _title : '本次会话期间允许' ,
approval _btn _always : '始终允许' ,
approval _btn _always _title : '始终允许此命令模式' ,
approval _btn _deny : '拒绝' ,
approval _btn _deny _title : '拒绝 — 不执行此命令' ,
approval _responding : '处理中…' ,
2026-04-08 14:55:03 +00:00
untitled : '\u672a\u547d\u540d' ,
n _messages : ( n ) => ` ${ n } \u 6761 \u 6d88 \u 606f ` ,
model _unavailable : '\uff08\u4e0d\u53ef\u7528\uff09' ,
model _unavailable _title : '\u8fd9\u4e2a\u6a21\u578b\u5df2\u7ecf\u4e0d\u5728\u5f53\u524d provider \u5217\u8868\u4e2d' ,
fix: warn on provider/model mismatch, surface auth errors (#266)
* fix: warn on provider/model mismatch, surface auth errors (#266)
Fixes #266 — WebUI silently ignores provider/model selection mismatch.
The problem: selecting an OpenRouter (or Anthropic/OpenAI) model while
Hermes is configured for a different provider (e.g. local Ollama) sends
the request to the wrong endpoint, which returns a 401 Unauthorized error
with no UI indication of why.
Three-layer fix:
1. api/streaming.py — detect 401/auth errors explicitly
Added is_auth_error detection covering '401', 'AuthenticationError',
'authentication', 'unauthorized', 'invalid api key', and the specific
Ollama error string 'no cookie auth credentials'. Auth errors emit
apperror with type='auth_mismatch' and a hint pointing to 'hermes model'.
2. static/ui.js — expose active_provider and warn on selection
- populateModelDropdown() stores data.active_provider from /api/models
as window._activeProvider (the field was already in the response but
the frontend never used it)
- New _checkProviderMismatch(modelId) helper: compares the selected
model's slash-prefix (e.g. 'openai/' from 'openai/gpt-4o') against
the active provider. Skips the check for 'openrouter' and 'custom'
to avoid false positives on configs that legitimately route any model.
3. static/boot.js — warn on model dropdown change
modelSelect.onchange calls _checkProviderMismatch() and shows a toast
when the selected model looks incompatible with the configured provider.
4. static/messages.js — distinct UI label for auth errors
apperror handler now distinguishes type='auth_mismatch' and shows
'Provider mismatch' as the error label instead of 'Error'.
5. static/i18n.js — provider_mismatch_warning and provider_mismatch_label
keys added to all 5 locales (en, es, de, zh-Hans, zh-Hant).
Tests: 21 new tests in tests/test_provider_mismatch.py covering all
five change areas. 679/679 total pass (658 baseline + 21 new).
* fix: t() call args spread + use i18n label for auth mismatch
1. ui.js: _checkProviderMismatch passed [modelId, ap] as a single
array arg to t(). Since t(key, ...args) spreads, the function
received the array as m and undefined as p. Fixed to pass as
separate args: t('provider_mismatch_warning', modelId, ap).
2. messages.js: 'Provider mismatch' label was hardcoded instead of
using t('provider_mismatch_label'). Now uses the i18n key with
fallback for when t() isn't available.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 04:25:18 +00:00
provider _mismatch _warning : ( m , p ) => ` \" ${ m } \" \u 53ef \u 80fd \u 65e0 \u 6cd5 \u 5728 \u 5f53 \u 524d \u 914d \u 7f6e \u 7684 \u 63d0 \u 4f9b \u 5546 ( ${ p } ) \u 4e0b \u 5de5 \u 4f5c \u 3002 \u 76f4 \u 63a5 \u 53d1 \u 9001 \u ff0c \u 6216 \u 5728 \u 7ec8 \u 7aef \u 8fd0 \u 884c \` hermes model \` \u 5207 \u 6362 \u 3002 ` ,
provider _mismatch _label : '\u63d0\u4f9b\u5546\u4e0d\u5339\u914d' ,
2026-04-14 20:56:56 +00:00
model _custom _label : '\u81ea\u5b9a\u4e49\u6a21\u578b ID' ,
model _custom _placeholder : '\u4f8b\u5982 openai/gpt-5.4' ,
2026-04-08 14:55:03 +00:00
// commands.js
cmd _help : '\u67e5\u770b\u53ef\u7528\u547d\u4ee4' ,
cmd _clear : '\u6e05\u7a7a\u5f53\u524d\u5bf9\u8bdd\u6d88\u606f' ,
cmd _compact : '\u538b\u7f29\u5bf9\u8bdd\u4e0a\u4e0b\u6587' ,
cmd _model : '\u5207\u6362\u6a21\u578b\uff08\u4f8b\u5982 /model gpt-4o\uff09' ,
cmd _workspace : '\u6309\u540d\u79f0\u5207\u6362\u5de5\u4f5c\u533a' ,
cmd _new : '\u65b0\u5efa\u804a\u5929\u4f1a\u8bdd' ,
cmd _usage : '\u5207\u6362 token \u7528\u91cf\u663e\u793a' ,
cmd _theme : '\u5207\u6362\u4e3b\u9898\uff08dark/light/slate/solarized/monokai/nord/oled\uff09' ,
cmd _personality : '\u5207\u6362 Agent \u4eba\u8bbe' ,
v0.47.0: dialogs, session menu, /skills, mobile fixes, mobile QA suite
* fix: custom provider with slash model name no longer rerouted to OpenRouter (#255)
When base_url is configured in config.yaml, resolve_model_provider() now
trusts the configured provider/base_url entirely and skips the slash-based
OpenRouter heuristic. Fixes google/gemma-4-26b-a4b with provider:custom
being silently routed to OpenRouter, resulting in 401 errors.
Fixes #230
* test: mobile layout regression suite — 14 tests for every QA run (#254)
Adds tests/test_mobile_layout.py with 14 static regression tests that run
on every QA pass to catch mobile layout breakage before it reaches prod.
Covers: breakpoints at 900px/640px, right panel slide-over CSS, mobile
overlay, bottom nav, files button, profile dropdown z-index, chip overflow,
workspace close, 100dvh, 44px touch targets, 16px font-size on textarea.
* feat: /skills slash command lists and filters available Hermes skills (#257)
Adds /skills [query] command to commands.js. Fetches from /api/skills,
groups by category (alphabetically sorted), displays as a formatted
assistant message. Optional query filters by name, description, or category.
i18n keys added for en, de, zh, zh-Hant. 1 regression test added.
Fixes #248
* feat: shared app dialogs replace native confirm()/prompt() calls (#251)
Adds showConfirmDialog() and showPromptDialog() helpers to ui.js, backed
by a themed #appDialogOverlay. Replaces all 11 native browser confirm/prompt
call sites across panels.js, sessions.js, ui.js, workspace.js.
Supports: danger mode, keyboard focus trap (Tab/Escape/Enter), focus restore,
ARIA roles, mobile-responsive stacked buttons at 640px. i18n for en/de/zh/zh-Hant.
5 new tests in test_sprint33.py verify markup, CSS, helpers, and absence of
native dialog calls.
Extracted from PR #242.
* fix: Android Chrome mobile — workspace panel close + profile dropdown (#256)
Fix #247: toggleMobileFiles() now shows/hides the mobile overlay when
toggling the right workspace panel. New closeMobileFiles() helper closes
the panel with correct overlay state tracking. Overlay onclick calls both
closeMobileSidebar() and closeMobileFiles(). Mobile-only close button (x)
added to workspace panel header.
Fix #246: profile dropdown uses position:fixed;top:56px;right:8px at
max-width:900px, escaping the overflow-x:auto stacking context that was
clipping it on Android Chrome.
Fix applied during review: closeMobileSidebar() now checks if the right
panel is still open before hiding the overlay, preventing the overlay from
disappearing when only the sidebar is closed.
Fixes #247 Fixes #246
* feat: session ⋯ action dropdown replaces per-row buttons (#252)
Replaces the 5 per-row hover action buttons (pin/move/archive/duplicate/trash)
with a single ⋯ trigger that opens a positioned dropdown menu. Menu has full
keyboard (Escape), click-outside, scroll, and resize-reposition handling.
Position:fixed prevents sidebar clipping.
5 actions: Pin/Unpin, Move to project, Archive/Unarchive, Duplicate, Delete
(danger style). Each with icon and descriptive subtitle.
Updated test_sprint16.py: test_sessions_js_uses_action_menu_not_per_row_buttons
asserts the new trigger and menu functions exist, old per-row classes are gone.
Extracted from PR #242.
* docs: v0.47.0 release notes, bump version, update test counts (645)
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 19:19:12 +00:00
cmd _skills : '\u5217\u51fa\u53ef\u7528\u7684 Hermes \u6280\u80fd' ,
2026-04-08 14:55:03 +00:00
available _commands : '\u53ef\u7528\u547d\u4ee4\uff1a' ,
type _slash : '\u8f93\u5165 / \u53ef\u67e5\u770b\u547d\u4ee4' ,
conversation _cleared : '\u5bf9\u8bdd\u5df2\u6e05\u7a7a' ,
model _usage : '\u7528\u6cd5\uff1a/model <name>' ,
no _model _match : '\u6ca1\u6709\u5339\u914d\u201c' ,
switched _to : '\u5df2\u5207\u6362\u5230 ' ,
workspace _usage : '\u7528\u6cd5\uff1a/workspace <name>' ,
no _workspace _match : '\u6ca1\u6709\u5339\u914d\u201c' ,
switched _workspace : '\u5df2\u5207\u6362\u5de5\u4f5c\u533a\uff1a' ,
workspace _switch _failed : '\u5de5\u4f5c\u533a\u5207\u6362\u5931\u8d25\uff1a' ,
new _session : '\u5df2\u65b0\u5efa\u4f1a\u8bdd' ,
compressing : '\u6b63\u5728\u8bf7\u6c42\u538b\u7f29\u4e0a\u4e0b\u6587...' ,
token _usage _on : 'Token \u7528\u91cf\u663e\u793a\u5df2\u5f00\u542f' ,
token _usage _off : 'Token \u7528\u91cf\u663e\u793a\u5df2\u5173\u95ed' ,
theme _usage : '\u7528\u6cd5\uff1a/theme ' ,
theme _set : '\u4e3b\u9898\uff1a' ,
no _active _session : '\u5f53\u524d\u6ca1\u6709\u6d3b\u52a8\u4f1a\u8bdd' ,
no _personalities : '\u6ca1\u6709\u627e\u5230\u4eba\u8bbe\uff08\u53ef\u6dfb\u52a0\u5230 ~/.hermes/personalities/\uff09' ,
available _personalities : '\u53ef\u7528\u4eba\u8bbe\uff1a' ,
personality _switch _hint : '\n\n\u4f7f\u7528 `/personality <name>` \u5207\u6362\uff0c\u6216\u7528 `/personality none` \u6e05\u7a7a\u3002' ,
personalities _load _failed : '\u52a0\u8f7d\u4eba\u8bbe\u5931\u8d25' ,
personality _cleared : '\u4eba\u8bbe\u5df2\u6e05\u7a7a' ,
personality _set : '\u5f53\u524d\u4eba\u8bbe\uff1a' ,
failed _colon : '\u5931\u8d25\uff1a' ,
// ui.js
no _workspace : '\u672a\u9009\u62e9\u5de5\u4f5c\u533a' ,
feat: composer-centric UI refresh + Hermes Control Center (v0.50.0, closes #242)
* Polish workspace panel behavior and app dialogs
* Replace remaining emoji UI glyphs with Lucide icons
* Redesign composer footer around model and context controls
Move the model selector into the composer footer, replace the linear context pill with a compact circular badge plus tooltip, and remove the redundant topbar model pill.
Design credit and inspiration: Theo / T3 Code.
Reference implementation: https://github.com/pingdotgg/t3code/
* Remove obsolete activity bar
Drop the old activity bar, keep turn-scoped state in the composer footer, and route remaining non-chat status messages through toasts.
This leaves live tool cards and the message timeline as the primary progress UI, with the composer owning stop/cancel and brief turn status.
* Move workspace and model switching into composer footer
* Move profile switching into composer footer
* Refactor Hermes control center UI
* Redesign control center settings modal layout
Widen the modal to 860px, simplify the tab list to icon+label rows,
stretch the tab column's divider to full height, lock the panel to a
fixed height so switching tabs no longer resizes the outer shell, and
always open on the Conversation tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Put session item actions in a dropdown
* Use Hermes mark in sidebar control button
* Reset control center section on close
* Drop session-item left border indicator
Remove the left-border accent used for active, CLI, and project rows —
each state already has a dedicated cue (gold fill, cli badge, project
dot), so the border was redundant. Fully round the row, add 2px
bottom spacing between rows, and strip the matching JS/CSS overrides.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Increase session search input vertical padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Normalise odd pixel values across UI
Snap padding, gap, and border-radius values to the 2/4/6/8/10/12 grid
across composer chips, sidebar panels, cron list, settings, approval
buttons, dropdowns, and inline message edit — eliminating the 7/9/11px
drift that was making sibling elements feel subtly misaligned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add missing #btnMobileFiles button and .mobile-files-btn CSS (for mobile QA suite)
The mobile layout regression suite (test_mobile_layout.py) requires:
- #btnMobileFiles onclick=toggleMobileFiles() in topbar chips
- .mobile-files-btn CSS rules for responsive show/hide at 640/900px breakpoints
Also adds max-width guard to .profile-dropdown to prevent clipping at narrow viewports.
* Improve composer footer mobile responsiveness and UX
- Collapse composer chips to icon-only at <=400px viewports
- Add model chip icon (CPU) so it remains tappable when labels are hidden
- Show send button always (disabled state when empty, hidden during streaming)
- Show context usage indicator on session load, not just after streaming
- Add cancel status fallback timeout to prevent stale "Cancelling..." text
- Update tests to match new send button and busy state behavior
* Fix duplicate files button and broken workspace close on mobile
Remove redundant #btnMobileFiles button that duplicated #btnWorkspacePanelToggle
in the mobile topbar. Fix workspace panel close button calling undefined
closeMobileFiles() — now calls closeWorkspacePanel().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix model chip icon vertical alignment in composer footer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix workspace toggle button hidden on desktop by conflicting CSS class
Remove mobile-files-btn class from #btnWorkspacePanelToggle — its
display:none!important rule was overriding workspace-toggle-btn visibility
on non-mobile viewports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix session actions dots button inaccessible on mobile sidebar
Always show the session actions trigger on mobile (no hover state on
touch devices) and restore right padding so text truncates with
ellipsis before the dots icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer footer manage links not opening sidebar panel
The "Manage profiles" and "Manage workspaces" links in the composer
footer dropdowns called switchPanel() which only changes the active
panel content but doesn't open the sidebar. Replaced with
mobileSwitchPanel() which also opens the sidebar so the panel is
actually visible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Widen icon-only composer chips breakpoint from 400px to 768px
Move the icon-only chip styling up into the existing max-width:768px
media query so chips collapse to icon-only on tablets too, preventing
composer footer overflow on mid-size screens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer-left vertical scrollbar by setting overflow-y:hidden
When overflow-x is set to auto, the CSS spec implicitly changes
overflow-y from visible to auto, allowing a vertical scrollbar to
appear from slight chip padding/border overflow. Explicitly set
overflow-y:hidden to prevent this.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve rebase conflicts and fix control center test assertions
- Resolved 4 conflicts during rebase onto master (workspace.js,
boot.js, index.html, test_sprint34.py)
- Fixed test_sprint34.py: _controlSection -> _settingsSection,
cc-tab -> settings-tabs (matching actual implementation)
- Fixed quoting syntax error in test assertion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update version badge in System tab to v0.49.4
* docs: update README and CHANGELOG for v0.50.0 UI refresh, bump version badge
---------
Co-authored-by: Aron Prins <pwf.aron@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 18:55:40 +00:00
dialog _confirm _title : '\u786e\u8ba4\u64cd\u4f5c' ,
dialog _prompt _title : '\u8f93\u5165\u5185\u5bb9' ,
dialog _confirm _btn : '\u786e\u8ba4' ,
2026-04-08 14:55:03 +00:00
// workspace.js
unsaved _confirm : '\u9884\u89c8\u533a\u6709\u672a\u4fdd\u5b58\u4fee\u6539\uff0c\u8981\u653e\u5f03\u66f4\u6539\u5e76\u7ee7\u7eed\u8df3\u8f6c\u5417\uff1f' ,
feat: composer-centric UI refresh + Hermes Control Center (v0.50.0, closes #242)
* Polish workspace panel behavior and app dialogs
* Replace remaining emoji UI glyphs with Lucide icons
* Redesign composer footer around model and context controls
Move the model selector into the composer footer, replace the linear context pill with a compact circular badge plus tooltip, and remove the redundant topbar model pill.
Design credit and inspiration: Theo / T3 Code.
Reference implementation: https://github.com/pingdotgg/t3code/
* Remove obsolete activity bar
Drop the old activity bar, keep turn-scoped state in the composer footer, and route remaining non-chat status messages through toasts.
This leaves live tool cards and the message timeline as the primary progress UI, with the composer owning stop/cancel and brief turn status.
* Move workspace and model switching into composer footer
* Move profile switching into composer footer
* Refactor Hermes control center UI
* Redesign control center settings modal layout
Widen the modal to 860px, simplify the tab list to icon+label rows,
stretch the tab column's divider to full height, lock the panel to a
fixed height so switching tabs no longer resizes the outer shell, and
always open on the Conversation tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Put session item actions in a dropdown
* Use Hermes mark in sidebar control button
* Reset control center section on close
* Drop session-item left border indicator
Remove the left-border accent used for active, CLI, and project rows —
each state already has a dedicated cue (gold fill, cli badge, project
dot), so the border was redundant. Fully round the row, add 2px
bottom spacing between rows, and strip the matching JS/CSS overrides.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Increase session search input vertical padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Normalise odd pixel values across UI
Snap padding, gap, and border-radius values to the 2/4/6/8/10/12 grid
across composer chips, sidebar panels, cron list, settings, approval
buttons, dropdowns, and inline message edit — eliminating the 7/9/11px
drift that was making sibling elements feel subtly misaligned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add missing #btnMobileFiles button and .mobile-files-btn CSS (for mobile QA suite)
The mobile layout regression suite (test_mobile_layout.py) requires:
- #btnMobileFiles onclick=toggleMobileFiles() in topbar chips
- .mobile-files-btn CSS rules for responsive show/hide at 640/900px breakpoints
Also adds max-width guard to .profile-dropdown to prevent clipping at narrow viewports.
* Improve composer footer mobile responsiveness and UX
- Collapse composer chips to icon-only at <=400px viewports
- Add model chip icon (CPU) so it remains tappable when labels are hidden
- Show send button always (disabled state when empty, hidden during streaming)
- Show context usage indicator on session load, not just after streaming
- Add cancel status fallback timeout to prevent stale "Cancelling..." text
- Update tests to match new send button and busy state behavior
* Fix duplicate files button and broken workspace close on mobile
Remove redundant #btnMobileFiles button that duplicated #btnWorkspacePanelToggle
in the mobile topbar. Fix workspace panel close button calling undefined
closeMobileFiles() — now calls closeWorkspacePanel().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix model chip icon vertical alignment in composer footer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix workspace toggle button hidden on desktop by conflicting CSS class
Remove mobile-files-btn class from #btnWorkspacePanelToggle — its
display:none!important rule was overriding workspace-toggle-btn visibility
on non-mobile viewports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix session actions dots button inaccessible on mobile sidebar
Always show the session actions trigger on mobile (no hover state on
touch devices) and restore right padding so text truncates with
ellipsis before the dots icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer footer manage links not opening sidebar panel
The "Manage profiles" and "Manage workspaces" links in the composer
footer dropdowns called switchPanel() which only changes the active
panel content but doesn't open the sidebar. Replaced with
mobileSwitchPanel() which also opens the sidebar so the panel is
actually visible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Widen icon-only composer chips breakpoint from 400px to 768px
Move the icon-only chip styling up into the existing max-width:768px
media query so chips collapse to icon-only on tablets too, preventing
composer footer overflow on mid-size screens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer-left vertical scrollbar by setting overflow-y:hidden
When overflow-x is set to auto, the CSS spec implicitly changes
overflow-y from visible to auto, allowing a vertical scrollbar to
appear from slight chip padding/border overflow. Explicitly set
overflow-y:hidden to prevent this.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve rebase conflicts and fix control center test assertions
- Resolved 4 conflicts during rebase onto master (workspace.js,
boot.js, index.html, test_sprint34.py)
- Fixed test_sprint34.py: _controlSection -> _settingsSection,
cc-tab -> settings-tabs (matching actual implementation)
- Fixed quoting syntax error in test assertion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update version badge in System tab to v0.49.4
* docs: update README and CHANGELOG for v0.50.0 UI refresh, bump version badge
---------
Co-authored-by: Aron Prins <pwf.aron@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 18:55:40 +00:00
discard : '\u653e\u5f03' ,
2026-04-08 14:55:03 +00:00
save : '\u4fdd\u5b58' ,
edit : '\u7f16\u8f91' ,
feat: composer-centric UI refresh + Hermes Control Center (v0.50.0, closes #242)
* Polish workspace panel behavior and app dialogs
* Replace remaining emoji UI glyphs with Lucide icons
* Redesign composer footer around model and context controls
Move the model selector into the composer footer, replace the linear context pill with a compact circular badge plus tooltip, and remove the redundant topbar model pill.
Design credit and inspiration: Theo / T3 Code.
Reference implementation: https://github.com/pingdotgg/t3code/
* Remove obsolete activity bar
Drop the old activity bar, keep turn-scoped state in the composer footer, and route remaining non-chat status messages through toasts.
This leaves live tool cards and the message timeline as the primary progress UI, with the composer owning stop/cancel and brief turn status.
* Move workspace and model switching into composer footer
* Move profile switching into composer footer
* Refactor Hermes control center UI
* Redesign control center settings modal layout
Widen the modal to 860px, simplify the tab list to icon+label rows,
stretch the tab column's divider to full height, lock the panel to a
fixed height so switching tabs no longer resizes the outer shell, and
always open on the Conversation tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Put session item actions in a dropdown
* Use Hermes mark in sidebar control button
* Reset control center section on close
* Drop session-item left border indicator
Remove the left-border accent used for active, CLI, and project rows —
each state already has a dedicated cue (gold fill, cli badge, project
dot), so the border was redundant. Fully round the row, add 2px
bottom spacing between rows, and strip the matching JS/CSS overrides.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Increase session search input vertical padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Normalise odd pixel values across UI
Snap padding, gap, and border-radius values to the 2/4/6/8/10/12 grid
across composer chips, sidebar panels, cron list, settings, approval
buttons, dropdowns, and inline message edit — eliminating the 7/9/11px
drift that was making sibling elements feel subtly misaligned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add missing #btnMobileFiles button and .mobile-files-btn CSS (for mobile QA suite)
The mobile layout regression suite (test_mobile_layout.py) requires:
- #btnMobileFiles onclick=toggleMobileFiles() in topbar chips
- .mobile-files-btn CSS rules for responsive show/hide at 640/900px breakpoints
Also adds max-width guard to .profile-dropdown to prevent clipping at narrow viewports.
* Improve composer footer mobile responsiveness and UX
- Collapse composer chips to icon-only at <=400px viewports
- Add model chip icon (CPU) so it remains tappable when labels are hidden
- Show send button always (disabled state when empty, hidden during streaming)
- Show context usage indicator on session load, not just after streaming
- Add cancel status fallback timeout to prevent stale "Cancelling..." text
- Update tests to match new send button and busy state behavior
* Fix duplicate files button and broken workspace close on mobile
Remove redundant #btnMobileFiles button that duplicated #btnWorkspacePanelToggle
in the mobile topbar. Fix workspace panel close button calling undefined
closeMobileFiles() — now calls closeWorkspacePanel().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix model chip icon vertical alignment in composer footer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix workspace toggle button hidden on desktop by conflicting CSS class
Remove mobile-files-btn class from #btnWorkspacePanelToggle — its
display:none!important rule was overriding workspace-toggle-btn visibility
on non-mobile viewports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix session actions dots button inaccessible on mobile sidebar
Always show the session actions trigger on mobile (no hover state on
touch devices) and restore right padding so text truncates with
ellipsis before the dots icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer footer manage links not opening sidebar panel
The "Manage profiles" and "Manage workspaces" links in the composer
footer dropdowns called switchPanel() which only changes the active
panel content but doesn't open the sidebar. Replaced with
mobileSwitchPanel() which also opens the sidebar so the panel is
actually visible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Widen icon-only composer chips breakpoint from 400px to 768px
Move the icon-only chip styling up into the existing max-width:768px
media query so chips collapse to icon-only on tablets too, preventing
composer footer overflow on mid-size screens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer-left vertical scrollbar by setting overflow-y:hidden
When overflow-x is set to auto, the CSS spec implicitly changes
overflow-y from visible to auto, allowing a vertical scrollbar to
appear from slight chip padding/border overflow. Explicitly set
overflow-y:hidden to prevent this.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve rebase conflicts and fix control center test assertions
- Resolved 4 conflicts during rebase onto master (workspace.js,
boot.js, index.html, test_sprint34.py)
- Fixed test_sprint34.py: _controlSection -> _settingsSection,
cc-tab -> settings-tabs (matching actual implementation)
- Fixed quoting syntax error in test assertion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update version badge in System tab to v0.49.4
* docs: update README and CHANGELOG for v0.50.0 UI refresh, bump version badge
---------
Co-authored-by: Aron Prins <pwf.aron@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 18:55:40 +00:00
clear : '\u6e05\u7a7a' ,
create : '\u521b\u5efa' ,
remove : '\u79fb\u9664' ,
2026-04-08 14:55:03 +00:00
save _title : '\u4fdd\u5b58\u4fee\u6539' ,
edit _title : '\u7f16\u8f91\u6b64\u6587\u4ef6' ,
saved : '\u5df2\u4fdd\u5b58' ,
save _failed : '\u4fdd\u5b58\u5931\u8d25\uff1a' ,
image _load _failed : '\u56fe\u7247\u52a0\u8f7d\u5931\u8d25' ,
file _open _failed : '\u65e0\u6cd5\u6253\u5f00\u6587\u4ef6' ,
downloading : ( name ) => ` \u 6b63 \u 5728 \u 4e0b \u 8f7d ${ name } ... ` ,
double _click _rename : '\u53cc\u51fb\u91cd\u547d\u540d' ,
renamed _to : '\u5df2\u91cd\u547d\u540d\u4e3a ' ,
rename _failed : '\u91cd\u547d\u540d\u5931\u8d25\uff1a' ,
delete _title : '\u5220\u9664' ,
delete _confirm : ( name ) => ` \u 8981 \u 5220 \u 9664 ${ name } \u 5417 \u ff1f ` ,
deleted : '\u5df2\u5220\u9664 ' ,
delete _failed : '\u5220\u9664\u5931\u8d25\uff1a' ,
new _file _prompt : '\u65b0\u6587\u4ef6\u540d\uff08\u4f8b\u5982 notes.md\uff09\uff1a' ,
feat: composer-centric UI refresh + Hermes Control Center (v0.50.0, closes #242)
* Polish workspace panel behavior and app dialogs
* Replace remaining emoji UI glyphs with Lucide icons
* Redesign composer footer around model and context controls
Move the model selector into the composer footer, replace the linear context pill with a compact circular badge plus tooltip, and remove the redundant topbar model pill.
Design credit and inspiration: Theo / T3 Code.
Reference implementation: https://github.com/pingdotgg/t3code/
* Remove obsolete activity bar
Drop the old activity bar, keep turn-scoped state in the composer footer, and route remaining non-chat status messages through toasts.
This leaves live tool cards and the message timeline as the primary progress UI, with the composer owning stop/cancel and brief turn status.
* Move workspace and model switching into composer footer
* Move profile switching into composer footer
* Refactor Hermes control center UI
* Redesign control center settings modal layout
Widen the modal to 860px, simplify the tab list to icon+label rows,
stretch the tab column's divider to full height, lock the panel to a
fixed height so switching tabs no longer resizes the outer shell, and
always open on the Conversation tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Put session item actions in a dropdown
* Use Hermes mark in sidebar control button
* Reset control center section on close
* Drop session-item left border indicator
Remove the left-border accent used for active, CLI, and project rows —
each state already has a dedicated cue (gold fill, cli badge, project
dot), so the border was redundant. Fully round the row, add 2px
bottom spacing between rows, and strip the matching JS/CSS overrides.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Increase session search input vertical padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Normalise odd pixel values across UI
Snap padding, gap, and border-radius values to the 2/4/6/8/10/12 grid
across composer chips, sidebar panels, cron list, settings, approval
buttons, dropdowns, and inline message edit — eliminating the 7/9/11px
drift that was making sibling elements feel subtly misaligned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add missing #btnMobileFiles button and .mobile-files-btn CSS (for mobile QA suite)
The mobile layout regression suite (test_mobile_layout.py) requires:
- #btnMobileFiles onclick=toggleMobileFiles() in topbar chips
- .mobile-files-btn CSS rules for responsive show/hide at 640/900px breakpoints
Also adds max-width guard to .profile-dropdown to prevent clipping at narrow viewports.
* Improve composer footer mobile responsiveness and UX
- Collapse composer chips to icon-only at <=400px viewports
- Add model chip icon (CPU) so it remains tappable when labels are hidden
- Show send button always (disabled state when empty, hidden during streaming)
- Show context usage indicator on session load, not just after streaming
- Add cancel status fallback timeout to prevent stale "Cancelling..." text
- Update tests to match new send button and busy state behavior
* Fix duplicate files button and broken workspace close on mobile
Remove redundant #btnMobileFiles button that duplicated #btnWorkspacePanelToggle
in the mobile topbar. Fix workspace panel close button calling undefined
closeMobileFiles() — now calls closeWorkspacePanel().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix model chip icon vertical alignment in composer footer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix workspace toggle button hidden on desktop by conflicting CSS class
Remove mobile-files-btn class from #btnWorkspacePanelToggle — its
display:none!important rule was overriding workspace-toggle-btn visibility
on non-mobile viewports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix session actions dots button inaccessible on mobile sidebar
Always show the session actions trigger on mobile (no hover state on
touch devices) and restore right padding so text truncates with
ellipsis before the dots icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer footer manage links not opening sidebar panel
The "Manage profiles" and "Manage workspaces" links in the composer
footer dropdowns called switchPanel() which only changes the active
panel content but doesn't open the sidebar. Replaced with
mobileSwitchPanel() which also opens the sidebar so the panel is
actually visible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Widen icon-only composer chips breakpoint from 400px to 768px
Move the icon-only chip styling up into the existing max-width:768px
media query so chips collapse to icon-only on tablets too, preventing
composer footer overflow on mid-size screens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer-left vertical scrollbar by setting overflow-y:hidden
When overflow-x is set to auto, the CSS spec implicitly changes
overflow-y from visible to auto, allowing a vertical scrollbar to
appear from slight chip padding/border overflow. Explicitly set
overflow-y:hidden to prevent this.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve rebase conflicts and fix control center test assertions
- Resolved 4 conflicts during rebase onto master (workspace.js,
boot.js, index.html, test_sprint34.py)
- Fixed test_sprint34.py: _controlSection -> _settingsSection,
cc-tab -> settings-tabs (matching actual implementation)
- Fixed quoting syntax error in test assertion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update version badge in System tab to v0.49.4
* docs: update README and CHANGELOG for v0.50.0 UI refresh, bump version badge
---------
Co-authored-by: Aron Prins <pwf.aron@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 18:55:40 +00:00
project _name _prompt : '\u9879\u76ee\u540d\u79f0\uff1a' ,
2026-04-08 14:55:03 +00:00
created : '\u5df2\u521b\u5efa ' ,
create _failed : '\u521b\u5efa\u5931\u8d25\uff1a' ,
new _folder _prompt : '\u65b0\u6587\u4ef6\u5939\u540d\u79f0\uff1a' ,
folder _created : '\u5df2\u521b\u5efa\u6587\u4ef6\u5939 ' ,
folder _create _failed : '\u521b\u5efa\u6587\u4ef6\u5939\u5931\u8d25\uff1a' ,
remove _title : '\u79fb\u9664' ,
empty _dir : '(\u7a7a)' ,
upload _failed : '\u4e0a\u4f20\u5931\u8d25\uff1a' ,
all _uploads _failed : ( n ) => ` ${ n } \u 4e2a \u 6587 \u 4ef6 \u 5168 \u 90e8 \u 4e0a \u 4f20 \u 5931 \u 8d25 ` ,
// settings panel
settings _title : '\u8bbe\u7f6e' ,
settings _save _btn : '\u4fdd\u5b58\u8bbe\u7f6e' ,
settings _label _model : '\u9ed8\u8ba4\u6a21\u578b' ,
settings _label _send _key : '\u53d1\u9001\u5feb\u6377\u952e' ,
settings _label _theme : '\u4e3b\u9898' ,
settings _label _language : '\u8bed\u8a00' ,
settings _label _token _usage : '\u663e\u793a token \u7528\u91cf' ,
2026-04-14 17:14:01 +00:00
settings _label _bubble _layout : '聊天气泡布局' ,
2026-04-08 14:55:03 +00:00
settings _label _cli _sessions : '\u663e\u793a CLI \u4f1a\u8bdd' ,
settings _label _sync _insights : '\u540c\u6b65\u5230 insights' ,
settings _label _check _updates : '\u68c0\u67e5\u66f4\u65b0' ,
settings _label _bot _name : '\u52a9\u624b\u540d\u79f0' ,
settings _label _password : '\u8bbf\u95ee\u5bc6\u7801' ,
settings _saved : '\u8bbe\u7f6e\u5df2\u4fdd\u5b58' ,
settings _save _failed : '\u4fdd\u5b58\u5931\u8d25\uff1a' ,
settings _load _failed : '\u8bbe\u7f6e\u52a0\u8f7d\u5931\u8d25\uff1a' ,
2026-04-14 12:51:19 +00:00
settings _saved _pw : '\u8bbe\u7f6e\u5df2\u4fdd\u5b58\uff0c\u5df2\u542f\u7528\u5bc6\u7801\u4fdd\u62a4\uff0c\u5f53\u524d\u6d4f\u89c8\u5668\u4f1a\u4fdd\u6301\u767b\u5f55' ,
settings _saved _pw _updated : '\u8bbe\u7f6e\u5df2\u4fdd\u5b58\uff0c\u5bc6\u7801\u5df2\u66f4\u65b0' ,
2026-04-08 14:55:03 +00:00
// login page
login _title : '\u767b\u5f55' ,
login _subtitle : '\u8f93\u5165\u5bc6\u7801\u7ee7\u7eed\u4f7f\u7528' ,
login _placeholder : '\u5bc6\u7801' ,
login _btn : '\u767b\u5f55' ,
login _invalid _pw : '\u5bc6\u7801\u9519\u8bef' ,
login _conn _failed : '\u8fde\u63a5\u5931\u8d25' ,
2026-04-13 01:10:37 +00:00
// sidebar & navigation
tab _chat : '聊天' ,
tab _memory : '记忆' ,
tab _skills : '技能' ,
tab _tasks : '任务' ,
tab _todos : '待办' ,
tab _workspaces : '工作区' ,
2026-04-14 17:14:01 +00:00
tab _profiles : '配置' ,
2026-04-13 01:10:37 +00:00
new _conversation : '新建对话' ,
filter _conversations : '筛选对话…' ,
2026-04-14 17:14:01 +00:00
session _time _unknown : '未知' ,
session _time _just _now : '刚刚' ,
session _time _minutes _ago : ( n ) => ` ${ n } 分钟前 ` ,
session _time _hours _ago : ( n ) => ` ${ n } 小时前 ` ,
session _time _days _ago : ( n ) => ` ${ n } 天前 ` ,
session _time _last _week : '上周' ,
session _time _bucket _today : '今天' ,
session _time _bucket _yesterday : '昨天' ,
session _time _bucket _this _week : '本周' ,
session _time _bucket _last _week : '上周' ,
session _time _bucket _older : '更早' ,
2026-04-13 01:10:37 +00:00
scheduled _jobs : '定时任务' ,
new _job : '新任务' ,
search _skills : '搜索技能…' ,
new _skill : '新技能' ,
save _skill : '保存技能' ,
personal _memory : '个人记忆' ,
current _task _list : '当前任务列表' ,
2026-04-14 17:14:01 +00:00
workspace _desc : '为你的会话添加并切换工作区。' ,
2026-04-13 01:10:37 +00:00
new _profile : '新配置' ,
transcript : '记录' ,
download _transcript : '下载为 Markdown' ,
import : '导入' ,
editing : '编辑中' ,
empty _title : '有什么可以帮您?' ,
empty _subtitle : '随时提问、运行命令、浏览文件或管理定时任务。' ,
cancel : '取消' ,
loading : '加载中…' ,
create _job : '创建任务' ,
suggest _plan : '帮我规划一个小项目。' ,
suggest _schedule : '今天有什么安排?' ,
suggest _files : '这个工作区有哪些文件?' ,
sign _out : '退出登录' ,
password _placeholder : '输入新密码…' ,
disable _auth : '停用认证' ,
settings _label _sound : '通知声音' ,
settings _label _notifications : '浏览器通知' ,
settings _desc _sound : '助手完成回复时播放提示音。' ,
settings _desc _notifications : '当标签页在后台时,回复完成后显示系统通知。' ,
settings _desc _token _usage : '在助手每次回复下方显示输入/输出 token 数量。也可以用 /usage 切换。' ,
2026-04-14 17:14:01 +00:00
settings _desc _bubble _layout : '开启后将用户消息右对齐、助手消息左对齐。默认关闭,以保持代码块和工具输出为全宽显示。' ,
2026-04-13 01:10:37 +00:00
settings _desc _cli _sessions : '将 Hermes CLI( state.db) 中的会话合并到会话列表。点击某个 CLI 会话可导入并继续对话。' ,
settings _desc _sync _insights : '将 WebUI token 使用情况同步到 state.db, 使 hermes /insights 包含浏览器会话数据。默认关闭。' ,
settings _desc _check _updates : '当有更新的 WebUI 或助手版本时显示横幅。会在后台定期执行 git fetch。' ,
settings _desc _bot _name : '助手在 UI 中的显示名称。默认为 Hermes。' ,
settings _desc _password : '输入新密码以设置或更改。留空保持当前设置。' ,
2026-04-14 17:14:01 +00:00
// onboarding
onboarding _badge : '首次运行' ,
onboarding _title : '欢迎使用 Hermes Web UI' ,
onboarding _lead : '快速引导将验证 Hermes、保存真实的提供商配置、选择工作区和模型, 并可选设置密码保护应用。' ,
onboarding _back : '返回' ,
onboarding _continue : '继续' ,
2026-04-14 17:10:22 +00:00
onboarding _skip : '跳过设置' ,
onboarding _skipped : '设置已跳过 — 使用现有配置。' ,
2026-04-14 17:14:01 +00:00
onboarding _open : '打开 Hermes' ,
onboarding _step _system _title : '系统检查' ,
onboarding _step _system _desc : '验证 Hermes Agent 与配置可见性。' ,
onboarding _step _setup _title : '提供商设置' ,
onboarding _step _setup _desc : '保存最小可用的 Hermes 提供商配置。' ,
onboarding _step _workspace _title : '工作区 + 模型' ,
onboarding _step _workspace _desc : '为新会话和聊天选择默认值。' ,
onboarding _step _password _title : '可选密码' ,
onboarding _step _password _desc : '在分享前为 Web UI 添加保护。' ,
onboarding _step _finish _title : '完成' ,
onboarding _step _finish _desc : '确认信息并进入应用。' ,
onboarding _notice _system _ready : 'Hermes Agent 看起来可从 Web UI 访问。' ,
onboarding _notice _system _unavailable : 'Hermes Agent 尚未完全可用。Bootstrap 可以安装它,但提供商设置可能仍需要终端。' ,
onboarding _check _agent : 'Hermes Agent' ,
onboarding _check _agent _ready : '已检测且可导入' ,
onboarding _check _agent _missing : '缺失或仅部分可导入' ,
onboarding _check _password : '密码' ,
onboarding _check _password _enabled : '已启用' ,
onboarding _check _password _disabled : '尚未启用' ,
onboarding _check _provider : '提供商配置' ,
onboarding _check _provider _ready : '可开始聊天' ,
onboarding _check _provider _partial : '已保存但不完整' ,
onboarding _check _provider _pending : '需要验证' ,
onboarding _config _file : '配置文件:' ,
onboarding _env _file : '.env 文件:' ,
onboarding _unknown : '未知' ,
onboarding _current _provider : '当前配置:' ,
onboarding _missing _imports : '缺失导入:' ,
onboarding _notice _setup _required : '请先在此选择一个简单的提供商路径。高级 OAuth 流程暂时仍建议在 Hermes CLI 中完成。' ,
onboarding _notice _setup _already _ready : '已检测到可用的 Hermes 提供商配置。你可以保留它,或在这里替换。' ,
onboarding _oauth _provider _ready _title : '提供商已完成认证' ,
onboarding _oauth _provider _ready _body : '此实例已配置为使用通过 Hermes CLI 设置的 OAuth 提供商(<strong>{provider}</strong>)。这里不需要 API key, 点击继续即可完成设置。' ,
onboarding _oauth _provider _not _ready _title : 'OAuth 提供商尚未认证' ,
onboarding _oauth _provider _not _ready _body : '此实例已配置为使用 <strong>{provider}</strong>,该提供商使用 OAuth 而非 API key。请在终端运行 <code>hermes auth</code> 或 <code>hermes model</code> 完成认证,然后重新加载 Web UI。' ,
onboarding _oauth _switch _hint : '或者在下方选择其他提供商,切换到 API key 配置:' ,
onboarding _notice _workspace : '这些值复用与正式应用相同的设置 API。' ,
onboarding _workspace _label : '工作区' ,
onboarding _workspace _or _path : '或输入工作区路径' ,
onboarding _workspace _placeholder : '/home/you/workspace' ,
onboarding _provider _label : '设置模式' ,
onboarding _quick _setup _badge : '快速设置' ,
onboarding _api _key _label : 'API key' ,
onboarding _api _key _placeholder : '留空可保留已保存的 key' ,
onboarding _api _key _help _prefix : '会作为密钥保存到 Hermes .env 文件中,变量名为' ,
onboarding _base _url _label : 'Base URL' ,
onboarding _base _url _placeholder : 'https://your-endpoint.example/v1' ,
onboarding _base _url _help : '用于 OpenAI 兼容路由、自托管服务、LiteLLM、Ollama、LM Studio、vLLM 或类似端点。' ,
onboarding _model _label : '默认模型' ,
onboarding _workspace _help : '选择设置完成后 Hermes 在新聊天中使用的模型。' ,
onboarding _custom _model _placeholder : 'your-model-name' ,
onboarding _custom _model _help : '对于自定义端点,请填写服务端要求的精确模型 ID。' ,
onboarding _notice _password _enabled : '已配置密码。仅在你想替换时输入新密码。' ,
onboarding _notice _password _recommended : '可选,但如果你会把 UI 暴露到 localhost 之外,建议设置。' ,
onboarding _password _label : '密码(可选)' ,
onboarding _password _placeholder : '留空则跳过' ,
onboarding _password _help : '密码通过现有设置 API 保存,并在服务端进行哈希处理。' ,
onboarding _notice _finish : '你之后仍可在设置中修改这些选项。' ,
onboarding _not _set : '未设置' ,
onboarding _password _will _enable : '将启用' ,
2026-04-14 17:48:18 +00:00
onboarding _password _will _replace : '将被替换' ,
onboarding _password _keep _existing : '保留当前密码' ,
onboarding _password _remains _disabled : '将保持禁用' ,
2026-04-14 17:14:01 +00:00
onboarding _password _skipped : '暂时跳过' ,
onboarding _finish _help : '完成后会在设置中写入 <code>onboarding_completed</code>,并进入常规应用界面。' ,
onboarding _error _choose _workspace : '继续前请先选择工作区。' ,
onboarding _error _choose _model : '继续前请先选择模型。' ,
onboarding _error _provider _required : '继续前请先选择设置模式。' ,
onboarding _error _base _url _required : '自定义端点必须填写 Base URL。' ,
onboarding _error _workspace _required : '必须填写工作区。' ,
onboarding _error _model _required : '必须填写模型。' ,
onboarding _complete : '引导完成' ,
// panel/runtime i18n
error _prefix : '错误:' ,
not _available : '无' ,
never : '从未' ,
add : '添加' ,
add _failed : '添加失败:' ,
remove _failed : '移除失败:' ,
switch _failed : '切换失败:' ,
name _required : '名称不能为空' ,
content _required : '内容不能为空' ,
view : '查看' ,
dismiss : '忽略' ,
disable : '停用' ,
cron _no _jobs : '未找到定时任务。' ,
cron _status _off : '关闭' ,
cron _status _paused : '暂停' ,
cron _status _error : '错误' ,
cron _status _active : '运行中' ,
cron _next : '下次' ,
cron _last : '上次' ,
cron _run _now : '立即运行' ,
cron _pause : '暂停' ,
cron _resume : '恢复' ,
cron _job _name _placeholder : '任务名称' ,
cron _schedule _placeholder : '调度表达式' ,
cron _prompt _placeholder : '提示词' ,
cron _last _output : '最近输出' ,
cron _all _runs : '全部运行记录' ,
cron _hide _runs : '隐藏记录' ,
cron _no _runs _yet : '(暂无运行记录)' ,
cron _schedule _required _example : '必须填写调度(例如 "0 9 * * *" 或 "every 1h") ' ,
cron _schedule _required : '必须填写调度' ,
cron _prompt _required : '必须填写提示词' ,
cron _job _created : '任务已创建' ,
cron _job _triggered : '任务已触发' ,
cron _job _paused : '任务已暂停' ,
cron _job _resumed : '任务已恢复' ,
cron _job _updated : '任务已更新' ,
cron _delete _confirm _title : '删除定时任务' ,
cron _delete _confirm _message : '此操作无法撤销。' ,
cron _job _deleted : '任务已删除' ,
cron _completion _status : ( name , status ) => ` 定时任务“ ${ name } ” ${ status } ` ,
status _failed : '失败' ,
status _completed : '完成' ,
todos _no _active : '此会话暂无活动任务列表。' ,
clear _conversation _title : '清空对话' ,
clear _conversation _message : '要清空所有消息吗?此操作无法撤销。' ,
clear _failed : '清空失败:' ,
skills _no _match : '没有匹配的技能。' ,
linked _files : '关联文件' ,
skill _load _failed : '加载技能失败:' ,
skill _file _load _failed : '加载文件失败:' ,
skill _name _required : '技能名称不能为空' ,
skill _updated : '技能已更新' ,
skill _created : '技能已创建' ,
memory _notes _label : '记忆(备注)' ,
memory _saved : '记忆已保存' ,
my _notes : '我的备注' ,
user _profile : '用户画像' ,
no _notes _yet : '暂无备注。' ,
no _profile _yet : '暂无用户画像。' ,
workspace _choose _path : '选择工作区路径' ,
workspace _choose _path _meta : '添加已校验路径并切换当前会话' ,
workspace _manage : '管理工作区' ,
workspace _manage _meta : '打开 Spaces 面板' ,
workspace _use _title : '用于当前会话' ,
workspace _use : '使用' ,
workspace _add _path _placeholder : '添加工作区路径(例如 /home/user/my-project) ' ,
workspace _paths _validated _hint : '保存前会校验路径是否为已存在目录。' ,
workspace _added : '工作区已添加' ,
workspace _remove _confirm _title : '移除工作区' ,
workspace _remove _confirm _message : ( path ) => ` 要移除“ ${ path } ”吗? ` ,
workspace _removed : '工作区已移除' ,
workspace _switch _prompt _title : '切换工作区' ,
workspace _switch _prompt _message : '输入绝对路径以添加并切换当前会话的工作区。' ,
workspace _switch _prompt _confirm : '切换' ,
workspace _switch _prompt _placeholder : '/Users/you/project' ,
workspace _not _added : '工作区未添加成功' ,
workspace _already _saved : '工作区已存在,请在列表中选择' ,
workspace _busy _switch : 'Agent 运行中,无法切换工作区' ,
discard _file _edits _title : '放弃文件编辑?' ,
discard _file _edits _message : '切换工作区将丢弃预览区未保存的文件修改。' ,
workspace _switched _to : ( name ) => ` 已切换到 ${ name } ` ,
profiles _no _profiles : '未找到配置档。' ,
profile _api _keys _configured : '已配置 API 密钥' ,
profile _gateway _running : '网关运行中' ,
profile _gateway _stopped : '网关已停止' ,
profile _active : '当前' ,
profile _no _configuration : '无配置' ,
2026-04-14 17:14:01 +00:00
profile _skill _count : ( count ) => ` ${ count } 个技能 ` ,
2026-04-14 17:14:01 +00:00
profile _use : '使用' ,
profile _switch _title : '切换到此配置档' ,
2026-04-14 17:14:01 +00:00
profile _delete _title : '删除此配置档' ,
2026-04-14 17:14:01 +00:00
manage _profiles : '管理配置档' ,
profiles _load _failed : '加载配置档失败' ,
profiles _busy _switch : 'Agent 运行中,无法切换配置档' ,
profile _switched _new _conversation : ( name ) => ` 已切换到配置档: ${ name } ,并新建对话 ` ,
profile _switched : ( name ) => ` 已切换到配置档: ${ name } ` ,
profile _name _rule : '仅允许小写字母、数字、连字符和下划线' ,
profile _base _url _rule : 'Base URL 必须以 http:// 或 https:// 开头' ,
profile _created : ( name ) => ` 配置档已创建: ${ name } ` ,
profile _delete _confirm _title : ( name ) => ` 删除配置档“ ${ name } ”? ` ,
profile _delete _confirm _message : '这将删除该配置档的所有配置、技能、记忆和会话。' ,
profile _deleted : ( name ) => ` 配置档已删除: ${ name } ` ,
active _conversation _none : '当前未选择活动会话。' ,
active _conversation _meta : ( title , count ) => ` ${ title } · ${ count } 条消息 ` ,
settings _unsaved _changes : '你有未保存的更改。' ,
sign _out _failed : '退出登录失败:' ,
disable _auth _confirm _title : '停用密码保护' ,
disable _auth _confirm _message : '任何人都可以访问此实例。' ,
auth _disabled : '认证已停用,密码保护已移除' ,
disable _auth _failed : '停用认证失败:' ,
bg _error _single : ( title ) => ` “ ${ title } ”出现错误 ` ,
bg _error _multi : ( count ) => ` ${ count } 个会话出现错误 ` ,
v0.46.0: security, Docker UID/GID, model discovery, i18n, cancel fix
* fix: decode HTML entities before markdown processing + zh/zh-Hant translations (#239)
Adds decode() helper in renderMd() to fix double-escaping of HTML entities
from LLM output (e.g. <code> becoming &lt;code&gt; instead
of rendering). XSS-safe: decode runs before esc(), only 5 entity patterns.
Also adds 40+ missing zh (Simplified Chinese) translation keys and a new
zh-Hant (Traditional Chinese) locale with 163 keys.
Fix applied: removed duplicate settings_label_notifications key in both
zh and zh-Hant locales.
Fixes #240
* fix: restore custom model list discovery with config api key (#238)
get_available_models() now reads api_key from config.yaml before env vars:
1. model.api_key
2. providers.<active>.api_key / providers.custom.api_key
3. env var fallbacks (HERMES_API_KEY, OPENAI_API_KEY, etc.)
Also adds OpenAI/Python User-Agent header and a regression test covering
authenticated /v1/models discovery.
Fixes users with LM Studio / Ollama custom endpoints configured in
config.yaml whose model picker silently collapsed to the default model.
* feat: Docker UID/GID matching to avoid root-owned .hermes files (#237)
Adds docker_init.bash with hermeswebuitoo/hermeswebui user pattern so
container files match the host user UID/GID. Prevents .hermes volume
mounts from being owned by root when using a non-root host user.
Configure via WANTED_UID and WANTED_GID env vars (default 1000/1000).
Readme updated with setup instructions.
Fix applied: removed duplicate WANTED_GID=1000 line in docker-compose.yml
that was overriding the ${GID:-1000} variable expansion.
* security: redact credentials from API responses and fix credential file permissions (#243)
Adds response-layer credential redaction to three endpoints:
- GET /api/session — messages[], tool_calls[], and title
- GET /api/session/export — download also redacted
- SSE done event — session payload in stream
- GET /api/memory — MEMORY.md and USER.md content
Adds api/startup.py with fix_credential_permissions() at server startup.
Adds 13 tests in tests/test_security_redaction.py.
Merged with #237 container detection changes in server.py.
* fix: cancel button now interrupts agent and cleans up UI state (#244)
Wires agent.interrupt() into cancel_stream() so the backend actually
stops tool execution when the user clicks Cancel, rather than only
stopping the SSE stream while the agent keeps running.
Changes:
- api/config.py: adds AGENT_INSTANCES dict (stream_id -> AIAgent)
- api/streaming.py: stores agent in AGENT_INSTANCES after creation,
checks CANCEL_FLAGS immediately after store (race condition fix),
calls agent.interrupt() in cancel_stream(), cleans up in finally block
- static/boot.js: removes stale setStatus(cancelling) call
- static/messages.js: setBusy(false)/setStatus('') unconditionally on cancel
Race condition fix: after storing agent in AGENT_INSTANCES, immediately
checks if CANCEL_FLAGS[stream_id] is already set (cancel arrived during
agent init) and interrupts before starting. Check is inside the same
STREAMS_LOCK acquisition, making it atomic.
New test file: tests/test_cancel_interrupt.py with 6 unit tests.
* docs: v0.46.0 release notes, bump version, update test counts
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 17:17:52 +00:00
} ,
// Traditional Chinese (zh-Hant)
'zh-Hant' : {
_lang : 'zh-Hant' ,
_label : '\u7e41\u9ad4\u4e2d\u6587' ,
_speech : 'zh-TW' ,
// boot.js
cancelling : '\u6b63\u5728\u53d6\u6d88...' ,
cancel _failed : '\u53d6\u6d88\u5931\u6557\uff1a' ,
mic _denied : '\u9ea6\u514b\u98a8\u8a2a\u554f\u88ab\u62d2\u7d75\uff0c\u8acb\u6aa2\u67e5\u700f\u89bd\u5668\u6b0a\u9650\u3002' ,
mic _no _speech : '\u6c92\u6709\u6aa2\u6e2c\u5230\u8a71\u97f3\uff0c\u8acb\u518d\u5617\u4e00\u6b21\u3002' ,
mic _network : '\u8a71\u97f3\u8b58\u5225\u76ee\u524d\u4e0d\u53ef\u7528\u3002' ,
mic _error : '\u8a71\u97f3\u8f38\u5165\u51fa\u932f\uff1a' ,
session _imported : '\u6703\u8a71\u5df2\u5c0e\u5165' ,
import _failed : '\u5c0e\u5165\u5931\u6557\uff1a' ,
import _invalid _json : 'JSON \u7121\u6548' ,
image _pasted : '\u5df2\u7c98\u8cbc\u5716\u7247\uff1a' ,
// messages.js
edit _message : '\u7de8\u8f2f\u8a0a\u606f' ,
regenerate : '\u91cd\u65b0\u751f\u6210\u56de\u8986' ,
copy : '\u8907\u88fd' ,
copied : '\u5df2\u8907\u88fd' ,
you : '\u4f60' ,
thinking : '\u601d\u8003\u904e\u7a0b' ,
expand _all : '\u5168\u90e8\u5c55\u958b' ,
collapse _all : '\u5168\u90e8\u6298\u758a' ,
edit _failed : '\u7de8\u8f2f\u5931\u6557\uff1a' ,
regen _failed : '\u91cd\u65b0\u751f\u6210\u5931\u6557\uff1a' ,
reconnect _active : '\u56de\u8986\u4ecd\u5728\u751f\u6210\u4e2d\uff0c\u6e96\u5099\u597d\u5f8c\u8981\u91cd\u65b0\u52a0\u8f09\u55ce\uff1f' ,
reconnect _finished : '\u4f60\u96e2\u958b\u6642\u6709\u56de\u8986\u6b63\u5728\u751f\u6210\uff0c\u8a0a\u606f\u5167\u5bb9\u53ef\u80fd\u5df2\u7d93\u66f4\u65b0\u3002' ,
// approval card
approval _heading : '\u9700\u8981\u5ba1\u6838' ,
approval _desc _prefix : '\u6aa2\u6e2c\u5230\u5371\u96aa\u547d\u4ee4' ,
approval _btn _once : '\u5141\u8a31\u4e00\u6b21' ,
approval _btn _once _title : '\u5141\u8a31\u57f7\u884c\u6b64\u547d\u4ee4\u4e00\u6b21\uff08Enter\uff09' ,
approval _btn _session : '\u672c\u6b21\u5141\u8a31' ,
approval _btn _session _title : '\u672c\u6b21\u6703\u8a71\u671f\u9593\u5141\u8a31' ,
2026-04-13 02:19:57 +00:00
approval _btn _always : '始終允許' ,
approval _btn _always _title : '始終允許此命令模式' ,
v0.46.0: security, Docker UID/GID, model discovery, i18n, cancel fix
* fix: decode HTML entities before markdown processing + zh/zh-Hant translations (#239)
Adds decode() helper in renderMd() to fix double-escaping of HTML entities
from LLM output (e.g. <code> becoming &lt;code&gt; instead
of rendering). XSS-safe: decode runs before esc(), only 5 entity patterns.
Also adds 40+ missing zh (Simplified Chinese) translation keys and a new
zh-Hant (Traditional Chinese) locale with 163 keys.
Fix applied: removed duplicate settings_label_notifications key in both
zh and zh-Hant locales.
Fixes #240
* fix: restore custom model list discovery with config api key (#238)
get_available_models() now reads api_key from config.yaml before env vars:
1. model.api_key
2. providers.<active>.api_key / providers.custom.api_key
3. env var fallbacks (HERMES_API_KEY, OPENAI_API_KEY, etc.)
Also adds OpenAI/Python User-Agent header and a regression test covering
authenticated /v1/models discovery.
Fixes users with LM Studio / Ollama custom endpoints configured in
config.yaml whose model picker silently collapsed to the default model.
* feat: Docker UID/GID matching to avoid root-owned .hermes files (#237)
Adds docker_init.bash with hermeswebuitoo/hermeswebui user pattern so
container files match the host user UID/GID. Prevents .hermes volume
mounts from being owned by root when using a non-root host user.
Configure via WANTED_UID and WANTED_GID env vars (default 1000/1000).
Readme updated with setup instructions.
Fix applied: removed duplicate WANTED_GID=1000 line in docker-compose.yml
that was overriding the ${GID:-1000} variable expansion.
* security: redact credentials from API responses and fix credential file permissions (#243)
Adds response-layer credential redaction to three endpoints:
- GET /api/session — messages[], tool_calls[], and title
- GET /api/session/export — download also redacted
- SSE done event — session payload in stream
- GET /api/memory — MEMORY.md and USER.md content
Adds api/startup.py with fix_credential_permissions() at server startup.
Adds 13 tests in tests/test_security_redaction.py.
Merged with #237 container detection changes in server.py.
* fix: cancel button now interrupts agent and cleans up UI state (#244)
Wires agent.interrupt() into cancel_stream() so the backend actually
stops tool execution when the user clicks Cancel, rather than only
stopping the SSE stream while the agent keeps running.
Changes:
- api/config.py: adds AGENT_INSTANCES dict (stream_id -> AIAgent)
- api/streaming.py: stores agent in AGENT_INSTANCES after creation,
checks CANCEL_FLAGS immediately after store (race condition fix),
calls agent.interrupt() in cancel_stream(), cleans up in finally block
- static/boot.js: removes stale setStatus(cancelling) call
- static/messages.js: setBusy(false)/setStatus('') unconditionally on cancel
Race condition fix: after storing agent in AGENT_INSTANCES, immediately
checks if CANCEL_FLAGS[stream_id] is already set (cancel arrived during
agent init) and interrupts before starting. Check is inside the same
STREAMS_LOCK acquisition, making it atomic.
New test file: tests/test_cancel_interrupt.py with 6 unit tests.
* docs: v0.46.0 release notes, bump version, update test counts
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 17:17:52 +00:00
approval _btn _deny : '\u62d2\u7edd' ,
approval _btn _deny _title : '\u62d2\u7edd — \u4e0d\u57f7\u884c\u6b64\u547d\u4ee4' ,
approval _responding : '\u8655\u7406\u4e2d\u2026' ,
untitled : '\u672a\u547d\u540d' ,
n _messages : ( n ) => ` ${ n } \u 689d \u 8a0a \u 606f ` ,
model _unavailable : '\uff08\u4e0d\u53ef\u7528\uff09' ,
model _unavailable _title : '\u6b64\u6a21\u578b\u5df2\u7d93\u4e0d\u5728\u7576\u524d provider \u5217\u8868\u4e2d' ,
fix: warn on provider/model mismatch, surface auth errors (#266)
* fix: warn on provider/model mismatch, surface auth errors (#266)
Fixes #266 — WebUI silently ignores provider/model selection mismatch.
The problem: selecting an OpenRouter (or Anthropic/OpenAI) model while
Hermes is configured for a different provider (e.g. local Ollama) sends
the request to the wrong endpoint, which returns a 401 Unauthorized error
with no UI indication of why.
Three-layer fix:
1. api/streaming.py — detect 401/auth errors explicitly
Added is_auth_error detection covering '401', 'AuthenticationError',
'authentication', 'unauthorized', 'invalid api key', and the specific
Ollama error string 'no cookie auth credentials'. Auth errors emit
apperror with type='auth_mismatch' and a hint pointing to 'hermes model'.
2. static/ui.js — expose active_provider and warn on selection
- populateModelDropdown() stores data.active_provider from /api/models
as window._activeProvider (the field was already in the response but
the frontend never used it)
- New _checkProviderMismatch(modelId) helper: compares the selected
model's slash-prefix (e.g. 'openai/' from 'openai/gpt-4o') against
the active provider. Skips the check for 'openrouter' and 'custom'
to avoid false positives on configs that legitimately route any model.
3. static/boot.js — warn on model dropdown change
modelSelect.onchange calls _checkProviderMismatch() and shows a toast
when the selected model looks incompatible with the configured provider.
4. static/messages.js — distinct UI label for auth errors
apperror handler now distinguishes type='auth_mismatch' and shows
'Provider mismatch' as the error label instead of 'Error'.
5. static/i18n.js — provider_mismatch_warning and provider_mismatch_label
keys added to all 5 locales (en, es, de, zh-Hans, zh-Hant).
Tests: 21 new tests in tests/test_provider_mismatch.py covering all
five change areas. 679/679 total pass (658 baseline + 21 new).
* fix: t() call args spread + use i18n label for auth mismatch
1. ui.js: _checkProviderMismatch passed [modelId, ap] as a single
array arg to t(). Since t(key, ...args) spreads, the function
received the array as m and undefined as p. Fixed to pass as
separate args: t('provider_mismatch_warning', modelId, ap).
2. messages.js: 'Provider mismatch' label was hardcoded instead of
using t('provider_mismatch_label'). Now uses the i18n key with
fallback for when t() isn't available.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 04:25:18 +00:00
provider _mismatch _warning : ( m , p ) => ` \" ${ m } \" \u 53ef \u 80fd \u 7121 \u 6cd5 \u 5728 \u 7576 \u 524d \u 914d \u 7f6e \u 7684 \u 63d0 \u 4f9b \u 8005 ( ${ p } ) \u 4e0b \u 904b \u 4f5c \u 3002 \u 5c1a \u 9001 \u ff0c \u 6216 \u 5728 \u 7d42 \u 7aef \u 57f7 \u 884c \` hermes model \` \u 5207 \u 63db \u 3002 ` ,
provider _mismatch _label : '\u63d0\u4f9b\u8005\u4e0d\u76f8\u7b26' ,
v0.46.0: security, Docker UID/GID, model discovery, i18n, cancel fix
* fix: decode HTML entities before markdown processing + zh/zh-Hant translations (#239)
Adds decode() helper in renderMd() to fix double-escaping of HTML entities
from LLM output (e.g. <code> becoming &lt;code&gt; instead
of rendering). XSS-safe: decode runs before esc(), only 5 entity patterns.
Also adds 40+ missing zh (Simplified Chinese) translation keys and a new
zh-Hant (Traditional Chinese) locale with 163 keys.
Fix applied: removed duplicate settings_label_notifications key in both
zh and zh-Hant locales.
Fixes #240
* fix: restore custom model list discovery with config api key (#238)
get_available_models() now reads api_key from config.yaml before env vars:
1. model.api_key
2. providers.<active>.api_key / providers.custom.api_key
3. env var fallbacks (HERMES_API_KEY, OPENAI_API_KEY, etc.)
Also adds OpenAI/Python User-Agent header and a regression test covering
authenticated /v1/models discovery.
Fixes users with LM Studio / Ollama custom endpoints configured in
config.yaml whose model picker silently collapsed to the default model.
* feat: Docker UID/GID matching to avoid root-owned .hermes files (#237)
Adds docker_init.bash with hermeswebuitoo/hermeswebui user pattern so
container files match the host user UID/GID. Prevents .hermes volume
mounts from being owned by root when using a non-root host user.
Configure via WANTED_UID and WANTED_GID env vars (default 1000/1000).
Readme updated with setup instructions.
Fix applied: removed duplicate WANTED_GID=1000 line in docker-compose.yml
that was overriding the ${GID:-1000} variable expansion.
* security: redact credentials from API responses and fix credential file permissions (#243)
Adds response-layer credential redaction to three endpoints:
- GET /api/session — messages[], tool_calls[], and title
- GET /api/session/export — download also redacted
- SSE done event — session payload in stream
- GET /api/memory — MEMORY.md and USER.md content
Adds api/startup.py with fix_credential_permissions() at server startup.
Adds 13 tests in tests/test_security_redaction.py.
Merged with #237 container detection changes in server.py.
* fix: cancel button now interrupts agent and cleans up UI state (#244)
Wires agent.interrupt() into cancel_stream() so the backend actually
stops tool execution when the user clicks Cancel, rather than only
stopping the SSE stream while the agent keeps running.
Changes:
- api/config.py: adds AGENT_INSTANCES dict (stream_id -> AIAgent)
- api/streaming.py: stores agent in AGENT_INSTANCES after creation,
checks CANCEL_FLAGS immediately after store (race condition fix),
calls agent.interrupt() in cancel_stream(), cleans up in finally block
- static/boot.js: removes stale setStatus(cancelling) call
- static/messages.js: setBusy(false)/setStatus('') unconditionally on cancel
Race condition fix: after storing agent in AGENT_INSTANCES, immediately
checks if CANCEL_FLAGS[stream_id] is already set (cancel arrived during
agent init) and interrupts before starting. Check is inside the same
STREAMS_LOCK acquisition, making it atomic.
New test file: tests/test_cancel_interrupt.py with 6 unit tests.
* docs: v0.46.0 release notes, bump version, update test counts
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 17:17:52 +00:00
// commands.js
cmd _help : '\u67e5\u770b\u53ef\u7528\u547d\u4ee4' ,
cmd _clear : '\u6e05\u7a7a\u7576\u524d\u5c0d\u8a71\u8a0a\u606f' ,
cmd _compact : '\u58d3\u7e2e\u5c0d\u8a71\u4e0a\u4e0b\u6587' ,
cmd _model : '\u5207\u63db\u6a21\u578b\uff08\u4f8b\u5982 /model gpt-4o\uff09' ,
cmd _workspace : '\u6309\u540d\u7a31\u5207\u63db\u5de5\u4f5c\u5340' ,
cmd _new : '\u65b0\u5efa\u804a\u5929\u6703\u8a71' ,
cmd _usage : '\u5207\u63db token \u7528\u91cf\u986f\u793a' ,
cmd _theme : '\u5207\u63db\u4e3b\u984c\uff08dark/light/slate/solarized/monokai/nord/oled\uff09' ,
cmd _personality : '\u5207\u63db Agent \u4eba\u8a2d' ,
v0.47.0: dialogs, session menu, /skills, mobile fixes, mobile QA suite
* fix: custom provider with slash model name no longer rerouted to OpenRouter (#255)
When base_url is configured in config.yaml, resolve_model_provider() now
trusts the configured provider/base_url entirely and skips the slash-based
OpenRouter heuristic. Fixes google/gemma-4-26b-a4b with provider:custom
being silently routed to OpenRouter, resulting in 401 errors.
Fixes #230
* test: mobile layout regression suite — 14 tests for every QA run (#254)
Adds tests/test_mobile_layout.py with 14 static regression tests that run
on every QA pass to catch mobile layout breakage before it reaches prod.
Covers: breakpoints at 900px/640px, right panel slide-over CSS, mobile
overlay, bottom nav, files button, profile dropdown z-index, chip overflow,
workspace close, 100dvh, 44px touch targets, 16px font-size on textarea.
* feat: /skills slash command lists and filters available Hermes skills (#257)
Adds /skills [query] command to commands.js. Fetches from /api/skills,
groups by category (alphabetically sorted), displays as a formatted
assistant message. Optional query filters by name, description, or category.
i18n keys added for en, de, zh, zh-Hant. 1 regression test added.
Fixes #248
* feat: shared app dialogs replace native confirm()/prompt() calls (#251)
Adds showConfirmDialog() and showPromptDialog() helpers to ui.js, backed
by a themed #appDialogOverlay. Replaces all 11 native browser confirm/prompt
call sites across panels.js, sessions.js, ui.js, workspace.js.
Supports: danger mode, keyboard focus trap (Tab/Escape/Enter), focus restore,
ARIA roles, mobile-responsive stacked buttons at 640px. i18n for en/de/zh/zh-Hant.
5 new tests in test_sprint33.py verify markup, CSS, helpers, and absence of
native dialog calls.
Extracted from PR #242.
* fix: Android Chrome mobile — workspace panel close + profile dropdown (#256)
Fix #247: toggleMobileFiles() now shows/hides the mobile overlay when
toggling the right workspace panel. New closeMobileFiles() helper closes
the panel with correct overlay state tracking. Overlay onclick calls both
closeMobileSidebar() and closeMobileFiles(). Mobile-only close button (x)
added to workspace panel header.
Fix #246: profile dropdown uses position:fixed;top:56px;right:8px at
max-width:900px, escaping the overflow-x:auto stacking context that was
clipping it on Android Chrome.
Fix applied during review: closeMobileSidebar() now checks if the right
panel is still open before hiding the overlay, preventing the overlay from
disappearing when only the sidebar is closed.
Fixes #247 Fixes #246
* feat: session ⋯ action dropdown replaces per-row buttons (#252)
Replaces the 5 per-row hover action buttons (pin/move/archive/duplicate/trash)
with a single ⋯ trigger that opens a positioned dropdown menu. Menu has full
keyboard (Escape), click-outside, scroll, and resize-reposition handling.
Position:fixed prevents sidebar clipping.
5 actions: Pin/Unpin, Move to project, Archive/Unarchive, Duplicate, Delete
(danger style). Each with icon and descriptive subtitle.
Updated test_sprint16.py: test_sessions_js_uses_action_menu_not_per_row_buttons
asserts the new trigger and menu functions exist, old per-row classes are gone.
Extracted from PR #242.
* docs: v0.47.0 release notes, bump version, update test counts (645)
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 19:19:12 +00:00
cmd _skills : '\u5217\u51fa\u53ef\u7528\u7684 Hermes \u6280\u80fd' ,
v0.46.0: security, Docker UID/GID, model discovery, i18n, cancel fix
* fix: decode HTML entities before markdown processing + zh/zh-Hant translations (#239)
Adds decode() helper in renderMd() to fix double-escaping of HTML entities
from LLM output (e.g. <code> becoming &lt;code&gt; instead
of rendering). XSS-safe: decode runs before esc(), only 5 entity patterns.
Also adds 40+ missing zh (Simplified Chinese) translation keys and a new
zh-Hant (Traditional Chinese) locale with 163 keys.
Fix applied: removed duplicate settings_label_notifications key in both
zh and zh-Hant locales.
Fixes #240
* fix: restore custom model list discovery with config api key (#238)
get_available_models() now reads api_key from config.yaml before env vars:
1. model.api_key
2. providers.<active>.api_key / providers.custom.api_key
3. env var fallbacks (HERMES_API_KEY, OPENAI_API_KEY, etc.)
Also adds OpenAI/Python User-Agent header and a regression test covering
authenticated /v1/models discovery.
Fixes users with LM Studio / Ollama custom endpoints configured in
config.yaml whose model picker silently collapsed to the default model.
* feat: Docker UID/GID matching to avoid root-owned .hermes files (#237)
Adds docker_init.bash with hermeswebuitoo/hermeswebui user pattern so
container files match the host user UID/GID. Prevents .hermes volume
mounts from being owned by root when using a non-root host user.
Configure via WANTED_UID and WANTED_GID env vars (default 1000/1000).
Readme updated with setup instructions.
Fix applied: removed duplicate WANTED_GID=1000 line in docker-compose.yml
that was overriding the ${GID:-1000} variable expansion.
* security: redact credentials from API responses and fix credential file permissions (#243)
Adds response-layer credential redaction to three endpoints:
- GET /api/session — messages[], tool_calls[], and title
- GET /api/session/export — download also redacted
- SSE done event — session payload in stream
- GET /api/memory — MEMORY.md and USER.md content
Adds api/startup.py with fix_credential_permissions() at server startup.
Adds 13 tests in tests/test_security_redaction.py.
Merged with #237 container detection changes in server.py.
* fix: cancel button now interrupts agent and cleans up UI state (#244)
Wires agent.interrupt() into cancel_stream() so the backend actually
stops tool execution when the user clicks Cancel, rather than only
stopping the SSE stream while the agent keeps running.
Changes:
- api/config.py: adds AGENT_INSTANCES dict (stream_id -> AIAgent)
- api/streaming.py: stores agent in AGENT_INSTANCES after creation,
checks CANCEL_FLAGS immediately after store (race condition fix),
calls agent.interrupt() in cancel_stream(), cleans up in finally block
- static/boot.js: removes stale setStatus(cancelling) call
- static/messages.js: setBusy(false)/setStatus('') unconditionally on cancel
Race condition fix: after storing agent in AGENT_INSTANCES, immediately
checks if CANCEL_FLAGS[stream_id] is already set (cancel arrived during
agent init) and interrupts before starting. Check is inside the same
STREAMS_LOCK acquisition, making it atomic.
New test file: tests/test_cancel_interrupt.py with 6 unit tests.
* docs: v0.46.0 release notes, bump version, update test counts
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 17:17:52 +00:00
available _commands : '\u53ef\u7528\u547d\u4ee4\uff1a' ,
type _slash : '\u8f38\u5165 / \u53ef\u67e5\u770b\u547d\u4ee4' ,
conversation _cleared : '\u5c0d\u8a71\u5df2\u6e05\u7a7a' ,
model _usage : '\u7528\u6cd5\uff1a/model <name>' ,
no _model _match : '\u6c92\u6709\u5339\u914d\u201c' ,
switched _to : '\u5df2\u5207\u63db\u5230 ' ,
workspace _usage : '\u7528\u6cd5\uff1a/workspace <name>' ,
no _workspace _match : '\u6c92\u6709\u5339\u914d\u201c' ,
switched _workspace : '\u5df2\u5207\u63db\u5de5\u4f5c\u5340\uff1a' ,
workspace _switch _failed : '\u5de5\u4f5c\u5340\u5207\u63db\u5931\u6557\uff1a' ,
new _session : '\u5df2\u65b0\u5efa\u6703\u8a71' ,
compressing : '\u6b63\u5728\u8981\u6c42\u58d3\u7e2e\u4e0a\u4e0b\u6587...' ,
token _usage _on : 'Token \u7528\u91cf\u986f\u793a\u5df2\u958b\u555f' ,
token _usage _off : 'Token \u7528\u91cf\u986f\u793a\u5df2\u95dc\u9589' ,
theme _usage : '\u7528\u6cd5\uff1a/theme ' ,
theme _set : '\u4e3b\u984c\uff1a' ,
no _active _session : '\u7576\u524d\u6c92\u6709\u6d3b\u52d5\u6703\u8a71' ,
no _personalities : '\u6c92\u6709\u627e\u5230\u4eba\u8a2d\uff08\u53ef\u6dfb\u52a0\u5230 ~/.hermes/personalities/\uff09' ,
available _personalities : '\u53ef\u7528\u4eba\u8a2d\uff1a' ,
personality _switch _hint : '\n\n\u4f7f\u7528 `/personality <name>` \u5207\u63db\uff0c\u6216\u7528 `/personality none` \u6e05\u7a7a\u3002' ,
personalities _load _failed : '\u52a0\u8f7d\u4eba\u8a2d\u5931\u6557' ,
personality _cleared : '\u4eba\u8a2d\u5df2\u6e05\u7a7a' ,
personality _set : '\u7576\u524d\u4eba\u8a2d\uff1a' ,
failed _colon : '\u5931\u6557\uff1a' ,
// ui.js
no _workspace : '\u672a\u9078\u64c7\u5de5\u4f5c\u5340' ,
// workspace.js
unsaved _confirm : '\u9810\u89bd\u5340\u6709\u672a\u5132\u5b58\u4fee\u6539\uff0c\u8981\u653e\u68c4\u66f4\u6539\u5e76\u7e7c\u7e8c\u8df3\u8ee2\u55ce\uff1f' ,
save : '\u5132\u5b58' ,
edit : '\u7de8\u8f2f' ,
save _title : '\u5132\u5b58\u4fee\u6539' ,
edit _title : '\u7de8\u8f2f\u6b64\u6587\u4ef6' ,
saved : '\u5df2\u5132\u5b58' ,
save _failed : '\u5132\u5b58\u5931\u6557\uff1a' ,
image _load _failed : '\u5716\u7247\u52a0\u8f09\u5931\u6557' ,
file _open _failed : '\u7121\u6cd5\u6253\u958b\u6587\u4ef6' ,
downloading : ( name ) => ` \u 6b63 \u 5728 \u 4e0b \u 8f09 ${ name } ... ` ,
double _click _rename : '\u96d9\u64ca\u91cd\u547d\u540d' ,
renamed _to : '\u5df2\u91cd\u547d\u540d\u70ba ' ,
rename _failed : '\u91cd\u547d\u540d\u5931\u6557\uff1a' ,
delete _title : '\u522a\u9664' ,
delete _confirm : ( name ) => ` \u 8981 \u 522a \u 9664 ${ name } \u 55ce \u ff1f ` ,
deleted : '\u5df2\u522a\u9664 ' ,
delete _failed : '\u522a\u9664\u5931\u6557\uff1a' ,
new _file _prompt : '\u65b0\u6587\u4ef6\u540d\uff08\u4f8b\u5982 notes.md\uff09\uff1a' ,
created : '\u5df2\u5275\u5efa ' ,
create _failed : '\u5275\u5efa\u5931\u6557\uff1a' ,
new _folder _prompt : '\u65b0\u6587\u4ef6\u593e\u540d\u7a31\uff1a' ,
folder _created : '\u5df2\u5275\u5efa\u6587\u4ef6\u593e ' ,
folder _create _failed : '\u5275\u5efa\u6587\u4ef6\u593e\u5931\u6557\uff1a' ,
remove _title : '\u79fb\u9664' ,
empty _dir : '(\u7a7a)' ,
upload _failed : '\u4e0a\u50b3\u5931\u6557\uff1a' ,
all _uploads _failed : ( n ) => ` ${ n } \u 500b \u 6587 \u 4ef6 \u 5168 \u 90e8 \u 4e0a \u 50b3 \u 5931 \u 6557 ` ,
// settings panel
settings _title : '\u8a2d\u5b9a' ,
settings _save _btn : '\u5132\u5b58\u8a2d\u5b9a' ,
settings _label _model : '\u9ed8\u8a8d\u6a21\u578b' ,
settings _label _send _key : '\u767c\u9001\u5feb\u6377\u9375' ,
settings _label _theme : '\u4e3b\u984c' ,
settings _label _language : '\u8a9d\u8a00' ,
settings _label _token _usage : '\u986f\u793a token \u7528\u91cf' ,
settings _label _cli _sessions : '\u986f\u793a CLI \u6703\u8a71' ,
settings _label _sync _insights : '\u540c\u6b65\u5230 insights' ,
settings _label _check _updates : '\u6aa2\u67e5\u66f4\u65b0' ,
settings _label _bot _name : '\u52a9\u624b\u540d\u7a31' ,
settings _label _password : '\u8a2a\u8aad\u5bc6\u78bc' ,
settings _saved : '\u8a2d\u5b9a\u5df2\u5132\u5b58' ,
settings _save _failed : '\u5132\u5b58\u5931\u6557\uff1a' ,
settings _load _failed : '\u8a2d\u5b9a\u52a0\u8f09\u5931\u6557\uff1a' ,
2026-04-14 12:51:19 +00:00
settings _saved _pw : '\u8a2d\u5b9a\u5df2\u5132\u5b58\uff0c\u5bc6\u78bc\u4fdd\u8b77\u5df2\u555f\u7528\uff0c\u7576\u524d\u700f\u89bd\u5668\u6703\u4fdd\u6301\u767b\u5165' ,
settings _saved _pw _updated : '\u8a2d\u5b9a\u5df2\u5132\u5b58\uff0c\u5bc6\u78bc\u5df2\u66f4\u65b0' ,
v0.46.0: security, Docker UID/GID, model discovery, i18n, cancel fix
* fix: decode HTML entities before markdown processing + zh/zh-Hant translations (#239)
Adds decode() helper in renderMd() to fix double-escaping of HTML entities
from LLM output (e.g. <code> becoming &lt;code&gt; instead
of rendering). XSS-safe: decode runs before esc(), only 5 entity patterns.
Also adds 40+ missing zh (Simplified Chinese) translation keys and a new
zh-Hant (Traditional Chinese) locale with 163 keys.
Fix applied: removed duplicate settings_label_notifications key in both
zh and zh-Hant locales.
Fixes #240
* fix: restore custom model list discovery with config api key (#238)
get_available_models() now reads api_key from config.yaml before env vars:
1. model.api_key
2. providers.<active>.api_key / providers.custom.api_key
3. env var fallbacks (HERMES_API_KEY, OPENAI_API_KEY, etc.)
Also adds OpenAI/Python User-Agent header and a regression test covering
authenticated /v1/models discovery.
Fixes users with LM Studio / Ollama custom endpoints configured in
config.yaml whose model picker silently collapsed to the default model.
* feat: Docker UID/GID matching to avoid root-owned .hermes files (#237)
Adds docker_init.bash with hermeswebuitoo/hermeswebui user pattern so
container files match the host user UID/GID. Prevents .hermes volume
mounts from being owned by root when using a non-root host user.
Configure via WANTED_UID and WANTED_GID env vars (default 1000/1000).
Readme updated with setup instructions.
Fix applied: removed duplicate WANTED_GID=1000 line in docker-compose.yml
that was overriding the ${GID:-1000} variable expansion.
* security: redact credentials from API responses and fix credential file permissions (#243)
Adds response-layer credential redaction to three endpoints:
- GET /api/session — messages[], tool_calls[], and title
- GET /api/session/export — download also redacted
- SSE done event — session payload in stream
- GET /api/memory — MEMORY.md and USER.md content
Adds api/startup.py with fix_credential_permissions() at server startup.
Adds 13 tests in tests/test_security_redaction.py.
Merged with #237 container detection changes in server.py.
* fix: cancel button now interrupts agent and cleans up UI state (#244)
Wires agent.interrupt() into cancel_stream() so the backend actually
stops tool execution when the user clicks Cancel, rather than only
stopping the SSE stream while the agent keeps running.
Changes:
- api/config.py: adds AGENT_INSTANCES dict (stream_id -> AIAgent)
- api/streaming.py: stores agent in AGENT_INSTANCES after creation,
checks CANCEL_FLAGS immediately after store (race condition fix),
calls agent.interrupt() in cancel_stream(), cleans up in finally block
- static/boot.js: removes stale setStatus(cancelling) call
- static/messages.js: setBusy(false)/setStatus('') unconditionally on cancel
Race condition fix: after storing agent in AGENT_INSTANCES, immediately
checks if CANCEL_FLAGS[stream_id] is already set (cancel arrived during
agent init) and interrupts before starting. Check is inside the same
STREAMS_LOCK acquisition, making it atomic.
New test file: tests/test_cancel_interrupt.py with 6 unit tests.
* docs: v0.46.0 release notes, bump version, update test counts
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 17:17:52 +00:00
// login page
login _title : '\u767b\u5f55' ,
login _subtitle : '\u8f38\u5165\u5bc6\u78bc\u7e7c\u7e8c\u4f7f\u7528' ,
login _placeholder : '\u5bc6\u78bc' ,
login _btn : '\u767b\u5f55' ,
login _invalid _pw : '\u5bc6\u78bc\u932f\u8aa4' ,
login _conn _failed : '\u9023\u63a5\u5931\u6557' ,
// missing keys from English
v0.47.0: dialogs, session menu, /skills, mobile fixes, mobile QA suite
* fix: custom provider with slash model name no longer rerouted to OpenRouter (#255)
When base_url is configured in config.yaml, resolve_model_provider() now
trusts the configured provider/base_url entirely and skips the slash-based
OpenRouter heuristic. Fixes google/gemma-4-26b-a4b with provider:custom
being silently routed to OpenRouter, resulting in 401 errors.
Fixes #230
* test: mobile layout regression suite — 14 tests for every QA run (#254)
Adds tests/test_mobile_layout.py with 14 static regression tests that run
on every QA pass to catch mobile layout breakage before it reaches prod.
Covers: breakpoints at 900px/640px, right panel slide-over CSS, mobile
overlay, bottom nav, files button, profile dropdown z-index, chip overflow,
workspace close, 100dvh, 44px touch targets, 16px font-size on textarea.
* feat: /skills slash command lists and filters available Hermes skills (#257)
Adds /skills [query] command to commands.js. Fetches from /api/skills,
groups by category (alphabetically sorted), displays as a formatted
assistant message. Optional query filters by name, description, or category.
i18n keys added for en, de, zh, zh-Hant. 1 regression test added.
Fixes #248
* feat: shared app dialogs replace native confirm()/prompt() calls (#251)
Adds showConfirmDialog() and showPromptDialog() helpers to ui.js, backed
by a themed #appDialogOverlay. Replaces all 11 native browser confirm/prompt
call sites across panels.js, sessions.js, ui.js, workspace.js.
Supports: danger mode, keyboard focus trap (Tab/Escape/Enter), focus restore,
ARIA roles, mobile-responsive stacked buttons at 640px. i18n for en/de/zh/zh-Hant.
5 new tests in test_sprint33.py verify markup, CSS, helpers, and absence of
native dialog calls.
Extracted from PR #242.
* fix: Android Chrome mobile — workspace panel close + profile dropdown (#256)
Fix #247: toggleMobileFiles() now shows/hides the mobile overlay when
toggling the right workspace panel. New closeMobileFiles() helper closes
the panel with correct overlay state tracking. Overlay onclick calls both
closeMobileSidebar() and closeMobileFiles(). Mobile-only close button (x)
added to workspace panel header.
Fix #246: profile dropdown uses position:fixed;top:56px;right:8px at
max-width:900px, escaping the overflow-x:auto stacking context that was
clipping it on Android Chrome.
Fix applied during review: closeMobileSidebar() now checks if the right
panel is still open before hiding the overlay, preventing the overlay from
disappearing when only the sidebar is closed.
Fixes #247 Fixes #246
* feat: session ⋯ action dropdown replaces per-row buttons (#252)
Replaces the 5 per-row hover action buttons (pin/move/archive/duplicate/trash)
with a single ⋯ trigger that opens a positioned dropdown menu. Menu has full
keyboard (Escape), click-outside, scroll, and resize-reposition handling.
Position:fixed prevents sidebar clipping.
5 actions: Pin/Unpin, Move to project, Archive/Unarchive, Duplicate, Delete
(danger style). Each with icon and descriptive subtitle.
Updated test_sprint16.py: test_sessions_js_uses_action_menu_not_per_row_buttons
asserts the new trigger and menu functions exist, old per-row classes are gone.
Extracted from PR #242.
* docs: v0.47.0 release notes, bump version, update test counts (645)
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 19:19:12 +00:00
dialog _confirm _title : '確認操作' ,
dialog _prompt _title : '輸入內容' ,
dialog _confirm _btn : '確認' ,
discard : '放棄' ,
clear : '清空' ,
create : '建立' ,
remove : '移除' ,
project _name _prompt : '專案名稱:' ,
v0.46.0: security, Docker UID/GID, model discovery, i18n, cancel fix
* fix: decode HTML entities before markdown processing + zh/zh-Hant translations (#239)
Adds decode() helper in renderMd() to fix double-escaping of HTML entities
from LLM output (e.g. <code> becoming &lt;code&gt; instead
of rendering). XSS-safe: decode runs before esc(), only 5 entity patterns.
Also adds 40+ missing zh (Simplified Chinese) translation keys and a new
zh-Hant (Traditional Chinese) locale with 163 keys.
Fix applied: removed duplicate settings_label_notifications key in both
zh and zh-Hant locales.
Fixes #240
* fix: restore custom model list discovery with config api key (#238)
get_available_models() now reads api_key from config.yaml before env vars:
1. model.api_key
2. providers.<active>.api_key / providers.custom.api_key
3. env var fallbacks (HERMES_API_KEY, OPENAI_API_KEY, etc.)
Also adds OpenAI/Python User-Agent header and a regression test covering
authenticated /v1/models discovery.
Fixes users with LM Studio / Ollama custom endpoints configured in
config.yaml whose model picker silently collapsed to the default model.
* feat: Docker UID/GID matching to avoid root-owned .hermes files (#237)
Adds docker_init.bash with hermeswebuitoo/hermeswebui user pattern so
container files match the host user UID/GID. Prevents .hermes volume
mounts from being owned by root when using a non-root host user.
Configure via WANTED_UID and WANTED_GID env vars (default 1000/1000).
Readme updated with setup instructions.
Fix applied: removed duplicate WANTED_GID=1000 line in docker-compose.yml
that was overriding the ${GID:-1000} variable expansion.
* security: redact credentials from API responses and fix credential file permissions (#243)
Adds response-layer credential redaction to three endpoints:
- GET /api/session — messages[], tool_calls[], and title
- GET /api/session/export — download also redacted
- SSE done event — session payload in stream
- GET /api/memory — MEMORY.md and USER.md content
Adds api/startup.py with fix_credential_permissions() at server startup.
Adds 13 tests in tests/test_security_redaction.py.
Merged with #237 container detection changes in server.py.
* fix: cancel button now interrupts agent and cleans up UI state (#244)
Wires agent.interrupt() into cancel_stream() so the backend actually
stops tool execution when the user clicks Cancel, rather than only
stopping the SSE stream while the agent keeps running.
Changes:
- api/config.py: adds AGENT_INSTANCES dict (stream_id -> AIAgent)
- api/streaming.py: stores agent in AGENT_INSTANCES after creation,
checks CANCEL_FLAGS immediately after store (race condition fix),
calls agent.interrupt() in cancel_stream(), cleans up in finally block
- static/boot.js: removes stale setStatus(cancelling) call
- static/messages.js: setBusy(false)/setStatus('') unconditionally on cancel
Race condition fix: after storing agent in AGENT_INSTANCES, immediately
checks if CANCEL_FLAGS[stream_id] is already set (cancel arrived during
agent init) and interrupts before starting. Check is inside the same
STREAMS_LOCK acquisition, making it atomic.
New test file: tests/test_cancel_interrupt.py with 6 unit tests.
* docs: v0.46.0 release notes, bump version, update test counts
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 17:17:52 +00:00
tab _chat : '\u804a\u5929' ,
tab _memory : '\u8a18\u61b6' ,
tab _skills : '\u6280\u80fd' ,
tab _tasks : '\u4efb\u52d9' ,
tab _todos : '\u5f85\u8e29' ,
tab _workspaces : '\u5de5\u4f5c\u5340' ,
new _conversation : '\u65b0\u5b58\u5c0d\u8a71' ,
filter _conversations : '\u7b5c\u9078\u5b58\u5c0d\u8a71' ,
scheduled _jobs : '\u5b58\u5287\u4efb\u52d9' ,
new _job : '\u65b0\u4efb\u52d9' ,
search _skills : '\u641c\u5c0b\u6280\u80fd' ,
new _skill : '\u65b0\u6280\u80fd' ,
save _skill : '\u5132\u5b58\u6280\u80fd' ,
personal _memory : '\u500b\u4eba\u8a18\u61b6' ,
current _task _list : '\u76ee\u524d\u4efb\u52d9\u6e05\u55ae' ,
new _profile : '\u65b0\u914d\u7f6e\u6a94' ,
transcript : '\u8a18\u9304' ,
download _transcript : '\u4e0b\u8f09\u8a18\u9304' ,
import : '\u5c0e\u5165' ,
editing : '\u7de8\u8f2f\u4e2d' ,
empty _title : '\u7a7a\u767c\u5b58\u7a7a\u9593' ,
empty _subtitle : '\u9ede\u64ca\u4e0a\u65b9\u6309\u9215\u958b\u59cb\u5c0d\u8a71' ,
cancel : '\u53d6\u6d88' ,
loading : '\u52a0\u8f09\u4e2d' ,
create _job : '\u5efa\u7acb\u4efb\u52d9' ,
suggest _plan : '\u5efa\u8b70\u8a08\u5287' ,
suggest _schedule : '\u5efa\u8b70\u6642\u7a0b' ,
suggest _files : '\u5efa\u8b70\u6a94\u6848' ,
sign _out : '\u767b\u51fa' ,
password _placeholder : '\u5bc6\u78bc' ,
disable _auth : '\u505c\u7528\u9a57\u8b49' ,
settings _label _sound : '\u901a\u77e5\u8072\u97f3' ,
settings _label _notifications : '\u700f\u89bd\u901a\u77e5' ,
settings _desc _sound : '\u52a9\u624b\u5b8c\u6210\u56de\u7b54\u6642\u64a9\u653e\u8072\u97f3\u3002' ,
settings _desc _notifications : '\u7576\u5206\u9801\u5728\u5f8c\u81ea\u6642\uff0c\u6709\u56de\u7b54\u5b8c\u6210\u6e05\u55ae\u6703\u986f\u793a\u7cfb\u7d71\u901a\u77e5\u3002' ,
settings _desc _token _usage : '\u5728\u52a9\u624b\u6bcf\u6b21\u56de\u7b54\u4e0b\u65b9\u986f\u793a Input/Output token \u6578\u91cf\u3002\u4e5f\u53ef\u4ee5\u7528 /usage \u5207\u63db\u3002' ,
settings _desc _cli _sessions : '\u5c07 Hermes CLI (\u7684 state.db) \u4e2d\u7684\u6703\u8a71\u6dfb\u52a0\u5230\u6703\u8a71\u6e05\u55ae\u3002\u9ede\u64ca\u4e00\u500b CLI \u6703\u8a71\u5c07\u5c0e\u5165\u5b83\u7a0b\u5f0f\u4e26\u7e7c\u7e8c\u5b58\u5c0d\u8a71\u3002' ,
settings _desc _sync _insights : '\u5c07 WebUI token \u4f7f\u7528\u60c5\u6cc1\u540c\u6b65\u5230 state.db\uff0c\u8a93 hermes /insights \u5305\u542b\u700f\u89bd\u5668\u6703\u8a71\u6578\u64da\u3002\u9810\u8a2d\u70b8\u555f\u7528\u3002' ,
settings _desc _check _updates : '\u7576\u6709\u66f4\u65b0\u7684 WebUI \u6216\u52a9\u624b\u7248\u672c\u6642\u986f\u793a\u6a19\u8a18\u3002\u5c07\u5728\u5f8c\u81ea\u6b63\u5e38\u57f7\u884c Git-Fetch\u3002' ,
settings _desc _bot _name : '\u52a9\u624b\u5728 UI \u4e2d\u7684\u986f\u793a\u540d\u7a31\u3002\u9810\u8a2d\u70b8\u7528\u6539\u3002' ,
settings _desc _password : '\u8a2d\u5b9a WebUI \u767b\u5165\u5bc6\u78bc\u3002\u5047\u5982\u5df2\u8a2d\u7f6e\uff0c\u6bcf\u6b21\u52a0\u8f09\u90fd\u9700\u8981\u767b\u5165\u3002' ,
2026-04-14 12:51:19 +00:00
onboarding _password _will _enable : '\u5c07\u6703\u555f\u7528' ,
onboarding _password _will _replace : '\u5c07\u6703\u53d6\u4ee3' ,
onboarding _password _keep _existing : '\u4fdd\u7559\u76ee\u524d\u5bc6\u78bc' ,
onboarding _password _remains _disabled : '\u6703\u7e7c\u7e8c\u4fdd\u6301\u95dc\u9589' ,
settings _label _sound : '\u901a\u77e5\u8072\u97f3' ,
// boot.js
cancelling : '\u6b63\u5728\u53d6\u6d88...' ,
cancel _failed : '\u53d6\u6d88\u5931\u6557\uff1a' ,
mic _denied : '\u9ea6\u514b\u98a8\u8a2a\u554f\u88ab\u62d2\u7d75\uff0c\u8acb\u6aa2\u67e5\u700f\u89bd\u5668\u6b0a\u9650\u3002' ,
mic _no _speech : '\u6c92\u6709\u6aa2\u6e2c\u5230\u8a71\u97f3\uff0c\u8acb\u518d\u5617\u4e00\u6b21\u3002' ,
mic _network : '\u8a71\u97f3\u8b58\u5225\u76ee\u524d\u4e0d\u53ef\u7528\u3002' ,
mic _error : '\u8a71\u97f3\u8f38\u5165\u51fa\u932f\uff1a' ,
session _imported : '\u6703\u8a71\u5df2\u5c0e\u5165' ,
import _failed : '\u5c0e\u5165\u5931\u6557\uff1a' ,
import _invalid _json : 'JSON \u7121\u6548' ,
image _pasted : '\u5df2\u7c98\u8cbc\u5716\u7247\uff1a' ,
// messages.js
edit _message : '\u7de8\u8f2f\u8a0a\u606f' ,
regenerate : '\u91cd\u65b0\u751f\u6210\u56de\u8986' ,
copy : '\u8907\u88fd' ,
copied : '\u5df2\u8907\u88fd' ,
v0.46.0: security, Docker UID/GID, model discovery, i18n, cancel fix
* fix: decode HTML entities before markdown processing + zh/zh-Hant translations (#239)
Adds decode() helper in renderMd() to fix double-escaping of HTML entities
from LLM output (e.g. <code> becoming &lt;code&gt; instead
of rendering). XSS-safe: decode runs before esc(), only 5 entity patterns.
Also adds 40+ missing zh (Simplified Chinese) translation keys and a new
zh-Hant (Traditional Chinese) locale with 163 keys.
Fix applied: removed duplicate settings_label_notifications key in both
zh and zh-Hant locales.
Fixes #240
* fix: restore custom model list discovery with config api key (#238)
get_available_models() now reads api_key from config.yaml before env vars:
1. model.api_key
2. providers.<active>.api_key / providers.custom.api_key
3. env var fallbacks (HERMES_API_KEY, OPENAI_API_KEY, etc.)
Also adds OpenAI/Python User-Agent header and a regression test covering
authenticated /v1/models discovery.
Fixes users with LM Studio / Ollama custom endpoints configured in
config.yaml whose model picker silently collapsed to the default model.
* feat: Docker UID/GID matching to avoid root-owned .hermes files (#237)
Adds docker_init.bash with hermeswebuitoo/hermeswebui user pattern so
container files match the host user UID/GID. Prevents .hermes volume
mounts from being owned by root when using a non-root host user.
Configure via WANTED_UID and WANTED_GID env vars (default 1000/1000).
Readme updated with setup instructions.
Fix applied: removed duplicate WANTED_GID=1000 line in docker-compose.yml
that was overriding the ${GID:-1000} variable expansion.
* security: redact credentials from API responses and fix credential file permissions (#243)
Adds response-layer credential redaction to three endpoints:
- GET /api/session — messages[], tool_calls[], and title
- GET /api/session/export — download also redacted
- SSE done event — session payload in stream
- GET /api/memory — MEMORY.md and USER.md content
Adds api/startup.py with fix_credential_permissions() at server startup.
Adds 13 tests in tests/test_security_redaction.py.
Merged with #237 container detection changes in server.py.
* fix: cancel button now interrupts agent and cleans up UI state (#244)
Wires agent.interrupt() into cancel_stream() so the backend actually
stops tool execution when the user clicks Cancel, rather than only
stopping the SSE stream while the agent keeps running.
Changes:
- api/config.py: adds AGENT_INSTANCES dict (stream_id -> AIAgent)
- api/streaming.py: stores agent in AGENT_INSTANCES after creation,
checks CANCEL_FLAGS immediately after store (race condition fix),
calls agent.interrupt() in cancel_stream(), cleans up in finally block
- static/boot.js: removes stale setStatus(cancelling) call
- static/messages.js: setBusy(false)/setStatus('') unconditionally on cancel
Race condition fix: after storing agent in AGENT_INSTANCES, immediately
checks if CANCEL_FLAGS[stream_id] is already set (cancel arrived during
agent init) and interrupts before starting. Check is inside the same
STREAMS_LOCK acquisition, making it atomic.
New test file: tests/test_cancel_interrupt.py with 6 unit tests.
* docs: v0.46.0 release notes, bump version, update test counts
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 17:17:52 +00:00
// ui.js
workspace _desc : '\u8acb\u9078\u64c7\u5de5\u4f5c\u5340\uff0c\u6216\u8f09\u5165\u65b0\u540d\u7a31\u5beb\u4e00\u500b' ,
tab _profiles : '\u914d\u7f6e' ,
2026-04-08 14:55:03 +00:00
} ,
} ;
// Active locale — defaults to English; overridden by loadLocale() at boot.
let _locale = LOCALES . en ;
2026-04-14 17:14:01 +00:00
/ * *
* Resolve an incoming locale tag to a known LOCALES key .
* Supports exact keys , case - insensitive matches , and a few common aliases
* ( e . g . zh - CN - > zh , zh - TW - > zh - Hant ) . Returns null when unresolved .
* @ param { string } lang
* @ returns { string | null }
* /
function resolveLocale ( lang ) {
if ( typeof lang !== 'string' ) return null ;
const raw = lang . trim ( ) ;
if ( ! raw ) return null ;
if ( LOCALES [ raw ] ) return raw ;
const lower = raw . toLowerCase ( ) . replace ( /_/g , '-' ) ;
// Case-insensitive direct match first.
const direct = Object . keys ( LOCALES ) . find ( ( k ) => k . toLowerCase ( ) === lower ) ;
if ( direct ) return direct ;
// Common Chinese variants.
if ( lower === 'zh' || lower . startsWith ( 'zh-cn' ) || lower . startsWith ( 'zh-sg' ) || lower . startsWith ( 'zh-hans' ) ) {
return LOCALES . zh ? 'zh' : null ;
}
if ( lower . startsWith ( 'zh-tw' ) || lower . startsWith ( 'zh-hk' ) || lower . startsWith ( 'zh-mo' ) || lower . startsWith ( 'zh-hant' ) ) {
return LOCALES [ 'zh-Hant' ] ? 'zh-Hant' : null ;
}
// Fallback to base language subtag (e.g. en-US -> en).
const base = lower . split ( '-' ) [ 0 ] ;
const baseMatch = Object . keys ( LOCALES ) . find ( ( k ) => k . toLowerCase ( ) === base ) ;
return baseMatch || null ;
}
/ * *
* Resolve locale with precedence :
* 1 ) primary ( typically server setting )
* 2 ) fallback ( typically localStorage )
* 3 ) English
* @ param { string } primary
* @ param { string } fallback
* @ returns { string }
* /
function resolvePreferredLocale ( primary , fallback ) {
return resolveLocale ( primary ) || resolveLocale ( fallback ) || 'en' ;
}
2026-04-08 14:55:03 +00:00
/ * *
* Translate a key . Falls back to English if the key is missing in the active locale .
* Supports function values ( for interpolated strings ) : call t ( 'key' , arg ) .
* @ param { string } key
* @ param { ... * } args - forwarded to function - valued translations
* @ returns { string }
* /
function t ( key , ... args ) {
const val = _locale [ key ] ? ? LOCALES . en [ key ] ;
if ( val === undefined ) return key ; // final fallback: return key itself
return typeof val === 'function' ? val ( ... args ) : val ;
}
/ * *
* Switch locale by language code ( e . g . 'en' , 'zh' ) .
* Persists to localStorage and updates the < html lang > attribute .
* @ param { string } lang
* /
function setLocale ( lang ) {
2026-04-14 17:14:01 +00:00
const resolved = resolveLocale ( lang ) || 'en' ;
2026-04-09 02:35:03 +00:00
_locale = LOCALES [ resolved ] ;
localStorage . setItem ( 'hermes-lang' , resolved ) ;
document . documentElement . lang = _locale . _speech || resolved ;
2026-04-08 14:55:03 +00:00
}
/ * *
* Load locale from localStorage ( called once at boot , before DOMContentLoaded ) .
* Server - persisted preference is applied later in loadSettingsPanel ( ) .
* /
function loadLocale ( ) {
2026-04-14 17:14:01 +00:00
setLocale ( resolvePreferredLocale ( null , localStorage . getItem ( 'hermes-lang' ) ) ) ;
2026-04-08 14:55:03 +00:00
}
2026-04-08 15:04:42 +00:00
/ * *
* Re - stamp all [ data - i18n ] elements in the DOM with the current locale .
* Safe to call at any time — missing keys fall back to English .
* Call after setLocale ( ) to make static HTML text update without a reload .
* /
function applyLocaleToDOM ( ) {
document . querySelectorAll ( '[data-i18n]' ) . forEach ( el => {
const key = el . getAttribute ( 'data-i18n' ) ;
const val = t ( key ) ;
if ( val && val !== key ) el . textContent = val ;
} ) ;
2026-04-10 01:35:23 +00:00
document . querySelectorAll ( '[data-i18n-title]' ) . forEach ( el => {
const key = el . getAttribute ( 'data-i18n-title' ) ;
const val = t ( key ) ;
if ( val && val !== key ) el . title = val ;
} ) ;
document . querySelectorAll ( '[data-i18n-placeholder]' ) . forEach ( el => {
const key = el . getAttribute ( 'data-i18n-placeholder' ) ;
const val = t ( key ) ;
if ( val && val !== key ) el . placeholder = val ;
} ) ;
2026-04-08 15:04:42 +00:00
}
2026-04-08 14:55:03 +00:00
// Apply saved locale immediately so there's no flash of English on reload.
loadLocale ( ) ;