mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Idle Response / Continuous Generation (#1132)
* Initial idle stuff * Much closer, can now quietly send as user to get a char response. * Tweaks * Better, reset the count of getting a message back, don't send while prompt is waiting. * Allow selecting who is being prompted * Comments and cleaup * Remove char name for the moment (needs something here probably) * Add random time period and "Always add character's name to prompt" respect * Tooltips * Load/unload listeners * Reduce log spam * Add inline prompt inclusion * Add full loud prompting * Comments * Fix instruct newline (I think) * Don't reset count on continue * add quietToLoud for script.js * add quietToLoud for slashcommands.js * Keep instruct directives * Removed some logging, don't do the Novel formatting if Q2L * Logspam begone. * Removed a bit more logging * Add alignment style * Reformat files. Add comments * Reorder extensions * Fix repeat logic to prompt once then only repeat the number specified * Make repeat count more clear --------- Co-authored-by: RossAscends <124905043+RossAscends@users.noreply.github.com> Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
This commit is contained in:
@ -102,6 +102,7 @@ input.extension_missing[type="checkbox"] {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** LEFT COLUMN **/
|
||||||
#extensions_settings>.expression_settings {
|
#extensions_settings>.expression_settings {
|
||||||
order: 1;
|
order: 1;
|
||||||
}
|
}
|
||||||
@ -138,6 +139,7 @@ input.extension_missing[type="checkbox"] {
|
|||||||
order: 9;
|
order: 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** RIGHT COLUMN **/
|
||||||
#extensions_settings2>.translation_settings {
|
#extensions_settings2>.translation_settings {
|
||||||
order: 1;
|
order: 1;
|
||||||
}
|
}
|
||||||
@ -150,26 +152,59 @@ input.extension_missing[type="checkbox"] {
|
|||||||
order: 3;
|
order: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
#extensions_settings2>#memory_settings {
|
#extensions_settings2>.idle-settings {
|
||||||
order: 4;
|
order: 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
#extensions_settings2>.hypebot_settings {
|
#extensions_settings2>#memory_settings {
|
||||||
order: 5;
|
order: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
#extensions_settings2>.regex_settings {
|
#extensions_settings2>.hypebot_settings {
|
||||||
order: 6;
|
order: 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
#extensions_settings2>.vectors_settings {
|
#extensions_settings2>.regex_settings {
|
||||||
order: 7;
|
order: 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
#extensions_settings2>.chromadb_settings {
|
#extensions_settings2>.vectors_settings {
|
||||||
order: 8;
|
order: 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
#extensions_settings2>.randomizer_settings {
|
#extensions_settings2>.chromadb_settings {
|
||||||
order: 9;
|
order: 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#extensions_settings2>.randomizer_settings {
|
||||||
|
order: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** WAND MENU **/
|
||||||
|
#extensionsMenu>#ttsExtensionMenuItem {
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#extensionsMenu>#sd_gen {
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#extensionsMenu>#send_picture {
|
||||||
|
order: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#extensionsMenu>#token_counter {
|
||||||
|
order: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#extensionsMenu>#objective-task-manual-check-menu-item {
|
||||||
|
order: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#extensionsMenu>#roll_dice {
|
||||||
|
order: 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
#extensionsMenu>#translate_chat {
|
||||||
|
order: 7;
|
||||||
|
}
|
||||||
|
@ -1862,17 +1862,31 @@ function getStoppingStrings(isImpersonate) {
|
|||||||
return result.filter(onlyUnique);
|
return result.filter(onlyUnique);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
// Background prompt generation
|
* Background generation based on the provided prompt.
|
||||||
export async function generateQuietPrompt(quiet_prompt) {
|
* @param {string} quiet_prompt Instruction prompt for the AI
|
||||||
|
* @param {boolean} quietToLoud Whether a message should be sent in a foreground (loud) or background (quiet) mode
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function generateQuietPrompt(quiet_prompt, quietToLoud) {
|
||||||
return await new Promise(
|
return await new Promise(
|
||||||
async function promptPromise(resolve, reject) {
|
async function promptPromise(resolve, reject) {
|
||||||
|
if (quietToLoud === true) {
|
||||||
try {
|
try {
|
||||||
await Generate('quiet', { resolve, reject, quiet_prompt, force_name2: true, });
|
await Generate('quiet', { resolve, reject, quiet_prompt, quietToLoud: true, force_name2: true, });
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
reject();
|
reject();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
await Generate('quiet', { resolve, reject, quiet_prompt, quietToLoud: false, force_name2: true, });
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2289,7 +2303,7 @@ class StreamingProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function Generate(type, { automatic_trigger, force_name2, resolve, reject, quiet_prompt, force_chid, signal } = {}, dryRun = false) {
|
async function Generate(type, { automatic_trigger, force_name2, resolve, reject, quiet_prompt, quietToLoud, force_chid, signal } = {}, dryRun = false) {
|
||||||
//console.log('Generate entered');
|
//console.log('Generate entered');
|
||||||
setGenerationProgress(0);
|
setGenerationProgress(0);
|
||||||
generation_started = new Date();
|
generation_started = new Date();
|
||||||
@ -2371,9 +2385,11 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#########QUIET PROMPT STUFF##############
|
||||||
|
//this function just gives special care to novel quiet instruction prompts
|
||||||
if (quiet_prompt) {
|
if (quiet_prompt) {
|
||||||
quiet_prompt = substituteParams(quiet_prompt);
|
quiet_prompt = substituteParams(quiet_prompt);
|
||||||
quiet_prompt = main_api == 'novel' ? adjustNovelInstructionPrompt(quiet_prompt) : quiet_prompt;
|
quiet_prompt = main_api == 'novel' && !quietToLoud ? adjustNovelInstructionPrompt(quiet_prompt) : quiet_prompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (true === dryRun ||
|
if (true === dryRun ||
|
||||||
@ -2712,9 +2728,14 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
|||||||
}
|
}
|
||||||
|
|
||||||
function modifyLastPromptLine(lastMesString) {
|
function modifyLastPromptLine(lastMesString) {
|
||||||
|
//#########QUIET PROMPT STUFF PT2##############
|
||||||
|
|
||||||
// Add quiet generation prompt at depth 0
|
// Add quiet generation prompt at depth 0
|
||||||
if (quiet_prompt && quiet_prompt.length) {
|
if (quiet_prompt && quiet_prompt.length) {
|
||||||
|
|
||||||
|
// here name1 is forced for all quiet prompts..why?
|
||||||
const name = name1;
|
const name = name1;
|
||||||
|
//checks if we are in instruct, if so, formats the chat as such, otherwise just adds the quiet prompt
|
||||||
const quietAppend = isInstruct ? formatInstructModeChat(name, quiet_prompt, false, true, '', name1, name2, false) : `\n${quiet_prompt}`;
|
const quietAppend = isInstruct ? formatInstructModeChat(name, quiet_prompt, false, true, '', name1, name2, false) : `\n${quiet_prompt}`;
|
||||||
|
|
||||||
//This begins to fix quietPrompts (particularly /sysgen) for instruct
|
//This begins to fix quietPrompts (particularly /sysgen) for instruct
|
||||||
@ -2723,13 +2744,23 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
|||||||
//TODO: respect output_sequence vs last_output_sequence settings
|
//TODO: respect output_sequence vs last_output_sequence settings
|
||||||
//TODO: decide how to prompt this to clarify who is talking 'Narrator', 'System', etc.
|
//TODO: decide how to prompt this to clarify who is talking 'Narrator', 'System', etc.
|
||||||
if (isInstruct) {
|
if (isInstruct) {
|
||||||
lastMesString += '\n' + quietAppend + power_user.instruct.output_sequence + '\n';
|
lastMesString += '\n' + quietAppend; // + power_user.instruct.output_sequence + '\n';
|
||||||
} else {
|
} else {
|
||||||
lastMesString += quietAppend;
|
lastMesString += quietAppend;
|
||||||
}
|
}
|
||||||
// Bail out early
|
|
||||||
|
|
||||||
|
// Ross: bailing out early prevents quiet prompts from respecting other instruct prompt toggles
|
||||||
|
// for sysgen, SD, and summary this is desireable as it prevents the AI from responding as char..
|
||||||
|
// but for idle prompting, we want the flexibility of the other prompt toggles, and to respect them as per settings in the extension
|
||||||
|
// need a detection for what the quiet prompt is being asked for...
|
||||||
|
|
||||||
|
// Bail out early?
|
||||||
|
if (quietToLoud !== true) {
|
||||||
return lastMesString;
|
return lastMesString;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Get instruct mode line
|
// Get instruct mode line
|
||||||
if (isInstruct && !isContinue) {
|
if (isInstruct && !isContinue) {
|
||||||
@ -2752,7 +2783,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
|||||||
if (!lastMesString.endsWith('\n')) {
|
if (!lastMesString.endsWith('\n')) {
|
||||||
lastMesString += '\n';
|
lastMesString += '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
lastMesString += `${name2}:`;
|
lastMesString += `${name2}:`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
#img_form {
|
#img_form {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#send_picture {
|
|
||||||
order: 4;
|
|
||||||
}
|
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
order: 4;
|
|
||||||
/* justify-content: center; */
|
/* justify-content: center; */
|
||||||
|
|
||||||
}
|
}
|
||||||
|
54
public/scripts/extensions/idle/dropdown.html
Normal file
54
public/scripts/extensions/idle/dropdown.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<div class="idle-settings">
|
||||||
|
<div class="inline-drawer">
|
||||||
|
<div class="inline-drawer-toggle inline-drawer-header" title="Indicates the settings for the idle feature.">
|
||||||
|
<b>Idle</b>
|
||||||
|
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inline-drawer-content">
|
||||||
|
<div class="idle_block flex-container">
|
||||||
|
<input id="idle_enabled" type="checkbox" title="Toggle to enable or disable the idle feature." />
|
||||||
|
<label for="idle_enabled">Enabled</label>
|
||||||
|
</div>
|
||||||
|
<div class="idle_block flex-container">
|
||||||
|
<input id="idle_repeats" class="text_pole widthUnset" type="number" min="0" max="100000" step="1" title="The number of times the idle action will be prompted." />
|
||||||
|
<label for="idle_repeats">Idle Prompt Count</label>
|
||||||
|
</div>
|
||||||
|
<div class="idle_block flex-container" style="display: none;">
|
||||||
|
<input id="idle_timer_min" class="text_pole widthUnset" type="number" min="0" max="600000" step="1" title="The minimum amount of time in seconds before the idle action is triggered." />
|
||||||
|
<label for="idle_timer_min">Idle Timer Minimum</label>
|
||||||
|
</div>
|
||||||
|
<div class="idle_block flex-container">
|
||||||
|
<input id="idle_timer" class="text_pole widthUnset" type="number" min="0" max="600000" step="1" title="The amount of time in seconds before the idle action is triggered." />
|
||||||
|
<label for="idle_timer">Idle Timer</label>
|
||||||
|
</div>
|
||||||
|
<div class="idle_block flex-container">
|
||||||
|
<label for="idle_prompts">Idle Prompts</label>
|
||||||
|
<textarea id="idle_prompts" class="text_pole textarea_compact" rows="6" title="The prompts to be sent to initial the idle reply (newline seperated)."></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="idle_block flex-container">
|
||||||
|
<input id="idle_use_continuation" type="checkbox" title="Indicates whether the idle action will just use the 'Continue' function instead of a prompt." />
|
||||||
|
<label for="idle_use_continuation">Use Continuation</label>
|
||||||
|
</div>
|
||||||
|
<div class="idle_block flex-container">
|
||||||
|
<input id="idle_random_time" type="checkbox" title="Indicates if the idle time should be randomized between a min/max value." />
|
||||||
|
<label for="idle_random_time">Randomize Time</label>
|
||||||
|
</div>
|
||||||
|
<div class="idle_block flex-container">
|
||||||
|
<input id="idle_include_prompt" type="checkbox" title="Indicates if the idle prompting should be included in context. (Sends as user)" />
|
||||||
|
<label for="idle_include_prompt">Include Idle Prompt</label>
|
||||||
|
</div>
|
||||||
|
<div class="idle_block flex-container">
|
||||||
|
<label for="idle_sendAs">Send As</label>
|
||||||
|
<select id="idle_sendAs" class="text_pole" title="Determines how the idle message prompting is sent; as a user, character, system, or raw message.">
|
||||||
|
<option value="user">User</option>
|
||||||
|
<option value="char">Character</option>
|
||||||
|
<option value="sys">System</option>
|
||||||
|
<option value="raw">Raw</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="sysHR" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
320
public/scripts/extensions/idle/index.js
Normal file
320
public/scripts/extensions/idle/index.js
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
import {
|
||||||
|
saveSettingsDebounced,
|
||||||
|
substituteParams
|
||||||
|
} from "../../../script.js";
|
||||||
|
import { debounce } from "../../utils.js";
|
||||||
|
import { promptQuietForLoudResponse, sendMessageAs, sendNarratorMessage } from "../../slash-commands.js";
|
||||||
|
import { extension_settings, getContext, renderExtensionTemplate } from "../../extensions.js";
|
||||||
|
const extensionName = "idle";
|
||||||
|
|
||||||
|
let idleTimer = null;
|
||||||
|
let repeatCount = 0;
|
||||||
|
|
||||||
|
let defaultSettings = {
|
||||||
|
enabled: false,
|
||||||
|
timer: 120,
|
||||||
|
prompts: [
|
||||||
|
"*stands silently, looking deep in thought*",
|
||||||
|
"*pauses, eyes wandering over the surroundings*",
|
||||||
|
"*hesitates, appearing lost for a moment*",
|
||||||
|
"*takes a deep breath, collecting their thoughts*",
|
||||||
|
"*gazes into the distance, seemingly distracted*",
|
||||||
|
"*remains still, absorbing the ambiance*",
|
||||||
|
"*lingers in silence, a contemplative look on their face*",
|
||||||
|
"*stops, fingers brushing against an old memory*",
|
||||||
|
"*seems to drift into a momentary daydream*",
|
||||||
|
"*waits quietly, allowing the weight of the moment to settle*",
|
||||||
|
],
|
||||||
|
useContinuation: true,
|
||||||
|
repeats: 2, // 0 = infinite
|
||||||
|
sendAs: "user",
|
||||||
|
randomTime: false,
|
||||||
|
timeMin: 60,
|
||||||
|
includePrompt: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//TODO: Can we make this a generic function?
|
||||||
|
/**
|
||||||
|
* Load the extension settings and set defaults if they don't exist.
|
||||||
|
*/
|
||||||
|
async function loadSettings() {
|
||||||
|
if (!extension_settings.idle) {
|
||||||
|
console.log("Creating extension_settings.idle");
|
||||||
|
extension_settings.idle = {};
|
||||||
|
}
|
||||||
|
for (const [key, value] of Object.entries(defaultSettings)) {
|
||||||
|
if (!extension_settings.idle.hasOwnProperty(key)) {
|
||||||
|
console.log(`Setting default for: ${key}`);
|
||||||
|
extension_settings.idle[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
populateUIWithSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Can we make this a generic function too?
|
||||||
|
/**
|
||||||
|
* Populate the UI components with values from the extension settings.
|
||||||
|
*/
|
||||||
|
function populateUIWithSettings() {
|
||||||
|
$("#idle_timer").val(extension_settings.idle.timer).trigger("input");
|
||||||
|
$("#idle_prompts").val(extension_settings.idle.prompts.join("\n")).trigger("input");
|
||||||
|
$("#idle_use_continuation").prop("checked", extension_settings.idle.useContinuation).trigger("input");
|
||||||
|
$("#idle_enabled").prop("checked", extension_settings.idle.enabled).trigger("input");
|
||||||
|
$("#idle_repeats").val(extension_settings.idle.repeats).trigger("input");
|
||||||
|
$("#idle_sendAs").val(extension_settings.idle.sendAs).trigger("input");
|
||||||
|
$("#idle_random_time").prop("checked", extension_settings.idle.randomTime).trigger("input");
|
||||||
|
$("#idle_timer_min").val(extension_settings.idle.timerMin).trigger("input");
|
||||||
|
$("#idle_include_prompt").prop("checked", extension_settings.idle.includePrompt).trigger("input");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the idle timer based on the extension settings and context.
|
||||||
|
*/
|
||||||
|
function resetIdleTimer() {
|
||||||
|
console.debug("Resetting idle timer");
|
||||||
|
if (idleTimer) clearTimeout(idleTimer);
|
||||||
|
let context = getContext();
|
||||||
|
if (!context.characterId && !context.groupID) return;
|
||||||
|
if (!extension_settings.idle.enabled) return;
|
||||||
|
if (extension_settings.idle.randomTime) {
|
||||||
|
// ensure these are ints
|
||||||
|
let min = extension_settings.idle.timerMin;
|
||||||
|
let max = extension_settings.idle.timer;
|
||||||
|
min = parseInt(min);
|
||||||
|
max = parseInt(max);
|
||||||
|
let randomTime = (Math.random() * (max - min + 1)) + min;
|
||||||
|
idleTimer = setTimeout(sendIdlePrompt, 1000 * randomTime);
|
||||||
|
} else {
|
||||||
|
idleTimer = setTimeout(sendIdlePrompt, 1000 * extension_settings.idle.timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a random idle prompt to the AI based on the extension settings.
|
||||||
|
* Checks conditions like if the extension is enabled and repeat conditions.
|
||||||
|
*/
|
||||||
|
async function sendIdlePrompt() {
|
||||||
|
if (!extension_settings.idle.enabled) return;
|
||||||
|
|
||||||
|
// Check repeat conditions and waiting for a response
|
||||||
|
if (repeatCount >= extension_settings.idle.repeats || $('#mes_stop').is(':visible')) {
|
||||||
|
//console.debug("Not sending idle prompt due to repeat conditions or waiting for a response.");
|
||||||
|
resetIdleTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const randomPrompt = extension_settings.idle.prompts[
|
||||||
|
Math.floor(Math.random() * extension_settings.idle.prompts.length)
|
||||||
|
];
|
||||||
|
|
||||||
|
sendPrompt(randomPrompt);
|
||||||
|
repeatCount++;
|
||||||
|
resetIdleTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add our prompt to the chat and then send the chat to the backend.
|
||||||
|
* @param {string} sendAs - The type of message to send. "user", "char", or "sys".
|
||||||
|
* @param {string} prompt - The prompt text to send to the AI.
|
||||||
|
*/
|
||||||
|
function sendLoud(sendAs, prompt) {
|
||||||
|
if (sendAs === "user") {
|
||||||
|
prompt = substituteParams(prompt);
|
||||||
|
|
||||||
|
$("#send_textarea").val(prompt);
|
||||||
|
|
||||||
|
// Set the focus back to the textarea
|
||||||
|
$("#send_textarea").focus();
|
||||||
|
|
||||||
|
$("#send_but").trigger('click');
|
||||||
|
} else if (sendAs === "char") {
|
||||||
|
sendMessageAs("", `${getContext().name2}\n${prompt}`);
|
||||||
|
promptQuietForLoudResponse(sendAs, "");
|
||||||
|
} else if (sendAs === "sys") {
|
||||||
|
sendNarratorMessage("", prompt);
|
||||||
|
promptQuietForLoudResponse(sendAs, "");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error(`Unknown sendAs value: ${sendAs}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the provided prompt to the AI. Determines method based on continuation setting.
|
||||||
|
* @param {string} prompt - The prompt text to send to the AI.
|
||||||
|
*/
|
||||||
|
function sendPrompt(prompt) {
|
||||||
|
clearTimeout(idleTimer);
|
||||||
|
$("#send_textarea").off("input");
|
||||||
|
|
||||||
|
if (extension_settings.idle.useContinuation) {
|
||||||
|
$('#option_continue').trigger('click');
|
||||||
|
console.debug("Sending idle prompt with continuation");
|
||||||
|
} else {
|
||||||
|
console.debug("Sending idle prompt");
|
||||||
|
console.log(extension_settings.idle);
|
||||||
|
if (extension_settings.idle.includePrompt) {
|
||||||
|
sendLoud(extension_settings.idle.sendAs, prompt);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
promptQuietForLoudResponse(extension_settings.idle.sendAs, prompt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the settings HTML and append to the designated area.
|
||||||
|
*/
|
||||||
|
async function loadSettingsHTML() {
|
||||||
|
const settingsHtml = renderExtensionTemplate(extensionName, "dropdown");
|
||||||
|
$("#extensions_settings2").append(settingsHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a specific setting based on user input.
|
||||||
|
* @param {string} elementId - The HTML element ID tied to the setting.
|
||||||
|
* @param {string} property - The property name in the settings object.
|
||||||
|
* @param {boolean} [isCheckbox=false] - Whether the setting is a checkbox.
|
||||||
|
*/
|
||||||
|
function updateSetting(elementId, property, isCheckbox = false) {
|
||||||
|
let value = $(`#${elementId}`).val();
|
||||||
|
if (isCheckbox) {
|
||||||
|
value = $(`#${elementId}`).prop('checked');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property === "prompts") {
|
||||||
|
value = value.split("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
extension_settings.idle[property] = value;
|
||||||
|
saveSettingsDebounced();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach an input listener to a UI component to update the corresponding setting.
|
||||||
|
* @param {string} elementId - The HTML element ID tied to the setting.
|
||||||
|
* @param {string} property - The property name in the settings object.
|
||||||
|
* @param {boolean} [isCheckbox=false] - Whether the setting is a checkbox.
|
||||||
|
*/
|
||||||
|
function attachUpdateListener(elementId, property, isCheckbox = false) {
|
||||||
|
$(`#${elementId}`).on('input', debounce(() => {
|
||||||
|
updateSetting(elementId, property, isCheckbox);
|
||||||
|
}, 250));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the enabling or disabling of the idle extension.
|
||||||
|
* Adds or removes the idle listeners based on the checkbox's state.
|
||||||
|
*/
|
||||||
|
function handleIdleEnabled() {
|
||||||
|
if (!extension_settings.idle.enabled) {
|
||||||
|
clearTimeout(idleTimer);
|
||||||
|
removeIdleListeners();
|
||||||
|
} else {
|
||||||
|
resetIdleTimer();
|
||||||
|
attachIdleListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup input listeners for the various settings and actions related to the idle extension.
|
||||||
|
*/
|
||||||
|
function setupListeners() {
|
||||||
|
const settingsToWatch = [
|
||||||
|
['idle_timer', 'timer'],
|
||||||
|
['idle_prompts', 'prompts'],
|
||||||
|
['idle_use_continuation', 'useContinuation', true],
|
||||||
|
['idle_enabled', 'enabled', true],
|
||||||
|
['idle_repeats', 'repeats'],
|
||||||
|
['idle_sendAs', 'sendAs'],
|
||||||
|
['idle_random_time', 'randomTime', true],
|
||||||
|
['idle_timer_min', 'timerMin'],
|
||||||
|
['idle_include_prompt', 'includePrompt', true]
|
||||||
|
];
|
||||||
|
settingsToWatch.forEach(setting => {
|
||||||
|
attachUpdateListener(...setting);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Idleness listeners, could be made better
|
||||||
|
$('#idle_enabled').on('input', debounce(handleIdleEnabled, 250));
|
||||||
|
|
||||||
|
// Add the idle listeners initially if the idle feature is enabled
|
||||||
|
if (extension_settings.idle.enabled) {
|
||||||
|
attachIdleListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
//show/hide timer min parent div
|
||||||
|
$('#idle_random_time').on('input', function () {
|
||||||
|
if ($(this).prop('checked')) {
|
||||||
|
$('#idle_timer_min').parent().show();
|
||||||
|
} else {
|
||||||
|
$('#idle_timer_min').parent().hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#idle_timer').trigger('input');
|
||||||
|
});
|
||||||
|
|
||||||
|
// if we're including the prompt, hide raw from the sendAs dropdown
|
||||||
|
$('#idle_include_prompt').on('input', function () {
|
||||||
|
if ($(this).prop('checked')) {
|
||||||
|
$('#idle_sendAs option[value="raw"]').hide();
|
||||||
|
} else {
|
||||||
|
$('#idle_sendAs option[value="raw"]').show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//make sure timer min is less than timer
|
||||||
|
$('#idle_timer').on('input', function () {
|
||||||
|
if ($('#idle_random_time').prop('checked')) {
|
||||||
|
if ($(this).val() < $('#idle_timer_min').val()) {
|
||||||
|
$('#idle_timer_min').val($(this).val());
|
||||||
|
$('#idle_timer_min').trigger('input');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const debouncedActivityHandler = debounce((event) => {
|
||||||
|
// Check if the event target (or any of its parents) has the id "option_continue"
|
||||||
|
if ($(event.target).closest('#option_continue').length) {
|
||||||
|
return; // Do not proceed if the click was on (or inside) an element with id "option_continue"
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug("Activity detected, resetting idle timer");
|
||||||
|
resetIdleTimer();
|
||||||
|
repeatCount = 0;
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
function attachIdleListeners() {
|
||||||
|
$(document).on("click keypress", debouncedActivityHandler);
|
||||||
|
document.addEventListener('keydown', debouncedActivityHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove idle-specific listeners.
|
||||||
|
*/
|
||||||
|
function removeIdleListeners() {
|
||||||
|
$(document).off("click keypress", debouncedActivityHandler);
|
||||||
|
document.removeEventListener('keydown', debouncedActivityHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
jQuery(async () => {
|
||||||
|
await loadSettingsHTML();
|
||||||
|
loadSettings();
|
||||||
|
setupListeners();
|
||||||
|
if (extension_settings.idle.enabled) {
|
||||||
|
resetIdleTimer();
|
||||||
|
}
|
||||||
|
// once the doc is ready, check if random time is checked and hide/show timer min
|
||||||
|
if ($('#idle_random_time').prop('checked')) {
|
||||||
|
$('#idle_timer_min').parent().show();
|
||||||
|
}
|
||||||
|
});
|
12
public/scripts/extensions/idle/manifest.json
Normal file
12
public/scripts/extensions/idle/manifest.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"display_name": "Idle",
|
||||||
|
"loading_order": 6,
|
||||||
|
"requires": [],
|
||||||
|
"optional": [
|
||||||
|
],
|
||||||
|
"js": "index.js",
|
||||||
|
"css": "style.css",
|
||||||
|
"author": "City-Unit",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"homePage": "https://github.com/SillyTavern/SillyTavern"
|
||||||
|
}
|
3
public/scripts/extensions/idle/style.css
Normal file
3
public/scripts/extensions/idle/style.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.idle_block {
|
||||||
|
align-items: center;
|
||||||
|
}
|
@ -50,7 +50,3 @@
|
|||||||
margin: unset;
|
margin: unset;
|
||||||
margin-bottom: 5px !important;
|
margin-bottom: 5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#objective-task-manual-check-menu-item {
|
|
||||||
order: 3;
|
|
||||||
}
|
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
order: 6;
|
|
||||||
/* justify-content: center; */
|
/* justify-content: center; */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
#token_counter {
|
|
||||||
order: 2;
|
|
||||||
}
|
|
||||||
|
@ -4,7 +4,3 @@
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
#translate_chat {
|
|
||||||
order: 7;
|
|
||||||
}
|
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ttsExtensionMenuItem {
|
#ttsExtensionMenuItem {
|
||||||
order: 1;
|
|
||||||
transition: 0.3s;
|
transition: 0.3s;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,14 @@ import {
|
|||||||
generateQuietPrompt,
|
generateQuietPrompt,
|
||||||
reloadCurrentChat,
|
reloadCurrentChat,
|
||||||
sendMessageAsUser,
|
sendMessageAsUser,
|
||||||
|
name1,
|
||||||
} from "../script.js";
|
} from "../script.js";
|
||||||
import { getMessageTimeStamp } from "./RossAscends-mods.js";
|
import { getMessageTimeStamp } from "./RossAscends-mods.js";
|
||||||
import { resetSelectedGroup } from "./group-chats.js";
|
import { resetSelectedGroup } from "./group-chats.js";
|
||||||
import { getRegexedString, regex_placement } from "./extensions/regex/engine.js";
|
import { getRegexedString, regex_placement } from "./extensions/regex/engine.js";
|
||||||
import { chat_styles, power_user } from "./power-user.js";
|
import { chat_styles, power_user } from "./power-user.js";
|
||||||
import { autoSelectPersona } from "./personas.js";
|
import { autoSelectPersona } from "./personas.js";
|
||||||
|
import { getContext } from "./extensions.js";
|
||||||
export {
|
export {
|
||||||
executeSlashCommands,
|
executeSlashCommands,
|
||||||
registerSlashCommand,
|
registerSlashCommand,
|
||||||
@ -225,7 +227,7 @@ function continueChatCallback() {
|
|||||||
$('#option_continue').trigger('click', { fromSlashCommand: true });
|
$('#option_continue').trigger('click', { fromSlashCommand: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateSystemMessage(_, prompt) {
|
export async function generateSystemMessage(_, prompt) {
|
||||||
$('#send_textarea').val('');
|
$('#send_textarea').val('');
|
||||||
|
|
||||||
if (!prompt) {
|
if (!prompt) {
|
||||||
@ -289,7 +291,7 @@ async function setNarratorName(_, text) {
|
|||||||
await saveChatConditional();
|
await saveChatConditional();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendMessageAs(_, text) {
|
export async function sendMessageAs(_, text) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -343,7 +345,7 @@ async function sendMessageAs(_, text) {
|
|||||||
await saveChatConditional();
|
await saveChatConditional();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendNarratorMessage(_, text) {
|
export async function sendNarratorMessage(_, text) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -374,6 +376,45 @@ async function sendNarratorMessage(_, text) {
|
|||||||
await saveChatConditional();
|
await saveChatConditional();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function promptQuietForLoudResponse(who, text) {
|
||||||
|
|
||||||
|
let character_id = getContext().characterId;
|
||||||
|
if (who === 'sys') {
|
||||||
|
text = "System: " + text;
|
||||||
|
} else if (who === 'user') {
|
||||||
|
text = name1 + ": " + text;
|
||||||
|
} else if (who === 'char') {
|
||||||
|
text = characters[character_id].name + ": " + text;
|
||||||
|
} else if (who === 'raw') {
|
||||||
|
text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
//text = `${text}${power_user.instruct.enabled ? '' : '\n'}${(power_user.always_force_name2 && who != 'raw') ? characters[character_id].name + ":" : ""}`
|
||||||
|
|
||||||
|
let reply = await generateQuietPrompt(text, true);
|
||||||
|
text = await getRegexedString(reply, regex_placement.SLASH_COMMAND);
|
||||||
|
|
||||||
|
const message = {
|
||||||
|
name: characters[character_id].name,
|
||||||
|
is_user: false,
|
||||||
|
is_name: true,
|
||||||
|
is_system: false,
|
||||||
|
send_date: getMessageTimeStamp(),
|
||||||
|
mes: substituteParams(text.trim()),
|
||||||
|
extra: {
|
||||||
|
type: system_message_types.COMMENT,
|
||||||
|
gen_id: Date.now(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
chat.push(message);
|
||||||
|
await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1));
|
||||||
|
addOneMessage(message);
|
||||||
|
await eventSource.emit(event_types.USER_MESSAGE_RENDERED, (chat.length - 1));
|
||||||
|
await saveChatConditional();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
async function sendCommentMessage(_, text) {
|
async function sendCommentMessage(_, text) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return;
|
return;
|
||||||
|
Reference in New Issue
Block a user