Add three chat profiles: Varaverk Assistant, General Chat, Code Sketcher

Explicit buttons rather than an automatic router. Misclassifying a Varaverk
question as chat produces a confident invention about the user's system, which
is exactly what retrieval exists to prevent — with buttons there is no hidden
heuristic to be wrong and the strict profile is the default you land on.

Only Varaverk Assistant retrieves; the other two would be carrying passages
that cannot help write a folder-copy script. Memory goes to all three, since
that is what lets chat know the setup without claiming authority over it.
History depth is per profile and set server-side: retrieval costs ~2500 of
16384, so the profiles that skip it can hold a real conversation. Code
Sketcher is told to flag flags it is unsure of, after it invented
rsync --no-overwrite.
This commit is contained in:
Gmer4Lfe
2026-08-03 17:36:24 -04:00
parent d0b3588f6c
commit 8d393a1e1c
3 changed files with 157 additions and 43 deletions
+62 -5
View File
@@ -83,6 +83,16 @@ if (is_dir('/var/log/varaverk')) {
.vv-ai-warn { color:#ffb74d !important; }
.vv-ai-bad { color:#e57 !important; }
/* ── Profiles ───────────────────────────────────────────────────────────── */
.vv-ai-profiles { display:flex; gap:6px; align-items:center; flex-wrap:wrap; }
.vv-ai-prof { background:#0e0e0e; border:1px solid #262626; color:#5a5a5a; font-size:11px;
padding:5px 12px; border-radius:4px; cursor:pointer; font-family:inherit; }
.vv-ai-prof:hover { color:#8a8a8a; border-color:#333; }
.vv-ai-prof.active { background:#152238; border-color:#2d4a6a; color:#9bd; }
.vv-ai-prof-hint { font-size:10px; color:#4a4a4a; margin-left:6px; flex:1; min-width:180px; }
.vv-ai-switch { text-align:center; font-size:10px; color:#3a3a3a; margin:10px 0;
border-top:1px dashed #1e1e1e; padding-top:8px; }
/* ── Chat ───────────────────────────────────────────────────────────────── */
.vv-ai-chat { border:1px solid #262626; border-radius:6px; background:#0b0b0b;
min-height:340px; max-height:60vh; overflow-y:auto; padding:14px; }
@@ -180,6 +190,13 @@ if (is_dir('/var/log/varaverk')) {
</div>
</div>
<div class="vv-ai-profiles">
<button class="vv-ai-prof active" data-prof="varaverk" type="button">Varaverk Assistant</button>
<button class="vv-ai-prof" data-prof="chat" type="button">General Chat</button>
<button class="vv-ai-prof" data-prof="code" type="button">Code Sketcher</button>
<span class="vv-ai-prof-hint" id="vv-ai-prof-hint"></span>
</div>
<div class="vv-ai-chat" id="vv-ai-chat">
<div class="vv-ai-empty">
Ask Varaverk about itself.<br>
@@ -238,11 +255,23 @@ if (is_dir('/var/log/varaverk')) {
<script>
(function () {
const API = '/plugins/varaverk/api/ai.php';
const MAXTURN = 3;
const POLL_MS = 1200;
const POLL_CEIL = 300000; // stop polling a worker that never wrote a terminal state
let history = []; // {role, content} — trimmed to MAXTURN pairs
// Server-side is the authority on retrieval and history depth; these are for the UI only.
// Always starts on varaverk — the strict profile is the one you land on, so a misuse costs a
// "the docs don't cover that" rather than an invented claim about the system.
const PROFILES = {
varaverk: { turns: 3, kind: true,
hint: 'Answers only from Varaverk\'s own docs, with sources. Says so when they don\'t cover it.' },
chat: { turns: 8, kind: false,
hint: 'Ordinary conversation. Knows your memory notes, but not the docs — it\'ll point you back here for specifics.' },
code: { turns: 4, kind: false,
hint: 'Drafts short scripts for Custom Scripts. First drafts — it flags flags it isn\'t sure of. Test before trusting.' },
};
let profile = 'varaverk';
let history = []; // {role, content} — trimmed per profile
let busy = false;
let lastSources = [];
@@ -463,9 +492,10 @@ if (is_dir('/var/log/varaverk')) {
try {
const body = new URLSearchParams({
action: 'ask',
profile: profile,
question: q,
history: JSON.stringify(history.slice(-MAXTURN * 2)),
kind: $('vv-ai-kind').value,
history: JSON.stringify(history.slice(-PROFILES[profile].turns * 2)),
kind: PROFILES[profile].kind ? $('vv-ai-kind').value : '',
think: $('vv-ai-think').checked ? '1' : '0',
});
res = fetch(API, {
@@ -516,7 +546,8 @@ if (is_dir('/var/log/varaverk')) {
if (j.status === 'done') {
addAnswer(j);
history.push({ role: 'assistant', content: j.answer });
if (history.length > MAXTURN * 2) history = history.slice(-MAXTURN * 2);
const cap = PROFILES[profile].turns * 2;
if (history.length > cap) history = history.slice(-cap);
fetch(API, { method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
body: new URLSearchParams({ action: 'clear', token }) }).catch(() => {});
@@ -547,6 +578,32 @@ if (is_dir('/var/log/varaverk')) {
if (s && s.path) vvAiOpen(s.path);
};
// ── Profiles ────────────────────────────────────────────────────────────
// Switching clears the conversation history sent to the model but leaves the transcript on
// screen. Carrying turns across a profile change would mean feeding cited, retrieval-grounded
// answers into a mode that has no retrieval — the model would keep referring to sources it can
// no longer see. The visible marker is so the transcript still reads honestly afterwards.
function setProfile(p) {
if (!PROFILES[p] || p === profile) return;
profile = p;
document.querySelectorAll('.vv-ai-prof').forEach(b =>
b.classList.toggle('active', b.dataset.prof === p));
$('vv-ai-prof-hint').textContent = PROFILES[p].hint;
$('vv-ai-kind').style.display = PROFILES[p].kind ? '' : 'none';
if (history.length) {
const label = document.querySelector('.vv-ai-prof[data-prof="' + p + '"]').textContent;
chat().appendChild(el('<div class="vv-ai-switch">switched to ' + esc(label)
+ ' — earlier turns are no longer carried</div>'));
scroll();
}
history = [];
$('vv-ai-input').focus();
}
document.querySelectorAll('.vv-ai-prof').forEach(b =>
b.addEventListener('click', () => setProfile(b.dataset.prof)));
$('vv-ai-prof-hint').textContent = PROFILES[profile].hint;
// ── Memory panel ────────────────────────────────────────────────────────
// Live character count against the cap, because the budget is the whole point: this text is
// prepended to every single turn and competes with retrieval for a 16k context.