* feat(ui): opt-in chat bubble layout Closes #336. Adds a settings toggle that right-aligns user messages and left-aligns assistant replies. Off by default - the current full-width layout is friendlier to code blocks and tool output, so bubbles are strictly opt-in per the maintainer note on the issue. Wiring follows the existing token-usage / cli-sessions pattern: - api/config.py: new bubble_layout bool in _SETTINGS_DEFAULTS and _SETTINGS_BOOL_KEYS, validated + persisted like the rest. - static/style.css: .bubble-layout gated selectors using :has() to tag msg-rows by .msg-role.user / .msg-role.assistant without any JS changes to message creation. User rows get align-self: flex-end, max-width: 75%, and a row-reverse header; assistant rows flex-start. A 700px media query widens the max to 92% on narrow screens. - static/index.html: new checkbox with i18n keys next to the existing token-usage toggle. - static/panels.js: loads the setting into the checkbox, saves it back, and toggles body.bubble-layout immediately on save. - static/boot.js: applies the class on initial load so refreshed tabs honor the persisted setting without a flash. - static/i18n.js: English label + description. Test suite errors are environmental (test server fails to start on port 8788 on main as well). * i18n(es): add Spanish translations for bubble_layout setting * fix+test: boot.js bubble-layout reset on failure; add 22 tests for issue #336 * docs: v0.50.24 release — version badge and CHANGELOG --------- Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
This commit is contained in:
@@ -1225,6 +1225,8 @@ async function loadSettingsPanel(){
|
||||
if(soundCb){soundCb.checked=!!settings.sound_enabled;soundCb.addEventListener('change',_markSettingsDirty,{once:false});}
|
||||
const notifCb=$('settingsNotificationsEnabled');
|
||||
if(notifCb){notifCb.checked=!!settings.notifications_enabled;notifCb.addEventListener('change',_markSettingsDirty,{once:false});}
|
||||
const bubbleCb=$('settingsBubbleLayout');
|
||||
if(bubbleCb){bubbleCb.checked=!!settings.bubble_layout;bubbleCb.addEventListener('change',_markSettingsDirty,{once:false});}
|
||||
// Bot name
|
||||
const botNameField=$('settingsBotName');
|
||||
if(botNameField){botNameField.value=settings.bot_name||'Hermes';botNameField.addEventListener('input',_markSettingsDirty,{once:false});}
|
||||
@@ -1267,6 +1269,8 @@ async function saveSettings(andClose){
|
||||
body.check_for_updates=!!($('settingsCheckUpdates')||{}).checked;
|
||||
body.sound_enabled=!!($('settingsSoundEnabled')||{}).checked;
|
||||
body.notifications_enabled=!!($('settingsNotificationsEnabled')||{}).checked;
|
||||
body.bubble_layout=!!($('settingsBubbleLayout')||{}).checked;
|
||||
document.body.classList.toggle('bubble-layout', body.bubble_layout);
|
||||
const botName=(($('settingsBotName')||{}).value||'').trim();
|
||||
body.bot_name=botName||'Hermes';
|
||||
// Password: only act if the field has content; blank = leave auth unchanged
|
||||
|
||||
Reference in New Issue
Block a user