mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
#1853 Add WI/Script link by entry automation id
This commit is contained in:
@ -4597,6 +4597,10 @@
|
|||||||
<option value="false" data-i18n="No">No</option>
|
<option value="false" data-i18n="No">No</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="world_entry_form_control flex1" title="Can be used to automatically activate Quick Replies" data-i18n="[title]Can be used to automatically activate Quick Replies">
|
||||||
|
<small class="textAlignCenter" data-i18n="Automation ID">Automation ID</small>
|
||||||
|
<input class="text_pole" name="automationId" type="text" placeholder="( None )" data-i18n="[placeholder]( None )">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div name="contentAndCharFilterBlock" class="world_entry_thin_controls flex2">
|
<div name="contentAndCharFilterBlock" class="world_entry_thin_controls flex2">
|
||||||
<div class="world_entry_form_control flex1">
|
<div class="world_entry_form_control flex1">
|
||||||
|
@ -395,6 +395,7 @@ export const event_types = {
|
|||||||
GROUP_CHAT_DELETED: 'group_chat_deleted',
|
GROUP_CHAT_DELETED: 'group_chat_deleted',
|
||||||
GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts',
|
GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts',
|
||||||
GROUP_MEMBER_DRAFTED: 'group_member_drafted',
|
GROUP_MEMBER_DRAFTED: 'group_member_drafted',
|
||||||
|
WORLD_INFO_ACTIVATED: 'world_info_activated',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const eventSource = new EventEmitter();
|
export const eventSource = new EventEmitter();
|
||||||
@ -3192,7 +3193,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
|
|||||||
setFloatingPrompt();
|
setFloatingPrompt();
|
||||||
// Add WI to prompt (and also inject WI to AN value via hijack)
|
// Add WI to prompt (and also inject WI to AN value via hijack)
|
||||||
|
|
||||||
let { worldInfoString, worldInfoBefore, worldInfoAfter, worldInfoDepth } = await getWorldInfoPrompt(chat2, this_max_context);
|
let { worldInfoString, worldInfoBefore, worldInfoAfter, worldInfoDepth } = await getWorldInfoPrompt(chat2, this_max_context, dryRun);
|
||||||
|
|
||||||
if (skipWIAN !== true) {
|
if (skipWIAN !== true) {
|
||||||
console.log('skipWIAN not active, adding WIAN');
|
console.log('skipWIAN not active, adding WIAN');
|
||||||
|
@ -191,6 +191,7 @@ export class QuickReplyApi {
|
|||||||
* @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
* @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
||||||
* @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
|
* @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
|
||||||
* @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
|
* @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
|
||||||
|
* @param {String} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated
|
||||||
* @returns {QuickReply} the new quick reply
|
* @returns {QuickReply} the new quick reply
|
||||||
*/
|
*/
|
||||||
createQuickReply(setName, label, {
|
createQuickReply(setName, label, {
|
||||||
@ -202,6 +203,7 @@ export class QuickReplyApi {
|
|||||||
executeOnAi,
|
executeOnAi,
|
||||||
executeOnChatChange,
|
executeOnChatChange,
|
||||||
executeOnGroupMemberDraft,
|
executeOnGroupMemberDraft,
|
||||||
|
automationId,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const set = this.getSetByName(setName);
|
const set = this.getSetByName(setName);
|
||||||
if (!set) {
|
if (!set) {
|
||||||
@ -217,6 +219,7 @@ export class QuickReplyApi {
|
|||||||
qr.executeOnAi = executeOnAi ?? false;
|
qr.executeOnAi = executeOnAi ?? false;
|
||||||
qr.executeOnChatChange = executeOnChatChange ?? false;
|
qr.executeOnChatChange = executeOnChatChange ?? false;
|
||||||
qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? false;
|
qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? false;
|
||||||
|
qr.automationId = automationId ?? '';
|
||||||
qr.onUpdate();
|
qr.onUpdate();
|
||||||
return qr;
|
return qr;
|
||||||
}
|
}
|
||||||
@ -236,6 +239,7 @@ export class QuickReplyApi {
|
|||||||
* @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
* @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
||||||
* @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
|
* @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
|
||||||
* @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
|
* @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
|
||||||
|
* @param {String} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated
|
||||||
* @returns {QuickReply} the altered quick reply
|
* @returns {QuickReply} the altered quick reply
|
||||||
*/
|
*/
|
||||||
updateQuickReply(setName, label, {
|
updateQuickReply(setName, label, {
|
||||||
@ -248,6 +252,7 @@ export class QuickReplyApi {
|
|||||||
executeOnAi,
|
executeOnAi,
|
||||||
executeOnChatChange,
|
executeOnChatChange,
|
||||||
executeOnGroupMemberDraft,
|
executeOnGroupMemberDraft,
|
||||||
|
automationId,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const qr = this.getQrByLabel(setName, label);
|
const qr = this.getQrByLabel(setName, label);
|
||||||
if (!qr) {
|
if (!qr) {
|
||||||
@ -262,6 +267,7 @@ export class QuickReplyApi {
|
|||||||
qr.executeOnAi = executeOnAi ?? qr.executeOnAi;
|
qr.executeOnAi = executeOnAi ?? qr.executeOnAi;
|
||||||
qr.executeOnChatChange = executeOnChatChange ?? qr.executeOnChatChange;
|
qr.executeOnChatChange = executeOnChatChange ?? qr.executeOnChatChange;
|
||||||
qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? qr.executeOnGroupMemberDraft;
|
qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? qr.executeOnGroupMemberDraft;
|
||||||
|
qr.automationId = automationId ?? qr.automationId;
|
||||||
qr.onUpdate();
|
qr.onUpdate();
|
||||||
return qr;
|
return qr;
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,10 @@
|
|||||||
<input type="checkbox" id="qr--executeOnGroupMemberDraft">
|
<input type="checkbox" id="qr--executeOnGroupMemberDraft">
|
||||||
<span><i class="fa-solid fa-fw fa-people-group"></i> Execute before group member message</span>
|
<span><i class="fa-solid fa-fw fa-people-group"></i> Execute before group member message</span>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="flex-container alignItemsBaseline" title="Activate this quick reply when a World Info entry with the same Automation ID is triggered.">
|
||||||
|
<small>Automation ID</small>
|
||||||
|
<input type="text" id="qr--automationId" class="text_pole flex1" placeholder="( None )">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,6 +104,7 @@ const loadSets = async () => {
|
|||||||
qr.executeOnAi = slot.autoExecute_botMessage ?? false;
|
qr.executeOnAi = slot.autoExecute_botMessage ?? false;
|
||||||
qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false;
|
qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false;
|
||||||
qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false;
|
qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false;
|
||||||
|
qr.automationId = slot.automationId ?? false;
|
||||||
qr.contextList = (slot.contextMenu ?? []).map(it=>({
|
qr.contextList = (slot.contextMenu ?? []).map(it=>({
|
||||||
set: it.preset,
|
set: it.preset,
|
||||||
isChained: it.chain,
|
isChained: it.chain,
|
||||||
@ -244,3 +245,8 @@ const onGroupMemberDraft = async () => {
|
|||||||
await autoExec.handleGroupMemberDraft();
|
await autoExec.handleGroupMemberDraft();
|
||||||
};
|
};
|
||||||
eventSource.on(event_types.GROUP_MEMBER_DRAFTED, (...args) => executeIfReadyElseQueue(onGroupMemberDraft, args));
|
eventSource.on(event_types.GROUP_MEMBER_DRAFTED, (...args) => executeIfReadyElseQueue(onGroupMemberDraft, args));
|
||||||
|
|
||||||
|
const onWIActivation = async (entries) => {
|
||||||
|
await autoExec.handleWIActivation(entries);
|
||||||
|
};
|
||||||
|
eventSource.on(event_types.WORLD_INFO_ACTIVATED, (...args) => executeIfReadyElseQueue(onWIActivation, args));
|
||||||
|
@ -82,4 +82,20 @@ export class AutoExecuteHandler {
|
|||||||
];
|
];
|
||||||
await this.performAutoExecute(qrList);
|
await this.performAutoExecute(qrList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any[]} entries Set of activated entries
|
||||||
|
*/
|
||||||
|
async handleWIActivation(entries) {
|
||||||
|
if (!this.checkExecute() || !Array.isArray(entries) || entries.length === 0) return;
|
||||||
|
const automationIds = entries.map(entry => entry.automationId).filter(Boolean);
|
||||||
|
if (automationIds.length === 0) return;
|
||||||
|
|
||||||
|
const qrList = [
|
||||||
|
...this.settings.config.setList.map(link=>link.set.qrList.filter(qr=>qr.automationId && automationIds.includes(qr.automationId))).flat(),
|
||||||
|
...(this.settings.chatConfig?.setList?.map(link=>link.set.qrList.filter(qr=>qr.automationId && automationIds.includes(qr.automationId)))?.flat() ?? []),
|
||||||
|
];
|
||||||
|
|
||||||
|
await this.performAutoExecute(qrList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ export class QuickReply {
|
|||||||
/**@type {Boolean}*/ executeOnAi = false;
|
/**@type {Boolean}*/ executeOnAi = false;
|
||||||
/**@type {Boolean}*/ executeOnChatChange = false;
|
/**@type {Boolean}*/ executeOnChatChange = false;
|
||||||
/**@type {Boolean}*/ executeOnGroupMemberDraft = false;
|
/**@type {Boolean}*/ executeOnGroupMemberDraft = false;
|
||||||
|
/**@type {String}*/ automationId = '';
|
||||||
|
|
||||||
/**@type {Function}*/ onExecute;
|
/**@type {Function}*/ onExecute;
|
||||||
/**@type {Function}*/ onDelete;
|
/**@type {Function}*/ onDelete;
|
||||||
@ -359,6 +360,13 @@ export class QuickReply {
|
|||||||
this.executeOnGroupMemberDraft = executeOnGroupMemberDraft.checked;
|
this.executeOnGroupMemberDraft = executeOnGroupMemberDraft.checked;
|
||||||
this.updateContext();
|
this.updateContext();
|
||||||
});
|
});
|
||||||
|
/**@type {HTMLInputElement}*/
|
||||||
|
const automationId = dom.querySelector('#qr--automationId');
|
||||||
|
automationId.value = this.automationId;
|
||||||
|
automationId.addEventListener('input', () => {
|
||||||
|
this.automationId = automationId.value;
|
||||||
|
this.updateContext();
|
||||||
|
});
|
||||||
|
|
||||||
/**@type {HTMLElement}*/
|
/**@type {HTMLElement}*/
|
||||||
const executeErrors = dom.querySelector('#qr--modal-executeErrors');
|
const executeErrors = dom.querySelector('#qr--modal-executeErrors');
|
||||||
@ -492,6 +500,7 @@ export class QuickReply {
|
|||||||
executeOnAi: this.executeOnAi,
|
executeOnAi: this.executeOnAi,
|
||||||
executeOnChatChange: this.executeOnChatChange,
|
executeOnChatChange: this.executeOnChatChange,
|
||||||
executeOnGroupMemberDraft: this.executeOnGroupMemberDraft,
|
executeOnGroupMemberDraft: this.executeOnGroupMemberDraft,
|
||||||
|
automationId: this.automationId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,7 @@ export class SlashCommandHandler {
|
|||||||
executeOnAi: isTrueBoolean(args.bot),
|
executeOnAi: isTrueBoolean(args.bot),
|
||||||
executeOnChatChange: isTrueBoolean(args.load),
|
executeOnChatChange: isTrueBoolean(args.load),
|
||||||
executeOnGroupMemberDraft: isTrueBoolean(args.group),
|
executeOnGroupMemberDraft: isTrueBoolean(args.group),
|
||||||
|
automationId: args.automationId ?? '',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
@ -171,6 +172,7 @@ export class SlashCommandHandler {
|
|||||||
executeOnAi: args.bot === undefined ? undefined : isTrueBoolean(args.bot),
|
executeOnAi: args.bot === undefined ? undefined : isTrueBoolean(args.bot),
|
||||||
executeOnChatChange: args.load === undefined ? undefined : isTrueBoolean(args.load),
|
executeOnChatChange: args.load === undefined ? undefined : isTrueBoolean(args.load),
|
||||||
executeOnGroupMemberDraft: args.group === undefined ? undefined : isTrueBoolean(args.group),
|
executeOnGroupMemberDraft: args.group === undefined ? undefined : isTrueBoolean(args.group),
|
||||||
|
automationId: args.automationId ?? '',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
|
@ -230,14 +230,27 @@ const world_info_position = {
|
|||||||
|
|
||||||
const worldInfoCache = {};
|
const worldInfoCache = {};
|
||||||
|
|
||||||
async function getWorldInfoPrompt(chat2, maxContext) {
|
/**
|
||||||
|
* Gets the world info based on chat messages.
|
||||||
|
* @param {string[]} chat The chat messages to scan.
|
||||||
|
* @param {number} maxContext The maximum context size of the generation.
|
||||||
|
* @param {boolean} isDryRun If true, the function will not emit any events.
|
||||||
|
* @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoDepth: any[]}} WIPromptResult
|
||||||
|
* @returns {Promise<WIPromptResult>} The world info string and depth.
|
||||||
|
*/
|
||||||
|
async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
|
||||||
let worldInfoString = '', worldInfoBefore = '', worldInfoAfter = '';
|
let worldInfoString = '', worldInfoBefore = '', worldInfoAfter = '';
|
||||||
|
|
||||||
const activatedWorldInfo = await checkWorldInfo(chat2, maxContext);
|
const activatedWorldInfo = await checkWorldInfo(chat, maxContext);
|
||||||
worldInfoBefore = activatedWorldInfo.worldInfoBefore;
|
worldInfoBefore = activatedWorldInfo.worldInfoBefore;
|
||||||
worldInfoAfter = activatedWorldInfo.worldInfoAfter;
|
worldInfoAfter = activatedWorldInfo.worldInfoAfter;
|
||||||
worldInfoString = worldInfoBefore + worldInfoAfter;
|
worldInfoString = worldInfoBefore + worldInfoAfter;
|
||||||
|
|
||||||
|
if (!isDryRun && activatedWorldInfo.allActivatedEntries && activatedWorldInfo.allActivatedEntries.size > 0) {
|
||||||
|
const arg = Array.from(activatedWorldInfo.allActivatedEntries);
|
||||||
|
await eventSource.emit(event_types.WORLD_INFO_ACTIVATED, arg);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
worldInfoString,
|
worldInfoString,
|
||||||
worldInfoBefore,
|
worldInfoBefore,
|
||||||
@ -926,6 +939,7 @@ const originalDataKeyMap = {
|
|||||||
'matchWholeWords': 'extensions.match_whole_words',
|
'matchWholeWords': 'extensions.match_whole_words',
|
||||||
'caseSensitive': 'extensions.case_sensitive',
|
'caseSensitive': 'extensions.case_sensitive',
|
||||||
'scanDepth': 'extensions.scan_depth',
|
'scanDepth': 'extensions.scan_depth',
|
||||||
|
'automationId': 'extensions.automation_id',
|
||||||
};
|
};
|
||||||
|
|
||||||
function setOriginalDataValue(data, uid, key, value) {
|
function setOriginalDataValue(data, uid, key, value) {
|
||||||
@ -1559,6 +1573,19 @@ function getWorldEntry(name, data, entry) {
|
|||||||
});
|
});
|
||||||
matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input');
|
matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input');
|
||||||
|
|
||||||
|
// automation id
|
||||||
|
const automationIdInput = template.find('input[name="automationId"]');
|
||||||
|
automationIdInput.data('uid', entry.uid);
|
||||||
|
automationIdInput.on('input', function () {
|
||||||
|
const uid = $(this).data('uid');
|
||||||
|
const value = $(this).val();
|
||||||
|
|
||||||
|
data.entries[uid].automationId = value;
|
||||||
|
setOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);
|
||||||
|
saveWorldInfo(name, data);
|
||||||
|
});
|
||||||
|
automationIdInput.val(entry.automationId ?? '').trigger('input');
|
||||||
|
|
||||||
template.find('.inline-drawer-content').css('display', 'none'); //entries start collapsed
|
template.find('.inline-drawer-content').css('display', 'none'); //entries start collapsed
|
||||||
|
|
||||||
function updatePosOrdDisplay(uid) {
|
function updatePosOrdDisplay(uid) {
|
||||||
@ -1620,6 +1647,7 @@ const newEntryTemplate = {
|
|||||||
scanDepth: null,
|
scanDepth: null,
|
||||||
caseSensitive: null,
|
caseSensitive: null,
|
||||||
matchWholeWords: null,
|
matchWholeWords: null,
|
||||||
|
automationId: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
function createWorldInfoEntry(name, data, fromSlashCommand = false) {
|
function createWorldInfoEntry(name, data, fromSlashCommand = false) {
|
||||||
@ -1896,6 +1924,13 @@ async function getSortedEntries() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a scan on the chat and returns the world info activated.
|
||||||
|
* @param {string[]} chat The chat messages to scan.
|
||||||
|
* @param {number} maxContext The maximum context size of the generation.
|
||||||
|
* @typedef {{ worldInfoBefore: string, worldInfoAfter: string, WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated
|
||||||
|
* @returns {Promise<WIActivated>} The world info activated.
|
||||||
|
*/
|
||||||
async function checkWorldInfo(chat, maxContext) {
|
async function checkWorldInfo(chat, maxContext) {
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
const buffer = new WorldInfoBuffer(chat);
|
const buffer = new WorldInfoBuffer(chat);
|
||||||
@ -1932,7 +1967,7 @@ async function checkWorldInfo(chat, maxContext) {
|
|||||||
const sortedEntries = await getSortedEntries();
|
const sortedEntries = await getSortedEntries();
|
||||||
|
|
||||||
if (sortedEntries.length === 0) {
|
if (sortedEntries.length === 0) {
|
||||||
return { worldInfoBefore: '', worldInfoAfter: '' };
|
return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], allActivatedEntries: new Set() };
|
||||||
}
|
}
|
||||||
|
|
||||||
while (needsToScan) {
|
while (needsToScan) {
|
||||||
@ -2179,7 +2214,7 @@ async function checkWorldInfo(chat, maxContext) {
|
|||||||
context.setExtensionPrompt(NOTE_MODULE_NAME, ANWithWI, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth], extension_settings.note.allowWIScan);
|
context.setExtensionPrompt(NOTE_MODULE_NAME, ANWithWI, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth], extension_settings.note.allowWIScan);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { worldInfoBefore, worldInfoAfter, WIDepthEntries };
|
return { worldInfoBefore, worldInfoAfter, WIDepthEntries, allActivatedEntries };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2373,6 +2408,7 @@ function convertCharacterBook(characterBook) {
|
|||||||
scanDepth: entry.extensions?.scan_depth ?? null,
|
scanDepth: entry.extensions?.scan_depth ?? null,
|
||||||
caseSensitive: entry.extensions?.case_sensitive ?? null,
|
caseSensitive: entry.extensions?.case_sensitive ?? null,
|
||||||
matchWholeWords: entry.extensions?.match_whole_words ?? null,
|
matchWholeWords: entry.extensions?.match_whole_words ?? null,
|
||||||
|
automationId: entry.extensions?.automation_id ?? '',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -395,6 +395,7 @@ function convertWorldInfoToCharacterBook(name, entries) {
|
|||||||
scan_depth: entry.scanDepth ?? null,
|
scan_depth: entry.scanDepth ?? null,
|
||||||
match_whole_words: entry.matchWholeWords ?? null,
|
match_whole_words: entry.matchWholeWords ?? null,
|
||||||
case_sensitive: entry.caseSensitive ?? null,
|
case_sensitive: entry.caseSensitive ?? null,
|
||||||
|
automation_id: entry.automationId ?? '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user