mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-08-05 02:45:28 +00:00
Two independent bugs caused the session hash to disappear from the URL:
Bug 1 — ReferenceError in catch block silently killed error recovery
In handleChatSubmit, two const variables (streamingTTS at line 1922 and
abortCtrl at line 1741) were declared inside the try block but referenced
in the catch block. Since const is block-scoped in JavaScript, they were
undefined in catch, causing a ReferenceError that silently aborted the
error handler. This prevented materializePendingSession() from ever being
called, so no hash was written to the URL.
Fix: Hoisted both as let declarations before the try { block.
Bug 2 — Dual sessions.js ES module instances with mismatched state
app.js imported sessions.js with a version query string
(?v=20260722ctxheader4) while every other module imported ./sessions.js
without one. The browser treated them as different URLs, creating two
separate module instances with independent _pendingChat and
currentSessionId state. createDirectChat() set pending on one instance
while handleChatSubmit() checked hasPendingChat() on the other — so the
pending session never materialized.
Fix: Removed the version query string from the sessions.js import in
app.js and from the modulepreload + script tags in index.html. All
modules now share a single sessions.js instance.
Bonus guard: _adoptOpenedSessionBeforeAutoCreate() now checks
hasPendingChat() before adopting a stale DOM-active session, preventing
the send path from landing in the wrong session when a New Chat is pending.
2538 lines
228 KiB
HTML
2538 lines
228 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content, viewport-fit=cover" />
|
|
<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/icons/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",opendyslexic:"'OpenDyslexic', sans-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 UI text-size scale early (global accessibility pref, independent
|
|
// of the active theme) so there's no flash on load.
|
|
try {
|
|
var _us = localStorage.getItem('odysseus-ui-scale');
|
|
if (_us && _us !== '100') document.documentElement.classList.add('ui-scale-' + _us);
|
|
} catch(e){}
|
|
// Restore the sidebar mode before first paint. Otherwise the icon rail
|
|
// and full sidebar can both flash visible until sidebar-layout.js runs.
|
|
try {
|
|
var _mobileStartup = window.matchMedia && window.matchMedia('(max-width: 768px)').matches;
|
|
if (_mobileStartup) {
|
|
document.documentElement.classList.add('ody-sidebar-off', 'ody-mobile-startup-sidebar-hidden');
|
|
} else {
|
|
var _sm = localStorage.getItem('odysseus-sidebar-mode') || 'full';
|
|
if (_sm === 'mini') document.documentElement.classList.add('ody-sidebar-mini');
|
|
else if (_sm === 'off') document.documentElement.classList.add('ody-sidebar-off');
|
|
}
|
|
} catch(e){}
|
|
// 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'); }
|
|
@media (max-width: 768px) {
|
|
html.ody-sidebar-off .sidebar,
|
|
html.ody-mobile-startup-sidebar-hidden .sidebar {
|
|
transform: translateX(-100%) !important;
|
|
pointer-events: none !important;
|
|
opacity: 0 !important;
|
|
}
|
|
html.ody-sidebar-off .sidebar.right-side,
|
|
html.ody-mobile-startup-sidebar-hidden .sidebar.right-side {
|
|
transform: translateX(100%) !important;
|
|
}
|
|
html.ody-sidebar-off .icon-rail,
|
|
html.ody-mobile-startup-sidebar-hidden .icon-rail {
|
|
display: none !important;
|
|
}
|
|
}
|
|
</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?v=20260723tasksbulkfeedback1">
|
|
<link rel="modulepreload" href="/static/app.js?v=20260723tasksbulkfeedback1">
|
|
<link rel="modulepreload" href="/static/js/chat.js?v=20260801fix1">
|
|
<link rel="modulepreload" href="/static/js/ui.js">
|
|
<link rel="modulepreload" href="/static/js/sessions.js">
|
|
<link rel="modulepreload" href="/static/js/markdown.js">
|
|
</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:color-mix(in srgb, #9cdef2 62%, var(--brand-color,var(--red,#e06c75)));font-family:monospace;font-size:11px;opacity:.5;position:relative;padding-top:10px;line-height:1;white-space:pre">▁▂▃</div>
|
|
</div>
|
|
<script nonce="{{CSP_NONCE}}">
|
|
(function(){
|
|
var el=document.getElementById('loader-wave');
|
|
if(!el)return;
|
|
var frames=[
|
|
{wave:'▁▂▃',y:9},
|
|
{wave:'▂▃▄',y:7},
|
|
{wave:'▃▄▅',y:5},
|
|
{wave:'▄▅▆',y:3},
|
|
{wave:'▅▆▅',y:1},
|
|
{wave:'▆▅▄',y:3},
|
|
{wave:'▅▄▃',y:5},
|
|
{wave:'▄▃▂',y:7},
|
|
{wave:'▃▂▁',y:9}
|
|
];
|
|
var i=0;
|
|
function render(){
|
|
var f=frames[i%frames.length];
|
|
el.innerHTML='<span style="position:absolute;left:50%;top:0;transform:translate(-50%,'+f.y+'px);font-size:1.24em;line-height:1">•</span><span>'+f.wave+'</span>';
|
|
i++;
|
|
}
|
|
render();
|
|
var iv=setInterval(function(){
|
|
if(!document.getElementById('app-loader')){clearInterval(iv);return}
|
|
render();
|
|
},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;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--accent, var(--red));flex-shrink:0;"><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-h2" class="memory-count" style="font-size:0.6em;opacity:0.6;font-weight:normal"></span></h2>
|
|
<span style="flex:1"></span>
|
|
<span class="admin-toggle-state"></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">
|
|
<div class="memory-sort-picker" id="memory-sort-picker" style="position:relative;">
|
|
<select id="memory-sort" class="memory-sort-select" aria-label="Sort memories" style="display:none;">
|
|
<option value="newest">Newest</option>
|
|
<option value="oldest">Oldest</option>
|
|
<option value="alpha">A-Z</option>
|
|
<option value="uses">Most used</option>
|
|
</select>
|
|
<button type="button" class="memory-sort-btn" id="memory-sort-btn" aria-haspopup="listbox" aria-expanded="false">
|
|
<span class="memory-sort-current"><span class="memory-sort-icon-cur"></span><span class="memory-sort-label">Newest</span></span>
|
|
<svg class="memory-sort-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="memory-sort-menu" id="memory-sort-menu" role="listbox" hidden></div>
|
|
</div>
|
|
<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;color:var(--accent, var(--red));"><path d="M12 0L14.59 8.41L23 12L14.59 15.59L12 24L9.41 15.59L1 12L9.41 8.41Z"/></svg> Tidy</button>
|
|
<button id="memory-select-btn" class="memory-toolbar-btn" title="Select multiple memories" style="position:relative;left:-2px;"><svg class="memory-select-btn-icon" 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:-2px;margin-right:3px;"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3" fill="currentColor" stroke="none"/></svg>Select</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;color:var(--accent, var(--red));"><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 — 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> — 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>
|
|
<select id="new-memory-category" class="memory-edit-cat-select" aria-label="Memory category"></select>
|
|
</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;color:var(--accent, var(--red));"><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;">Import a skill from GitHub or <a href="https://skills.sh" target="_blank" rel="noopener noreferrer">skills.sh</a> (folder with <code>SKILL.md</code> and optional templates).</p>
|
|
<div class="memory-add-row" style="margin-top:6px;margin-bottom:10px;">
|
|
<div class="skill-ph-wrap" style="flex:1;min-width:0;">
|
|
<input type="url" id="skill-import-url" placeholder=" " class="memory-add-input skill-hint-input" aria-label="Skill import URL" />
|
|
<span class="skill-rich-ph"><span class="k">Import URL</span> — e.g. GitHub tree link to a skill folder</span>
|
|
</div>
|
|
<button type="button" id="skill-import-url-btn" class="theme-io-btn" title="Import skill from URL" style="flex:none;height:28px;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;" aria-hidden="true"><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>
|
|
</div>
|
|
<p class="memory-desc doclib-desc" style="margin-top:0;">Or 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="confirm-btn confirm-btn-primary">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="display:flex;align-items:center;gap:6px;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="color:var(--accent, var(--red));flex-shrink:0;"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>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>
|
|
<span class="admin-toggle-state"></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-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="var(--accent, var(--red))" 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</button>
|
|
<button id="skills-select-btn" class="memory-toolbar-btn" title="Select multiple skills"><svg class="memory-select-btn-icon" 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:-2px;margin-right:3px;"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3" fill="currentColor" stroke="none"/></svg>Select</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;min-height:32px">
|
|
<h2 style="margin:0;display:inline-flex;align-items:center;gap:6px"><svg width="13" height="13" viewBox="0 0 24 24" fill="var(--accent, var(--red))" aria-hidden="true"><path d="M12 0L14.59 8.41L23 12L14.59 15.59L12 24L9.41 15.59L1 12L9.41 8.41Z"/></svg>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;min-height:32px">
|
|
<h2 style="margin:0;display:inline-flex;align-items:center;gap:6px"><svg width="13" height="13" viewBox="0 0 24 24" fill="var(--accent, var(--red))" aria-hidden="true"><path d="M12 0L14.59 8.41L23 12L14.59 15.59L12 24L9.41 15.59L1 12L9.41 8.41Z"/></svg>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;min-height:32px">
|
|
<h2 style="margin:0;display:inline-flex;align-items:center;gap:6px"><svg width="13" height="13" viewBox="0 0 24 24" fill="var(--accent, var(--red))" aria-hidden="true"><path d="M12 0L14.59 8.41L23 12L14.59 15.59L12 24L9.41 15.59L1 12L9.41 8.41Z"/></svg>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">≥ 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 class="admin-card">
|
|
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:32px">
|
|
<h2 style="margin:0;display:inline-flex;align-items:center;gap:6px"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="opacity:0.7"><path d="M12 5v14"/><polyline points="6 11 12 17 18 11"/><path d="M5 20h14"/></svg>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>
|
|
</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">✖</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><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;opacity:0.6"><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>Default Themes</h2>
|
|
<div class="theme-grid" id="themeGrid"></div>
|
|
</div>
|
|
<div class="admin-card" id="themeUserCard" style="display:none">
|
|
<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;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>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><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;opacity:0.6"><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>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">↺</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">↺</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">↺</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">↺</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">↺</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">↺</button></div>
|
|
</div>
|
|
</div>
|
|
<div class="theme-adv-toggle" id="theme-adv-toggle">
|
|
<span class="theme-adv-arrow">▶</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">↺</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">↺</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">↺</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">↺</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">↺</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">↺</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">↺</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">↺</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">↺</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">↺</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">↺</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">↺</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><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;opacity:0.6"><path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/></svg>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>
|
|
<option value="opendyslexic">OpenDyslexic (dyslexia-friendly)</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">
|
|
<label class="theme-fd-label">Text size</label>
|
|
<select id="theme-text-size-select" class="theme-fd-select" aria-label="Text size">
|
|
<option value="100">Default</option>
|
|
<option value="125">Larger</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"> </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">↺</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><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;opacity:0.6"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>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">⤒ Import</button>
|
|
<button id="theme-export-btn" class="theme-io-btn" title="Export current colors as JSON">⤓ 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">☰</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">✕</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"><rect x="3" y="4" width="7" height="16" rx="1.5"/><rect x="14" y="4" width="7" height="16" rx="1.5"/><path d="M10 8h4"/><path d="M10 16h4"/></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 list-item-plus-btn chats-manage-btn" id="chats-library-btn" title="Manage Chats (Library)">
|
|
<span aria-hidden="true" style="display:inline-block;width:13px;height:13px;"></span>
|
|
<span class="list-item-plus-label">manage</span>
|
|
</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 5px 4px;cursor:pointer;display:inline-flex;align-items:center;gap:6px;">
|
|
<span class="auto-sort-icon"><svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" style="vertical-align:-1px;"><path d="M12 0L14.59 8.41L23 12L14.59 15.59L12 24L9.41 15.59L1 12L9.41 8.41Z"/></svg></span>
|
|
<span>Group</span>
|
|
<span class="auto-sort-spinner" style="display:none;">Sorting...</span>
|
|
</span>
|
|
</div>
|
|
<div class="dropdown-item rearrange-toggle sort-dropdown-item sort-dropdown-sep" id="session-rearrange-toggle">
|
|
↑↓ Rearrange <span class="rearrange-check" style="float:right; opacity:0;">•</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="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;"><rect x="3" y="4" width="7" height="16" rx="1.5"/><rect x="14" y="4" width="7" height="16" rx="1.5"/><path d="M10 8h4"/><path d="M10 16h4"/></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-right:12px;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">document</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><button type="button" class="chat-context-pill" id="chat-context-pill" title="Chat context" hidden><span class="chat-context-dot"></span><span id="chat-context-pill-label">0%</span></button><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-compact-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="M4 7h16"/><path d="M7 12h10"/><path d="M10 17h4"/></svg></span><span class="export-compact-pill">Compact</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 class="export-dropdown-item dropdown-item-danger" id="export-delete-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="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></span><span>Delete Chat</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">New chat ready.</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 = 'Pick a model if you want, or just type.';
|
|
}
|
|
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>
|
|
<div id="plan-mode-status" class="plan-mode-status" hidden>
|
|
<span class="plan-mode-status-label">Plan mode</span>
|
|
<button type="button" id="plan-mode-status-toggle" class="plan-mode-status-toggle" aria-label="Turn off plan mode" aria-pressed="true">
|
|
<span></span>
|
|
</button>
|
|
</div>
|
|
<!-- 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">
|
|
<div class="model-picker-search-wrap">
|
|
<input type="text" id="model-picker-search" placeholder="Search models..." autocomplete="off" aria-label="Search models">
|
|
<button type="button" class="model-picker-refresh-btn" id="model-picker-refresh-btn" title="Refresh model picker" aria-label="Refresh model picker">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
|
|
</button>
|
|
</div>
|
|
<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>
|
|
<script nonce="{{CSP_NONCE}}">
|
|
(function(){
|
|
function applyDefault(dc) {
|
|
if (!dc || !dc.model) return false;
|
|
window.__odysseusDefaultChat = dc;
|
|
var label = document.getElementById('model-picker-label');
|
|
if (!label) return false;
|
|
label.textContent = String(dc.model).split('/').pop();
|
|
label.title = dc.model;
|
|
return true;
|
|
}
|
|
try {
|
|
var dc = JSON.parse(localStorage.getItem('odysseus-default-chat-cache') || 'null');
|
|
if (applyDefault(dc)) return;
|
|
} catch (_) {}
|
|
fetch('/api/default-chat', { credentials: 'same-origin' })
|
|
.then(function(r){ return r.ok ? r.json() : null; })
|
|
.then(function(dc){
|
|
if (!applyDefault(dc)) return;
|
|
try { localStorage.setItem('odysseus-default-chat-cache', JSON.stringify(dc)); } catch (_) {}
|
|
})
|
|
.catch(function(){});
|
|
})();
|
|
</script>
|
|
</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>
|
|
<button type="button" class="overflow-menu-item" id="overflow-workspace-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="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
|
</svg>
|
|
<span>Workspace</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>
|
|
<!-- Workspace indicator (hidden until a folder is set) -->
|
|
<button type="button" class="input-icon-btn tool-indicator" title="Workspace - click to clear" id="workspace-indicator-btn" aria-label="Clear workspace" style="display:none;">
|
|
<svg class="workspace-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="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>
|
|
<span style="font-size:11px;margin-left:2px;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" id="workspace-indicator-name"></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>
|
|
<!-- 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"><rect x="3" y="4" width="7" height="16" rx="1.5"/><rect x="14" y="4" width="7" height="16" rx="1.5"/><path d="M10 8h4"/><path d="M10 16h4"/></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;">
|
|
<input type="checkbox" id="plan-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">↺ 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); 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">⚙</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="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.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
|
|
<span>Add Models</span>
|
|
</button>
|
|
<button class="settings-nav-item" data-settings-tab="added-models">
|
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
|
<span>Added 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>
|
|
<span class="adm-model-logo" id="set-defaultEpSelect-logo" style="display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;flex-shrink:0;opacity:0.9;color:var(--fg);"></span>
|
|
<select id="set-defaultEpSelect" class="settings-select"></select>
|
|
</div>
|
|
<div class="settings-row">
|
|
<label class="settings-label">Model</label>
|
|
<span class="adm-model-logo" id="set-defaultModelSelect-logo" style="display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;flex-shrink:0;opacity:0.9;color:var(--fg);"></span>
|
|
<select id="set-defaultModelSelect" class="settings-select"></select>
|
|
</div>
|
|
<div class="settings-row" style="align-items:flex-start;">
|
|
<label class="settings-label" style="margin-top:6px;">Fallbacks</label>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:6px;">
|
|
<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>
|
|
</div>
|
|
<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>
|
|
<span class="adm-model-logo" id="set-utilityEpSelect-logo" style="display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;flex-shrink:0;opacity:0.9;color:var(--fg);"></span>
|
|
<select id="set-utilityEpSelect" class="settings-select"><option value="">—</option></select>
|
|
</div>
|
|
<div class="settings-row">
|
|
<label class="settings-label">Model</label>
|
|
<span class="adm-model-logo" id="set-utilityModelSelect-logo" style="display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;flex-shrink:0;opacity:0.9;color:var(--fg);"></span>
|
|
<select id="set-utilityModelSelect" class="settings-select"><option value="">—</option></select>
|
|
</div>
|
|
<div class="settings-row" style="align-items:flex-start;">
|
|
<label class="settings-label" style="margin-top:6px;">Fallbacks</label>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:6px;">
|
|
<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>
|
|
</div>
|
|
<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>
|
|
<span class="adm-model-logo" id="set-vlModelSelect-logo" style="display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;flex-shrink:0;opacity:0.9;color:var(--fg);"></span>
|
|
<select id="set-vlModelSelect" class="settings-select"><option value="">Auto-detect</option></select>
|
|
</div>
|
|
<div class="settings-row" style="align-items:flex-start;">
|
|
<label class="settings-label" style="margin-top:6px;">Fallbacks</label>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:6px;">
|
|
<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>
|
|
</div>
|
|
<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, more settings under <a href="#" data-go-settings-tab="search" style="color:var(--accent, var(--red));text-decoration:underline;font-weight:600;">Search →</a></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>
|
|
<span class="adm-model-logo" id="set-researchModel-logo" style="display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;flex-shrink:0;opacity:0.9;color:var(--fg);"></span>
|
|
<select id="set-researchModel" class="settings-select">
|
|
<option value="">Same as chat</option>
|
|
</select>
|
|
</div>
|
|
<div id="set-researchMsg" style="font-size:11px;color:color-mix(in srgb, var(--fg) 45%, transparent);margin-top:2px;"></div>
|
|
</div>
|
|
</div>
|
|
<!-- Agent card moved to the Agent Tools tab. -->
|
|
<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"><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"><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>
|
|
<!-- Teacher Model settings card hidden as part of the 2.0
|
|
"harden the core" pass. The escalation flow is dormant when
|
|
`teacher_model` is unset (its default), so the backend keeps
|
|
working for anyone who wired it via `manage_settings` /
|
|
settings backup. Re-add this card to surface the toggle
|
|
again once the core experience is faster. -->
|
|
<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">Used when AI drafts email replies. Keep this email-specific: greetings, sign-off, tone, and length.</div>
|
|
<div class="settings-col">
|
|
<textarea id="set-email-style" rows="6" class="settings-select" style="font-family:inherit;resize:none" 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;display:inline-flex;align-items:center;gap:5px;"><svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 0L14.59 8.41L23 12L14.59 15.59L12 24L9.41 15.59L1 12L9.41 8.41Z"/></svg>Extract from Sent (15 emails)</button>
|
|
<button class="admin-btn-add" id="set-email-style-save">Save</button>
|
|
</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:2px;flex-shrink:0;position:relative;top:2px;display:inline-flex;align-items:center;gap:4px;">
|
|
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>Test
|
|
</button>
|
|
</div>
|
|
<div class="settings-row">
|
|
<label class="settings-label" title="How many web search results to fetch per query">Results per query</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 (optional)" class="settings-select">
|
|
</div>
|
|
<div id="set-searchKeyRow" class="settings-row" style="display:none;">
|
|
<label class="settings-label">API Key</label>
|
|
<div style="position:relative;flex:1;display:flex;align-items:center;">
|
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="position:absolute;left:9px;top:50%;transform:translateY(-50%);opacity:0.55;pointer-events:none;"><path d="M21 2l-9.6 9.6"/><circle cx="7.5" cy="15.5" r="5.5"/><path d="M15.5 7.5l3 3"/></svg>
|
|
<input id="set-searchApiKey" type="password" placeholder="API key" class="settings-select" style="flex:1;padding-left:28px;">
|
|
</div>
|
|
</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" style="align-items:flex-start;">
|
|
<label class="settings-label" style="margin-top:6px;" title="Providers tried in order when the primary fails or hits a rate limit">Fallbacks</label>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:6px;">
|
|
<div class="settings-fallbacks" id="set-searchFallbackChain"></div>
|
|
<button type="button" class="settings-fallback-add" id="set-searchAddFallback" title="Add a search provider to try if the primary fails">+ Add fallback</button>
|
|
</div>
|
|
</div>
|
|
<div id="set-searchHint" class="admin-toggle-sub"></div>
|
|
<div id="set-searchMsg" style="font-size:11px;"></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>Deep Research</h2>
|
|
<div class="admin-toggle-sub" style="margin-bottom:8px">Deep Research runtime settings. Default Model is picked in <a href="#" data-go-settings-tab="ai" style="color:var(--accent, var(--red));text-decoration:underline;font-weight:600;">AI Defaults →</a></div>
|
|
<div class="settings-col">
|
|
<div class="settings-row">
|
|
<label class="settings-label">Search</label>
|
|
<span style="margin-left:auto;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;flex-shrink:0;color:var(--fg);" id="set-researchSearch-logo"></span>
|
|
<select id="set-researchSearch" class="settings-select" style="width:358.5px;flex:0 0 auto;max-width:calc(100% - 24px);">
|
|
<option value="" data-search-logo="">Same as web search</option>
|
|
<option value="searxng" data-search-logo="searxng">SearXNG</option>
|
|
<option value="duckduckgo" data-search-logo="duckduckgo">DuckDuckGo</option>
|
|
<option value="tavily" data-search-logo="tavily">Tavily</option>
|
|
<option value="brave" data-search-logo="brave">Brave</option>
|
|
<option value="google" data-search-logo="google_pse">Google</option>
|
|
<option value="serper" data-search-logo="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:382.5px;flex:0 0 auto;margin-left:auto;">
|
|
</div>
|
|
<div class="settings-row">
|
|
<label class="settings-label">Extract Timeout</label>
|
|
<div style="position:relative;width:382.5px;flex:0 0 auto;margin-left:auto;">
|
|
<input id="set-researchExtractTimeout" type="text" inputmode="numeric" placeholder="90 sec" class="settings-select" style="width:100%;padding-right:30px;">
|
|
<span title="How long the researcher waits for a single URL to fetch and extract before giving up on it. Slow sites get skipped. Default 90 seconds." style="position:absolute;right:8px;top:50%;transform:translateY(-50%);width:16px;height:16px;border-radius:50%;border:1px solid var(--border);display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;opacity:0.55;cursor:help;user-select:none;">?</span>
|
|
</div>
|
|
</div>
|
|
<div class="settings-row">
|
|
<label class="settings-label">Extract Parallel</label>
|
|
<div style="position:relative;width:382.5px;flex:0 0 auto;margin-left:auto;">
|
|
<input id="set-researchExtractConcurrency" type="text" inputmode="numeric" placeholder="3" class="settings-select" style="width:100%;padding-right:30px;">
|
|
<span title="How many URLs the researcher fetches and extracts in parallel. Higher is faster but uses more memory/CPU. Default 3." style="position:absolute;right:8px;top:50%;transform:translateY(-50%);width:16px;height:16px;border-radius:50%;border:1px solid var(--border);display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;opacity:0.55;cursor:help;user-select:none;">?</span>
|
|
</div>
|
|
</div>
|
|
<div class="settings-row">
|
|
<label class="settings-label">Timeout</label>
|
|
<input id="set-researchRunTimeout" type="text" inputmode="numeric" placeholder="1800 sec (0 = no limit)" class="settings-select" style="width:382.5px;flex:0 0 auto;margin-left:auto;">
|
|
</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 style="display:flex;align-items:center;gap:8px;"><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<span style="flex:1"></span><button type="button" class="vis-reset-btn" data-vis-reset title="Reset this section to defaults" aria-label="Reset Sidebar to defaults" style="background:none;border:none;padding:2px 4px;cursor:pointer;color:inherit;opacity:0.55;display:inline-flex;align-items:center;"><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="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg></button></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="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"><rect x="3" y="4" width="7" height="16" rx="1.5"/><rect x="14" y="4" width="7" height="16" rx="1.5"/><path d="M10 8h4"/><path d="M10 16h4"/></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 & 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 style="display:flex;align-items:center;gap:8px;"><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<span style="flex:1"></span><button type="button" class="vis-reset-btn" data-vis-reset title="Reset this section to defaults" aria-label="Reset Chat Area to defaults" style="background:none;border:none;padding:2px 4px;cursor:pointer;color:inherit;opacity:0.55;display:inline-flex;align-items:center;"><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="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg></button></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 & 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="M4 6h16"/><path d="M4 10h8"/></svg></span>
|
|
<span class="vis-label">Full-width chat <span class="vis-hint">Use the full window width (desktop)</span></span>
|
|
<input type="checkbox" data-ui-key="chat-fullwidth"><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 & 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 <think> 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 style="display:flex;align-items:center;gap:8px;"><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<span style="flex:1"></span><button type="button" class="vis-reset-btn" data-vis-reset title="Reset this section to defaults" aria-label="Reset Chat Bar to defaults" style="background:none;border:none;padding:2px 4px;cursor:pointer;color:inherit;opacity:0.55;display:inline-flex;align-items:center;"><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="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg></button></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 & system prompt</span></span>
|
|
<input type="checkbox" checked data-ui-key="preset-mini-btn"><span class="vis-switch"></span>
|
|
</label>
|
|
</div>
|
|
</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="vis-reset-btn" id="shortcuts-reset-btn" title="Reset shortcuts to defaults" aria-label="Reset shortcuts to defaults" style="background:none;border:none;padding:2px 4px;cursor:pointer;color:inherit;opacity:0.55;display:inline-flex;align-items:center;"><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="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg></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:12px 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" 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.3" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M12 15.5A3.5 3.5 0 1 0 12 8a3.5 3.5 0 0 0 0 7.5Z"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 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 1 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 1 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 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06A2 2 0 1 1 7.04 4.3l.06.06A1.65 1.65 0 0 0 8.92 4a1.65 1.65 0 0 0 1-1.51V2a2 2 0 1 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 1 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 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z"/></svg>Email Settings</h2>
|
|
<div class="settings-row" style="align-items:center;">
|
|
<div class="admin-toggle-sub" style="margin:0;flex:1;">Auto reply, newsletter unsubscribe, and writing style live in the Email window.</div>
|
|
<button class="admin-btn-add" id="set-email-open-library-settings" style="display:inline-flex;align-items:center;gap:6px;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="opacity:0.7"><path d="M9 18l6-6-6-6"/></svg>Open Email Settings</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="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" style="display:inline-flex;align-items:center;gap:6px;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="opacity:0.7"><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>Open 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" style="display:inline-flex;align-items:center;gap:6px;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="opacity:0.7"><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>Open Tasks</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- ═══ REMINDERS TAB ═══ -->
|
|
<div data-settings-panel="reminders" class="hidden">
|
|
<div class="admin-card">
|
|
<h2 style="display:flex;align-items:center;gap:8px;"><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<span style="flex:1"></span><span id="set-reminder-test-msg" style="font-size:11px;font-weight:normal;"></span><button class="admin-btn-sm" id="set-reminder-test-btn" style="font-size:11px;font-weight:normal;display:inline-flex;align-items:center;gap:4px;"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>Test</button></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>
|
|
<option value="webhook" id="set-reminder-channel-webhook-opt">Webhook</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-webhook-intg-row" class="settings-row" style="display:none">
|
|
<label class="settings-label">Integration</label>
|
|
<select id="set-reminder-webhook-intg" class="settings-select"></select>
|
|
</div>
|
|
<div id="set-reminder-webhook-template-row" class="settings-row" style="display:none;align-items:flex-start">
|
|
<label class="settings-label" style="padding-top:6px">Payload</label>
|
|
<textarea id="set-reminder-webhook-template" class="settings-select" rows="3" style="font-family:inherit;resize:vertical;flex:1" placeholder='{"content": "{{title}}: {{message}}"}'></textarea>
|
|
</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, ntfy, and webhook reminders instead of just the raw note content.</div>
|
|
<div class="settings-col">
|
|
<div class="settings-row">
|
|
<label class="settings-label" title="Optional — write the reminder in the voice of a saved character">Persona</label>
|
|
<select id="set-reminder-llm-persona" class="settings-select" style="flex:1;">
|
|
<option value="">Default (warm, neutral)</option>
|
|
</select>
|
|
</div>
|
|
<div style="font-size:11px;opacity:0.7;margin-top:2px;">
|
|
<a href="#" data-open-prompt-modal style="color:var(--accent, var(--red));text-decoration:underline;font-weight:600;">Edit persona settings here →</a>
|
|
</div>
|
|
<div id="set-reminder-llm-persona-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="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>
|
|
|
|
<!-- ═══ 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="M12 15v3m-3-3h6M12 3v2m0 16v-2M4.93 4.93l1.41 1.41m11.32 11.32l1.41 1.41M3 12h2m16 0h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/><circle cx="12" cy="12" r="3"/></svg>Model Defaults</h2>
|
|
<div class="admin-toggle-row">
|
|
<div>
|
|
<div class="admin-toggle-label">Share defaults with users</div>
|
|
<div class="admin-toggle-sub">When on, users without a personal default inherit the global default model (only if those models are allowed for them).</div>
|
|
</div>
|
|
<label class="admin-switch"><input type="checkbox" id="adm-shareDefaultsToggle"><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">
|
|
<input id="adm-newPassword" type="password" placeholder="Password">
|
|
<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">
|
|
|
|
<!-- ── Local card ─────────────────────────────────────────── -->
|
|
<div class="admin-card">
|
|
<h2 style="display:flex;align-items:center;gap:8px;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M12 5v14"/><path d="M5 12h14"/></svg>Add Local Models <span style="opacity:0.45;font-weight:normal;font-size:0.82em">(Endpoint)</span>
|
|
<span style="flex:1"></span>
|
|
<button class="admin-btn-sm" id="adm-epLocalTestBtn" style="font-size:11px;font-weight:normal;display:inline-flex;align-items:center;gap:4px;">
|
|
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>Test
|
|
</button>
|
|
<div style="position:relative;display:inline-block;">
|
|
<button class="admin-btn-sm" id="adm-epLocalMoreBtn" title="More options" aria-haspopup="true" aria-expanded="false" style="font-size:11px;font-weight:normal;padding:4px 8px;line-height:1;">
|
|
<svg width="14" height="4" viewBox="0 0 14 4" fill="currentColor"><circle cx="2" cy="2" r="1.4"/><circle cx="7" cy="2" r="1.4"/><circle cx="12" cy="2" r="1.4"/></svg>
|
|
</button>
|
|
<div id="adm-epLocalMoreMenu" style="display:none;position:absolute;top:calc(100% + 4px);right:0;z-index:50;min-width:170px;padding:4px;background:var(--panel,var(--bg));border:1px solid var(--border);border-radius:8px;box-shadow:0 6px 20px rgba(0,0,0,0.22);flex-direction:column;gap:1px;">
|
|
<button class="admin-btn-sm adm-more-item" id="adm-epDiscoverBtn" title="Scan your network for running model servers" style="background:none;border:0;border-radius:5px;padding:7px 9px;display:flex;align-items:center;gap:8px;width:100%;text-align:left;font-size:12px;font-weight:normal;color:var(--fg);cursor:pointer;">
|
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>Scan network
|
|
</button>
|
|
<button class="admin-btn-sm adm-more-item" id="adm-epOllamaBtn" title="Fill the default Ollama endpoint" style="background:none;border:0;border-radius:5px;padding:7px 9px;display:flex;align-items:center;gap:8px;width:100%;text-align:left;font-size:12px;font-weight:normal;color:var(--fg);cursor:pointer;"><span class="adm-ollama-logo" style="display:inline-flex;width:13px;height:13px;"></span>Add Ollama</button>
|
|
<button class="admin-btn-sm adm-more-item" id="adm-epLocalKeyBtn" title="Show / hide the API key field" aria-expanded="false" aria-controls="adm-epLocalApiKey-row" style="background:none;border:0;border-radius:5px;padding:7px 9px;display:flex;align-items:center;gap:8px;width:100%;text-align:left;font-size:12px;font-weight:normal;color:var(--fg);cursor:pointer;">
|
|
<svg 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 2l-9.6 9.6"/><circle cx="7.5" cy="15.5" r="5.5"/><path d="M15.5 7.5l3 3"/></svg>API key
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</h2>
|
|
<div class="admin-toggle-sub" style="margin:0 0 10px 2px;">Add a local model server (Ollama, llama.cpp, vLLM).</div>
|
|
<div class="adm-add-section">
|
|
<div class="admin-model-form">
|
|
<div class="admin-model-form-row">
|
|
<div class="adm-fused-group" style="display:flex;flex:1 1 180px;min-width:0;">
|
|
<select id="adm-epLocalType" style="padding:5px;width:66px;flex-shrink:0;border-top-right-radius:0;border-bottom-right-radius:0;border-right:0;">
|
|
<option value="llm" selected>LLM</option>
|
|
<option value="image">Image</option>
|
|
</select>
|
|
<input id="adm-epLocalUrl" type="text" placeholder="Paste endpoint URL, e.g. http://localhost:11434/v1" style="flex:1;min-width:0;border-top-left-radius:0;border-bottom-left-radius:0;">
|
|
</div>
|
|
<button class="admin-btn-add" id="adm-epLocalAddBtn" style="min-width:55px;text-align:center;display:inline-flex;align-items:center;justify-content:center;gap:4px;flex-shrink:0;">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>Add
|
|
</button>
|
|
</div>
|
|
<div class="admin-model-form-row" id="adm-epLocalApiKey-row" style="display:none;">
|
|
<input id="adm-epLocalApiKey" type="password" placeholder="API key (optional — for protected local endpoints)" autocomplete="off" style="flex:1">
|
|
</div>
|
|
<div id="adm-epLocalMsg" class="adm-ep-inline-msg"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── API card ───────────────────────────────────────────── -->
|
|
<div class="admin-card">
|
|
<h2 style="display:flex;align-items:center;gap:8px;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><circle cx="11" cy="12" r="8"/><path d="M19 5v6"/><path d="M16 8h6"/><path d="M7 12h8"/></svg>Add API Models <span style="opacity:0.45;font-weight:normal;font-size:0.82em">(Endpoint)</span>
|
|
<span style="flex:1"></span>
|
|
<button class="admin-btn-sm" id="adm-epApiTestBtn" style="font-size:11px;font-weight:normal;display:inline-flex;align-items:center;gap:4px;">
|
|
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>Test
|
|
</button>
|
|
<button class="admin-btn-sm hidden" id="adm-epApiCancelTestBtn" style="font-size:11px;font-weight:normal;">Cancel</button>
|
|
<div style="position:relative;display:inline-block;">
|
|
<button class="admin-btn-sm" id="adm-epApiMoreBtn" title="More options" aria-haspopup="true" aria-expanded="false" style="font-size:11px;font-weight:normal;padding:4px 8px;line-height:1;">
|
|
<svg width="14" height="4" viewBox="0 0 14 4" fill="currentColor"><circle cx="2" cy="2" r="1.4"/><circle cx="7" cy="2" r="1.4"/><circle cx="12" cy="2" r="1.4"/></svg>
|
|
</button>
|
|
<div id="adm-epApiMoreMenu" style="display:none;position:absolute;top:calc(100% + 4px);right:0;z-index:50;min-width:200px;padding:4px;background:var(--panel,var(--bg));border:1px solid var(--border);border-radius:8px;box-shadow:0 6px 20px rgba(0,0,0,0.22);flex-direction:column;gap:1px;">
|
|
<div style="font-size:10px;text-transform:uppercase;letter-spacing:0.5px;opacity:0.55;padding:6px 9px 2px;">Connection mode</div>
|
|
<button class="admin-btn-sm adm-more-item adm-kind-opt" data-kind="proxy" style="background:none;border:0;border-radius:5px;padding:7px 9px;display:flex;align-items:center;gap:8px;width:100%;text-align:left;font-size:12px;font-weight:normal;color:var(--fg);cursor:pointer;">
|
|
<svg class="adm-kind-check" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
|
<span>Proxy</span>
|
|
<span style="margin-left:auto;opacity:0.5;font-size:10px;">routed via server</span>
|
|
</button>
|
|
<button class="admin-btn-sm adm-more-item adm-kind-opt" data-kind="api" style="background:none;border:0;border-radius:5px;padding:7px 9px;display:flex;align-items:center;gap:8px;width:100%;text-align:left;font-size:12px;font-weight:normal;color:var(--fg);cursor:pointer;">
|
|
<svg class="adm-kind-check" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="visibility:hidden;"><polyline points="20 6 9 17 4 12"/></svg>
|
|
<span>API (direct)</span>
|
|
<span style="margin-left:auto;opacity:0.5;font-size:10px;">browser→provider</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</h2>
|
|
<div class="admin-toggle-sub" style="margin:0 0 10px 2px;">Connect a cloud provider (OpenAI, Anthropic, DeepSeek, OpenRouter, etc.).</div>
|
|
<div class="adm-add-section">
|
|
<div class="admin-model-form">
|
|
<div class="admin-model-form-row">
|
|
<div class="adm-provider-picker adm-provider-combo" id="adm-provider-picker" style="flex:1 1 220px;min-width:0;margin-bottom:0;">
|
|
<button type="button" class="adm-provider-btn" id="adm-provider-btn" title="Pick provider" style="border-top-right-radius:0;border-bottom-right-radius:0;border-top-left-radius:6px;border-bottom-left-radius:6px;border-left:1px solid var(--border);border-right:1px solid var(--border);">
|
|
<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>
|
|
<input id="adm-epUrl" type="text" placeholder="Base URL or pick provider" autocomplete="off" style="border-left:0;border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:6px;border-bottom-right-radius:6px;">
|
|
<div class="adm-provider-menu hidden" id="adm-provider-menu"></div>
|
|
</div>
|
|
</div>
|
|
<select id="adm-epKind" style="display:none">
|
|
<option value="proxy">proxy</option>
|
|
<option value="api" selected>api</option>
|
|
</select>
|
|
<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="copilot" data-logo="github" data-auth-flow="copilot">GitHub Copilot</option>
|
|
<option value="chatgpt-subscription" data-logo="openai" data-auth-flow="chatgpt-subscription">ChatGPT Subscription</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>
|
|
<option value="https://opencode.ai/zen/v1" data-logo="opencode">OpenCode Zen</option>
|
|
<option value="https://opencode.ai/zen/go/v1" data-logo="opencode">OpenCode Go</option>
|
|
<option value="https://api.z.ai/api/coding/paas/v4" data-logo="zhipu">Z.AI Coding Plan</option>
|
|
<option value="https://integrate.api.nvidia.com/v1" data-logo="nvidia">NVIDIA</option>
|
|
</select>
|
|
<div class="admin-model-form-row" id="adm-epApiKey-row">
|
|
<div style="position:relative;flex:1;display:flex;align-items:center;">
|
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="position:absolute;left:9px;top:50%;transform:translateY(-50%);opacity:0.55;pointer-events:none;"><path d="M21 2l-9.6 9.6"/><circle cx="7.5" cy="15.5" r="5.5"/><path d="M15.5 7.5l3 3"/></svg>
|
|
<input id="adm-epApiKey" type="password" placeholder="API key, e.g. sk-proj-AbCdEf…" autocomplete="off" style="flex:1;padding-left:28px;height:32px;box-sizing:border-box;">
|
|
</div>
|
|
<button class="admin-btn-add" id="adm-epAddBtn" style="height:32px;min-width:55px;text-align:center;display:inline-flex;align-items:center;justify-content:center;gap:4px;flex-shrink:0;box-sizing:border-box;">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>Add
|
|
</button>
|
|
</div>
|
|
<div id="adm-epApiMsg" class="adm-ep-inline-msg"></div>
|
|
<div id="adm-deviceAuthStatus" class="adm-ep-inline-msg" style="min-height:0;margin-top:0;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- ═══ ADDED MODELS TAB ═══ -->
|
|
<div data-settings-panel="added-models" class="hidden">
|
|
<div class="admin-card">
|
|
<h2 style="display:flex;align-items:center;gap:8px;"><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"><polyline points="20 6 9 17 4 12"/></svg>Added Models <span style="opacity:0.45;font-weight:normal;font-size:0.82em">(Endpoints)</span>
|
|
<span style="flex:1"></span>
|
|
<button class="admin-btn-sm" id="adm-epProbeAllBtn" title="Re-test every endpoint and refresh online status" style="font-size:11px;font-weight:normal;display:inline-flex;align-items:center;gap:4px;">
|
|
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>Probe
|
|
</button>
|
|
<button class="admin-btn-sm" id="adm-epClearOfflineBtn" title="Remove all endpoints currently marked offline" style="font-size:11px;font-weight:normal;display:inline-flex;align-items:center;gap:4px;opacity:0.85;">
|
|
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><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"/></svg>Clear offline <span id="adm-epOfflineCount" style="opacity:0.6;margin-left:2px;"></span>
|
|
</button>
|
|
</h2>
|
|
<div class="admin-toggle-sub" style="margin-bottom:12px">Endpoints you've connected. Probe re-tests them all; Clear offline removes the dead ones.</div>
|
|
<div class="adm-ep-section">
|
|
<div class="adm-ep-section-head" style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;opacity:0.7;margin-bottom:6px;display:inline-flex;align-items:center;gap:5px;">
|
|
<svg width="11" height="11" 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="14" rx="2"/><path d="M8 21h8"/><path d="M12 17v4"/></svg>Local
|
|
</div>
|
|
<div id="adm-epList-local"><div class="admin-empty">No local endpoints yet.</div></div>
|
|
</div>
|
|
<div class="adm-ep-section" style="margin-top:18px;">
|
|
<div class="adm-ep-section-head" style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;opacity:0.7;margin-bottom:6px;display:inline-flex;align-items:center;gap:5px;">
|
|
<svg width="11" height="11" 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"/><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>API
|
|
</div>
|
|
<div id="adm-epList-api"><div class="admin-empty">No API endpoints yet.</div></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>Integrations</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:right;padding:8px 0;">
|
|
<button type="button" class="admin-btn-add" id="unified-intg-add-btn" style="text-align:center;display:inline-flex;align-items:center;justify-content:center;gap:5px;flex-shrink:0;"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" 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>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>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 class="settings-row">
|
|
<label class="settings-label">Max steps per message</label>
|
|
<input id="set-agentMaxRounds" type="text" inputmode="numeric" placeholder="20" 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>
|
|
<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" id="settings-system-logs-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" class="settings-system-logs-svg">
|
|
<polyline points="4 17 10 11 4 5"></polyline>
|
|
<line x1="12" y1="19" x2="20" y2="19"></line>
|
|
</svg>
|
|
Terminal Logs
|
|
</h2>
|
|
<div class="admin-toggle-sub settings-system-logs-toggle-sub">Live diagnostic logs and system output from the Odysseus process.</div>
|
|
|
|
<div class="settings-col settings-system-logs-col">
|
|
<!-- Controls row -->
|
|
<div class="settings-system-logs-controls">
|
|
<!-- Search input -->
|
|
<input type="text" id="log-search-input" placeholder="Search logs..." class="settings-system-logs-search">
|
|
|
|
<!-- Level select -->
|
|
<select id="log-level-select" class="settings-system-logs-select">
|
|
<option value="ALL">All Levels</option>
|
|
<option value="INFO">INFO</option>
|
|
<option value="WARNING">WARNING</option>
|
|
<option value="ERROR">ERROR</option>
|
|
<option value="DEBUG">DEBUG</option>
|
|
</select>
|
|
|
|
<!-- Limit select -->
|
|
<select id="log-limit-select" class="settings-system-logs-select">
|
|
<option value="100">100 lines</option>
|
|
<option value="200" selected>200 lines</option>
|
|
<option value="500">500 lines</option>
|
|
<option value="1000">1000 lines</option>
|
|
</select>
|
|
|
|
<!-- Refresh Button -->
|
|
<button type="button" class="admin-btn-sm" id="log-refresh-btn">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="settings-system-logs-refresh-svg"><path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67"/></svg>
|
|
Refresh
|
|
</button>
|
|
|
|
<!-- Auto-refresh switch -->
|
|
<div class="settings-system-logs-autopoll-container">
|
|
<label class="admin-switch" title="Auto-polling every 3 seconds">
|
|
<input type="checkbox" id="log-auto-refresh-toggle">
|
|
<span class="admin-slider"></span>
|
|
</label>
|
|
<span>Auto-poll</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Console container -->
|
|
<div id="log-console-container">
|
|
<div class="settings-system-logs-placeholder">Initializing logs terminal viewer...</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"><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;"><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.75"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>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">Delete 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" title="Delete all chats" aria-label="Delete all chats" style="display:inline-flex;align-items:center;gap:5px;white-space:nowrap;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>Delete</button>
|
|
</div>
|
|
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;">
|
|
<div>
|
|
<div class="admin-toggle-label">Delete 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" title="Delete all memory" aria-label="Delete all memory" style="display:inline-flex;align-items:center;gap:5px;white-space:nowrap;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>Delete</button>
|
|
</div>
|
|
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;">
|
|
<div>
|
|
<div class="admin-toggle-label">Delete 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" title="Delete all skills" aria-label="Delete all skills" style="display:inline-flex;align-items:center;gap:5px;white-space:nowrap;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>Delete</button>
|
|
</div>
|
|
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;">
|
|
<div>
|
|
<div class="admin-toggle-label">Delete all notes</div>
|
|
<div class="admin-toggle-sub">Every note, todo, and checklist.</div>
|
|
</div>
|
|
<button class="admin-btn-delete" data-wipe-kind="notes" title="Delete all notes" aria-label="Delete all notes" style="display:inline-flex;align-items:center;gap:5px;white-space:nowrap;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>Delete</button>
|
|
</div>
|
|
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;">
|
|
<div>
|
|
<div class="admin-toggle-label">Delete 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" title="Delete all tasks" aria-label="Delete all tasks" style="display:inline-flex;align-items:center;gap:5px;white-space:nowrap;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>Delete</button>
|
|
</div>
|
|
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;">
|
|
<div>
|
|
<div class="admin-toggle-label">Delete 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" title="Delete all documents" aria-label="Delete all documents" style="display:inline-flex;align-items:center;gap:5px;white-space:nowrap;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>Delete</button>
|
|
</div>
|
|
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;">
|
|
<div>
|
|
<div class="admin-toggle-label">Delete 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" title="Delete all gallery" aria-label="Delete all gallery" style="display:inline-flex;align-items:center;gap:5px;white-space:nowrap;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>Delete</button>
|
|
</div>
|
|
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;">
|
|
<div>
|
|
<div class="admin-toggle-label">Delete 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" title="Delete all calendar" aria-label="Delete all calendar" style="display:inline-flex;align-items:center;gap:5px;white-space:nowrap;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>Delete</button>
|
|
</div>
|
|
|
|
<hr style="border:0;border-top:1px solid color-mix(in srgb, #e55 25%, var(--border));margin:14px 0 10px;">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;">
|
|
<div>
|
|
<div class="admin-toggle-label" style="color:#e55;">Delete everything</div>
|
|
<div class="admin-toggle-sub">All eight categories above, in one go. Same effect as wiping each one in sequence.</div>
|
|
</div>
|
|
<button class="admin-btn-delete" data-wipe-kind="__all__" title="Delete every category" aria-label="Delete everything" style="display:inline-flex;align-items:center;gap:5px;white-space:nowrap;font-weight:600;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>Delete All</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?v=20260722memoryloading1"></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?v=20260715startupcalm2"></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?v=20260722emailfastindex1"></script>
|
|
<script type="module" src="/static/js/gallery.js?v=20260708match1"></script>
|
|
<script type="module" src="/static/js/chatRenderer.js?v=20260722emailfastindex1"></script>
|
|
<script type="module" src="/static/js/codeRunner.js"></script>
|
|
<script type="module" src="/static/js/chatStream.js?v=20260722emailfastindex1"></script>
|
|
<script type="module" src="/static/js/chat.js?v=20260801fix1"></script>
|
|
<script type="module" src="/static/js/cookbook.js"></script>
|
|
<script src="/static/js/cookbookSchedule.js"></script>
|
|
<script type="module" src="/static/js/search-chat.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?v=20260723compareicon1"></script>
|
|
<script type="module" src="/static/js/assistant.js"></script>
|
|
<script type="module" src="/static/app.js?v=20260723tasksbulkfeedback1"></script> <!-- app.js must be LAST -->
|
|
<script type="module" src="/static/js/init.js?v=20260715freshroot3"></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>
|