Fix model-card previews leaking into Generate and vision.

Treat ViewSpecial/Models/*.preview URLs as non-gens, scrub them from the board, and never attach them to chat/look_at/snapshot.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-21 23:16:21 +03:00
co-authored by Cursor
parent 1b9cc1ad78
commit 714da5c38b
4 changed files with 65 additions and 16 deletions
+62 -13
View File
@@ -1,6 +1,6 @@
/**
* Swarm Assistent — Krea 2 collaborative chat (Ollama via Swarm API).
* v0.7.0: Config presets, persona folders, exact KV + vector memory, chat|memory model roles.
* v0.7.1: Exact KV memory; block SwarmUI ViewSpecial/model-card previews from Generate+vision.
*/
(function () {
const LS_BASE = 'swarm_assistent_base_url';
@@ -667,7 +667,7 @@
return state.slots.filter((s) => s.attach && s.src);
}
function setSlotSrc(id, src, { select = true, attach = null, note = null, switchTab = false } = {}) {
function setSlotSrc(id, src, { select = true, attach = null, note = null, switchTab = false, allowPreview = false } = {}) {
const slot = slotById(id);
if (!slot) {
return false;
@@ -676,6 +676,10 @@
if (cleaned && cleaned.startsWith('#')) {
return false;
}
if (cleaned && !allowPreview && looksLikeModelPreview(cleaned)) {
setStatus('Пропуск превью модели (нужна реальная генерация)');
return false;
}
slot.src = cleaned || null;
if (attach != null) {
slot.attach = !!attach;
@@ -745,9 +749,12 @@
}
function snapshotGenerateToRef() {
const src = generateSlot()?.src || findCurrentGenerateSrc({ allowPreview: true });
// Never promote checkpoint/LoRA card previews into Refs.
const src = generateSlot()?.src && !looksLikeModelPreview(generateSlot().src)
? generateSlot().src
: findCurrentGenerateSrc({ allowPreview: false });
if (!src) {
setStatus('Нет текущего кадра Generate');
setStatus('Нет текущего кадра Generate (превью модели не считается)');
return false;
}
const empty = refSlots().find((s) => !s.src);
@@ -1120,11 +1127,15 @@
}
function syncGenerateSlot() {
const src = findCurrentGenerateSrc();
const slot = generateSlot();
if (!slot) {
return;
}
// Drop checkpoint/LoRA card previews that slipped into the Generate pane.
if (scrubPreviewFromGenerateSlot()) {
renderBoard();
}
const src = findCurrentGenerateSrc();
if (src && src !== slot.src) {
slot.src = src;
const img = document.querySelector('.sa-slot-gen img');
@@ -1140,6 +1151,17 @@
empty.hidden = true;
}
frame?.classList.add('sa-has-image');
} else if (!src && !slot.src) {
const empty = document.querySelector('.sa-slot-gen .sa-image-empty');
const frame = document.querySelector('.sa-slot-gen');
const img = document.querySelector('.sa-slot-gen img');
if (img) {
img.remove();
}
if (empty) {
empty.hidden = false;
}
frame?.classList.remove('sa-has-image', 'sa-attached');
}
syncGenerateBusy();
syncPatchActionAvailability();
@@ -2824,9 +2846,17 @@
if (!s) {
return false;
}
return s.includes('.preview.')
// SwarmUI checkpoint/LoRA cards: ViewSpecial/*, View/Models/*, *.preview.*, PLACEHOLDER.
if (s.includes('.preview.')
|| s.includes('placeholder')
|| /\/models\//i.test(s);
|| s.includes('/viewspecial/')
|| s.includes('viewspecial/')
|| s.includes('/models/')
|| s.includes('view/models/')
|| /[?&](?:path|file)=[^&]*\.preview\./i.test(s)) {
return true;
}
return false;
}
function findCurrentGenerateSrc({ allowPreview = false } = {}) {
@@ -2837,7 +2867,7 @@
|| document.querySelector('.current-image img')
|| document.querySelector('#current_image_batch img');
if (cur) {
src = cur.dataset?.src || cur.src || null;
src = cur.dataset?.src || cur.getAttribute?.('data-src') || cur.src || null;
}
} catch (e) { /* ignore */ }
if (!src) {
@@ -2850,20 +2880,38 @@
if (!src) {
return null;
}
// Strip cache-busters for classification, keep original for display when accepted.
if (!allowPreview && looksLikeModelPreview(src)) {
return null;
}
return src;
}
function scrubPreviewFromGenerateSlot() {
const slot = generateSlot();
if (!slot?.src) {
return false;
}
if (!looksLikeModelPreview(slot.src)) {
return false;
}
slot.src = null;
slot.attach = false;
syncLastImageAlias();
return true;
}
function refreshImagePreview() {
scrubPreviewFromGenerateSlot();
if (wantsAutoVision()) {
const gen = generateSlot();
if (gen) {
gen.attach = true;
const src = findCurrentGenerateSrc();
if (src) {
gen.attach = true;
gen.src = src;
} else {
gen.attach = false;
}
renderBoard();
}
@@ -4409,7 +4457,8 @@
if (!ids.length || state.visionHopUsed) {
return false;
}
const have = ids.map((id) => slotById(id)).filter((s) => s && s.src);
scrubPreviewFromGenerateSlot();
const have = ids.map((id) => slotById(id)).filter((s) => s && s.src && !looksLikeModelPreview(s.src));
if (!have.length) {
const genSrc = findCurrentGenerateSrc();
if (ids.includes(GEN_ID) && genSrc) {
@@ -4421,7 +4470,7 @@
}
}
if (!have.length) {
setStatus('look_at: those windows are empty');
setStatus('look_at: нет реального кадра (превью модели пропущено)');
return false;
}
const already = new Set(attachedSlotIds || []);
@@ -4521,10 +4570,10 @@
if (!wantedIds.length) {
wantedIds = attachableSlots().map((s) => s.id);
}
if (wantsAutoVision() && generateSlot()?.src && !wantedIds.includes(GEN_ID)) {
if (wantsAutoVision() && generateSlot()?.src && !looksLikeModelPreview(generateSlot().src) && !wantedIds.includes(GEN_ID)) {
wantedIds.push(GEN_ID);
}
const visionSlots = wantedIds.map((id) => slotById(id)).filter((s) => s && s.src);
const visionSlots = wantedIds.map((id) => slotById(id)).filter((s) => s && s.src && !looksLikeModelPreview(s.src));
let images = null;
if (visionSlots.length) {
startBusyUi('encoding');