Offer to drop an assistant's code block straight into the Scheduler editor
This commit is contained in:
@@ -2437,11 +2437,14 @@ if (document.getElementById('vv-sched-ai-chat')) {
|
||||
vvAiLastQ = q;
|
||||
return false;
|
||||
},
|
||||
onTurn: () => { vvAiFixArm(); requestAnimationFrame(vvFitRight); },
|
||||
onTurn: (answer) => { vvAiFixArm(); vvAiCodeArm(answer); requestAnimationFrame(vvFitRight); },
|
||||
// Expanding changes how much of the panel is left for the views above it. Without this the
|
||||
// chat grows downward past the end of the panel instead of upward into it.
|
||||
onResize: () => requestAnimationFrame(vvFitRight),
|
||||
onOffer: (kind, yes) => { if (kind === 'fix') vvAiFixAnswer(yes); },
|
||||
onOffer: (kind, yes) => {
|
||||
if (kind === 'fix') vvAiFixAnswer(yes);
|
||||
if (kind === 'code') vvAiCodeAnswer(yes);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2499,6 +2502,82 @@ let vvAiFixAsk = null; // { scope, symptom } while the offer is open and awa
|
||||
// Armed when a troubleshoot turn finishes. The question is the symptom; the scope is whatever was
|
||||
// being diagnosed. `since` guards against a run that had already completed before the diagnosis
|
||||
// started being read as proof that the diagnosis worked.
|
||||
// ── AI → editor handoff ──────────────────────────────────────────────────────────────────────
|
||||
// Asking the assistant for a script and then hand-selecting it out of the transcript is the one
|
||||
// step in this tab that is pure friction — and a transcript selection loses leading whitespace
|
||||
// the moment a line wraps, which for bash is not cosmetic. When the editor is open and an answer
|
||||
// carries a real code block, offer to place it instead.
|
||||
//
|
||||
// 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.
|
||||
|
||||
let vvAiCodePending = null;
|
||||
|
||||
// Largest fenced block wins when an answer carries several — an explanation typically quotes a
|
||||
// line or two before giving the whole thing, and the whole thing is what was asked for. A shebang
|
||||
// beats size outright, since that is unambiguously a script rather than an excerpt.
|
||||
function vvAiCodeExtract(text) {
|
||||
if (!text) return '';
|
||||
const blocks = [];
|
||||
const re = /```[a-zA-Z0-9_+-]*\n([\s\S]*?)```/g;
|
||||
let m;
|
||||
while ((m = re.exec(text)) !== null) blocks.push(m[1].replace(/\s+$/, ''));
|
||||
if (!blocks.length) return '';
|
||||
const shebang = blocks.find(b => b.startsWith('#!'));
|
||||
return shebang || blocks.sort((a, b) => b.length - a.length)[0];
|
||||
}
|
||||
|
||||
// display:'' vs 'none' is how this panel switches views — see vvShowEditor and friends.
|
||||
function vvAiEditorOpen() {
|
||||
const ed = document.getElementById('vv-editor');
|
||||
return !!ed && ed.style.display !== 'none';
|
||||
}
|
||||
|
||||
function vvAiCodeArm(answer) {
|
||||
vvAiCodePending = null;
|
||||
if (!vvSchedChat || !vvAiEditorOpen()) return;
|
||||
|
||||
const code = vvAiCodeExtract(answer);
|
||||
// A single short line is a mention, not a script. Offering on those turns every answer into a
|
||||
// question and trains the operator to ignore the buttons.
|
||||
if (!code || (code.length < 40 && code.indexOf('\n') === -1)) return;
|
||||
|
||||
vvAiCodePending = code;
|
||||
const lines = code.split('\n').length;
|
||||
vvSchedChat.offer('code', 'Insert that ' + lines + '-line block into the editor at your cursor?');
|
||||
}
|
||||
|
||||
function vvAiCodeAnswer(yes) {
|
||||
const code = vvAiCodePending;
|
||||
vvAiCodePending = null;
|
||||
if (!yes || !code) return;
|
||||
|
||||
const ta = document.getElementById('vv-editor-body');
|
||||
// They may have switched views between the answer and the click. Inserting into a hidden
|
||||
// textarea would look like nothing happened and be discovered much later.
|
||||
if (!ta || !vvAiEditorOpen()) {
|
||||
vvSchedChat.note('The editor is no longer open — nothing was inserted.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Commit the pre-insert state immediately. Without the flag, the 600ms coalescing window can
|
||||
// merge this with whatever was typed just before it, and one Ctrl+Z would take back both.
|
||||
vvUndoCapture(true);
|
||||
|
||||
const s = ta.selectionStart, e = ta.selectionEnd;
|
||||
ta.value = ta.value.slice(0, s) + code + ta.value.slice(e);
|
||||
ta.selectionStart = ta.selectionEnd = s + code.length;
|
||||
|
||||
// A programmatic value change fires no input event, so the highlight overlay, the line gutter
|
||||
// and the current-line marker all keep rendering the old text until these are called by hand.
|
||||
vvUndoCapture(true);
|
||||
vvSyncHlOverlay();
|
||||
vvEditorCursorMoved();
|
||||
ta.focus();
|
||||
|
||||
vvSchedChat.note('Inserted at the cursor. Ctrl+Z in the editor takes it back.');
|
||||
}
|
||||
|
||||
function vvAiFixArm() {
|
||||
if (vvAiProfile !== 'troubleshoot' || !vvAiScope) return;
|
||||
vvAiFixWatch = {
|
||||
|
||||
Reference in New Issue
Block a user