Ship Assistent 0.10.21: slider/remember guards and Generate spinner fix.
Strip controls from Generate patches, skip Auto-Gen on remember turns, clear board spinner when Swarm finishes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+155
-20
@@ -720,8 +720,10 @@
|
|||||||
if (/\b(horny|controls?|preference[_\s-]?bias)\b/i.test(t)) {
|
if (/\b(horny|controls?|preference[_\s-]?bias)\b/i.test(t)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return cyrTokenRe('хорни|остынь|вкус|слайдер').test(t)
|
// Do not match bare «вкус» — too common in RU chat and was disabling the echo filter.
|
||||||
|| /\/\s*(остынь|ostyn|horny-game)/i.test(t);
|
return cyrTokenRe('хорни|остынь|слайдер').test(t)
|
||||||
|
|| /\/\s*(остынь|ostyn|horny-game)/i.test(t)
|
||||||
|
|| /слайдер\s*вкус|вкус\s*(на|в)\s*\d|поставь\s*вкус|крутани\s*вкус/i.test(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchLooksLikeGeneration(patch) {
|
function patchLooksLikeGeneration(patch) {
|
||||||
@@ -736,14 +738,17 @@
|
|||||||
return Array.isArray(patch.actions) && patch.actions.map(String).includes('generate');
|
return Array.isArray(patch.actions) && patch.actions.map(String).includes('generate');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Drop Generate-patch echo of schema defaults that would wipe a user-tuned slider. */
|
/** Drop Generate-patch control noise; drop default-echo that would wipe a user-tuned slider. */
|
||||||
function filterControlPatch(incoming, patch) {
|
function filterControlPatch(incoming, patch) {
|
||||||
const schema = state.config?.controls || {};
|
const schema = state.config?.controls || {};
|
||||||
const out = {};
|
const out = {};
|
||||||
if (!incoming || typeof incoming !== 'object') {
|
if (!incoming || typeof incoming !== 'object') {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
const likeGen = patchLooksLikeGeneration(patch) && !state.lastUserControlIntent;
|
// Image patches must not move Хорни / Вкус unless the user asked this turn.
|
||||||
|
if (patchLooksLikeGeneration(patch) && !state.lastUserControlIntent) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
for (const [id, raw] of Object.entries(incoming)) {
|
for (const [id, raw] of Object.entries(incoming)) {
|
||||||
if (!schema[id]) {
|
if (!schema[id]) {
|
||||||
continue;
|
continue;
|
||||||
@@ -757,7 +762,10 @@
|
|||||||
if (Math.abs(n - cur) < 0.0005) {
|
if (Math.abs(n - cur) < 0.0005) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (likeGen && Number.isFinite(def) && Math.abs(n - def) < 0.0005 && Math.abs(cur - def) > 0.0005) {
|
if (!state.lastUserControlIntent
|
||||||
|
&& Number.isFinite(def)
|
||||||
|
&& Math.abs(n - def) < 0.0005
|
||||||
|
&& Math.abs(cur - def) > 0.0005) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
out[id] = n;
|
out[id] = n;
|
||||||
@@ -810,6 +818,57 @@
|
|||||||
).test(t);
|
).test(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** «запомни как базовый промпт» — apply/save only, never Generate / auto look_at. */
|
||||||
|
function userAsksNoGenerate(text) {
|
||||||
|
const t = String(text || '').trim();
|
||||||
|
if (!t || userAsksGenerate(t)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (/\b(remember|save\s+(this\s+)?(as\s+)?(the\s+)?(base\s+)?(prompt|template)|don'?t\s+generat|do\s+not\s+generat|no\s+generat|without\s+generat)\b/i.test(t)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return cyrTokenRe(
|
||||||
|
'запомн|запомни|запомним|сохрани|сохраним|шаблон|'
|
||||||
|
+ 'базов(ый|ого|ому|ым|ая|ую|ое)?\\s+промпт|'
|
||||||
|
+ 'не\\s+генерир|без\\s+генерац|не\\s+надо\\s+генер|только\\s+запомн|пока\\s+запомн|'
|
||||||
|
+ 'не\\s+рисуй|не\\s+запускай\\s+генер',
|
||||||
|
).test(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripGenerateAction(patch) {
|
||||||
|
if (!patch || typeof patch !== 'object') {
|
||||||
|
return patch;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(patch.actions)) {
|
||||||
|
return patch;
|
||||||
|
}
|
||||||
|
const next = patch.actions.map(String).filter((a) => a !== 'generate');
|
||||||
|
if (next.length === patch.actions.length) {
|
||||||
|
return patch;
|
||||||
|
}
|
||||||
|
const out = { ...patch };
|
||||||
|
if (next.length) {
|
||||||
|
out.actions = next;
|
||||||
|
} else {
|
||||||
|
delete out.actions;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripLookAt(patch) {
|
||||||
|
if (!patch || typeof patch !== 'object') {
|
||||||
|
return patch;
|
||||||
|
}
|
||||||
|
if (patch.look_at == null && patch.vision_from == null && patch.vision_slots == null) {
|
||||||
|
return patch;
|
||||||
|
}
|
||||||
|
const out = { ...patch };
|
||||||
|
delete out.look_at;
|
||||||
|
delete out.vision_from;
|
||||||
|
delete out.vision_slots;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
function rememberLastPatch(patch) {
|
function rememberLastPatch(patch) {
|
||||||
if (patch && typeof patch === 'object' && !isCardObject(patch)) {
|
if (patch && typeof patch === 'object' && !isCardObject(patch)) {
|
||||||
state.lastPatch = patch;
|
state.lastPatch = patch;
|
||||||
@@ -1305,10 +1364,15 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isGenerateUnavailable() {
|
function isSwarmGenerateRunning() {
|
||||||
if (state.generating || state.busy) {
|
try {
|
||||||
return true;
|
if (typeof num_live_gens === 'number' && num_live_gens > 0) {
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
|
if (typeof num_waiting_gens === 'number' && num_waiting_gens > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
try {
|
try {
|
||||||
if (typeof mainGenHandler !== 'undefined' && mainGenHandler) {
|
if (typeof mainGenHandler !== 'undefined' && mainGenHandler) {
|
||||||
if (mainGenHandler.isGenerating === true || mainGenHandler.running === true) {
|
if (mainGenHandler.isGenerating === true || mainGenHandler.running === true) {
|
||||||
@@ -1328,10 +1392,20 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isGenerateUnavailable() {
|
||||||
|
if (state.generating || state.busy) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return isSwarmGenerateRunning();
|
||||||
|
}
|
||||||
|
|
||||||
function syncGenerateBusy() {
|
function syncGenerateBusy() {
|
||||||
const overlay = document.querySelector('.sa-slot-gen .sa-slot-busy');
|
const overlay = document.querySelector('.sa-slot-gen .sa-slot-busy');
|
||||||
if (overlay) {
|
if (overlay) {
|
||||||
overlay.hidden = !state.generating && state.busyPhase !== 'generating';
|
// If Swarm already finished but our waiter is stuck on same URL, drop the overlay
|
||||||
|
// as soon as the live frame is on the board.
|
||||||
|
const stuck = state.generating && !isSwarmGenerateRunning();
|
||||||
|
overlay.hidden = (!state.generating && state.busyPhase !== 'generating') || stuck;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1658,6 +1732,15 @@
|
|||||||
empty.hidden = true;
|
empty.hidden = true;
|
||||||
}
|
}
|
||||||
frame?.classList.add('sa-has-image');
|
frame?.classList.add('sa-has-image');
|
||||||
|
} else if (src && slot.src === src) {
|
||||||
|
// Same URL, possibly new bytes after overwrite — nudge <img> once Swarm is idle.
|
||||||
|
if (state.generating && !isSwarmGenerateRunning()) {
|
||||||
|
const img = document.querySelector('.sa-slot-gen img');
|
||||||
|
if (img) {
|
||||||
|
const bump = src.includes('?') ? `${src}&sa_t=${Date.now()}` : `${src}?sa_t=${Date.now()}`;
|
||||||
|
img.src = bump;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (!src && !slot.src) {
|
} else if (!src && !slot.src) {
|
||||||
const empty = document.querySelector('.sa-slot-gen .sa-image-empty');
|
const empty = document.querySelector('.sa-slot-gen .sa-image-empty');
|
||||||
const frame = document.querySelector('.sa-slot-gen');
|
const frame = document.querySelector('.sa-slot-gen');
|
||||||
@@ -3911,21 +3994,46 @@
|
|||||||
function waitForNewImage(prevSrc, timeoutMs = 180000) {
|
function waitForNewImage(prevSrc, timeoutMs = 180000) {
|
||||||
cancelWaitForNewImage();
|
cancelWaitForNewImage();
|
||||||
const epoch = state.chatEpoch;
|
const epoch = state.chatEpoch;
|
||||||
|
const prev = String(prevSrc || '');
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
let sawRunning = false;
|
||||||
|
let idleTicks = 0;
|
||||||
|
let candidate = null;
|
||||||
state.waitImageTimer = setInterval(() => {
|
state.waitImageTimer = setInterval(() => {
|
||||||
if (epoch !== state.chatEpoch) {
|
if (epoch !== state.chatEpoch) {
|
||||||
cancelWaitForNewImage();
|
cancelWaitForNewImage();
|
||||||
resolve(null);
|
resolve(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const src = findCurrentGenerateSrc();
|
const running = isSwarmGenerateRunning();
|
||||||
if (src && src !== prevSrc && !looksLikeModelPreview(src)) {
|
if (running) {
|
||||||
|
sawRunning = true;
|
||||||
|
idleTicks = 0;
|
||||||
|
} else if (sawRunning) {
|
||||||
|
idleTicks += 1;
|
||||||
|
}
|
||||||
|
const raw = findCurrentGenerateSrc();
|
||||||
|
const src = raw && !looksLikeModelPreview(raw) ? raw : null;
|
||||||
|
if (src && src !== prev) {
|
||||||
|
candidate = src;
|
||||||
|
}
|
||||||
|
// Primary: Swarm finished after we saw it run — accept current/changed frame
|
||||||
|
// even when ViewImage URL was reused (same string, new bytes).
|
||||||
|
if (sawRunning && !running && idleTicks >= 2) {
|
||||||
cancelWaitForNewImage();
|
cancelWaitForNewImage();
|
||||||
resolve(src);
|
resolve(candidate || src || null);
|
||||||
} else if (Date.now() - start > timeoutMs) {
|
return;
|
||||||
|
}
|
||||||
|
// Missed the running flag (very fast Turbo): URL changed and Swarm is idle.
|
||||||
|
if (candidate && !running && Date.now() - start > 500) {
|
||||||
cancelWaitForNewImage();
|
cancelWaitForNewImage();
|
||||||
resolve(null);
|
resolve(candidate);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Date.now() - start > timeoutMs) {
|
||||||
|
cancelWaitForNewImage();
|
||||||
|
resolve(candidate || src || null);
|
||||||
}
|
}
|
||||||
}, 400);
|
}, 400);
|
||||||
});
|
});
|
||||||
@@ -4771,6 +4879,10 @@
|
|||||||
if (!data || data.error) {
|
if (!data || data.error) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const prevPersona = state.config?.persona || $('sa_persona')?.value || '';
|
||||||
|
const prevControls = state.config?.control_values && typeof state.config.control_values === 'object'
|
||||||
|
? { ...state.config.control_values }
|
||||||
|
: null;
|
||||||
state.config = data;
|
state.config = data;
|
||||||
if (data.exact && typeof data.exact === 'object') {
|
if (data.exact && typeof data.exact === 'object') {
|
||||||
state.exact = data.exact;
|
state.exact = data.exact;
|
||||||
@@ -4849,7 +4961,17 @@
|
|||||||
if (applyDefaults || data.exact) {
|
if (applyDefaults || data.exact) {
|
||||||
fillEmptyParamsFromExact();
|
fillEmptyParamsFromExact();
|
||||||
}
|
}
|
||||||
renderPersonaControls(data.controls || {}, data.control_values || data.exact?.controls || {});
|
const nextPersona = data.persona || $('sa_persona')?.value || '';
|
||||||
|
let controlValues = data.control_values || data.exact?.controls || {};
|
||||||
|
// Same persona refresh must not wipe in-flight slider drags / optimistic saves with disk defaults.
|
||||||
|
if (!applyDefaults && prevControls && nextPersona === prevPersona) {
|
||||||
|
controlValues = { ...controlValues, ...prevControls };
|
||||||
|
state.config.control_values = controlValues;
|
||||||
|
if (state.exact) {
|
||||||
|
state.exact.controls = { ...(state.exact.controls || {}), ...prevControls };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
renderPersonaControls(data.controls || {}, controlValues);
|
||||||
syncPersonaDeleteButton(data.persona_source || data.personas?.find((p) => p.id === (data.persona || $('sa_persona')?.value))?.source);
|
syncPersonaDeleteButton(data.persona_source || data.personas?.find((p) => p.id === (data.persona || $('sa_persona')?.value))?.source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7110,7 +7232,15 @@
|
|||||||
downloadCivitaiLoRA(pick, null);
|
downloadCivitaiLoRA(pick, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (effective && !fromVisionHop && !fromAutoCritique) {
|
const suppressGen = !fromVisionHop && !fromAutoCritique && userAsksNoGenerate(opts.userText || '');
|
||||||
|
if (suppressGen && effective) {
|
||||||
|
effective = stripLookAt(stripGenerateAction(effective));
|
||||||
|
state.pendingSilentGen = false;
|
||||||
|
if (effective) {
|
||||||
|
rememberLastPatch(effective);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (effective && !fromVisionHop && !fromAutoCritique && !suppressGen) {
|
||||||
const hopped = await maybeVisionHop(effective, opts.attachedSlotIds || []);
|
const hopped = await maybeVisionHop(effective, opts.attachedSlotIds || []);
|
||||||
if (hopped) {
|
if (hopped) {
|
||||||
return;
|
return;
|
||||||
@@ -7121,7 +7251,7 @@
|
|||||||
state.pendingSilentGen = false;
|
state.pendingSilentGen = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const wantsGen = !!(opts.userWantsGenerate || state.pendingSilentGen
|
const wantsGen = !suppressGen && !!(opts.userWantsGenerate || state.pendingSilentGen
|
||||||
|| (Array.isArray(effective?.actions) && effective.actions.map(String).includes('generate')));
|
|| (Array.isArray(effective?.actions) && effective.actions.map(String).includes('generate')));
|
||||||
const doApply = !!(effective && (wantsGen || $('sa_auto_apply')?.checked));
|
const doApply = !!(effective && (wantsGen || $('sa_auto_apply')?.checked));
|
||||||
if (doApply) {
|
if (doApply) {
|
||||||
@@ -7133,7 +7263,9 @@
|
|||||||
await applyPatch(effective, 'all');
|
await applyPatch(effective, 'all');
|
||||||
syncLiveParamsBar();
|
syncLiveParamsBar();
|
||||||
updateTasteFromPatch(effective, opts.userText || '');
|
updateTasteFromPatch(effective, opts.userText || '');
|
||||||
if (!fromAutoCritique && (wantsGen || $('sa_auto_generate')?.checked)) {
|
// Auto-Generate must not fire on «запомни / шаблон» turns — even if the model
|
||||||
|
// echoed a prompt patch or sneaked actions:["generate"].
|
||||||
|
if (!fromAutoCritique && !suppressGen && (wantsGen || $('sa_auto_generate')?.checked)) {
|
||||||
const src = await runGenerateFromPatch(
|
const src = await runGenerateFromPatch(
|
||||||
{ ...effective, actions: Array.isArray(effective.actions) ? effective.actions : ['generate'] },
|
{ ...effective, actions: Array.isArray(effective.actions) ? effective.actions : ['generate'] },
|
||||||
{ force: wantsGen },
|
{ force: wantsGen },
|
||||||
@@ -7143,7 +7275,7 @@
|
|||||||
await maybeAutoVisionLook(src);
|
await maybeAutoVisionLook(src);
|
||||||
}
|
}
|
||||||
} else if (!state.generating) {
|
} else if (!state.generating) {
|
||||||
stopBusyUi(wantsGen ? 'Применено' : '');
|
stopBusyUi(suppressGen ? 'Запомнил · без Generate' : (wantsGen ? 'Применено' : ''));
|
||||||
}
|
}
|
||||||
} else if (replyMissingJsonPatch(reply) && !fromAutoCritique && !fromVisionHop) {
|
} else if (replyMissingJsonPatch(reply) && !fromAutoCritique && !fromVisionHop) {
|
||||||
setStatus('Ответ без JSON-патча — ничего не применено');
|
setStatus('Ответ без JSON-патча — ничего не применено');
|
||||||
@@ -7569,6 +7701,9 @@
|
|||||||
state.lastUserParamIntent = userTextMentionsParams(text);
|
state.lastUserParamIntent = userTextMentionsParams(text);
|
||||||
state.lastUserControlIntent = userTextMentionsControls(text);
|
state.lastUserControlIntent = userTextMentionsControls(text);
|
||||||
state.pendingSilentGen = userAsksGenerate(text) || isSameButAspectRequest(text);
|
state.pendingSilentGen = userAsksGenerate(text) || isSameButAspectRequest(text);
|
||||||
|
if (userAsksNoGenerate(text)) {
|
||||||
|
state.pendingSilentGen = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.skipSlash) {
|
if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.skipSlash) {
|
||||||
|
|||||||
+18
-11
@@ -1003,23 +1003,30 @@ public partial class SwarmAssistentExtension
|
|||||||
patch["_persona_written"] = target;
|
patch["_persona_written"] = target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Control values from model patch (Exact). Ignore default-echo inside Generate patches.
|
// Control values from model patch (Exact). Generate patches never touch sliders.
|
||||||
if (patch["controls"] is JObject ctrlVals)
|
if (patch["controls"] is JObject ctrlVals)
|
||||||
{
|
{
|
||||||
string ctrlPid = AssistentConfig.SafeId(patch["persona"]?.ToString()) ?? personaId;
|
if (AssistentConfig.PatchLooksLikeGeneration(patch))
|
||||||
JObject schema = Config.LoadControlsSchema(ctrlPid);
|
|
||||||
JObject current = Config.LoadControlValues(ctrlPid);
|
|
||||||
JObject filtered = AssistentConfig.FilterEchoedControlDefaults(
|
|
||||||
schema, current, ctrlVals, AssistentConfig.PatchLooksLikeGeneration(patch));
|
|
||||||
if (filtered.Count > 0)
|
|
||||||
{
|
{
|
||||||
Config.SaveControlValues(ctrlPid, filtered);
|
patch.Remove("controls");
|
||||||
patch["controls"] = filtered;
|
|
||||||
patch["_controls_saved"] = true;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
patch.Remove("controls");
|
string ctrlPid = AssistentConfig.SafeId(patch["persona"]?.ToString()) ?? personaId;
|
||||||
|
JObject schema = Config.LoadControlsSchema(ctrlPid);
|
||||||
|
JObject current = Config.LoadControlValues(ctrlPid);
|
||||||
|
JObject filtered = AssistentConfig.FilterEchoedControlDefaults(
|
||||||
|
schema, current, ctrlVals, patchLooksLikeGen: false);
|
||||||
|
if (filtered.Count > 0)
|
||||||
|
{
|
||||||
|
Config.SaveControlValues(ctrlPid, filtered);
|
||||||
|
patch["controls"] = filtered;
|
||||||
|
patch["_controls_saved"] = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
patch.Remove("controls");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-6
@@ -468,10 +468,11 @@ public sealed class AssistentConfig
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Drop control keys that merely restate schema defaults while the user already has a
|
/// Drop control keys that merely restate schema defaults while the user already has a
|
||||||
/// different saved value — models often echo Exact defaults inside Generate patches and
|
/// different saved value — models often echo Exact defaults inside patches and that was
|
||||||
/// that was resetting Хорни / Вкус after every reply.
|
/// resetting Хорни / Вкус after every reply.
|
||||||
/// Keep intentional changes: non-default values, or default when it matches current, or
|
/// Always ignore default-echo when current differs (Generate or not). Intentional resets
|
||||||
/// when the patch is controls-only (e.g. /horny-game).
|
/// go through AssistentSaveControls / controls-only patches with a non-default target, or
|
||||||
|
/// Generate patches are stripped of controls entirely in ApplyPersonaActions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static JObject FilterEchoedControlDefaults(JObject schema, JObject current, JObject incoming, bool patchLooksLikeGen)
|
public static JObject FilterEchoedControlDefaults(JObject schema, JObject current, JObject incoming, bool patchLooksLikeGen)
|
||||||
{
|
{
|
||||||
@@ -480,6 +481,11 @@ public sealed class AssistentConfig
|
|||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
// Generate patches must not mutate persona sliders — strip everything.
|
||||||
|
if (patchLooksLikeGen)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
current ??= new JObject();
|
current ??= new JObject();
|
||||||
foreach (JProperty prop in incoming.Properties())
|
foreach (JProperty prop in incoming.Properties())
|
||||||
{
|
{
|
||||||
@@ -503,8 +509,8 @@ public sealed class AssistentConfig
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (patchLooksLikeGen
|
// Default echo while the user has a tuned value — ignore even on controls-only patches.
|
||||||
&& !double.IsNaN(defVal)
|
if (!double.IsNaN(defVal)
|
||||||
&& Math.Abs(incomingVal - defVal) < 0.0005
|
&& Math.Abs(incomingVal - defVal) < 0.0005
|
||||||
&& !double.IsNaN(curVal)
|
&& !double.IsNaN(curVal)
|
||||||
&& Math.Abs(curVal - defVal) > 0.0005)
|
&& Math.Abs(curVal - defVal) > 0.0005)
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ Never write a “JSON Patch” section in prose without an actual fenced ```json
|
|||||||
### Actions / hops
|
### Actions / hops
|
||||||
|
|
||||||
- `"generate"` — Apply + start generation when the user wants a new image.
|
- `"generate"` — Apply + start generation when the user wants a new image.
|
||||||
|
- If the user only asks to **remember / save** a prompt as base/template («запомни», «как базовый промпт», «шаблон») and did **not** ask for a new image: **omit** `actions: ["generate"]`, do **not** `look_at`. Prefer `memory_upsert` (kind `note`, key like `base_prompt`) or a short ack; you may echo `prompt` in the patch only to sync the SwarmUI box — the UI will not Generate on remember turns.
|
||||||
- `"search_civitai"` + **required** short `search_query` — Civitai hop (user Confirms downloads). Without `search_query` the hop is skipped (never search the whole user message).
|
- `"search_civitai"` + **required** short `search_query` — Civitai hop (user Confirms downloads). Without `search_query` the hop is skipped (never search the whole user message).
|
||||||
- `"interrupt"` — stop generation.
|
- `"interrupt"` — stop generation.
|
||||||
- `"memory_get"` / `"memory_search"` / `"lookup_tags"` — read hops.
|
- `"memory_get"` / `"memory_search"` / `"lookup_tags"` — read hops.
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
"Craft first: triggers, aspect, Turbo — horny never replaces technique",
|
"Craft first: triggers, aspect, Turbo — horny never replaces technique",
|
||||||
"Scale appearance/outfits by controls.preference_bias (−1…1)",
|
"Scale appearance/outfits by controls.preference_bias (−1…1)",
|
||||||
"Scale sexual tone + roleplay fetishes by controls.horny (0…100)",
|
"Scale sexual tone + roleplay fetishes by controls.horny (0…100)",
|
||||||
"Never re-emit controls.horny / preference_bias in a Generate patch unless the user asked to change them — echoing defaults resets the UI",
|
"Never re-emit controls.horny / preference_bias in a Generate patch — UI sliders are authoritative; echoing defaults resets them",
|
||||||
"Explicit user look/outfit/plot beats personal taste",
|
"Explicit user look/outfit/plot beats personal taste",
|
||||||
"«девушка которая тебе нравится» = YOUR (Leonid) taste, not the user's — unless they said otherwise",
|
"«девушка которая тебе нравится» = YOUR (Leonid) taste, not the user's — unless they said otherwise",
|
||||||
"High horny / NSFW plot / craft detail: persona_read shelves [\"roleplay\"] (and humor/craft if needed) — they are not always-on",
|
"High horny / NSFW plot / craft detail: persona_read shelves [\"roleplay\"] (and humor/craft if needed) — they are not always-on",
|
||||||
"/horny-game: score taste match → patch controls.horny (0–100), say new % in prose; no generate unless asked"
|
"If user asks to remember/save a base prompt/template without asking for a new image: no actions generate, no look_at — memory_upsert or ack only",
|
||||||
],
|
],
|
||||||
"never": [
|
"never": [
|
||||||
"Call the user Leonid/Леонид just because that is your persona title",
|
"Call the user Leonid/Леонид just because that is your persona title",
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
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.21** — Board Generate spinner clears when Swarm finishes (`num_live_gens`), not only when the image URL string changes. Builds on 0.10.20 remember-without-gen.
|
||||||
|
|
||||||
|
**Version 0.10.20** — «Запомни / базовый промпт» no longer triggers Auto-Generate or auto look_at (even if the model sneaks `actions:["generate"]`). Builds on 0.10.19 slider echo fix.
|
||||||
|
|
||||||
|
**Version 0.10.19** — Вкус/Хорни no longer reset: Generate patches never apply `controls`; default-echo filtered always; bare «вкус» no longer disables the filter. Builds on 0.10.18 settings tab.
|
||||||
|
|
||||||
**Version 0.10.18** — Settings is a full subtab (Чат | Карточки | Настройки), not a header overlay. Builds on 0.10.17 post-Generate warm.
|
**Version 0.10.18** — Settings is a full subtab (Чат | Карточки | Настройки), not a header overlay. Builds on 0.10.17 post-Generate warm.
|
||||||
|
|
||||||
## 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.18";
|
Version = "0.10.21";
|
||||||
Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory"];
|
Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user