Commit
•
4905b6b
1
Parent(s):
660842c
use stable cascade
Browse files- .env +1 -8
- README.md +6 -48
- next.config.js +6 -0
- src/app/main.tsx +68 -50
- src/app/server/background.ts +6 -5
- src/app/server/stableCascade.ts +66 -0
- src/app/server/{generate.ts → stableDiffusion.ts} +3 -3
- src/app/server/upscale.ts +5 -5
- src/lib/config.ts +4 -2
- src/lib/getStableCascadeParams.ts +119 -0
- src/lib/{getGenerationParams.ts → getStableDiffusionParams.ts} +5 -5
- src/types.ts +20 -1
.env
CHANGED
@@ -4,11 +4,4 @@ HF_INFERENCE_API_BASE_MODEL="stabilityai/stable-diffusion-xl-base-1.0"
|
|
4 |
HF_INFERENCE_API_REFINER_MODEL="stabilityai/stable-diffusion-xl-refiner-1.0"
|
5 |
HF_INFERENCE_API_MODEL_TRIGGER=""
|
6 |
HF_INFERENCE_API_FILE_TYPE="image/png"
|
7 |
-
|
8 |
-
STABLE_CASCADE_API_GRADIO_TOKEN="<USE YOUR OWN>"
|
9 |
-
|
10 |
-
IMAGE_UPSCALING_API_GRADIO_URL="https://jbilcke-hf-image-upscaling-api.hf.space"
|
11 |
-
IMAGE_UPSCALING_API_GRADIO_TOKEN="<USE YOUR OWN>"
|
12 |
-
|
13 |
-
BACKGROUND_REMOVAL_API_GRADIO_URL="https://jbilcke-hf-background-removal-api.hf.space"
|
14 |
-
BACKGROUND_REMOVAL_API_GRADIO_TOKEN="<USE YOUR OWN>"
|
|
|
4 |
HF_INFERENCE_API_REFINER_MODEL="stabilityai/stable-diffusion-xl-refiner-1.0"
|
5 |
HF_INFERENCE_API_MODEL_TRIGGER=""
|
6 |
HF_INFERENCE_API_FILE_TYPE="image/png"
|
7 |
+
MICROSERVICE_API_SECRET_TOKEN="<USE YOUR OWN>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
@@ -1,52 +1,10 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
-
sdk: docker
|
7 |
pinned: true
|
8 |
-
|
9 |
-
disable_embedding: true
|
10 |
-
hf_oauth_redirect_path: /api/oauth/callback
|
11 |
---
|
12 |
|
13 |
-
|
14 |
-
|
15 |
-
The AI Clip Factory is a space to create animated videos in an ultra simple and fun way. It is meant to be a child's play.
|
16 |
-
|
17 |
-
## Text-to-video model
|
18 |
-
|
19 |
-
The AI Clip Factory is a space about clip generation and providing a fun UI, and is not meant to promote a specific AI model.
|
20 |
-
|
21 |
-
As a consequence, a model currently defined as default may be replaced at anytime by a newer SOTA model.
|
22 |
-
|
23 |
-
Right now (2023-10-19) the default model is the base Hotshot-XL (use the official website for faster inference at [https://hotshot.co](https://hotshot.co)).
|
24 |
-
|
25 |
-
# Interpolation model
|
26 |
-
|
27 |
-
The default model used for interpolation is [ST-MFNet](https://github.com/zsxkib/ST-MFNet)
|
28 |
-
|
29 |
-
## Setup
|
30 |
-
|
31 |
-
If you run the app locally you need to create a `.env.local` file
|
32 |
-
(If you deploy to Hugging Face, just set the environment variable from the settings)
|
33 |
-
|
34 |
-
### Video rendering engine
|
35 |
-
|
36 |
-
Note: the app is in heavy development, not all backends are supported
|
37 |
-
|
38 |
-
Set `VIDEO_ENGINE` to one of:
|
39 |
-
|
40 |
-
- `VIDEO_ENGINE="VIDEO_HOTSHOT_XL_API_GRADIO"`
|
41 |
-
- `VIDEO_ENGINE="VIDEO_HOTSHOT_XL_API_REPLICATE"`
|
42 |
-
- `VIDEO_ENGINE="VIDEO_HOTSHOT_XL_API_NODE"` <- not working yet
|
43 |
-
- `VIDEO_ENGINE="VIDEO_HOTSHOT_XL_API_OFFICIAL"` <- not working yet
|
44 |
-
|
45 |
-
|
46 |
-
### Authentication
|
47 |
-
|
48 |
-
If you intent to use a special provider (eg. Replicate) you need to setup your token
|
49 |
-
|
50 |
-
- `AUTH_REPLICATE_API_TOKEN="<YOUR SECRET>"`
|
51 |
-
|
52 |
-
|
|
|
1 |
---
|
2 |
+
title: Illustrateur (cloud)
|
3 |
+
emoji: ⚡
|
4 |
+
colorFrom: purple
|
5 |
+
colorTo: green
|
|
|
6 |
pinned: true
|
7 |
+
header: mini
|
|
|
|
|
8 |
---
|
9 |
|
10 |
+
This is the "cloud" version of Illustrateur, which means you do not need a GPU in your laptop!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
next.config.js
CHANGED
@@ -1,6 +1,12 @@
|
|
1 |
/** @type {import('next').NextConfig} */
|
2 |
const nextConfig = {
|
3 |
output: 'standalone',
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
}
|
5 |
|
6 |
module.exports = nextConfig
|
|
|
1 |
/** @type {import('next').NextConfig} */
|
2 |
const nextConfig = {
|
3 |
output: 'standalone',
|
4 |
+
|
5 |
+
experimental: {
|
6 |
+
serverActions: {
|
7 |
+
bodySizeLimit: '10mb'
|
8 |
+
}
|
9 |
+
}
|
10 |
}
|
11 |
|
12 |
module.exports = nextConfig
|
src/app/main.tsx
CHANGED
@@ -20,11 +20,14 @@ import { SliderField } from '@/components/form/slider-field'
|
|
20 |
import { Toaster } from '@/components/ui/sonner'
|
21 |
import { cn } from '@/lib/utils'
|
22 |
import { CharacterExpression, CharacterGender, CharacterPose, MIN_AGE, DEFAULT_AGE, MAX_AGE, DEFAULT_WEIGHT, GenerationMode, HairColor, HairStyle, PhotoshootMode, characterExpressions, characterGenders, characterPoses, hairColors, hairStyles, photoshootModes } from '../lib/config'
|
23 |
-
import {
|
24 |
-
import {
|
|
|
|
|
25 |
import { generateSeed } from '@/lib/generateSeed'
|
26 |
import { BackgroundRemovalParams, GenerationStatus, UpscalingParams } from '@/types'
|
27 |
import { removeBackground } from './server/background'
|
|
|
28 |
|
29 |
/*
|
30 |
Maybe we should use this classification instead:
|
@@ -47,19 +50,21 @@ export function Main() {
|
|
47 |
)
|
48 |
const [characterWeight, setCharacterWeight] = useState(DEFAULT_WEIGHT)
|
49 |
const [characterHairStyle, setCharacterHairStyle] = useState<HairStyle>(
|
50 |
-
"
|
51 |
)
|
52 |
const [characterHairColor, setCharacterHairColor] = useState<HairColor>(
|
53 |
-
"
|
54 |
)
|
55 |
const [characterExpression, setCharacterExpression] = useState<CharacterExpression>("neutral")
|
56 |
const [characterPose, setCharacterPose] = useState<CharacterPose>("side-on pose")
|
57 |
-
const [photoshootMode, setPhotoshootMode] = useState<PhotoshootMode>(photoshootModes[
|
58 |
|
59 |
const [prompt, setPrompt] = useState('llamacookie')
|
60 |
const [negativePrompt, setNegativePrompt] = useState('')
|
61 |
const [inferenceSteps, setInferenceSteps] = useState(20)
|
62 |
-
const [
|
|
|
|
|
63 |
const [seed, setSeed] = useState('')
|
64 |
const [strength, setStrength] = useState(0.8)
|
65 |
const [runVaeOnEachStep, setRunVaeOnEachStep] = useState(false)
|
@@ -73,7 +78,8 @@ export function Main() {
|
|
73 |
|
74 |
const showAdvancedSettings = true
|
75 |
|
76 |
-
const
|
|
|
77 |
generationMode,
|
78 |
negativePrompt,
|
79 |
characterAge,
|
@@ -85,21 +91,6 @@ export function Main() {
|
|
85 |
characterPose
|
86 |
})
|
87 |
|
88 |
-
const upscalingParams: UpscalingParams = {
|
89 |
-
imageAsBase64: "",
|
90 |
-
prompt: generationParams.prompt,
|
91 |
-
negativePrompt: generationParams.negativePrompt,
|
92 |
-
scaleFactor: 2,
|
93 |
-
seed: generateSeed(),
|
94 |
-
|
95 |
-
// // for a single image we can afford a higher rate, such as 25
|
96 |
-
nbSteps: 25,
|
97 |
-
}
|
98 |
-
|
99 |
-
const backgroundRemovalParams: BackgroundRemovalParams = {
|
100 |
-
imageAsBase64: ""
|
101 |
-
}
|
102 |
-
|
103 |
const onDraw = async () => {
|
104 |
console.log("onRender")
|
105 |
|
@@ -107,53 +98,80 @@ export function Main() {
|
|
107 |
let upscaledImageUrl = ""
|
108 |
let croppedImageUrl = ""
|
109 |
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
setCroppedImageUrl(croppedImageUrl)
|
115 |
-
// })
|
116 |
|
117 |
try {
|
118 |
-
baseImageUrl = await
|
|
|
119 |
} catch (err) {
|
120 |
console.error(`failed to generate:`, err)
|
121 |
}
|
122 |
|
123 |
if (!baseImageUrl) {
|
124 |
-
|
125 |
-
setStatus("error")
|
126 |
-
// })
|
127 |
-
|
128 |
return
|
129 |
}
|
130 |
|
131 |
-
|
132 |
-
setBaseImageUrl(baseImageUrl)
|
133 |
-
// })
|
134 |
|
|
|
135 |
try {
|
136 |
croppedImageUrl = await removeBackground({
|
137 |
-
...backgroundRemovalParams,
|
138 |
imageAsBase64: baseImageUrl,
|
139 |
})
|
140 |
} catch (err) {
|
141 |
-
console.error(`failed to crop:`, err)
|
142 |
}
|
143 |
|
144 |
if (!croppedImageUrl) {
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
149 |
return
|
150 |
}
|
151 |
|
152 |
-
|
153 |
-
setCroppedImageUrl(croppedImageUrl)
|
154 |
-
setStatus("finished")
|
155 |
-
// })
|
156 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
157 |
}
|
158 |
|
159 |
const isBusy = status === "generating" || status === "cropping" || status === "upscaling"
|
@@ -343,13 +361,13 @@ export function Main() {
|
|
343 |
label="Guidance Scale"
|
344 |
type='number'
|
345 |
min={1}
|
346 |
-
max={
|
347 |
-
step={0.
|
348 |
// disabled={modelState != 'ready'}
|
349 |
onChange={(e) => setGuidanceScale(parseFloat(e.target.value))}
|
350 |
value={guidanceScale}
|
351 |
className={cn({
|
352 |
-
hidden: !showAdvancedSettings
|
353 |
})}
|
354 |
/>
|
355 |
<SliderField
|
@@ -418,7 +436,7 @@ export function Main() {
|
|
418 |
defaultValue={[inferenceSteps]}
|
419 |
value={[inferenceSteps]}
|
420 |
className={cn({
|
421 |
-
hidden: !showAdvancedSettings
|
422 |
})}
|
423 |
/>
|
424 |
|
@@ -432,7 +450,7 @@ export function Main() {
|
|
432 |
onChange={(e) => setGuidanceScale(parseFloat(e.target.value))}
|
433 |
value={guidanceScale}
|
434 |
className={cn({
|
435 |
-
hidden: !showAdvancedSettings
|
436 |
})}
|
437 |
/>
|
438 |
{/*
|
|
|
20 |
import { Toaster } from '@/components/ui/sonner'
|
21 |
import { cn } from '@/lib/utils'
|
22 |
import { CharacterExpression, CharacterGender, CharacterPose, MIN_AGE, DEFAULT_AGE, MAX_AGE, DEFAULT_WEIGHT, GenerationMode, HairColor, HairStyle, PhotoshootMode, characterExpressions, characterGenders, characterPoses, hairColors, hairStyles, photoshootModes } from '../lib/config'
|
23 |
+
import { getStableDiffusionParams } from '@/lib/getStableDiffusionParams'
|
24 |
+
import { getStableCascadeParams } from '@/lib/getStableCascadeParams'
|
25 |
+
import { stableDiffusion } from './server/stableDiffusion'
|
26 |
+
import { stableCascade } from './server/stableCascade'
|
27 |
import { generateSeed } from '@/lib/generateSeed'
|
28 |
import { BackgroundRemovalParams, GenerationStatus, UpscalingParams } from '@/types'
|
29 |
import { removeBackground } from './server/background'
|
30 |
+
import { upscale } from './server/upscale'
|
31 |
|
32 |
/*
|
33 |
Maybe we should use this classification instead:
|
|
|
50 |
)
|
51 |
const [characterWeight, setCharacterWeight] = useState(DEFAULT_WEIGHT)
|
52 |
const [characterHairStyle, setCharacterHairStyle] = useState<HairStyle>(
|
53 |
+
"Braids" // pick<HairStyle>(hairStyles)
|
54 |
)
|
55 |
const [characterHairColor, setCharacterHairColor] = useState<HairColor>(
|
56 |
+
"chesnut" // pick<HairColor>(hairColors)
|
57 |
)
|
58 |
const [characterExpression, setCharacterExpression] = useState<CharacterExpression>("neutral")
|
59 |
const [characterPose, setCharacterPose] = useState<CharacterPose>("side-on pose")
|
60 |
+
const [photoshootMode, setPhotoshootMode] = useState<PhotoshootMode>(photoshootModes[0])
|
61 |
|
62 |
const [prompt, setPrompt] = useState('llamacookie')
|
63 |
const [negativePrompt, setNegativePrompt] = useState('')
|
64 |
const [inferenceSteps, setInferenceSteps] = useState(20)
|
65 |
+
const [nbPriorInferenceSteps, setNbPriorInferenceSteps] = useState(20)
|
66 |
+
const [nbDecoderInferenceSteps, setNbDecoderInferenceSteps] = useState(10)
|
67 |
+
const [guidanceScale, setGuidanceScale] = useState(4)
|
68 |
const [seed, setSeed] = useState('')
|
69 |
const [strength, setStrength] = useState(0.8)
|
70 |
const [runVaeOnEachStep, setRunVaeOnEachStep] = useState(false)
|
|
|
78 |
|
79 |
const showAdvancedSettings = true
|
80 |
|
81 |
+
const stableCascadeParams = getStableCascadeParams({
|
82 |
+
prompt,
|
83 |
generationMode,
|
84 |
negativePrompt,
|
85 |
characterAge,
|
|
|
91 |
characterPose
|
92 |
})
|
93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
const onDraw = async () => {
|
95 |
console.log("onRender")
|
96 |
|
|
|
98 |
let upscaledImageUrl = ""
|
99 |
let croppedImageUrl = ""
|
100 |
|
101 |
+
setStatus("generating")
|
102 |
+
setBaseImageUrl(baseImageUrl)
|
103 |
+
setUpscaledImageUrl(upscaledImageUrl)
|
104 |
+
setCroppedImageUrl(croppedImageUrl)
|
|
|
|
|
105 |
|
106 |
try {
|
107 |
+
// baseImageUrl = await stableDiffusion(stableDiffusionParams)
|
108 |
+
baseImageUrl = await stableCascade(stableCascadeParams)
|
109 |
} catch (err) {
|
110 |
console.error(`failed to generate:`, err)
|
111 |
}
|
112 |
|
113 |
if (!baseImageUrl) {
|
114 |
+
setStatus("error")
|
|
|
|
|
|
|
115 |
return
|
116 |
}
|
117 |
|
118 |
+
setBaseImageUrl(baseImageUrl)
|
|
|
|
|
119 |
|
120 |
+
/*
|
121 |
try {
|
122 |
croppedImageUrl = await removeBackground({
|
|
|
123 |
imageAsBase64: baseImageUrl,
|
124 |
})
|
125 |
} catch (err) {
|
126 |
+
console.error(`failed to crop the low-resolution image:`, err)
|
127 |
}
|
128 |
|
129 |
if (!croppedImageUrl) {
|
130 |
+
setStatus( "error")
|
131 |
+
return
|
132 |
+
}
|
133 |
|
134 |
+
setCroppedImageUrl(croppedImageUrl)
|
135 |
+
*/
|
136 |
+
setStatus("upscaling")
|
137 |
+
|
138 |
+
try {
|
139 |
+
upscaledImageUrl = await upscale({
|
140 |
+
imageAsBase64: baseImageUrl,
|
141 |
+
prompt: stableCascadeParams.prompt,
|
142 |
+
negativePrompt: stableCascadeParams.negativePrompt,
|
143 |
+
scaleFactor: 2,
|
144 |
+
seed: generateSeed(),
|
145 |
+
|
146 |
+
// // for a single image we can afford a higher rate, such as 25
|
147 |
+
nbSteps: 20,
|
148 |
+
})
|
149 |
+
} catch (err) {
|
150 |
+
console.error(`failed to upscale:`, err)
|
151 |
+
}
|
152 |
+
|
153 |
+
if (!upscaledImageUrl) {
|
154 |
+
setStatus( "error")
|
155 |
return
|
156 |
}
|
157 |
|
158 |
+
setUpscaledImageUrl(upscaledImageUrl)
|
|
|
|
|
|
|
159 |
|
160 |
+
try {
|
161 |
+
croppedImageUrl = await removeBackground({
|
162 |
+
imageAsBase64: upscaledImageUrl,
|
163 |
+
})
|
164 |
+
} catch (err) {
|
165 |
+
console.error(`failed to crop the upscaled image:`, err)
|
166 |
+
}
|
167 |
+
|
168 |
+
if (!croppedImageUrl) {
|
169 |
+
setStatus( "error")
|
170 |
+
return
|
171 |
+
}
|
172 |
+
|
173 |
+
setCroppedImageUrl(croppedImageUrl)
|
174 |
+
setStatus("finished")
|
175 |
}
|
176 |
|
177 |
const isBusy = status === "generating" || status === "cropping" || status === "upscaling"
|
|
|
361 |
label="Guidance Scale"
|
362 |
type='number'
|
363 |
min={1}
|
364 |
+
max={15}
|
365 |
+
step={0.1}
|
366 |
// disabled={modelState != 'ready'}
|
367 |
onChange={(e) => setGuidanceScale(parseFloat(e.target.value))}
|
368 |
value={guidanceScale}
|
369 |
className={cn({
|
370 |
+
hidden: true, // !showAdvancedSettings
|
371 |
})}
|
372 |
/>
|
373 |
<SliderField
|
|
|
436 |
defaultValue={[inferenceSteps]}
|
437 |
value={[inferenceSteps]}
|
438 |
className={cn({
|
439 |
+
hidden: true, // !showAdvancedSettings
|
440 |
})}
|
441 |
/>
|
442 |
|
|
|
450 |
onChange={(e) => setGuidanceScale(parseFloat(e.target.value))}
|
451 |
value={guidanceScale}
|
452 |
className={cn({
|
453 |
+
hidden: true, // !showAdvancedSettings
|
454 |
})}
|
455 |
/>
|
456 |
{/*
|
src/app/server/background.ts
CHANGED
@@ -1,14 +1,15 @@
|
|
1 |
"use server"
|
2 |
|
3 |
import { BackgroundRemovalParams } from "@/types"
|
|
|
4 |
import { addBase64HeaderToPng } from "./addBase64HeaderToPng"
|
5 |
|
6 |
-
const gradioApi =
|
7 |
-
const
|
8 |
|
9 |
-
export
|
10 |
imageAsBase64,
|
11 |
-
}: BackgroundRemovalParams): Promise<string>
|
12 |
|
13 |
// remember: a space needs to be public for the classic fetch() to work
|
14 |
const res = await fetch(gradioApi + (gradioApi.endsWith("/") ? "" : "/") + "api/predict", {
|
@@ -20,7 +21,7 @@ export const removeBackground = async ({
|
|
20 |
body: JSON.stringify({
|
21 |
fn_index: 0, // <- is it 0 or 1?
|
22 |
data: [
|
23 |
-
|
24 |
imageAsBase64,
|
25 |
],
|
26 |
}),
|
|
|
1 |
"use server"
|
2 |
|
3 |
import { BackgroundRemovalParams } from "@/types"
|
4 |
+
|
5 |
import { addBase64HeaderToPng } from "./addBase64HeaderToPng"
|
6 |
|
7 |
+
const gradioApi = `https://jbilcke-hf-background-removal-api.hf.space`
|
8 |
+
const microserviceApiKey = `${process.env.MICROSERVICE_API_SECRET_TOKEN || ""}`
|
9 |
|
10 |
+
export async function removeBackground({
|
11 |
imageAsBase64,
|
12 |
+
}: BackgroundRemovalParams): Promise<string> {
|
13 |
|
14 |
// remember: a space needs to be public for the classic fetch() to work
|
15 |
const res = await fetch(gradioApi + (gradioApi.endsWith("/") ? "" : "/") + "api/predict", {
|
|
|
21 |
body: JSON.stringify({
|
22 |
fn_index: 0, // <- is it 0 or 1?
|
23 |
data: [
|
24 |
+
microserviceApiKey,
|
25 |
imageAsBase64,
|
26 |
],
|
27 |
}),
|
src/app/server/stableCascade.ts
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use server"
|
2 |
+
|
3 |
+
import { generateSeed } from "@/lib/generateSeed"
|
4 |
+
import { StableCascadeParams } from "@/types"
|
5 |
+
import { addBase64HeaderToPng } from "./addBase64HeaderToPng"
|
6 |
+
|
7 |
+
const gradioApi = `https://jbilcke-hf-stable-cascade-api.hf.space`
|
8 |
+
const microserviceApiKey = `${process.env.MICROSERVICE_API_SECRET_TOKEN || ""}`
|
9 |
+
|
10 |
+
export async function stableCascade({
|
11 |
+
prompt,
|
12 |
+
negativePrompt,
|
13 |
+
guidanceScale,
|
14 |
+
nbPriorInferenceSteps,
|
15 |
+
nbDecoderInferenceSteps,
|
16 |
+
seed,
|
17 |
+
width,
|
18 |
+
height,
|
19 |
+
}: StableCascadeParams): Promise<string> {
|
20 |
+
|
21 |
+
// console.log(`calling `+ gradioApi + (gradioApi.endsWith("/") ? "" : "/") + "api/predict")
|
22 |
+
|
23 |
+
// remember: a space needs to be public for the classic fetch() to work
|
24 |
+
const res = await fetch(gradioApi + (gradioApi.endsWith("/") ? "" : "/") + "api/predict", {
|
25 |
+
method: "POST",
|
26 |
+
headers: {
|
27 |
+
"Content-Type": "application/json",
|
28 |
+
// Authorization: `Bearer ${hfApiToken}`,
|
29 |
+
},
|
30 |
+
body: JSON.stringify({
|
31 |
+
fn_index: 0, // <- is it 0 or 1?
|
32 |
+
data: [
|
33 |
+
microserviceApiKey,
|
34 |
+
prompt,
|
35 |
+
negativePrompt,
|
36 |
+
height,
|
37 |
+
width,
|
38 |
+
guidanceScale,
|
39 |
+
seed || generateSeed(),
|
40 |
+
nbPriorInferenceSteps,
|
41 |
+
nbDecoderInferenceSteps
|
42 |
+
],
|
43 |
+
}),
|
44 |
+
cache: "no-store",
|
45 |
+
// we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
|
46 |
+
// next: { revalidate: 1 }
|
47 |
+
})
|
48 |
+
|
49 |
+
const { data } = await res.json()
|
50 |
+
|
51 |
+
// console.log("data:", data)
|
52 |
+
// Recommendation: handle errors
|
53 |
+
if (res.status !== 200 || !Array.isArray(data)) {
|
54 |
+
// This will activate the closest `error.js` Error Boundary
|
55 |
+
throw new Error(`Failed to fetch data (status: ${res.status})`)
|
56 |
+
}
|
57 |
+
// console.log("data:", data.slice(0, 50))
|
58 |
+
|
59 |
+
const base64Content = (data?.[0] || "") as string
|
60 |
+
|
61 |
+
if (!base64Content) {
|
62 |
+
throw new Error(`invalid response (no content)`)
|
63 |
+
}
|
64 |
+
|
65 |
+
return addBase64HeaderToPng(base64Content)
|
66 |
+
}
|
src/app/server/{generate.ts → stableDiffusion.ts}
RENAMED
@@ -1,9 +1,9 @@
|
|
1 |
"use server"
|
2 |
|
3 |
-
import {
|
4 |
import { serverHuggingfaceApiKey, serverHuggingfaceInferenceApiFileType, serverHuggingfaceInferenceApiModel, serverHuggingfaceInferenceApiModelRefinerModel, serverHuggingfaceInferenceApiModelTrigger } from "./config"
|
5 |
|
6 |
-
export async function
|
7 |
prompt,
|
8 |
negativePrompt,
|
9 |
guidanceScale,
|
@@ -12,7 +12,7 @@ export async function generate({
|
|
12 |
height,
|
13 |
numInferenceSteps,
|
14 |
hfApiKey,
|
15 |
-
}:
|
16 |
// throw new Error("Planned maintenance")
|
17 |
if (!prompt) {
|
18 |
const error = `cannot call the rendering API without a prompt, aborting..`
|
|
|
1 |
"use server"
|
2 |
|
3 |
+
import { StableDiffusionParams } from "@/types"
|
4 |
import { serverHuggingfaceApiKey, serverHuggingfaceInferenceApiFileType, serverHuggingfaceInferenceApiModel, serverHuggingfaceInferenceApiModelRefinerModel, serverHuggingfaceInferenceApiModelTrigger } from "./config"
|
5 |
|
6 |
+
export async function stableDiffusion({
|
7 |
prompt,
|
8 |
negativePrompt,
|
9 |
guidanceScale,
|
|
|
12 |
height,
|
13 |
numInferenceSteps,
|
14 |
hfApiKey,
|
15 |
+
}: StableDiffusionParams) {
|
16 |
// throw new Error("Planned maintenance")
|
17 |
if (!prompt) {
|
18 |
const error = `cannot call the rendering API without a prompt, aborting..`
|
src/app/server/upscale.ts
CHANGED
@@ -4,17 +4,17 @@ import { generateSeed } from "@/lib/generateSeed"
|
|
4 |
import { UpscalingParams } from "@/types"
|
5 |
import { addBase64HeaderToPng } from "./addBase64HeaderToPng"
|
6 |
|
7 |
-
const gradioApi =
|
8 |
-
const
|
9 |
|
10 |
-
export
|
11 |
imageAsBase64,
|
12 |
prompt,
|
13 |
negativePrompt,
|
14 |
scaleFactor,
|
15 |
nbSteps,
|
16 |
seed,
|
17 |
-
}: UpscalingParams): Promise<string>
|
18 |
|
19 |
const addedPrompt = [
|
20 |
"clean",
|
@@ -66,7 +66,7 @@ export const upscale = async ({
|
|
66 |
body: JSON.stringify({
|
67 |
fn_index: 0, // <- is it 0 or 1?
|
68 |
data: [
|
69 |
-
|
70 |
imageAsBase64, // blob in 'parameter_5' Image component
|
71 |
prompt, // string in 'Prompt' Textbox component
|
72 |
addedPrompt, // string in 'Added Prompt' Textbox component
|
|
|
4 |
import { UpscalingParams } from "@/types"
|
5 |
import { addBase64HeaderToPng } from "./addBase64HeaderToPng"
|
6 |
|
7 |
+
const gradioApi = `https://jbilcke-hf-image-upscaling-api.hf.space`
|
8 |
+
const microserviceApiKey = `${process.env.MICROSERVICE_API_SECRET_TOKEN || ""}`
|
9 |
|
10 |
+
export async function upscale({
|
11 |
imageAsBase64,
|
12 |
prompt,
|
13 |
negativePrompt,
|
14 |
scaleFactor,
|
15 |
nbSteps,
|
16 |
seed,
|
17 |
+
}: UpscalingParams): Promise<string> {
|
18 |
|
19 |
const addedPrompt = [
|
20 |
"clean",
|
|
|
66 |
body: JSON.stringify({
|
67 |
fn_index: 0, // <- is it 0 or 1?
|
68 |
data: [
|
69 |
+
microserviceApiKey,
|
70 |
imageAsBase64, // blob in 'parameter_5' Image component
|
71 |
prompt, // string in 'Prompt' Textbox component
|
72 |
addedPrompt, // string in 'Added Prompt' Textbox component
|
src/lib/config.ts
CHANGED
@@ -100,8 +100,10 @@ export const characterExpressions = [
|
|
100 |
export type CharacterExpression = typeof characterExpressions[number]
|
101 |
|
102 |
export const photoshootModes = [
|
103 |
-
"
|
104 |
-
"gopro webcam
|
|
|
105 |
]
|
|
|
106 |
export type PhotoshootMode = typeof photoshootModes[number]
|
107 |
|
|
|
100 |
export type CharacterExpression = typeof characterExpressions[number]
|
101 |
|
102 |
export const photoshootModes = [
|
103 |
+
"realistic photo",
|
104 |
+
"gopro webcam photo",
|
105 |
+
"instagram influencer"
|
106 |
]
|
107 |
+
|
108 |
export type PhotoshootMode = typeof photoshootModes[number]
|
109 |
|
src/lib/getStableCascadeParams.ts
ADDED
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { StableCascadeParams } from "@/types"
|
2 |
+
|
3 |
+
import { CharacterExpression, CharacterGender, CharacterPose, GenerationMode, HairColor, HairStyle } from './config'
|
4 |
+
import { generateSeed } from "./generateSeed"
|
5 |
+
|
6 |
+
export function getStableCascadeParams({
|
7 |
+
prompt,
|
8 |
+
generationMode,
|
9 |
+
negativePrompt,
|
10 |
+
characterAge,
|
11 |
+
characterExpression,
|
12 |
+
characterGender,
|
13 |
+
characterHairColor,
|
14 |
+
characterHairStyle,
|
15 |
+
characterName,
|
16 |
+
characterPose
|
17 |
+
}: {
|
18 |
+
prompt: string
|
19 |
+
generationMode: GenerationMode
|
20 |
+
negativePrompt: string
|
21 |
+
characterAge: number
|
22 |
+
characterExpression: CharacterExpression
|
23 |
+
characterGender: CharacterGender
|
24 |
+
characterHairColor: HairColor
|
25 |
+
characterHairStyle: HairStyle
|
26 |
+
characterName: string
|
27 |
+
characterPose: CharacterPose
|
28 |
+
}): StableCascadeParams {
|
29 |
+
|
30 |
+
const baseParams: StableCascadeParams = {
|
31 |
+
prompt: "",
|
32 |
+
negativePrompt: "",
|
33 |
+
|
34 |
+
// between 0.1 and 15 (default 4)
|
35 |
+
guidanceScale: 4,
|
36 |
+
|
37 |
+
seed: generateSeed(),
|
38 |
+
width: 1024,
|
39 |
+
height: 1024,
|
40 |
+
|
41 |
+
// between 1 and 50 (default 20)
|
42 |
+
nbPriorInferenceSteps: 20,
|
43 |
+
|
44 |
+
// between 1 and 50 (default 10)
|
45 |
+
nbDecoderInferenceSteps: 10,
|
46 |
+
}
|
47 |
+
|
48 |
+
const params: StableCascadeParams = {
|
49 |
+
...baseParams,
|
50 |
+
...generationMode === "characters" ? {
|
51 |
+
prompt: [
|
52 |
+
`${characterAge} years old $${characterGender}`,
|
53 |
+
`${characterHairColor} ${characterHairStyle}`,
|
54 |
+
`looking ${characterExpression}`,
|
55 |
+
`${characterPose}`,
|
56 |
+
`named ${characterName}`,
|
57 |
+
'beautiful',
|
58 |
+
'award winning',
|
59 |
+
'sharp',
|
60 |
+
'crisp',
|
61 |
+
'centered',
|
62 |
+
'aligned'
|
63 |
+
].filter(x => x).join(", "),
|
64 |
+
negativePrompt: [
|
65 |
+
negativePrompt,
|
66 |
+
'drawing',
|
67 |
+
'painting',
|
68 |
+
'unrealistic',
|
69 |
+
'sitting',
|
70 |
+
'chair',
|
71 |
+
'3D render',
|
72 |
+
'unaligned',
|
73 |
+
'cropped',
|
74 |
+
'bad hands',
|
75 |
+
'wrong hands',
|
76 |
+
'deformed',
|
77 |
+
'glitch',
|
78 |
+
'blurry',
|
79 |
+
'overexposed'
|
80 |
+
].join(", "),
|
81 |
+
} : {
|
82 |
+
prompt: [
|
83 |
+
'picture of a single',
|
84 |
+
prompt,
|
85 |
+
'3D render',
|
86 |
+
'logo',
|
87 |
+
'ios icon',
|
88 |
+
'illustration',
|
89 |
+
'vector graphics',
|
90 |
+
'svg',
|
91 |
+
'beautiful',
|
92 |
+
'award winning',
|
93 |
+
'sharp',
|
94 |
+
'crisp',
|
95 |
+
'centered',
|
96 |
+
'aligned',
|
97 |
+
].join(", "),
|
98 |
+
negativePrompt: [
|
99 |
+
negativePrompt,
|
100 |
+
'photo',
|
101 |
+
'gradient',
|
102 |
+
'many',
|
103 |
+
'realistic',
|
104 |
+
'shadow',
|
105 |
+
'multiple',
|
106 |
+
'various',
|
107 |
+
'unaligned',
|
108 |
+
'cropped',
|
109 |
+
'bad hands',
|
110 |
+
'wrong hands',
|
111 |
+
'deformed',
|
112 |
+
'glitch',
|
113 |
+
'blurry',
|
114 |
+
'overexposed'
|
115 |
+
].join(", "),
|
116 |
+
}
|
117 |
+
}
|
118 |
+
return params
|
119 |
+
}
|
src/lib/{getGenerationParams.ts → getStableDiffusionParams.ts}
RENAMED
@@ -1,9 +1,9 @@
|
|
1 |
-
import {
|
2 |
|
3 |
import { CharacterExpression, CharacterGender, CharacterPose, GenerationMode, HairColor, HairStyle } from './config'
|
4 |
import { generateSeed } from "./generateSeed"
|
5 |
|
6 |
-
export function
|
7 |
generationMode,
|
8 |
negativePrompt,
|
9 |
characterAge,
|
@@ -23,9 +23,9 @@ export function getGenerationParams({
|
|
23 |
characterHairStyle: HairStyle
|
24 |
characterName: string
|
25 |
characterPose: CharacterPose
|
26 |
-
}):
|
27 |
|
28 |
-
const baseParams:
|
29 |
prompt: "",
|
30 |
negativePrompt: "",
|
31 |
guidanceScale: 9,
|
@@ -36,7 +36,7 @@ export function getGenerationParams({
|
|
36 |
hfApiKey: "",
|
37 |
}
|
38 |
|
39 |
-
const params:
|
40 |
...baseParams,
|
41 |
...generationMode === "characters" ? {
|
42 |
prompt: [
|
|
|
1 |
+
import { StableDiffusionParams } from "@/types"
|
2 |
|
3 |
import { CharacterExpression, CharacterGender, CharacterPose, GenerationMode, HairColor, HairStyle } from './config'
|
4 |
import { generateSeed } from "./generateSeed"
|
5 |
|
6 |
+
export function getStableDiffusionParams({
|
7 |
generationMode,
|
8 |
negativePrompt,
|
9 |
characterAge,
|
|
|
23 |
characterHairStyle: HairStyle
|
24 |
characterName: string
|
25 |
characterPose: CharacterPose
|
26 |
+
}): StableDiffusionParams {
|
27 |
|
28 |
+
const baseParams: StableDiffusionParams = {
|
29 |
prompt: "",
|
30 |
negativePrompt: "",
|
31 |
guidanceScale: 9,
|
|
|
36 |
hfApiKey: "",
|
37 |
}
|
38 |
|
39 |
+
const params: StableDiffusionParams = {
|
40 |
...baseParams,
|
41 |
...generationMode === "characters" ? {
|
42 |
prompt: [
|
src/types.ts
CHANGED
@@ -13,7 +13,8 @@ export type Settings = {
|
|
13 |
huggingfaceInferenceApiFileType: string
|
14 |
}
|
15 |
|
16 |
-
|
|
|
17 |
prompt: string
|
18 |
negativePrompt: string
|
19 |
guidanceScale: number
|
@@ -24,6 +25,24 @@ export interface GenerationParams {
|
|
24 |
hfApiKey: string
|
25 |
}
|
26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
export type UpscalingParams = {
|
28 |
imageAsBase64: string
|
29 |
prompt: string
|
|
|
13 |
huggingfaceInferenceApiFileType: string
|
14 |
}
|
15 |
|
16 |
+
|
17 |
+
export interface StableDiffusionParams {
|
18 |
prompt: string
|
19 |
negativePrompt: string
|
20 |
guidanceScale: number
|
|
|
25 |
hfApiKey: string
|
26 |
}
|
27 |
|
28 |
+
export interface StableCascadeParams {
|
29 |
+
prompt: string
|
30 |
+
negativePrompt: string
|
31 |
+
|
32 |
+
|
33 |
+
// between 0.1 and 15 (default 4)
|
34 |
+
guidanceScale: number
|
35 |
+
seed: number
|
36 |
+
width: number
|
37 |
+
height: number
|
38 |
+
|
39 |
+
// between 1 and 50 (default 20)
|
40 |
+
nbPriorInferenceSteps: number
|
41 |
+
|
42 |
+
// between 1 and 50 (default 10)
|
43 |
+
nbDecoderInferenceSteps: number
|
44 |
+
}
|
45 |
+
|
46 |
export type UpscalingParams = {
|
47 |
imageAsBase64: string
|
48 |
prompt: string
|