| import { chat, chat_metadata, eventSource, event_types, getRequestHeaders, this_chid, characters } from '../../../script.js'; |
| import { extension_settings } from '../../extensions.js'; |
| import { QuickReplyApi } from './api/QuickReplyApi.js'; |
| import { AutoExecuteHandler } from './src/AutoExecuteHandler.js'; |
| import { QuickReply } from './src/QuickReply.js'; |
| import { QuickReplyConfig } from './src/QuickReplyConfig.js'; |
| import { QuickReplySet } from './src/QuickReplySet.js'; |
| import { QuickReplySettings } from './src/QuickReplySettings.js'; |
| import { SlashCommandHandler } from './src/SlashCommandHandler.js'; |
| import { ButtonUi } from './src/ui/ButtonUi.js'; |
| import { SettingsUi } from './src/ui/SettingsUi.js'; |
| import { debounceAsync } from '../../utils.js'; |
| import { selected_group } from '../../group-chats.js'; |
| export { debounceAsync }; |
|
|
|
|
|
|
|
|
| const _VERBOSE = true; |
| export const debug = (...msg) => _VERBOSE ? console.debug('[QR2]', ...msg) : null; |
| export const log = (...msg) => _VERBOSE ? console.log('[QR2]', ...msg) : null; |
| export const warn = (...msg) => _VERBOSE ? console.warn('[QR2]', ...msg) : null; |
|
|
|
|
| const defaultConfig = { |
| setList: [{ |
| set: 'Default', |
| isVisible: true, |
| }], |
| }; |
|
|
| const defaultSettings = { |
| isEnabled: false, |
| isCombined: false, |
| config: defaultConfig, |
| }; |
|
|
|
|
| |
| let isReady = false; |
| |
| let executeQueue = []; |
| |
| let lastCharId; |
| |
| let settings; |
| |
| let manager; |
| |
| let buttons; |
| |
| let autoExec; |
| |
| export let quickReplyApi; |
|
|
|
|
|
|
|
|
| const loadSets = async () => { |
| const response = await fetch('/api/settings/get', { |
| method: 'POST', |
| headers: getRequestHeaders(), |
| body: JSON.stringify({}), |
| }); |
|
|
| if (response.ok) { |
| const setList = (await response.json()).quickReplyPresets ?? []; |
| for (const set of setList) { |
| if (set.version !== 2) { |
| |
| set.version = 2; |
| set.disableSend = set.quickActionEnabled ?? false; |
| set.placeBeforeInput = set.placeBeforeInputEnabled ?? false; |
| set.injectInput = set.AutoInputInject ?? false; |
| set.qrList = set.quickReplySlots.map((slot,idx)=>{ |
| const qr = {}; |
| qr.id = idx + 1; |
| qr.label = slot.label ?? ''; |
| qr.title = slot.title ?? ''; |
| qr.message = slot.mes ?? ''; |
| qr.isHidden = slot.hidden ?? false; |
| qr.executeOnStartup = slot.autoExecute_appStartup ?? false; |
| qr.executeOnUser = slot.autoExecute_userMessage ?? false; |
| qr.executeOnAi = slot.autoExecute_botMessage ?? false; |
| qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false; |
| qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false; |
| qr.executeOnNewChat = slot.autoExecute_newChat ?? false; |
| qr.executeBeforeGeneration = slot.autoExecute_beforeGeneration ?? false; |
| qr.automationId = slot.automationId ?? ''; |
| qr.contextList = (slot.contextMenu ?? []).map(it=>({ |
| set: it.preset, |
| isChained: it.chain, |
| })); |
| return qr; |
| }); |
| } |
| if (set.version == 2) { |
| QuickReplySet.list.push(QuickReplySet.from(JSON.parse(JSON.stringify(set)))); |
| } |
| } |
| |
| setList.forEach((set, idx)=>{ |
| QuickReplySet.list[idx].qrList = set.qrList.map(it=>QuickReply.from(it)); |
| QuickReplySet.list[idx].init(); |
| }); |
| log('sets: ', QuickReplySet.list); |
| } |
| }; |
|
|
| const loadSettings = async () => { |
| if (!extension_settings.quickReplyV2) { |
| if (!extension_settings.quickReply) { |
| extension_settings.quickReplyV2 = defaultSettings; |
| } else { |
| extension_settings.quickReplyV2 = { |
| isEnabled: extension_settings.quickReply.quickReplyEnabled ?? false, |
| isCombined: false, |
| isPopout: false, |
| config: { |
| setList: [{ |
| set: extension_settings.quickReply.selectedPreset ?? extension_settings.quickReply.name ?? 'Default', |
| isVisible: true, |
| }], |
| }, |
| }; |
| } |
| } |
| try { |
| settings = QuickReplySettings.from(extension_settings.quickReplyV2); |
| settings.config.scope = 'global'; |
| settings.config.onUpdate = () => settings.save(); |
| } catch (ex) { |
| settings = QuickReplySettings.from(defaultSettings); |
| } |
| }; |
|
|
| const executeIfReadyElseQueue = async (functionToCall, args) => { |
| if (isReady) { |
| log('calling', { functionToCall, args }); |
| await functionToCall(...args); |
| } else { |
| log('queueing', { functionToCall, args }); |
| executeQueue.push(async()=>await functionToCall(...args)); |
| } |
| }; |
|
|
| const handleCharChange = () => { |
| if (lastCharId === this_chid) return; |
|
|
| |
| settings.charConfig = null; |
| lastCharId = this_chid; |
|
|
| |
| |
| const character = characters[this_chid]; |
| if (!character || selected_group) { |
| return; |
| } |
|
|
| |
| let charConfig = settings.characterConfigs[character.avatar]; |
|
|
| |
| if (!charConfig) { |
| charConfig = QuickReplyConfig.from({ setList: [] }); |
| settings.characterConfigs[character.avatar] = charConfig; |
| } |
|
|
| charConfig.scope = 'character'; |
| |
| charConfig.onUpdate = () => settings.save(); |
| settings.charConfig = charConfig; |
| }; |
|
|
| const init = async () => { |
| await loadSets(); |
| await loadSettings(); |
| log('settings: ', settings); |
|
|
| manager = new SettingsUi(settings); |
| document.querySelector('#qr_container').append(await manager.render()); |
|
|
| buttons = new ButtonUi(settings); |
| buttons.show(); |
| settings.onSave = ()=>buttons.refresh(); |
|
|
| window['executeQuickReplyByName'] = async(name, args = {}, options = {}) => { |
| let qr = [ |
| ...settings.config.setList, |
| ...(settings.chatConfig?.setList ?? []), |
| ...(settings.charConfig?.setList ?? []), |
| ] |
| .map(it => it.set.qrList) |
| .flat() |
| .find(it=>it.label == name) |
| ; |
| if (!qr) { |
| let [setName, ...qrName] = name.split('.'); |
| qrName = qrName.join('.'); |
| let qrs = QuickReplySet.get(setName); |
| if (qrs) { |
| qr = qrs.qrList.find(it=>it.label == qrName); |
| } |
| } |
| if (qr && qr.onExecute) { |
| return await qr.execute(args, false, true, options); |
| } else { |
| throw new Error(`No Quick Reply found for "${name}".`); |
| } |
| }; |
|
|
| quickReplyApi = new QuickReplyApi(settings, manager); |
| const slash = new SlashCommandHandler(quickReplyApi); |
| slash.init(); |
| autoExec = new AutoExecuteHandler(settings); |
|
|
| eventSource.on(event_types.APP_READY, async()=>await finalizeInit()); |
|
|
| globalThis.quickReplyApi = quickReplyApi; |
| }; |
| const finalizeInit = async () => { |
| debug('executing startup'); |
| await autoExec.handleStartup(); |
| debug('/executing startup'); |
|
|
| debug(`executing queue (${executeQueue.length} items)`); |
| while (executeQueue.length > 0) { |
| const func = executeQueue.shift(); |
| await func(); |
| } |
| debug('/executing queue'); |
| isReady = true; |
| debug('READY'); |
| }; |
| await init(); |
|
|
| const purgeCharacterQuickReplySets = ({ character }) => { |
| |
| const avatar = character?.avatar; |
| if (avatar && avatar in settings.characterConfigs) { |
| log(`Purging Quick Reply Sets for character: ${avatar}`); |
| delete settings.characterConfigs[avatar]; |
| settings.save(); |
| } |
| }; |
|
|
| const updateCharacterQuickReplySets = (oldAvatar, newAvatar) => { |
| |
| if (oldAvatar && newAvatar && oldAvatar !== newAvatar) { |
| log(`Updating Quick Reply Sets for character: ${oldAvatar} -> ${newAvatar}`); |
| if (settings.characterConfigs[oldAvatar]) { |
| settings.characterConfigs[newAvatar] = settings.characterConfigs[oldAvatar]; |
| delete settings.characterConfigs[oldAvatar]; |
| settings.save(); |
| } |
| } |
| }; |
|
|
| const onChatChanged = async (chatIdx) => { |
| log('CHAT_CHANGED', chatIdx); |
|
|
| handleCharChange(); |
|
|
| if (chatIdx) { |
| const chatConfig = QuickReplyConfig.from(chat_metadata.quickReply ?? {}); |
| chatConfig.scope = 'chat'; |
| chatConfig.onUpdate = () => settings.save(); |
| settings.chatConfig = chatConfig; |
| } else { |
| settings.chatConfig = null; |
| } |
| manager.rerender(); |
| buttons.refresh(); |
|
|
| await autoExec.handleChatChanged(); |
| }; |
| eventSource.on(event_types.CHAT_CHANGED, (...args)=>executeIfReadyElseQueue(onChatChanged, args)); |
| eventSource.on(event_types.CHARACTER_DELETED, purgeCharacterQuickReplySets); |
| eventSource.on(event_types.CHARACTER_RENAMED, updateCharacterQuickReplySets); |
|
|
| const onUserMessage = async () => { |
| await autoExec.handleUser(); |
| }; |
| eventSource.makeFirst(event_types.USER_MESSAGE_RENDERED, (...args)=>executeIfReadyElseQueue(onUserMessage, args)); |
|
|
| const onAiMessage = async (messageId) => { |
| if (['...'].includes(chat[messageId]?.mes)) { |
| log('QR auto-execution suppressed for swiped message'); |
| return; |
| } |
|
|
| await autoExec.handleAi(); |
| }; |
| eventSource.makeFirst(event_types.CHARACTER_MESSAGE_RENDERED, (...args)=>executeIfReadyElseQueue(onAiMessage, args)); |
|
|
| const onGroupMemberDraft = async () => { |
| await autoExec.handleGroupMemberDraft(); |
| }; |
| 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)); |
|
|
| const onNewChat = async () => { |
| await autoExec.handleNewChat(); |
| }; |
| eventSource.on(event_types.CHAT_CREATED, (...args) => executeIfReadyElseQueue(onNewChat, args)); |
| eventSource.on(event_types.GROUP_CHAT_CREATED, (...args) => executeIfReadyElseQueue(onNewChat, args)); |
|
|
| const onBeforeGeneration = async (_generationType, _options = {}, isDryRun = false) => { |
| if (isDryRun) { |
| log('Before-generation hook skipped due to dryRun.'); |
| return; |
| } |
| if (selected_group && this_chid === undefined) { |
| log('Before-generation hook skipped for event before group wrapper.'); |
| return; |
| } |
| await autoExec.handleBeforeGeneration(); |
| }; |
| eventSource.on(event_types.GENERATION_AFTER_COMMANDS, (...args) => executeIfReadyElseQueue(onBeforeGeneration, args)); |
|
|