Open a cited script in the editor instead of a read-only viewer

This commit is contained in:
Gmer4Lfe
2026-08-10 21:15:45 -04:00
parent 9adf760c8e
commit 33affc5a59
2 changed files with 49 additions and 5 deletions
+35 -5
View File
@@ -194,7 +194,11 @@ function vv_ai_chat_assets(): void {
.vv-ai-think.open { display:block; } .vv-ai-think.open { display:block; }
.vv-ai-src { margin-top:9px; border-top:1px solid #1c1c1c; padding-top:7px; } .vv-ai-src { margin-top:9px; border-top:1px solid #1c1c1c; padding-top:7px; }
.vv-ai-src-h { font-size:9px; letter-spacing:.07em; text-transform:uppercase; color:#3a3a3a; margin-bottom:4px; } .vv-ai-src-h { font-size:9px; letter-spacing:.07em; text-transform:uppercase; color:#3a3a3a;
margin-bottom:4px; cursor:pointer; user-select:none; }
.vv-ai-src-h:hover { color:#6a6a6a; }
.vv-ai-src-car { display:inline-block; width:10px; }
.vv-ai-src.collapsed .vv-ai-src-l { display:none; }
.vv-ai-src-i { font-size:11px; color:#5a5a5a; padding:2px 0; cursor:pointer; display:flex; gap:8px; } .vv-ai-src-i { font-size:11px; color:#5a5a5a; padding:2px 0; cursor:pointer; display:flex; gap:8px; }
.vv-ai-src-i:hover { color:#8a8a8a; } .vv-ai-src-i:hover { color:#8a8a8a; }
.vv-ai-src-n { color:#3a4a6a; font-family:monospace; flex-shrink:0; } .vv-ai-src-n { color:#3a4a6a; font-family:monospace; flex-shrink:0; }
@@ -556,7 +560,12 @@ vv_ai_profiles_script();
// path carrying a quote interpolated into an attribute is code execution, not a display bug. // path carrying a quote interpolated into an attribute is code execution, not a display bug.
function sourcesHtml(sources) { function sourcesHtml(sources) {
if (!sources || !sources.length) return ''; if (!sources || !sources.length) return '';
let h = '<div class="vv-ai-src"><div class="vv-ai-src-h">Sources</div>'; // Expanded by default and collapsible by choice, never the reverse. What the model was given
// is the first thing worth seeing when an answer is wrong, and hiding it behind a click makes
// a bad retrieval look like a bad model.
let h = `<div class="vv-ai-src"><div class="vv-ai-src-h" data-src-toggle>`
+ `<span class="vv-ai-src-car">▾</span>Sources (${sources.length})</div>`
+ `<div class="vv-ai-src-l">`;
sources.forEach((s, i) => { sources.forEach((s, i) => {
// A web result is a page on the internet, not a file in this install: it opens in a tab // A web result is a page on the internet, not a file in this install: it opens in a tab
// rather than in the source viewer, it leads with its title rather than its URL, and it // rather than in the source viewer, it leads with its title rather than its URL, and it
@@ -573,7 +582,7 @@ vv_ai_profiles_script();
+ `<span class="vv-ai-src-n">[${i+1}]</span><span>${esc(label)}</span>` + `<span class="vv-ai-src-n">[${i+1}]</span><span>${esc(label)}</span>`
+ `<span class="vv-ai-src-s">${Number(s.score).toFixed(3)}</span></div>`; + `<span class="vv-ai-src-s">${Number(s.score).toFixed(3)}</span></div>`;
}); });
return h + '</div>'; return h + '</div></div>';
} }
function addAnswer(job) { function addAnswer(job) {
@@ -623,7 +632,14 @@ vv_ai_profiles_script();
if (u) window.open(u, '_blank', 'noopener,noreferrer'); if (u) window.open(u, '_blank', 'noopener,noreferrer');
return; return;
} }
if (src && src.dataset.src) { vvAiOpen(src.dataset.src); return; } // A page that can do something better with a source than show it gets first refusal — the
// Scheduler opens scripts in its editor, where they can actually be changed. Everything
// else falls through to the read-only viewer.
if (src && src.dataset.src) {
if (o.onOpenSource) o.onOpenSource(src.dataset.src);
else vvAiOpen(src.dataset.src);
return;
}
const cite = e.target.closest('.vv-ai-cite'); const cite = e.target.closest('.vv-ai-cite');
if (cite) { if (cite) {
const s = lastSources[Number(cite.dataset.cite) - 1]; const s = lastSources[Number(cite.dataset.cite) - 1];
@@ -632,9 +648,23 @@ vv_ai_profiles_script();
if (u) window.open(u, '_blank', 'noopener,noreferrer'); if (u) window.open(u, '_blank', 'noopener,noreferrer');
return; return;
} }
if (s && s.path) vvAiOpen(s.path); // Same routing as a source row — a citation and the row it refers to must not behave
// differently, or clicking [3] and clicking source 3 land in different places.
if (s && s.path) {
if (o.onOpenSource) o.onOpenSource(s.path);
else vvAiOpen(s.path);
}
return; return;
} }
const stog = e.target.closest('[data-src-toggle]');
if (stog) {
const box = stog.parentElement;
box.classList.toggle('collapsed');
const car = stog.querySelector('.vv-ai-src-car');
if (car) car.textContent = box.classList.contains('collapsed') ? '▸' : '▾';
return;
}
const off = e.target.closest('[data-offer]'); const off = e.target.closest('[data-offer]');
if (off) { answerOffer(+off.dataset.offer, off.dataset.yes === '1'); return; } if (off) { answerOffer(+off.dataset.offer, off.dataset.yes === '1'); return; }
+14
View File
@@ -2444,6 +2444,7 @@ if (document.getElementById('vv-sched-ai-chat')) {
onOffer: (kind, yes) => { if (kind === 'fix') vvAiFixAnswer(yes); }, onOffer: (kind, yes) => { if (kind === 'fix') vvAiFixAnswer(yes); },
// Per-block Insert buttons render only where a page can receive them. // Per-block Insert buttons render only where a page can receive them.
onInsertCode: vvAiInsertCode, onInsertCode: vvAiInsertCode,
onOpenSource: vvAiOpenSource,
}); });
} }
@@ -2510,6 +2511,19 @@ let vvAiFixAsk = null; // { scope, symptom } while the offer is open and awa
// It is an offer, never automatic: the editor usually holds work in progress, and a reply that // It is an offer, never automatic: the editor usually holds work in progress, and a reply that
// silently rewrote it would be far worse than copy/paste. // silently rewrote it would be far worse than copy/paste.
// Clicking a retrieved source here opens it where it can be changed, not just read. The component
// falls back to its read-only viewer for anything this declines — which is everything that is not
// a script, since api/script.php serves scripts and the index also carries READMEs and manuals.
function vvAiOpenSource(path) {
if (!path) return;
if (/\.sh$/.test(path)) {
vvEditScript(path);
if (vvSchedChat) vvSchedChat.note('Opened ' + path + ' in the editor.');
return;
}
vvAiOpen(path);
}
// display:'' vs 'none' is how this panel switches views — see vvShowEditor and friends. // display:'' vs 'none' is how this panel switches views — see vvShowEditor and friends.
function vvAiEditorOpen() { function vvAiEditorOpen() {
const ed = document.getElementById('vv-editor'); const ed = document.getElementById('vv-editor');