feat: make bot name configurable
This commit is contained in:
@@ -26,3 +26,6 @@
|
|||||||
|
|
||||||
# Path to your Hermes config.yaml (for toolsets and model config)
|
# Path to your Hermes config.yaml (for toolsets and model config)
|
||||||
# HERMES_CONFIG_PATH=~/.hermes/config.yaml
|
# HERMES_CONFIG_PATH=~/.hermes/config.yaml
|
||||||
|
|
||||||
|
# Display name for the assistant in the UI (default: Hermes)
|
||||||
|
# HERMES_WEBUI_BOT_NAME=Hermes
|
||||||
|
|||||||
@@ -680,6 +680,7 @@ _SETTINGS_DEFAULTS = {
|
|||||||
'sync_to_insights': False, # mirror WebUI token usage to state.db for /insights
|
'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
|
'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', # active UI theme name (no enum gate -- allows custom themes)
|
||||||
|
'bot_name': os.getenv('HERMES_WEBUI_BOT_NAME', 'Hermes'), # display name for the assistant
|
||||||
'password_hash': None, # SHA-256 hash; None = auth disabled
|
'password_hash': None, # SHA-256 hash; None = auth disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,13 @@ def handle_get(handler, parsed) -> bool:
|
|||||||
content_type='text/html; charset=utf-8')
|
content_type='text/html; charset=utf-8')
|
||||||
|
|
||||||
if parsed.path == '/login':
|
if parsed.path == '/login':
|
||||||
return t(handler, _LOGIN_PAGE_HTML, content_type='text/html; charset=utf-8')
|
import html as _html
|
||||||
|
_bot = _html.escape(load_settings().get('bot_name', 'Hermes'))
|
||||||
|
_page = _LOGIN_PAGE_HTML.replace(
|
||||||
|
'<title>Hermes — Sign in</title>',
|
||||||
|
f'<title>{_bot} — Sign in</title>',
|
||||||
|
).replace('<h1>Hermes</h1>', f'<h1>{_bot}</h1>')
|
||||||
|
return t(handler, _page, content_type='text/html; charset=utf-8')
|
||||||
|
|
||||||
if parsed.path == '/api/auth/status':
|
if parsed.path == '/api/auth/status':
|
||||||
from api.auth import is_auth_enabled, parse_cookie, verify_session
|
from api.auth import is_auth_enabled, parse_cookie, verify_session
|
||||||
|
|||||||
@@ -306,10 +306,23 @@ document.querySelectorAll('.suggestion').forEach(btn=>{
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
function applyBotName(){
|
||||||
|
const name=window._botName||'Hermes';
|
||||||
|
document.title=name;
|
||||||
|
const sidebarH1=document.querySelector('.sidebar-header h1');
|
||||||
|
if(sidebarH1) sidebarH1.textContent=name;
|
||||||
|
const logo=document.querySelector('.sidebar-header .logo');
|
||||||
|
if(logo) logo.textContent=name.charAt(0).toUpperCase();
|
||||||
|
const topbarTitle=$('topbarTitle');
|
||||||
|
if(topbarTitle && (!S.session)) topbarTitle.textContent=name;
|
||||||
|
const msg=$('msg');
|
||||||
|
if(msg) msg.placeholder='Message '+name+'\u2026';
|
||||||
|
}
|
||||||
|
|
||||||
(async()=>{
|
(async()=>{
|
||||||
// Load send key preference
|
// Load send key preference
|
||||||
let _bootSettings={};
|
let _bootSettings={};
|
||||||
try{const s=await api('/api/settings');_bootSettings=s;window._sendKey=s.send_key||'enter';window._showTokenUsage=!!s.show_token_usage;window._showCliSessions=!!s.show_cli_sessions;const _theme=s.theme||'dark';document.documentElement.dataset.theme=_theme;localStorage.setItem('hermes-theme',_theme);}catch(e){window._sendKey='enter';window._showTokenUsage=false;window._showCliSessions=false;_bootSettings={check_for_updates:false};}
|
try{const s=await api('/api/settings');_bootSettings=s;window._sendKey=s.send_key||'enter';window._showTokenUsage=!!s.show_token_usage;window._showCliSessions=!!s.show_cli_sessions;window._botName=s.bot_name||'Hermes';const _theme=s.theme||'dark';document.documentElement.dataset.theme=_theme;localStorage.setItem('hermes-theme',_theme);applyBotName();}catch(e){window._sendKey='enter';window._showTokenUsage=false;window._showCliSessions=false;window._botName='Hermes';_bootSettings={check_for_updates:false};}
|
||||||
// Non-blocking update check (fire-and-forget, once per tab session)
|
// Non-blocking update check (fire-and-forget, once per tab session)
|
||||||
// ?test_updates=1 in URL forces banner display for testing (bypasses sessionStorage guards)
|
// ?test_updates=1 in URL forces banner display for testing (bypasses sessionStorage guards)
|
||||||
const _testUpdates=new URLSearchParams(location.search).get('test_updates')==='1';
|
const _testUpdates=new URLSearchParams(location.search).get('test_updates')==='1';
|
||||||
|
|||||||
@@ -372,6 +372,11 @@
|
|||||||
</label>
|
</label>
|
||||||
<div style="font-size:11px;color:var(--muted);margin-top:4px">Show a banner when newer versions of the WebUI or Agent are available. Runs a background git fetch periodically.</div>
|
<div style="font-size:11px;color:var(--muted);margin-top:4px">Show a banner when newer versions of the WebUI or Agent are available. Runs a background git fetch periodically.</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="settings-field">
|
||||||
|
<label for="settingsBotName">Assistant Name</label>
|
||||||
|
<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Display name for the assistant throughout the UI. Defaults to Hermes.</div>
|
||||||
|
<input type="text" id="settingsBotName" placeholder="Hermes" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px;font-size:13px">
|
||||||
|
</div>
|
||||||
<div class="settings-field" style="border-top:1px solid var(--border);padding-top:12px;margin-top:8px">
|
<div class="settings-field" style="border-top:1px solid var(--border);padding-top:12px;margin-top:8px">
|
||||||
<label for="settingsPassword">Access Password</label>
|
<label for="settingsPassword">Access Password</label>
|
||||||
<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Enter a new password to set or change it. Leave blank to keep current setting.</div>
|
<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Enter a new password to set or change it. Leave blank to keep current setting.</div>
|
||||||
|
|||||||
@@ -93,8 +93,9 @@ async function send(){
|
|||||||
assistantRow=document.createElement('div');assistantRow.className='msg-row';
|
assistantRow=document.createElement('div');assistantRow.className='msg-row';
|
||||||
assistantBody=document.createElement('div');assistantBody.className='msg-body';
|
assistantBody=document.createElement('div');assistantBody.className='msg-body';
|
||||||
const role=document.createElement('div');role.className='msg-role assistant';
|
const role=document.createElement('div');role.className='msg-role assistant';
|
||||||
const icon=document.createElement('div');icon.className='role-icon assistant';icon.textContent='H';
|
const _bn=window._botName||'Hermes';
|
||||||
const lbl=document.createElement('span');lbl.style.fontSize='12px';lbl.textContent='Hermes';
|
const icon=document.createElement('div');icon.className='role-icon assistant';icon.textContent=_bn.charAt(0).toUpperCase();
|
||||||
|
const lbl=document.createElement('span');lbl.style.fontSize='12px';lbl.textContent=_bn;
|
||||||
role.appendChild(icon);role.appendChild(lbl);
|
role.appendChild(icon);role.appendChild(lbl);
|
||||||
assistantRow.appendChild(role);assistantRow.appendChild(assistantBody);
|
assistantRow.appendChild(role);assistantRow.appendChild(assistantBody);
|
||||||
$('msgInner').appendChild(assistantRow);
|
$('msgInner').appendChild(assistantRow);
|
||||||
|
|||||||
@@ -1009,6 +1009,9 @@ async function loadSettingsPanel(){
|
|||||||
if(syncCb){syncCb.checked=!!settings.sync_to_insights;syncCb.addEventListener('change',_markSettingsDirty,{once:false});}
|
if(syncCb){syncCb.checked=!!settings.sync_to_insights;syncCb.addEventListener('change',_markSettingsDirty,{once:false});}
|
||||||
const updateCb=$('settingsCheckUpdates');
|
const updateCb=$('settingsCheckUpdates');
|
||||||
if(updateCb){updateCb.checked=settings.check_for_updates!==false;updateCb.addEventListener('change',_markSettingsDirty,{once:false});}
|
if(updateCb){updateCb.checked=settings.check_for_updates!==false;updateCb.addEventListener('change',_markSettingsDirty,{once:false});}
|
||||||
|
// Bot name
|
||||||
|
const botNameField=$('settingsBotName');
|
||||||
|
if(botNameField){botNameField.value=settings.bot_name||'Hermes';botNameField.addEventListener('input',_markSettingsDirty,{once:false});}
|
||||||
// Password field: always blank (we don't send hash back)
|
// Password field: always blank (we don't send hash back)
|
||||||
const pwField=$('settingsPassword');
|
const pwField=$('settingsPassword');
|
||||||
if(pwField){pwField.value='';pwField.addEventListener('input',_markSettingsDirty,{once:false});}
|
if(pwField){pwField.value='';pwField.addEventListener('input',_markSettingsDirty,{once:false});}
|
||||||
@@ -1042,6 +1045,8 @@ async function saveSettings(andClose){
|
|||||||
body.show_cli_sessions=showCliSessions;
|
body.show_cli_sessions=showCliSessions;
|
||||||
body.sync_to_insights=!!($('settingsSyncInsights')||{}).checked;
|
body.sync_to_insights=!!($('settingsSyncInsights')||{}).checked;
|
||||||
body.check_for_updates=!!($('settingsCheckUpdates')||{}).checked;
|
body.check_for_updates=!!($('settingsCheckUpdates')||{}).checked;
|
||||||
|
const botName=(($('settingsBotName')||{}).value||'').trim();
|
||||||
|
body.bot_name=botName||'Hermes';
|
||||||
// Password: only act if the field has content; blank = leave auth unchanged
|
// Password: only act if the field has content; blank = leave auth unchanged
|
||||||
if(pw && pw.trim()){
|
if(pw && pw.trim()){
|
||||||
try{
|
try{
|
||||||
@@ -1060,6 +1065,8 @@ async function saveSettings(andClose){
|
|||||||
window._sendKey=sendKey||'enter';
|
window._sendKey=sendKey||'enter';
|
||||||
window._showTokenUsage=showTokenUsage;
|
window._showTokenUsage=showTokenUsage;
|
||||||
window._showCliSessions=showCliSessions;
|
window._showCliSessions=showCliSessions;
|
||||||
|
window._botName=body.bot_name;
|
||||||
|
if(typeof applyBotName==='function') applyBotName();
|
||||||
_settingsDirty=false; _settingsThemeOnOpen=theme;
|
_settingsDirty=false; _settingsThemeOnOpen=theme;
|
||||||
const bar=$('settingsUnsavedBar'); if(bar) bar.style.display='none';
|
const bar=$('settingsUnsavedBar'); if(bar) bar.style.display='none';
|
||||||
renderMessages();
|
renderMessages();
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ async function loadSession(sid){
|
|||||||
if(tc&&tc.name) appendLiveToolCard(tc);
|
if(tc&&tc.name) appendLiveToolCard(tc);
|
||||||
}
|
}
|
||||||
syncTopbar();await loadDir('.');renderMessages();appendThinking();
|
syncTopbar();await loadDir('.');renderMessages();appendThinking();
|
||||||
setBusy(true);setStatus('Hermes is thinking\u2026');
|
setBusy(true);setStatus((window._botName||'Hermes')+' is thinking\u2026');
|
||||||
startApprovalPolling(sid);
|
startApprovalPolling(sid);
|
||||||
}else{
|
}else{
|
||||||
MSG_QUEUE.length=0;updateQueueBadge(); // clear queue for the viewed session
|
MSG_QUEUE.length=0;updateQueueBadge(); // clear queue for the viewed session
|
||||||
@@ -429,7 +429,7 @@ async function deleteSession(sid){
|
|||||||
if(remaining.sessions&&remaining.sessions.length){
|
if(remaining.sessions&&remaining.sessions.length){
|
||||||
await loadSession(remaining.sessions[0].session_id);
|
await loadSession(remaining.sessions[0].session_id);
|
||||||
}else{
|
}else{
|
||||||
$('topbarTitle').textContent='Hermes';
|
$('topbarTitle').textContent=window._botName||'Hermes';
|
||||||
$('topbarMeta').textContent='Start a new conversation';
|
$('topbarMeta').textContent='Start a new conversation';
|
||||||
$('msgInner').innerHTML='';
|
$('msgInner').innerHTML='';
|
||||||
$('emptyState').style.display='';
|
$('emptyState').style.display='';
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ function setStatus(t){
|
|||||||
txt.textContent=t;
|
txt.textContent=t;
|
||||||
bar.style.display='';
|
bar.style.display='';
|
||||||
// Show dismiss X only for static/error messages, not transient busy ones
|
// Show dismiss X only for static/error messages, not transient busy ones
|
||||||
const transient = t.endsWith('…') || t === 'Hermes is thinking…';
|
const transient = t.endsWith('…') || t === (window._botName||'Hermes')+' is thinking\u2026';
|
||||||
if(dismiss)dismiss.style.display=(!transient && !S.busy)?'inline':'none';
|
if(dismiss)dismiss.style.display=(!transient && !S.busy)?'inline':'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -402,7 +402,7 @@ async function checkInflightOnBoot(sid) {
|
|||||||
|
|
||||||
function syncTopbar(){
|
function syncTopbar(){
|
||||||
if(!S.session){
|
if(!S.session){
|
||||||
document.title='Hermes';
|
document.title=window._botName||'Hermes';
|
||||||
// Show default workspace name even without a session
|
// Show default workspace name even without a session
|
||||||
const sidebarName=$('sidebarWsName');
|
const sidebarName=$('sidebarWsName');
|
||||||
if(sidebarName && sidebarName.textContent==='Workspace'){
|
if(sidebarName && sidebarName.textContent==='Workspace'){
|
||||||
@@ -412,7 +412,7 @@ function syncTopbar(){
|
|||||||
}
|
}
|
||||||
const sessionTitle=S.session.title||'Untitled';
|
const sessionTitle=S.session.title||'Untitled';
|
||||||
$('topbarTitle').textContent=sessionTitle;
|
$('topbarTitle').textContent=sessionTitle;
|
||||||
document.title=sessionTitle+' \u2014 Hermes';
|
document.title=sessionTitle+' \u2014 '+(window._botName||'Hermes');
|
||||||
const vis=S.messages.filter(m=>m&&m.role&&m.role!=='tool');
|
const vis=S.messages.filter(m=>m&&m.role&&m.role!=='tool');
|
||||||
$('topbarMeta').textContent=`${vis.length} messages`;
|
$('topbarMeta').textContent=`${vis.length} messages`;
|
||||||
// If a profile switch just happened, apply its model rather than the session's stale value.
|
// If a profile switch just happened, apply its model rather than the session's stale value.
|
||||||
@@ -505,7 +505,8 @@ function renderMessages(){
|
|||||||
const retryBtn = isLastAssistant ? `<button class="msg-action-btn" title="Regenerate response" onclick="regenerateResponse(this)">↻</button>` : '';
|
const retryBtn = isLastAssistant ? `<button class="msg-action-btn" title="Regenerate response" onclick="regenerateResponse(this)">↻</button>` : '';
|
||||||
const tsVal=m._ts||m.timestamp;
|
const tsVal=m._ts||m.timestamp;
|
||||||
const tsTitle=tsVal?new Date(tsVal*1000).toLocaleString():'';
|
const tsTitle=tsVal?new Date(tsVal*1000).toLocaleString():'';
|
||||||
row.innerHTML=`<div class="msg-role ${m.role}" ${tsTitle?`title="${esc(tsTitle)}"`:''}><div class="role-icon ${m.role}">${isUser?'Y':'H'}</div><span style="font-size:12px">${isUser?'You':'Hermes'}</span>${tsTitle?`<span class="msg-time">${new Date(tsVal*1000).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'})}</span>`:''}<span class="msg-actions">${editBtn}<button class="msg-copy-btn msg-action-btn" title="Copy" onclick="copyMsg(this)">📋</button>${retryBtn}</span></div>${filesHtml}<div class="msg-body">${bodyHtml}</div>`;
|
const _bn=window._botName||'Hermes';
|
||||||
|
row.innerHTML=`<div class="msg-role ${m.role}" ${tsTitle?`title="${esc(tsTitle)}"`:''}><div class="role-icon ${m.role}">${isUser?'Y':_bn.charAt(0).toUpperCase()}</div><span style="font-size:12px">${isUser?'You':esc(_bn)}</span>${tsTitle?`<span class="msg-time">${new Date(tsVal*1000).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'})}</span>`:''}<span class="msg-actions">${editBtn}<button class="msg-copy-btn msg-action-btn" title="Copy" onclick="copyMsg(this)">📋</button>${retryBtn}</span></div>${filesHtml}<div class="msg-body">${bodyHtml}</div>`;
|
||||||
row.dataset.rawText = String(content).trim();
|
row.dataset.rawText = String(content).trim();
|
||||||
inner.appendChild(row);
|
inner.appendChild(row);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user