One profile picker for both surfaces, and open where you left off
This commit is contained in:
@@ -1073,7 +1073,25 @@ Still the same two servers, two households, the same media stack running itself.
|
||||
<div id="vv-ai-dock">
|
||||
<div id="vv-ai-dock-body" style="display:none"></div>
|
||||
<div id="vv-ai-dock-bar">
|
||||
<span id="vv-ai-dock-chip" title="What the assistant will answer about — follows the view you have open">Assistant · Scheduler</span>
|
||||
<!-- The chip is now the trigger for the profile picker as well as the scope readout.
|
||||
#vv-ai-dock-chip stays the label span it always was, so every existing write to
|
||||
its textContent still lands; the button around it carries the decoration. -->
|
||||
<div class="vv-ai-picker" id="vv-ai-dock-picker">
|
||||
<button class="vv-ai-chip" id="vv-ai-dock-chipbtn" type="button"
|
||||
aria-haspopup="listbox" aria-expanded="false"
|
||||
title="What the assistant will answer about — follows the view you have open. Click to change profile.">
|
||||
<span id="vv-ai-dock-chip">Assistant · Scheduler</span><span class="vv-ai-chip-c">▾</span>
|
||||
</button>
|
||||
<div class="vv-ai-menu" id="vv-ai-dock-menu" role="listbox">
|
||||
<?php foreach (vv_ai_profiles_ui() as $key => $def): ?>
|
||||
<button class="vv-ai-opt" data-prof="<?= htmlspecialchars($key, ENT_QUOTES) ?>"
|
||||
type="button" role="option">
|
||||
<span class="vv-ai-opt-l"><?= htmlspecialchars($def['label']) ?></span>
|
||||
<span class="vv-ai-opt-h"><?= htmlspecialchars($def['hint']) ?></span>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" id="vv-ai-dock-input" autocomplete="off"
|
||||
placeholder="Ask about what is on screen…"
|
||||
onkeydown="if(event.key==='Enter'){event.preventDefault();vvAiDockSend();}">
|
||||
@@ -2358,6 +2376,7 @@ function vvClickCog(el) {
|
||||
|
||||
let vvAiProfile = 'varaverk';
|
||||
let vvAiScope = 'Scheduler';
|
||||
let vvAiScopeLabel = 'Scheduler'; // what the chip reads; the target is the id behind it
|
||||
let vvAiHist = []; // what the model is told; reset when the scope changes
|
||||
let vvAiChatId = ""; // the stored conversation this thread is appending to
|
||||
let vvAiBusy = false;
|
||||
@@ -2365,6 +2384,39 @@ let vvAiPoll = null;
|
||||
|
||||
function vvAiDockOn() { return !!document.getElementById('vv-ai-dock'); }
|
||||
|
||||
// ── Profile picker ───────────────────────────────────────────────────────────
|
||||
// The chip states the contract and the thing being asked about; clicking it changes the first
|
||||
// without moving the second. Same control as the Monitor card's, same stylesheet — the dock keeps
|
||||
// its own bar because that is a real difference, but which profile answers is not a per-surface
|
||||
// idea and should not have a per-surface control.
|
||||
//
|
||||
// Routes through vvAiDockScope() rather than assigning vvAiProfile, so a profile change gets the
|
||||
// history reset and the on-screen marker that a scope change already gets. Picking a new contract
|
||||
// mid-thread and silently feeding it the previous one's turns is the bug that rule exists for.
|
||||
(function vvAiPickerInit() {
|
||||
const picker = document.getElementById('vv-ai-dock-picker');
|
||||
if (!picker) return;
|
||||
const btn = document.getElementById('vv-ai-dock-chipbtn');
|
||||
const menu = document.getElementById('vv-ai-dock-menu');
|
||||
const close = () => { picker.classList.remove('open'); btn.setAttribute('aria-expanded', 'false'); };
|
||||
|
||||
btn.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
const open = picker.classList.toggle('open');
|
||||
btn.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
menu.querySelectorAll('.vv-ai-opt').forEach(o =>
|
||||
o.classList.toggle('active', o.dataset.prof === vvAiProfile));
|
||||
});
|
||||
menu.addEventListener('click', e => {
|
||||
const o = e.target.closest('.vv-ai-opt');
|
||||
if (!o) return;
|
||||
close();
|
||||
vvAiDockScope(o.dataset.prof, vvAiScopeLabel, vvAiScope);
|
||||
});
|
||||
document.addEventListener('click', e => { if (!picker.contains(e.target)) close(); });
|
||||
document.addEventListener('keydown', e => { if (e.key === 'Escape') close(); });
|
||||
})();
|
||||
|
||||
// Called by every view switch. Profile and scope are derived from what is open and shown on the
|
||||
// chip — never chosen, never hidden. If the user can see what it thinks it is looking at, a
|
||||
// wrong inference costs a glance instead of a confidently wrong answer.
|
||||
@@ -2379,6 +2431,7 @@ function vvAiDockScope(profile, label, target) {
|
||||
const had = vvAiHist.length > 0;
|
||||
vvAiProfile = profile;
|
||||
vvAiScope = target;
|
||||
vvAiScopeLabel = label; // kept so the picker can change profile without moving the scope
|
||||
// Short label from the registry, not a literal map. The map that used to be here was the
|
||||
// fifth place a profile got defined, and it silently fell back to "Assistant" for any id it
|
||||
// had not been told about — so a new profile would have shown up in the chip as the strict one.
|
||||
@@ -2400,7 +2453,9 @@ function vvAiDockScope(profile, label, target) {
|
||||
// The scope moved under text already typed. Not blocked — just never silent.
|
||||
const input = document.getElementById('vv-ai-dock-input');
|
||||
if (input.value.trim() !== '') {
|
||||
const chip = document.getElementById('vv-ai-dock-chip');
|
||||
// The button, not the label span inside it. The flash animates a background, and the
|
||||
// background moved to the button when the chip became the picker's trigger.
|
||||
const chip = document.getElementById('vv-ai-dock-chipbtn');
|
||||
chip.classList.remove('vv-ai-chip-flash');
|
||||
void chip.offsetWidth;
|
||||
chip.classList.add('vv-ai-chip-flash');
|
||||
|
||||
Reference in New Issue
Block a user