Skip to content

Commit 85cd7b9

Browse files
authored
feat: support nai4 full (#274)
* chore: make linter happy * chore: update deps * feat: support nai4 full
1 parent 4d09b73 commit 85cd7b9

File tree

3 files changed

+42
-32
lines changed

3 files changed

+42
-32
lines changed

package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,24 +63,24 @@
6363
"generate"
6464
],
6565
"peerDependencies": {
66-
"koishi": "^4.17.8"
66+
"koishi": "^4.18.7"
6767
},
6868
"devDependencies": {
69-
"@cordisjs/vitepress": "^3.2.7",
70-
"@koishijs/plugin-help": "^2.4.3",
69+
"@cordisjs/vitepress": "^3.3.2",
70+
"@koishijs/plugin-help": "^2.4.5",
7171
"@koishijs/translator": "^1.1.1",
72-
"@types/adm-zip": "^0.5.5",
72+
"@types/adm-zip": "^0.5.7",
7373
"@types/libsodium-wrappers-sumo": "^0.7.8",
74-
"@types/node": "^20.14.1",
74+
"@types/node": "^22.13.8",
7575
"atsc": "^1.2.2",
76-
"koishi": "^4.17.8",
77-
"sass": "^1.77.4",
78-
"typescript": "^5.4.5",
76+
"koishi": "^4.18.7",
77+
"sass": "^1.85.1",
78+
"typescript": "^5.8.2",
7979
"vitepress": "1.0.0-rc.40"
8080
},
8181
"dependencies": {
82-
"adm-zip": "^0.5.13",
83-
"image-size": "^1.1.1",
84-
"libsodium-wrappers-sumo": "^0.7.13"
82+
"adm-zip": "^0.5.16",
83+
"image-size": "^1.2.0",
84+
"libsodium-wrappers-sumo": "^0.7.15"
8585
}
8686
}

src/config.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const modelMap = {
1111
furry: 'nai-diffusion-furry',
1212
'nai-v3': 'nai-diffusion-3',
1313
'nai-v4-curated-preview': 'nai-diffusion-4-curated-preview',
14+
'nai-v4-full': 'nai-diffusion-4-full',
1415
} as const
1516

1617
export const orientMap = {
@@ -266,6 +267,12 @@ export interface Config extends PromptConfig, ParamConfig {
266267
workflowImage2Image?: string
267268
}
268269

270+
const NAI4ParamConfig = Schema.object({
271+
sampler: sampler.createSchema(sampler.nai4).default('k_euler_a'),
272+
scheduler: Schema.union(scheduler.nai4).description('默认的调度器。').default('karras'),
273+
rescale: Schema.computed(Schema.number(), options).min(0).max(1).description('输入服从度调整规模。').default(0),
274+
})
275+
269276
export const Config = Schema.intersect([
270277
Schema.object({
271278
type: Schema.union([
@@ -404,9 +411,11 @@ export const Config = Schema.intersect([
404411
}),
405412
Schema.object({
406413
model: Schema.const('nai-v4-curated-preview'),
407-
sampler: sampler.createSchema(sampler.nai4).default('k_euler_a'),
408-
scheduler: Schema.union(scheduler.nai4).description('默认的调度器。').default('karras'),
409-
rescale: Schema.computed(Schema.number(), options).min(0).max(1).description('输入服从度调整规模。').default(0),
414+
...NAI4ParamConfig.dict,
415+
}),
416+
Schema.object({
417+
model: Schema.const('nai-v4-full'),
418+
...NAI4ParamConfig.dict,
410419
}),
411420
Schema.object({ sampler: sampler.createSchema(sampler.nai) }),
412421
]),

src/index.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Computed, Context, Dict, h, Logger, omit, Quester, Session, SessionError, trimSlash } from 'koishi'
1+
import { Computed, Context, Dict, h, omit, Quester, Session, SessionError, trimSlash } from 'koishi'
22
import { Config, modelMap, models, orientMap, parseInput, sampler, upscalers, scheduler } from './config'
33
import { ImageData, NovelAI, StableDiffusionWebUI } from './types'
44
import { closestMultiple, download, forceDataPrefix, getImageSize, login, NetworkError, project, resizeInput, Size } from './utils'
@@ -333,7 +333,9 @@ export function apply(ctx: Context, config: Config) {
333333
delete parameters.uc
334334
}
335335
parameters.dynamic_thresholding = options.decrisper ?? config.decrisper
336-
if (model === 'nai-diffusion-3' || model === 'nai-diffusion-4-curated-preview') {
336+
const isNAI3 = model === 'nai-diffusion-3'
337+
const isNAI4 = model === 'nai-diffusion-4-curated-preview' || model === 'nai-diffusion-4-full'
338+
if (isNAI3 || isNAI4) {
337339
parameters.params_version = 3
338340
parameters.legacy = false
339341
parameters.legacy_v3_extend = false
@@ -344,7 +346,7 @@ export function apply(ctx: Context, config: Config) {
344346
if (parameters.scale > 10) {
345347
parameters.scale = parameters.scale / 2
346348
}
347-
if (model === 'nai-diffusion-3') {
349+
if (isNAI3) {
348350
parameters.sm_dyn = options.smeaDyn ?? config.smeaDyn
349351
parameters.sm = (options.smea ?? config.smea) || parameters.sm_dyn
350352
if (['k_euler_ancestral', 'k_dpmpp_2s_ancestral'].includes(parameters.sampler)
@@ -356,19 +358,18 @@ export function apply(ctx: Context, config: Config) {
356358
parameters.sm_dyn = false
357359
delete parameters.noise_schedule
358360
}
359-
}
360-
if (model === 'nai-diffusion-4-curated-preview') {
361-
parameters.add_original_image = true // unknown
361+
} else if (isNAI4) {
362+
parameters.add_original_image = true // unknown
362363
parameters.cfg_rescale = session.resolve(config.rescale)
363364
parameters.characterPrompts = [] satisfies NovelAI.V4CharacterPrompt[]
364-
parameters.controlnet_strength = 1 // unknown
365-
parameters.deliberate_euler_ancestral_bug = false // unknown
366-
parameters.prefer_brownian = true // unknown
367-
parameters.reference_image_multiple = [] // unknown
368-
parameters.reference_information_extracted_multiple = [] // unknown
369-
parameters.reference_strength_multiple = [] // unknown
370-
parameters.skip_cfg_above_sigma = null // unknown
371-
parameters.use_coords = false // unknown
365+
parameters.controlnet_strength = 1 // unknown
366+
parameters.deliberate_euler_ancestral_bug = false // unknown
367+
parameters.prefer_brownian = true // unknown
368+
parameters.reference_image_multiple = [] // unknown
369+
parameters.reference_information_extracted_multiple = [] // unknown
370+
parameters.reference_strength_multiple = [] // unknown
371+
parameters.skip_cfg_above_sigma = null // unknown
372+
parameters.use_coords = false // unknown
372373
parameters.v4_prompt = {
373374
caption: {
374375
base_caption: prompt,
@@ -452,13 +453,13 @@ export function apply(ctx: Context, config: Config) {
452453
if (image) {
453454
const body = new FormData()
454455
const capture = /^data:([\w/.+-]+);base64,(.*)$/.exec(image.dataUrl)
455-
const [, mime,] = capture
456+
const [, mime] = capture
456457

457458
let name = Date.now().toString()
458459
const ext = mime === 'image/jpeg' ? 'jpg' : mime === 'image/png' ? 'png' : ''
459460
if (ext) name += `.${ext}`
460461
const imageFile = new Blob([image.buffer], { type: mime })
461-
body.append("image", imageFile, name)
462+
body.append('image', imageFile, name)
462463
const res = await ctx.http(trimSlash(config.endpoint) + '/upload/image', {
463464
method: 'POST',
464465
headers: {
@@ -583,7 +584,7 @@ export function apply(ctx: Context, config: Config) {
583584
await sleep(config.pollInterval)
584585
}
585586
// get images by filename
586-
const imagesOutput: { data: ArrayBuffer, mime: string }[] = [];
587+
const imagesOutput: { data: ArrayBuffer; mime: string }[] = []
587588
for (const nodeId in outputs) {
588589
const nodeOutput = outputs[nodeId]
589590
if ('images' in nodeOutput) {
@@ -603,7 +604,7 @@ export function apply(ctx: Context, config: Config) {
603604
// data:
604605
// ↓ nai-v3
605606
if (res.headers.get('content-type') === 'application/x-zip-compressed' || res.headers.get('content-disposition')?.includes('.zip')) {
606-
const buffer = Buffer.from(res.data, 'binary') // Ensure 'binary' encoding
607+
const buffer = Buffer.from(res.data, 'binary') // Ensure 'binary' encoding
607608
const zip = new AdmZip(buffer)
608609

609610
// Gets all files in the ZIP file

0 commit comments

Comments
 (0)