This commit is contained in:
+99
-28
@@ -22,6 +22,7 @@ import { chartIdsFromToolLog, mountChartEmbeds } from './chart-embed.js'
|
||||
* messages: HTMLElement|null,
|
||||
* input: HTMLTextAreaElement|null,
|
||||
* sendBtn: HTMLElement|null,
|
||||
* stopBtn?: HTMLElement|null,
|
||||
* status: HTMLElement|null,
|
||||
* modelChip: HTMLElement|null,
|
||||
* samples: HTMLElement|null,
|
||||
@@ -792,13 +793,36 @@ export function createQvacView(opts) {
|
||||
}
|
||||
}
|
||||
|
||||
function setBusyUi(on) {
|
||||
busy = on
|
||||
const send = opts.els.sendBtn
|
||||
const stop = opts.els.stopBtn
|
||||
if (send) {
|
||||
send.disabled = on
|
||||
send.classList.toggle('hidden', on)
|
||||
}
|
||||
if (stop) {
|
||||
stop.classList.toggle('hidden', !on)
|
||||
stop.disabled = false
|
||||
}
|
||||
}
|
||||
|
||||
async function stopInference() {
|
||||
if (!busy) return
|
||||
setStatus('Stopping…', 'busy')
|
||||
try {
|
||||
await engine.stop?.()
|
||||
} catch (err) {
|
||||
opts.log?.(`QVAC stop: ${err?.message || err}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function send() {
|
||||
const input = opts.els.input
|
||||
const text = (input?.value || '').trim()
|
||||
if (!text || busy) return
|
||||
if (input) input.value = ''
|
||||
busy = true
|
||||
opts.els.sendBtn && (opts.els.sendBtn.disabled = true)
|
||||
setBusyUi(true)
|
||||
appendMsg('user', text)
|
||||
const assistantUi = appendMsg('assistant', '…', { streaming: true })
|
||||
const streamBody = assistantUi?.body
|
||||
@@ -826,7 +850,11 @@ export function createQvacView(opts) {
|
||||
maxSubAgents: Number(settings().qvacMaxSubAgents) || 3,
|
||||
}
|
||||
|
||||
if (shouldUseSubAgents(text, prefs) && opts.isConnected?.()) {
|
||||
if (
|
||||
shouldUseSubAgents(text, prefs) &&
|
||||
opts.isConnected?.() &&
|
||||
!engine.isAborted?.()
|
||||
) {
|
||||
setStatus('Spinning up agents…', 'busy')
|
||||
if (streamBody) {
|
||||
streamBody.innerHTML = formatMdLite('_Dispatching multi-agent investigation…_')
|
||||
@@ -835,11 +863,19 @@ export function createQvacView(opts) {
|
||||
tools,
|
||||
query: text,
|
||||
maxAgents: prefs.maxSubAgents,
|
||||
isAborted: () => Boolean(engine.isAborted?.()),
|
||||
onAgent: (ev) => {
|
||||
if (engine.isAborted?.()) return
|
||||
paintAgentEvent(ev)
|
||||
setStatus(`Agent: ${ev.label}…`, 'busy')
|
||||
},
|
||||
})
|
||||
if (engine.isAborted?.()) {
|
||||
acc = '_(Stopped during multi-agent investigation.)_'
|
||||
paintAssistant(streamBody, acc, false)
|
||||
setStatus('Stopped', 'warn')
|
||||
return
|
||||
}
|
||||
completeOpts.agentBriefing = pack.contextText
|
||||
completeOpts.agentFallbackText = synthesizeFromAgents(text, pack)
|
||||
completeOpts.agentToolCalls = pack.agents.map((a) => ({
|
||||
@@ -862,6 +898,13 @@ export function createQvacView(opts) {
|
||||
setStatus('Synthesizing…', 'busy')
|
||||
}
|
||||
|
||||
if (engine.isAborted?.()) {
|
||||
acc = '_(Stopped.)_'
|
||||
paintAssistant(streamBody, acc, false)
|
||||
setStatus('Stopped', 'warn')
|
||||
return
|
||||
}
|
||||
|
||||
const result = await engine.complete(hist, {
|
||||
...completeOpts,
|
||||
onToken: (t) => {
|
||||
@@ -876,6 +919,7 @@ export function createQvacView(opts) {
|
||||
paintAssistant(streamBody, raw, true)
|
||||
},
|
||||
onTool: (name, args, res) => {
|
||||
if (engine.isAborted?.()) return
|
||||
toolLog.push({ name, args, result: res })
|
||||
setStatus(`Tool: ${name}…`, 'busy')
|
||||
if (assistantUi?.div) {
|
||||
@@ -905,35 +949,49 @@ export function createQvacView(opts) {
|
||||
assistantUi.div.appendChild(renderToolChips(toolLog))
|
||||
}
|
||||
// Live sparklines under the answer for chart-related tools
|
||||
const embedIds = chartIdsFromToolLog(toolLog)
|
||||
if (embedIds.length && assistantUi?.div && opts.manager?.request) {
|
||||
mountChartEmbeds(assistantUi.div, embedIds, {
|
||||
request: (m, a) => opts.manager.request(m, a || {}),
|
||||
catalog: opts.getCatalog?.() || {},
|
||||
onOpen: (id) => {
|
||||
opts.onOpenView?.('charts')
|
||||
if (opts.charts?.showCharts) {
|
||||
opts.charts.showCharts({
|
||||
charts: [id],
|
||||
pin: true,
|
||||
boardOnly: true,
|
||||
focus: id,
|
||||
openFocus: true,
|
||||
})
|
||||
} else {
|
||||
opts.onOpenChart?.(id)
|
||||
}
|
||||
},
|
||||
}).catch(() => {})
|
||||
if (!result.stopped) {
|
||||
const embedIds = chartIdsFromToolLog(toolLog)
|
||||
if (embedIds.length && assistantUi?.div && opts.manager?.request) {
|
||||
mountChartEmbeds(assistantUi.div, embedIds, {
|
||||
request: (m, a) => opts.manager.request(m, a || {}),
|
||||
catalog: opts.getCatalog?.() || {},
|
||||
onOpen: (id) => {
|
||||
opts.onOpenView?.('charts')
|
||||
if (opts.charts?.showCharts) {
|
||||
opts.charts.showCharts({
|
||||
charts: [id],
|
||||
pin: true,
|
||||
boardOnly: true,
|
||||
focus: id,
|
||||
openFocus: true,
|
||||
})
|
||||
} else {
|
||||
opts.onOpenChart?.(id)
|
||||
}
|
||||
},
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
if (result.stopped || result.mode === 'stopped') {
|
||||
setStatus('Stopped', 'warn')
|
||||
} else {
|
||||
setStatus(result.mode === 'fallback' ? 'Ready (tools-only)' : 'Ready', 'ok')
|
||||
}
|
||||
setStatus(result.mode === 'fallback' ? 'Ready (tools-only)' : 'Ready', 'ok')
|
||||
} catch (err) {
|
||||
const msg = err?.message || String(err)
|
||||
if (streamBody) streamBody.innerHTML = formatMdLite(`Error: ${msg}`)
|
||||
setStatus(msg, 'error')
|
||||
if (/cancel|abort|stopped/i.test(msg)) {
|
||||
if (streamBody && !acc) {
|
||||
paintAssistant(streamBody, '_(Stopped.)_', false)
|
||||
} else if (streamBody && acc) {
|
||||
paintAssistant(streamBody, acc + '\n\n_(Stopped.)_', false)
|
||||
}
|
||||
setStatus('Stopped', 'warn')
|
||||
} else {
|
||||
if (streamBody) streamBody.innerHTML = formatMdLite(`Error: ${msg}`)
|
||||
setStatus(msg, 'error')
|
||||
}
|
||||
} finally {
|
||||
busy = false
|
||||
if (opts.els.sendBtn) opts.els.sendBtn.disabled = false
|
||||
setBusyUi(false)
|
||||
syncModelChip()
|
||||
}
|
||||
}
|
||||
@@ -1003,12 +1061,25 @@ export function createQvacView(opts) {
|
||||
|
||||
function bind() {
|
||||
opts.els.sendBtn?.addEventListener('click', () => send())
|
||||
opts.els.stopBtn?.addEventListener('click', () => stopInference())
|
||||
opts.els.input?.addEventListener('keydown', (ev) => {
|
||||
if (ev.key === 'Escape' && busy) {
|
||||
ev.preventDefault()
|
||||
stopInference()
|
||||
return
|
||||
}
|
||||
if (ev.key === 'Enter' && !ev.shiftKey) {
|
||||
ev.preventDefault()
|
||||
send()
|
||||
}
|
||||
})
|
||||
// Global Esc while QVAC tab is active
|
||||
opts.els.root?.addEventListener('keydown', (ev) => {
|
||||
if (ev.key === 'Escape' && busy) {
|
||||
ev.preventDefault()
|
||||
stopInference()
|
||||
}
|
||||
})
|
||||
opts.els.resetBtn?.addEventListener('click', () => {
|
||||
closeSettings()
|
||||
resetOnboarding()
|
||||
|
||||
Reference in New Issue
Block a user