/** * Swarm Assistent — Krea 2 collaborative chat (Ollama via Swarm API). * v0.3.4: do not auto-fill Vision from the checkpoint preview. */ (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_AUTO_APPLY = 'swarm_assistent_auto_apply'; const LS_AUTO_GENERATE = 'swarm_assistent_auto_generate'; const LS_AUTO_CRITIQUE = 'swarm_assistent_auto_critique'; const LS_AUTO_DOWNLOAD = 'swarm_assistent_auto_download'; const LS_PANE_WIDTH = 'swarm_assistent_pane_width'; const TAB_BUTTON_ID = 'maintab_assistent'; const state = { history: [], packsLoaded: false, busy: false, generating: false, lastImageDataUrl: null, preferredModel: null, dragDepth: 0, inventory: { loras: [], checkpoints: [], wildcards: [], has_civitai_key: false }, streamEl: null, critiqueHopUsed: false, busyPhase: 'idle', busyStarted: 0, gotDelta: false, busyTimer: null, }; function $(id) { return document.getElementById(id); } function modelShort(name) { const s = String(name || ''); const slash = s.lastIndexOf('/'); return (slash >= 0 ? s.slice(slash + 1) : s) || 'model'; } function fmtElapsed(ms) { const s = Math.max(0, Math.floor(ms / 1000)); if (s < 60) { return `${s}s`; } return `${Math.floor(s / 60)}m ${String(s % 60).padStart(2, '0')}s`; } function hideChatEmpty() { const empty = $('sa_chat_empty'); if (empty) { empty.hidden = true; } } function showChatEmptyIfIdle() { const box = $('sa_messages'); const empty = $('sa_chat_empty'); if (!box || !empty) { return; } const hasMsg = [...box.children].some((el) => el.id !== 'sa_chat_empty'); empty.hidden = hasMsg; } function setBusyPhase(phase) { state.busyPhase = phase || 'thinking'; tickBusyUi(); } function tickBusyUi() { if (state.busyPhase === 'idle') { return; } const elapsed = Date.now() - (state.busyStarted || Date.now()); if (!state.gotDelta && (state.busyPhase === 'thinking' || state.busyPhase === 'waiting') && elapsed > 1600) { state.busyPhase = 'loading'; } const model = modelShort($('sa_model')?.value); const labels = { encoding: 'Encoding image…', waiting: 'Waiting for Ollama…', loading: `Loading ${model} into GPU… first load can take a minute`, thinking: 'Thinking…', streaming: 'Writing…', generating: 'Generating image…', applying: 'Applying patch…', refining: 'Civitai search done — refining…', }; const text = labels[state.busyPhase] || 'Working…'; const barText = $('sa_livebar_text'); if (barText) { barText.textContent = text; } const elapsedEl = $('sa_elapsed'); if (elapsedEl) { elapsedEl.textContent = fmtElapsed(elapsed); } const status = $('sa_status'); if (status) { status.textContent = text; status.classList.add('sa-status-busy'); } } function startBusyUi(phase) { state.busyStarted = Date.now(); state.gotDelta = false; state.busyPhase = phase || 'thinking'; $('swarm_assistent_root')?.classList.add('sa-is-busy'); $('sa_composer')?.classList.add('sa-composer-busy'); const send = $('sa_btn_send'); if (send) { send.disabled = true; } const input = $('sa_input'); if (input) { input.classList.add('sa-input-busy'); } const bar = $('sa_livebar'); if (bar) { bar.hidden = false; } const dot = $('sa_live_dot'); if (dot) { dot.hidden = false; } tickBusyUi(); if (state.busyTimer) { clearInterval(state.busyTimer); } state.busyTimer = setInterval(tickBusyUi, 400); } function stopBusyUi(finalStatus) { if (state.busyTimer) { clearInterval(state.busyTimer); state.busyTimer = null; } const elapsed = Date.now() - (state.busyStarted || Date.now()); state.busyPhase = 'idle'; $('swarm_assistent_root')?.classList.remove('sa-is-busy'); $('sa_composer')?.classList.remove('sa-composer-busy'); const send = $('sa_btn_send'); if (send) { send.disabled = false; } const input = $('sa_input'); if (input) { input.classList.remove('sa-input-busy'); } const bar = $('sa_livebar'); if (bar) { bar.hidden = true; } const dot = $('sa_live_dot'); if (dot) { dot.hidden = true; } const status = $('sa_status'); if (status) { status.classList.remove('sa-status-busy'); } if (finalStatus != null) { const suffix = elapsed >= 1000 ? ` · ${fmtElapsed(elapsed)}` : ''; setStatus(finalStatus + suffix); } } function setStatus(text) { const el = $('sa_status'); if (el) { el.textContent = text || ''; } } function setInterruptVisible(on) { const btn = $('sa_btn_interrupt'); if (btn) { btn.hidden = !on; btn.classList.toggle('sa-interrupt-active', !!on); } } function looksLikeKrea(text) { const s = String(text || ''); return /krea\s*2|krea2|krea-2/i.test(s) || /krea/i.test(s); } 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 Krea 2 models only. Current: ${escapeHtml( seen, )} — pick a checkpoint with architecture krea-2.` : 'Swarm Assistent is for Krea 2 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, '&') .replace(//g, '>') .replace(/"/g, '"'); } 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; } 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'); 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 countPromptImages() { try { const box = document.getElementById('alt_prompt_textbox') || document.getElementById('input_prompt'); if (!box) { return 0; } const text = box.value || ''; const matches = text.match(/)/gi) || text.match(/data:image\//gi); return matches ? matches.length : 0; } catch (e) { return 0; } } function triggerChangeForEl(el) { if (!el) { return; } if (typeof triggerChangeFor === 'function') { triggerChangeFor(el); return; } el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true })); } function openInitImageGroup() { try { const initEl = document.getElementById('input_initimage'); if (initEl && typeof toggleGroupOpen === 'function') { toggleGroupOpen(initEl, true); } } catch (e) { /* ignore */ } const toggler = document.getElementById('input_group_content_initimage_toggle'); if (toggler) { toggler.checked = true; triggerChangeForEl(toggler); } } function hasFileParam(id) { const el = document.getElementById(id); return !!(el && el.files && el.files.length > 0); } function clearFileParam(id) { const el = document.getElementById(id); if (!el) { return false; } try { el.value = ''; if (el.files && typeof DataTransfer !== 'undefined') { el.files = new DataTransfer().files; } } catch (e) { /* ignore */ } triggerChangeForEl(el); return true; } function guessImageMime(src) { const s = String(src || ''); if (s.startsWith('data:image/')) { const m = s.match(/^data:(image\/[a-z0-9.+-]+)/i); return (m && m[1]) || 'image/png'; } const path = s.split('?')[0]; const ext = path.substring(path.lastIndexOf('.') + 1).toLowerCase(); if (ext === 'jpg' || ext === 'jpeg') { return 'image/jpeg'; } if (ext === 'webp') { return 'image/webp'; } if (ext === 'gif') { return 'image/gif'; } return 'image/png'; } async function srcToBlob(src) { if (!src) { return null; } const cleaned = String(src).trim().split(/\s+/)[0]; if (cleaned.startsWith('data:') || cleaned.startsWith('/') || cleaned.startsWith('View/') || cleaned.startsWith('http')) { try { const resp = await fetch(cleaned); return await resp.blob(); } catch (e) { console.warn('Assistent: fetch blob failed', e); } } return await new Promise((resolve) => { const tmpImg = new Image(); tmpImg.crossOrigin = 'Anonymous'; tmpImg.onload = () => { try { const canvas = document.createElement('canvas'); canvas.width = tmpImg.naturalWidth; canvas.height = tmpImg.naturalHeight; const ctx = canvas.getContext('2d'); ctx.drawImage(tmpImg, 0, 0); canvas.toBlob((blob) => resolve(blob), 'image/png'); } catch (e) { resolve(null); } }; tmpImg.onerror = () => resolve(null); tmpImg.src = cleaned; }); } async function setFileParamFromSrc(paramId, src, { filename = 'assistent.png' } = {}) { const el = document.getElementById(paramId); if (!el) { setStatus(`Missing ${paramId} on Generate tab`); return false; } const blob = await srcToBlob(src); if (!blob) { setStatus('Could not load image for init/mask'); return false; } const mime = blob.type || guessImageMime(src); const file = new File([blob], filename, { type: mime }); const container = new DataTransfer(); container.items.add(file); el.files = container.files; triggerChangeForEl(el); openInitImageGroup(); return true; } async function setInitFromSrc(src) { const ok = await setFileParamFromSrc('input_initimage', src, { filename: 'assistent_init.png' }); if (ok) { setStatus('Init Image set'); } return ok; } async function setMaskFromSrc(src) { const ok = await setFileParamFromSrc('input_maskimage', src, { filename: 'assistent_mask.png' }); if (ok) { setStatus('Mask Image set (white = edit)'); } return ok; } function clearInitAndMask() { clearFileParam('input_initimage'); clearFileParam('input_maskimage'); const toggler = document.getElementById('input_group_content_initimage_toggle'); if (toggler) { toggler.checked = false; triggerChangeForEl(toggler); } setStatus('Init Image + Mask cleared'); } function readInitContext() { const creativityRaw = val('input_initimagecreativity'); const creativity = creativityRaw === '' ? null : parseFloat(creativityRaw); return { has_init_image: hasFileParam('input_initimage'), has_mask_image: hasFileParam('input_maskimage'), init_creativity: Number.isFinite(creativity) ? creativity : null, mask_blur: parseFloat(val('input_maskblur') || '') || null, mask_grow: parseInt(val('input_maskgrow') || val('input_maskshrinkgrow') || '', 10) || null, init_group_on: !!(document.getElementById('input_group_content_initimage_toggle')?.checked), }; } function collectLiveContext() { const inv = state.inventory || {}; const initCtx = readInitContext(); 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, sampler: val('input_sampler') || val('input_samplerate') || null, scheduler: val('input_scheduler') || null, batch: parseInt(val('input_images') || val('input_batchsize') || '0', 10) || null, prompt_image_count: countPromptImages(), selected_loras: [], available_loras: (inv.loras || []).slice(0, 100), wildcards: (inv.wildcards || []).map((w) => w.name || w).slice(0, 60), has_vision_image: !!state.lastImageDataUrl, has_civitai_key: !!inv.has_civitai_key, auto_apply: !!$('sa_auto_apply')?.checked, auto_generate: !!$('sa_auto_generate')?.checked, ...initCtx, }; 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 */ } // Fallback if inventory empty if (!ctx.available_loras.length) { 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)) { 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 */ } } return ctx; } function isPatchObject(obj) { if (!obj || typeof obj !== 'object') { return false; } return ( obj.prompt != null || obj.loras || obj.width || obj.height || obj.steps || obj.cfg || obj.seed != null || obj.sigma_shift != null || obj.sampler || obj.actions || obj.search_query || obj.civitai_query || obj.use_init_image != null || obj.clear_init_image != null || obj.init_creativity != null || obj.denoise != null || obj.use_mask_image != null || obj.clear_mask_image != null || obj.mask_blur != null || obj.mask_grow != null ); } 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 (isPatchObject(obj)) { 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 patchHasGenTrigger(patch) { if (!patch) { return false; } if (Array.isArray(patch.actions) && patch.actions.map(String).includes('generate')) { return true; } return ( patch.prompt != null || patch.loras || patch.width != null || patch.height != null || patch.steps != null || patch.cfg != null || patch.seed != null || patch.sigma_shift != null || patch.use_init_image || patch.clear_init_image || patch.init_creativity != null || patch.denoise != null || patch.use_mask_image || patch.clear_mask_image ); } async function applyPatch(patch, which) { if (!patch) { return; } const doPrompt = !which || which === 'all' || which === 'prompt'; const doLoras = !which || which === 'all' || which === 'loras'; const doParams = !which || which === 'all' || which === 'size' || which === 'params'; const doInit = !which || which === 'all' || which === 'params' || which === 'init'; 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 (doParams) { 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)); } } if (patch.seed != null) { setVal('input_seed', String(patch.seed)); } if (patch.sigma_shift != null) { setVal('input_sigmashift', String(patch.sigma_shift)); } if (patch.sampler != null) { if (document.getElementById('input_sampler')) { setVal('input_sampler', String(patch.sampler)); } } if (patch.scheduler != null && document.getElementById('input_scheduler')) { setVal('input_scheduler', String(patch.scheduler)); } } if (doInit) { const creativity = patch.init_creativity != null ? patch.init_creativity : patch.denoise; if (creativity != null && document.getElementById('input_initimagecreativity')) { setVal('input_initimagecreativity', String(creativity)); openInitImageGroup(); } if (patch.mask_blur != null && document.getElementById('input_maskblur')) { setVal('input_maskblur', String(patch.mask_blur)); } if (patch.mask_grow != null) { if (document.getElementById('input_maskgrow')) { setVal('input_maskgrow', String(patch.mask_grow)); } else if (document.getElementById('input_maskshrinkgrow')) { setVal('input_maskshrinkgrow', String(patch.mask_grow)); } } if (patch.clear_init_image || patch.clear_mask_image) { if (patch.clear_init_image) { clearFileParam('input_initimage'); } if (patch.clear_mask_image) { clearFileParam('input_maskimage'); } if (patch.clear_init_image && patch.clear_mask_image) { const toggler = document.getElementById('input_group_content_initimage_toggle'); if (toggler) { toggler.checked = false; triggerChangeForEl(toggler); } } } const src = state.lastImageDataUrl || findCurrentGenerateSrc(); const wantInit = patch.use_init_image === true || (Array.isArray(patch.actions) && patch.actions.map(String).includes('use_init')); const wantMask = patch.use_mask_image === true || (Array.isArray(patch.actions) && patch.actions.map(String).includes('use_mask')); if (wantInit) { if (src) { await setInitFromSrc(src); } else { setStatus('No image for Init — drop/Send to Assistent first'); } } if (wantMask) { if (src) { await setMaskFromSrc(src); } else { setStatus('No image for Mask — drop a mask (white=edit) first'); } } } setStatus('Applied patch'); } function triggerGenerate() { try { if (typeof mainGenHandler !== 'undefined' && mainGenHandler && typeof mainGenHandler.doGenerate === 'function') { mainGenHandler.doGenerate(); return true; } } catch (e) { console.warn('Assistent: doGenerate failed', e); } const btn = document.getElementById('generate_button') || document.getElementById('alt_generate_button') || document.querySelector('button.generate-button') || document.querySelector('#generate_button, button[id*="generate"]'); if (btn) { btn.click(); return true; } return false; } function doInterruptNow() { try { if (typeof doInterrupt === 'function') { doInterrupt(false); return; } } catch (e) { /* ignore */ } if (typeof genericRequest === 'function') { genericRequest('InterruptAll', { other_sessions: false }, () => {}, 0, () => {}); } } function waitForNewImage(prevSrc, timeoutMs = 180000) { return new Promise((resolve) => { const start = Date.now(); const timer = setInterval(() => { const src = findCurrentGenerateSrc(); if (src && src !== prevSrc && !looksLikeModelPreview(src)) { clearInterval(timer); resolve(src); } else if (Date.now() - start > timeoutMs) { clearInterval(timer); resolve(null); } }, 400); }); } async function runGenerateFromPatch(patch) { if (!$('sa_auto_generate')?.checked || !patchHasGenTrigger(patch)) { return null; } const prev = findCurrentGenerateSrc(); setStatus('Generating…'); startBusyUi('generating'); state.generating = true; setInterruptVisible(true); const ok = triggerGenerate(); if (!ok) { state.generating = false; setInterruptVisible(state.busy); setStatus('Could not start Generate (UI hook missing)'); return null; } const src = await waitForNewImage(prev); state.generating = false; setInterruptVisible(state.busy); if (!state.busy) { stopBusyUi(src ? 'Generate done' : 'Generate finished (no new image detected)'); } if (src) { setImageFromSrc(src, { note: 'Result → vision' }); return src; } if (state.busy) { setStatus('Generate finished (no new image detected)'); } return null; } async function maybeAutoCritique(imageSrc) { if (!$('sa_auto_critique')?.checked || state.critiqueHopUsed || !imageSrc) { return; } state.critiqueHopUsed = true; const pack = $('sa_pack'); if (pack) { pack.value = 'critique_image'; saveSettings(); } if ($('sa_input')) { $('sa_input').value = 'Critique this result and improve the prompt for the next generation.'; } setStatus('Auto-critique…'); await sendChat({ fromAutoCritique: true }); } function appendMessage(role, text, patch, civitaiResults) { const box = $('sa_messages'); if (!box) { return null; } hideChatEmpty(); 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'], ['Params', 'params'], ]) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'basic-button'; btn.textContent = label; btn.addEventListener('click', () => applyPatch(finalPatch, which)); actions.appendChild(btn); } const genBtn = document.createElement('button'); genBtn.type = 'button'; genBtn.className = 'basic-button'; genBtn.textContent = 'Apply + Generate'; genBtn.addEventListener('click', async () => { await applyPatch(finalPatch, 'all'); await runGenerateFromPatch({ ...finalPatch, actions: ['generate'] }); }); actions.appendChild(genBtn); wrap.appendChild(actions); div.appendChild(wrap); } if (civitaiResults && civitaiResults.length) { div.appendChild(buildCivitaiCards(civitaiResults)); } box.appendChild(div); box.scrollTop = box.scrollHeight; return div; } function beginStreamMessage() { const box = $('sa_messages'); if (!box) { return null; } hideChatEmpty(); const div = document.createElement('div'); div.className = 'sa-msg assistant sa-streaming sa-typing'; div.innerHTML = 'Waiting for the model…'; box.appendChild(div); box.scrollTop = box.scrollHeight; state.streamEl = div; return div; } function appendStreamDelta(delta) { if (!state.streamEl) { beginStreamMessage(); } if (state.streamEl) { if (state.streamEl.classList.contains('sa-typing')) { state.streamEl.classList.remove('sa-typing'); state.streamEl.textContent = ''; } state.gotDelta = true; if (state.busyPhase !== 'refining') { setBusyPhase('streaming'); } state.streamEl.textContent += delta; const box = $('sa_messages'); if (box) { box.scrollTop = box.scrollHeight; } } } function finalizeStreamMessage(fullReply, civitaiResults) { const el = state.streamEl; state.streamEl = null; if (!el) { appendMessage('assistant', fullReply, null, civitaiResults); return; } el.classList.remove('sa-streaming', 'sa-typing'); const { prose, patch } = extractPatch(fullReply); el.textContent = prose || fullReply || ''; if (patch) { const wrap = document.createElement('div'); wrap.className = 'sa-patch'; const pre = document.createElement('pre'); pre.textContent = JSON.stringify(patch, 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'], ['Params', 'params'], ]) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'basic-button'; btn.textContent = label; btn.addEventListener('click', () => applyPatch(patch, which)); actions.appendChild(btn); } const genBtn = document.createElement('button'); genBtn.type = 'button'; genBtn.className = 'basic-button'; genBtn.textContent = 'Apply + Generate'; genBtn.addEventListener('click', async () => { await applyPatch(patch, 'all'); await runGenerateFromPatch({ ...patch, actions: ['generate'] }); }); actions.appendChild(genBtn); wrap.appendChild(actions); el.appendChild(wrap); } if (civitaiResults && civitaiResults.length) { el.appendChild(buildCivitaiCards(civitaiResults)); } const box = $('sa_messages'); if (box) { box.scrollTop = box.scrollHeight; } } function buildCivitaiCards(results) { const list = document.createElement('div'); list.className = 'sa-civitai-list'; for (const r of results) { const card = document.createElement('div'); card.className = 'sa-civitai-card' + (r.already_installed ? ' sa-installed' : ''); const title = document.createElement('div'); title.className = 'sa-civitai-title'; title.textContent = r.name || r.file_name || 'LoRA'; card.appendChild(title); const meta = document.createElement('div'); meta.className = 'sa-civitai-meta'; const bits = [ r.base_model || '?', r.krea_likely ? 'Krea?' : null, r.already_installed ? 'already installed' : null, (r.triggers || []).slice(0, 3).join(', ') || null, ].filter(Boolean); meta.textContent = bits.join(' · '); card.appendChild(meta); const actions = document.createElement('div'); actions.className = 'sa-civitai-actions'; if (r.already_installed) { const note = document.createElement('span'); note.textContent = 'Installed'; actions.appendChild(note); } else if (r.download_url) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'basic-button sa-primary'; btn.textContent = 'Confirm download'; btn.addEventListener('click', () => downloadCivitaiLoRA(r, btn)); actions.appendChild(btn); } else { const note = document.createElement('span'); note.textContent = 'No download URL'; actions.appendChild(note); } card.appendChild(actions); list.appendChild(card); } return list; } function downloadCivitaiLoRA(card, btn) { if (!card.download_url) { return; } if (btn) { btn.disabled = true; btn.textContent = 'Downloading…'; } setStatus(`Downloading ${card.file_name || card.name}…`); setInterruptVisible(true); const payload = { url: card.download_url, type: 'LoRA', name: card.file_name || card.name || 'lora', }; const onDone = (ok, msg) => { setInterruptVisible(state.busy || state.generating); if (ok) { setStatus(`Downloaded ${payload.name}`); if (btn) { btn.textContent = 'Downloaded'; } refreshInventory(() => { if ($('sa_input')) { $('sa_input').value = `LoRA "${payload.name}" is now installed. Enable it with its triggers and improve the prompt.`; } sendChat({ fromDownload: true }); }); } else { setStatus(msg || 'Download failed'); if (btn) { btn.disabled = false; btn.textContent = 'Confirm download'; } appendMessage('error', msg || 'Download failed'); } }; if (typeof makeWSRequest === 'function') { makeWSRequest( 'DoModelDownloadWS', payload, (data) => { if (data.error) { onDone(false, String(data.error)); return; } if (data.success || data.overall_percent >= 1 || data.current_percent >= 1) { if (data.success || data.overall_percent >= 0.99) { onDone(true); } else if (data.current_percent != null) { setStatus(`Download ${(data.current_percent * 100).toFixed(0)}%`); } } }, 0, (err) => onDone(false, String(err || 'Download failed')), ); } else { onDone(false, 'makeWSRequest unavailable'); } } function wantsAutoVision() { return !!$('sa_auto_vision')?.checked; } function looksLikeModelPreview(src) { const s = String(src || '').toLowerCase(); if (!s) { return false; } return s.includes('.preview.') || s.includes('placeholder') || /\/models\//i.test(s); } function findCurrentGenerateSrc({ allowPreview = false } = {}) { let src = null; 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) { src = cur.dataset?.src || cur.src || null; } } catch (e) { /* ignore */ } if (!src) { try { if (typeof currentMetadataMap !== 'undefined' && currentMetadataMap && currentMetadataMap.image) { src = currentMetadataMap.image; } } catch (e) { /* ignore */ } } if (!src) { return null; } if (!allowPreview && looksLikeModelPreview(src)) { return null; } return src; } function refreshImagePreview({ onlyIfEmpty = false } = {}) { if (!wantsAutoVision()) { return; } if (onlyIfEmpty && state.lastImageDataUrl) { return; } const src = findCurrentGenerateSrc(); if (src) { setImageFromSrc(src); } } 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' }); } } 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, maxEdge = 1024) { if (!src) { return null; } const dataUrl = await srcToDataUrl(src); if (!dataUrl) { return null; } try { const img = await new Promise((resolve, reject) => { const el = new Image(); el.onload = () => resolve(el); el.onerror = reject; el.src = dataUrl; }); const w = img.naturalWidth || img.width || 0; const h = img.naturalHeight || img.height || 0; const edge = Math.max(w, h); const canvas = document.createElement('canvas'); if (!edge || edge <= maxEdge) { canvas.width = Math.max(w, 1); canvas.height = Math.max(h, 1); canvas.getContext('2d').drawImage(img, 0, 0); } else { const scale = maxEdge / edge; canvas.width = Math.max(1, Math.round(w * scale)); canvas.height = Math.max(1, Math.round(h * scale)); canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height); } const jpeg = canvas.toDataURL('image/jpeg', 0.85); const i = jpeg.indexOf(','); return i >= 0 ? jpeg.slice(i + 1) : null; } catch (e) { console.warn('Assistent: vision resize failed', e); const i = dataUrl.indexOf(','); return i >= 0 ? dataUrl.slice(i + 1) : null; } } async function srcToDataUrl(src) { if (src.startsWith('data:')) { return src; } try { const resp = await fetch(src); const blob = await resp.blob(); return await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(String(reader.result || '')); 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 autoApply = localStorage.getItem(LS_AUTO_APPLY); const autoGen = localStorage.getItem(LS_AUTO_GENERATE); const autoCrit = localStorage.getItem(LS_AUTO_CRITIQUE); const autoDl = localStorage.getItem(LS_AUTO_DOWNLOAD); 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 ($('sa_auto_apply')) { $('sa_auto_apply').checked = autoApply == null ? true : autoApply === '1'; } if ($('sa_auto_generate')) { $('sa_auto_generate').checked = autoGen == null ? true : autoGen === '1'; } if ($('sa_auto_critique') && autoCrit != null) { $('sa_auto_critique').checked = autoCrit === '1'; } if ($('sa_auto_download') && autoDl != null) { $('sa_auto_download').checked = autoDl === '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'); localStorage.setItem(LS_AUTO_APPLY, $('sa_auto_apply')?.checked ? '1' : '0'); localStorage.setItem(LS_AUTO_GENERATE, $('sa_auto_generate')?.checked ? '1' : '0'); localStorage.setItem(LS_AUTO_CRITIQUE, $('sa_auto_critique')?.checked ? '1' : '0'); localStorage.setItem(LS_AUTO_DOWNLOAD, $('sa_auto_download')?.checked ? '1' : '0'); } function setModelOptions(models, { error } = {}) { const sel = $('sa_model'); if (!sel) { return; } const names = (models || []).map((n) => String(n || '').trim()).filter(Boolean); sel.innerHTML = ''; if (error) { const opt = document.createElement('option'); opt.value = ''; opt.textContent = `⚠ ${String(error).replace(/\s+/g, ' ').slice(0, 90)}`; sel.appendChild(opt); sel.disabled = true; return; } sel.disabled = false; if (!names.length) { const opt = document.createElement('option'); opt.value = ''; opt.textContent = 'No Ollama models — pull / Refresh'; sel.appendChild(opt); return; } for (const name of names) { const opt = document.createElement('option'); opt.value = name; opt.textContent = name; sel.appendChild(opt); } const prefer = state.preferredModel || localStorage.getItem(LS_MODEL); if (prefer && names.includes(prefer)) { sel.value = prefer; } } function refreshModels() { const baseUrl = $('sa_base_url')?.value || 'http://127.0.0.1:11434'; setStatus('Loading models…'); if (typeof genericRequest !== 'function') { setStatus('SwarmUI API not ready'); setModelOptions([], { error: 'SwarmUI API not ready' }); return; } genericRequest( 'AssistentListModels', { baseUrl }, (data) => { const models = data.models || []; setModelOptions(models); const prefer = state.preferredModel || localStorage.getItem(LS_MODEL); if (prefer && models.includes(prefer) && $('sa_model')) { $('sa_model').value = prefer; } setStatus(models.length ? `${models.length} models` : 'No Ollama models (gpu-rent: ollama pull)'); saveSettings(); }, 0, (err) => { const msg = String(err || 'Ollama unreachable'); setStatus(msg); setModelOptions([], { error: msg }); appendMessage('error', msg); }, ); } function refreshInventory(done) { if (typeof genericRequest !== 'function') { if (done) { done(); } return; } genericRequest( 'AssistentListInventory', {}, (data) => { state.inventory = { loras: data.loras || [], checkpoints: data.checkpoints || [], wildcards: data.wildcards || [], has_civitai_key: !!data.has_civitai_key, }; setStatus(`Inventory: ${state.inventory.loras.length} LoRAs, ${state.inventory.wildcards.length} wildcards`); if (done) { done(); } }, 0, (err) => { console.warn('Assistent inventory', err); if (done) { done(); } }, ); } async function handleReplySideEffects(reply, civitaiResults, { fromAutoCritique } = {}) { const { patch } = extractPatch(reply); if (Array.isArray(patch?.actions) && patch.actions.map(String).includes('interrupt')) { doInterruptNow(); } if (civitaiResults && civitaiResults.length && $('sa_auto_download')?.checked) { const pick = civitaiResults.find((r) => !r.already_installed && r.download_url && r.krea_likely) || civitaiResults.find((r) => !r.already_installed && r.download_url); if (pick) { downloadCivitaiLoRA(pick, null); } } if (patch && $('sa_auto_apply')?.checked) { await applyPatch(patch, 'all'); if (!fromAutoCritique) { const src = await runGenerateFromPatch(patch); if (src) { await maybeAutoCritique(src); } } } } async function sendChat(opts = {}) { 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 && wantsAutoVision()) { refreshImagePreview(); } const attach = ($('sa_attach_vision')?.checked || wantsAutoVision()) && state.lastImageDataUrl; let images = null; if (attach) { startBusyUi('encoding'); setStatus('Encoding image…'); const b64 = await imageToBase64ForOllama(state.lastImageDataUrl); if (b64) { images = [b64]; } } if (!opts.fromAutoCritique && !opts.fromDownload) { state.critiqueHopUsed = false; } 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; setInterruptVisible(true); startBusyUi('thinking'); saveSettings(); const baseUrl = $('sa_base_url')?.value || 'http://127.0.0.1:11434'; // Flat fields: SwarmUI JObject params receive the whole request body. const payload = { baseUrl, model, pack, includeBase: true, messages, context_json: JSON.stringify(context), raw: { messages, context_json: JSON.stringify(context), pack, base_url: baseUrl, model, }, }; const finishOk = async (reply, civitaiResults) => { state.busy = false; setInterruptVisible(state.generating); state.history.push({ role: 'assistant', content: reply }); stopBusyUi('Done'); await handleReplySideEffects(reply, civitaiResults, opts); }; const finishErr = (msg) => { state.busy = false; setInterruptVisible(state.generating); stopBusyUi(msg); if (state.streamEl) { state.streamEl.classList.remove('sa-streaming', 'sa-typing'); state.streamEl.classList.add('error'); state.streamEl.textContent = msg; state.streamEl = null; } else { appendMessage('error', msg); } }; if (typeof makeWSRequest === 'function') { beginStreamMessage(); makeWSRequest( 'AssistentChatWS', payload, (data) => { if (data.phase === 'waiting_ollama') { setBusyPhase('loading'); const label = state.streamEl?.querySelector('.sa-typing-label'); if (label) { label.textContent = `Loading ${modelShort(model)} into GPU…`; } return; } if (data.error) { finishErr(String(data.error)); return; } if (data.clear_stream) { if (state.streamEl) { state.streamEl.classList.add('sa-typing'); state.streamEl.innerHTML = 'Refining…'; } setBusyPhase('refining'); return; } if (data.delta) { appendStreamDelta(data.delta); return; } if (data.done || data.reply != null) { const reply = data.reply || (state.streamEl && state.streamEl.textContent) || ''; const civitai = data.civitai_results || []; finalizeStreamMessage(reply, civitai); finishOk(reply, civitai); } }, 0, (err) => { // Fallback to HTTP AssistentChat console.warn('AssistentChatWS failed, falling back', err); if (state.streamEl) { state.streamEl.remove(); state.streamEl = null; } genericRequest( 'AssistentChat', payload, (data) => { if (data.error) { finishErr(String(data.error)); return; } const reply = data.reply || ''; appendMessage('assistant', reply, null, data.civitai_results || []); finishOk(reply, data.civitai_results || []); }, 0, (err2) => finishErr(String(err2 || err || 'Chat failed')), ); }, ); return; } genericRequest( 'AssistentChat', payload, (data) => { if (data.error) { finishErr(String(data.error)); return; } const reply = data.reply || ''; appendMessage('assistant', reply, null, data.civitai_results || []); finishOk(reply, data.civitai_results || []); }, 0, (err) => finishErr(String(err || 'Chat failed')), ); } 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); }); 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); }); 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')) { 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') { setTimeout(registerSendButton, 500); return; } if (window.__swarmAssistentMediaRegistered) { return; } window.__swarmAssistentMediaRegistered = true; registerMediaButton( 'Send to Assistent', (src) => { setImageFromSrc(src, { switchTab: true, note: 'Image sent to Assistent', }); 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; } if (typeof genericRequest !== 'function') { setTimeout(wire, 300); return; } if (window.__swarmAssistentWired) { return; } window.__swarmAssistentWired = true; loadSettings(); updateGate(); if (wantsAutoVision()) { refreshImagePreview(); } refreshModels(); refreshInventory(); 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_refresh_inventory')?.addEventListener('click', () => refreshInventory()); $('sa_btn_use_current')?.addEventListener('click', () => { const src = findCurrentGenerateSrc({ allowPreview: true }); if (src) { setImageFromSrc(src, { note: 'Using current Generate image' }); } else { setStatus('No current Generate image'); } }); $('sa_btn_as_init')?.addEventListener('click', async () => { const src = state.lastImageDataUrl || findCurrentGenerateSrc(); if (!src) { setStatus('No vision/Generate image for Init'); return; } if (!state.lastImageDataUrl) { setImageFromSrc(src); } await setInitFromSrc(src); const pack = $('sa_pack'); if (pack && pack.value === 'write_prompt') { pack.value = 'inpaint_edit'; saveSettings(); } }); $('sa_btn_as_mask')?.addEventListener('click', async () => { const src = state.lastImageDataUrl || findCurrentGenerateSrc(); if (!src) { setStatus('No vision image for Mask'); return; } await setMaskFromSrc(src); const pack = $('sa_pack'); if (pack) { pack.value = 'inpaint_edit'; saveSettings(); } }); $('sa_btn_clear_init')?.addEventListener('click', clearInitAndMask); $('sa_btn_clear_image')?.addEventListener('click', clearVisionImage); $('sa_btn_send')?.addEventListener('click', () => sendChat()); $('sa_btn_interrupt')?.addEventListener('click', () => { doInterruptNow(); state.busy = false; state.generating = false; setInterruptVisible(false); if (state.streamEl?.classList.contains('sa-typing')) { state.streamEl.remove(); state.streamEl = null; } stopBusyUi('Interrupted'); }); $('sa_btn_clear')?.addEventListener('click', () => { state.history = []; state.critiqueHopUsed = false; const box = $('sa_messages'); if (box) { box.innerHTML = ''; const empty = document.createElement('div'); empty.className = 'sa-chat-empty'; empty.id = 'sa_chat_empty'; empty.innerHTML = '
Collaborative Krea 2
Write a prompt, drop a reference on the left, or send the current Generate image.
'; box.appendChild(empty); } stopBusyUi(''); 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); $('sa_auto_apply')?.addEventListener('change', saveSettings); $('sa_auto_generate')?.addEventListener('change', saveSettings); $('sa_auto_critique')?.addEventListener('change', saveSettings); $('sa_auto_download')?.addEventListener('change', saveSettings); setInterval(updateGate, 2000); setInterval(() => refreshImagePreview({ onlyIfEmpty: true }), 5000); setInterval(() => { if (!state.busy) { refreshInventory(); } }, 120000); window.swarmAssistent = { setImageFromSrc, clearVisionImage, openAssistentTab, sendToAssistent: (src) => setImageFromSrc(src, { switchTab: true, note: 'Image sent to Assistent' }), isKreaSelected, resolveCurrentCheckpoint, refreshInventory, applyPatch, triggerGenerate, setInitFromSrc, setMaskFromSrc, clearInitAndMask, }; } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', wire); } else { wire(); } })();