diff --git a/CHANGELOG.md b/CHANGELOG.md index 03c1756..876b76b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Hermes Web UI -- Changelog +## [v0.50.77] — 2026-04-17 + +### Changed +- **Color scheme system replaced with theme + skin axes** — the old monolithic theme list (`dark`, `slate`, `solarized`, `monokai`, `nord`, `oled`, `light`) is split into two orthogonal axes: **theme** (`light` / `dark` / `system`) and **skin** (accent palette: Default gold, Ares red, Mono gray, Slate blue-gray, Poseidon ocean blue, Sisyphus purple, Charizard orange). Users can now mix any theme with any skin via the new **Appearance** settings tab. Internally, `.dark` class on `` replaces `data-theme`; skin uses `data-skin` attribute and overrides only 5 accent CSS vars per skin, eliminating ~200 lines of duplicated palette overrides. (PR #627 by @aronprins) + +### Migration notes +- **Legacy theme names are silently migrated on first load** to the closest theme + skin pair: `slate → dark+slate`, `solarized → dark+poseidon`, `monokai → dark+sisyphus`, `nord → dark+slate`, `oled → dark+default`. Both backend (`api/config.py::_normalize_appearance`) and frontend (`static/boot.js::_normalizeAppearance`) apply the same mapping. +- **Custom themes set via `data-theme` CSS overrides will reset** to `dark + default` on first load. The pre-PR `theme` setting was open-ended ("no enum gate -- allows custom themes"); the new system enumerates valid values. Users who maintained custom CSS will need to re-apply via a skin choice or by overriding skin variables (`--accent`, `--accent-hover`, `--accent-bg`, `--accent-bg-strong`, `--accent-text`). + +### Fixed +- **Send button stays active after clearing composer text** — input listener now correctly toggles disabled state. (PR #627) +- **Composer workspace/model label flash on page load** — chips now wait for `_bootReady` before populating, eliminating the placeholder-then-real-value flicker. (PR #627) +- **Topbar border invisible in light mode** — added `:root:not(.dark)` border override. (PR #627) +- **User message bubble text contrast** — accent-colored bubbles now use skin-aware text colors meeting WCAG AA (Poseidon dark improved from 2.8 → 6.5 ratio). (PR #627) +- **Settings skin persistence race condition** — save now waits for server confirmation before applying. (PR #627) + ## [v0.50.76] — 2026-04-17 ### Fixed diff --git a/api/config.py b/api/config.py index 04f6c2d..abdf255 100644 --- a/api/config.py +++ b/api/config.py @@ -1216,7 +1216,8 @@ _SETTINGS_DEFAULTS = { "show_cli_sessions": False, # merge CLI sessions from state.db into the sidebar "sync_to_insights": False, # mirror WebUI token usage to state.db for /insights "check_for_updates": True, # check if webui/agent repos are behind upstream - "theme": "dark", # active UI theme name (no enum gate -- allows custom themes) + "theme": "dark", # light | dark | system + "skin": "default", # accent color skin: default | ares | mono | slate | poseidon | sisyphus | charizard "language": "en", # UI locale code; must match a key in static/i18n.js LOCALES "bot_name": os.getenv( "HERMES_WEBUI_BOT_NAME", "Hermes" @@ -1227,11 +1228,68 @@ _SETTINGS_DEFAULTS = { "password_hash": None, # PBKDF2-HMAC-SHA256 hash; None = auth disabled } _SETTINGS_LEGACY_DROP_KEYS = {"assistant_language"} +_SETTINGS_THEME_VALUES = {"light", "dark", "system"} +_SETTINGS_SKIN_VALUES = { + "default", + "ares", + "mono", + "slate", + "poseidon", + "sisyphus", + "charizard", +} +_SETTINGS_LEGACY_THEME_MAP = { + # Legacy full themes now map onto the closest supported theme + accent skin pair. + "slate": ("dark", "slate"), + "solarized": ("dark", "poseidon"), + "monokai": ("dark", "sisyphus"), + "nord": ("dark", "slate"), + "oled": ("dark", "default"), +} + + +def _normalize_appearance(theme, skin) -> tuple[str, str]: + """Normalize a (theme, skin) pair, migrating legacy theme names. + + Legacy migration table (from `_SETTINGS_LEGACY_THEME_MAP`): + + slate → ("dark", "slate") + solarized → ("dark", "poseidon") + monokai → ("dark", "sisyphus") + nord → ("dark", "slate") + oled → ("dark", "default") + + Unknown / custom theme names fall back to ("dark", "default"). This is a + behavior change vs. the pre-PR-#627 state, where the `theme` field was + open-ended ("no enum gate -- allows custom themes"). Users who set a + custom CSS theme via `data-theme` will need to re-apply via skin or + custom CSS — see CHANGELOG entry for details. + + The same mapping is mirrored in `static/boot.js` (`_LEGACY_THEME_MAP`) + so client and server normalize identically; keep them in sync. + """ + raw_theme = theme.strip().lower() if isinstance(theme, str) else "" + raw_skin = skin.strip().lower() if isinstance(skin, str) else "" + legacy = _SETTINGS_LEGACY_THEME_MAP.get(raw_theme) + if legacy: + next_theme, legacy_skin = legacy + elif raw_theme in _SETTINGS_THEME_VALUES: + next_theme, legacy_skin = raw_theme, "default" + else: + # Unknown themes used to exist; default to dark so upgrades stay visually stable. + next_theme, legacy_skin = "dark", "default" + next_skin = ( + raw_skin + if raw_skin in _SETTINGS_SKIN_VALUES + else legacy_skin + ) + return next_theme, next_skin def load_settings() -> dict: """Load settings from disk, merging with defaults for any missing keys.""" settings = dict(_SETTINGS_DEFAULTS) + stored = None try: settings_exists = SETTINGS_FILE.exists() except OSError: @@ -1252,6 +1310,10 @@ def load_settings() -> dict: ) except Exception: logger.debug("Failed to load settings from %s", SETTINGS_FILE) + settings["theme"], settings["skin"] = _normalize_appearance( + stored.get("theme") if isinstance(stored, dict) else settings.get("theme"), + stored.get("skin") if isinstance(stored, dict) else settings.get("skin"), + ) return settings @@ -1276,6 +1338,10 @@ _SETTINGS_LANG_RE = __import__("re").compile(r"^[a-zA-Z]{2,10}(-[a-zA-Z0-9]{2,8} def save_settings(settings: dict) -> dict: """Save settings to disk. Returns the merged settings. Ignores unknown keys.""" current = load_settings() + pending_theme = current.get("theme") + pending_skin = current.get("skin") + theme_was_explicit = False + skin_was_explicit = False # Handle _set_password: hash and store as password_hash raw_pw = settings.pop("_set_password", None) if raw_pw and isinstance(raw_pw, str) and raw_pw.strip(): @@ -1288,6 +1354,16 @@ def save_settings(settings: dict) -> dict: current["password_hash"] = None for k, v in settings.items(): if k in _SETTINGS_ALLOWED_KEYS: + if k == "theme": + if isinstance(v, str) and v.strip(): + pending_theme = v + theme_was_explicit = True + continue + if k == "skin": + if isinstance(v, str) and v.strip(): + pending_skin = v + skin_was_explicit = True + continue # Validate enum-constrained keys if k in _SETTINGS_ENUM_VALUES and v not in _SETTINGS_ENUM_VALUES[k]: continue @@ -1300,6 +1376,13 @@ def save_settings(settings: dict) -> dict: if k in _SETTINGS_BOOL_KEYS: v = bool(v) current[k] = v + theme_value = pending_theme + skin_value = pending_skin + if theme_was_explicit and not skin_was_explicit: + raw_theme = pending_theme.strip().lower() if isinstance(pending_theme, str) else "" + if raw_theme not in _SETTINGS_THEME_VALUES: + skin_value = None + current["theme"], current["skin"] = _normalize_appearance(theme_value, skin_value) current["default_workspace"] = str( resolve_default_workspace(current.get("default_workspace")) diff --git a/static/boot.js b/static/boot.js index 4c91da4..00aaedd 100644 --- a/static/boot.js +++ b/static/boot.js @@ -579,29 +579,134 @@ window.addEventListener('resize',()=>{ }; })(); -// ── System theme helper ────────────────────────────────────────────────────── +// ── Appearance helpers (theme = light/dark/system, skin = accent color) ────── +const _SKINS=[ + {name:'Default', colors:['#FFD700','#FFBF00','#CD7F32']}, + {name:'Ares', colors:['#FF4444','#CC3333','#992222']}, + {name:'Mono', colors:['#CCCCCC','#999999','#666666']}, + {name:'Slate', colors:['#334155','#475569','#64748b']}, + {name:'Poseidon', colors:['#0EA5E9','#0284C7','#0369A1']}, + {name:'Sisyphus', colors:['#A78BFA','#8B5CF6','#7C3AED']}, + {name:'Charizard',colors:['#FB923C','#F97316','#EA580C']}, +]; +const _VALID_THEMES=new Set(['system','dark','light']); +const _VALID_SKINS=new Set((_SKINS||[]).map(s=>s.name.toLowerCase())); +const _LEGACY_THEME_MAP={ + slate:{theme:'dark',skin:'slate'}, + solarized:{theme:'dark',skin:'poseidon'}, + monokai:{theme:'dark',skin:'sisyphus'}, + nord:{theme:'dark',skin:'slate'}, + oled:{theme:'dark',skin:'default'}, +}; +let _systemThemeMq=null; +let _onSystemThemeChange=null; + +function _normalizeAppearance(theme,skin){ + const rawTheme=typeof theme==='string'?theme.trim().toLowerCase():''; + const rawSkin=typeof skin==='string'?skin.trim().toLowerCase():''; + const legacy=_LEGACY_THEME_MAP[rawTheme]; + const nextTheme=legacy?legacy.theme:(_VALID_THEMES.has(rawTheme)?rawTheme:'dark'); + const nextSkin=_VALID_SKINS.has(rawSkin)?rawSkin:(legacy?legacy.skin:'default'); + return {theme:nextTheme,skin:nextSkin}; +} + +function _setResolvedTheme(isDark){ + document.documentElement.classList.toggle('dark',!!isDark); + const link=document.getElementById('prism-theme'); + if(!link) return; + const want=isDark + ?'https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css' + :'https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css'; + if(link.href!==want){ link.href=want; } +} + function _applyTheme(name){ - const resolved=(name==='system') - ?(window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light') - :name; - document.documentElement.dataset.theme=resolved||'dark'; - // Swap Prism syntax-highlighting theme to match UI theme - (function(){ - const link=document.getElementById('prism-theme'); - if(!link) return; - const isDark=(resolved!=='light'); - const want=isDark - ?'https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css' - :'https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css'; - if(link.href!==want){ link.href=want; } - })(); - // Re-register OS change listener whenever system theme is active - if(name==='system'){ - const mq=window.matchMedia('(prefers-color-scheme:dark)'); - const _onOsChange=()=>{ document.documentElement.dataset.theme=mq.matches?'dark':'light'; }; - mq.removeEventListener('change',_onOsChange); - mq.addEventListener('change',_onOsChange); + const normalized=_normalizeAppearance(name,'default'); + if(_systemThemeMq&&_onSystemThemeChange){ + _systemThemeMq.removeEventListener('change',_onSystemThemeChange); + _systemThemeMq=null; + _onSystemThemeChange=null; } + if(normalized.theme==='system'){ + _systemThemeMq=window.matchMedia('(prefers-color-scheme:dark)'); + _onSystemThemeChange=()=>_setResolvedTheme(_systemThemeMq.matches); + _setResolvedTheme(_systemThemeMq.matches); + _systemThemeMq.addEventListener('change',_onSystemThemeChange); + return; + } + _setResolvedTheme(normalized.theme==='dark'); +} + +function _applySkin(name){ + const key=(name||'default').toLowerCase(); + if(key==='default') delete document.documentElement.dataset.skin; + else document.documentElement.dataset.skin=key; +} + +function _pickTheme(name){ + const currentSkin=localStorage.getItem('hermes-skin'); + const appearance=_normalizeAppearance(name,currentSkin); + localStorage.setItem('hermes-theme',appearance.theme); + localStorage.setItem('hermes-skin',appearance.skin); + _applyTheme(appearance.theme); + _applySkin(appearance.skin); + _syncThemePicker(appearance.theme); + _syncSkinPicker(appearance.skin); + if(typeof _markSettingsDirty==='function') _markSettingsDirty(); + const hidden=$('settingsTheme'); + if(hidden) hidden.value=appearance.theme; + const skinHidden=$('settingsSkin'); + if(skinHidden) skinHidden.value=appearance.skin; +} + +function _pickSkin(name){ + const appearance=_normalizeAppearance(localStorage.getItem('hermes-theme'),name); + localStorage.setItem('hermes-theme',appearance.theme); + localStorage.setItem('hermes-skin',appearance.skin); + _applyTheme(appearance.theme); + _applySkin(appearance.skin); + _syncThemePicker(appearance.theme); + _syncSkinPicker(appearance.skin); + if(typeof _markSettingsDirty==='function') _markSettingsDirty(); + const hidden=$('settingsSkin'); + if(hidden) hidden.value=appearance.skin; + const themeHidden=$('settingsTheme'); + if(themeHidden) themeHidden.value=appearance.theme; +} + +function _syncThemePicker(active){ + document.querySelectorAll('#themePickerGrid .theme-pick-btn').forEach(btn=>{ + const sel=btn.dataset.themeVal===active; + btn.style.borderColor=sel?'var(--accent)':'var(--border2)'; + btn.style.boxShadow=sel?'0 0 0 1px var(--accent-bg-strong)':'none'; + }); +} + +function _syncSkinPicker(active){ + document.querySelectorAll('#skinPickerGrid .skin-pick-btn').forEach(btn=>{ + const sel=btn.dataset.skinVal===active; + btn.style.borderColor=sel?'var(--accent)':'var(--border2)'; + btn.style.boxShadow=sel?'0 0 0 1px var(--accent-bg-strong)':'none'; + }); +} + +function _buildSkinPicker(activeSkin){ + const grid=$('skinPickerGrid'); + if(!grid) return; + grid.innerHTML=''; + for(const skin of _SKINS){ + const key=skin.name.toLowerCase(); + const btn=document.createElement('button'); + btn.type='button'; + btn.className='skin-pick-btn'; + btn.dataset.skinVal=key; + btn.style.cssText='border:1px solid var(--border2);border-radius:8px;padding:8px 4px;text-align:center;cursor:pointer;background:none;transition:all .15s'; + btn.onclick=()=>_pickSkin(skin.name); + const dots=skin.colors.map(c=>``).join(''); + btn.innerHTML=`
${dots}
${skin.name}`; + grid.appendChild(btn); + } + _syncSkinPicker((activeSkin||'default').toLowerCase()); } function applyBotName(){ @@ -629,9 +734,11 @@ function applyBotName(){ window._soundEnabled=!!s.sound_enabled; window._notificationsEnabled=!!s.notifications_enabled; window._botName=s.bot_name||'Hermes'; - const _theme=s.theme||'dark'; - localStorage.setItem('hermes-theme',_theme); - _applyTheme(_theme); + const appearance=_normalizeAppearance(s.theme,s.skin); + localStorage.setItem('hermes-theme',appearance.theme); + _applyTheme(appearance.theme); + localStorage.setItem('hermes-skin',appearance.skin); + _applySkin(appearance.skin); document.body.classList.toggle('bubble-layout',!!s.bubble_layout); if(typeof setLocale==='function'){ const _lang=typeof resolvePreferredLocale==='function' @@ -695,10 +802,12 @@ function applyBotName(){ if(S.session&&S.session.workspace&&localStorage.getItem('hermes-webui-workspace-panel')==='open'){ _workspacePanelMode='browse'; } - syncWorkspacePanelState();await renderSessionList();if(typeof startGatewaySSE==='function')startGatewaySSE();await checkInflightOnBoot(saved);return;} + S._bootReady=true; + syncTopbar();syncWorkspacePanelState();await renderSessionList();if(typeof startGatewaySSE==='function')startGatewaySSE();await checkInflightOnBoot(saved);return;} catch(e){localStorage.removeItem('hermes-webui-session');} } // no saved session - show empty state, wait for user to hit + + S._bootReady=true; syncTopbar(); syncWorkspacePanelState(); $('emptyState').style.display=''; diff --git a/static/commands.js b/static/commands.js index d6c38ac..0a59832 100644 --- a/static/commands.js +++ b/static/commands.js @@ -121,19 +121,48 @@ async function cmdUsage(){ } async function cmdTheme(args){ - const themes=['system','dark','light','slate','solarized','monokai','nord','oled']; - if(!args||!themes.includes(args.toLowerCase())){ - showToast(t('theme_usage')+themes.join('|')); + const themes=['system','dark','light']; + const skins=(_SKINS||[]).map(s=>s.name.toLowerCase()); + const legacyThemes=Object.keys(_LEGACY_THEME_MAP||{}); + const val=(args||'').toLowerCase().trim(); + // Check if it's a theme + if(themes.includes(val)||legacyThemes.includes(val)){ + const appearance=_normalizeAppearance( + val, + legacyThemes.includes(val)?null:localStorage.getItem('hermes-skin') + ); + localStorage.setItem('hermes-theme',appearance.theme); + localStorage.setItem('hermes-skin',appearance.skin); + _applyTheme(appearance.theme); + _applySkin(appearance.skin); + try{await api('/api/settings',{method:'POST',body:JSON.stringify({theme:appearance.theme,skin:appearance.skin})});}catch(e){} + const sel=$('settingsTheme'); + if(sel)sel.value=appearance.theme; + const skinSel=$('settingsSkin'); + if(skinSel)skinSel.value=appearance.skin; + if(typeof _syncThemePicker==='function') _syncThemePicker(appearance.theme); + if(typeof _syncSkinPicker==='function') _syncSkinPicker(appearance.skin); + showToast(t('theme_set')+appearance.theme+(legacyThemes.includes(val)?` + ${appearance.skin}`:'')); return; } - const themeName=args.toLowerCase(); - localStorage.setItem('hermes-theme',themeName); - _applyTheme(themeName); - try{await api('/api/settings',{method:'POST',body:JSON.stringify({theme:themeName})});}catch(e){} - // Update settings dropdown if panel is open - const sel=$('settingsTheme'); - if(sel)sel.value=themeName; - showToast(t('theme_set')+themeName); + // Check if it's a skin + if(skins.includes(val)){ + const appearance=_normalizeAppearance(localStorage.getItem('hermes-theme'),val); + localStorage.setItem('hermes-theme',appearance.theme); + localStorage.setItem('hermes-skin',appearance.skin); + _applyTheme(appearance.theme); + _applySkin(appearance.skin); + try{await api('/api/settings',{method:'POST',body:JSON.stringify({theme:appearance.theme,skin:appearance.skin})});}catch(e){} + const sel=$('settingsSkin'); + if(sel)sel.value=appearance.skin; + const themeSel=$('settingsTheme'); + if(themeSel)themeSel.value=appearance.theme; + if(typeof _syncThemePicker==='function') _syncThemePicker(appearance.theme); + if(typeof _syncSkinPicker==='function') _syncSkinPicker(appearance.skin); + showToast(t('theme_set')+appearance.skin); + return; + } + showToast(t('theme_usage')+themes.join('|')+' | '+skins.join('|')+' | legacy:'+legacyThemes.join('|')); } async function cmdSkills(args){ diff --git a/static/i18n.js b/static/i18n.js index 5172981..c530090 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -66,7 +66,7 @@ const LOCALES = { 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 (system/dark/light/slate/solarized/monokai/nord/oled)', + cmd_theme: 'Switch appearance (theme: system/dark/light, skin: default/ares/mono/slate/poseidon/sisyphus/charizard)', cmd_personality: 'Switch agent personality', cmd_skills: 'List available Hermes skills', available_commands: 'Available commands:', @@ -137,6 +137,7 @@ const LOCALES = { settings_label_model: 'Default Model', settings_label_send_key: 'Send Key', settings_label_theme: 'Theme', + settings_label_skin: 'Skin', settings_label_language: 'Language', settings_label_token_usage: 'Show token usage', settings_label_bubble_layout: 'Chat bubble layout', @@ -480,7 +481,7 @@ const LOCALES = { 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 (system/dark/light/slate/solarized/monokai/nord/oled)', + cmd_theme: 'Cambiar apariencia (tema: system/dark/light, skin: default/ares/mono/slate/poseidon/sisyphus/charizard)', cmd_personality: 'Cambiar la personalidad del agente', cmd_skills: 'Listar las skills de Hermes disponibles', available_commands: 'Comandos disponibles:', @@ -543,6 +544,7 @@ const LOCALES = { settings_label_model: 'Modelo predeterminado', settings_label_send_key: 'Tecla de envío', settings_label_theme: 'Tema', + settings_label_skin: 'Piel', settings_label_language: 'Idioma', settings_label_token_usage: 'Mostrar uso de tokens', settings_label_bubble_layout: 'Disposición en burbujas', @@ -884,7 +886,7 @@ const LOCALES = { cmd_workspace: 'Workspace nach Namen wechseln', cmd_new: 'Neue Chat-Sitzung starten', cmd_usage: 'Token-Verbrauchsanzeige umschalten', - cmd_theme: 'Theme wechseln (system/dark/light/slate/solarized/monokai/nord/oled)', + cmd_theme: 'Darstellung wechseln (Theme: system/dark/light, Skin: default/ares/mono/slate/poseidon/sisyphus/charizard)', cmd_personality: 'Agenten-Persönlichkeit wechseln', cmd_skills: 'Verfügbare Hermes-Skills auflisten', available_commands: 'Verfügbare Befehle:', @@ -955,6 +957,7 @@ const LOCALES = { settings_label_model: 'Standard-Modell', settings_label_send_key: 'Sende-Taste', settings_label_theme: 'Theme', + settings_label_skin: 'Skin', settings_label_language: 'Sprache', settings_label_token_usage: 'Token-Verbrauch anzeigen', settings_label_cli_sessions: 'Agent-Sitzungen anzeigen', @@ -1096,7 +1099,7 @@ const LOCALES = { 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\uff08system/dark/light/slate/solarized/monokai/nord/oled\uff09', + cmd_theme: '\u5207\u6362\u5916\u89c2\uff08\u4e3b\u9898\uff1asystem/dark/light\uff0c\u76ae\u80a4\uff1adefault/ares/mono/slate/poseidon/sisyphus/charizard\uff09', cmd_personality: '\u5207\u6362 Agent \u4eba\u8bbe', cmd_skills: '\u5217\u51fa\u53ef\u7528\u7684 Hermes \u6280\u80fd', available_commands: '\u53ef\u7528\u547d\u4ee4\uff1a', @@ -1167,6 +1170,7 @@ const LOCALES = { settings_label_model: '\u9ed8\u8ba4\u6a21\u578b', settings_label_send_key: '\u53d1\u9001\u5feb\u6377\u952e', settings_label_theme: '\u4e3b\u9898', + settings_label_skin: '\u76ae\u80a4', settings_label_language: '\u8bed\u8a00', settings_label_token_usage: '\u663e\u793a token \u7528\u91cf', settings_label_bubble_layout: '聊天气泡布局', @@ -1499,7 +1503,7 @@ const LOCALES = { 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\uff08system/dark/light/slate/solarized/monokai/nord/oled\uff09', + cmd_theme: '\u5207\u63db\u5916\u89c0\uff08\u4e3b\u984c\uff1asystem/dark/light\uff0c\u76ae\u819a\uff1adefault/ares/mono/slate/poseidon/sisyphus/charizard\uff09', cmd_personality: '\u5207\u63db Agent \u4eba\u8a2d', cmd_skills: '\u5217\u51fa\u53ef\u7528\u7684 Hermes \u6280\u80fd', available_commands: '\u53ef\u7528\u547d\u4ee4\uff1a', @@ -1562,6 +1566,7 @@ const LOCALES = { settings_label_model: '\u9ed8\u8a8d\u6a21\u578b', settings_label_send_key: '\u767c\u9001\u5feb\u6377\u9375', settings_label_theme: '\u4e3b\u984c', + settings_label_skin: '\u76ae\u819a', settings_label_language: '\u8a9d\u8a00', settings_label_token_usage: '\u986f\u793a token \u7528\u91cf', settings_label_cli_sessions: '\u986f\u793a CLI \u6703\u8a71', diff --git a/static/index.html b/static/index.html index 21bc73c..d427e53 100644 --- a/static/index.html +++ b/static/index.html @@ -9,7 +9,7 @@ - + @@ -305,14 +305,14 @@
+
+
+
+
Appearance
+ +
+
+
+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
@@ -482,19 +525,6 @@
-
- - -
@@ -561,7 +591,7 @@
System
- v0.50.76 + v0.50.77
diff --git a/static/panels.js b/static/panels.js index 849983f..97abd7a 100644 --- a/static/panels.js +++ b/static/panels.js @@ -50,7 +50,7 @@ async function loadCrons() { ? `` : ``} - +