jbilcke-hf HF staff commited on
Commit
f5d8038
β€’
1 Parent(s): 2d323fb

fixed upscaling

Browse files
.env CHANGED
@@ -1,6 +1,6 @@
1
- NEXT_PUBLIC_BASE_URL=https://jbilcke-hf-fishtank.hf.space
2
  # NEXT_PUBLIC_RENDERING_ENGINE_API=https://hysts-zeroscope-v2.hf.space
3
  RENDERING_ENGINE_API=https://jbilcke-hf-videochain-api.hf.space
4
- #VC_SECRET_ACCESS_TOKEN=<SECRET>
5
  #HF_API_TOKEN=<SECRET>
6
  #HF_INFERENCE_ENDPOINT_URL=<SECRET>
 
1
+ NEXT_PUBLIC_BASE_URL=https://jbilcke-hf-ai-comic-factory.hf.space
2
  # NEXT_PUBLIC_RENDERING_ENGINE_API=https://hysts-zeroscope-v2.hf.space
3
  RENDERING_ENGINE_API=https://jbilcke-hf-videochain-api.hf.space
4
+ #VC_SECRET_ACCESS_TOKEN=<secret>
5
  #HF_API_TOKEN=<SECRET>
6
  #HF_INFERENCE_ENDPOINT_URL=<SECRET>
next.config.js CHANGED
@@ -4,6 +4,7 @@ const nextConfig = {
4
 
5
  experimental: {
6
  serverActions: true,
 
7
  },
8
  }
9
 
 
4
 
5
  experimental: {
6
  serverActions: true,
7
+ serverActionsBodySizeLimit: '8mb',
8
  },
9
  }
10
 
src/app/engine/presets.ts CHANGED
@@ -123,13 +123,12 @@ export const presets: Record<string, Preset> = {
123
  font: "actionman",
124
  llmPrompt: "american comic",
125
  imagePrompt: (prompt: string) => [
126
- `american comic about ${prompt}`,
127
- "single panel",
128
- "modern american comic",
129
- "comicbook style",
130
- "2010s",
131
- "digital print",
132
- "color comicbook",
133
  // "color drawing"
134
  ],
135
  negativePrompt: () => [
@@ -183,13 +182,12 @@ export const presets: Record<string, Preset> = {
183
  font: "actionman",
184
  llmPrompt: "american comic",
185
  imagePrompt: (prompt: string) => [
186
- `american comic about ${prompt}`,
187
- "single panel",
188
- "american comic",
189
- "comicbook style",
190
  "1950",
191
  "50s",
192
- "color comicbook",
193
  // "color drawing"
194
  ],
195
  negativePrompt: () => [
@@ -244,15 +242,12 @@ export const presets: Record<string, Preset> = {
244
  font: "actionman",
245
  llmPrompt: "new pulp science fiction",
246
  imagePrompt: (prompt: string) => [
247
- `color comic panel`,
248
  `${prompt}`,
249
  "40s",
250
  "1940",
251
- "vintage comic",
252
- "pulp magazine",
253
- "pulp science fiction",
254
  "vintage science fiction",
255
- "single panel",
256
  // "comic album"
257
  ],
258
  negativePrompt: () => [
@@ -311,9 +306,9 @@ export const presets: Record<string, Preset> = {
311
  "tintin style",
312
  "french comic panel",
313
  "franco-belgian style",
314
- "color panel",
315
- "bande dessinΓ©e",
316
- "single panel",
317
  // "comic album"
318
  ],
319
  negativePrompt: () => [
@@ -343,8 +338,8 @@ export const presets: Record<string, Preset> = {
343
  "franco-belgian style",
344
  "bande dessinΓ©e",
345
  "single panel",
346
- "comical",
347
- "comic album",
348
  // "color drawing"
349
  ],
350
  negativePrompt: () => [
 
123
  font: "actionman",
124
  llmPrompt: "american comic",
125
  imagePrompt: (prompt: string) => [
126
+ `modern american comic about ${prompt}`,
127
+ //"single panel",
128
+ "digital color comicbook style",
129
+ // "2010s",
130
+ // "digital print",
131
+ // "color comicbook",
 
132
  // "color drawing"
133
  ],
134
  negativePrompt: () => [
 
182
  font: "actionman",
183
  llmPrompt: "american comic",
184
  imagePrompt: (prompt: string) => [
185
+ `vintage american color comic about ${prompt}`,
186
+ // "single panel",
187
+ // "comicbook style",
 
188
  "1950",
189
  "50s",
190
+ // "color comicbook",
191
  // "color drawing"
192
  ],
193
  negativePrompt: () => [
 
242
  font: "actionman",
243
  llmPrompt: "new pulp science fiction",
244
  imagePrompt: (prompt: string) => [
245
+ `vintage color pulp comic panel`,
246
  `${prompt}`,
247
  "40s",
248
  "1940",
 
 
 
249
  "vintage science fiction",
250
+ // "single panel",
251
  // "comic album"
252
  ],
253
  negativePrompt: () => [
 
306
  "tintin style",
307
  "french comic panel",
308
  "franco-belgian style",
309
+ // "color panel",
310
+ // "bande dessinΓ©e",
311
+ // "single panel",
312
  // "comic album"
313
  ],
314
  negativePrompt: () => [
 
338
  "franco-belgian style",
339
  "bande dessinΓ©e",
340
  "single panel",
341
+ // "comical",
342
+ // "comic album",
343
  // "color drawing"
344
  ],
345
  negativePrompt: () => [
src/app/engine/render.ts CHANGED
@@ -19,7 +19,7 @@ export async function newRender({
19
  width: number
20
  height: number
21
  }) {
22
- console.log(`newRender(${prompt})`)
23
  if (!prompt) {
24
  console.error(`cannot call the rendering API without a prompt, aborting..`)
25
  throw new Error(`cannot call the rendering API without a prompt, aborting..`)
@@ -37,7 +37,7 @@ export async function newRender({
37
 
38
 
39
  try {
40
- console.log(`calling POST ${apiUrl}/render with prompt: ${prompt}`)
41
 
42
  const res = await fetch(`${apiUrl}/render`, {
43
  method: "POST",
@@ -99,7 +99,7 @@ export async function getRender(renderId: string) {
99
 
100
  let defaulResult: RenderedScene = {
101
  renderId: "",
102
- status: "error",
103
  assetUrl: "",
104
  alt: "",
105
  maskUrl: "",
@@ -114,7 +114,7 @@ export async function getRender(renderId: string) {
114
  headers: {
115
  Accept: "application/json",
116
  "Content-Type": "application/json",
117
- // Authorization: `Bearer ${process.env.VC_SECRET_ACCESS_TOKEN}`,
118
  },
119
  cache: 'no-store',
120
  // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
@@ -134,6 +134,62 @@ export async function getRender(renderId: string) {
134
  const response = (await res.json()) as RenderedScene
135
  // console.log("response:", response)
136
  return response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  } catch (err) {
138
  console.error(err)
139
  // Gorgon.clear(cacheKey)
 
19
  width: number
20
  height: number
21
  }) {
22
+ // console.log(`newRender(${prompt})`)
23
  if (!prompt) {
24
  console.error(`cannot call the rendering API without a prompt, aborting..`)
25
  throw new Error(`cannot call the rendering API without a prompt, aborting..`)
 
37
 
38
 
39
  try {
40
+ // console.log(`calling POST ${apiUrl}/render with prompt: ${prompt}`)
41
 
42
  const res = await fetch(`${apiUrl}/render`, {
43
  method: "POST",
 
99
 
100
  let defaulResult: RenderedScene = {
101
  renderId: "",
102
+ status: "pending",
103
  assetUrl: "",
104
  alt: "",
105
  maskUrl: "",
 
114
  headers: {
115
  Accept: "application/json",
116
  "Content-Type": "application/json",
117
+ Authorization: `Bearer ${process.env.VC_SECRET_ACCESS_TOKEN}`,
118
  },
119
  cache: 'no-store',
120
  // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
 
134
  const response = (await res.json()) as RenderedScene
135
  // console.log("response:", response)
136
  return response
137
+ } catch (err) {
138
+ console.error(err)
139
+ defaulResult.status = "error"
140
+ defaulResult.error = `${err}`
141
+ // Gorgon.clear(cacheKey)
142
+ return defaulResult
143
+ }
144
+
145
+ // }, cacheDurationInSec * 1000)
146
+ }
147
+
148
+ export async function upscaleImage(image: string): Promise<{
149
+ assetUrl: string
150
+ error: string
151
+ }> {
152
+ if (!image) {
153
+ console.error(`cannot call the rendering API without an image, aborting..`)
154
+ throw new Error(`cannot call the rendering API without an image, aborting..`)
155
+ }
156
+
157
+ let defaulResult = {
158
+ assetUrl: "",
159
+ error: "failed to fetch the data",
160
+ }
161
+
162
+ try {
163
+ // console.log(`calling GET ${apiUrl}/render with renderId: ${renderId}`)
164
+ const res = await fetch(`${apiUrl}/upscale`, {
165
+ method: "POST",
166
+ headers: {
167
+ Accept: "application/json",
168
+ "Content-Type": "application/json",
169
+ Authorization: `Bearer ${process.env.VC_SECRET_ACCESS_TOKEN}`,
170
+ },
171
+ cache: 'no-store',
172
+ body: JSON.stringify({ image, factor: 3 })
173
+ // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
174
+ // next: { revalidate: 1 }
175
+ })
176
+
177
+ // console.log("res:", res)
178
+ // The return value is *not* serialized
179
+ // You can return Date, Map, Set, etc.
180
+
181
+ // Recommendation: handle errors
182
+ if (res.status !== 200) {
183
+ // This will activate the closest `error.js` Error Boundary
184
+ throw new Error('Failed to fetch data')
185
+ }
186
+
187
+ const response = (await res.json()) as {
188
+ assetUrl: string
189
+ error: string
190
+ }
191
+ // console.log("response:", response)
192
+ return response
193
  } catch (err) {
194
  console.error(err)
195
  // Gorgon.clear(cacheKey)
src/app/interface/bottom-bar/index.tsx CHANGED
@@ -5,6 +5,9 @@ import { base64ToFile } from "@/lib/base64ToFile"
5
  import { uploadToHuggingFace } from "@/lib/uploadToHuggingFace"
6
  import { cn } from "@/lib/utils"
7
  import { About } from "../about"
 
 
 
8
 
9
  export function BottomBar() {
10
  const download = useStore(state => state.download)
@@ -17,15 +20,42 @@ export function BottomBar() {
17
 
18
  const allStatus = Object.values(panelGenerationStatus)
19
  const remainingImages = allStatus.reduce((acc, s) => (acc + (s ? 1 : 0)), 0)
20
-
21
- /*
 
 
 
 
 
22
  const handleUpscale = () => {
 
23
  startTransition(() => {
24
- we are blocked here, because we don't know the render id
25
- also we will have to store a state about the
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  })
27
  }
28
- */
29
 
30
  const handleShare = async () => {
31
  const dataUrl = await pageToImage()
@@ -83,16 +113,6 @@ ${uploadUrl
83
  )}>
84
  <About />
85
  </div>
86
- {/*
87
- <div>
88
- <Button
89
- onClick={handleUpscale}
90
- disabled={!prompt?.length && remainingImages}
91
- >
92
- Upscale
93
- </Button>
94
- </div>
95
- */}
96
  <div className={cn(
97
  `flex flex-row`,
98
  `animation-all duration-300 ease-in-out`,
@@ -100,6 +120,16 @@ ${uploadUrl
100
  `space-x-3`,
101
  `scale-[0.9]`
102
  )}>
 
 
 
 
 
 
 
 
 
 
103
  <div>
104
  <Button
105
  onClick={handlePrint}
@@ -114,10 +144,10 @@ ${uploadUrl
114
  disabled={!prompt?.length}
115
  >
116
  <span className="hidden md:inline">{
117
- remainingImages ? `${allStatus.length - remainingImages}/4 panels βŒ›` : `Save`
118
  }</span>
119
  <span className="inline md:hidden">{
120
- remainingImages ? `${allStatus.length - remainingImages}/4 βŒ›` : `Save`
121
  }</span>
122
  </Button>
123
  </div>
 
5
  import { uploadToHuggingFace } from "@/lib/uploadToHuggingFace"
6
  import { cn } from "@/lib/utils"
7
  import { About } from "../about"
8
+ import { startTransition, useState } from "react"
9
+ import { upscaleImage } from "@/app/engine/render"
10
+ import { sleep } from "@/lib/sleep"
11
 
12
  export function BottomBar() {
13
  const download = useStore(state => state.download)
 
20
 
21
  const allStatus = Object.values(panelGenerationStatus)
22
  const remainingImages = allStatus.reduce((acc, s) => (acc + (s ? 1 : 0)), 0)
23
+
24
+ const upscaleQueue = useStore(state => state.upscaleQueue)
25
+ const renderedScenes = useStore(state => state.renderedScenes)
26
+ const removeFromUpscaleQueue = useStore(state => state.removeFromUpscaleQueue)
27
+ const setRendered = useStore(state => state.setRendered)
28
+ const [isUpscaling, setUpscaling] = useState(false)
29
+
30
  const handleUpscale = () => {
31
+ setUpscaling(true)
32
  startTransition(() => {
33
+ const fn = async () => {
34
+ for (let [panelId, renderedScene] of Object.entries(upscaleQueue)) {
35
+ try {
36
+ console.log(`upscaling panel ${panelId} (${renderedScene.renderId})`)
37
+ const result = await upscaleImage(renderedScene.assetUrl)
38
+ await sleep(1000)
39
+ if (result.assetUrl) {
40
+ console.log(`upscale successful, removing ${panelId} (${renderedScene.renderId}) from upscale queue`)
41
+ setRendered(panelId, {
42
+ ...renderedScene,
43
+ assetUrl: result.assetUrl
44
+ })
45
+ removeFromUpscaleQueue(panelId)
46
+ }
47
+
48
+ } catch (err) {
49
+ console.error(`failed to upscale: ${err}`)
50
+ }
51
+ }
52
+
53
+ setUpscaling(false)
54
+ }
55
+
56
+ fn()
57
  })
58
  }
 
59
 
60
  const handleShare = async () => {
61
  const dataUrl = await pageToImage()
 
113
  )}>
114
  <About />
115
  </div>
 
 
 
 
 
 
 
 
 
 
116
  <div className={cn(
117
  `flex flex-row`,
118
  `animation-all duration-300 ease-in-out`,
 
120
  `space-x-3`,
121
  `scale-[0.9]`
122
  )}>
123
+ <div>
124
+ <Button
125
+ onClick={handleUpscale}
126
+ disabled={!prompt?.length || remainingImages > 0 || !Object.values(upscaleQueue).length}
127
+ >
128
+ {isUpscaling
129
+ ? `${allStatus.length - Object.values(upscaleQueue).length}/${allStatus.length} βŒ›`
130
+ : "Upscale"}
131
+ </Button>
132
+ </div>
133
  <div>
134
  <Button
135
  onClick={handlePrint}
 
144
  disabled={!prompt?.length}
145
  >
146
  <span className="hidden md:inline">{
147
+ remainingImages ? `${allStatus.length - remainingImages}/${allStatus.length} panels βŒ›` : `Save`
148
  }</span>
149
  <span className="inline md:hidden">{
150
+ remainingImages ? `${allStatus.length - remainingImages}/${allStatus.length} βŒ›` : `Save`
151
  }</span>
152
  </Button>
153
  </div>
src/app/interface/panel/index.tsx CHANGED
@@ -26,9 +26,12 @@ export function Panel({
26
  width?: number
27
  height?: number
28
  }) {
 
 
29
  const ref = useRef<HTMLImageElement>(null)
30
  const font = useStore(state => state.font)
31
  const preset = useStore(state => state.preset)
 
32
  const setGeneratingImages = useStore(state => state.setGeneratingImages)
33
 
34
  const [imageWithText, setImageWithText] = useState("")
@@ -41,13 +44,18 @@ export function Panel({
41
  const zoomLevel = useStore(state => state.zoomLevel)
42
  const showCaptions = useStore(state => state.showCaptions)
43
 
44
- // const setCaption = useStore(state => state.setCaption)
45
- // const captions = useStore(state => state.captions)
46
- // const caption = captions[panel] || ""
47
 
48
  const [_isPending, startTransition] = useTransition()
49
- const [rendered, setRendered] = useState<RenderedScene>(getInitialRenderedScene())
 
 
 
 
 
50
  const renderedRef = useRef<RenderedScene>()
 
 
51
 
52
  const timeoutRef = useRef<any>(null)
53
 
@@ -60,41 +68,40 @@ export function Panel({
60
  if (!prompt?.length) { return }
61
 
62
  // important: update the status, and clear the scene
63
- setGeneratingImages(panel, true)
64
 
65
  // just to empty it
66
- setRendered(getInitialRenderedScene())
67
 
68
  setTimeout(() => {
69
  startTransition(async () => {
70
 
71
- console.log(`Loading panel ${panel}..`)
72
 
73
  let newRendered: RenderedScene
74
  try {
75
  newRendered = await newRender({ prompt, width, height })
76
  } catch (err) {
77
- console.log("Failed to load the panel! Don't worry, we are retrying..")
78
  newRendered = await newRender({ prompt, width, height })
79
  }
80
 
81
  if (newRendered) {
82
  // console.log("newRendered:", newRendered)
83
- setRendered(renderedRef.current = newRendered)
84
- // addRenderedScene(newRendered)
85
 
86
  // but we are still loading!
87
  } else {
88
- setRendered(renderedRef.current = {
89
  renderId: "",
90
- status: "error",
91
  assetUrl: "",
92
  alt: "",
93
  maskUrl: "",
94
- error: "failed to fetch the data",
95
  segments: []
96
  })
97
- setGeneratingImages(panel, false)
98
  return
99
  }
100
  })
@@ -111,15 +118,15 @@ export function Panel({
111
  return
112
  }
113
  try {
114
- setGeneratingImages(panel, true)
115
  // console.log(`Checking job status API for job ${renderedRef.current?.renderId}`)
116
  const newRendered = await getRender(renderedRef.current.renderId)
117
  // console.log("got a response!", newRendered)
118
 
119
  if (JSON.stringify(renderedRef.current) !== JSON.stringify(newRendered)) {
120
- console.log("updated panel:", newRendered)
121
- setRendered(renderedRef.current = newRendered)
122
- setGeneratingImages(panel, true)
123
  }
124
  // console.log("status:", newRendered.status)
125
 
@@ -128,17 +135,18 @@ export function Panel({
128
  timeoutRef.current = setTimeout(checkStatus, delay)
129
  } else if (newRendered.status === "error" ||
130
  (newRendered.status === "completed" && !newRendered.assetUrl?.length)) {
131
- console.log(`panel got an error and/or an empty asset url :/ "${newRendered.error}", but let's try to recover..`)
132
  try {
133
  const newAttempt = await newRender({ prompt, width, height })
134
- setRendered(renderedRef.current = newAttempt)
135
  } catch (err) {
136
- console.error("yeah sorry, something is wrong.. aborting")
137
- setGeneratingImages(panel, false)
138
  }
139
  } else {
140
  console.log("panel finished!")
141
- setGeneratingImages(panel, false)
 
142
  }
143
  } catch (err) {
144
  console.error(err)
@@ -251,7 +259,6 @@ export function Panel({
251
  zoomLevel > 40 ? `border-b-[0.5px] md:border-b-[1px]` :
252
  `border-transparent md:border-b-[0.5px]`,
253
  `print:border-b-[1.5px]`,
254
- showCaptions ? `block` : `hidden`,
255
  `truncate`,
256
 
257
  zoomLevel > 200 ? `p-4 md:p-8` :
@@ -271,8 +278,11 @@ export function Panel({
271
  zoomLevel > 120 ? `text-3xs md:text-xl` :
272
  zoomLevel > 100 ? `text-4xs md:text-lg` :
273
  zoomLevel > 90 ? `text-5xs md:text-sm` :
274
- zoomLevel > 40 ? `hidden md:block md:text-xs` :
275
- `hidden md:block md:text-2xs`,
 
 
 
276
  )}
277
  >{caption || ""}
278
  </div>
 
26
  width?: number
27
  height?: number
28
  }) {
29
+ const panelId = `${panel}`
30
+
31
  const ref = useRef<HTMLImageElement>(null)
32
  const font = useStore(state => state.font)
33
  const preset = useStore(state => state.preset)
34
+
35
  const setGeneratingImages = useStore(state => state.setGeneratingImages)
36
 
37
  const [imageWithText, setImageWithText] = useState("")
 
44
  const zoomLevel = useStore(state => state.zoomLevel)
45
  const showCaptions = useStore(state => state.showCaptions)
46
 
47
+ const addToUpscaleQueue = useStore(state => state.addToUpscaleQueue)
 
 
48
 
49
  const [_isPending, startTransition] = useTransition()
50
+ const renderedScenes = useStore(state => state.renderedScenes)
51
+ const setRendered = useStore(state => state.setRendered)
52
+
53
+ const rendered = renderedScenes[panel] || getInitialRenderedScene()
54
+
55
+ // keep a ref in sync
56
  const renderedRef = useRef<RenderedScene>()
57
+ const renderedKey = JSON.stringify(rendered)
58
+ useEffect(() => { renderedRef.current = rendered }, [renderedKey])
59
 
60
  const timeoutRef = useRef<any>(null)
61
 
 
68
  if (!prompt?.length) { return }
69
 
70
  // important: update the status, and clear the scene
71
+ setGeneratingImages(panelId, true)
72
 
73
  // just to empty it
74
+ setRendered(panelId, getInitialRenderedScene())
75
 
76
  setTimeout(() => {
77
  startTransition(async () => {
78
 
79
+ // console.log(`Loading panel ${panel}..`)
80
 
81
  let newRendered: RenderedScene
82
  try {
83
  newRendered = await newRender({ prompt, width, height })
84
  } catch (err) {
85
+ // "Failed to load the panel! Don't worry, we are retrying..")
86
  newRendered = await newRender({ prompt, width, height })
87
  }
88
 
89
  if (newRendered) {
90
  // console.log("newRendered:", newRendered)
91
+ setRendered(panelId, newRendered)
 
92
 
93
  // but we are still loading!
94
  } else {
95
+ setRendered(panelId, {
96
  renderId: "",
97
+ status: "pending",
98
  assetUrl: "",
99
  alt: "",
100
  maskUrl: "",
101
+ error: "",
102
  segments: []
103
  })
104
+ setGeneratingImages(panelId, false)
105
  return
106
  }
107
  })
 
118
  return
119
  }
120
  try {
121
+ setGeneratingImages(panelId, true)
122
  // console.log(`Checking job status API for job ${renderedRef.current?.renderId}`)
123
  const newRendered = await getRender(renderedRef.current.renderId)
124
  // console.log("got a response!", newRendered)
125
 
126
  if (JSON.stringify(renderedRef.current) !== JSON.stringify(newRendered)) {
127
+ // console.log("updated panel:", newRendered)
128
+ setRendered(panelId, renderedRef.current = newRendered)
129
+ setGeneratingImages(panelId, true)
130
  }
131
  // console.log("status:", newRendered.status)
132
 
 
135
  timeoutRef.current = setTimeout(checkStatus, delay)
136
  } else if (newRendered.status === "error" ||
137
  (newRendered.status === "completed" && !newRendered.assetUrl?.length)) {
138
+ // console.log(`panel got an error and/or an empty asset url :/ "${newRendered.error}", but let's try to recover..`)
139
  try {
140
  const newAttempt = await newRender({ prompt, width, height })
141
+ setRendered(panelId, newAttempt)
142
  } catch (err) {
143
+ console.error("yeah sorry, something is wrong.. aborting", err)
144
+ setGeneratingImages(panelId, false)
145
  }
146
  } else {
147
  console.log("panel finished!")
148
+ setGeneratingImages(panelId, false)
149
+ addToUpscaleQueue(panelId, newRendered)
150
  }
151
  } catch (err) {
152
  console.error(err)
 
259
  zoomLevel > 40 ? `border-b-[0.5px] md:border-b-[1px]` :
260
  `border-transparent md:border-b-[0.5px]`,
261
  `print:border-b-[1.5px]`,
 
262
  `truncate`,
263
 
264
  zoomLevel > 200 ? `p-4 md:p-8` :
 
278
  zoomLevel > 120 ? `text-3xs md:text-xl` :
279
  zoomLevel > 100 ? `text-4xs md:text-lg` :
280
  zoomLevel > 90 ? `text-5xs md:text-sm` :
281
+ zoomLevel > 40 ? `md:text-xs` : `md:text-2xs`,
282
+
283
+ showCaptions ? (
284
+ zoomLevel > 90 ? `block` : `hidden md:block`
285
+ ) : `hidden`,
286
  )}
287
  >{caption || ""}
288
  </div>
src/app/main.tsx CHANGED
@@ -30,7 +30,6 @@ export default function Main() {
30
 
31
  const [waitABitMore, setWaitABitMore] = useState(false)
32
 
33
-
34
  // react to prompt changes
35
  useEffect(() => {
36
  if (!prompt) { return }
@@ -42,27 +41,29 @@ export default function Main() {
42
  try {
43
 
44
  const llmResponse = await getStory({ preset, prompt })
45
- console.log("response:", llmResponse)
46
 
47
  // we have to limit the size of the prompt, otherwise the rest of the style won't be followed
48
 
49
  let limitedPrompt = prompt.slice(0, 77)
50
- console.log("had to cut the length og the prompt to:", limitedPrompt)
 
 
51
 
52
  const panelPromptPrefix = preset.imagePrompt(limitedPrompt).join(", ")
53
- console.log("panel prompt prefix:", panelPromptPrefix)
54
-
55
  const nbPanels = 4
56
  const newPanels: string[] = []
57
  const newCaptions: string[] = []
58
  setWaitABitMore(true)
59
-
60
  for (let p = 0; p < nbPanels; p++) {
61
  newCaptions.push(llmResponse[p]?.caption || "...")
62
- const newPanel = [panelPromptPrefix, llmResponse[p]?.instructions || ""]
63
- newPanels.push(newPanel.map(chunk => chunk).join(", "))
 
64
  }
65
- console.log("newPanels:", newPanels)
66
  setCaptions(newCaptions)
67
  setPanels(newPanels)
68
  } catch (err) {
 
30
 
31
  const [waitABitMore, setWaitABitMore] = useState(false)
32
 
 
33
  // react to prompt changes
34
  useEffect(() => {
35
  if (!prompt) { return }
 
41
  try {
42
 
43
  const llmResponse = await getStory({ preset, prompt })
44
+ console.log("LLM responded:", llmResponse)
45
 
46
  // we have to limit the size of the prompt, otherwise the rest of the style won't be followed
47
 
48
  let limitedPrompt = prompt.slice(0, 77)
49
+ if (limitedPrompt.length !== prompt.length) {
50
+ console.log("Sorry folks, the prompt was cut to:", limitedPrompt)
51
+ }
52
 
53
  const panelPromptPrefix = preset.imagePrompt(limitedPrompt).join(", ")
54
+
 
55
  const nbPanels = 4
56
  const newPanels: string[] = []
57
  const newCaptions: string[] = []
58
  setWaitABitMore(true)
59
+ console.log("Panel prompts for SDXL:")
60
  for (let p = 0; p < nbPanels; p++) {
61
  newCaptions.push(llmResponse[p]?.caption || "...")
62
+ const newPanel = [panelPromptPrefix, llmResponse[p]?.instructions || ""].map(chunk => chunk).join(", ")
63
+ newPanels.push(newPanel)
64
+ console.log(newPanel)
65
  }
66
+
67
  setCaptions(newCaptions)
68
  setPanels(newPanels)
69
  } catch (err) {
src/app/queries/getStory.ts CHANGED
@@ -54,7 +54,7 @@ export const getStory = async ({
54
  }
55
  }
56
 
57
- console.log("Raw response from LLM:", result)
58
  const tmp = cleanJson(result)
59
 
60
  let llmResponse: LLMResponse = []
@@ -63,6 +63,7 @@ export const getStory = async ({
63
  llmResponse = dirtyLLMJsonParser(tmp)
64
  } catch (err) {
65
  console.log(`failed to read LLM response: ${err}`)
 
66
 
67
  // in case of failure here, it might be because the LLM hallucinated a completely different response,
68
  // such as markdown. There is no real solution.. but we can try a fallback:
 
54
  }
55
  }
56
 
57
+ // console.log("Raw response from LLM:", result)
58
  const tmp = cleanJson(result)
59
 
60
  let llmResponse: LLMResponse = []
 
63
  llmResponse = dirtyLLMJsonParser(tmp)
64
  } catch (err) {
65
  console.log(`failed to read LLM response: ${err}`)
66
+ console.log(`original response was:`, result)
67
 
68
  // in case of failure here, it might be because the LLM hallucinated a completely different response,
69
  // such as markdown. There is no real solution.. but we can try a fallback:
src/app/store/index.ts CHANGED
@@ -15,7 +15,9 @@ export const useStore = create<{
15
  nbFrames: number
16
  panels: string[]
17
  captions: string[]
 
18
  showCaptions: boolean
 
19
  layout: LayoutName
20
  layouts: LayoutName[]
21
  zoomLevel: number
@@ -24,6 +26,9 @@ export const useStore = create<{
24
  panelGenerationStatus: Record<number, boolean>
25
  isGeneratingText: boolean
26
  atLeastOnePanelIsBusy: boolean
 
 
 
27
  setPrompt: (prompt: string) => void
28
  setFont: (font: FontName) => void
29
  setPreset: (preset: Preset) => void
@@ -35,7 +40,7 @@ export const useStore = create<{
35
  setZoomLevel: (zoomLevel: number) => void
36
  setPage: (page: HTMLDivElement) => void
37
  setGeneratingStory: (isGeneratingStory: boolean) => void
38
- setGeneratingImages: (panelId: number, value: boolean) => void
39
  setGeneratingText: (isGeneratingText: boolean) => void
40
  pageToImage: () => Promise<string>
41
  download: () => Promise<void>
@@ -47,6 +52,8 @@ export const useStore = create<{
47
  nbFrames: 1,
48
  panels: [],
49
  captions: [],
 
 
50
  showCaptions: false,
51
  layout: defaultLayout,
52
  layouts: [defaultLayout, defaultLayout],
@@ -56,6 +63,31 @@ export const useStore = create<{
56
  panelGenerationStatus: {},
57
  isGeneratingText: false,
58
  atLeastOnePanelIsBusy: false,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  setPrompt: (prompt: string) => {
60
  const existingPrompt = get().prompt
61
  if (prompt === existingPrompt) { return }
@@ -112,9 +144,8 @@ export const useStore = create<{
112
  set({ page })
113
  },
114
  setGeneratingStory: (isGeneratingStory: boolean) => set({ isGeneratingStory }),
115
- setGeneratingImages: (panelId: number, value: boolean) => {
116
-
117
- const panelGenerationStatus: Record<number, boolean> = {
118
  ...get().panelGenerationStatus,
119
  [panelId]: value
120
  }
 
15
  nbFrames: number
16
  panels: string[]
17
  captions: string[]
18
+ upscaleQueue: Record<string, RenderedScene>
19
  showCaptions: boolean
20
+ renderedScenes: Record<string, RenderedScene>
21
  layout: LayoutName
22
  layouts: LayoutName[]
23
  zoomLevel: number
 
26
  panelGenerationStatus: Record<number, boolean>
27
  isGeneratingText: boolean
28
  atLeastOnePanelIsBusy: boolean
29
+ setRendered: (panelId: string, renderedScene: RenderedScene) => void
30
+ addToUpscaleQueue: (panelId: string, renderedScene: RenderedScene) => void
31
+ removeFromUpscaleQueue: (panelId: string) => void
32
  setPrompt: (prompt: string) => void
33
  setFont: (font: FontName) => void
34
  setPreset: (preset: Preset) => void
 
40
  setZoomLevel: (zoomLevel: number) => void
41
  setPage: (page: HTMLDivElement) => void
42
  setGeneratingStory: (isGeneratingStory: boolean) => void
43
+ setGeneratingImages: (panelId: string, value: boolean) => void
44
  setGeneratingText: (isGeneratingText: boolean) => void
45
  pageToImage: () => Promise<string>
46
  download: () => Promise<void>
 
52
  nbFrames: 1,
53
  panels: [],
54
  captions: [],
55
+ upscaleQueue: {} as Record<string, RenderedScene>,
56
+ renderedScenes: {} as Record<string, RenderedScene>,
57
  showCaptions: false,
58
  layout: defaultLayout,
59
  layouts: [defaultLayout, defaultLayout],
 
63
  panelGenerationStatus: {},
64
  isGeneratingText: false,
65
  atLeastOnePanelIsBusy: false,
66
+ setRendered: (panelId: string, renderedScene: RenderedScene) => {
67
+ const { renderedScenes } = get()
68
+ set({
69
+ renderedScenes: {
70
+ ...renderedScenes,
71
+ [panelId]: renderedScene
72
+ }
73
+ })
74
+ },
75
+ addToUpscaleQueue: (panelId: string, renderedScene: RenderedScene) => {
76
+ const { upscaleQueue } = get()
77
+ set({
78
+ upscaleQueue: {
79
+ ...upscaleQueue,
80
+ [panelId]: renderedScene
81
+ },
82
+ })
83
+ },
84
+ removeFromUpscaleQueue: (panelId: string) => {
85
+ const upscaleQueue = { ...get().upscaleQueue }
86
+ delete upscaleQueue[panelId]
87
+ set({
88
+ upscaleQueue,
89
+ })
90
+ },
91
  setPrompt: (prompt: string) => {
92
  const existingPrompt = get().prompt
93
  if (prompt === existingPrompt) { return }
 
144
  set({ page })
145
  },
146
  setGeneratingStory: (isGeneratingStory: boolean) => set({ isGeneratingStory }),
147
+ setGeneratingImages: (panelId: string, value: boolean) => {
148
+ const panelGenerationStatus: Record<string, boolean> = {
 
149
  ...get().panelGenerationStatus,
150
  [panelId]: value
151
  }
src/lib/sleep.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export const sleep = async (durationInMs: number) =>
2
+ new Promise((resolve) => {
3
+ setTimeout(() => {
4
+ resolve(true)
5
+ }, durationInMs)
6
+ })