Files
swarm-assistent/Assets/assistent.js
T
Leonid PershinandCursor 06d5df9e93 Fix Assistent Krea gate for modern SwarmUI currentModelHelper.
getCurrentModel() no longer exists, so the gate stayed locked after selecting a checkpoint.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-21 12:30:33 +03:00

960 lines
34 KiB
JavaScript

/**
* Swarm Assistent — Krea 2 collaborative chat (Ollama via Swarm API).
*/
(function () {
const LS_BASE = 'swarm_assistent_base_url';
const LS_MODEL = 'swarm_assistent_model';
const LS_PACK = 'swarm_assistent_pack';
const LS_AUTO_VISION = 'swarm_assistent_auto_vision';
const LS_PANE_WIDTH = 'swarm_assistent_pane_width';
const TAB_BUTTON_ID = 'maintab_assistent';
const state = {
history: [],
packsLoaded: false,
busy: false,
lastImageDataUrl: null,
preferredModel: null,
dragDepth: 0,
};
function $(id) {
return document.getElementById(id);
}
function setStatus(text) {
const el = $('sa_status');
if (el) {
el.textContent = text || '';
}
}
function looksLikeKrea(text) {
const s = String(text || '');
return /krea\s*2|krea2|krea-2/i.test(s) || /krea/i.test(s);
}
/** Resolve current checkpoint from modern SwarmUI APIs (getCurrentModel is gone). */
function resolveCurrentCheckpoint() {
const out = {
name: null,
architecture: null,
compat_class: null,
title: null,
class: null,
source: null,
};
try {
if (typeof currentModelHelper !== 'undefined' && currentModelHelper) {
out.name = currentModelHelper.curModel || null;
out.architecture = currentModelHelper.curArch || null;
out.compat_class = currentModelHelper.curCompatClass || null;
out.source = 'currentModelHelper';
}
} catch (e) { /* ignore */ }
try {
if (typeof getCurrentModel === 'function') {
const model = getCurrentModel();
if (model) {
out.name = out.name || model.name || null;
out.title = model.title || null;
out.architecture = out.architecture || model.architecture || null;
out.class = model.class || null;
out.compat_class = out.compat_class || model.compat_class || null;
out.source = out.source || 'getCurrentModel';
}
}
} catch (e) { /* ignore */ }
try {
const sel =
document.getElementById('current_model') ||
document.getElementById('input_model');
if (sel) {
const opt = sel.selectedOptions && sel.selectedOptions[0];
const hint = [
sel.value,
opt && opt.text,
opt && opt.dataset && opt.dataset.cleanname,
]
.filter(Boolean)
.join(' ');
if (!out.name && sel.value) {
out.name = sel.value;
out.source = out.source || 'dropdown';
}
if (hint && !out.architecture) {
out.title = out.title || hint;
}
}
} catch (e) { /* ignore */ }
return out;
}
function isKreaSelected() {
try {
const m = resolveCurrentCheckpoint();
const blob = [
m.architecture,
m.compat_class,
m.title,
m.name,
m.class,
].join(' ');
return looksLikeKrea(blob);
} catch (e) {
return false;
}
}
function updateGate() {
const ok = isKreaSelected();
const gate = $('sa_gate');
const layout = $('sa_layout');
if (gate) {
gate.hidden = ok;
if (!ok) {
const m = resolveCurrentCheckpoint();
const seen = [m.architecture, m.compat_class, m.name]
.filter(Boolean)
.join(' · ');
const p = gate.querySelector('p');
if (p) {
p.innerHTML = seen
? `Swarm Assistent is for <strong>Krea 2</strong> models only. Current: <code>${escapeHtml(
seen,
)}</code> — pick a checkpoint with architecture <code>krea-2</code>.`
: 'Swarm Assistent is for <strong>Krea 2</strong> models only. Select a Krea 2 checkpoint on Generate to enable the chat.';
}
}
}
if (layout) {
layout.classList.toggle('sa-disabled', !ok);
}
return ok;
}
function escapeHtml(s) {
return String(s)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
function val(id) {
const el = document.getElementById(id);
return el ? el.value : '';
}
function setVal(id, value) {
const el = document.getElementById(id);
if (!el) {
return;
}
el.value = value;
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
}
function openAssistentTab() {
const tab = document.getElementById(TAB_BUTTON_ID);
if (tab) {
tab.click();
return true;
}
// Fallback: try hash / pane
const pane = document.getElementById('assistent');
if (pane && typeof bootstrap !== 'undefined' && bootstrap.Tab) {
try {
bootstrap.Tab.getOrCreateInstance(tab || pane).show();
} catch (e) { /* ignore */ }
}
return !!tab;
}
function flashImagePane() {
const frame = $('sa_image_frame');
if (!frame) {
return;
}
frame.classList.remove('sa-flash');
// reflow
void frame.offsetWidth;
frame.classList.add('sa-flash');
}
function setImageFromSrc(src, { switchTab = false, note = null } = {}) {
if (!src) {
return false;
}
const cleaned = String(src).trim().split(/\s+/)[0];
if (!cleaned || cleaned.startsWith('#')) {
return false;
}
state.lastImageDataUrl = cleaned;
const img = $('sa_image_preview');
const empty = $('sa_image_empty');
const chip = $('sa_vision_chip');
const frame = $('sa_image_frame');
if (img) {
img.src = cleaned;
img.hidden = false;
}
if (empty) {
empty.hidden = true;
}
if (chip) {
chip.hidden = false;
}
if (frame) {
frame.classList.add('sa-has-image');
}
if (switchTab) {
openAssistentTab();
}
flashImagePane();
if (note) {
setStatus(note);
}
return true;
}
function clearVisionImage({ silent = false } = {}) {
state.lastImageDataUrl = null;
const img = $('sa_image_preview');
const empty = $('sa_image_empty');
const chip = $('sa_vision_chip');
const frame = $('sa_image_frame');
if (img) {
img.removeAttribute('src');
img.hidden = true;
}
if (empty) {
empty.hidden = false;
}
if (chip) {
chip.hidden = true;
}
if (frame) {
frame.classList.remove('sa-has-image');
}
if (!silent) {
setStatus('Vision image cleared');
}
}
function collectLiveContext() {
const ctx = {
architecture_ok: isKreaSelected(),
checkpoint: null,
prompt: val('alt_prompt_textbox') || val('input_prompt') || '',
negative: val('input_negativeprompt') || val('alt_negativeprompt_textbox') || '',
width: parseInt(val('input_width') || '0', 10) || null,
height: parseInt(val('input_height') || '0', 10) || null,
steps: parseInt(val('input_steps') || '0', 10) || null,
cfg: parseFloat(val('input_cfgscale') || val('input_cfg') || '') || null,
sigma_shift: parseFloat(val('input_sigmashift') || '') || null,
seed: val('input_seed') || null,
selected_loras: [],
available_loras: [],
has_vision_image: !!state.lastImageDataUrl,
};
try {
const model = resolveCurrentCheckpoint();
if (model.name || model.architecture) {
ctx.checkpoint = {
name: model.name || model.title || null,
architecture: model.architecture || model.compat_class || model.class || null,
title: model.title || null,
};
}
} catch (e) { /* ignore */ }
try {
if (typeof loraHelper !== 'undefined' && loraHelper && Array.isArray(loraHelper.selected)) {
ctx.selected_loras = loraHelper.selected.map((l) => ({
name: l.name || l,
weight: (loraHelper.loraWeightPref && loraHelper.loraWeightPref[l.name || l]) || 1,
}));
}
} catch (e) { /* ignore */ }
try {
const models = (typeof allModels !== 'undefined' && allModels) || (typeof model_list !== 'undefined' && model_list) || [];
const list = Array.isArray(models) ? models : Object.values(models || {});
for (const m of list) {
if (!m) {
continue;
}
const folder = `${m.folder || m.path || ''}`;
const isLora = /lora/i.test(m.category || m.type || '') || (m.name && String(m.name).toLowerCase().includes('lora'));
const inLoraFolder = /lora/i.test(folder);
if (!(isLora || inLoraFolder)) {
if (!/lora/i.test(JSON.stringify(m).slice(0, 200))) {
continue;
}
}
ctx.available_loras.push({
name: m.name || m.title,
title: m.title || m.name,
trigger_phrase: m.trigger_phrase || m.trigger || (m.metadata && (m.metadata.trigger_phrase || m.metadata.trigger)) || null,
architecture: m.architecture || null,
});
}
if (ctx.available_loras.length > 80) {
ctx.available_loras = ctx.available_loras.slice(0, 80);
}
} catch (e) { /* ignore */ }
try {
const sel = document.getElementById('input_loras');
if (sel && sel.options && ctx.available_loras.length === 0) {
for (const opt of sel.options) {
if (opt.value) {
ctx.available_loras.push({ name: opt.value, title: opt.text || opt.value, trigger_phrase: null });
}
}
}
} catch (e) { /* ignore */ }
return ctx;
}
function extractPatch(text) {
if (!text) {
return { prose: text || '', patch: null };
}
const re = /```(?:json)?\s*([\s\S]*?)```/gi;
let match;
let lastPatch = null;
let prose = text;
while ((match = re.exec(text)) !== null) {
const raw = match[1].trim();
try {
const obj = JSON.parse(raw);
if (obj && typeof obj === 'object' && (obj.prompt != null || obj.loras || obj.width || obj.height || obj.steps || obj.cfg)) {
lastPatch = obj;
prose = (text.slice(0, match.index) + text.slice(match.index + match[0].length)).trim();
}
} catch (e) { /* not json */ }
}
return { prose, patch: lastPatch };
}
function applyPatch(patch, which) {
if (!patch) {
return;
}
const doPrompt = !which || which === 'all' || which === 'prompt';
const doLoras = !which || which === 'all' || which === 'loras';
const doSize = !which || which === 'all' || which === 'size';
if (doPrompt && patch.prompt != null) {
const box = document.getElementById('alt_prompt_textbox') || document.getElementById('input_prompt');
if (box) {
box.value = patch.prompt;
box.dispatchEvent(new Event('input', { bubbles: true }));
box.dispatchEvent(new Event('change', { bubbles: true }));
}
if (patch.negative != null) {
setVal('input_negativeprompt', patch.negative);
}
if (Array.isArray(patch.loras)) {
for (const l of patch.loras) {
const triggers = l.triggers || (l.trigger_phrase ? [l.trigger_phrase] : []);
for (const t of triggers) {
if (t && box && box.value && !box.value.includes(t)) {
box.value = `${box.value.trim()}, ${t}`;
box.dispatchEvent(new Event('input', { bubbles: true }));
}
}
}
}
}
if (doLoras && Array.isArray(patch.loras) && typeof loraHelper !== 'undefined' && loraHelper) {
try {
if (typeof loraHelper.clearLoras === 'function') {
loraHelper.clearLoras();
}
} catch (e) { /* ignore */ }
for (const l of patch.loras) {
const name = l.name;
if (!name) {
continue;
}
try {
if (typeof loraHelper.selectLora === 'function') {
loraHelper.selectLora(name);
}
if (loraHelper.loraWeightPref && l.weight != null) {
loraHelper.loraWeightPref[name] = l.weight;
}
} catch (e) {
console.warn('Assistent: selectLora failed', name, e);
}
}
try {
if (typeof loraHelper.rebuildUI === 'function') {
loraHelper.rebuildUI();
}
} catch (e) { /* ignore */ }
}
if (doSize) {
if (patch.width != null) {
setVal('input_width', String(patch.width));
}
if (patch.height != null) {
setVal('input_height', String(patch.height));
}
if (patch.steps != null) {
setVal('input_steps', String(patch.steps));
}
if (patch.cfg != null) {
if (document.getElementById('input_cfgscale')) {
setVal('input_cfgscale', String(patch.cfg));
} else {
setVal('input_cfg', String(patch.cfg));
}
}
}
setStatus('Applied patch');
}
function appendMessage(role, text, patch) {
const box = $('sa_messages');
if (!box) {
return;
}
const div = document.createElement('div');
div.className = `sa-msg ${role}`;
const { prose, patch: extracted } = role === 'assistant' ? extractPatch(text) : { prose: text, patch: null };
const finalPatch = patch || extracted;
div.textContent = prose || text || '';
if (finalPatch) {
const wrap = document.createElement('div');
wrap.className = 'sa-patch';
const pre = document.createElement('pre');
pre.textContent = JSON.stringify(finalPatch, null, 2);
wrap.appendChild(pre);
const actions = document.createElement('div');
actions.className = 'sa-patch-actions';
for (const [label, which] of [
['Apply all', 'all'],
['Prompt', 'prompt'],
['LoRAs', 'loras'],
['Size', 'size'],
]) {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'basic-button';
btn.textContent = label;
btn.addEventListener('click', () => applyPatch(finalPatch, which));
actions.appendChild(btn);
}
wrap.appendChild(actions);
div.appendChild(wrap);
}
box.appendChild(div);
box.scrollTop = box.scrollHeight;
}
function findCurrentGenerateSrc() {
try {
const cur = document.getElementById('current_image_img')
|| document.querySelector('#current_image img')
|| document.querySelector('.current-image img')
|| document.querySelector('#current_image_batch img');
if (cur) {
return cur.dataset?.src || cur.src || null;
}
} catch (e) { /* ignore */ }
try {
if (typeof currentMetadataMap !== 'undefined' && currentMetadataMap && currentMetadataMap.image) {
return currentMetadataMap.image;
}
} catch (e) { /* ignore */ }
return null;
}
function refreshImagePreview({ onlyIfEmpty = false } = {}) {
if (onlyIfEmpty && state.lastImageDataUrl) {
return;
}
const src = findCurrentGenerateSrc();
if (src) {
setImageFromSrc(src);
} else if (!state.lastImageDataUrl) {
clearVisionImage({ silent: true });
}
}
function fileToDataUrl(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result || ''));
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
async function acceptImageFile(file) {
if (!file || !String(file.type || '').startsWith('image/')) {
setStatus('Not an image file');
return false;
}
const dataUrl = await fileToDataUrl(file);
return setImageFromSrc(dataUrl, { note: `Loaded ${file.name || 'image'}` });
}
async function handleDropDataTransfer(dt) {
if (!dt) {
return false;
}
if (dt.files && dt.files.length) {
for (const file of dt.files) {
if (String(file.type || '').startsWith('image/')) {
return acceptImageFile(file);
}
}
}
const uri = (dt.getData('text/uri-list') || dt.getData('text/plain') || '').trim();
if (uri) {
const first = uri.split('\n').map((l) => l.trim()).find((l) => l && !l.startsWith('#'));
if (first) {
return setImageFromSrc(first, { note: 'Image from drag' });
}
}
// HTML img drag
const html = dt.getData('text/html') || '';
const m = html.match(/src=["']([^"']+)["']/i);
if (m && m[1]) {
return setImageFromSrc(m[1], { note: 'Image from drag' });
}
return false;
}
async function imageToBase64ForOllama(src) {
if (!src) {
return null;
}
if (src.startsWith('data:')) {
const i = src.indexOf(',');
return i >= 0 ? src.slice(i + 1) : null;
}
try {
const resp = await fetch(src);
const blob = await resp.blob();
return await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const data = String(reader.result || '');
const i = data.indexOf(',');
resolve(i >= 0 ? data.slice(i + 1) : null);
};
reader.onerror = reject;
reader.readAsDataURL(blob);
});
} catch (e) {
console.warn('Assistent: vision fetch failed', e);
return null;
}
}
function loadSettings() {
const base = localStorage.getItem(LS_BASE);
const model = localStorage.getItem(LS_MODEL);
const pack = localStorage.getItem(LS_PACK);
const auto = localStorage.getItem(LS_AUTO_VISION);
const paneW = localStorage.getItem(LS_PANE_WIDTH);
if (base && $('sa_base_url')) {
$('sa_base_url').value = base;
}
if (pack && $('sa_pack')) {
$('sa_pack').value = pack;
}
if (auto != null && $('sa_auto_vision')) {
$('sa_auto_vision').checked = auto === '1';
}
if (model) {
state.preferredModel = model;
}
if (paneW) {
document.documentElement.style.setProperty('--sa-image-width', paneW);
}
}
function saveSettings() {
localStorage.setItem(LS_BASE, $('sa_base_url')?.value || '');
localStorage.setItem(LS_MODEL, $('sa_model')?.value || '');
localStorage.setItem(LS_PACK, $('sa_pack')?.value || 'write_prompt');
localStorage.setItem(LS_AUTO_VISION, $('sa_auto_vision')?.checked ? '1' : '0');
}
function refreshModels() {
const baseUrl = $('sa_base_url')?.value || 'http://127.0.0.1:11434';
setStatus('Loading models…');
genericRequest('AssistentListModels', { baseUrl }, (data) => {
if (data.error) {
setStatus(data.error);
appendMessage('error', data.error);
return;
}
const sel = $('sa_model');
if (!sel) {
return;
}
sel.innerHTML = '';
const models = data.models || [];
for (const name of models) {
if (!name) {
continue;
}
const opt = document.createElement('option');
opt.value = name;
opt.textContent = name;
sel.appendChild(opt);
}
const prefer = state.preferredModel || localStorage.getItem(LS_MODEL);
if (prefer && models.includes(prefer)) {
sel.value = prefer;
}
setStatus(models.length ? `${models.length} models` : 'No Ollama models');
saveSettings();
});
}
async function sendChat() {
if (state.busy) {
return;
}
if (!updateGate()) {
setStatus('Select a Krea 2 model');
return;
}
const text = ($('sa_input')?.value || '').trim();
if (!text) {
return;
}
const pack = $('sa_pack')?.value || 'write_prompt';
const model = $('sa_model')?.value;
if (!model) {
setStatus('Pick an Ollama model in ⚙');
refreshModels();
return;
}
if (!state.lastImageDataUrl) {
refreshImagePreview();
}
const attach = ($('sa_attach_vision')?.checked || $('sa_auto_vision')?.checked) && state.lastImageDataUrl;
let images = null;
if (attach) {
setStatus('Encoding image…');
const b64 = await imageToBase64ForOllama(state.lastImageDataUrl);
if (b64) {
images = [b64];
}
}
state.history.push({ role: 'user', content: text });
appendMessage('user', text);
$('sa_input').value = '';
const context = collectLiveContext();
context.has_vision_image = !!images;
const messages = state.history.slice(-12).map((m) => ({ role: m.role, content: m.content }));
if (images && messages.length) {
messages[messages.length - 1].images = images;
}
state.busy = true;
setStatus('Thinking…');
saveSettings();
const payload = {
baseUrl: $('sa_base_url')?.value || 'http://127.0.0.1:11434',
model,
pack,
includeBase: true,
raw: {
messages,
context_json: JSON.stringify(context),
pack,
base_url: $('sa_base_url')?.value || 'http://127.0.0.1:11434',
model,
},
};
genericRequest('AssistentChat', payload, (data) => {
state.busy = false;
if (data.error) {
setStatus(data.error);
appendMessage('error', data.error);
return;
}
const reply = data.reply || '';
state.history.push({ role: 'assistant', content: reply });
appendMessage('assistant', reply);
setStatus('Done');
});
}
function wireDropZone() {
const frame = $('sa_image_frame');
const overlay = $('sa_drop_overlay');
if (!frame) {
return;
}
const setDrag = (on) => {
frame.classList.toggle('sa-dragover', on);
if (overlay) {
overlay.hidden = !on;
}
};
['dragenter', 'dragover'].forEach((ev) => {
frame.addEventListener(ev, (e) => {
e.preventDefault();
e.stopPropagation();
state.dragDepth += ev === 'dragenter' ? 1 : 0;
setDrag(true);
if (e.dataTransfer) {
e.dataTransfer.dropEffect = 'copy';
}
});
});
frame.addEventListener('dragleave', (e) => {
e.preventDefault();
state.dragDepth = Math.max(0, state.dragDepth - 1);
if (state.dragDepth === 0) {
setDrag(false);
}
});
frame.addEventListener('drop', async (e) => {
e.preventDefault();
e.stopPropagation();
state.dragDepth = 0;
setDrag(false);
await handleDropDataTransfer(e.dataTransfer);
});
// Also accept drops on the whole layout (easier target)
const layout = $('sa_layout');
layout?.addEventListener('dragover', (e) => {
if (e.dataTransfer?.types?.includes('Files') || e.dataTransfer?.types?.includes('text/uri-list')) {
e.preventDefault();
}
});
layout?.addEventListener('drop', async (e) => {
if (!e.dataTransfer) {
return;
}
e.preventDefault();
await handleDropDataTransfer(e.dataTransfer);
});
frame.addEventListener('paste', async (e) => {
const items = e.clipboardData?.items;
if (!items) {
return;
}
for (const item of items) {
if (item.type.startsWith('image/')) {
e.preventDefault();
const file = item.getAsFile();
if (file) {
await acceptImageFile(file);
}
return;
}
}
});
// Global paste when Assistent tab is visible
document.addEventListener('paste', async (e) => {
const pane = document.getElementById('assistent');
if (!pane || !pane.classList.contains('active')) {
return;
}
if (e.target && (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT')) {
// Still allow image paste over text fields when clipboard has image
const items = e.clipboardData?.items;
let hasImage = false;
if (items) {
for (const item of items) {
if (item.type.startsWith('image/')) {
hasImage = true;
break;
}
}
}
if (!hasImage) {
return;
}
}
const items = e.clipboardData?.items;
if (!items) {
return;
}
for (const item of items) {
if (item.type.startsWith('image/')) {
e.preventDefault();
const file = item.getAsFile();
if (file) {
await acceptImageFile(file);
}
return;
}
}
});
}
function wireSplitter() {
const splitter = $('sa_splitter');
const layout = $('sa_layout');
const pane = $('sa_image_pane');
if (!splitter || !layout || !pane) {
return;
}
let dragging = false;
splitter.addEventListener('mousedown', (e) => {
e.preventDefault();
dragging = true;
splitter.classList.add('sa-dragging');
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
});
window.addEventListener('mousemove', (e) => {
if (!dragging) {
return;
}
const rect = layout.getBoundingClientRect();
const x = e.clientX - rect.left;
const pct = Math.min(45, Math.max(18, (x / rect.width) * 100));
const value = `${pct}%`;
document.documentElement.style.setProperty('--sa-image-width', value);
localStorage.setItem(LS_PANE_WIDTH, value);
});
window.addEventListener('mouseup', () => {
if (!dragging) {
return;
}
dragging = false;
splitter.classList.remove('sa-dragging');
document.body.style.cursor = '';
document.body.style.userSelect = '';
});
}
function registerSendButton() {
if (typeof registerMediaButton !== 'function') {
// SwarmUI core not ready yet — retry briefly
setTimeout(registerSendButton, 500);
return;
}
// Avoid duplicate registrations on hot-reload
if (window.__swarmAssistentMediaRegistered) {
return;
}
window.__swarmAssistentMediaRegistered = true;
registerMediaButton(
'Send to Assistent',
(src) => {
setImageFromSrc(src, {
switchTab: true,
note: 'Image sent to Assistent',
});
// Prefer critique pack when coming from an image
const pack = $('sa_pack');
if (pack && pack.value === 'write_prompt') {
pack.value = 'critique_image';
saveSettings();
}
},
'Open Assistent with this image (vision / critique / prompt help)',
['image'],
true,
true,
);
}
function wire() {
if (!$('swarm_assistent_root')) {
return;
}
loadSettings();
updateGate();
refreshImagePreview();
refreshModels();
wireDropZone();
wireSplitter();
registerSendButton();
$('sa_btn_settings')?.addEventListener('click', () => {
const s = $('sa_settings');
if (s) {
s.hidden = !s.hidden;
}
});
$('sa_btn_refresh_models')?.addEventListener('click', () => {
saveSettings();
refreshModels();
});
$('sa_btn_use_current')?.addEventListener('click', () => {
const src = findCurrentGenerateSrc();
if (src) {
setImageFromSrc(src, { note: 'Using current Generate image' });
} else {
setStatus('No current Generate image');
}
});
$('sa_btn_clear_image')?.addEventListener('click', clearVisionImage);
$('sa_btn_send')?.addEventListener('click', () => sendChat());
$('sa_btn_clear')?.addEventListener('click', () => {
state.history = [];
const box = $('sa_messages');
if (box) {
box.innerHTML = '';
}
setStatus('');
});
$('sa_input')?.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey && !e.altKey) {
e.preventDefault();
sendChat();
}
});
$('sa_base_url')?.addEventListener('change', saveSettings);
$('sa_model')?.addEventListener('change', saveSettings);
$('sa_pack')?.addEventListener('change', saveSettings);
$('sa_auto_vision')?.addEventListener('change', saveSettings);
setInterval(updateGate, 2000);
// Soft sync from Generate only when we have no pinned vision image
setInterval(() => refreshImagePreview({ onlyIfEmpty: true }), 5000);
// Expose for console / other extensions
window.swarmAssistent = {
setImageFromSrc,
clearVisionImage,
openAssistentTab,
sendToAssistent: (src) => setImageFromSrc(src, { switchTab: true, note: 'Image sent to Assistent' }),
isKreaSelected,
resolveCurrentCheckpoint,
};
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', wire);
} else {
wire();
}
})();