Files
odysseus/static/index.html

2273 lines
190 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Odysseus Chat</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath d='M16 4L16 22L6 22Z' fill='%23e06c75'/%3E%3Cpath d='M16 8L16 22L24 22Z' fill='%23e06c75' opacity='0.6'/%3E%3Cpath d='M4 24Q10 20 16 24Q22 28 28 24' stroke='%23e06c75' stroke-width='2.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E">
<link rel="manifest" href="/static/manifest.json">
<meta name="theme-color" content="#282c34">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<!-- Disable iOS / Android auto-linkifying of numbers (phone/date/address)
in email bodies — was wrapping random digits in <a href="tel:..."> with
browser-default styling that didn't match the Odysseus theme. -->
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
<link rel="apple-touch-icon" href="/static/icon-192.png">
<script nonce="{{CSP_NONCE}}">
window._odysseusLoadTime = Date.now();
(function(){
try {
var t = JSON.parse(localStorage.getItem('odysseus-theme'));
if (t && t.colors) {
var s = document.documentElement.style;
var c = t.colors;
s.setProperty('--bg', c.bg);
s.setProperty('--fg', c.fg);
s.setProperty('--panel', c.panel);
s.setProperty('--border', c.border);
if (c.red) s.setProperty('--red', c.red);
// Set --brand-color now (= custom brand or the theme red) so the
// loading-screen ASCII wave shows its final colour from the first
// paint. Otherwise it starts on --red and visibly switches once the
// app boots and applyColors() fills --brand-color.
var _bc = (c.advanced && c.advanced.brandColor) || c.red;
if (_bc) s.setProperty('--brand-color', _bc);
// Match the mobile browser toolbar / status bar to the saved theme bg
// from the very first paint. Otherwise the hardcoded dark meta sits
// there during load and the bar "switches" colour once the app boots.
var mtc = document.querySelector('meta[name="theme-color"]');
if (mtc && c.bg) mtc.setAttribute('content', c.bg);
// Update favicon to match accent color
var ac = c.red || '#e06c75';
var fav = document.querySelector("link[rel='icon']");
if (fav) fav.href = 'data:image/svg+xml,' + encodeURIComponent("<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><path d='M16 4L16 22L6 22Z' fill='" + ac + "'/><path d='M16 8L16 22L24 22Z' fill='" + ac + "' opacity='0.6'/><path d='M4 24Q10 20 16 24Q22 28 28 24' stroke='" + ac + "' stroke-width='2.5' fill='none' stroke-linecap='round'/></svg>");
// Derive syntax highlighting colors from theme
function h2hsl(hex) {
hex = hex.replace('#','');
var r=parseInt(hex.substring(0,2),16)/255, g=parseInt(hex.substring(2,4),16)/255, b=parseInt(hex.substring(4,6),16)/255;
var mx=Math.max(r,g,b), mn=Math.min(r,g,b), h, sv, l=(mx+mn)/2;
if(mx===mn){h=sv=0}else{var d=mx-mn;sv=l>0.5?d/(2-mx-mn):d/(mx+mn);if(mx===r)h=((g-b)/d+(g<b?6:0))/6;else if(mx===g)h=((b-r)/d+2)/6;else h=((r-g)/d+4)/6}
return[h*360,sv*100,l*100];
}
function hsl2h(h,sv,l) {
h=((h%360)+360)%360;sv=Math.max(0,Math.min(100,sv))/100;l=Math.max(0,Math.min(100,l))/100;
var a=sv*Math.min(l,1-l);function f(n){var k=(n+h/30)%12;return l-a*Math.max(-1,Math.min(k-3,9-k,1))}
function th(v){return Math.round(v*255).toString(16).padStart(2,'0')}
return'#'+th(f(0))+th(f(8))+th(f(4));
}
var fH=h2hsl(c.fg),bH=h2hsl(c.bg),rH=h2hsl(c.red||'#e06c75');
var dk=bH[2]<50;
s.setProperty('--hl-bg', hsl2h(bH[0],bH[1],dk?Math.max(bH[2]-4,0):Math.min(bH[2]+4,100)));
s.setProperty('--hl-fg', c.fg);
s.setProperty('--hl-keyword', hsl2h((rH[0]+280)%360,Math.min(rH[1]+10,80),dk?70:45));
s.setProperty('--hl-string', hsl2h(40,Math.min(fH[1]+20,70),dk?72:42));
s.setProperty('--hl-comment', hsl2h(fH[0],Math.max(fH[1]-20,5),(fH[2]*0.5+bH[2]*0.5)));
s.setProperty('--hl-function', hsl2h(210,Math.min(fH[1]+20,75),dk?70:45));
s.setProperty('--hl-number', hsl2h(20,Math.min(fH[1]+15,65),dk?68:48));
s.setProperty('--hl-builtin', hsl2h(180,Math.min(fH[1]+15,60),dk?65:40));
s.setProperty('--hl-variable', hsl2h((fH[0]+30)%360,Math.min(fH[1]+5,60),fH[2]));
s.setProperty('--hl-params', hsl2h(fH[0],Math.max(fH[1]-5,10),dk?Math.min(fH[2]+8,85):Math.max(fH[2]-8,25)));
// Apply advanced overrides if present
if (c.advanced) {
var a = c.advanced;
var advMap = {userBubbleBg:'--user-bubble-bg',aiBubbleBg:'--ai-bubble-bg',bubbleBorder:'--bubble-border',sidebarBg:'--sidebar-bg',sectionAccent:'--section-accent',brandColor:'--brand-color',inputBg:'--input-bg',inputBorder:'--input-border',sendBtnBg:'--send-btn-bg',sendBtnHover:'--send-btn-hover',codeBg:'--code-bg',codeFg:'--code-fg',toggleBg:'--toggle-bg',toggleActive:'--toggle-active',accentPrimary:'--accent-primary',accentError:'--accent-error'};
for (var k in advMap) { if (a[k]) s.setProperty(advMap[k], a[k]); }
}
}
// Apply font early
if (t && t.font) {
var fm = {mono:"'Fira Code', monospace",sans:"system-ui, -apple-system, 'Segoe UI', sans-serif",serif:"Georgia, 'Times New Roman', serif"};
if (fm[t.font]) { s.setProperty('--font-family', fm[t.font]); }
else { s.setProperty('--font-family', "'" + t.font.replace(/'/g,'') + "', sans-serif"); }
}
// Apply density class on html
if (t && t.density && t.density !== 'comfortable') {
document.documentElement.classList.add('density-' + t.density);
}
// Apply background pattern on body once available
if (t && t.bgPattern && t.bgPattern !== 'none') {
document.addEventListener('DOMContentLoaded', function() {
document.body.classList.add('bg-pattern-' + t.bgPattern);
}, {once:true});
}
} catch(e){}
})();
</script>
<!-- Per-route favicon. Bookmarking /calendar shows a calendar icon,
/notes a notes icon, etc. Each shape is rendered in the current
theme accent color. Falls back to the boat logo on the root path. -->
<script nonce="{{CSP_NONCE}}">
(function(){
try {
var path = (window.location.pathname || '').toLowerCase();
var theme = (function(){ try { return JSON.parse(localStorage.getItem('odysseus-theme')); } catch(_) { return null; } })();
var ac = (theme && (theme.red || theme.colors && theme.colors.red)) || '#e06c75';
// Shapes are line-stroke SVGs (no emoji glyphs). Each entry returns
// the inner SVG markup rendered against currentColor=`ac`.
var SHAPES = {
'/calendar':
"<rect x='4' y='6' width='24' height='22' rx='2' fill='none' stroke='" + ac + "' stroke-width='2.5'/>" +
"<line x1='4' y1='12' x2='28' y2='12' stroke='" + ac + "' stroke-width='2.5'/>" +
"<line x1='10' y1='3' x2='10' y2='9' stroke='" + ac + "' stroke-width='2.5' stroke-linecap='round'/>" +
"<line x1='22' y1='3' x2='22' y2='9' stroke='" + ac + "' stroke-width='2.5' stroke-linecap='round'/>",
'/notes':
"<rect x='6' y='4' width='20' height='24' rx='2' fill='none' stroke='" + ac + "' stroke-width='2.5'/>" +
"<line x1='10' y1='10' x2='22' y2='10' stroke='" + ac + "' stroke-width='2'/>" +
"<line x1='10' y1='15' x2='22' y2='15' stroke='" + ac + "' stroke-width='2'/>" +
"<line x1='10' y1='20' x2='18' y2='20' stroke='" + ac + "' stroke-width='2'/>",
'/cookbook':
"<path d='M5 8 L5 26 A2 2 0 0 0 7 28 L25 28 A2 2 0 0 0 27 26 L27 8' fill='none' stroke='" + ac + "' stroke-width='2.5' stroke-linejoin='round'/>" +
"<path d='M9 4 L23 4 L23 8 L9 8 Z' fill='none' stroke='" + ac + "' stroke-width='2.5' stroke-linejoin='round'/>" +
"<line x1='11' y1='14' x2='21' y2='14' stroke='" + ac + "' stroke-width='2'/>" +
"<line x1='11' y1='19' x2='17' y2='19' stroke='" + ac + "' stroke-width='2'/>",
'/email':
"<rect x='4' y='7' width='24' height='18' rx='2' fill='none' stroke='" + ac + "' stroke-width='2.5'/>" +
"<path d='M5 9 L16 17 L27 9' fill='none' stroke='" + ac + "' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/>",
'/memory':
"<path d='M16 5 C10 5 6 9 6 14 C6 19 10 21 11 22 L11 26 L21 26 L21 22 C22 21 26 19 26 14 C26 9 22 5 16 5 Z' fill='none' stroke='" + ac + "' stroke-width='2.5' stroke-linejoin='round'/>" +
"<line x1='12' y1='28' x2='20' y2='28' stroke='" + ac + "' stroke-width='2'/>",
'/gallery':
"<rect x='4' y='4' width='24' height='24' rx='2' fill='none' stroke='" + ac + "' stroke-width='2.5'/>" +
"<circle cx='12' cy='12' r='2.5' fill='" + ac + "'/>" +
"<path d='M4 22 L11 16 L18 21 L23 17 L28 22' fill='none' stroke='" + ac + "' stroke-width='2.5' stroke-linejoin='round'/>",
'/tasks':
"<rect x='4' y='4' width='24' height='24' rx='3' fill='none' stroke='" + ac + "' stroke-width='2.5'/>" +
"<path d='M9 16 L14 21 L23 11' fill='none' stroke='" + ac + "' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/>",
'/library':
"<rect x='5' y='5' width='5' height='22' rx='1' fill='none' stroke='" + ac + "' stroke-width='2.5'/>" +
"<rect x='13' y='5' width='5' height='22' rx='1' fill='none' stroke='" + ac + "' stroke-width='2.5'/>" +
"<rect x='21' y='8' width='6' height='19' rx='1' fill='none' stroke='" + ac + "' stroke-width='2.5' transform='rotate(8 24 17)'/>",
};
var inner = SHAPES[path];
if (!inner) return; // Root path keeps the default boat icon.
var svg = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'>" + inner + "</svg>";
var href = 'data:image/svg+xml,' + encodeURIComponent(svg);
var fav = document.querySelector("link[rel='icon']");
if (!fav) { fav = document.createElement('link'); fav.rel = 'icon'; fav.type = 'image/svg+xml'; document.head.appendChild(fav); }
fav.href = href;
var apple = document.querySelector("link[rel='apple-touch-icon']");
if (!apple) { apple = document.createElement('link'); apple.rel = 'apple-touch-icon'; document.head.appendChild(apple); }
apple.href = href;
// Also customize the page title so the bookmark inherits a useful name.
var titles = {
'/calendar': 'Calendar — Odysseus',
'/notes': 'Notes — Odysseus',
'/cookbook': 'Cookbook — Odysseus',
'/email': 'Email — Odysseus',
'/memory': 'Memory — Odysseus',
'/gallery': 'Gallery — Odysseus',
'/tasks': 'Tasks — Odysseus',
'/library': 'Library — Odysseus',
};
if (titles[path]) document.title = titles[path];
// Per-route Android home-screen icon. We swap the <link rel="manifest">
// to a per-page Blob URL with this route's SVG icon — that way "Add to
// Home Screen" picks up the route-specific glyph instead of the shared
// boat logo. Falls back silently on browsers without Blob URL support.
try {
if (inner && typeof Blob !== 'undefined') {
var pwa = {
name: (titles[path] || 'Odysseus'),
short_name: (titles[path] || 'Odysseus').split('—')[0].trim(),
start_url: path,
scope: '/',
display: 'standalone',
background_color: '#0e0e10',
theme_color: ac,
icons: [
{ src: href, sizes: '192x192', type: 'image/svg+xml', purpose: 'any maskable' },
{ src: href, sizes: '512x512', type: 'image/svg+xml', purpose: 'any maskable' },
],
};
var blob = new Blob([JSON.stringify(pwa)], { type: 'application/manifest+json' });
var url = URL.createObjectURL(blob);
var ml = document.querySelector("link[rel='manifest']");
if (!ml) { ml = document.createElement('link'); ml.rel = 'manifest'; document.head.appendChild(ml); }
ml.href = url;
}
} catch(_) {}
} catch(e){}
})();
</script>
<script defer src="/static/lib/highlight.min.js"></script>
<!-- Inter font — self-hosted, no Google dependencies -->
<style>
@font-face { font-family: 'Inter'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/Inter-Regular.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 500; font-style: normal; font-display: swap; src: url('/static/fonts/Inter-Medium.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 600; font-style: normal; font-display: swap; src: url('/static/fonts/Inter-SemiBold.woff2') format('woff2'); }
</style>
<!-- KaTeX CSS is loaded with media="print" so it doesn't block render,
then flipped to "all" via JS after load. Mermaid init runs once the
library finishes loading. Both hooks are wired via addEventListener
below (inline onload= attrs are blocked by CSP script-src-attr). -->
<link id="katex-css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css" media="print">
<script async src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js"></script>
<script id="mermaid-script" async src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
<script nonce="{{CSP_NONCE}}">
(function(){
var k = document.getElementById('katex-css');
if (k) k.addEventListener('load', function(){ k.media = 'all'; }, { once: true });
var m = document.getElementById('mermaid-script');
if (m) m.addEventListener('load', function(){
if (window.odysseusInitMermaid) window.odysseusInitMermaid();
}, { once: true });
})();
</script>
<link rel="stylesheet" href="/static/style.css">
<link rel="modulepreload" href="/static/app.js">
<link rel="modulepreload" href="/static/js/chat.js">
<link rel="modulepreload" href="/static/js/ui.js">
<link rel="modulepreload" href="/static/js/sessions.js">
<link rel="modulepreload" href="/static/js/markdown.js">
<meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content, viewport-fit=cover" />
</head>
<body>
<!-- Loading overlay — hides all flashing until app is ready -->
<div id="app-loader" style="position:fixed;inset:0;z-index:99999;background:var(--bg,#282c34);display:flex;align-items:center;justify-content:center;flex-direction:column;gap:8px;transition:opacity .3s">
<div id="loader-wave" style="color:var(--brand-color,var(--red,#e06c75));font-family:monospace;font-size:11px;opacity:.5">▁▂▃</div>
</div>
<script nonce="{{CSP_NONCE}}">
(function(){
var el=document.getElementById('loader-wave');
if(!el)return;
var frames=['▁▂▃','▂▃▄','▃▄▅','▄▅▆','▅▆▅','▆▅▄','▅▄▃','▄▃▂','▃▂▁'];
var i=0;
var iv=setInterval(function(){
if(!document.getElementById('app-loader')){clearInterval(iv);return}
el.textContent=frames[i%frames.length];
i++;
},150);
setTimeout(function(){var l=document.getElementById('app-loader');if(l){l.style.opacity='0';setTimeout(function(){l.remove()},300)}},5000);
})();
</script>
<!-- Memory Management Modal -->
<div id="memory-modal" class="modal hidden">
<div class="modal-content memory-modal-content" role="dialog" aria-label="Brain" style="background:var(--bg)">
<div class="modal-header">
<h4><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"/><path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"/></svg>Brain</h4>
<button class="close-btn" id="close-memory-modal" aria-label="Close memory modal"></button>
</div>
<div class="modal-body memory-modal-body">
<div class="memory-tabs">
<button class="memory-tab active" data-memory-tab="browse"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px"><path d="M12 2a7 7 0 0 1 7 7c0 2.4-1.2 4.5-3 5.7V17a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-2.3C6.2 13.5 5 11.4 5 9a7 7 0 0 1 7-7z"/><line x1="10" y1="22" x2="14" y2="22"/></svg>Memories <span id="memory-count" class="memory-count" style="font-size:0.8em;opacity:0.6;font-weight:normal;margin-left:4px">0</span></button>
<button class="memory-tab" data-memory-tab="skills"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>Skills <span id="skills-count" class="memory-count" style="font-size:0.8em;opacity:0.6;font-weight:normal;margin-left:4px">0</span></button>
<button class="memory-tab" data-memory-tab="add"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>Add</button>
<button class="memory-tab" data-memory-tab="settings"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>Settings</button>
</div>
<!-- ── Browse tab ── -->
<div class="memory-tab-panel" data-memory-panel="browse">
<div class="admin-card" style="display:flex;flex-direction:column;overflow:hidden;flex:1;min-height:0;">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:2px;">
<h2 style="display:flex;align-items:center;gap:6px;margin:0;padding:0;line-height:1;">Memories <span id="memory-count-h2" class="memory-count" style="font-size:0.6em;opacity:0.6;font-weight:normal"></span></h2>
<span style="flex:1"></span>
<label class="admin-switch" title="Include memories in chat context"><input type="checkbox" id="memory-enabled-header-toggle" checked /><span class="admin-slider"></span></label>
</div>
<p class="memory-desc doclib-desc" style="margin-top:6px;">Long-term facts the AI remembers across chats — recall, edit, or curate.</p>
<div class="memory-toolbar">
<div class="memory-toolbar-row">
<select id="memory-sort" class="memory-sort-select" aria-label="Sort memories">
<option value="newest">Newest</option>
<option value="oldest">Oldest</option>
<option value="alpha">A-Z</option>
<option value="uses">Most used</option>
</select>
<button id="memory-select-btn" class="memory-toolbar-btn" title="Select multiple memories">Select</button>
<button id="memory-tidy-btn" class="memory-toolbar-btn" title="AI tidy: deduplicate and clean up memories"><svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" style="vertical-align:-1px;margin-right:2px;"><path d="M12 0L14.59 8.41L23 12L14.59 15.59L12 24L9.41 15.59L1 12L9.41 8.41Z"/></svg> Tidy</button>
</div>
<input type="text" id="memory-search" placeholder="Search memories…" class="memory-search-input" aria-label="Search memories" />
<div id="memory-category-filters" class="memory-category-filters">
<button class="memory-cat-chip active" data-cat="all">all</button>
</div>
</div>
<div id="memory-bulk-bar" class="memory-bulk-bar hidden">
<label class="memory-bulk-check-all" style="position:relative;top:0px;"><input type="checkbox" id="memory-select-all" /> All</label>
<span id="memory-selected-count">0 Selected</span>
<button id="memory-bulk-delete" class="memory-toolbar-btn danger" disabled><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:3px;"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg>Delete</button>
<button id="memory-bulk-cancel" class="memory-toolbar-btn" title="Cancel (Esc)" style="margin-left:4px;padding:3px 6px;"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>
</div>
<div id="memory-list" class="memory-list"></div>
<div id="memory-suggestions-body" class="memory-suggestions hidden"></div>
</div>
</div>
<!-- ── Add tab ── -->
<div class="memory-tab-panel hidden" data-memory-panel="add">
<div class="admin-card">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:2px;">
<h2 style="margin:0;padding:0;line-height:1;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><path d="M12 2a7 7 0 0 1 7 7c0 2.4-1.2 4.5-3 5.7V17a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-2.3C6.2 13.5 5 11.4 5 9a7 7 0 0 1 7-7z"/><line x1="10" y1="22" x2="14" y2="22"/></svg>Add Memory</h2>
<span style="flex:1"></span>
<button id="memory-import-btn" class="theme-io-btn" title="Import memories from a file" style="height:26px;font-size:12px;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>Import</button>
<button id="memory-export-btn" class="theme-io-btn" title="Export all memories as JSON" style="height:26px;font-size:12px;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>Export</button>
<input type="file" id="memory-import-file" accept=".txt,.md,.pdf,.csv,.log,.json,.py,.js,.html" hidden />
</div>
<p class="memory-desc doclib-desc" style="margin:4px 0 6px;">
Import a <code>.txt</code>, <code>.md</code>, <code>.pdf</code>, <code>.csv</code>, <code>.log</code>, <code>.json</code>, <code>.py</code>, <code>.js</code>, or <code>.html</code> file &mdash; the AI reads it and suggests candidate memories you can approve.
</p>
<div class="memory-add-row" style="margin-top:8px;">
<div class="skill-ph-wrap" style="flex:1;min-width:0;">
<input type="text" id="new-memory-input" placeholder=" " class="memory-add-input skill-hint-input" aria-label="New memory text" />
<span class="skill-rich-ph"><span class="k">Add a memory</span> &mdash; e.g. 'I prefer concise replies' <svg class="k" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-left:4px;" aria-hidden="true"><polyline points="9 10 4 15 9 20"/><path d="M20 4v7a4 4 0 0 1-4 4H4"/></svg></span>
</div>
</div>
</div>
<div class="admin-card">
<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:2px;">
<h2 style="margin:0;padding:0;line-height:1;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>Add Skill</h2>
</div>
<p class="memory-desc doclib-desc" style="margin-top:6px;">Create a skill by hand — title, what it solves, and an approach.</p>
<div class="skill-ph-wrap" style="margin-top:4px;margin-bottom:6px;">
<input type="text" id="new-skill-title" placeholder=" " class="memory-add-input skill-hint-input" aria-label="Skill title" />
<span class="skill-rich-ph"><span class="k">Title</span> — short name, e.g. “build-vllm-wheel”</span>
</div>
<div class="skill-ph-wrap" style="margin-bottom:6px;">
<input type="text" id="new-skill-problem" placeholder=" " class="memory-add-input skill-hint-input" aria-label="When to use this skill" />
<span class="skill-rich-ph"><span class="k">When to use</span> — what problem does this skill solve?</span>
</div>
<div class="skill-ph-wrap" style="margin-bottom:6px;">
<textarea id="new-skill-solution" placeholder=" " class="memory-add-input skill-hint-input" rows="2" style="resize:vertical;" aria-label="How — the approach or steps"></textarea>
<span class="skill-rich-ph skill-rich-ph-top"><span class="k">How</span> — the approach, steps, commands, or rules to follow</span>
</div>
<div class="skill-ph-wrap" style="margin-bottom:8px;">
<input type="text" id="new-skill-tags" placeholder=" " class="memory-add-input skill-hint-input" aria-label="Tags" />
<span class="skill-rich-ph"><span class="k">Tags</span> — comma-separated, e.g. python, build, vllm</span>
</div>
<div style="display:flex;justify-content:flex-end;">
<button id="add-skill-btn" class="memory-toolbar-btn">Add Skill</button>
</div>
</div>
</div>
<!-- ── Skills tab ── -->
<div class="memory-tab-panel hidden" data-memory-panel="skills">
<div class="admin-card" style="display:flex;flex-direction:column;overflow:hidden;flex:1;min-height:0;">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:2px;">
<h2 style="margin:0;padding:0;line-height:1;">Skills <span id="skills-count-h2" class="memory-count" style="font-size:0.6em;opacity:0.6;font-weight:normal"></span></h2>
<span style="flex:1"></span>
<label class="admin-switch" title="Inject relevant skills into chat context"><input type="checkbox" id="skills-enabled-header-toggle" checked /><span class="admin-slider"></span></label>
</div>
<p class="memory-desc doclib-desc" style="margin-top:6px;">Reusable procedures the AI can call via /skill — sort by confidence to surface the proven ones.</p>
<div class="memory-toolbar">
<div class="memory-toolbar-row">
<select id="skills-sort" class="memory-sort-select">
<optgroup label="Sort">
<option value="sort:confidence">Confidence</option>
<option value="sort:uses">Most used</option>
<option value="sort:alpha">A-Z</option>
<option value="sort:recent">Recent</option>
</optgroup>
<optgroup label="Filter">
<option value="filter:all">All skills</option>
<option value="filter:drafts">Drafts only</option>
<option value="filter:published">Published only</option>
<option value="filter:conf95">Confidence ≤ 95%</option>
<option value="filter:conf90">Confidence ≤ 90%</option>
<option value="filter:conf85">Confidence ≤ 85%</option>
<option value="filter:conf80">Confidence ≤ 80%</option>
<option value="filter:conf75">Confidence ≤ 75%</option>
<option value="filter:conf70">Confidence ≤ 70%</option>
</optgroup>
</select>
<button id="skills-select-btn" class="memory-toolbar-btn" title="Select multiple skills">Select</button>
<button id="skills-audit-btn" class="memory-toolbar-btn" title="Test every skill, auto-fix the weak ones, flag what still fails"><svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" style="vertical-align:-1px;margin-right:3px;"><path d="M12 0L14.59 8.41L23 12L14.59 15.59L12 24L9.41 15.59L1 12L9.41 8.41Z"/></svg>Audit all</button>
</div>
<input type="text" id="skills-search" placeholder="Search skills…" class="memory-search-input" aria-label="Search skills" />
</div>
<div id="skills-audit-panel" class="skills-audit-panel hidden"></div>
<div id="skills-bulk-bar" class="memory-bulk-bar hidden">
<label class="memory-bulk-check-all" style="position:relative;top:0px;"><input type="checkbox" id="skills-select-all" /> All</label>
<span id="skills-selected-count">0 Selected</span>
<button id="skills-bulk-publish" class="memory-toolbar-btn" disabled title="Publish selected drafts" style="margin-left:auto;"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:3px;"><polyline points="20 6 9 17 4 12"/></svg>Approve</button>
<button id="skills-bulk-audit" class="memory-toolbar-btn" disabled title="Audit selected draft skills"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:3px;"><polygon points="5 3 19 12 5 21 5 3"/></svg>Audit</button>
<button id="skills-bulk-delete-nonpassing" class="memory-toolbar-btn danger" disabled title="Delete selected duplicates, generic/irrelevant skills, failed audits, and skills below threshold"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:3px;"><path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11l4 4"/><path d="M14 11l-4 4"/></svg>Delete non passing</button>
<button id="skills-bulk-delete" class="memory-toolbar-btn danger" disabled><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:3px;"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg>Delete</button>
<button id="skills-bulk-cancel" class="memory-toolbar-btn" title="Cancel (Esc)" style="padding:3px 6px;"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>
</div>
<div id="skills-list" class="memory-list"></div>
</div>
</div>
<!-- ── Settings tab ── -->
<div class="memory-tab-panel hidden" data-memory-panel="settings">
<div class="admin-card">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px">
<h2 style="margin:0">Auto-extract memories</h2>
<label class="admin-switch" style="flex-shrink:0"><input type="checkbox" id="auto-memory-toggle" checked /><span class="admin-slider"></span></label>
</div>
<span class="admin-toggle-sub" style="display:block;margin-top:6px">Automatically extract memories from conversations.</span>
</div>
<div class="admin-card">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px">
<h2 style="margin:0">Auto-extract skills</h2>
<label class="admin-switch" style="flex-shrink:0"><input type="checkbox" id="auto-skills-toggle" /><span class="admin-slider"></span></label>
</div>
<span class="admin-toggle-sub" style="display:block;margin-top:6px;opacity:0.6">Automatically draft reusable skills from your workflows. Audit all can publish passing skills using the threshold below.</span>
<span class="admin-toggle-sub" style="display:block;margin-top:6px;opacity:0.6">The library can grow; cleanup retires weak/duplicate skills only after review.</span>
</div>
<div class="admin-card">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px">
<h2 style="margin:0">Inject Skills</h2>
</div>
<span class="admin-toggle-sub" style="display:block;margin-top:6px;opacity:0.6">Controls how many relevant published or approved skills are added to each agent request.</span>
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:8px">
<span class="admin-toggle-sub" style="margin:0">Max skills per request</span>
<input type="number" id="skill-max-input" min="0" max="12" step="1" value="3" aria-label="Max skills to inject" style="flex-shrink:0;width:72px;background:var(--input-bg,var(--panel));color:var(--fg);border:1px solid var(--border);border-radius:6px;padding:4px 6px;font-size:12px;text-align:right;font-variant-numeric:tabular-nums" />
</div>
<span class="admin-toggle-sub" style="display:block;margin-top:6px;opacity:0.5">Set to 0 to disable skill injection.</span>
</div>
<div class="admin-card">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px">
<h2 style="margin:0">Auto-approve skills</h2>
<label class="admin-switch" style="flex-shrink:0"><input type="checkbox" id="auto-approve-skills-toggle" checked /><span class="admin-slider"></span></label>
</div>
<span class="admin-toggle-sub" style="display:block;margin-top:6px;opacity:0.6">Audit all publishes passing, necessary skills at or above this confidence. Off = keep audit results as drafts unless manually approved.</span>
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:6px">
<span class="admin-toggle-sub" style="margin:0">Minimum confidence</span>
<span style="display:flex;align-items:center;gap:8px;flex-shrink:0">
<span id="skill-confidence-label" class="admin-toggle-sub" style="margin:0;min-width:42px;text-align:right;font-variant-numeric:tabular-nums">&ge; 85%</span>
<input type="range" id="skill-confidence-slider" class="preset-range" min="50" max="100" step="5" value="85" style="width:120px;margin:0" />
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Theme Popup (floating panel) -->
<div id="theme-modal" class="modal hidden">
<div id="theme-popup" class="modal-content admin-modal-content" role="dialog" aria-label="Theme" style="background:var(--bg)">
<div class="modal-header theme-popup-header" id="theme-popup-header">
<h4><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><circle cx="12" cy="12" r="10"/><path d="M12 2a7 7 0 0 0 0 20 4 4 0 0 1 0-8 4 4 0 0 0 0-8"/><circle cx="8" cy="9" r="1.5" fill="currentColor"/><circle cx="15" cy="14" r="1.5" fill="currentColor"/><circle cx="9" cy="15" r="1.5" fill="currentColor"/></svg>Theme</h4>
<button type="button" class="theme-opacity-wrap theme-opacity-toggle hidden" id="theme-opacity-wrap" title="Fade this window to preview the page behind it" aria-pressed="false">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
<span class="theme-opacity-label">Peek</span>
</button>
<button class="close-btn" id="close-theme-popup" aria-label="Close theme">&#x2716;</button>
</div>
<!-- Theme tabs -->
<div class="admin-tabs" id="theme-tabs">
<button class="admin-tab active" data-tab="theme-tab-browse"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px"><circle cx="12" cy="12" r="10"/><path d="M12 2a7 7 0 0 0 0 20 4 4 0 0 1 0-8 4 4 0 0 0 0-8"/><circle cx="8" cy="9" r="1.4" fill="currentColor"/><circle cx="15" cy="14" r="1.4" fill="currentColor"/><circle cx="9" cy="15" r="1.4" fill="currentColor"/></svg>Themes</button>
<button class="admin-tab" data-tab="theme-tab-customize"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px"><path d="M9.06 11.9l-3.5 3.5a2.85 2.85 0 1 0 4.03 4.03l8.49-8.49a4.5 4.5 0 1 0-6.36-6.36L3.18 12.62"/><path d="M14 7l3 3"/></svg>Customize</button>
</div>
<!-- Tab: Browse themes -->
<div id="theme-tab-browse" class="theme-tab-panel">
<div class="admin-card">
<h2>Default Themes</h2>
<div class="theme-grid" id="themeGrid"></div>
</div>
<div class="admin-card" id="themeUserCard" style="display:none">
<h2>Your Themes</h2>
<div class="theme-grid" id="themeUserGrid"></div>
</div>
</div>
<!-- Tab: Customize -->
<div id="theme-tab-customize" class="theme-tab-panel" style="display:none">
<div class="admin-card">
<h2>Colors</h2>
<div class="theme-custom" id="themeCustom">
<div class="color-row"><label>Background</label><input type="color" id="clr-bg"><button class="color-reset-btn" data-reset="bg" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
<div class="color-row"><label>Text</label><input type="color" id="clr-fg"><button class="color-reset-btn" data-reset="fg" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
<div class="color-row"><label>Panel</label><input type="color" id="clr-panel"><button class="color-reset-btn" data-reset="panel" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
<div class="color-row"><label>Sidebar</label><input type="color" id="adv-sidebarBg"><button class="color-reset-btn" data-reset-adv="sidebarBg" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
<div class="color-row"><label>Border</label><input type="color" id="clr-border"><button class="color-reset-btn" data-reset="border" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
<div class="color-row"><label>Accent</label><input type="color" id="clr-red"><button class="color-reset-btn" data-reset="red" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
</div>
</div>
<div class="theme-adv-toggle" id="theme-adv-toggle">
<span class="theme-adv-arrow">&#x25B6;</span> More Colors
</div>
<div class="theme-adv-section hidden" id="themeAdvanced">
<div class="theme-adv-group">
<div class="theme-adv-group-label">Chat Bubbles</div>
<div class="theme-custom">
<div class="color-row"><label>User Chat Bubble</label><input type="color" id="adv-userBubbleBg"><button class="color-reset-btn" data-reset-adv="userBubbleBg" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
<div class="color-row"><label>AI Chat Bubble</label><input type="color" id="adv-aiBubbleBg"><button class="color-reset-btn" data-reset-adv="aiBubbleBg" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
<div class="color-row"><label>Border Chat Bubble</label><input type="color" id="adv-bubbleBorder"><button class="color-reset-btn" data-reset-adv="bubbleBorder" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
</div>
</div>
<div class="theme-adv-group">
<div class="theme-adv-group-label">Sidebar</div>
<div class="theme-custom">
<div class="color-row"><label>Odysseus Logo</label><input type="color" id="adv-brandColor"><button class="color-reset-btn" data-reset-adv="brandColor" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
<div class="color-row"><label title="Hamburger menu"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" style="vertical-align:-2px;"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg></label><input type="color" id="adv-hamburgerColor"><button class="color-reset-btn" data-reset-adv="hamburgerColor" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
</div>
</div>
<div class="theme-adv-group">
<div class="theme-adv-group-label">Chat Input / Prompt Area</div>
<div class="theme-custom">
<div class="color-row"><label>Input Bg</label><input type="color" id="adv-inputBg"><button class="color-reset-btn" data-reset-adv="inputBg" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
<div class="color-row"><label>Input Border</label><input type="color" id="adv-inputBorder"><button class="color-reset-btn" data-reset-adv="inputBorder" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
<div class="color-row"><label>Send Btn</label><input type="color" id="adv-sendBtnBg"><button class="color-reset-btn" data-reset-adv="sendBtnBg" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
<div class="color-row"><label>Send Hover</label><input type="color" id="adv-sendBtnHover"><button class="color-reset-btn" data-reset-adv="sendBtnHover" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
</div>
</div>
<div class="theme-adv-group">
<div class="theme-adv-group-label">Code Blocks</div>
<div class="theme-custom">
<div class="color-row"><label>Code Bg</label><input type="color" id="adv-codeBg"><button class="color-reset-btn" data-reset-adv="codeBg" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
<div class="color-row"><label>Code Text</label><input type="color" id="adv-codeFg"><button class="color-reset-btn" data-reset-adv="codeFg" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
</div>
</div>
<div class="theme-adv-group">
<div class="theme-adv-group-label">Controls</div>
<div class="theme-custom">
<div class="color-row"><label>Toggle On</label><input type="color" id="adv-toggleActive"><button class="color-reset-btn" data-reset-adv="toggleActive" title="Reset this color" aria-label="Reset color">&#x21BA;</button></div>
</div>
</div>
<div class="theme-adv-group">
<div class="theme-adv-group-label">Custom Fonts</div>
<div class="theme-fd-label" style="opacity:0.4;">Drop <code>.woff2</code>, <code>.ttf</code>, or <code>.otf</code> files into <code>static/fonts/custom/</code> and reload — they'll appear in the Font dropdown above.</div>
</div>
<button class="theme-adv-clear-btn" id="theme-adv-clear">Clear Advanced Overrides</button>
</div>
<!-- Color Harmony Generator (its own card) -->
<div class="admin-card" id="theme-harmony-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><circle cx="13.5" cy="6.5" r="2.5"/><circle cx="19" cy="13" r="2.5"/><circle cx="6" cy="12" r="2.5"/><circle cx="10" cy="20" r="2.5"/></svg>Color Harmony</h2>
<div class="theme-harmony-row">
<div class="theme-fd-group">
<label class="theme-fd-label">Accent Color</label>
<div class="color-row" style="justify-content:flex-start;gap:8px;">
<input type="color" id="harmony-accent" value="#e06c75">
<span id="harmony-accent-hex" style="font-size:11px;opacity:0.65;font-family:ui-monospace,monospace;letter-spacing:0.02em;">#e06c75</span>
</div>
</div>
<div class="theme-fd-group">
<label class="theme-fd-label">Harmony</label>
<select id="harmony-type" class="theme-fd-select">
<option value="complementary">Complementary</option>
<option value="analogous">Analogous</option>
<option value="triadic">Triadic</option>
<option value="monochromatic">Monochromatic</option>
</select>
</div>
</div>
<div class="theme-harmony-row" style="margin-top:6px;">
<div class="theme-fd-group">
<label class="theme-fd-label">Mode</label>
<select id="harmony-mode" class="theme-fd-select">
<option value="dark">Dark</option>
<option value="light">Light</option>
</select>
</div>
<div class="theme-fd-group" style="justify-content:flex-end;">
<button id="harmony-generate-btn" class="harmony-generate-btn">Generate</button>
</div>
</div>
<div id="harmony-preview" class="harmony-preview"></div>
</div>
<div class="admin-card">
<h2>Font & Layout</h2>
<div class="theme-fd-row">
<div class="theme-fd-group">
<label class="theme-fd-label">Font</label>
<select id="theme-font-select" class="theme-fd-select" aria-label="Font">
<option value="mono">Monospace</option>
<option value="sans">Sans-serif</option>
<option value="serif">Serif</option>
</select>
</div>
<div class="theme-fd-group">
<label class="theme-fd-label">Density</label>
<select id="theme-density-select" class="theme-fd-select" aria-label="Density">
<option value="compact">Compact</option>
<option value="comfortable">Comfortable</option>
<option value="spacious">Spacious</option>
</select>
</div>
<div class="theme-fd-group" id="theme-frosted-group">
<label class="theme-fd-label" for="theme-frosted-toggle">Frosted</label>
<label class="admin-switch" style="margin-top:4px;">
<input type="checkbox" id="theme-frosted-toggle">
<span class="admin-slider"></span>
</label>
</div>
</div>
<div class="theme-fd-row">
<div class="theme-fd-group" style="flex:1 1 0;">
<label class="theme-fd-label">Background / Effect</label>
<select id="theme-bg-pattern-select" class="theme-fd-select">
<option value="none">Solid</option>
<option value="dots">Dots</option>
<option value="synapse">Synapse</option>
<option value="rain">Rain</option>
<option value="constellations">Constellations</option>
<option value="perlin-flow">Perlin Flow</option>
<option value="petals">Petals</option>
<option value="sparkles">Sparkles</option>
<option value="embers">Embers</option>
</select>
</div>
<div class="theme-fd-group" style="flex:0 0 auto;align-items:center;">
<label class="theme-fd-label">&nbsp;</label>
<div class="color-row" style="margin:0;padding:0;">
<input type="color" id="theme-bg-effect-color" value="#9cdef2" title="Effect color">
<button class="color-reset-btn" data-reset-effect title="Reset to text color">&#x21BA;</button>
</div>
</div>
</div>
<div class="theme-fd-row">
<div class="theme-fd-group" id="theme-bg-intensity-group" style="flex:1 1 0;">
<label class="theme-fd-label">Intensity</label>
<input type="range" id="theme-bg-intensity" class="theme-fd-range" min="0" max="100" step="5" value="100">
</div>
<div class="theme-fd-group" id="theme-bg-size-group" style="flex:1 1 0;">
<label class="theme-fd-label">Size</label>
<input type="range" id="theme-bg-size" class="theme-fd-range" min="30" max="250" step="10" value="100">
</div>
</div>
</div>
<div class="admin-card" style="margin-top:8px;">
<h2>Save / Share</h2>
<div class="theme-save-row" id="theme-save-row">
<input type="text" id="theme-save-name" placeholder="Theme name..." maxlength="32">
<button id="theme-save-go">Save</button>
</div>
<div class="theme-save-error" id="theme-save-error"></div>
<div class="theme-io-row" style="margin-top:6px;">
<button id="theme-import-btn" class="theme-io-btn" title="Import a theme from JSON">&#x2912; Import</button>
<button id="theme-export-btn" class="theme-io-btn" title="Export current colors as JSON">&#x2913; Export</button>
</div>
<textarea id="theme-import-area" class="theme-import-area hidden" placeholder="Paste theme JSON here..." rows="3"></textarea>
<div class="theme-import-actions hidden" id="theme-import-actions">
<button id="theme-import-go" class="theme-io-btn">Apply</button>
<button id="theme-import-cancel" class="theme-io-btn">Cancel</button>
</div>
</div>
<button id="theme-reset-btn">Reset to Default</button>
</div>
</div>
</div>
<div id="mobile-backdrop"></div>
<button id="mobile-menu-btn" aria-label="Toggle sidebar">&#x2630;</button>
<button class="hamburger-btn" id="hamburger-btn" title="Show sidebar"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg></button>
<!-- new session button removed — use + in send button instead -->
<div class="icon-rail" id="icon-rail">
<div class="rail-resize-handle" id="rail-resize-handle"></div>
<!-- Static: core actions -->
<button class="icon-rail-btn" id="rail-search-btn" title="Search conversations (Ctrl+K)"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="10" cy="10" r="7"/><path d="M21 21l-4.35-4.35"/></svg></button>
<button class="icon-rail-btn rail-new-chat" id="rail-new-session" title="New chat"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></button>
<button class="icon-rail-btn" id="rail-delete-session" title="Delete session">&#x2715;</button>
<div class="rail-separator"></div>
<!-- Dynamic contextual indicators (shown only while active) -->
<button class="icon-rail-btn rail-dynamic" id="rail-chats" title="Chat ready" style="display:none"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></button>
<button class="icon-rail-btn rail-dynamic" id="rail-documents" title="Documents" style="display:none"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="13" y2="17"/></svg></button>
<!-- Tool launchers — always visible, alphabetical -->
<button class="icon-rail-btn" id="rail-calendar" title="Calendar"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg></button>
<button class="icon-rail-btn" id="rail-compare" title="Compare"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M13 6h3a2 2 0 0 1 2 2v7"/><path d="M11 18H8a2 2 0 0 1-2-2V9"/></svg></button>
<button class="icon-rail-btn" id="rail-cookbook" title="Cookbook"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" style="opacity:0.7"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg></button>
<button class="icon-rail-btn" id="rail-research" title="Deep Research"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg></button>
<button class="icon-rail-btn" id="rail-email" title="Email"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg></button>
<button class="icon-rail-btn" id="rail-gallery" title="Gallery"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg></button>
<button class="icon-rail-btn" id="rail-archive" title="Library"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/><path d="M9 7h6M9 11h4"/></svg></button>
<button class="icon-rail-btn" id="rail-memory" title="Brain"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"/><path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"/></svg></button>
<button class="icon-rail-btn" id="rail-notes" title="Notes"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 3h10l4 4v14H5z"/><path d="M15 3v5h5"/><path d="M8 17.5 15.5 10l2.5 2.5L10.5 20H8z"/></svg></button>
<button class="icon-rail-btn" id="rail-tasks" title="Tasks"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/><path d="M9 16l2 2 4-4"/></svg></button>
<button class="icon-rail-btn" id="rail-theme" title="Theme"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 2a10 10 0 0 0 0 20 5 5 0 0 0 5-5 3 3 0 0 0-3-3h-2a3 3 0 0 1-3-3 5 5 0 0 1 5-5"/></svg></button>
<div style="flex:1"></div>
<!-- Static: bottom -->
<button class="icon-rail-btn" id="rail-settings" title="Settings"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></button>
</div>
<nav class="sidebar" id="sidebar" role="navigation" aria-label="Sidebar">
<div class="sidebar-resize-handle" id="sidebar-resize-handle"></div>
<div class="sidebar-header">
<button class="sidebar-hamburger" id="sidebar-toggle-btn" title="Toggle sidebar"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg></button>
<div class="sidebar-brand" id="sidebar-brand-btn" style="cursor:pointer;" title="New chat">
<span class="sidebar-brand-title">Odysseus</span>
</div>
</div>
<div class="sidebar-inner">
<div class="list-item" id="sidebar-new-chat-btn" title="New chat">
<svg class="sidebar-action-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="position:relative;left:-2px;"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
<span class="grow" style="position:relative;left:-3px;">New Chat</span>
</div>
<div class="list-item" id="sidebar-search-btn" title="Search conversations (Ctrl+K)">
<svg class="sidebar-action-icon" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="10" cy="10" r="7"/><path d="M21 21l-4.35-4.35"/></svg>
<span class="grow" style="position:relative;left:-1px;">Search</span>
</div>
<!-- Assistant sidebar entry removed — its log + settings live inside
the Tasks modal (Activity + Settings tabs). The backend
assistant session is still maintained for log_to_assistant()
writes from scheduled tasks. -->
<div class="section" id="sessions-section">
<div class="section-header-flex">
<span class="section-title" id="chats-section-title"><svg class="section-icon" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg><span id="chats-section-label" class="section-title-label">Chats</span><span id="chats-notif-dot" class="sidebar-notif-dot" style="display:none"></span></span>
<div style="position:relative; display:inline-block; display:flex; gap:4px; align-items:center;">
<button type="button" class="section-header-btn chats-manage-btn" id="chats-library-btn" title="Manage Chats (Library)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/>
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>
<path d="M9 7h6M9 11h4"/>
</svg>
</button>
<button type="button" class="section-header-btn" id="session-sort-btn" title="Sort sessions">
<svg class="sort-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="4" y1="6" x2="20" y2="6"/>
<line x1="4" y1="12" x2="14" y2="12"/>
<line x1="4" y1="18" x2="9" y2="18"/>
</svg>
<svg class="sort-spinner" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" style="display:none; animation: spin 0.7s linear infinite;">
<path d="M12 2a10 10 0 0 1 10 10" />
</svg>
</button>
<div id="session-sort-dropdown" class="dropdown sort-dropdown" style="display:none;">
<div class="dropdown-item sort-option sort-dropdown-item" data-sort="active">Last Active</div>
<div class="dropdown-item sort-option sort-dropdown-item" data-sort="newest">Newest First</div>
<div class="dropdown-item sort-option sort-dropdown-item" data-sort="group">By Folder</div>
<div class="dropdown-item sort-dropdown-item sort-dropdown-sep" id="auto-sort-sessions-row" style="display:flex;align-items:center;padding:0;">
<span id="auto-sort-sessions-btn" style="flex:1;padding:5px 10px;cursor:pointer;display:inline-flex;align-items:center;gap:4px;">
<span class="auto-sort-icon"><svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" style="vertical-align:-1px;margin-right:2px;"><path d="M12 0L14.59 8.41L23 12L14.59 15.59L12 24L9.41 15.59L1 12L9.41 8.41Z"/></svg> Tidy</span>
<span class="auto-sort-spinner" style="display:none;">Sorting...</span>
</span>
<button type="button" id="auto-sort-sessions-more" title="Tidy options" aria-label="Tidy options" style="background:none;border:none;border-left:1px solid var(--border);color:inherit;cursor:pointer;padding:5px 8px;font-size:9px;opacity:0.7;"><svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></button>
</div>
<div class="dropdown-item sort-dropdown-item" id="auto-sort-sessions-noai-btn" style="display:none;padding-left:24px;">
Tidy <span class="auto-sort-noai-spinner" style="display:none;font-size:9px;opacity:0.6;margin-left:4px;">Cleaning...</span>
</div>
<div class="dropdown-item rearrange-toggle sort-dropdown-item sort-dropdown-sep" id="session-rearrange-toggle">
&#8593;&#8595; Rearrange <span class="rearrange-check" style="float:right; opacity:0;">&#x2022;</span>
</div>
<div class="dropdown-item sort-dropdown-item" id="session-select-from-dropdown">
● Select
</div>
</div>
</div>
</div>
<div id="session-bulk-bar" class="session-bulk-bar hidden">
<span id="session-select-all-dot" style="cursor:pointer;font-size:14px;opacity:0.4;user-select:none;"></span><input type="checkbox" id="session-select-all" style="display:none;"><span style="font-size:10px;cursor:pointer;opacity:0.5;" id="session-select-all-label">All</span>
<div style="margin-left:auto;display:flex;gap:2px;align-items:center;">
<button class="session-bulk-btn" id="session-bulk-archive" title="Archive selected"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="5" rx="1"/><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8"/><path d="M10 12h4"/></svg></button>
<button class="session-bulk-btn session-bulk-btn-danger" id="session-bulk-delete" title="Delete selected"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/></svg></button>
<button class="session-bulk-btn" id="session-bulk-cancel" title="Cancel"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>
</div>
</div>
<div id="session-list" role="listbox"></div>
</div>
<!-- Hidden dropdown for session actions -->
<div id="session-actions-dropdown" class="dropdown hidden">
<div class="dropdown-item" id="rename-session-option">
<h4>Rename</h4>
<p>Change the session name</p>
</div>
<div class="dropdown-item" id="delete-session-option">
<h4>Delete</h4>
<p>Remove this session permanently</p>
</div>
<div class="dropdown-item" id="memory-session-option">
<h4>Memory</h4>
<p>Extract memories from this session</p>
</div>
</div>
<div class="section" id="email-section">
<div class="section-header-flex">
<span class="section-title" id="email-section-title" style="cursor:pointer;" title="Open email inbox"><svg class="section-icon" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg><span>Email</span></span>
<button type="button" class="section-header-btn list-item-plus-btn" id="email-compose-btn" title="Compose email">
<svg class="list-item-plus-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
<span class="list-item-plus-label">new</span>
</button>
<!-- Unread dot is a direct header child placed AFTER the compose
button (it used to live inside the title, before the button).
With the title's flex:1 eating the free space, this lands the
dot at the far-right corner with the + just to its left —
i.e. the dot and + swap places when the dot appears. -->
<span id="email-unread-dot" class="sidebar-notif-dot" style="display:none"></span>
</div>
</div>
<div class="section" id="models-section">
<div class="section-header-flex">
<span class="section-title"> Models</span>
<div style="position:relative; display:inline-block;">
<button type="button" class="section-header-btn" id="model-sort-btn" title="Sort models">
<svg class="sort-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="4" y1="6" x2="20" y2="6"/>
<line x1="4" y1="12" x2="14" y2="12"/>
<line x1="4" y1="18" x2="9" y2="18"/>
</svg>
</button>
<div id="model-sort-dropdown" class="dropdown sort-dropdown" style="display:none;">
<div class="dropdown-item sort-option sort-dropdown-item" data-sort="alpha">A-Z</div>
<div class="dropdown-item sort-option sort-dropdown-item" data-sort="last-used">Last used</div>
<div class="dropdown-item sort-option sort-dropdown-item" data-sort="most-used">Most used</div>
<div class="dropdown-item rearrange-toggle sort-dropdown-item sort-dropdown-sep" id="model-rearrange-toggle">
&#8593;&#8595; Rearrange <span class="rearrange-check" style="float:right; opacity:0;">&#x2022;</span>
</div>
</div>
</div>
</div>
<div id="models">
<div class="models-row">
<select id="model-select" aria-label="Select model" style="flex: 1; padding: 6px 8px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--fg);"></select>
<button type="button" id="btn-model-chat" class="model-chat-btn" aria-label="Add model chat" style="transition: all 0.2s ease;"><span class="model-chat-btn-label">+ Chat</span></button>
</div>
</div>
</div>
<div class="section" id="tools-section">
<div class="section-header-flex">
<span class="section-title"><svg class="section-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>Tools</span>
</div>
<div class="list-item" id="tool-memory-btn">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
style="flex-shrink:0;opacity:0.5;">
<path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/>
<path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"/>
<path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"/>
</svg>
<span class="grow">Brain</span>
</div>
<div class="list-item" id="tool-calendar-btn">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
style="flex-shrink:0;opacity:0.5;">
<rect x="3" y="4" width="18" height="18" rx="2"/>
<line x1="16" y1="2" x2="16" y2="6"/>
<line x1="8" y1="2" x2="8" y2="6"/>
<line x1="3" y1="10" x2="21" y2="10"/>
</svg>
<span class="grow">Calendar</span>
</div>
<div class="list-item" id="tool-compare-btn">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;opacity:0.5;"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M13 6h3a2 2 0 0 1 2 2v7"/><path d="M11 18H8a2 2 0 0 1-2-2V9"/></svg>
<span class="grow">Compare</span>
</div>
<div class="list-item" id="tool-cookbook-btn">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"
style="flex-shrink:0;opacity:0.5;">
<path d="M12 7v14"/>
<path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/>
</svg>
<span class="grow">Cookbook</span>
<span id="cookbook-bg-status" style="display:none;font-size:9px;opacity:0.5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-left:6px;flex-shrink:1;min-width:0;position:relative;top:-1px;"></span>
<span class="cookbook-notif-dot" id="cookbook-notif-dot" style="display:none;margin-left:6px;margin-right:4px;position:relative;top:-1px;left:0px;"></span>
</div>
<div class="list-item" id="tool-research-btn">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="flex-shrink:0;opacity:0.5;"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
<span class="grow">Deep Research</span>
</div>
<div class="list-item" id="tool-gallery-btn">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
style="flex-shrink:0;opacity:0.5;">
<rect x="3" y="3" width="18" height="18" rx="2"/>
<circle cx="8.5" cy="8.5" r="1.5"/>
<path d="M21 15l-5-5L5 21"/>
</svg>
<span class="grow">Gallery</span>
</div>
<div class="list-item" id="tool-library-btn">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;opacity:0.5;">
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/><path d="M9 7h6M9 11h4"/>
</svg>
<span class="grow">Library</span>
<button type="button" class="list-item-plus-btn" id="library-new-doc-btn" title="New document">
<svg class="list-item-plus-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="width:11px;height:11px;"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
<span class="list-item-plus-label">new</span>
</button>
</div>
<div class="list-item" id="tool-notes-btn">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
style="flex-shrink:0;opacity:0.5;">
<path d="M5 3h10l4 4v14H5z"/>
<path d="M15 3v5h5"/>
<path d="M8 17.5 15.5 10l2.5 2.5L10.5 20H8z"/>
</svg>
<span class="grow">Notes</span>
</div>
<div class="list-item" id="tool-tasks-btn">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
style="flex-shrink:0;opacity:0.5;">
<rect x="3" y="4" width="18" height="18" rx="2"/>
<line x1="16" y1="2" x2="16" y2="6"/>
<line x1="8" y1="2" x2="8" y2="6"/>
<line x1="3" y1="10" x2="21" y2="10"/>
<path d="M9 16l2 2 4-4"/>
</svg>
<span class="grow">Tasks</span>
<span id="assistant-notif-dot" class="sidebar-notif-dot" style="display:none"></span>
</div>
<div class="list-item" id="tool-theme-btn">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
style="flex-shrink:0;opacity:0.5;">
<circle cx="12" cy="12" r="10"/>
<path d="M12 2a7 7 0 0 0 0 20 4 4 0 0 1 0-8 4 4 0 0 0 0-8"/>
<circle cx="8" cy="9" r="1.5" fill="currentColor"/>
<circle cx="15" cy="14" r="1.5" fill="currentColor"/>
<circle cx="9" cy="15" r="1.5" fill="currentColor"/>
</svg>
<span class="grow">Theme</span>
</div>
</div>
</div>
<div class="sidebar-user-bar" id="sidebar-user-bar">
<div class="user-bar-left" id="user-bar-profile">
<div class="user-bar-avatar" id="user-bar-avatar"></div>
<span class="user-bar-name" id="user-bar-name">User</span>
</div>
<div class="user-bar-actions">
<button type="button" class="user-bar-btn" id="user-bar-settings" title="Settings">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
</button>
</div>
</div>
</nav>
<main class="chat-container welcome-active" id="chat-container" aria-label="Chat area" aria-busy="false">
<!-- Persistent page heading for assistive tech. Visually hidden so it
never affects layout, but always present inside the main landmark
(the sidebar that shows the visible brand is hidden off-canvas on
mobile) so the page always exposes a single level-1 heading. -->
<h1 class="a11y-visually-hidden">Odysseus</h1>
<div class="chat-top-bar">
<button type="button" class="incognito-indicator" id="incognito-indicator" title="Nobody mode active — click to deactivate" style="display:none;"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><line x1="8" y1="16" x2="16" y2="8"/><line x1="8" y1="8" x2="16" y2="16"/></svg></button>
<div class="chat-meta-overlay"><span id="current-meta">Odysseus Chat</span><span id="current-meta-count" class="chat-meta-count" aria-hidden="true"></span><span id="session-cost-display" class="session-cost-display" style="display:none;"></span><span class="export-dropdown-wrap" id="export-dropdown-wrap"><button type="button" class="export-dl-btn" id="export-dl-btn" title="More"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></button><div class="export-dropdown-menu" id="export-dropdown-menu"><div class="export-dropdown-item" id="export-rename-btn"><span class="dropdown-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.83 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg></span><span>Rename</span></div><div class="export-dropdown-item" id="export-copy-btn"><span class="dropdown-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></span><span>Copy Chat</span></div><div class="export-dropdown-item" id="export-pdf-btn"><span class="dropdown-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><path d="M9 15v-2h2a1.5 1.5 0 0 1 0 3H9z"/></svg></span><span>PDF</span></div><div class="export-dropdown-item" id="export-doc-btn"><span class="dropdown-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg></span><span>Save to Documents</span></div></div></span></div> </div>
<div id="welcome-screen">
<div class="welcome-name"><svg class="welcome-boat" viewBox="0 0 32 32"><path d="M16 4L16 22L6 22Z" fill="currentColor"/><path d="M16 8L16 22L24 22Z" fill="currentColor" opacity="0.6"/><path d="M4 24Q10 20 16 24Q22 28 28 24" stroke="currentColor" stroke-width="2.5" fill="none" stroke-linecap="round"/></svg>Odysseus</div>
<div class="welcome-sub" id="welcome-sub">Welcome, <span class="setup-trigger-link" style="color:var(--accent,var(--red));font-weight:600;cursor:pointer;text-decoration:underline;" title="Click to launch setup">type /setup</span> to get started.</div>
<div class="welcome-tip" id="welcome-tip"></div>
<button type="button" class="incognito-btn" id="incognito-btn" title="Enable Nobody mode — no memory, no history saved">
<svg class="eye-open" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>
</svg>
<svg class="eye-blinded" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><line x1="8" y1="16" x2="16" y2="8"/><line x1="8" y1="8" x2="16" y2="16"/>
</svg>
<span class="incognito-label">Nobody</span>
</button>
<div id="welcome-setup" style="display:none"></div>
</div>
<script nonce="{{CSP_NONCE}}">
(function(){
var mobile = window.matchMedia('(max-width: 768px)').matches;
var desktop = [
'Tip: Press Ctrl+K to search across all your conversations.',
'Tip: Press Ctrl+B to quickly toggle the sidebar.',
'Tip: Shift-click the sidebar toggle to swap it to the other side.',
'Tip: Drag and drop files onto the chat to attach them.',
'Tip: Right-click a session for rename, delete, and memory options.',
];
var phone = [
'Tip: Long-press a session for rename, delete, and memory options.',
'Tip: Tap the eye icon for Nobody mode — no history saved.',
'Tip: Switch to Agent mode for web search and code execution.',
'Tip: Use Compare mode to test different models side by side.',
'Tip: Attach images or files using the + button next to the input.',
];
var tips = mobile ? phone : desktop;
var el = document.getElementById('welcome-tip');
if (el) {
el.textContent = 'Type /setup, then choose Local models or API.';
}
fetch('/api/version').then(function(r){return r.json()}).then(function(d){
if (d.version) window._appVersion = d.version;
}).catch(function(){});
})();
</script>
<div id="chat-history" class="chat-history" role="log" aria-live="polite"></div>
<!-- Attachments strip -->
<div id="attach-strip" class="attach-strip"></div>
<!-- Hidden elements for form logic -->
<input type="checkbox" id="research-toggle" style="display:none;">
<input type="checkbox" id="rag-toggle" style="display:none;">
<input type="checkbox" id="incognito-toggle" style="display:none;">
<input type="file" id="file-input" class="hidden" multiple />
<!-- Unified chat input bar -->
<div class="chat-input-bar">
<div class="chat-input-top">
<div id="message-ghost" class="ghost-text-overlay" aria-hidden="true"></div>
<textarea id="message" placeholder="Message Odysseus..." required autocomplete="off" aria-label="Message input" rows="1" autofocus></textarea>
<!-- Model picker (inside chatbox, top-right) -->
<div class="model-picker-wrap" id="model-picker-wrap">
<button type="button" class="model-picker-btn" id="model-picker-btn" title="Switch model"><span id="model-picker-label">Select model</span> <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 15 12 9 18 15"/></svg></button>
<div class="model-picker-menu hidden" id="model-picker-menu">
<div class="model-picker-search-row">
<input type="text" id="model-picker-search" placeholder="Search models..." autocomplete="off" aria-label="Search models">
<button type="button" class="model-picker-action-btn primary" id="model-picker-add-models-btn" title="Add model endpoints" aria-label="Add model endpoints">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
</button>
</div>
<div class="model-picker-list" id="model-picker-list"></div>
</div>
</div>
</div>
<div id="pinned-tools-bar"></div>
<div class="chat-input-bottom" style="visibility:hidden">
<div class="chat-input-left">
<!-- Overflow menu (+) — always first/left -->
<div class="overflow-wrapper">
<button type="button" class="input-icon-btn overflow-plus-btn" id="overflow-plus-btn" title="More tools" aria-label="More tools" aria-haspopup="true">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 15 12 9 18 15"/>
</svg>
<span class="plus-active-dot"></span>
</button>
<div id="overflow-menu" class="overflow-menu hidden">
<button type="button" class="overflow-menu-item" id="overflow-attach-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
<span>Attach files</span>
</button>
<button type="button" class="overflow-menu-item" id="overflow-doc-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>
</svg>
<span>Documents</span>
</button>
<button type="button" class="overflow-menu-item" id="overflow-rag-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>
</svg>
<span>RAG</span>
<span class="overflow-active-dot"></span>
</button>
<!-- Inline "deep research mode" toggle removed (superseded by the
Deep Research sidebar / trigger_research). The hidden
#research-toggle checkbox is kept inert so existing JS refs
don't break; without this entry point it can't be enabled. -->
<!-- Group Chat moved to Characters modal Group tab -->
<!-- TTS Mode hidden — read-aloud feature is off in this build. -->
<button type="button" class="overflow-menu-item" id="overflow-tts-btn" hidden style="display:none">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>
<span>TTS Mode</span>
<span class="overflow-active-dot"></span>
</button>
<button type="button" class="overflow-menu-item" id="overflow-preset-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m18 2 4 4"/><path d="m17 7 3-3"/><path d="M19 9 8.7 19.3c-1 1-2.5 1-3.4 0l-.6-.6c-1-1-1-2.5 0-3.4L15 5"/><path d="m9 11 4 4"/><path d="m5 19-3 3"/><path d="m14 4 6 6"/>
</svg>
<span>Prompt</span>
</button>
</div>
</div>
<!-- Web search (magnifying glass) -->
<button type="button" class="input-icon-btn" title="Web search" id="web-toggle-btn" data-mode-tool="true" aria-label="Web search" aria-pressed="false">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
</button>
<!-- Shell commands (terminal) -->
<button type="button" class="input-icon-btn" title="Shell Access" id="bash-toggle-btn" data-mode-tool="true" aria-label="Shell access" aria-pressed="false">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/>
</svg>
</button>
<!-- RAG toolbar indicator (hidden until active) -->
<button type="button" class="input-icon-btn tool-indicator" title="RAG active — click to deactivate" id="rag-indicator-btn" style="display:none;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>
</svg>
<span style="font-size:11px;margin-left:2px;">RAG</span>
<svg class="tool-indicator-x" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg>
</button>
<!-- 6. Deep Research (hidden until active) -->
<button type="button" class="input-icon-btn tool-indicator" title="Deep Research active — click to deactivate" id="research-toggle-btn" style="display:none;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
<span style="font-size:11px;margin-left:2px;">Research</span>
<svg class="tool-indicator-x" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg>
</button>
<!-- 7. Group Chat (hidden until active) -->
<button type="button" class="input-icon-btn tool-indicator" title="Group Chat active — click to deactivate" id="group-toggle-btn" style="display:none;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
<span style="font-size:11px;margin-left:2px;">Group</span>
<svg class="tool-indicator-x" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg>
</button>
<input type="checkbox" id="group-toggle" style="display:none;">
<!-- Character indicator (hidden until active) -->
<button type="button" class="input-icon-btn tool-indicator" title="Persona active — click to deactivate" id="character-indicator-btn" style="display:none;">
<svg id="char-indicator-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<span id="character-indicator-name" style="font-size:11px;margin-left:2px;max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"></span>
<svg class="tool-indicator-x" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg>
</button>
<!-- Compare toolbar indicator (hidden until active) -->
<button type="button" class="input-icon-btn tool-indicator" title="Compare active — click to deactivate" id="compare-indicator-btn" style="display:none;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M13 6h3a2 2 0 0 1 2 2v7"/><path d="M11 18H8a2 2 0 0 1-2-2V9"/></svg>
<span style="font-size:11px;margin-left:2px;">Compare</span>
<svg class="tool-indicator-x" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg>
</button>
</div>
<div class="chat-input-right">
<!-- Agent / Chat mode toggle -->
<div class="mode-toggle">
<button type="button" class="mode-toggle-btn active" id="mode-agent-btn" aria-pressed="true">Agent</button>
<button type="button" class="mode-toggle-btn" id="mode-chat-btn" aria-pressed="false">Chat</button>
</div>
<button type="submit" form="chat-form" class="send-btn newchat-mode" data-mode="newchat" aria-label="New chat">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg><span class="send-btn-label">+ New</span>
</button>
</div>
</div>
<!-- Hidden checkboxes for state -->
<input type="checkbox" id="web-toggle" style="display:none;">
<input type="checkbox" id="bash-toggle" style="display:none;">
</div>
<form id="chat-form" autocomplete="off" action="javascript:void(0);" style="display:none;"></form>
<!-- Character (custom preset) modal -->
<div id="custom-preset-modal" class="modal hidden">
<div class="modal-content preset-modal-content" role="dialog" aria-label="Prompt" style="background:var(--bg)">
<div class="modal-header">
<h4><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><path d="m18 2 4 4"/><path d="m17 7 3-3"/><path d="M19 9 8.7 19.3c-1 1-2.5 1-3.4 0l-.6-.6c-1-1-1-2.5 0-3.4L15 5"/><path d="m9 11 4 4"/><path d="m5 19-3 3"/><path d="m14 4 6 6"/></svg>Prompt</h4>
<button class="close-btn" id="close-custom-preset" aria-label="Close prompt"></button>
</div>
<div class="modal-body preset-modal-body">
<div id="char-fields-wrap">
<div class="preset-tabs">
<button class="preset-tab active" data-chartab="inject"><svg class="preset-tab-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 2 4 4"/><path d="m17 7 3-3"/><path d="M19 9 8.7 19.3c-1 1-2.5 1-3.4 0l-.6-.6c-1-1-1-2.5 0-3.4L15 5"/><path d="m9 11 4 4"/><path d="m5 19-3 3"/><path d="m14 4 6 6"/></svg><span>Inject</span></button>
<button class="preset-tab" data-chartab="character"><svg class="preset-tab-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg><span>Persona</span></button>
<button class="preset-tab" data-chartab="group"><svg class="preset-tab-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg><span>Group</span></button>
</div>
<!-- Inject tab (also holds model tuning: temperature + max tokens) -->
<div class="preset-chartab" data-chartab-panel="inject">
<label for="inject-prefix">Prefix</label>
<textarea id="inject-prefix" rows="2" placeholder="Added before your message" style="margin-bottom:8px"></textarea>
<label for="inject-suffix">Suffix</label>
<textarea id="inject-suffix" rows="2" placeholder="Added after your message" style="margin-bottom:12px"></textarea>
<div class="preset-slider-row">
<label>Temperature <span class="preset-hint-icon" title="Controls randomness. Lower values give focused, deterministic answers (good for code). Higher values give more creative, varied responses.">?</span></label>
<span class="preset-slider-value" id="temp-value">1.0</span>
</div>
<input type="range" class="preset-range" id="custom-temperature" min="0" max="2" step="0.1" value="1.0">
<div class="preset-temp-hints">
<span>Precise / Code</span>
<span>Balanced</span>
<span>Creative</span>
</div>
<div class="preset-slider-row">
<label>Max Tokens <span class="preset-hint-icon" title="Maximum length of the AI response. 'No limit' lets the model decide when to stop.">?</span></label>
<span class="preset-slider-value" id="tokens-value">No limit</span>
</div>
<input type="range" class="preset-range" id="custom-max-tokens" min="256" max="8448" step="256" value="8448">
</div>
<!-- Prompt (character/persona) tab -->
<div class="preset-chartab" data-chartab-panel="character" style="display:none">
<label>Persona</label>
<div class="char-name-combo">
<select id="char-template-select" class="char-template-select">
<option value="">Select persona...</option>
</select>
<button type="button" id="char-new-btn" class="char-action-btn" title="Create a new persona">+ New</button>
</div>
<div id="char-name-row">
<label for="custom-character-name">Name</label>
<div class="char-name-combo">
<input type="text" id="custom-character-name" maxlength="50" placeholder="Give your persona a name..." autocomplete="off" style="flex:1">
<button type="button" id="char-delete-template-btn" class="char-action-btn" title="Delete this persona and its memories" style="display:none;margin-top:-6px !important"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>Delete</button>
<button type="button" id="reset-character-btn" class="char-action-btn" title="Reset to default" style="margin-top:-6px !important">&#x21BA; Reset</button>
</div>
</div>
<label for="custom-system-prompt">System prompt</label>
<div class="char-prompt-wrap">
<textarea id="custom-system-prompt" rows="4" placeholder="Write rough notes and click Expand, or leave empty"></textarea>
<button type="button" id="char-expand-btn" class="char-expand-btn" title="AI expand — turn your notes into a full system prompt">
<svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" style="vertical-align:-1px;margin-right:2px;"><path d="M12 0L14.59 8.41L23 12L14.59 15.59L12 24L9.41 15.59L1 12L9.41 8.41Z"/></svg>
Expand
</button>
</div>
</div>
<!-- Group tab -->
<div class="preset-chartab" data-chartab-panel="group" style="display:none">
<div style="display:flex;gap:4px;margin-bottom:8px;">
<button type="button" class="compare-parallel-toggle" id="group-mode-btn" style="flex:1;height:auto;width:auto;">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="8" y1="6" x2="20" y2="6"/><line x1="8" y1="12" x2="20" y2="12"/><line x1="8" y1="18" x2="20" y2="18"/><circle cx="4" cy="6" r="1.5" fill="currentColor"/><circle cx="4" cy="12" r="1.5" fill="currentColor"/><circle cx="4" cy="18" r="1.5" fill="currentColor"/></svg>
<span class="compare-toggle-label">Sequential</span>
</button>
</div>
<div id="group-participants" style="display:flex;flex-direction:column;gap:4px;margin-bottom:0;max-height:220px;overflow-y:auto;"></div>
<button type="button" id="group-add-btn" class="preset-save-btn" style="width:100%;background:none;border:1px dashed var(--border);color:var(--fg);opacity:0.6;font-size:11px;padding:4px;margin-top:0;">+ Add participant</button>
</div>
</div>
</div>
<div class="modal-footer">
<div style="flex:1"></div>
<button type="button" id="cancel-custom-preset" style="margin-right:8px;display:none;">Cancel</button>
<button type="button" id="save-custom-preset">Start</button>
</div>
</div>
</div>
</main>
<button id="scroll-bottom-btn" class="scroll-nav-btn" title="Scroll to bottom"></button>
<script nonce="{{CSP_NONCE}}">
(function(){
const container = document.getElementById('chat-history');
const chatBar = document.querySelector('.chat-input-bar');
const bottomBtn = document.getElementById('scroll-bottom-btn');
if (!container || !chatBar || !bottomBtn) return;
function reposition() {
const barRect = chatBar.getBoundingClientRect();
bottomBtn.style.bottom = (window.innerHeight - barRect.top + 16) + 'px';
bottomBtn.style.right = (window.innerWidth - barRect.right + 8) + 'px';
}
function update() {
reposition();
const scrollable = container.scrollHeight > container.clientHeight + 10;
const atBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 300;
if (scrollable && !atBottom) {
bottomBtn.classList.remove('slide-out');
bottomBtn.classList.add('show');
} else if (bottomBtn.classList.contains('show')) {
bottomBtn.classList.add('slide-out');
bottomBtn.classList.remove('show');
}
}
let _scrollRaf = null;
let _scrollTimeout = null;
let _scrollingToBottom = false;
function cancelAutoScroll() {
if (_scrollRaf) { cancelAnimationFrame(_scrollRaf); _scrollRaf = null; }
if (_scrollTimeout) { clearTimeout(_scrollTimeout); _scrollTimeout = null; }
_scrollingToBottom = false;
}
container.addEventListener('scroll', update, {passive:true});
container.addEventListener('wheel', () => { if (_scrollingToBottom) cancelAutoScroll(); }, {passive:true});
container.addEventListener('touchstart', () => { if (_scrollingToBottom) cancelAutoScroll(); }, {passive:true});
bottomBtn.addEventListener('click', () => {
cancelAutoScroll();
const target = container.scrollHeight - container.clientHeight;
_scrollingToBottom = true;
function step() {
if (!_scrollingToBottom) return;
const diff = target - container.scrollTop;
if (diff <= 8) { container.scrollTop = target; _scrollingToBottom = false; return; }
container.scrollTop += diff * 0.2;
_scrollRaf = requestAnimationFrame(step);
}
step();
_scrollTimeout = setTimeout(() => { if (_scrollingToBottom) { container.scrollTop = target; _scrollingToBottom = false; } }, 1500);
});
new ResizeObserver(reposition).observe(chatBar);
new MutationObserver(update).observe(container, {childList:true, subtree:true});
window.addEventListener('resize', reposition);
update();
})();
</script>
<!-- Rename Session Modal -->
<div id="rename-session-modal" class="modal hidden">
<div class="modal-content" role="dialog" aria-label="Rename session" style="width: 400px;">
<div class="modal-header">
<h4>Rename Session</h4>
<button class="close-btn" id="close-rename-session" aria-label="Close rename session modal"></button>
</div>
<div class="modal-body">
<div style="margin-bottom: 12px;">
<label for="session-name-input" style="display: block; margin-bottom: 6px; font-weight: 500;">Session Name</label>
<input
type="text"
id="session-name-input"
placeholder="Enter session name"
style="width: 100%; padding: 8px; border-radius: 4px;"
/>
</div>
<div style="display: flex; justify-content: flex-end; gap: 8px;">
<button id="cancel-rename-session" style="padding: 6px 12px;">Cancel</button>
<button id="save-session-name" style="padding: 6px 12px; background: var(--fg); color: var(--bg);">Save</button>
</div>
</div>
</div>
</div>
<!-- Cookbook Modal -->
<div id="cookbook-modal" class="modal hidden">
<div class="modal-content" role="dialog" aria-label="Cookbook" style="width: min(780px, 92vw); height: 94vh; max-height: 94vh; background: var(--bg);">
<div class="modal-header">
<h4 style="margin:0;margin-right:auto"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg>Cookbook</h4>
<button class="close-btn" id="close-cookbook-modal" aria-label="Close cookbook"></button>
</div>
<div class="modal-body cookbook-body"></div>
</div>
</div>
<!-- Settings Modal (all users) -->
<div id="settings-modal" class="modal hidden">
<div class="modal-content settings-modal-content" role="dialog" aria-label="Settings">
<div class="modal-header">
<h4><span style="vertical-align:-1px;margin-right:6px;font-size:15px">&#x2699;</span>Settings</h4>
<button type="button" class="theme-opacity-wrap theme-opacity-toggle hidden" id="settings-opacity-wrap" title="Fade this window to preview the page behind it" aria-pressed="false">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
<span class="theme-opacity-label">Peek</span>
</button>
<button class="close-btn" aria-label="Close settings"></button>
</div>
<div class="admin-toggle-sub" style="padding:0 12px 8px;opacity:0.6;font-size:11px;">Toggle on/off visibility of tools and modules across the interface.</div>
<div class="settings-layout">
<div class="settings-sidebar">
<!-- Section 1: AI plumbing (Add Models → AI Defaults → Search) -->
<button class="settings-nav-item active" data-settings-tab="services">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/><circle cx="6" cy="6" r="1"/><circle cx="6" cy="18" r="1"/></svg>
<span>Add Models</span>
</button>
<button class="settings-nav-item" data-settings-tab="ai">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a4 4 0 0 0-4 4v2H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2h-2V6a4 4 0 0 0-4-4z"/></svg>
<span>AI Defaults</span>
</button>
<button class="settings-nav-item" data-settings-tab="search">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<span>Search</span>
</button>
<div class="settings-sidebar-divider"></div>
<!-- Section 2: Comms (Integrations → Email → Reminders).
Integrations leads here because it's where the underlying
accounts the other two tabs depend on actually live. -->
<button class="settings-nav-item" data-settings-tab="integrations">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
<span>Integrations</span>
</button>
<button class="settings-nav-item" data-settings-tab="email">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>
<span>Email</span>
</button>
<button class="settings-nav-item" data-settings-tab="reminders">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
<span>Reminders</span>
</button>
<div class="settings-sidebar-divider"></div>
<!-- Section 3: UX (Appearance → Shortcuts) -->
<button class="settings-nav-item" data-settings-tab="appearance">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 2a7 7 0 0 0 0 20 4 4 0 0 1 0-8 4 4 0 0 0 0-8"/></svg>
<span>Appearance</span>
</button>
<button class="settings-nav-item" data-settings-tab="shortcuts">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.01M10 8h.01M14 8h.01M18 8h.01M8 12h.01M12 12h.01M16 12h.01M7 16h10"/></svg>
<span>Shortcuts</span>
</button>
<div class="settings-sidebar-divider"></div>
<!-- Section 4: Account (sits at the bottom of the user section) -->
<button class="settings-nav-item" data-settings-tab="account">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<span>Account</span>
</button>
<div class="settings-sidebar-divider admin-only"></div>
<div class="settings-sidebar-label admin-only">Admin</div>
<button class="settings-nav-item admin-only" data-settings-tab="tools">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>
<span>Agent Tools</span>
</button>
<button class="settings-nav-item admin-only" data-settings-tab="users">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
<span>Users</span>
</button>
<button class="settings-nav-item admin-only" data-settings-tab="system">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
<span>System</span>
</button>
</div>
<div class="settings-panels">
<!-- ═══ AI TAB ═══ -->
<div data-settings-panel="ai" class="hidden">
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>Default Chat Model</h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">The model used when creating a new chat session.</div>
<div class="settings-col">
<div class="settings-row">
<label class="settings-label">Endpoint</label>
<select id="set-defaultEpSelect" class="settings-select"></select>
</div>
<div class="settings-row">
<label class="settings-label">Model</label>
<select id="set-defaultModelSelect" class="settings-select"></select>
</div>
<div id="set-defaultFallbacks" class="settings-fallbacks"></div>
<button type="button" class="settings-fallback-add" id="set-defaultAddFallback" title="Add a model to try if the one above fails">+ Add fallback</button>
<div id="set-defaultChatMsg" style="font-size:11px;color:color-mix(in srgb, var(--fg) 45%, transparent);"></div>
</div>
</div>
<div class="admin-card">
<h2 style="display:flex;align-items:center;gap:6px;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:1px;opacity:0.6;flex-shrink:0"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>Utility Model <span style="font-size:0.72em;opacity:0.55;font-weight:normal;">(Recommended: Local Endpoint)</span></h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">Runs background tasks (compaction, cleanup, auto-naming, retrieving memories from files) on a small/local model instead of your chat model. Leave blank to use the chat model.</div>
<div class="settings-col">
<div class="settings-row">
<label class="settings-label">Endpoint</label>
<select id="set-utilityEpSelect" class="settings-select"><option value=""></option></select>
</div>
<div class="settings-row">
<label class="settings-label">Model</label>
<select id="set-utilityModelSelect" class="settings-select"><option value=""></option></select>
</div>
<div id="set-utilityFallbacks" class="settings-fallbacks"></div>
<button type="button" class="settings-fallback-add" id="set-utilityAddFallback" title="Add a model to try if the utility model fails">+ Add fallback</button>
<div id="set-utilityChatMsg" style="font-size:11px;color:color-mix(in srgb, var(--fg) 45%, transparent);"></div>
</div>
</div>
<div class="admin-card">
<h2 style="display:flex;align-items:center;gap:6px;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:1px;opacity:0.6;flex-shrink:0"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>Vision<span style="flex:1"></span><label class="admin-switch" title="Analyze images with a vision-capable model"><input type="checkbox" id="set-visionEnabledToggle" checked><span class="admin-slider"></span></label></h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">Analyze images with a vision-capable model.</div>
<div style="display:flex;flex-direction:column;gap:0.5rem;">
<div style="display:flex;align-items:center;gap:0.75rem;">
<label class="settings-label">Model</label>
<select id="set-vlModelSelect" class="settings-select"><option value="">Auto-detect</option></select>
</div>
<div id="set-visionFallbacks" class="settings-fallbacks"></div>
<button type="button" class="settings-fallback-add" id="set-visionAddFallback" title="Add a vision model to try if the one above fails">+ Add fallback</button>
<div id="set-visionSettingsMsg" style="font-size:11px;color:color-mix(in srgb, var(--fg) 45%, transparent);"></div>
</div>
</div>
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>Research Model</h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">Model used for Deep Research. Falls back to the default chat model if not set.</div>
<div class="settings-col">
<div class="settings-row">
<label class="settings-label">Endpoint</label>
<select id="set-researchEndpoint" class="settings-select">
<option value="">Same as chat</option>
</select>
</div>
<div class="settings-row">
<label class="settings-label">Model</label>
<select id="set-researchModel" class="settings-select">
<option value="">Same as chat</option>
</select>
</div>
<div class="settings-row">
<label class="settings-label">Search</label>
<select id="set-researchSearch" class="settings-select">
<option value="">Same as web search</option>
<option value="searxng">SearXNG</option>
<option value="duckduckgo">DuckDuckGo</option>
<option value="tavily">Tavily</option>
<option value="brave">Brave</option>
<option value="google">Google</option>
<option value="serper">Serper</option>
</select>
</div>
<div class="settings-row">
<label class="settings-label">Max Tokens</label>
<input id="set-researchMaxTokens" type="text" inputmode="numeric" placeholder="8192 (default)" class="settings-select" style="width:120px;">
</div>
<div class="settings-row">
<label class="settings-label">Extract Timeout</label>
<input id="set-researchExtractTimeout" type="text" inputmode="numeric" placeholder="90 sec" class="settings-select" style="width:120px;">
</div>
<div class="settings-row">
<label class="settings-label">Extract Parallel</label>
<input id="set-researchExtractConcurrency" type="text" inputmode="numeric" placeholder="3" class="settings-select" style="width:120px;">
</div>
<div id="set-researchMsg" style="font-size:11px;color:color-mix(in srgb, var(--fg) 45%, transparent);"></div>
</div>
</div>
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>Agent</h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">Controls for the agent tool loop.</div>
<div class="settings-col">
<div class="settings-row">
<label class="settings-label">Tool call limit</label>
<input id="set-agentMaxTools" type="text" inputmode="numeric" placeholder="0 = unlimited" class="settings-select" style="width:120px;">
</div>
<div id="set-agentMsg" style="font-size:11px;color:color-mix(in srgb, var(--fg) 45%, transparent);"></div>
</div>
</div>
<!-- Image Generation removed — only inpaint remains in this build,
and inpaint is configured via the gallery editor not this card.
Keeping the DOM (hidden) so JS wiring against the inputs
doesn't throw. -->
<div class="admin-card" hidden style="display:none">
<h2 style="display:flex;align-items:center;gap:6px;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:1px;opacity:0.6;flex-shrink:0"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>Image Generation<span style="flex:1"></span><label class="admin-switch"><input type="checkbox" id="set-imgEnabledToggle" checked><span class="admin-slider"></span></label></h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">Configure which model to use for image generation.</div>
<div style="display:flex;flex-direction:column;gap:0.5rem;">
<div style="display:flex;align-items:center;gap:0.75rem;">
<label class="settings-label">Model</label>
<select id="set-imgModelSelect" class="settings-select"><option value="">Auto-detect</option></select>
</div>
<div style="display:flex;align-items:center;gap:0.75rem;">
<label class="settings-label">Quality</label>
<select id="set-imgQualitySelect" class="settings-select">
<option value="low">Low (fastest, cheapest)</option>
<option value="medium">Medium (default)</option>
<option value="high">High (best quality)</option>
</select>
</div>
<div id="set-imgSettingsMsg" style="font-size:11px;color:color-mix(in srgb, var(--fg) 45%, transparent);"></div>
</div>
</div>
<!-- TTS panel hidden — user opted out of read-aloud entirely. Kept
in DOM so the JS that wires set-ttsProviderSelect etc. doesn't
throw on missing nodes; not visible to anyone. -->
<div class="admin-card" hidden style="display:none">
<h2 style="display:flex;align-items:center;gap:6px;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>Text to Speech<span style="flex:1"></span><label class="admin-switch"><input type="checkbox" id="set-ttsEnabledToggle" checked><span class="admin-slider"></span></label></h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">Configure TTS provider for assistant message read-aloud.</div>
<div style="display:flex;flex-direction:column;gap:0.5rem;">
<div style="display:flex;align-items:center;gap:0.75rem;">
<label class="settings-label">Provider</label>
<select id="set-ttsProviderSelect" class="settings-select">
<option value="disabled">Disabled</option>
<option value="browser">Browser (built-in)</option>
<option value="local">Local (Kokoro-82M)</option>
</select>
</div>
<div id="set-ttsModelRow" style="display:flex;align-items:center;gap:0.75rem;">
<label class="settings-label">Model</label>
<select id="set-ttsModelSelect" class="settings-select">
<option value="tts-1">tts-1 (fast)</option>
<option value="tts-1-hd">tts-1-hd (quality)</option>
<option value="gpt-4o-mini-tts">gpt-4o-mini-tts (steerable)</option>
</select>
<input id="set-ttsModelInput" type="text" placeholder="model name" style="flex:1;padding:5px;display:none;">
</div>
<div id="set-ttsVoiceRow" style="display:flex;align-items:center;gap:0.75rem;">
<label class="settings-label">Voice</label>
<select id="set-ttsVoiceSelect" class="settings-select">
<option value="alloy">Alloy</option>
<option value="ash">Ash</option>
<option value="coral">Coral</option>
<option value="echo">Echo</option>
<option value="fable">Fable</option>
<option value="nova">Nova</option>
<option value="onyx">Onyx</option>
<option value="sage">Sage</option>
<option value="shimmer">Shimmer</option>
</select>
<input id="set-ttsVoiceInput" type="text" placeholder="af_heart" style="flex:1;padding:5px;display:none;">
</div>
<div id="set-ttsSpeedRow" style="display:flex;align-items:center;gap:0.75rem;">
<label class="settings-label">Speed</label>
<select id="set-ttsSpeedSelect" class="settings-select">
<option value="0.5">0.5x</option>
<option value="0.75">0.75x</option>
<option value="1" selected>1x (normal)</option>
<option value="1.25">1.25x</option>
<option value="1.5">1.5x</option>
<option value="2">2x</option>
</select>
</div>
<button type="button" id="set-ttsPreviewBtn" class="admin-btn-sm" style="margin-top:4px;padding:5px 16px;font-size:12px;">Preview</button>
<div id="set-ttsSettingsMsg" style="font-size:11px;color:color-mix(in srgb, var(--fg) 45%, transparent);"></div>
</div>
</div>
<div class="admin-card">
<h2 style="display:flex;align-items:center;gap:6px;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M22 10v6M2 10l10-5 10 5-10 5z"/><path d="M6 12v5c3 3 9 3 12 0v-5"/></svg>Teacher Model <span style="font-size:0.72em;opacity:0.55;font-weight:normal;">(Experimental)</span><span style="flex:1"></span><label class="admin-switch"><input type="checkbox" id="set-teacherEnabledToggle"><span class="admin-slider"></span></label></h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">When a self-hosted student fails an agent-mode task, escalate to a SOTA teacher that writes a SKILL.md procedure so the student can do it next time. Off by default.</div>
<div class="settings-col">
<div class="settings-row">
<label class="settings-label">Endpoint</label>
<select id="set-teacherEpSelect" class="settings-select"><option value=""></option></select>
</div>
<div class="settings-row">
<label class="settings-label">Model</label>
<select id="set-teacherModelSelect" class="settings-select"><option value=""></option></select>
</div>
<div id="set-teacherChatMsg" style="font-size:11px;color:color-mix(in srgb, var(--fg) 45%, transparent);"></div>
</div>
</div>
</div>
<!-- ═══ SEARCH TAB ═══ -->
<div data-settings-panel="search" class="hidden">
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>Web Search</h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">Search API used for web search and deep research.</div>
<div class="settings-col">
<div class="settings-row">
<label class="settings-label">Provider</label>
<!-- Custom picker (with logos). Hidden native <select> mirrors
its value so the existing JS that reads
set-searchProvider keeps working unchanged. -->
<div class="adm-provider-picker search-provider-picker" id="search-provider-picker" style="flex:1;position:relative;top:5px;">
<button type="button" class="adm-provider-btn" id="search-provider-btn">
<span class="adm-provider-current"><span class="adm-provider-logo"></span><span class="adm-provider-name">SearXNG</span></span>
<svg class="adm-provider-caret" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<div class="adm-provider-menu hidden" id="search-provider-menu"></div>
</div>
<select id="set-searchProvider" class="settings-select" style="display:none;">
<option value="searxng" data-search-logo="searxng">SearXNG (self-hosted)</option>
<option value="duckduckgo" data-search-logo="duckduckgo">DuckDuckGo (free, no key)</option>
<option value="brave" data-search-logo="brave">Brave Search</option>
<option value="google_pse" data-search-logo="google_pse">Google PSE</option>
<option value="tavily" data-search-logo="tavily">Tavily</option>
<option value="serper" data-search-logo="serper">Serper.dev</option>
<option value="disabled" data-search-logo="disabled">Disabled</option>
</select>
<button type="button" class="admin-btn-sm" id="set-searchTestBtn" title="Run a test query against the configured provider" style="margin-left:6px;flex-shrink:0;position:relative;top:2px;">Test</button>
</div>
<div class="settings-row">
<label class="settings-label">Results</label>
<div style="display:flex;gap:8px;flex:1;">
<select id="set-searchResultCount" class="settings-select" style="flex:1;">
<option value="3">3</option>
<option value="5" selected>5</option>
<option value="10">10</option>
<option value="20">20</option>
<option value="custom">Custom</option>
</select>
<input id="set-searchResultCountCustom" type="number" class="settings-select" placeholder="Enter custom value" style="flex:1;display:none;min-width:120px;" min="1" max="100">
</div>
</div>
<div id="set-searchUrlRow" class="settings-row">
<label class="settings-label">URL</label>
<input id="set-searchUrl" type="text" placeholder="http://localhost:8080" class="settings-select">
</div>
<div id="set-searchKeyRow" class="settings-row" style="display:none;">
<label class="settings-label">API Key</label>
<input id="set-searchApiKey" type="password" placeholder="API key" class="settings-select">
</div>
<div id="set-searchCxRow" class="settings-row" style="display:none;">
<label class="settings-label">CX ID</label>
<input id="set-searchCx" type="text" placeholder="Google PSE engine ID" class="settings-select">
</div>
<div class="settings-row">
<label class="settings-label" title="Providers tried in order when the primary fails or hits a rate limit">Fallbacks</label>
<div class="search-fallback-chain" id="set-searchFallbackChain"></div>
</div>
<div id="set-searchHint" class="admin-toggle-sub"></div>
<div id="set-searchMsg" style="font-size:11px;"></div>
</div>
</div>
</div>
<!-- ═══ APPEARANCE TAB ═══ -->
<div data-settings-panel="appearance" class="settings-appearance-panel hidden">
<div class="admin-card" style="padding-bottom:6px;">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/></svg>Sidebar</h2>
<div class="vis-toggles">
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><path d="M8 12l2.5 2.5L16 9"/></svg></span>
<span class="vis-label">Odysseus <span class="vis-hint">Brand name</span></span>
<input type="checkbox" checked data-ui-key="sidebar-brand"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="10" cy="10" r="7"/><path d="M21 21l-4.35-4.35"/></svg></span>
<span class="vis-label">Search</span>
<input type="checkbox" checked data-ui-key="sidebar-search"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></span>
<span class="vis-label">New Chat</span>
<input type="checkbox" checked data-ui-key="sidebar-new-chat"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></span>
<span class="vis-label">Chats <span class="vis-hint">Chat history list</span></span>
<input type="checkbox" checked data-ui-key="sessions-section"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg></span>
<span class="vis-label">Email</span>
<input type="checkbox" checked data-ui-key="email-section"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg></span>
<span class="vis-label">Models <span class="vis-hint">Model selector &amp; quick-chat</span></span>
<input type="checkbox" checked data-ui-key="models-section"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg></span>
<span class="vis-label">Tools <span class="vis-hint">Whole section (header + all tools)</span></span>
<input type="checkbox" checked data-ui-key="tools-section"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"/><path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"/></svg></span>
<span class="vis-label">Brain</span>
<input type="checkbox" checked data-ui-key="tool-memory"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg></span>
<span class="vis-label">Calendar</span>
<input type="checkbox" checked data-ui-key="tool-calendar"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M13 6h3a2 2 0 0 1 2 2v7"/><path d="M11 18H8a2 2 0 0 1-2-2V9"/></svg></span>
<span class="vis-label">Compare</span>
<input type="checkbox" checked data-ui-key="tool-compare"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/><path d="M9 7h6M9 11h4"/></svg></span>
<span class="vis-label">Cookbook</span>
<input type="checkbox" checked data-ui-key="tool-cookbook"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg></span>
<span class="vis-label">Deep Research</span>
<input type="checkbox" checked data-ui-key="tool-research"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg></span>
<span class="vis-label">Gallery</span>
<input type="checkbox" checked data-ui-key="tool-gallery"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="13" y2="17"/></svg></span>
<span class="vis-label">Library</span>
<input type="checkbox" checked data-ui-key="tool-library"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 7h6M9 11h6M9 15h4"/></svg></span>
<span class="vis-label">Notes</span>
<input type="checkbox" checked data-ui-key="tool-notes"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/><path d="M9 16l2 2 4-4"/></svg></span>
<span class="vis-label">Tasks</span>
<input type="checkbox" checked data-ui-key="tool-tasks"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 2a10 10 0 0 0 0 20 5 5 0 0 0 5-5 3 3 0 0 0-3-3h-2a3 3 0 0 1-3-3 5 5 0 0 1 5-5"/></svg></span>
<span class="vis-label">Theme</span>
<input type="checkbox" checked data-ui-key="tool-theme"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></span>
<span class="vis-label">User <span class="vis-hint">Avatar &amp; name</span></span>
<input type="checkbox" checked data-ui-key="user-bar"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.6 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></span>
<span class="vis-label">Settings Button <span class="vis-hint">Cog next to user — re-open with <code>/settings</code></span></span>
<input type="checkbox" checked data-ui-key="sidebar-settings-btn"><span class="vis-switch"></span>
</label>
</div>
</div>
<div class="admin-card" style="padding-bottom:6px;">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>Chat Area</h2>
<div class="vis-toggles">
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 6h16"/><path d="M4 10h8"/></svg></span>
<span class="vis-label">Session Header <span class="vis-hint">Model name &amp; export above chat</span></span>
<input type="checkbox" checked data-ui-key="chat-meta"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 3v2m0 14v2m-7-9H3m18 0h-2m-1.5-6.5L16 7m-8-1.5L6.5 7m11 11l-1.5-1.5M8 18l-1.5 1.5"/><circle cx="12" cy="12" r="4"/></svg></span>
<span class="vis-label">Welcome Message <span class="vis-hint">Logo &amp; tips on empty chat</span></span>
<input type="checkbox" checked data-ui-key="welcome-text"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></span>
<span class="vis-label">Incognito Mode <span class="vis-hint">No memory, no history saved</span></span>
<input type="checkbox" checked data-ui-key="incognito-btn"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon" style="font-size:13px;line-height:14px;">Aa</span>
<span class="vis-label">Text-only Emojis <span class="vis-hint">Strip emojis from AI replies</span></span>
<input type="checkbox" data-ui-key="text-emojis"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 2a7 7 0 0 0-7 7c0 3 2 5.5 4.5 7.5.7.6 1.2 1.3 1.5 2h2c.3-.7.8-1.4 1.5-2C17 14.5 19 12 19 9a7 7 0 0 0-7-7z"/><line x1="10" y1="22" x2="14" y2="22"/></svg></span>
<span class="vis-label">Thinking Process <span class="vis-hint">Show &lt;think&gt; collapsible bars</span></span>
<input type="checkbox" checked data-ui-key="show-thinking"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/><path d="M4 4l16 16"/></svg></span>
<span class="vis-label">Sensitive Blur <span class="vis-hint">Blur emails, tokens, and secrets in AI output</span></span>
<input type="checkbox" data-privacy-key="sensitive-blur"><span class="vis-switch"></span>
</label>
</div>
</div>
<div class="admin-card" style="padding-bottom:6px;">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><line x1="17" y1="10" x2="3" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="17" y1="18" x2="3" y2="18"/></svg>Chat Bar</h2>
<div class="vis-toggles">
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></span>
<span class="vis-label">Web Search</span>
<input type="checkbox" checked data-ui-key="web-toggle-btn"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg></span>
<span class="vis-label">Document Editor</span>
<input type="checkbox" checked data-ui-key="doc-toggle-btn"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg></span>
<span class="vis-label">Shell</span>
<input type="checkbox" checked data-ui-key="bash-toggle-btn"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg></span>
<span class="vis-label">More Tools <span class="vis-hint">Overflow menu</span></span>
<input type="checkbox" checked data-ui-key="overflow-plus-btn"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon vis-icon-text">A|C</span>
<span class="vis-label">Agent / Chat <span class="vis-hint">Mode switcher</span></span>
<input type="checkbox" checked data-ui-key="mode-toggle"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg></span>
<span class="vis-label">Attach Files</span>
<input type="checkbox" checked data-ui-key="attach-btn"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg></span>
<span class="vis-label">Deep Research</span>
<input type="checkbox" checked data-ui-key="research-btn"><span class="vis-switch"></span>
</label>
<label class="vis-row">
<span class="vis-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></span>
<span class="vis-label">Personas <span class="vis-hint">Persona picker &amp; system prompt</span></span>
<input type="checkbox" checked data-ui-key="preset-mini-btn"><span class="vis-switch"></span>
</label>
</div>
</div>
<div style="text-align:right;padding:0 4px;">
<button type="button" class="admin-btn-sm" id="set-uiVisResetBtn" style="opacity:0.5;">Reset All</button>
</div>
</div>
<!-- ═══ THEME TAB ═══ -->
<!-- ═══ MEMORY TAB ═══ -->
<!-- ═══ SHORTCUTS TAB ═══ -->
<div data-settings-panel="shortcuts" class="hidden">
<div class="admin-card" style="display:flex;align-items:center;justify-content:space-between;padding:10px 16px;">
<div>
<h2 style="margin:0;font-size:13px;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.01M10 8h.01M14 8h.01M18 8h.01M8 12h.01M12 12h.01M16 12h.01M7 16h10"/></svg>Keyboard Shortcuts</h2>
<p style="font-size:10px;opacity:0.4;margin:2px 0 0;">Click a shortcut to rebind. Press Escape to cancel.</p>
</div>
<button type="button" class="shortcut-action-btn is-reset" id="shortcuts-reset-btn" title="Reset Shortcuts" style="width:28px;height:28px;font-size:15px;">&#x21A9;</button>
</div>
<div class="admin-card">
<div id="shortcuts-list"></div>
</div>
</div>
<!-- ═══ ACCOUNT TAB ═══ -->
<div data-settings-panel="account" class="hidden">
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>Account</h2>
<div style="display:flex;align-items:center;gap:10px;margin:4px 0 12px;">
<div class="user-bar-avatar" id="settings-account-avatar" style="width:32px;height:32px;font-size:14px;"></div>
<div style="flex:1;">
<div id="settings-account-username" style="font-size:13px;font-weight:600;"></div>
<div id="settings-account-role" style="font-size:11px;opacity:0.5;"></div>
</div>
<button id="settings-logout-btn" style="background:color-mix(in srgb, var(--color-error) 10%, transparent);border:1px solid var(--color-error);border-radius:6px;padding:6px 12px;color:var(--color-error);font-family:inherit;font-size:12px;font-weight:500;cursor:pointer;transition:opacity 0.15s,background 0.15s;display:inline-flex;align-items:center;gap:6px;">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
Logout
</button>
</div>
</div>
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>Change Password</h2>
<div class="settings-col">
<input id="settings-pw-current" type="password" placeholder="Current password" autocomplete="current-password" style="padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg);font-family:inherit;font-size:12px;">
<input id="settings-pw-new" type="password" placeholder="New password (min 8)" autocomplete="new-password" style="padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg);font-family:inherit;font-size:12px;">
<input id="settings-pw-confirm" type="password" placeholder="Confirm new password" autocomplete="new-password" style="padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg);font-family:inherit;font-size:12px;">
<div class="settings-row" style="margin-top:2px;justify-content:flex-end;">
<span id="settings-pw-msg" style="font-size:11px;margin-right:auto;"></span>
<button class="admin-btn-add" id="settings-pw-save">Update Password</button>
</div>
</div>
</div>
<div class="admin-card" id="settings-2fa-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/><circle cx="12" cy="16" r="1"/></svg>Two-Factor Authentication</h2>
<div id="settings-2fa-content">
<!-- Populated by JS -->
</div>
</div>
</div>
<!-- ═══ EMAIL TAB ═══ -->
<div data-settings-panel="email" class="hidden">
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>Email Accounts</h2>
<div class="settings-row" style="align-items:center;">
<div class="admin-toggle-sub" style="margin:0;flex:1;">Add, edit, delete, and test accounts in Integrations.</div>
<button class="admin-btn-add" id="set-email-open-integrations">Manage in Integrations</button>
</div>
</div>
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/><path d="M9 16l2 2 4-4"/></svg>Email Tasks</h2>
<div class="settings-row" style="align-items:center;">
<div class="admin-toggle-sub" style="margin:0;flex:1;">Manage email background tasks in Tasks.</div>
<button class="admin-btn-add" id="set-email-open-tasks">Open Tasks</button>
</div>
</div>
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>Writing Style</h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">AI-extracted from your sent emails. Used when AI drafts replies.</div>
<div class="settings-col">
<textarea id="set-email-style" rows="4" class="settings-select" style="font-family:inherit;resize:vertical" placeholder="e.g. I write emails in this style. I don't use exclamation marks. I sign emails with: ..."></textarea>
<div class="settings-row" style="margin-top:4px">
<span id="set-email-style-msg" style="font-size:11px;"></span>
<button class="admin-btn-add" id="set-email-style-extract" style="margin-left:auto;">Extract from Sent (15 emails)</button>
<button class="admin-btn-add" id="set-email-style-save">Save</button>
</div>
</div>
</div>
</div>
<!-- ═══ REMINDERS TAB ═══ -->
<div data-settings-panel="reminders" class="hidden">
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>How you're reminded</h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">Controls how fired note reminders are delivered.</div>
<div class="settings-col">
<div class="settings-row">
<label class="settings-label">Channel</label>
<select id="set-reminder-channel" class="settings-select">
<option value="browser">Browser notification (default)</option>
<option value="email" id="set-reminder-channel-email-opt">Email</option>
<option value="ntfy" id="set-reminder-channel-ntfy-opt">ntfy</option>
</select>
</div>
<div id="set-reminder-email-from-row" class="settings-row" style="display:none">
<label class="settings-label">Send from</label>
<select id="set-reminder-email-account" class="settings-select"></select>
</div>
<div id="set-reminder-email-to-row" class="settings-row" style="display:none">
<label class="settings-label">Send to</label>
<input id="set-reminder-email-to" class="settings-select" type="email" placeholder="you@example.com" />
</div>
<div id="set-reminder-ntfy-topic-row" class="settings-row" style="display:none">
<label class="settings-label">ntfy topic</label>
<input id="set-reminder-ntfy-topic" class="settings-select" type="text" placeholder="reminders" />
</div>
<div id="set-reminder-channel-hint" style="font-size:11px;opacity:0.6;"></div>
<div style="font-size:11px;opacity:0.6;margin-top:4px;">Configure email account, ntfy server, etc. in <a href="#" id="set-reminders-open-integrations" style="color:var(--accent, var(--red));text-decoration:none;font-weight:600;">Integrations</a>.</div>
</div>
</div>
<div class="admin-card">
<h2 style="display:flex;align-items:center;gap:6px;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:1px;opacity:0.6;flex-shrink:0"><path d="M12 0L14.59 8.41L23 12L14.59 15.59L12 24L9.41 15.59L1 12L9.41 8.41Z"/></svg>AI Synthesis<span style="flex:1"></span><label class="admin-switch" title="Use the utility model to write reminder messages"><input type="checkbox" id="set-reminder-llm-toggle"><span class="admin-slider"></span></label></h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">When on, the utility model writes a short, warm one-line reminder for browser, email, AND ntfy reminders instead of just the raw note content.</div>
</div>
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>Public App URL</h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">Used to build clickable links back to Odysseus inside outgoing reminder / urgent-email emails (e.g. <code>https://chat.yourdomain.com</code>). Leave blank to omit links.</div>
<div class="settings-col">
<div class="settings-row">
<label class="settings-label">URL</label>
<input id="set-app-public-url" class="settings-select" type="url" placeholder="https://chat.example.com" style="flex:1;" />
</div>
<div id="set-app-public-url-msg" style="font-size:11px;color:color-mix(in srgb, var(--fg) 55%, transparent);"></div>
</div>
</div>
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>Test</h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">Fire a test reminder using your current settings to verify everything works.</div>
<div class="settings-row">
<span id="set-reminder-test-msg" style="font-size:11px;"></span>
<button class="admin-btn-add" id="set-reminder-test-btn" style="margin-left:auto;">Send Test Reminder</button>
</div>
</div>
</div>
<!-- ═══ ADMIN: USERS TAB ═══ -->
<div data-settings-panel="users" class="hidden">
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="8.5" cy="7" r="4"/><line x1="20" y1="8" x2="20" y2="14"/><line x1="23" y1="11" x2="17" y2="11"/></svg>Registration</h2>
<div class="admin-toggle-row">
<div>
<div class="admin-toggle-label">Open signup</div>
<div class="admin-toggle-sub">Allow anyone to create an account from the login page</div>
</div>
<label class="admin-switch"><input type="checkbox" id="adm-signupToggle"><span class="admin-slider"></span></label>
</div>
</div>
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>Users</h2>
<div id="adm-userList"><div class="admin-empty">Loading...</div></div>
</div>
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="8.5" cy="7" r="4"/><line x1="20" y1="8" x2="20" y2="14"/><line x1="23" y1="11" x2="17" y2="11"/></svg>Add User</h2>
<div class="admin-add-form">
<input id="adm-newUsername" type="text" placeholder="Username (email)">
<input id="adm-newPassword" type="password" placeholder="Password (min 8)">
<div class="admin-switch-inline" title="Grant full admin access"><label class="admin-switch"><input type="checkbox" id="adm-newIsAdmin"><span class="admin-slider"></span></label> Admin</div>
</div>
<div class="settings-row" style="margin-top:6px;">
<button class="admin-btn-add" id="adm-addBtn">Add User</button>
<span id="adm-addMsg" style="font-size:11px"></span>
</div>
</div>
</div>
<!-- ═══ SERVICES TAB ═══ -->
<div data-settings-panel="services">
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/><circle cx="6" cy="6" r="1"/><circle cx="6" cy="18" r="1"/></svg>Add Models <span style="opacity:0.45;font-weight:normal;font-size:0.82em">(Endpoints)</span></h2>
<div class="admin-toggle-sub" style="margin-bottom:10px">Connect local models first, or add a cloud API.</div>
<!-- Local subsection -->
<div class="adm-add-section collapsible collapsed" id="adm-add-local">
<div class="adm-ep-section-head adm-section-toggle" role="button" tabindex="0" aria-expanded="false">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:4px;"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8"/><path d="M12 17v4"/></svg>
<span>Local</span>
<svg class="adm-section-caret" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<div class="admin-model-form">
<div class="admin-model-form-row">
<input id="adm-epLocalUrl" type="text" placeholder="Paste endpoint URL, e.g. http://localhost:11434/v1" style="flex:1">
<select id="adm-epLocalType" style="padding:5px;width:72px;flex-shrink:0;">
<option value="llm">LLM</option>
<option value="image">Image</option>
</select>
</div>
<div class="admin-model-form-row">
<input id="adm-epLocalApiKey" type="password" placeholder="API key (optional — for protected local endpoints)" autocomplete="off" style="flex:1">
</div>
<div class="admin-model-form-row">
<span style="flex:1"></span>
<button class="admin-btn-sm" id="adm-epLocalTestBtn" style="width:55px;text-align:center;">Test</button>
<button class="admin-btn-add" id="adm-epLocalAddBtn" style="width:55px;text-align:center;">Add</button>
</div>
<div class="adm-quickstart-section collapsed" id="adm-add-local-quickstart">
<div class="adm-quickstart-toggle" role="button" tabindex="0" aria-expanded="false">
<span>Quickstart</span>
<svg class="adm-section-caret" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<div class="adm-quickstart-body">
<button class="admin-btn-sm" id="adm-epDiscoverBtn" title="Scan your network for running model servers">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" style="vertical-align:-1px;margin-right:4px;"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>Scan for Servers
</button>
<button class="admin-btn-sm" id="adm-epOllamaBtn" title="Fill the default Ollama endpoint">Ollama</button>
</div>
</div>
<div id="adm-epLocalMsg" class="adm-ep-inline-msg"></div>
</div>
</div>
<!-- API subsection -->
<div class="adm-add-section collapsible collapsed" id="adm-add-api" style="margin-top:14px">
<div class="adm-ep-section-head adm-section-toggle" role="button" tabindex="0" aria-expanded="false">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:4px;"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
<span>API</span>
<svg class="adm-section-caret" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<div class="admin-model-form">
<!-- Custom picker (with logos). Hidden native <select> mirrors
its value so the existing JS that reads adm-epProvider
keeps working unchanged. -->
<div class="adm-provider-picker adm-provider-combo" id="adm-provider-picker">
<input id="adm-epUrl" type="text" placeholder="Base URL or pick provider" autocomplete="off">
<button type="button" class="adm-provider-btn" id="adm-provider-btn" title="Pick provider">
<span class="adm-provider-current"><span class="adm-provider-logo"></span><span class="adm-provider-name">Provider</span></span>
<svg class="adm-provider-caret" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<div class="adm-provider-menu hidden" id="adm-provider-menu"></div>
</div>
<select id="adm-epProvider" style="display:none">
<option value="">Custom URL</option>
<option value="https://api.anthropic.com" data-logo="anthropic">Anthropic</option>
<option value="https://api.deepseek.com/v1" data-logo="deepseek" selected>DeepSeek</option>
<option value="https://api.openai.com/v1" data-logo="openai">OpenAI</option>
<option value="https://openrouter.ai/api/v1" data-logo="openrouter">OpenRouter</option>
<option value="https://ollama.com/api" data-logo="ollama">Ollama Cloud</option>
<option value="https://api.groq.com/openai/v1" data-logo="groq">Groq</option>
<option value="https://api.mistral.ai/v1" data-logo="mistral">Mistral</option>
<option value="https://api.together.xyz/v1" data-logo="together">Together AI</option>
<option value="https://api.fireworks.ai/inference/v1" data-logo="fireworks">Fireworks AI</option>
<option value="https://generativelanguage.googleapis.com/v1beta/openai" data-logo="gemini">Google Gemini</option>
<option value="https://api.x.ai/v1" data-logo="grok">xAI Grok</option>
<option value="https://api.z.ai/api/paas/v4" data-logo="zhipu">Z.AI (Zhipu)</option>
</select>
<div class="admin-model-form-row">
<input id="adm-epApiKey" type="password" placeholder="API key">
<select id="adm-epType" style="padding:5px;width:80px;">
<option value="llm">LLM</option>
<option value="image">Image</option>
</select>
<button class="admin-btn-sm" id="adm-epApiTestBtn" style="width:55px;text-align:center;">Test</button>
<button class="admin-btn-sm hidden" id="adm-epApiCancelTestBtn" style="width:62px;text-align:center;">Cancel</button>
<button class="admin-btn-add" id="adm-epAddBtn" style="width:55px;text-align:center;">Add</button>
</div>
<div id="adm-epApiMsg" class="adm-ep-inline-msg"></div>
</div>
</div>
</div>
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>Added Models <span style="opacity:0.45;font-weight:normal;font-size:0.82em">(Endpoints)</span></h2>
<div class="admin-toggle-sub" style="margin-bottom:10px">Manage the endpoints you've added.</div>
<div class="adm-ep-section">
<div class="adm-ep-section-head">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:4px;"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8"/><path d="M12 17v4"/></svg>
<span>Local</span>
</div>
<div id="adm-epList-local"><div class="admin-empty">Loading...</div></div>
</div>
<div class="adm-ep-section" style="margin-top:14px">
<div class="adm-ep-section-head">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:4px;"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
<span>API</span>
</div>
<div id="adm-epList-api"></div>
</div>
</div>
</div>
<!-- ═══ TOOLS TAB ═══ -->
<!-- ═══ INTEGRATIONS TAB ═══ -->
<div data-settings-panel="integrations" class="hidden">
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>Connections</h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">All external service connections in one place.</div>
<div id="unified-integrations-list"></div>
<div id="unified-intg-form" style="display:none"></div>
<div style="text-align:center;padding:8px 0;">
<button type="button" class="admin-btn-sm" id="unified-intg-add-btn">+ Add Integration</button>
</div>
</div>
</div>
<!-- ═══ TOOLS TAB ═══ -->
<div data-settings-panel="tools" class="hidden">
<div class="admin-card" style="margin-bottom:12px;">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>Built-in Tools</h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">Enable or disable tools available to the AI agent.</div>
<div id="adm-builtin-tools-list" class="admin-user-list"></div>
</div>
</div>
<!-- ═══ SYSTEM TAB ═══ -->
<div data-settings-panel="system" class="hidden">
<div class="admin-card">
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>Data Backup</h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">Export or import your user data (memories, presets, settings, skills, preferences) as a JSON file.</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<button class="admin-btn-add" id="adm-exportDataBtn">Export Data</button>
<button class="admin-btn-add" id="adm-importDataBtn">Import Data</button>
<input type="file" id="adm-importFile" accept=".json" style="display:none;">
</div>
<div id="adm-backupMsg" style="margin-top:6px;"></div>
</div>
<div class="admin-card admin-danger-card">
<h2 style="color:#e55;">Danger Zone</h2>
<div class="admin-toggle-sub" style="margin-bottom:8px">Irreversible. Each wipe targets one category — pick exactly what you want gone.</div>
<div style="display:flex;justify-content:space-between;align-items:center;">
<div>
<div class="admin-toggle-label">Wipe all chats</div>
<div class="admin-toggle-sub">Every session, message, and chat history. Documents/notes/etc. stay.</div>
</div>
<button class="admin-btn-delete" data-wipe-kind="chats" style="white-space:nowrap;">Wipe</button>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;">
<div>
<div class="admin-toggle-label">Wipe all memory</div>
<div class="admin-toggle-sub">Clears `memory.json`, the Memory table, and the vector store. Skills not affected.</div>
</div>
<button class="admin-btn-delete" data-wipe-kind="memory" style="white-space:nowrap;">Wipe</button>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;">
<div>
<div class="admin-toggle-label">Wipe all skills</div>
<div class="admin-toggle-sub">Drops `data/skills/` (all SKILL.md files). Memory not affected.</div>
</div>
<button class="admin-btn-delete" data-wipe-kind="skills" style="white-space:nowrap;">Wipe</button>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;">
<div>
<div class="admin-toggle-label">Wipe all notes</div>
<div class="admin-toggle-sub">Every note, todo, and checklist.</div>
</div>
<button class="admin-btn-delete" data-wipe-kind="notes" style="white-space:nowrap;">Wipe</button>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;">
<div>
<div class="admin-toggle-label">Wipe all tasks</div>
<div class="admin-toggle-sub">Every scheduled task and its run history (Tasks tool).</div>
</div>
<button class="admin-btn-delete" data-wipe-kind="tasks" style="white-space:nowrap;">Wipe</button>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;">
<div>
<div class="admin-toggle-label">Wipe all documents</div>
<div class="admin-toggle-sub">Every document and version. Drafts, exports, library — all gone.</div>
</div>
<button class="admin-btn-delete" data-wipe-kind="documents" style="white-space:nowrap;">Wipe</button>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;">
<div>
<div class="admin-toggle-label">Wipe all gallery</div>
<div class="admin-toggle-sub">Every image record and the upload directory on disk.</div>
</div>
<button class="admin-btn-delete" data-wipe-kind="gallery" style="white-space:nowrap;">Wipe</button>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;">
<div>
<div class="admin-toggle-label">Wipe all calendar</div>
<div class="admin-toggle-sub">Every event and every calendar (incl. CalDAV-synced ones; resync to restore).</div>
</div>
<button class="admin-btn-delete" data-wipe-kind="calendar" style="white-space:nowrap;">Wipe</button>
</div>
<div id="adm-wipeMsg" style="margin-top:8px;"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Search overlay (Ctrl+K command palette) -->
<div class="search-overlay hidden" id="search-overlay">
<div class="search-popup">
<input type="text" id="search-input" placeholder="Search conversations..." autocomplete="off" />
<div class="search-results" id="search-results"></div>
</div>
</div>
<!-- Toast container (aria-live for screen readers) -->
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<!-- Load modules in this order -->
<script type="module" src="/static/js/storage.js"></script>
<script type="module" src="/static/js/ui.js"></script>
<script type="module" src="/static/js/markdown.js"></script>
<script type="module" src="/static/js/dragSort.js"></script>
<script type="module" src="/static/js/sessions.js"></script>
<script type="module" src="/static/js/memory.js"></script>
<script type="module" src="/static/js/skills.js"></script>
<script type="module" src="/static/js/tourHints.js"></script>
<script type="module" src="/static/js/tourAutoplay.js"></script>
<script type="module" src="/static/js/fileHandler.js"></script>
<script type="module" src="/static/js/voiceRecorder.js"></script>
<script type="module" src="/static/js/models.js"></script> <!-- This must come BEFORE app.js -->
<script type="module" src="/static/js/rag.js"></script>
<script type="module" src="/static/js/presets.js"></script>
<script type="module" src="/static/js/search.js"></script>
<script type="module" src="/static/js/spinner.js"></script>
<script type="module" src="/static/js/tts-ai.js"></script>
<script type="module" src="/static/js/document.js"></script>
<script type="module" src="/static/js/gallery.js"></script>
<script type="module" src="/static/js/chatRenderer.js"></script>
<script type="module" src="/static/js/codeRunner.js"></script>
<script type="module" src="/static/js/chatStream.js"></script>
<script type="module" src="/static/js/chat.js?v=20260520m"></script>
<script type="module" src="/static/js/cookbook.js"></script>
<script type="module" src="/static/js/search-chat.js"></script>
<script type="module" src="/static/js/compare/index.js"></script>
<script type="module" src="/static/js/theme.js"></script>
<script type="module" src="/static/js/censor.js"></script>
<script type="module" src="/static/js/settings.js"></script>
<script type="module" src="/static/js/admin.js"></script>
<script type="module" src="/static/js/assistant.js"></script>
<script type="module" src="/static/app.js"></script> <!-- app.js must be LAST -->
<script type="module" src="/static/js/init.js"></script>
<script type="module" src="/static/js/a11y.js"></script>
<script nonce="{{CSP_NONCE}}">if('serviceWorker' in navigator){navigator.serviceWorker.register('/static/sw.js').catch(()=>{});}</script>
</body>
</html>