/** * Swarm Assistent — patch detection / extraction / alias normalization. * Loaded before assistent.js; mirrors AssistentPatch.cs on the server side. */ window.SA = window.SA || {}; (function () { const PATCH_KEYS = [ 'prompt', 'loras', 'width', 'height', 'steps', 'cfg', 'seed', 'sigma_shift', 'sampler', 'actions', 'search_query', 'civitai_query', 'use_init_image', 'clear_init_image', 'init_creativity', 'denoise', 'use_mask_image', 'clear_mask_image', 'mask_blur', 'mask_grow', 'look_at', 'vision_from', 'vision_slots', 'slot_to_init', 'slot_to_mask', 'snapshot_generate', 'select_slot', 'aspect', 'images', 'batch', 'vary', 'lock_seed', 'creativity', 'intensity', 'complexity', 'movement', 'clear_prompt_images', 'slot_to_prompt_image', 'pack', 'memories', 'memory_query', 'memory_kind', 'tag_query', 'user_prefs', 'controls', 'persona_clone', 'persona_shelves', 'persona', 'notes', 'variants', ]; const FENCE_RE = /```(?:json)?\s*([\s\S]*?)```/gi; function has(obj, key) { return obj[key] !== undefined && obj[key] !== null; } /** Model-card JSON (catalog_card) — must not be treated as a Generate patch. */ function isCardObject(obj) { if (!obj || typeof obj !== 'object') { return false; } const cardish = !!(obj.kind || obj.triggers || obj.when || obj.prompt_hint); const genish = !!(obj.prompt != null || obj.loras || obj.actions || obj.width || obj.height || obj.steps || obj.cfg || obj.aspect || obj.seed != null || obj.search_query || obj.civitai_query || obj.look_at || obj.controls); if (cardish && !genish && (obj.name || obj.triggers || obj.when)) { return true; } return !!(obj.kind && obj.name && (obj.triggers || obj.when || obj.prompt_hint || obj.notes != null)); } /** True when the object looks like a generation patch rather than a catalog card / arbitrary JSON. */ function isPatchObject(obj) { if (!obj || typeof obj !== 'object') { return false; } if (isCardObject(obj)) { return false; } return PATCH_KEYS.some((k) => has(obj, k)); } /** Maps alias fields onto canonical names, keeping the aliases in place. */ function normalizePatch(patch) { if (!patch || typeof patch !== 'object') { return patch; } if (!has(patch, 'search_query') && has(patch, 'civitai_query')) { patch.search_query = patch.civitai_query; } if (!has(patch, 'init_creativity') && has(patch, 'denoise')) { patch.init_creativity = patch.denoise; } if (!has(patch, 'look_at')) { if (has(patch, 'vision_from')) { patch.look_at = patch.vision_from; } else if (has(patch, 'vision_slots')) { patch.look_at = patch.vision_slots; } } return patch; } /** Splits a reply into prose and the last fenced patch object found in it. */ function extractPatch(text) { if (!text) { return { prose: text || '', patch: null }; } const re = new RegExp(FENCE_RE.source, 'gi'); let match; let lastPatch = null; let prose = text; while ((match = re.exec(text)) !== null) { try { const obj = JSON.parse(match[1].trim()); if (isPatchObject(obj)) { lastPatch = normalizePatch(obj); prose = (text.slice(0, match.index) + text.slice(match.index + match[0].length)).trim(); } } catch (e) { /* not json */ } } return { prose, patch: lastPatch }; } /** * Closed fence worth freezing the stream / stopping Ollama early. * Weak fences (pack / creativity / empty) must NOT stop — model often continues with the real patch. */ function isTerminalStreamPatch(obj) { if (!obj || typeof obj !== 'object') { return false; } if (isCardObject(obj)) { return true; } if (Array.isArray(obj.variants) && obj.variants.length) { return true; } if (obj.look_at != null || obj.vision_from != null || obj.vision_slots != null) { return true; } if (obj.search_query != null || obj.civitai_query != null || obj.memory_query != null || obj.tag_query != null || obj.inventory_query != null) { return true; } const acts = Array.isArray(obj.actions) ? obj.actions.map(String) : []; const hopOrGen = [ 'skill_load', 'persona_read', 'memory_get', 'memory_search', 'lookup_tags', 'list_inventory', 'search_civitai', 'interrupt', 'generate', 'memory_upsert', 'user_pref_upsert', ]; if (acts.some((a) => hopOrGen.includes(a))) { return true; } if (String(obj.prompt || '').trim().length >= 48) { return true; } if (obj.loras != null || obj.aspect != null || obj.steps != null || obj.width != null || obj.height != null || obj.cfg != null || obj.seed != null || obj.controls != null || obj.memories != null || obj.user_prefs != null) { return true; } return false; } SA.PATCH_KEYS = PATCH_KEYS; SA.isCardObject = isCardObject; SA.isPatchObject = isPatchObject; SA.isTerminalStreamPatch = isTerminalStreamPatch; SA.normalizePatch = normalizePatch; SA.extractPatch = extractPatch; })();