Ship Assistent 0.10.2: lean context packing with quality-first vision flags.
Cut always-on system tokens (slim core, inventory, identity shelves, hops) and fix has_vision_image vs images_in_request so look_at gates correctly; enrich rich LoRAs and expose system_chars in debug. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+169
-81
@@ -27,6 +27,8 @@
|
|||||||
let MAX_REF_SLOTS = 4;
|
let MAX_REF_SLOTS = 4;
|
||||||
let CONTEXT_PROMPT_MAX = 2000;
|
let CONTEXT_PROMPT_MAX = 2000;
|
||||||
let HISTORY_KEEP_TURNS = 4;
|
let HISTORY_KEEP_TURNS = 4;
|
||||||
|
let INVENTORY_PROMPT_RICH = 12;
|
||||||
|
let INVENTORY_PROMPT_NAMES = 24;
|
||||||
|
|
||||||
let ASPECT_TABLE = {
|
let ASPECT_TABLE = {
|
||||||
'1:1': [1024, 1024],
|
'1:1': [1024, 1024],
|
||||||
@@ -136,6 +138,8 @@
|
|||||||
streamMeta: null,
|
streamMeta: null,
|
||||||
critiqueHopUsed: false,
|
critiqueHopUsed: false,
|
||||||
visionHopUsed: false,
|
visionHopUsed: false,
|
||||||
|
lastSystemChars: 0,
|
||||||
|
lastContextChars: 0,
|
||||||
busyPhase: 'idle',
|
busyPhase: 'idle',
|
||||||
busyStarted: 0,
|
busyStarted: 0,
|
||||||
gotDelta: false,
|
gotDelta: false,
|
||||||
@@ -784,6 +788,12 @@
|
|||||||
return state.slots.filter((s) => s.attach && s.src);
|
return state.slots.filter((s) => s.attach && s.src);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Real board frames (not model previews). Used for has_vision_image even when JPEG is not sent. */
|
||||||
|
function visionReadySlots() {
|
||||||
|
ensureBoard();
|
||||||
|
return state.slots.filter((s) => s && s.src && !looksLikeModelPreview(s.src));
|
||||||
|
}
|
||||||
|
|
||||||
function setSlotSrc(id, src, { select = true, attach = null, note = null, switchTab = false, allowPreview = false } = {}) {
|
function setSlotSrc(id, src, { select = true, attach = null, note = null, switchTab = false, allowPreview = false } = {}) {
|
||||||
const slot = slotById(id);
|
const slot = slotById(id);
|
||||||
if (!slot) {
|
if (!slot) {
|
||||||
@@ -2374,11 +2384,11 @@
|
|||||||
batch: parseInt(val('input_images') || val('input_batchsize') || '0', 10) || null,
|
batch: parseInt(val('input_images') || val('input_batchsize') || '0', 10) || null,
|
||||||
prompt_image_count: countPromptImages(),
|
prompt_image_count: countPromptImages(),
|
||||||
selected_loras: [],
|
selected_loras: [],
|
||||||
available_loras: slimInventoryLoras(inv.loras || [], 100),
|
available_loras: [],
|
||||||
available_checkpoints: slimInventoryCheckpoints(inv.checkpoints || [], 40),
|
available_checkpoints: slimInventoryCheckpoints(inv.checkpoints || [], 8),
|
||||||
wildcards: (inv.wildcards || []).map((w) => w.name || w).slice(0, 60),
|
wildcards: (inv.wildcards || []).map((w) => w.name || w).slice(0, 20),
|
||||||
inventory_at: inv.inventory_at || null,
|
inventory_at: inv.inventory_at || null,
|
||||||
has_vision_image: attachableSlots().length > 0,
|
has_vision_image: visionReadySlots().length > 0,
|
||||||
image_slots: slotCatalog(),
|
image_slots: slotCatalog(),
|
||||||
attached_slot_ids: attachableSlots().map((s) => s.id),
|
attached_slot_ids: attachableSlots().map((s) => s.id),
|
||||||
has_civitai_key: !!inv.has_civitai_key,
|
has_civitai_key: !!inv.has_civitai_key,
|
||||||
@@ -2389,6 +2399,14 @@
|
|||||||
user_prefs_count: 0,
|
user_prefs_count: 0,
|
||||||
...initCtx,
|
...initCtx,
|
||||||
};
|
};
|
||||||
|
{
|
||||||
|
const slim = slimInventoryLoras(inv.loras || [], INVENTORY_PROMPT_NAMES);
|
||||||
|
ctx.available_loras = slim;
|
||||||
|
if ((inv.loras || []).length > slim.length) {
|
||||||
|
ctx.available_loras_truncated = true;
|
||||||
|
ctx.available_loras_total = (inv.loras || []).length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const model = resolveCurrentCheckpoint();
|
const model = resolveCurrentCheckpoint();
|
||||||
@@ -2403,14 +2421,35 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof loraHelper !== 'undefined' && loraHelper && Array.isArray(loraHelper.selected)) {
|
if (typeof loraHelper !== 'undefined' && loraHelper && Array.isArray(loraHelper.selected)) {
|
||||||
ctx.selected_loras = loraHelper.selected.map((l) => ({
|
const byName = new Map();
|
||||||
name: l.name || l,
|
for (const l of inv.loras || []) {
|
||||||
weight: (loraHelper.loraWeightPref && loraHelper.loraWeightPref[l.name || l]) || 1,
|
if (l?.name) {
|
||||||
}));
|
byName.set(String(l.name).toLowerCase(), l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.selected_loras = loraHelper.selected.map((l) => {
|
||||||
|
const name = l.name || l;
|
||||||
|
const invRow = byName.get(String(name).toLowerCase()) || {};
|
||||||
|
const out = {
|
||||||
|
name,
|
||||||
|
weight: (loraHelper.loraWeightPref && loraHelper.loraWeightPref[name]) || invRow.default_weight || 1,
|
||||||
|
};
|
||||||
|
if (invRow.trigger_phrase) {
|
||||||
|
out.trigger_phrase = invRow.trigger_phrase;
|
||||||
|
}
|
||||||
|
if (Array.isArray(invRow.triggers) && invRow.triggers.length) {
|
||||||
|
out.triggers = invRow.triggers.slice(0, 8);
|
||||||
|
}
|
||||||
|
if (invRow.blurb) {
|
||||||
|
out.blurb = invRow.blurb;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
ctx.enabled_loras = ctx.selected_loras;
|
||||||
}
|
}
|
||||||
} catch (e) { /* ignore */ }
|
} catch (e) { /* ignore */ }
|
||||||
|
|
||||||
// Recommendation cards: checkpoint + selected LoRAs + other has_card entries (capped).
|
// Recommendation cards: checkpoint + selected LoRAs only (no has_card sweep).
|
||||||
const cardKeys = [];
|
const cardKeys = [];
|
||||||
const seenCard = new Set();
|
const seenCard = new Set();
|
||||||
const addKey = (kind, name) => {
|
const addKey = (kind, name) => {
|
||||||
@@ -2432,22 +2471,6 @@
|
|||||||
addKey('lora', l.name);
|
addKey('lora', l.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const l of inv.loras || []) {
|
|
||||||
if (l?.has_card && l?.name) {
|
|
||||||
addKey('lora', l.name);
|
|
||||||
}
|
|
||||||
if (cardKeys.length >= 14) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const c of inv.checkpoints || []) {
|
|
||||||
if (c?.has_card && c?.name) {
|
|
||||||
addKey('checkpoint', c.name);
|
|
||||||
}
|
|
||||||
if (cardKeys.length >= 16) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const k of cardKeys) {
|
for (const k of cardKeys) {
|
||||||
const cached = state.modelCards[`${k.kind}:${k.name}`];
|
const cached = state.modelCards[`${k.kind}:${k.name}`];
|
||||||
if (cached) {
|
if (cached) {
|
||||||
@@ -2477,8 +2500,8 @@
|
|||||||
architecture: m.architecture || null,
|
architecture: m.architecture || null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (ctx.available_loras.length > 80) {
|
if (ctx.available_loras.length > INVENTORY_PROMPT_NAMES) {
|
||||||
ctx.available_loras = ctx.available_loras.slice(0, 80);
|
ctx.available_loras = ctx.available_loras.slice(0, INVENTORY_PROMPT_NAMES);
|
||||||
}
|
}
|
||||||
} catch (e) { /* ignore */ }
|
} catch (e) { /* ignore */ }
|
||||||
}
|
}
|
||||||
@@ -2491,28 +2514,33 @@
|
|||||||
const profile = hasRaw && !hasTurbo ? 'raw' : 'turbo';
|
const profile = hasRaw && !hasTurbo ? 'raw' : 'turbo';
|
||||||
ctx.krea_profile = profile;
|
ctx.krea_profile = profile;
|
||||||
const defaults = mergedGenerationDefaults(profile);
|
const defaults = mergedGenerationDefaults(profile);
|
||||||
ctx.recommended_params = {
|
// Only send recommended_params when live UI differs from Exact-backed defaults
|
||||||
|
// (Exact itself is already in the system prompt).
|
||||||
|
const rec = {
|
||||||
steps: defaults.steps ?? 8,
|
steps: defaults.steps ?? 8,
|
||||||
cfg: defaults.cfg ?? 1,
|
cfg: defaults.cfg ?? 1,
|
||||||
sigma_shift: defaults.sigma_shift ?? 1.15,
|
sigma_shift: defaults.sigma_shift ?? 1.15,
|
||||||
};
|
};
|
||||||
if (defaults.aspect) {
|
if (defaults.aspect) {
|
||||||
ctx.recommended_params.aspect = defaults.aspect;
|
rec.aspect = defaults.aspect;
|
||||||
|
}
|
||||||
|
const liveDiffers = (ctx.steps != null && ctx.steps !== rec.steps)
|
||||||
|
|| (ctx.cfg != null && ctx.cfg !== rec.cfg)
|
||||||
|
|| (ctx.sigma_shift != null && ctx.sigma_shift !== rec.sigma_shift);
|
||||||
|
if (liveDiffers) {
|
||||||
|
ctx.recommended_params = rec;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ctx.krea_profile = 'turbo';
|
ctx.krea_profile = 'turbo';
|
||||||
const defaults = mergedGenerationDefaults('turbo');
|
|
||||||
ctx.recommended_params = {
|
|
||||||
steps: defaults.steps ?? 8,
|
|
||||||
cfg: defaults.cfg ?? 1,
|
|
||||||
sigma_shift: defaults.sigma_shift ?? 1.15,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.session_exact = state.sessionExact && Object.keys(state.sessionExact).length
|
ctx.session_exact = state.sessionExact && Object.keys(state.sessionExact).length
|
||||||
? { ...state.sessionExact }
|
? { ...state.sessionExact }
|
||||||
: {};
|
: undefined;
|
||||||
// Exact KV is already in the system prompt — do not duplicate the full blob into live context.
|
// Exact KV is already in the system prompt — do not duplicate the full blob into live context.
|
||||||
|
if (!ctx.session_exact) {
|
||||||
|
delete ctx.session_exact;
|
||||||
|
}
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
@@ -2562,7 +2590,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) { /* ignore */ }
|
} catch (e) { /* ignore */ }
|
||||||
const softCap = Math.min(limit || 100, 80);
|
const namesCap = Math.min(limit || INVENTORY_PROMPT_NAMES, INVENTORY_PROMPT_NAMES);
|
||||||
|
const richCap = Math.max(4, Math.min(INVENTORY_PROMPT_RICH, namesCap));
|
||||||
const rows = (list || []).map((l) => {
|
const rows = (list || []).map((l) => {
|
||||||
const sel = selected.has(String(l.name || '').toLowerCase());
|
const sel = selected.has(String(l.name || '').toLowerCase());
|
||||||
const hasCard = !!l.has_card;
|
const hasCard = !!l.has_card;
|
||||||
@@ -2584,58 +2613,72 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
rows.sort((a, b) => b._score - a._score || String(a.name).localeCompare(String(b.name)));
|
rows.sort((a, b) => b._score - a._score || String(a.name).localeCompare(String(b.name)));
|
||||||
// Full detail for top tier; name+trigger only for the rest within softCap.
|
// Rich: enabled + top krea/card (triggers/blurb). Rest: name (+ krea_likely) only.
|
||||||
const fullDetail = 36;
|
let richUsed = 0;
|
||||||
return rows.slice(0, softCap).map((row, idx) => {
|
const out = [];
|
||||||
const out = { name: row.name, title: row.title };
|
for (const row of rows) {
|
||||||
if (row.trigger_phrase) {
|
if (out.length >= namesCap) {
|
||||||
out.trigger_phrase = row.trigger_phrase;
|
break;
|
||||||
}
|
}
|
||||||
if (row.triggers) {
|
const sel = selected.has(String(row.name || '').toLowerCase());
|
||||||
out.triggers = row.triggers;
|
let wantRich = sel;
|
||||||
|
if (!wantRich && richUsed < richCap && (row.krea_likely || row.has_card || row.blurb)) {
|
||||||
|
wantRich = true;
|
||||||
}
|
}
|
||||||
if (row.krea_likely) {
|
if (wantRich) {
|
||||||
out.krea_likely = true;
|
const rich = { name: row.name, title: row.title };
|
||||||
}
|
if (row.trigger_phrase) {
|
||||||
if (row.has_card) {
|
rich.trigger_phrase = row.trigger_phrase;
|
||||||
out.has_card = true;
|
|
||||||
}
|
|
||||||
const rich = idx < fullDetail || row._score >= 200;
|
|
||||||
if (rich) {
|
|
||||||
if (row.architecture) {
|
|
||||||
out.architecture = row.architecture;
|
|
||||||
}
|
}
|
||||||
if (row.compat_class) {
|
if (row.triggers) {
|
||||||
out.compat_class = row.compat_class;
|
rich.triggers = row.triggers;
|
||||||
|
}
|
||||||
|
if (row.krea_likely) {
|
||||||
|
rich.krea_likely = true;
|
||||||
|
}
|
||||||
|
if (row.has_card) {
|
||||||
|
rich.has_card = true;
|
||||||
}
|
}
|
||||||
if (row.blurb) {
|
if (row.blurb) {
|
||||||
out.blurb = row.blurb;
|
rich.blurb = row.blurb;
|
||||||
}
|
}
|
||||||
if (row.default_weight) {
|
if (row.default_weight) {
|
||||||
out.default_weight = row.default_weight;
|
rich.default_weight = row.default_weight;
|
||||||
}
|
}
|
||||||
if (row.tags) {
|
if (row.architecture) {
|
||||||
out.tags = row.tags;
|
rich.architecture = row.architecture;
|
||||||
}
|
}
|
||||||
|
out.push(rich);
|
||||||
|
if (!sel) {
|
||||||
|
richUsed++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const nameOnly = { name: row.name };
|
||||||
|
if (row.krea_likely) {
|
||||||
|
nameOnly.krea_likely = true;
|
||||||
|
}
|
||||||
|
out.push(nameOnly);
|
||||||
}
|
}
|
||||||
return out;
|
}
|
||||||
});
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
function slimInventoryCheckpoints(list, limit) {
|
function slimInventoryCheckpoints(list, limit) {
|
||||||
const rows = (list || []).slice();
|
const rows = (list || []).slice();
|
||||||
rows.sort((a, b) => ((b.krea_likely ? 1 : 0) - (a.krea_likely ? 1 : 0)) || ((b.has_card ? 1 : 0) - (a.has_card ? 1 : 0)) || String(a.name).localeCompare(String(b.name)));
|
rows.sort((a, b) => ((b.krea_likely ? 1 : 0) - (a.krea_likely ? 1 : 0)) || ((b.has_card ? 1 : 0) - (a.has_card ? 1 : 0)) || String(a.name).localeCompare(String(b.name)));
|
||||||
return rows.slice(0, limit || 40).map((c) => {
|
return rows.slice(0, limit || 8).map((c) => {
|
||||||
const out = {
|
const out = {
|
||||||
name: c.name,
|
name: c.name,
|
||||||
title: c.title || c.name,
|
title: c.title || c.name,
|
||||||
architecture: c.architecture || null,
|
|
||||||
compat_class: c.compat_class || null,
|
|
||||||
has_card: !!c.has_card,
|
|
||||||
krea_likely: !!c.krea_likely,
|
|
||||||
};
|
};
|
||||||
if (c.blurb) {
|
if (c.architecture) {
|
||||||
out.blurb = c.blurb;
|
out.architecture = c.architecture;
|
||||||
|
}
|
||||||
|
if (c.krea_likely) {
|
||||||
|
out.krea_likely = true;
|
||||||
|
}
|
||||||
|
if (c.has_card) {
|
||||||
|
out.has_card = true;
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
});
|
});
|
||||||
@@ -3566,6 +3609,30 @@
|
|||||||
await sendChat({ fromAutoCritique: true, forceSlotIds: [GEN_ID] });
|
await sendChat({ fromAutoCritique: true, forceSlotIds: [GEN_ID] });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** After Generate: send look_at with JPEG when sa_auto_vision is on (skipped if auto-critique already attaches vision). */
|
||||||
|
async function maybeAutoVisionLook(imageSrc) {
|
||||||
|
if (!wantsAutoVision() || $('sa_auto_critique')?.checked || state.visionHopUsed || state.busy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const src = await resolveFinishedGenerateSrc(imageSrc);
|
||||||
|
if (!src) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const gen = generateSlot();
|
||||||
|
if (gen) {
|
||||||
|
gen.src = src;
|
||||||
|
gen.attach = true;
|
||||||
|
renderBoard();
|
||||||
|
}
|
||||||
|
state.visionHopUsed = true;
|
||||||
|
setPackValue('critique_image', { flash: true });
|
||||||
|
if ($('sa_input')) {
|
||||||
|
$('sa_input').value = 'Look at the Generate result and briefly say what worked and what to fix next.';
|
||||||
|
}
|
||||||
|
setStatus('Auto look_at…');
|
||||||
|
await sendChat({ fromVisionHop: true, forceSlotIds: [GEN_ID], skipAutoPack: true });
|
||||||
|
}
|
||||||
|
|
||||||
/** Board action: attach the finished Generate frame and ask for a verdict. */
|
/** Board action: attach the finished Generate frame and ask for a verdict. */
|
||||||
async function askLookAtResult() {
|
async function askLookAtResult() {
|
||||||
if (state.busy || state.generating) {
|
if (state.busy || state.generating) {
|
||||||
@@ -4309,6 +4376,12 @@
|
|||||||
if (asst.context_prompt_max != null) {
|
if (asst.context_prompt_max != null) {
|
||||||
CONTEXT_PROMPT_MAX = Math.max(200, Number(asst.context_prompt_max) || 2000);
|
CONTEXT_PROMPT_MAX = Math.max(200, Number(asst.context_prompt_max) || 2000);
|
||||||
}
|
}
|
||||||
|
if (asst.inventory_prompt_rich != null) {
|
||||||
|
INVENTORY_PROMPT_RICH = Math.max(4, Number(asst.inventory_prompt_rich) || 12);
|
||||||
|
}
|
||||||
|
if (asst.inventory_prompt_names != null) {
|
||||||
|
INVENTORY_PROMPT_NAMES = Math.max(INVENTORY_PROMPT_RICH, Number(asst.inventory_prompt_names) || 24);
|
||||||
|
}
|
||||||
fillKnobsFromConfig(data);
|
fillKnobsFromConfig(data);
|
||||||
if (applyDefaults || data.exact) {
|
if (applyDefaults || data.exact) {
|
||||||
fillEmptyParamsFromExact();
|
fillEmptyParamsFromExact();
|
||||||
@@ -6375,6 +6448,7 @@
|
|||||||
);
|
);
|
||||||
if (src) {
|
if (src) {
|
||||||
await maybeAutoCritique(src);
|
await maybeAutoCritique(src);
|
||||||
|
await maybeAutoVisionLook(src);
|
||||||
}
|
}
|
||||||
} else if (!state.generating) {
|
} else if (!state.generating) {
|
||||||
stopBusyUi(wantsGen ? 'Применено' : '');
|
stopBusyUi(wantsGen ? 'Применено' : '');
|
||||||
@@ -6494,9 +6568,12 @@
|
|||||||
` ckpt=${ctx.checkpoint?.name || '—'} · krea_profile=${ctx.krea_profile || profile}`,
|
` ckpt=${ctx.checkpoint?.name || '—'} · krea_profile=${ctx.krea_profile || profile}`,
|
||||||
` ${ctx.width || '?'}×${ctx.height || '?'} (${aspect}) · steps=${ctx.steps ?? '—'} · cfg=${ctx.cfg ?? '—'} · sigma=${ctx.sigma_shift ?? '—'} · seed=${ctx.seed ?? '—'} · batch=${ctx.batch ?? '—'}`,
|
` ${ctx.width || '?'}×${ctx.height || '?'} (${aspect}) · steps=${ctx.steps ?? '—'} · cfg=${ctx.cfg ?? '—'} · sigma=${ctx.sigma_shift ?? '—'} · seed=${ctx.seed ?? '—'} · batch=${ctx.batch ?? '—'}`,
|
||||||
` loras: ${formatDebugLoras(ctx.selected_loras || ctx.enabled_loras)}`,
|
` loras: ${formatDebugLoras(ctx.selected_loras || ctx.enabled_loras)}`,
|
||||||
|
` available_loras=${(ctx.available_loras || []).length}${ctx.available_loras_truncated ? ` truncated/${ctx.available_loras_total || '?'}` : ''}`,
|
||||||
` prompt: ${clipDebug(ctx.prompt, 220)}`,
|
` prompt: ${clipDebug(ctx.prompt, 220)}`,
|
||||||
` negative: ${clipDebug(ctx.negative, 120)}`,
|
` negative: ${clipDebug(ctx.negative, 120)}`,
|
||||||
` init=${!!ctx.has_init_image} mask=${!!ctx.has_mask_image} prompt_images=${ctx.prompt_image_count || 0}`,
|
` init=${!!ctx.has_init_image} mask=${!!ctx.has_mask_image} prompt_images=${ctx.prompt_image_count || 0}`,
|
||||||
|
` has_vision_image=${!!ctx.has_vision_image} · images_in_request=${!!ctx.images_in_request} · vision_ready=${visionReadySlots().length}`,
|
||||||
|
` context_json_chars≈${JSON.stringify(ctx).length} · last_system_chars=${state.lastSystemChars || '—'} · last_context_chars=${state.lastContextChars || '—'}`,
|
||||||
'',
|
'',
|
||||||
'Exact defaults (merged):',
|
'Exact defaults (merged):',
|
||||||
` generation=${JSON.stringify(exactGen)}`,
|
` generation=${JSON.stringify(exactGen)}`,
|
||||||
@@ -6853,11 +6930,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let wantedIds = (opts.forceSlotIds || []).map(normalizeSlotId).filter(Boolean);
|
let wantedIds = (opts.forceSlotIds || []).map(normalizeSlotId).filter(Boolean);
|
||||||
if (!wantedIds.length) {
|
// JPEG only on look_at / vision hop — never auto-attach every board ref to ordinary chat.
|
||||||
wantedIds = attachableSlots().map((s) => s.id);
|
const sendVision = !!(opts.fromVisionHop || (wantedIds.length && opts.forceSlotIds));
|
||||||
}
|
if (!sendVision) {
|
||||||
if (wantsAutoVision() && generateSlot()?.src && !looksLikeModelPreview(generateSlot().src) && !wantedIds.includes(GEN_ID)) {
|
wantedIds = [];
|
||||||
wantedIds.push(GEN_ID);
|
|
||||||
}
|
}
|
||||||
const visionSlots = wantedIds.map((id) => slotById(id)).filter((s) => s && s.src && !looksLikeModelPreview(s.src));
|
const visionSlots = wantedIds.map((id) => slotById(id)).filter((s) => s && s.src && !looksLikeModelPreview(s.src));
|
||||||
let images = null;
|
let images = null;
|
||||||
@@ -6897,8 +6973,12 @@
|
|||||||
persistHistory();
|
persistHistory();
|
||||||
|
|
||||||
const context = collectLiveContext();
|
const context = collectLiveContext();
|
||||||
context.has_vision_image = !!images;
|
// has_vision_image = board has a real frame (even when JPEG is not in this request).
|
||||||
context.attached_slot_ids = visionSlots.map((s) => s.id);
|
// images_in_request = JPEG bytes are attached to the last user message this turn.
|
||||||
|
context.has_vision_image = visionReadySlots().length > 0;
|
||||||
|
context.images_in_request = !!(images && images.length);
|
||||||
|
context.attached_slot_ids = attachableSlots().map((s) => s.id);
|
||||||
|
context.vision_slot_ids = visionSlots.map((s) => s.id);
|
||||||
context.persona = persona;
|
context.persona = persona;
|
||||||
if (opts.cardTarget) {
|
if (opts.cardTarget) {
|
||||||
context.card_target = opts.cardTarget;
|
context.card_target = opts.cardTarget;
|
||||||
@@ -6935,10 +7015,18 @@
|
|||||||
embed_model: $('sa_embed_model')?.value || state.preferredEmbed || '',
|
embed_model: $('sa_embed_model')?.value || state.preferredEmbed || '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const finishOk = async (reply, civitaiResults) => {
|
const finishOk = async (reply, civitaiResults, meta = {}) => {
|
||||||
if (chatEpoch !== state.chatEpoch) {
|
if (chatEpoch !== state.chatEpoch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (meta.system_chars != null) {
|
||||||
|
state.lastSystemChars = Number(meta.system_chars) || 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
state.lastContextChars = (context && JSON.stringify(context).length) || 0;
|
||||||
|
} catch (e) {
|
||||||
|
state.lastContextChars = 0;
|
||||||
|
}
|
||||||
const prose = extractPatch(reply).prose || reply;
|
const prose = extractPatch(reply).prose || reply;
|
||||||
state.history.push({ role: 'assistant', content: prose, persona, pack });
|
state.history.push({ role: 'assistant', content: prose, persona, pack });
|
||||||
persistHistory();
|
persistHistory();
|
||||||
@@ -7018,7 +7106,7 @@
|
|||||||
const reply = data.reply || (state.streamEl?.querySelector('.sa-msg-body')?.textContent) || '';
|
const reply = data.reply || (state.streamEl?.querySelector('.sa-msg-body')?.textContent) || '';
|
||||||
const civitai = data.civitai_results || [];
|
const civitai = data.civitai_results || [];
|
||||||
finalizeStreamMessage(reply, civitai);
|
finalizeStreamMessage(reply, civitai);
|
||||||
finishOk(reply, civitai);
|
finishOk(reply, civitai, { system_chars: data.system_chars });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
@@ -7046,7 +7134,7 @@
|
|||||||
}
|
}
|
||||||
const reply = data.reply || '';
|
const reply = data.reply || '';
|
||||||
appendMessage('assistant', reply, null, data.civitai_results || [], msgMeta);
|
appendMessage('assistant', reply, null, data.civitai_results || [], msgMeta);
|
||||||
finishOk(reply, data.civitai_results || []);
|
finishOk(reply, data.civitai_results || [], { system_chars: data.system_chars });
|
||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
(err2) => finishErr(String(err2 || err || 'Chat failed')),
|
(err2) => finishErr(String(err2 || err || 'Chat failed')),
|
||||||
@@ -7069,7 +7157,7 @@
|
|||||||
}
|
}
|
||||||
const reply = data.reply || '';
|
const reply = data.reply || '';
|
||||||
appendMessage('assistant', reply, null, data.civitai_results || [], msgMeta);
|
appendMessage('assistant', reply, null, data.civitai_results || [], msgMeta);
|
||||||
finishOk(reply, data.civitai_results || []);
|
finishOk(reply, data.civitai_results || [], { system_chars: data.system_chars });
|
||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
(err) => finishErr(String(err || 'Chat failed')),
|
(err) => finishErr(String(err || 'Chat failed')),
|
||||||
|
|||||||
+418
-75
@@ -1,10 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using SwarmUI.Accounts;
|
using SwarmUI.Accounts;
|
||||||
|
using SwarmUI.Core;
|
||||||
|
using SwarmUI.Text2Image;
|
||||||
using SwarmUI.Utils;
|
using SwarmUI.Utils;
|
||||||
|
|
||||||
namespace Mrleo1nid.SwarmAssistent;
|
namespace Mrleo1nid.SwarmAssistent;
|
||||||
@@ -129,7 +132,7 @@ public partial class SwarmAssistentExtension
|
|||||||
return ollamaMessages;
|
return ollamaMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<(string reply, JObject raw, JArray civitaiResults)> RunChatWithHops(
|
async Task<(string reply, JObject raw, JArray civitaiResults, int systemChars)> RunChatWithHops(
|
||||||
Session session,
|
Session session,
|
||||||
string root,
|
string root,
|
||||||
string modelName,
|
string modelName,
|
||||||
@@ -175,6 +178,15 @@ public partial class SwarmAssistentExtension
|
|||||||
string enrichedContext = InjectMemoryHits(contextJson, hits);
|
string enrichedContext = InjectMemoryHits(contextJson, hits);
|
||||||
enrichedContext = EnrichPersonaContext(enrichedContext, pid, packName);
|
enrichedContext = EnrichPersonaContext(enrichedContext, pid, packName);
|
||||||
List<JObject> messages = BuildOllamaMessages(packName, includeBase, enrichedContext, userMessages, personaId: pid, skillIds: skills);
|
List<JObject> messages = BuildOllamaMessages(packName, includeBase, enrichedContext, userMessages, personaId: pid, skillIds: skills);
|
||||||
|
int systemChars = 0;
|
||||||
|
foreach (JObject m in messages)
|
||||||
|
{
|
||||||
|
if (string.Equals(m["role"]?.ToString(), "system", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
systemChars = m["content"]?.ToString()?.Length ?? 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
JArray civitaiResults = [];
|
JArray civitaiResults = [];
|
||||||
string reply = "";
|
string reply = "";
|
||||||
JObject lastRaw = null;
|
JObject lastRaw = null;
|
||||||
@@ -210,10 +222,14 @@ public partial class SwarmAssistentExtension
|
|||||||
{
|
{
|
||||||
civitaiResults = civitaiHop;
|
civitaiResults = civitaiHop;
|
||||||
}
|
}
|
||||||
messages.Add(new JObject { ["role"] = "assistant", ["content"] = reply });
|
// Re-feed only the parsed patch JSON (not full prose) to save hop tokens.
|
||||||
|
string assistantContent = patch is not null
|
||||||
|
? patch.ToString(Newtonsoft.Json.Formatting.None)
|
||||||
|
: reply;
|
||||||
|
messages.Add(new JObject { ["role"] = "assistant", ["content"] = assistantContent });
|
||||||
messages.Add(new JObject { ["role"] = "user", ["content"] = follow });
|
messages.Add(new JObject { ["role"] = "user", ["content"] = follow });
|
||||||
}
|
}
|
||||||
return (reply, lastRaw, civitaiResults);
|
return (reply, lastRaw, civitaiResults, systemChars);
|
||||||
}
|
}
|
||||||
|
|
||||||
static string BuildRetrieveQuery(JArray userMessages, string contextJson, string packName = null)
|
static string BuildRetrieveQuery(JArray userMessages, string contextJson, string packName = null)
|
||||||
@@ -281,7 +297,7 @@ public partial class SwarmAssistentExtension
|
|||||||
JObject a = Config.LoadAssistant(pid) ?? new JObject();
|
JObject a = Config.LoadAssistant(pid) ?? new JObject();
|
||||||
AssistentMemory.RetrieveOptions opt = new()
|
AssistentMemory.RetrieveOptions opt = new()
|
||||||
{
|
{
|
||||||
TopK = a["memory_top_k"]?.Value<int?>() ?? 10,
|
TopK = a["memory_top_k"]?.Value<int?>() ?? 8,
|
||||||
MinScore = a["memory_min_score"]?.Value<float?>() ?? 0.32f,
|
MinScore = a["memory_min_score"]?.Value<float?>() ?? 0.32f,
|
||||||
ApplyQuotas = true,
|
ApplyQuotas = true,
|
||||||
};
|
};
|
||||||
@@ -347,7 +363,7 @@ public partial class SwarmAssistentExtension
|
|||||||
return (null, null);
|
return (null, null);
|
||||||
}
|
}
|
||||||
string kind = patch["memory_kind"]?.ToString();
|
string kind = patch["memory_kind"]?.ToString();
|
||||||
int topK = Config.LoadAssistant(pid)["memory_top_k"]?.Value<int?>() ?? 10;
|
int topK = Config.LoadAssistant(pid)["memory_top_k"]?.Value<int?>() ?? 8;
|
||||||
JArray rows = await Memory.SearchAsync(root, q, kind, topK, embed, chain);
|
JArray rows = await Memory.SearchAsync(root, q, kind, topK, embed, chain);
|
||||||
return (
|
return (
|
||||||
"memory_search results (JSON, hybrid FTS+vector). Omit memory_search unless you need a different query.\n```json\n"
|
"memory_search results (JSON, hybrid FTS+vector). Omit memory_search unless you need a different query.\n```json\n"
|
||||||
@@ -368,6 +384,102 @@ public partial class SwarmAssistentExtension
|
|||||||
+ tags.ToString(Newtonsoft.Json.Formatting.None) + "\n```",
|
+ tags.ToString(Newtonsoft.Json.Formatting.None) + "\n```",
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
if (tool == "list_inventory")
|
||||||
|
{
|
||||||
|
string q = patch["inventory_query"]?.ToString()?.Trim() ?? "";
|
||||||
|
string sig = "inv:" + q.ToLowerInvariant();
|
||||||
|
if (!hopDone.Add(sig))
|
||||||
|
{
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
int lim = Config.LoadAssistant(pid)["inventory_hop_limit"]?.Value<int?>() ?? 20;
|
||||||
|
JArray rows = SearchInventoryForHop(q, lim);
|
||||||
|
return (
|
||||||
|
"list_inventory results (rich LoRA/checkpoint rows). Use exact names + listed triggers; omit list_inventory unless you need a different query.\n```json\n"
|
||||||
|
+ rows.ToString(Newtonsoft.Json.Formatting.None) + "\n```",
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
if (tool == "skill_load")
|
||||||
|
{
|
||||||
|
List<string> ids = [];
|
||||||
|
if (patch["skills"] is JArray skArr)
|
||||||
|
{
|
||||||
|
foreach (JToken t in skArr)
|
||||||
|
{
|
||||||
|
string sid = AssistentConfig.SafeId(t?.ToString());
|
||||||
|
if (!string.IsNullOrWhiteSpace(sid))
|
||||||
|
{
|
||||||
|
ids.Add(sid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ids.Count == 0)
|
||||||
|
{
|
||||||
|
ids.Add("memory");
|
||||||
|
}
|
||||||
|
StringBuilder sb = new();
|
||||||
|
foreach (string sid in ids)
|
||||||
|
{
|
||||||
|
string sig = "skill:" + sid;
|
||||||
|
if (!hopDone.Add(sig))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
string text = Config.LoadSkillPrompt(pid, sid);
|
||||||
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sb.AppendLine($"## Skill: {sid}");
|
||||||
|
sb.AppendLine(text);
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
if (sb.Length == 0)
|
||||||
|
{
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
"skill_load results. Follow these skill rules on the next reply; omit skill_load unless you need another skill.\n\n"
|
||||||
|
+ sb.ToString().TrimEnd(),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
if (tool == "persona_read")
|
||||||
|
{
|
||||||
|
List<string> shelves = [];
|
||||||
|
if (patch["persona_shelves"] is JArray shArr)
|
||||||
|
{
|
||||||
|
foreach (JToken t in shArr)
|
||||||
|
{
|
||||||
|
string name = t?.ToString()?.Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
shelves.Add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (patch["persona_shelves"] is JObject shObj)
|
||||||
|
{
|
||||||
|
// Model sometimes echoes shelf objects; treat keys as names.
|
||||||
|
foreach (JProperty p in shObj.Properties())
|
||||||
|
{
|
||||||
|
shelves.Add(p.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string sig = "persona_read:" + string.Join(",", shelves);
|
||||||
|
if (!hopDone.Add(sig))
|
||||||
|
{
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
string body = Config.RenderPersonaReadBlock(pid, shelves.Count > 0 ? shelves : null);
|
||||||
|
if (string.IsNullOrWhiteSpace(body))
|
||||||
|
{
|
||||||
|
return ("persona_read: no additional lore shelves for this persona.", null);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
"persona_read results (lore shelves). Use for roleplay/appearance/outfit detail; omit persona_read unless you need different shelves.\n\n"
|
||||||
|
+ body,
|
||||||
|
null);
|
||||||
|
}
|
||||||
if (tool == "civitai")
|
if (tool == "civitai")
|
||||||
{
|
{
|
||||||
string query = ExtractSearchQuery(patch);
|
string query = ExtractSearchQuery(patch);
|
||||||
@@ -391,7 +503,48 @@ public partial class SwarmAssistentExtension
|
|||||||
return (null, null);
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static string InjectMemoryHits(string contextJson, JArray hits, JObject exact = null)
|
/// <summary>Substring filter over current LoRA/checkpoint inventory for list_inventory hop.</summary>
|
||||||
|
JArray SearchInventoryForHop(string query, int limit)
|
||||||
|
{
|
||||||
|
int lim = Math.Max(1, Math.Min(limit, 40));
|
||||||
|
string q = (query ?? "").Trim().ToLowerInvariant();
|
||||||
|
JArray outRows = [];
|
||||||
|
void AddFromHandler(string setName, string kind)
|
||||||
|
{
|
||||||
|
if (!Program.T2IModelSets.TryGetValue(setName, out T2IModelHandler handler))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IEnumerable<T2IModel> models = handler.Models.Values
|
||||||
|
.OrderByDescending(LooksLikeKreaArch)
|
||||||
|
.ThenBy(m => m.Name);
|
||||||
|
foreach (T2IModel model in models)
|
||||||
|
{
|
||||||
|
if (outRows.Count >= lim)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
string name = model.Name ?? "";
|
||||||
|
if (!string.IsNullOrEmpty(q))
|
||||||
|
{
|
||||||
|
string blob = $"{name} {model.Metadata?.UsageHint} {model.Metadata?.Description}".ToLowerInvariant();
|
||||||
|
if (!blob.Contains(q, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outRows.Add(BuildInventoryModelEntry(model, kind));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddFromHandler("LoRA", "lora");
|
||||||
|
if (outRows.Count < lim)
|
||||||
|
{
|
||||||
|
AddFromHandler("Stable-Diffusion", "checkpoint");
|
||||||
|
}
|
||||||
|
return outRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
string InjectMemoryHits(string contextJson, JArray hits, JObject exact = null)
|
||||||
{
|
{
|
||||||
JObject ctx;
|
JObject ctx;
|
||||||
try
|
try
|
||||||
@@ -402,7 +555,36 @@ public partial class SwarmAssistentExtension
|
|||||||
{
|
{
|
||||||
ctx = new JObject { ["_raw_context"] = contextJson };
|
ctx = new JObject { ["_raw_context"] = contextJson };
|
||||||
}
|
}
|
||||||
ctx["memory_hits"] = hits ?? new JArray();
|
|
||||||
|
int hitChars = 240;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string pidHit = AssistentConfig.SafeId(ctx["persona"]?.ToString()) ?? Config?.DefaultPersonaId() ?? "neutral";
|
||||||
|
hitChars = Config?.LoadAssistant(pidHit)?["memory_hit_chars"]?.Value<int?>() ?? 240;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
hitChars = 240;
|
||||||
|
}
|
||||||
|
hitChars = Math.Max(80, Math.Min(hitChars, 800));
|
||||||
|
|
||||||
|
JArray clippedHits = [];
|
||||||
|
foreach (JToken t in hits ?? [])
|
||||||
|
{
|
||||||
|
if (t is not JObject ho)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
JObject copy = (JObject)ho.DeepClone();
|
||||||
|
string text = copy["text"]?.ToString() ?? "";
|
||||||
|
if (text.Length > hitChars)
|
||||||
|
{
|
||||||
|
copy["text"] = text[..hitChars] + "…";
|
||||||
|
copy["truncated"] = true;
|
||||||
|
}
|
||||||
|
clippedHits.Add(copy);
|
||||||
|
}
|
||||||
|
ctx["memory_hits"] = clippedHits;
|
||||||
ctx.Remove("taste_profile");
|
ctx.Remove("taste_profile");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -418,72 +600,209 @@ public partial class SwarmAssistentExtension
|
|||||||
}
|
}
|
||||||
// Never re-inject full Exact into live context (already in system prompt).
|
// Never re-inject full Exact into live context (already in system prompt).
|
||||||
ctx.Remove("exact");
|
ctx.Remove("exact");
|
||||||
if (ctx["session_exact"] is null)
|
if (ctx["session_exact"] is JObject se && !se.Properties().Any())
|
||||||
{
|
{
|
||||||
ctx["session_exact"] = new JObject();
|
ctx.Remove("session_exact");
|
||||||
}
|
|
||||||
// Slim inventory for LLM: keep enabled + current, drop full dump if present
|
|
||||||
if (ctx["available_loras"] is JArray allLoras && allLoras.Count > 24)
|
|
||||||
{
|
|
||||||
HashSet<string> keep = new(StringComparer.OrdinalIgnoreCase);
|
|
||||||
if (ctx["enabled_loras"] is JArray en)
|
|
||||||
{
|
|
||||||
foreach (JToken t in en)
|
|
||||||
{
|
|
||||||
string n = t?["name"]?.ToString() ?? t?.ToString();
|
|
||||||
if (!string.IsNullOrWhiteSpace(n))
|
|
||||||
{
|
|
||||||
keep.Add(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach (JToken hit in hits ?? [])
|
|
||||||
{
|
|
||||||
if (string.Equals(hit?["kind"]?.ToString(), "lora", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| string.Equals(hit?["kind"]?.ToString(), "card", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
string k = hit?["key"]?.ToString();
|
|
||||||
if (!string.IsNullOrWhiteSpace(k))
|
|
||||||
{
|
|
||||||
keep.Add(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JArray slim = [];
|
|
||||||
foreach (JToken t in allLoras)
|
|
||||||
{
|
|
||||||
string n = t?["name"]?.ToString();
|
|
||||||
if (!string.IsNullOrWhiteSpace(n) && (keep.Contains(n) || slim.Count < 12))
|
|
||||||
{
|
|
||||||
if (keep.Contains(n) || t?["krea_likely"]?.Value<bool>() == true)
|
|
||||||
{
|
|
||||||
slim.Add(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (slim.Count == 0)
|
|
||||||
{
|
|
||||||
foreach (JToken t in allLoras.Take(12))
|
|
||||||
{
|
|
||||||
slim.Add(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx["available_loras"] = slim;
|
|
||||||
ctx["available_loras_truncated"] = true;
|
|
||||||
ctx["available_loras_total"] = allLoras.Count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always normalize inventory: keep enabled + rich top-N; name-only for the rest.
|
||||||
|
SlimAvailableLorasInContext(ctx, hits);
|
||||||
|
DropNullOrEmpty(ctx);
|
||||||
return ctx.ToString(Newtonsoft.Json.Formatting.None);
|
return ctx.ToString(Newtonsoft.Json.Formatting.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
static string MemoryWritePersona(JObject mo, string currentPersonaId)
|
void SlimAvailableLorasInContext(JObject ctx, JArray hits)
|
||||||
{
|
{
|
||||||
string scope = (mo?["scope"]?.ToString() ?? "").Trim().ToLowerInvariant();
|
if (ctx["available_loras"] is not JArray allLoras || allLoras.Count == 0)
|
||||||
if (scope is "shared" or "common" or "global")
|
|
||||||
{
|
{
|
||||||
return AssistentMemory.SharedPersona;
|
return;
|
||||||
|
}
|
||||||
|
int richCap = 12;
|
||||||
|
int namesCap = 24;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string pid = AssistentConfig.SafeId(ctx["persona"]?.ToString()) ?? Config?.DefaultPersonaId() ?? "neutral";
|
||||||
|
JObject asst = Config?.LoadAssistant(pid) ?? new JObject();
|
||||||
|
richCap = asst["inventory_prompt_rich"]?.Value<int?>() ?? 12;
|
||||||
|
namesCap = asst["inventory_prompt_names"]?.Value<int?>() ?? 24;
|
||||||
|
}
|
||||||
|
catch { /* defaults */ }
|
||||||
|
richCap = Math.Max(4, Math.Min(richCap, 40));
|
||||||
|
namesCap = Math.Max(richCap, Math.Min(namesCap, 80));
|
||||||
|
|
||||||
|
HashSet<string> keepRich = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
if (ctx["enabled_loras"] is JArray en)
|
||||||
|
{
|
||||||
|
foreach (JToken t in en)
|
||||||
|
{
|
||||||
|
string n = t?["name"]?.ToString() ?? t?.ToString();
|
||||||
|
if (!string.IsNullOrWhiteSpace(n))
|
||||||
|
{
|
||||||
|
keepRich.Add(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ctx["selected_loras"] is JArray sel)
|
||||||
|
{
|
||||||
|
foreach (JToken t in sel)
|
||||||
|
{
|
||||||
|
string n = t?["name"]?.ToString() ?? t?.ToString();
|
||||||
|
if (!string.IsNullOrWhiteSpace(n))
|
||||||
|
{
|
||||||
|
keepRich.Add(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (JToken hit in hits ?? [])
|
||||||
|
{
|
||||||
|
if (string.Equals(hit?["kind"]?.ToString(), "lora", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(hit?["kind"]?.ToString(), "card", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
string k = hit?["key"]?.ToString();
|
||||||
|
if (!string.IsNullOrWhiteSpace(k))
|
||||||
|
{
|
||||||
|
keepRich.Add(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<JToken> ordered = allLoras
|
||||||
|
.OrderByDescending(t => keepRich.Contains(t?["name"]?.ToString() ?? "") ? 1000 : 0)
|
||||||
|
.ThenByDescending(t => t?["krea_likely"]?.Value<bool>() == true ? 50 : 0)
|
||||||
|
.ThenBy(t => t?["name"]?.ToString() ?? "", StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
JArray slim = [];
|
||||||
|
int richCount = 0;
|
||||||
|
foreach (JToken t in ordered)
|
||||||
|
{
|
||||||
|
if (slim.Count >= namesCap)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
string n = t?["name"]?.ToString();
|
||||||
|
if (string.IsNullOrWhiteSpace(n))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bool wantRich = keepRich.Contains(n) || (t?["krea_likely"]?.Value<bool>() == true && richCount < richCap);
|
||||||
|
if (wantRich)
|
||||||
|
{
|
||||||
|
JObject rich = EnrichLoraRowForPrompt(t as JObject ?? new JObject { ["name"] = n });
|
||||||
|
slim.Add(rich);
|
||||||
|
if (!keepRich.Contains(n))
|
||||||
|
{
|
||||||
|
richCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
JObject nameOnly = new() { ["name"] = n };
|
||||||
|
if (t?["krea_likely"]?.Value<bool>() == true)
|
||||||
|
{
|
||||||
|
nameOnly["krea_likely"] = true;
|
||||||
|
}
|
||||||
|
slim.Add(nameOnly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx["available_loras"] = slim;
|
||||||
|
if (allLoras.Count > slim.Count)
|
||||||
|
{
|
||||||
|
ctx["available_loras_truncated"] = true;
|
||||||
|
// Prefer client total if already set (full disk inventory count).
|
||||||
|
if (ctx["available_loras_total"] is null)
|
||||||
|
{
|
||||||
|
ctx["available_loras_total"] = allLoras.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Ensure rich LoRA rows have triggers/blurb from Swarm inventory when the client sent name-only.</summary>
|
||||||
|
JObject EnrichLoraRowForPrompt(JObject row)
|
||||||
|
{
|
||||||
|
if (row is null)
|
||||||
|
{
|
||||||
|
return new JObject();
|
||||||
|
}
|
||||||
|
JObject outRow = (JObject)row.DeepClone();
|
||||||
|
string name = outRow["name"]?.ToString();
|
||||||
|
bool needsTriggers = string.IsNullOrWhiteSpace(outRow["trigger_phrase"]?.ToString())
|
||||||
|
&& (outRow["triggers"] is not JArray tr || tr.Count == 0);
|
||||||
|
bool needsBlurb = string.IsNullOrWhiteSpace(outRow["blurb"]?.ToString());
|
||||||
|
if (!needsTriggers && !needsBlurb)
|
||||||
|
{
|
||||||
|
return outRow;
|
||||||
|
}
|
||||||
|
JObject fromDisk = FindInventoryLoraByName(name);
|
||||||
|
if (fromDisk is null)
|
||||||
|
{
|
||||||
|
return outRow;
|
||||||
|
}
|
||||||
|
if (needsTriggers)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(fromDisk["trigger_phrase"]?.ToString()))
|
||||||
|
{
|
||||||
|
outRow["trigger_phrase"] = fromDisk["trigger_phrase"];
|
||||||
|
}
|
||||||
|
if (fromDisk["triggers"] is JArray ft && ft.Count > 0)
|
||||||
|
{
|
||||||
|
outRow["triggers"] = ft.DeepClone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (needsBlurb && !string.IsNullOrWhiteSpace(fromDisk["blurb"]?.ToString()))
|
||||||
|
{
|
||||||
|
outRow["blurb"] = fromDisk["blurb"];
|
||||||
|
}
|
||||||
|
if (outRow["default_weight"] is null && fromDisk["default_weight"] is not null)
|
||||||
|
{
|
||||||
|
outRow["default_weight"] = fromDisk["default_weight"];
|
||||||
|
}
|
||||||
|
if (fromDisk["krea_likely"]?.Value<bool>() == true)
|
||||||
|
{
|
||||||
|
outRow["krea_likely"] = true;
|
||||||
|
}
|
||||||
|
if (fromDisk["has_card"]?.Value<bool>() == true)
|
||||||
|
{
|
||||||
|
outRow["has_card"] = true;
|
||||||
|
}
|
||||||
|
return outRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
JObject FindInventoryLoraByName(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name) || !Program.T2IModelSets.TryGetValue("LoRA", out T2IModelHandler handler))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
T2IModel model = handler.Models.Values.FirstOrDefault(m =>
|
||||||
|
string.Equals(m.Name, name, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(Path.GetFileNameWithoutExtension(m.Name), Path.GetFileNameWithoutExtension(name), StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| (m.Name?.EndsWith("/" + name, StringComparison.OrdinalIgnoreCase) ?? false));
|
||||||
|
return model is null ? null : BuildInventoryModelEntry(model, "lora");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DropNullOrEmpty(JObject ctx)
|
||||||
|
{
|
||||||
|
List<string> remove = [];
|
||||||
|
foreach (JProperty p in ctx.Properties())
|
||||||
|
{
|
||||||
|
if (p.Value is null || p.Value.Type == JTokenType.Null)
|
||||||
|
{
|
||||||
|
remove.Add(p.Name);
|
||||||
|
}
|
||||||
|
else if (p.Value is JObject jo && !jo.Properties().Any())
|
||||||
|
{
|
||||||
|
remove.Add(p.Name);
|
||||||
|
}
|
||||||
|
else if (p.Value is JArray ja && ja.Count == 0 && p.Name is not "memory_hits")
|
||||||
|
{
|
||||||
|
remove.Add(p.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (string k in remove)
|
||||||
|
{
|
||||||
|
ctx.Remove(k);
|
||||||
}
|
}
|
||||||
// Personal only — never let the model write into another personality's store.
|
|
||||||
return AssistentConfig.SafeId(currentPersonaId) ?? AssistentMemory.SharedPersona;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string EnrichPersonaContext(string contextJson, string personaId, string packName)
|
string EnrichPersonaContext(string contextJson, string personaId, string packName)
|
||||||
@@ -501,13 +820,23 @@ public partial class SwarmAssistentExtension
|
|||||||
ctx["persona_source"] = Config.PersonaSource(pid);
|
ctx["persona_source"] = Config.PersonaSource(pid);
|
||||||
JObject schema = Config.LoadControlsSchema(pid);
|
JObject schema = Config.LoadControlsSchema(pid);
|
||||||
JObject values = Config.LoadControlValues(pid);
|
JObject values = Config.LoadControlValues(pid);
|
||||||
if (schema.Properties().Any())
|
bool authorPack = string.Equals(packName, "author_persona", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(packName, "persona", StringComparison.OrdinalIgnoreCase);
|
||||||
|
// Values only outside author pack (schema is fat). Author pack gets full schema.
|
||||||
|
if (values.Properties().Any())
|
||||||
{
|
{
|
||||||
ctx["persona_controls"] = new JObject
|
if (authorPack && schema.Properties().Any())
|
||||||
{
|
{
|
||||||
["schema"] = schema,
|
ctx["persona_controls"] = new JObject
|
||||||
["values"] = values,
|
{
|
||||||
};
|
["schema"] = schema,
|
||||||
|
["values"] = values,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ctx["persona_controls"] = new JObject { ["values"] = values };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
JArray catalog = [];
|
JArray catalog = [];
|
||||||
foreach (var p in Config.ListPersonaCatalog())
|
foreach (var p in Config.ListPersonaCatalog())
|
||||||
@@ -516,21 +845,34 @@ public partial class SwarmAssistentExtension
|
|||||||
{
|
{
|
||||||
["id"] = p.id,
|
["id"] = p.id,
|
||||||
["title"] = p.title,
|
["title"] = p.title,
|
||||||
["source"] = p.source,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ctx["personas"] = catalog;
|
ctx["personas"] = catalog;
|
||||||
if (string.Equals(packName, "author_persona", StringComparison.OrdinalIgnoreCase)
|
if (authorPack)
|
||||||
|| string.Equals(packName, "persona", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
JObject shelves = Config.LoadIdentityParts(pid);
|
JObject shelves = Config.LoadIdentityParts(pid);
|
||||||
shelves.Remove("extra");
|
shelves.Remove("extra");
|
||||||
ctx["persona_shelves"] = shelves;
|
ctx["persona_shelves"] = shelves;
|
||||||
ctx["persona_controls_schema"] = schema;
|
if (schema.Properties().Any())
|
||||||
|
{
|
||||||
|
ctx["persona_controls_schema"] = schema;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
DropNullOrEmpty(ctx);
|
||||||
return ctx.ToString(Newtonsoft.Json.Formatting.None);
|
return ctx.ToString(Newtonsoft.Json.Formatting.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static string MemoryWritePersona(JObject mo, string currentPersonaId)
|
||||||
|
{
|
||||||
|
string scope = (mo?["scope"]?.ToString() ?? "").Trim().ToLowerInvariant();
|
||||||
|
if (scope is "shared" or "common" or "global")
|
||||||
|
{
|
||||||
|
return AssistentMemory.SharedPersona;
|
||||||
|
}
|
||||||
|
// Personal only — never let the model write into another personality's store.
|
||||||
|
return AssistentConfig.SafeId(currentPersonaId) ?? AssistentMemory.SharedPersona;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Apply overlay persona clone/write from patch. Ignores persona_delete. Updates pid ref after switch.</summary>
|
/// <summary>Apply overlay persona clone/write from patch. Ignores persona_delete. Updates pid ref after switch.</summary>
|
||||||
void ApplyPersonaActions(JObject patch, ref string personaId)
|
void ApplyPersonaActions(JObject patch, ref string personaId)
|
||||||
{
|
{
|
||||||
@@ -559,7 +901,8 @@ public partial class SwarmAssistentExtension
|
|||||||
{
|
{
|
||||||
wantClone = true;
|
wantClone = true;
|
||||||
}
|
}
|
||||||
if (patch["persona_shelves"] is JObject)
|
// persona_shelves as object of content = write; as array of names = persona_read hop (ignore here).
|
||||||
|
if (patch["persona_shelves"] is JObject && !ActionsContain(patch, "persona_read"))
|
||||||
{
|
{
|
||||||
wantWrite = true;
|
wantWrite = true;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1431,7 +1431,7 @@ public sealed class AssistentConfig
|
|||||||
["source"] = p.source,
|
["source"] = p.source,
|
||||||
})),
|
})),
|
||||||
["identity"] = identity,
|
["identity"] = identity,
|
||||||
["identity_summary"] = RenderIdentityBlock(id),
|
["identity_summary"] = RenderIdentityBlock(id, includeAllShelves: true),
|
||||||
["enabled_skills"] = new JArray(ResolveEnabledSkills(id, null)),
|
["enabled_skills"] = new JArray(ResolveEnabledSkills(id, null)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -243,7 +243,7 @@ public partial class SwarmAssistentExtension
|
|||||||
string embedModel = raw?["embed_model"]?.ToString() ?? Config.LoadSettings()["embed_model"]?.ToString();
|
string embedModel = raw?["embed_model"]?.ToString() ?? Config.LoadSettings()["embed_model"]?.ToString();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
(string reply, JObject parsed, JArray civitai) = await RunChatWithHops(
|
(string reply, JObject parsed, JArray civitai, int systemChars) = await RunChatWithHops(
|
||||||
session, root, modelName, packName, includeBase, contextJson, userMessages, personaId: persona, skillIds: skills, embedModel: embedModel);
|
session, root, modelName, packName, includeBase, contextJson, userMessages, personaId: persona, skillIds: skills, embedModel: embedModel);
|
||||||
return new JObject
|
return new JObject
|
||||||
{
|
{
|
||||||
@@ -254,6 +254,7 @@ public partial class SwarmAssistentExtension
|
|||||||
["persona"] = persona,
|
["persona"] = persona,
|
||||||
["raw"] = parsed,
|
["raw"] = parsed,
|
||||||
["civitai_results"] = civitai,
|
["civitai_results"] = civitai,
|
||||||
|
["system_chars"] = systemChars,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -305,7 +306,7 @@ public partial class SwarmAssistentExtension
|
|||||||
}, API.WebsocketTimeout);
|
}, API.WebsocketTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(string reply, JObject parsed, JArray civitai) = await RunChatWithHops(
|
(string reply, JObject parsed, JArray civitai, int systemChars) = await RunChatWithHops(
|
||||||
session, root, modelName, packName, includeBase, contextJson, userMessages, OnDelta, OnHopStart, persona, skills, embedModel);
|
session, root, modelName, packName, includeBase, contextJson, userMessages, OnDelta, OnHopStart, persona, skills, embedModel);
|
||||||
await ws.SendJson(new JObject
|
await ws.SendJson(new JObject
|
||||||
{
|
{
|
||||||
@@ -317,6 +318,7 @@ public partial class SwarmAssistentExtension
|
|||||||
["persona"] = persona,
|
["persona"] = persona,
|
||||||
["raw"] = parsed,
|
["raw"] = parsed,
|
||||||
["civitai_results"] = civitai,
|
["civitai_results"] = civitai,
|
||||||
|
["system_chars"] = systemChars,
|
||||||
}, API.WebsocketTimeout);
|
}, API.WebsocketTimeout);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
+2
-1
@@ -166,5 +166,6 @@ public partial class SwarmAssistentExtension
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool WantsCivitaiSearch(JObject patch) => NextToolHop(patch) == "civitai";
|
static bool WantsCivitaiSearch(JObject patch)
|
||||||
|
=> ActionsContain(patch, "search_civitai") || !string.IsNullOrWhiteSpace(ExtractSearchQuery(patch));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public partial class SwarmAssistentExtension
|
|||||||
["shelves"] = Config.LoadIdentityParts(pid),
|
["shelves"] = Config.LoadIdentityParts(pid),
|
||||||
["controls"] = Config.LoadControlsSchema(pid),
|
["controls"] = Config.LoadControlsSchema(pid),
|
||||||
["control_values"] = Config.LoadControlValues(pid),
|
["control_values"] = Config.LoadControlValues(pid),
|
||||||
["identity_summary"] = Config.RenderIdentityBlock(pid),
|
["identity_summary"] = Config.RenderIdentityBlock(pid, includeAllShelves: true),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ Never write a “JSON Patch” section in prose without an actual fenced ```json
|
|||||||
- Use only LoRA/checkpoint **names** from `available_loras` / `enabled_loras` (or Civitai hop results). Prefer listed `triggers` / `trigger_phrase` / `blurb` — **never invent**.
|
- Use only LoRA/checkpoint **names** from `available_loras` / `enabled_loras` (or Civitai hop results). Prefer listed `triggers` / `trigger_phrase` / `blurb` — **never invent**.
|
||||||
- Rich entries (blurbs/triggers) are enabled + top krea-likely. Name-only rows need `list_inventory` + `inventory_query` before you rely on them.
|
- Rich entries (blurbs/triggers) are enabled + top krea-likely. Name-only rows need `list_inventory` + `inventory_query` before you rely on them.
|
||||||
- `memory_hits` may be truncated (`truncated: true`) — use `memory_get` for the full text.
|
- `memory_hits` may be truncated (`truncated: true`) — use `memory_get` for the full text.
|
||||||
- `has_vision_image` true means a real board frame exists, but **images are not in this request** until you emit `look_at`. Do not invent what the image looks like.
|
- `has_vision_image` true means a real board frame exists. `images_in_request` true means JPEG bytes are in **this** request. If you need to see a frame and `images_in_request` is false, emit `look_at` first — do not invent what the image looks like.
|
||||||
- Prefer `krea_likely` / Krea architecture; ignore FLUX/SDXL. Respect current params unless asked or pack is `form_params`.
|
- Prefer `krea_likely` / Krea architecture; ignore FLUX/SDXL. Respect current params unless asked or pack is `form_params`.
|
||||||
- Init/inpaint flags and `image_slots` are in the JSON. Extra pack fields (aspect, inpaint, persona authoring) are documented in the active pack.
|
- Init/inpaint flags and `image_slots` are in the JSON. Extra pack fields (aspect, inpaint, persona authoring) are documented in the active pack.
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
# Mode: critique_image
|
# Mode: critique_image
|
||||||
|
|
||||||
Goal: look at the attached board image(s) and improve the next **Krea 2** generation.
|
Goal: look at board image(s) and improve the next **Krea 2** generation.
|
||||||
|
|
||||||
## Vision gate (mandatory)
|
## Vision gate (mandatory)
|
||||||
|
|
||||||
- If `has_vision_image` is false: emit **only** a one-line note + JSON with `look_at: ["generate"]` (or the ref id). Do **not** write a critique checklist. Do **not** invent defects.
|
- `has_vision_image` — a real frame exists on the board.
|
||||||
- If vision is present: short critique, then a real fenced JSON patch.
|
- `images_in_request` — JPEG bytes are attached **this turn**.
|
||||||
|
|
||||||
|
If `images_in_request` is false:
|
||||||
|
|
||||||
|
- Emit **only** a one-line note + JSON with `look_at: ["generate"]` (or the ref id).
|
||||||
|
- Do **not** write a critique checklist. Do **not** invent defects.
|
||||||
|
|
||||||
|
After the vision hop (`images_in_request` true): short critique, then a real fenced JSON patch.
|
||||||
|
|
||||||
## Critique (keep short)
|
## Critique (keep short)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
# Mode: describe_ref
|
# Mode: describe_ref
|
||||||
|
|
||||||
Goal: turn an attached / requested board image into a **Krea 2–ready** natural-language prompt (reverse prompt).
|
Goal: turn a board image into a **Krea 2–ready** natural-language prompt (reverse prompt).
|
||||||
|
|
||||||
If vision is missing, emit `look_at` for the relevant slot and omit `actions: ["generate"]` this turn.
|
## Vision gate (mandatory)
|
||||||
|
|
||||||
|
- `has_vision_image` — a real frame exists on the board.
|
||||||
|
- `images_in_request` — JPEG bytes are attached **this turn**.
|
||||||
|
|
||||||
|
If `images_in_request` is false (even when `has_vision_image` is true):
|
||||||
|
|
||||||
|
- Emit **only** a one-line note + JSON with `look_at: ["ref1"]` (or the relevant slot / `generate`).
|
||||||
|
- Do **not** invent a description. Do **not** emit `actions: ["generate"]` this turn.
|
||||||
|
|
||||||
|
After the vision hop (`images_in_request` true): write the reverse prompt.
|
||||||
|
|
||||||
## How to describe
|
## How to describe
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat + board (Generate | Refs tabs), LoRA chips, **persona presets** (`Config/personas/`), **About the user** prefs + craft vector memory, model cards with Civitai fetch, img2img/inpaint, slash commands, auto Generate.
|
SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat + board (Generate | Refs tabs), LoRA chips, **persona presets** (`Config/personas/`), **About the user** prefs + craft vector memory, model cards with Civitai fetch, img2img/inpaint, slash commands, auto Generate.
|
||||||
|
|
||||||
**Version 0.10.0** — Settings panel (Поведение / Модели / Личности / О пользователе / Крафт / Ещё), **UserPrefs** (global + per-persona) with prompt weight, persona export/import, craft memory clear APIs. Legacy `taste` migrates into UserPrefs once.
|
**Version 0.10.2** — Vision flag fix (`has_vision_image` vs `images_in_request`), enrich rich LoRAs from disk, post-Generate look_at option, `system_chars` in chat debug. Lean Ollama context (quality-first): slim core, always-on voice/likes + rich top LoRAs, hops for inventory/lore/`memory.md`, JPEG only on `look_at`.
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public partial class SwarmAssistentExtension : Extension
|
|||||||
ExtensionAuthor = "mrleo1nid";
|
ExtensionAuthor = "mrleo1nid";
|
||||||
Description = "Collaborative Krea 2 assistant: Ollama chat, persona presets, vector memory, model cards, Generate loop.";
|
Description = "Collaborative Krea 2 assistant: Ollama chat, persona presets, vector memory, model cards, Generate loop.";
|
||||||
License = "MIT";
|
License = "MIT";
|
||||||
Version = "0.10.0";
|
Version = "0.10.2";
|
||||||
Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory"];
|
Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory"];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ public partial class SwarmAssistentExtension : Extension
|
|||||||
["id"] = p.id,
|
["id"] = p.id,
|
||||||
["title"] = p.title,
|
["title"] = p.title,
|
||||||
["accent"] = p.accent,
|
["accent"] = p.accent,
|
||||||
["prompt"] = Config.RenderIdentityBlock(p.id),
|
["prompt"] = Config.RenderIdentityBlock(p.id, includeAllShelves: true),
|
||||||
["source"] = p.source,
|
["source"] = p.source,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
<div class="sa-settings-panes">
|
<div class="sa-settings-panes">
|
||||||
<div class="sa-spane" data-spane="behavior">
|
<div class="sa-spane" data-spane="behavior">
|
||||||
<p class="sa-settings-hint">Автодействия после ответа модели и скилы текущей личности.</p>
|
<p class="sa-settings-hint">Автодействия после ответа модели и скилы текущей личности.</p>
|
||||||
<label class="sa-check"><input type="checkbox" id="sa_auto_vision" /> Авто-прикреплять Generate к чату</label>
|
<label class="sa-check" title="После Generate шлёт look_at с JPEG кадра (не каждый чат). Если включена авто-критика — она уже подставляет кадр."><input type="checkbox" id="sa_auto_vision" /> После Generate — look_at кадра</label>
|
||||||
<label class="sa-check"><input type="checkbox" id="sa_auto_apply" checked /> Авто-применять патч</label>
|
<label class="sa-check"><input type="checkbox" id="sa_auto_apply" checked /> Авто-применять патч</label>
|
||||||
<label class="sa-check"><input type="checkbox" id="sa_auto_generate" checked /> Авто-Generate после патча</label>
|
<label class="sa-check"><input type="checkbox" id="sa_auto_generate" checked /> Авто-Generate после патча</label>
|
||||||
<label class="sa-check"><input type="checkbox" id="sa_auto_critique" /> Авто-критика после Generate</label>
|
<label class="sa-check"><input type="checkbox" id="sa_auto_critique" /> Авто-критика после Generate</label>
|
||||||
|
|||||||
Reference in New Issue
Block a user