From 5c8a76c44b25f9385f0688eae8524b1d8e62f0e7 Mon Sep 17 00:00:00 2001 From: Ninzore <33434617+Ninzore@users.noreply.github.com> Date: Tue, 25 Jul 2023 10:33:50 +0100 Subject: [PATCH] feat: add default prompt support (#204) * feat: add default prompt support Add a new option `-d` for adding default prompt to the user prompt. Allow user to generate images without input (in such case they must toggle the `default` option) Add a new short cut `default` for quick accessing default prompt without any user input. * feat: use respond prompt when the backend is sd-webui Use respond prompt to replace the original prompt as feedback, because the prompt may be affected by some plugins (e.g. dynamic-prompts) * Feat: add `defaultPromptSw` config and remove the `default` shortcut * Feat: separate the auth lv requirements for default and normal usage * Refactor: remove `default` shortcut * Chore: change some descriptions --- src/config.ts | 22 +++++++++++++++++++++- src/index.ts | 33 ++++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/config.ts b/src/config.ts index da47711..495635b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -143,6 +143,8 @@ export interface PromptConfig { basePrompt?: Computed negativePrompt?: Computed forbidden?: Computed + defaultPromptSw?: boolean + defaultPrompt?: Computed placement?: Computed<'before' | 'after'> latinOnly?: Computed translator?: boolean @@ -153,6 +155,8 @@ export const PromptConfig: Schema = Schema.object({ basePrompt: Schema.computed(Schema.string().role('textarea'), options).description('默认附加的标签。').default('masterpiece, best quality'), negativePrompt: Schema.computed(Schema.string().role('textarea'), options).description('默认附加的反向标签。').default(ucPreset), forbidden: Schema.computed(Schema.string().role('textarea'), options).description('违禁词列表。请求中的违禁词将会被自动删除。').default(''), + defaultPromptSw: Schema.boolean().description('是否启用默认标签。').default(false), + defaultPrompt: Schema.string().role('textarea', options).description('默认标签,可以在用户无输入prompt时调用。可选在sd-webui中安装dynamic prompt插件,配合使用以达到随机标签效果。').default(''), placement: Schema.computed(Schema.union([ Schema.const('before').description('置于最前'), Schema.const('after').description('置于最后'), @@ -202,6 +206,8 @@ export interface Config extends PromptConfig, ParamConfig { token?: string email?: string password?: string + authLv?: Computed + authLvDefault?: Computed output?: Computed<'minimal' | 'default' | 'verbose'> features?: FeatureConfig endpoint?: string @@ -273,6 +279,11 @@ export const Config = Schema.intersect([ }), ]), + Schema.object({ + authLv: Schema.computed(Schema.natural(), options).description('使用画图全部功能所需要的权限等级。').default(0), + authLvDefault: Schema.computed(Schema.natural(), options).description('使用默认参数生成所需要的权限等级。').default(0), + }).description('权限设置'), + Schema.object({ features: Schema.object({}), }).description('功能设置'), @@ -372,7 +383,15 @@ export function parseForbidden(input: string) { const backslash = /@@__BACKSLASH__@@/g -export function parseInput(session: Session, input: string, config: Config, override: boolean): string[] { +export function parseInput(session: Session, input: string, config: Config, override: boolean, addDefault: boolean): string[] { + if (!input) { + return [ + null, + [session.resolve(config.basePrompt), session.resolve(config.defaultPrompt)].join(','), + session.resolve(config.negativePrompt) + ] + } + input = input .replace(/\\\\/g, backslash.source) .replace(/,/g, ',') @@ -446,6 +465,7 @@ export function parseInput(session: Session, input: string, config: Config, over if (!override) { appendToList(positive, session.resolve(config.basePrompt)) appendToList(negative, session.resolve(config.negativePrompt)) + if (addDefault) appendToList(positive, session.resolve(config.defaultPrompt)) } return [null, positive.join(', '), negative.join(', ')] diff --git a/src/index.ts b/src/index.ts index f48a311..752728b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -106,7 +106,22 @@ export function apply(ctx: Context, config: Config) { .option('iterations', '-i ', { fallback: 1, hidden: () => config.maxIterations <= 1 }) .option('batch', '-b ', { fallback: 1, hidden: () => config.maxIterations <= 1 }) .action(async ({ session, options }, input) => { - if (!input?.trim()) return session.execute('help novelai') + if (config.defaultPromptSw) { + if (session.user.authority < session.resolve(config.authLvDefault)) { + return session.text('internal.low-authority') + } + if (session.user.authority < session.resolve(config.authLv)) { + input = '' + options = options.resolution ? { resolution: options.resolution } : {} + } + } + else if ( + !config.defaultPromptSw && + session.user.authority < session.resolve(config.authLv) + ) return session.text('internal.low-auth') + + const haveInput = input?.trim() ? true : false + if (!haveInput && !config.defaultPromptSw) return session.execute('help novelai') // Check if the user is allowed to use this command. // This code is originally written in the `resolution` function, @@ -126,7 +141,7 @@ export function apply(ctx: Context, config: Config) { const allowImage = useFilter(config.features.image)(session) let imgUrl: string, image: ImageData - if (!restricted(session)) { + if (!restricted(session) && haveInput) { input = h('', h.transform(h.parse(input), { image(attrs) { if (!allowImage) throw new SessionError('commands.novelai.messages.invalid-content') @@ -144,11 +159,11 @@ export function apply(ctx: Context, config: Config) { return session.text('.expect-prompt') } } else { - input = h('', h.transform(h.parse(input), { + input = haveInput ? h('', h.transform(h.parse(input), { image(attrs) { throw new SessionError('commands.novelai.messages.invalid-content') }, - })).toString(true) + })).toString(true) : input delete options.enhance delete options.steps delete options.noise @@ -160,7 +175,7 @@ export function apply(ctx: Context, config: Config) { return session.text('.expect-image') } - if (config.translator && ctx.translator && !options.noTranslator) { + if (haveInput && config.translator && ctx.translator && !options.noTranslator) { try { input = await ctx.translator.translate({ input, target: 'en' }) } catch (err) { @@ -168,7 +183,9 @@ export function apply(ctx: Context, config: Config) { } } - const [errPath, prompt, uc] = parseInput(session, input, config, options.override) + const [errPath, prompt, uc] = parseInput( + session, input, config, options.override, config.defaultPromptSw + ) if (errPath) return session.text(errPath) let token: string @@ -352,6 +369,7 @@ export function apply(ctx: Context, config: Config) { } } + let finalPrompt = prompt const iterate = async () => { const request = async () => { const res = await ctx.http.axios(trimSlash(config.endpoint) + path, { @@ -365,6 +383,7 @@ export function apply(ctx: Context, config: Config) { }) if (config.type === 'sd-webui') { + finalPrompt = (JSON.parse((res.data as StableDiffusionWebUI.Response).info)).prompt return forceDataPrefix((res.data as StableDiffusionWebUI.Response).images[0]) } if (config.type === 'stable-horde') { @@ -436,7 +455,7 @@ export function apply(ctx: Context, config: Config) { } } result.children.push(h('message', attrs, lines.join('\n'))) - result.children.push(h('message', attrs, `prompt = ${prompt}`)) + result.children.push(h('message', attrs, `prompt = ${finalPrompt}`)) if (output === 'verbose') { result.children.push(h('message', attrs, `undesired = ${uc}`)) }