feat: local LLM multi-model, session search, cron proactive types, notifications, docs overhaul

Local LLM:
- user_settings.py: per-user hosts/models config (local_llm.json)
- routers/local_llm.py + static/local_llm.html: dedicated settings page
- llm_client.py: local OpenAI-compatible backend via httpx
- config.py: LOCAL_API_URL/KEY/MODEL + per-backend timeouts
- Active model shown near backend toggle (amber hint text)

Memory distillation:
- memory_distiller.py: DISTILL_BACKEND_MID/LONG .env overrides
- scheduler.py + notification.py: notify NC Talk after mid/long distill
- notification.py: outbound channel abstraction (NC Talk, extensible)

Session search:
- routers/files.py: GET /sessions/search?q= with excerpts grouped by date
- static/index.html + app.js: search UI in file sidebar with highlight
- _esc() helper to prevent XSS in search results

Proactive cron:
- cron_runner.py: new job types — message (send directly) and brief (LLM + send)
- Both support optional per-job channel override

Channels:
- routers/nextcloud_talk.py: consolidated using notification._send_nct_message()
- routers/auth.py: local backend status in /auth/status
- routers/chat.py: /backend returns {primary, fallback, local_model} object

UI / UX:
- Copy button for user messages (matching assistant)
- Autocomplete disabled on sensitive form fields
- settings.html: local model section replaced with link to /settings/local

Docs overhaul:
- MASTER.md hub + ARCH__SYSTEM/BACKENDS/PERSONA/CHANNELS/FUTURE.md
- ARCH__Intelligence_Layer.md replaced with redirect table
- CORTEX.md trimmed to vision only; README updated
- OPEN_WEBUI_API.md added to docs/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-04-05 20:53:06 -04:00
parent bd6532e93a
commit a4daebdc9b
33 changed files with 2985 additions and 486 deletions

View File

@@ -431,6 +431,8 @@
padding: 0;
font-size: 0.85em;
}
/* Syntax highlighting — app theme controls the pre background; hljs adds token colors */
.message.assistant pre code.hljs { background: transparent; padding: 0; }
.message.system {
align-self: center;
@@ -440,6 +442,80 @@
padding: 2px 0;
}
/* ── Tool call step cards (agent mode) ── */
.tool-calls-container {
display: flex;
flex-direction: column;
gap: 3px;
margin: 4px 0 6px;
align-self: stretch;
}
.tool-call {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
overflow: hidden;
font-size: 0.78rem;
}
.tool-call summary {
display: flex;
align-items: baseline;
gap: 0.5rem;
padding: 0.35rem 0.65rem;
cursor: pointer;
list-style: none;
user-select: none;
color: var(--muted);
}
.tool-call summary::-webkit-details-marker { display: none; }
.tool-call summary::before {
content: '▶';
font-size: 0.55rem;
color: var(--muted);
transition: transform 0.12s;
flex-shrink: 0;
}
.tool-call[open] summary::before { transform: rotate(90deg); }
.tool-call summary:hover { color: var(--text); background: rgba(255,255,255,0.03); }
.tc-name {
font-weight: 600;
color: var(--accent);
font-family: 'Courier New', monospace;
}
.tc-snippet {
color: var(--muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 36ch;
}
.tc-body {
padding: 0 0.65rem 0.5rem;
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.tc-section { display: flex; flex-direction: column; gap: 2px; }
.tc-label {
font-size: 0.68rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted);
}
.tc-body pre {
margin: 0;
background: var(--pre-bg);
border: 1px solid var(--border);
border-radius: 4px;
padding: 6px 8px;
font-size: 0.78rem;
white-space: pre-wrap;
word-break: break-word;
color: var(--text);
overflow-x: auto;
}
.message.error {
align-self: flex-start;
background: var(--error-bg);
@@ -451,7 +527,7 @@
.message.thinking { color: var(--muted); font-style: italic; }
/* Copy button */
.message.assistant { position: relative; }
.message.assistant, .message.user { position: relative; }
.copy-btn {
display: inline-flex;
@@ -471,7 +547,8 @@
transition: opacity 0.15s, color 0.15s, border-color 0.15s;
}
.message.assistant:hover .copy-btn { opacity: 1; }
.message.assistant:hover .copy-btn,
.message.user:hover .copy-btn { opacity: 1; }
.copy-btn:hover { color: var(--text); border-color: var(--muted); }
.copy-btn.copied { color: var(--success); border-color: var(--success-dim); }
@@ -807,22 +884,12 @@
flex-shrink: 0;
}
#file-modal-header select {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 5px;
color: var(--text);
font-size: 0.85rem;
padding: 4px 8px;
cursor: pointer;
}
#file-modal-title {
font-size: 0.9rem;
font-weight: 600;
color: var(--accent);
flex: 1;
}
.fm-spacer { flex: 1; }
.fm-btn {
background: var(--bg);
@@ -838,13 +905,153 @@
.fm-btn.active { color: var(--accent); border-color: var(--accent); }
.fm-btn.save { color: var(--accent); border-color: var(--inara-border); }
.fm-btn.save:hover { background: var(--inara-bg); }
#file-saved-msg {
font-size: 0.75rem;
color: #6abf6a;
opacity: 0;
transition: opacity 0.3s;
#file-modal-content {
flex: 1;
display: flex;
overflow: hidden;
}
/* ── File sidebar ── */
#file-sidebar-wrap {
width: 190px;
flex-shrink: 0;
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
background: var(--bg);
}
#file-sidebar {
flex: 1;
overflow-y: auto;
}
/* ── Session search (within sidebar) ── */
#session-search-wrap {
border-top: 1px solid var(--border);
padding: 8px 8px 10px;
}
#session-search-label {
font-size: 0.65rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
margin-bottom: 5px;
}
#session-search-row {
display: flex;
gap: 4px;
}
#session-search-input {
flex: 1;
min-width: 0;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text);
font-size: 0.78rem;
padding: 3px 6px;
}
#session-search-btn {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--muted);
font-size: 0.78rem;
padding: 3px 8px;
cursor: pointer;
}
#session-search-btn:hover { color: var(--accent); border-color: var(--accent); }
/* ── Session search results panel ── */
#session-search-results {
flex: 1;
overflow-y: auto;
padding: 12px 14px;
font-size: 0.82rem;
}
.sr-header { color: var(--muted); font-size: 0.72rem; margin-bottom: 10px; }
.sr-date {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--accent);
margin: 14px 0 4px;
}
.sr-date:first-of-type { margin-top: 0; }
.sr-excerpt {
background: var(--surface);
border-left: 2px solid var(--border);
border-radius: 0 4px 4px 0;
padding: 6px 10px;
margin-bottom: 6px;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-word;
color: var(--text);
}
.sr-excerpt mark {
background: rgba(139,92,246,0.25);
color: var(--accent);
border-radius: 2px;
padding: 0 1px;
}
.sr-empty, .sr-error { color: var(--muted); padding: 8px 0; }
.fg-header {
display: flex;
align-items: center;
gap: 0.3rem;
padding: 7px 10px 5px;
font-size: 0.68rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
cursor: pointer;
user-select: none;
}
.fg-header::before {
content: '▾';
font-size: 0.7rem;
transition: transform 0.15s;
}
.fg-header.collapsed::before { transform: rotate(-90deg); }
.fg-header.collapsed + .fg-items { display: none; }
.fg-items { display: flex; flex-direction: column; }
.file-item {
padding: 6px 10px 6px 16px;
cursor: pointer;
border-left: 2px solid transparent;
transition: background 0.1s, border-color 0.1s;
}
.file-item:hover { background: var(--surface); }
.file-item.active {
background: var(--inara-bg);
border-left-color: var(--accent);
}
.file-item.missing { opacity: 0.45; }
.fi-name {
font-size: 0.8rem;
color: var(--text);
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.file-item.active .fi-name { color: var(--accent); }
.fi-meta {
display: flex;
gap: 0.5rem;
margin-top: 2px;
font-size: 0.68rem;
color: var(--muted);
}
#file-saved-msg.show { opacity: 1; }
#file-modal-body {
flex: 1;
@@ -935,9 +1142,14 @@
cursor: pointer;
transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.ctx-btn:hover { color: var(--text); border-color: var(--muted); }
.ctx-btn.active { color: var(--accent); border-color: var(--accent); }
.ctx-btn.mem-on { color: var(--success); border-color: var(--success-dim); }
.ctx-btn:hover { color: var(--text); border-color: var(--muted); }
.ctx-btn.active { color: var(--accent); border-color: var(--accent); }
.ctx-btn.mem-on { color: var(--success); border-color: var(--success-dim); }
.ctx-btn.local-on { color: #f59e0b; border-color: #92400e; }
#backend-model-hint {
font-size: 0.68rem; color: #f59e0b; opacity: 0.8;
margin-top: 4px; word-break: break-all; line-height: 1.3;
}
#ctx-distill-status {
margin-top: 6px;
@@ -1173,6 +1385,48 @@
#auth-banner-close:hover { opacity: 1; }
/* ── Toasts ──────────────────────────────────────────────── */
#toast-container {
position: fixed;
bottom: 1.25rem;
right: 1.25rem;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 0.4rem;
z-index: 9999;
pointer-events: none;
}
.toast {
padding: 0.45rem 0.85rem;
border-radius: 6px;
font-size: 0.8rem;
font-weight: 500;
color: #fff;
background: #334155;
border: 1px solid #475569;
box-shadow: 0 4px 12px rgba(0,0,0,0.35);
opacity: 0;
transform: translateY(6px);
transition: opacity 0.18s ease, transform 0.18s ease;
pointer-events: none;
white-space: nowrap;
}
.toast.show { opacity: 1; transform: translateY(0); }
.toast.success { background: #14532d; border-color: #16a34a; }
.toast.error { background: #7f1d1d; border-color: #dc2626; }
/* Sessions backdrop — hidden by default, visible only as mobile drawer overlay */
#sessions-backdrop {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 98;
animation: backdrop-in 0.2s ease;
}
@keyframes backdrop-in { from { opacity: 0; } to { opacity: 1; } }
/* ── Mobile responsive ───────────────────────────────────── */
@media (max-width: 520px) {
header { padding: 8px 12px; gap: 8px; }
@@ -1233,6 +1487,36 @@
/* Larger touch targets */
#send, #stop { padding: 12px 14px; font-size: 1rem; }
/* File modal: sidebar collapses to a narrow strip */
#file-modal-inner { width: 100vw; height: 100dvh; border-radius: 0; }
#file-sidebar-wrap { width: 130px; }
.fi-meta { display: none; }
/* Sessions backdrop active on mobile */
#sessions-backdrop.open { display: block; }
/* Sessions panel → full-height drawer sliding in from the right */
#sessions-panel {
display: block !important; /* keep rendered so transition works */
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: min(300px, 85vw);
max-height: none;
height: 100%;
border-radius: 0;
border-top: none;
border-right: none;
border-bottom: none;
border-left: 1px solid var(--border);
transform: translateX(110%);
transition: transform 0.25s ease;
z-index: 99;
overflow-y: auto;
}
#sessions-panel.open { transform: translateX(0); }
}
/* ── Touch devices — no hover capability ─────────────────── */