Add Leonid Horny control, /ostyn cool-down, and /horny-game.
Second persona slider (0-100%) auto-rendered from controls schema; cool-down drops 30 points; horny-game lets the model patch horny from taste match. Fix partial control saves to DeepMerge. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+88
-3
@@ -4352,6 +4352,22 @@
|
||||
action: s.action || '',
|
||||
}));
|
||||
}
|
||||
if (Array.isArray(data.ui?.slash_extra) && data.ui.slash_extra.length) {
|
||||
for (const s of data.ui.slash_extra) {
|
||||
const cmd = s.cmd || '';
|
||||
if (!cmd || SLASH_COMMANDS.some((c) => c.cmd === cmd)) {
|
||||
continue;
|
||||
}
|
||||
SLASH_COMMANDS.push({
|
||||
cmd,
|
||||
hint: s.hint || '',
|
||||
action: s.action || '',
|
||||
});
|
||||
}
|
||||
}
|
||||
if (data.ui?.help_extra) {
|
||||
HELP_TEXT = `${HELP_TEXT || ''}\n\n${data.ui.help_extra}`.trim();
|
||||
}
|
||||
state.enabledSkills = Array.isArray(data.enabled_skills) ? data.enabled_skills.slice() : [];
|
||||
if (Array.isArray(data.personas)) {
|
||||
state.personas = data.personas;
|
||||
@@ -4414,7 +4430,15 @@
|
||||
return;
|
||||
}
|
||||
box.hidden = false;
|
||||
for (const id of keys) {
|
||||
const ordered = keys.slice().sort((a, b) => {
|
||||
const oa = Number(schema[a]?.order ?? 100);
|
||||
const ob = Number(schema[b]?.order ?? 100);
|
||||
if (oa !== ob) {
|
||||
return oa - ob;
|
||||
}
|
||||
return String(a).localeCompare(String(b));
|
||||
});
|
||||
for (const id of ordered) {
|
||||
const def = schema[id];
|
||||
if (!def || typeof def !== 'object') {
|
||||
continue;
|
||||
@@ -4430,6 +4454,8 @@
|
||||
if (Number.isNaN(cur)) {
|
||||
cur = defVal;
|
||||
}
|
||||
const asPercent = String(def.display || '').toLowerCase() === 'percent';
|
||||
const fmt = (v) => (asPercent ? `${Math.round(v)}%` : Number(v).toFixed(2));
|
||||
const row = document.createElement('div');
|
||||
row.className = 'sa-control-row';
|
||||
row.title = def.hint || id;
|
||||
@@ -4444,10 +4470,10 @@
|
||||
input.dataset.controlId = id;
|
||||
const valEl = document.createElement('span');
|
||||
valEl.className = 'sa-control-val';
|
||||
valEl.textContent = cur.toFixed(2);
|
||||
valEl.textContent = fmt(cur);
|
||||
const onInput = () => {
|
||||
const v = Number(input.value);
|
||||
valEl.textContent = v.toFixed(2);
|
||||
valEl.textContent = fmt(v);
|
||||
if (controlSaveTimer) {
|
||||
clearTimeout(controlSaveTimer);
|
||||
}
|
||||
@@ -4462,6 +4488,57 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getControlValue(id, fallback) {
|
||||
const v = state.config?.control_values?.[id] ?? state.exact?.controls?.[id];
|
||||
const n = Number(v);
|
||||
return Number.isFinite(n) ? n : fallback;
|
||||
}
|
||||
|
||||
function coolDownHorny() {
|
||||
const persona = $('sa_persona')?.value || '';
|
||||
if (persona !== 'leonid') {
|
||||
setStatus('/остынь только для Leonid');
|
||||
return;
|
||||
}
|
||||
const schema = state.config?.controls || {};
|
||||
if (!schema.horny) {
|
||||
setStatus('У этой личности нет слайдера Хорни');
|
||||
return;
|
||||
}
|
||||
const min = Number(schema.horny.min ?? 0);
|
||||
const max = Number(schema.horny.max ?? 100);
|
||||
const cur = getControlValue('horny', Number(schema.horny.default ?? 35));
|
||||
const next = Math.max(min, Math.min(max, cur - 30));
|
||||
savePersonaControls({ horny: next });
|
||||
renderPersonaControls(schema, {
|
||||
...(state.config?.control_values || state.exact?.controls || {}),
|
||||
horny: next,
|
||||
});
|
||||
appendSystemNote(`Хорни: ${Math.round(cur)}% → ${Math.round(next)}% (−30)`);
|
||||
setStatus(`/остынь → ${Math.round(next)}%`);
|
||||
}
|
||||
|
||||
async function startHornyGame() {
|
||||
const persona = $('sa_persona')?.value || '';
|
||||
if (persona !== 'leonid') {
|
||||
setStatus('/horny-game только для Leonid');
|
||||
return;
|
||||
}
|
||||
const cur = getControlValue('horny', 35);
|
||||
await sendChat({
|
||||
skipSlash: true,
|
||||
skipAutoPack: true,
|
||||
forcedUserText:
|
||||
`Команда /horny-game. Текущий controls.horny = ${Math.round(cur)} (0–100).\n` +
|
||||
`Оцени, насколько вкусы пользователя в этом чате / последнем сообщении совпадают с твоими (roleplay, outfits, realism, fetishes).\n` +
|
||||
`Поставь новый controls.horny: умножь/сдвинь текущее значение пропорционально «насколько тебе это зашло» ` +
|
||||
`(слабое совпадение → чуть вниз или почти без изменений; сильное → заметный рост, clamp 0–100).\n` +
|
||||
`В прозе скажи кратко: совпало ли, какой множитель/сдвиг и новый %. ` +
|
||||
`Обязателен JSON patch с "controls": { "horny": <number> }. Без generate, если не просили картинку.`,
|
||||
});
|
||||
setStatus('/horny-game…');
|
||||
}
|
||||
|
||||
function savePersonaControls(partial) {
|
||||
const persona = $('sa_persona')?.value || 'neutral';
|
||||
if (typeof genericRequest !== 'function') {
|
||||
@@ -6768,6 +6845,14 @@
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (cmd === 'остынь' || cmd === 'ostyn' || cmd === 'cool' || cmd === 'cooldown') {
|
||||
coolDownHorny();
|
||||
return true;
|
||||
}
|
||||
if (cmd === 'horny-game' || cmd === 'hornygame' || cmd === 'horny_game') {
|
||||
await startHornyGame();
|
||||
return true;
|
||||
}
|
||||
if (cmd === 'civitai') {
|
||||
if (!arg) {
|
||||
setStatus('/civitai <query>');
|
||||
|
||||
Reference in New Issue
Block a user