mirror of
				https://github.com/SillyTavern/SillyTavern.git
				synced 2025-06-05 21:59:27 +02:00 
			
		
		
		
	add /import to import closures from other QRs
This commit is contained in:
		| @@ -1,8 +1,11 @@ | |||||||
| import { SlashCommand } from '../../../slash-commands/SlashCommand.js'; | import { SlashCommand } from '../../../slash-commands/SlashCommand.js'; | ||||||
| import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js'; | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js'; | ||||||
|  | import { SlashCommandClosure } from '../../../slash-commands/SlashCommandClosure.js'; | ||||||
| import { enumIcons } from '../../../slash-commands/SlashCommandCommonEnumsProvider.js'; | import { enumIcons } from '../../../slash-commands/SlashCommandCommonEnumsProvider.js'; | ||||||
|  | import { SlashCommandDebugController } from '../../../slash-commands/SlashCommandDebugController.js'; | ||||||
| import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js'; | import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js'; | ||||||
| import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; | import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; | ||||||
|  | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; | ||||||
| import { isTrueBoolean } from '../../../utils.js'; | import { isTrueBoolean } from '../../../utils.js'; | ||||||
| // eslint-disable-next-line no-unused-vars | // eslint-disable-next-line no-unused-vars | ||||||
| import { QuickReplyApi } from '../api/QuickReplyApi.js'; | import { QuickReplyApi } from '../api/QuickReplyApi.js'; | ||||||
| @@ -554,6 +557,106 @@ export class SlashCommandHandler { | |||||||
|                 </div> |                 </div> | ||||||
|             `, |             `, | ||||||
|         })); |         })); | ||||||
|  |  | ||||||
|  |         SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'import', | ||||||
|  |             /** | ||||||
|  |              * | ||||||
|  |              * @param {{_scope:SlashCommandScope, _debugController:SlashCommandDebugController, from:string}} args | ||||||
|  |              * @param {string} value | ||||||
|  |              */ | ||||||
|  |             callback: (args, value) => { | ||||||
|  |                 if (!args.from) throw new Error('/import requires from= to be set.'); | ||||||
|  |                 if (!value) throw new Error('/import requires the unnamed argument to be set.'); | ||||||
|  |                 let qr = [...this.api.listGlobalSets(), ...this.api.listChatSets()] | ||||||
|  |                     .map(it=>this.api.getSetByName(it)?.qrList ?? []) | ||||||
|  |                     .flat() | ||||||
|  |                     .find(it=>it.label == args.from) | ||||||
|  |                 ; | ||||||
|  |                 if (!qr) { | ||||||
|  |                     let [setName, ...qrNameParts] = args.from.split('.'); | ||||||
|  |                     let qrName = qrNameParts.join('.'); | ||||||
|  |                     let qrs = QuickReplySet.get(setName); | ||||||
|  |                     if (qrs) { | ||||||
|  |                         qr = qrs.qrList.find(it=>it.label == qrName); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 if (qr) { | ||||||
|  |                     const parser = new SlashCommandParser(); | ||||||
|  |                     const closure = parser.parse(qr.message, true, [], null, args._debugController); | ||||||
|  |                     if (args._debugController) { | ||||||
|  |                         closure.source = args.from; | ||||||
|  |                     } | ||||||
|  |                     const testCandidates = (executor)=>{ | ||||||
|  |                         return ( | ||||||
|  |                             executor.namedArgumentList.find(arg=>arg.name == 'key') | ||||||
|  |                             && executor.unnamedArgumentList.length > 0 | ||||||
|  |                             && executor.unnamedArgumentList[0].value instanceof SlashCommandClosure | ||||||
|  |                         ) || ( | ||||||
|  |                             !executor.namedArgumentList.find(arg=>arg.name == 'key') | ||||||
|  |                             && executor.unnamedArgumentList.length > 1 | ||||||
|  |                             && executor.unnamedArgumentList[1].value instanceof SlashCommandClosure | ||||||
|  |                         ); | ||||||
|  |                     }; | ||||||
|  |                     const candidates = closure.executorList | ||||||
|  |                         .filter(executor=>['let', 'var'].includes(executor.command.name)) | ||||||
|  |                         .filter(testCandidates) | ||||||
|  |                         .map(executor=>({ | ||||||
|  |                             key: executor.namedArgumentList.find(arg=>arg.name == 'key')?.value ?? executor.unnamedArgumentList[0].value, | ||||||
|  |                             value: executor.unnamedArgumentList[executor.namedArgumentList.find(arg=>arg.name == 'key') ? 0 : 1].value, | ||||||
|  |                         })) | ||||||
|  |                     ; | ||||||
|  |                     for (let i = 0; i < value.length; i++) { | ||||||
|  |                         const srcName = value[i]; | ||||||
|  |                         let dstName = srcName; | ||||||
|  |                         if (i + 2 < value.length && value[i + 1] == 'as') { | ||||||
|  |                             dstName = value[i + 2]; | ||||||
|  |                             i += 2; | ||||||
|  |                         } | ||||||
|  |                         const pick = candidates.find(it=>it.key == srcName); | ||||||
|  |                         if (!pick) throw new Error(`No scoped closure named "${srcName}" found in "${args.from}"`); | ||||||
|  |                         if (args._scope.existsVariableInScope(dstName)) { | ||||||
|  |                             args._scope.setVariable(dstName, pick.value); | ||||||
|  |                         } else { | ||||||
|  |                             args._scope.letVariable(dstName, pick.value); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     throw new Error(`No Quick Reply found for "${name}".`); | ||||||
|  |                 } | ||||||
|  |                 return ''; | ||||||
|  |             }, | ||||||
|  |             namedArgumentList: [ | ||||||
|  |                 SlashCommandNamedArgument.fromProps({ name: 'from', | ||||||
|  |                     description: 'Quick Reply to import from (QRSet.QRLabel)', | ||||||
|  |                     typeList: ARGUMENT_TYPE.STRING, | ||||||
|  |                     isRequired: true, | ||||||
|  |                 }), | ||||||
|  |             ], | ||||||
|  |             unnamedArgumentList: [ | ||||||
|  |                 SlashCommandArgument.fromProps({ description: 'what to import (x or x as y)', | ||||||
|  |                     acceptsMultiple: true, | ||||||
|  |                     typeList: ARGUMENT_TYPE.STRING, | ||||||
|  |                     isRequired: true, | ||||||
|  |                 }), | ||||||
|  |             ], | ||||||
|  |             splitUnnamedArgument: true, | ||||||
|  |             helpString: ` | ||||||
|  |                 <div> | ||||||
|  |                     Import one or more closures from another Quick Reply. | ||||||
|  |                 </div> | ||||||
|  |                 <div> | ||||||
|  |                     Only imports closures that are directly assigned a scoped variable via <code>/let</code> or <code>/var</code>. | ||||||
|  |                 </div> | ||||||
|  |                 <div> | ||||||
|  |                     <strong>Examples:</strong> | ||||||
|  |                     <ul> | ||||||
|  |                         <li><pre><code>/import from=LibraryQrSet.FooBar foo |\n/:foo</code></pre></li> | ||||||
|  |                         <li><pre><code>/import from=LibraryQrSet.FooBar\n\tfoo\n\tbar\n|\n/:foo |\n/:bar</code></pre></li> | ||||||
|  |                         <li><pre><code>/import from=LibraryQrSet.FooBar\n\tfoo as x\n\tbar as y\n|\n/:x |\n/:y</code></pre></li> | ||||||
|  |                     </ul> | ||||||
|  |                 </div> | ||||||
|  |             `, | ||||||
|  |         })); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -861,6 +861,17 @@ export class SlashCommandParser { | |||||||
|                 } else if (typeof cmd.unnamedArgumentList[0]?.value == 'string') { |                 } else if (typeof cmd.unnamedArgumentList[0]?.value == 'string') { | ||||||
|                     this.scope.variableNames.push(cmd.unnamedArgumentList[0].value); |                     this.scope.variableNames.push(cmd.unnamedArgumentList[0].value); | ||||||
|                 } |                 } | ||||||
|  |             } else if (cmd.name == 'import') { | ||||||
|  |                 const value = /**@type {string[]}*/(cmd.unnamedArgumentList.map(it=>it.value)); | ||||||
|  |                 for (let i = 0; i < value.length; i++) { | ||||||
|  |                     const srcName = value[i]; | ||||||
|  |                     let dstName = srcName; | ||||||
|  |                     if (i + 2 < value.length && value[i + 1] == 'as') { | ||||||
|  |                         dstName = value[i + 2]; | ||||||
|  |                         i += 2; | ||||||
|  |                     } | ||||||
|  |                     this.scope.variableNames.push(dstName); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if (this.testCommandEnd()) { |         if (this.testCommandEnd()) { | ||||||
|   | |||||||
| @@ -1872,6 +1872,7 @@ body[data-stscript-style] .hljs.language-stscript { | |||||||
|             >code { |             >code { | ||||||
|                 display: block; |                 display: block; | ||||||
|                 padding: 1px; |                 padding: 1px; | ||||||
|  |                 tab-size: 4; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user