Open a cited script in the editor instead of a read-only viewer
This commit is contained in:
@@ -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; }
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
Reference in New Issue
Block a user