jbilcke-hf HF staff commited on
Commit
3b81d2d
β€’
1 Parent(s): 9ee3c2f

update for oauth

Browse files
.env CHANGED
@@ -24,6 +24,8 @@ NEXT_PUBLIC_ENABLE_RATE_LIMITER="false"
24
  ENABLE_HUGGING_FACE_OAUTH=
25
  ENABLE_HUGGING_FACE_OAUTH_WALL=
26
  HUGGING_FACE_OAUTH_CLIENT_ID=
 
 
27
  HUGGING_FACE_OAUTH_REDIRECT_URL=
28
 
29
  # this one must be kept secret (and is unused for now)
 
24
  ENABLE_HUGGING_FACE_OAUTH=
25
  ENABLE_HUGGING_FACE_OAUTH_WALL=
26
  HUGGING_FACE_OAUTH_CLIENT_ID=
27
+
28
+ # in production this should be the space's domain and/or URL
29
  HUGGING_FACE_OAUTH_REDIRECT_URL=
30
 
31
  # this one must be kept secret (and is unused for now)
package-lock.json CHANGED
@@ -8,7 +8,7 @@
8
  "name": "@jbilcke/comic-factory",
9
  "version": "1.2.0",
10
  "dependencies": {
11
- "@aitube/clap": "0.0.10",
12
  "@anthropic-ai/sdk": "^0.19.1",
13
  "@huggingface/hub": "^0.14.2",
14
  "@huggingface/inference": "^2.6.1",
@@ -81,9 +81,9 @@
81
  }
82
  },
83
  "node_modules/@aitube/clap": {
84
- "version": "0.0.10",
85
- "resolved": "https://registry.npmjs.org/@aitube/clap/-/clap-0.0.10.tgz",
86
- "integrity": "sha512-pj855yWhUJ0QTJIaxMJhg0PwC3JaHsScJ5rQ8iqog7zDBWNNJR9MPBtFDYRuIs3vmUsGVxHwSlIBz5I1VjMF8w==",
87
  "dependencies": {
88
  "pure-uuid": "^1.8.1",
89
  "yaml": "^2.4.1"
@@ -119,9 +119,9 @@
119
  }
120
  },
121
  "node_modules/@anthropic-ai/sdk/node_modules/@types/node": {
122
- "version": "18.19.31",
123
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz",
124
- "integrity": "sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==",
125
  "dependencies": {
126
  "undici-types": "~5.26.4"
127
  }
@@ -227,9 +227,9 @@
227
  }
228
  },
229
  "node_modules/@floating-ui/dom": {
230
- "version": "1.6.4",
231
- "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.4.tgz",
232
- "integrity": "sha512-0G8R+zOvQsAG1pg2Q99P21jiqxqGBW1iRe/iXHsBRBxnpXKFI8QwbB4x5KmYLggNO5m34IQgOIu9SCRfR/WWiQ==",
233
  "dependencies": {
234
  "@floating-ui/core": "^1.0.0",
235
  "@floating-ui/utils": "^0.2.0"
@@ -2890,9 +2890,9 @@
2890
  }
2891
  },
2892
  "node_modules/caniuse-lite": {
2893
- "version": "1.0.30001615",
2894
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001615.tgz",
2895
- "integrity": "sha512-1IpazM5G3r38meiae0bHRnPhz+CBQ3ZLqbQMtrg+AsTPKAXgW38JNsXkyZ+v8waCsDmPq87lmfun5Q2AGysNEQ==",
2896
  "funding": [
2897
  {
2898
  "type": "opencollective",
@@ -3322,9 +3322,9 @@
3322
  }
3323
  },
3324
  "node_modules/cookies-next/node_modules/@types/node": {
3325
- "version": "16.18.96",
3326
- "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.96.tgz",
3327
- "integrity": "sha512-84iSqGXoO+Ha16j8pRZ/L90vDMKX04QTYMTfYeE1WrjWaZXuchBehGUZEpNgx7JnmlrIHdnABmpjrQjhCnNldQ=="
3328
  },
3329
  "node_modules/create-require": {
3330
  "version": "1.1.1",
@@ -3648,9 +3648,9 @@
3648
  "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
3649
  },
3650
  "node_modules/electron-to-chromium": {
3651
- "version": "1.4.756",
3652
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.756.tgz",
3653
- "integrity": "sha512-RJKZ9+vEBMeiPAvKNWyZjuYyUqMndcP1f335oHqn3BEQbs2NFtVrnK5+6Xg5wSM9TknNNpWghGDUCKGYF+xWXw=="
3654
  },
3655
  "node_modules/emoji-regex": {
3656
  "version": "9.2.2",
@@ -3666,9 +3666,9 @@
3666
  }
3667
  },
3668
  "node_modules/enhanced-resolve": {
3669
- "version": "5.16.0",
3670
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz",
3671
- "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==",
3672
  "dependencies": {
3673
  "graceful-fs": "^4.2.4",
3674
  "tapable": "^2.2.0"
@@ -4554,9 +4554,9 @@
4554
  }
4555
  },
4556
  "node_modules/get-tsconfig": {
4557
- "version": "4.7.3",
4558
- "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz",
4559
- "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==",
4560
  "dependencies": {
4561
  "resolve-pkg-maps": "^1.0.0"
4562
  },
@@ -4680,9 +4680,9 @@
4680
  }
4681
  },
4682
  "node_modules/groq-sdk/node_modules/@types/node": {
4683
- "version": "18.19.31",
4684
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz",
4685
- "integrity": "sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==",
4686
  "dependencies": {
4687
  "undici-types": "~5.26.4"
4688
  }
@@ -8298,9 +8298,9 @@
8298
  }
8299
  },
8300
  "node_modules/openai": {
8301
- "version": "4.40.2",
8302
- "resolved": "https://registry.npmjs.org/openai/-/openai-4.40.2.tgz",
8303
- "integrity": "sha512-r9AIaYQNHw8HGJpnny6Rcu/0moGUVqvpv0wTJfP0hKlk8ja5DVUMUCdPWEVfg7lxQMC+wIh+Qjp3onDIhVBemA==",
8304
  "dependencies": {
8305
  "@types/node": "^18.11.18",
8306
  "@types/node-fetch": "^2.6.4",
@@ -8316,9 +8316,9 @@
8316
  }
8317
  },
8318
  "node_modules/openai/node_modules/@types/node": {
8319
- "version": "18.19.31",
8320
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz",
8321
- "integrity": "sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==",
8322
  "dependencies": {
8323
  "undici-types": "~5.26.4"
8324
  }
 
8
  "name": "@jbilcke/comic-factory",
9
  "version": "1.2.0",
10
  "dependencies": {
11
+ "@aitube/clap": "0.0.14",
12
  "@anthropic-ai/sdk": "^0.19.1",
13
  "@huggingface/hub": "^0.14.2",
14
  "@huggingface/inference": "^2.6.1",
 
81
  }
82
  },
83
  "node_modules/@aitube/clap": {
84
+ "version": "0.0.14",
85
+ "resolved": "https://registry.npmjs.org/@aitube/clap/-/clap-0.0.14.tgz",
86
+ "integrity": "sha512-i4mq3YFecWVOTS/p5QaSQ0VJfurKXlyRc8FJMqKI6P/7rpf4vE4IL+jBKa4HPsYeNt85/KOt3MJKEFVtgiWGfQ==",
87
  "dependencies": {
88
  "pure-uuid": "^1.8.1",
89
  "yaml": "^2.4.1"
 
119
  }
120
  },
121
  "node_modules/@anthropic-ai/sdk/node_modules/@types/node": {
122
+ "version": "18.19.32",
123
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.32.tgz",
124
+ "integrity": "sha512-2bkg93YBSDKk8DLmmHnmj/Rwr18TLx7/n+I23BigFwgexUJoMHZOd8X1OFxuF/W3NN0S2W2E5sVabI5CPinNvA==",
125
  "dependencies": {
126
  "undici-types": "~5.26.4"
127
  }
 
227
  }
228
  },
229
  "node_modules/@floating-ui/dom": {
230
+ "version": "1.6.5",
231
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.5.tgz",
232
+ "integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==",
233
  "dependencies": {
234
  "@floating-ui/core": "^1.0.0",
235
  "@floating-ui/utils": "^0.2.0"
 
2890
  }
2891
  },
2892
  "node_modules/caniuse-lite": {
2893
+ "version": "1.0.30001616",
2894
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001616.tgz",
2895
+ "integrity": "sha512-RHVYKov7IcdNjVHJFNY/78RdG4oGVjbayxv8u5IO74Wv7Hlq4PnJE6mo/OjFijjVFNy5ijnCt6H3IIo4t+wfEw==",
2896
  "funding": [
2897
  {
2898
  "type": "opencollective",
 
3322
  }
3323
  },
3324
  "node_modules/cookies-next/node_modules/@types/node": {
3325
+ "version": "16.18.97",
3326
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.97.tgz",
3327
+ "integrity": "sha512-4muilE1Lbfn57unR+/nT9AFjWk0MtWi5muwCEJqnOvfRQDbSfLCUdN7vCIg8TYuaANfhLOV85ve+FNpiUsbSRg=="
3328
  },
3329
  "node_modules/create-require": {
3330
  "version": "1.1.1",
 
3648
  "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
3649
  },
3650
  "node_modules/electron-to-chromium": {
3651
+ "version": "1.4.757",
3652
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.757.tgz",
3653
+ "integrity": "sha512-jftDaCknYSSt/+KKeXzH3LX5E2CvRLm75P3Hj+J/dv3CL0qUYcOt13d5FN1NiL5IJbbhzHrb3BomeG2tkSlZmw=="
3654
  },
3655
  "node_modules/emoji-regex": {
3656
  "version": "9.2.2",
 
3666
  }
3667
  },
3668
  "node_modules/enhanced-resolve": {
3669
+ "version": "5.16.1",
3670
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz",
3671
+ "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==",
3672
  "dependencies": {
3673
  "graceful-fs": "^4.2.4",
3674
  "tapable": "^2.2.0"
 
4554
  }
4555
  },
4556
  "node_modules/get-tsconfig": {
4557
+ "version": "4.7.4",
4558
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.4.tgz",
4559
+ "integrity": "sha512-ofbkKj+0pjXjhejr007J/fLf+sW+8H7K5GCm+msC8q3IpvgjobpyPqSRFemNyIMxklC0zeJpi7VDFna19FacvQ==",
4560
  "dependencies": {
4561
  "resolve-pkg-maps": "^1.0.0"
4562
  },
 
4680
  }
4681
  },
4682
  "node_modules/groq-sdk/node_modules/@types/node": {
4683
+ "version": "18.19.32",
4684
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.32.tgz",
4685
+ "integrity": "sha512-2bkg93YBSDKk8DLmmHnmj/Rwr18TLx7/n+I23BigFwgexUJoMHZOd8X1OFxuF/W3NN0S2W2E5sVabI5CPinNvA==",
4686
  "dependencies": {
4687
  "undici-types": "~5.26.4"
4688
  }
 
8298
  }
8299
  },
8300
  "node_modules/openai": {
8301
+ "version": "4.42.0",
8302
+ "resolved": "https://registry.npmjs.org/openai/-/openai-4.42.0.tgz",
8303
+ "integrity": "sha512-xbiQQ2YNqdkE6cHqeWKa7lsAvdYfgp84XiNFOVkAMa6+9KpmOL4hCWCRR6e6I/clpaens/T9XeLVtyC5StXoRw==",
8304
  "dependencies": {
8305
  "@types/node": "^18.11.18",
8306
  "@types/node-fetch": "^2.6.4",
 
8316
  }
8317
  },
8318
  "node_modules/openai/node_modules/@types/node": {
8319
+ "version": "18.19.32",
8320
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.32.tgz",
8321
+ "integrity": "sha512-2bkg93YBSDKk8DLmmHnmj/Rwr18TLx7/n+I23BigFwgexUJoMHZOd8X1OFxuF/W3NN0S2W2E5sVabI5CPinNvA==",
8322
  "dependencies": {
8323
  "undici-types": "~5.26.4"
8324
  }
package.json CHANGED
@@ -9,7 +9,7 @@
9
  "lint": "next lint"
10
  },
11
  "dependencies": {
12
- "@aitube/clap": "0.0.10",
13
  "@anthropic-ai/sdk": "^0.19.1",
14
  "@huggingface/hub": "^0.14.2",
15
  "@huggingface/inference": "^2.6.1",
 
9
  "lint": "next lint"
10
  },
11
  "dependencies": {
12
+ "@aitube/clap": "0.0.14",
13
  "@anthropic-ai/sdk": "^0.19.1",
14
  "@huggingface/hub": "^0.14.2",
15
  "@huggingface/inference": "^2.6.1",
src/app/interface/bottom-bar/bottom-bar.tsx CHANGED
@@ -29,7 +29,7 @@ function BottomBar() {
29
 
30
  const preset = useStore(s => s.preset)
31
 
32
- const canSeeBetaFeatures = getParam<boolean>("beta", false)
33
 
34
  const allStatus = Object.values(panelGenerationStatus)
35
  const remainingImages = allStatus.reduce((acc, s) => (acc + (s ? 1 : 0)), 0)
 
29
 
30
  const preset = useStore(s => s.preset)
31
 
32
+ const canSeeBetaFeatures = true // getParam<boolean>("beta", false)
33
 
34
  const allStatus = Object.values(panelGenerationStatus)
35
  const remainingImages = allStatus.reduce((acc, s) => (acc + (s ? 1 : 0)), 0)
src/app/interface/grid/index.tsx CHANGED
@@ -6,7 +6,7 @@ import { cn } from "@/lib/utils"
6
  import { useStore } from "@/app/store"
7
 
8
  export function Grid({ children, className }: { children: ReactNode; className: string }) {
9
- const zoomLevel = useStore(state => state.zoomLevel)
10
 
11
  return (
12
  <div
 
6
  import { useStore } from "@/app/store"
7
 
8
  export function Grid({ children, className }: { children: ReactNode; className: string }) {
9
+ const zoomLevel = useStore(s => s.zoomLevel)
10
 
11
  return (
12
  <div
src/app/interface/page/index.tsx CHANGED
@@ -7,8 +7,8 @@ import { useStore } from "@/app/store"
7
  import { cn } from "@/lib/utils"
8
 
9
  export function Page({ page }: { page: number }) {
10
- const zoomLevel = useStore(state => state.zoomLevel)
11
- const layouts = useStore(state => state.layouts)
12
 
13
  // attention: here we use a fallback to layouts[0]
14
  // if no predetermined layout exists for this page number
@@ -39,9 +39,11 @@ export function Page({ page }: { page: number }) {
39
  // this was used to keep track of the page HTML element,
40
  // for use with a HTML-to-bitmap library
41
  // but the CSS layout wasn't followed properly and it depended on the zoom level
 
 
42
  /*
43
 
44
- const setPage = useStore(state => state.setPage)
45
  const pageRef = useRef<HTMLDivElement>(null)
46
 
47
  useEffect(() => {
 
7
  import { cn } from "@/lib/utils"
8
 
9
  export function Page({ page }: { page: number }) {
10
+ const zoomLevel = useStore(s => s.zoomLevel)
11
+ const layouts = useStore(s => s.layouts)
12
 
13
  // attention: here we use a fallback to layouts[0]
14
  // if no predetermined layout exists for this page number
 
39
  // this was used to keep track of the page HTML element,
40
  // for use with a HTML-to-bitmap library
41
  // but the CSS layout wasn't followed properly and it depended on the zoom level
42
+ //
43
+ // update: in the future if we want a good html to image convertion
44
  /*
45
 
46
+ const setPage = useStore(s => s.setPage)
47
  const pageRef = useRef<HTMLDivElement>(null)
48
 
49
  useEffect(() => {
src/app/interface/panel/bubble/index.tsx CHANGED
@@ -14,8 +14,8 @@ export function Bubble({ children, onChange }: {
14
  }) {
15
 
16
  const ref = useRef<HTMLDivElement>(null)
17
- const zoomLevel = useStore(state => state.zoomLevel)
18
- const showCaptions = useStore(state => state.showCaptions)
19
 
20
  const text = useRef(`${children || ''}`)
21
 
 
14
  }) {
15
 
16
  const ref = useRef<HTMLDivElement>(null)
17
+ const zoomLevel = useStore(s => s.zoomLevel)
18
+ const showCaptions = useStore(s => s.showCaptions)
19
 
20
  const text = useRef(`${children || ''}`)
21
 
src/app/interface/panel/index.tsx CHANGED
@@ -53,27 +53,27 @@ export function Panel({
53
 
54
  const [mouseOver, setMouseOver] = useState(false)
55
  const ref = useRef<HTMLImageElement>(null)
56
- const font = useStore(state => state.font)
57
- const preset = useStore(state => state.preset)
58
 
59
- const setGeneratingImages = useStore(state => state.setGeneratingImages)
60
 
61
- const panels = useStore(state => state.panels)
62
  const prompt = panels[panelIndex] || ""
63
 
64
- const setPanelPrompt = useStore(state => state.setPanelPrompt)
65
 
66
- const captions = useStore(state => state.captions)
67
  const caption = captions[panelIndex] || ""
68
- const setPanelCaption = useStore(state => state.setPanelCaption)
69
 
70
- const zoomLevel = useStore(state => state.zoomLevel)
71
 
72
- const addToUpscaleQueue = useStore(state => state.addToUpscaleQueue)
73
 
74
  const [_isPending, startTransition] = useTransition()
75
- const renderedScenes = useStore(state => state.renderedScenes)
76
- const setRendered = useStore(state => state.setRendered)
77
 
78
  const rendered = renderedScenes[panelIndex] || getInitialRenderedScene()
79
 
@@ -288,6 +288,8 @@ export function Panel({
288
 
289
  const renderedScene: RenderedScene | undefined = useStore.getState().renderedScenes[panelIndex]
290
 
 
 
291
  // I'm trying to find a rule to handle the case were we load a .clap file
292
  // I think we should trash all the Panel objects for this to work properly
293
  if (renderedScene && renderedScene.status === "pregenerated" && renderedScene.assetUrl) {
 
53
 
54
  const [mouseOver, setMouseOver] = useState(false)
55
  const ref = useRef<HTMLImageElement>(null)
56
+ const font = useStore(s => s.font)
57
+ const preset = useStore(s => s.preset)
58
 
59
+ const setGeneratingImages = useStore(s => s.setGeneratingImages)
60
 
61
+ const panels = useStore(s => s.panels)
62
  const prompt = panels[panelIndex] || ""
63
 
64
+ const setPanelPrompt = useStore(s => s.setPanelPrompt)
65
 
66
+ const captions = useStore(s => s.captions)
67
  const caption = captions[panelIndex] || ""
68
+ const setPanelCaption = useStore(s => s.setPanelCaption)
69
 
70
+ const zoomLevel = useStore(s => s.zoomLevel)
71
 
72
+ const addToUpscaleQueue = useStore(s => s.addToUpscaleQueue)
73
 
74
  const [_isPending, startTransition] = useTransition()
75
+ const renderedScenes = useStore(s => s.renderedScenes)
76
+ const setRendered = useStore(s => s.setRendered)
77
 
78
  const rendered = renderedScenes[panelIndex] || getInitialRenderedScene()
79
 
 
288
 
289
  const renderedScene: RenderedScene | undefined = useStore.getState().renderedScenes[panelIndex]
290
 
291
+ // console.log("renderedScene:", renderedScene)
292
+
293
  // I'm trying to find a rule to handle the case were we load a .clap file
294
  // I think we should trash all the Panel objects for this to work properly
295
  if (renderedScene && renderedScene.status === "pregenerated" && renderedScene.assetUrl) {
src/app/interface/select-global-layout/index.tsx ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useEffect, useState } from "react"
4
+ import { useSearchParams } from "next/navigation"
5
+
6
+ import { useStore } from "@/app/store"
7
+ import { LayoutName, defaultLayout, nonRandomLayouts } from "@/app/layouts"
8
+ import { useIsBusy } from "@/lib/useIsBusy"
9
+
10
+ import { SelectLayout } from "../select-layout"
11
+
12
+ export function SelectGlobalLayout() {
13
+ const searchParams = useSearchParams()
14
+
15
+ const requestedLayout = (searchParams?.get('layout') as LayoutName) || defaultLayout
16
+
17
+ const layout = useStore(s => s.layout)
18
+ const setLayout = useStore(s => s.setLayout)
19
+
20
+ const isBusy = useIsBusy()
21
+
22
+ const [draftLayout, setDraftLayout] = useState<LayoutName>(requestedLayout)
23
+
24
+ useEffect(() => {
25
+ const layoutChanged = draftLayout !== layout
26
+ if (layoutChanged && !isBusy) {
27
+ setLayout(draftLayout)
28
+ }
29
+ }, [layout, draftLayout, isBusy])
30
+
31
+ return (
32
+ <SelectLayout
33
+ defaultValue={defaultLayout}
34
+ onLayoutChange={setDraftLayout}
35
+ disabled={isBusy}
36
+ layouts={nonRandomLayouts}
37
+ />
38
+ )
39
+ }
src/app/interface/select-layout/index.tsx ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import Image from "next/image"
4
+
5
+ import {
6
+ Select,
7
+ SelectContent,
8
+ SelectItem,
9
+ SelectTrigger,
10
+ SelectValue,
11
+ } from "@/components/ui/select"
12
+ import { LayoutName, allLayoutLabels, defaultLayout, layoutIcons } from "@/app/layouts"
13
+
14
+ export function SelectLayout({
15
+ defaultValue = defaultLayout,
16
+ onLayoutChange,
17
+ disabled = false,
18
+ layouts = [],
19
+ }: {
20
+ defaultValue?: string | undefined
21
+ onLayoutChange?: ((name: LayoutName) => void)
22
+ disabled?: boolean
23
+ layouts: string[]
24
+ }) {
25
+ return (
26
+ <Select
27
+ defaultValue={defaultValue}
28
+ onValueChange={(name) => { onLayoutChange?.(name as LayoutName) }}
29
+ disabled={disabled}
30
+ >
31
+ <SelectTrigger className="flex-grow bg-gray-100 text-gray-700 dark:bg-gray-100 dark:text-gray-700">
32
+ <SelectValue className="text-2xs md:text-sm" placeholder="Layout" />
33
+ </SelectTrigger>
34
+ <SelectContent>
35
+ {layouts.map(key =>
36
+ <SelectItem key={key} value={key} className="w-full">
37
+ <div className="space-x-6 flex flex-row items-center justify-between">
38
+ <div className="flex">{
39
+ (allLayoutLabels as any)[key]
40
+ }</div>
41
+
42
+ {(layoutIcons as any)[key]
43
+ ? <Image
44
+ className="rounded-sm opacity-75"
45
+ src={(layoutIcons as any)[key]}
46
+ width={20}
47
+ height={18}
48
+ alt={key}
49
+ /> : null}
50
+ </div>
51
+ </SelectItem>
52
+ )}
53
+ </SelectContent>
54
+ </Select>
55
+ )
56
+ }
src/app/interface/share/index.tsx CHANGED
@@ -6,9 +6,9 @@ import { useState } from "react"
6
 
7
  export function Share() {
8
  const [isOpen, setOpen] = useState(false)
9
- const preset = useStore(state => state.preset)
10
- const prompt = useStore(state => state.prompt)
11
- const panelGenerationStatus = useStore(state => state.panelGenerationStatus)
12
  const allStatus = Object.values(panelGenerationStatus)
13
  const remainingImages = allStatus.reduce((acc, s) => (acc + (s ? 1 : 0)), 0)
14
 
 
6
 
7
  export function Share() {
8
  const [isOpen, setOpen] = useState(false)
9
+ const preset = useStore(s => s.preset)
10
+ const prompt = useStore(s => s.prompt)
11
+ const panelGenerationStatus = useStore(s => s.panelGenerationStatus)
12
  const allStatus = Object.values(panelGenerationStatus)
13
  const remainingImages = allStatus.reduce((acc, s) => (acc + (s ? 1 : 0)), 0)
14
 
src/app/interface/top-menu/index.tsx CHANGED
@@ -2,7 +2,6 @@
2
 
3
  import { useEffect, useState } from "react"
4
  import { useSearchParams } from "next/navigation"
5
- import Image from "next/image"
6
  import { StaticImageData } from "next/image"
7
  import { useLocalStorage } from "usehooks-ts"
8
 
@@ -20,25 +19,15 @@ import { Input } from "@/components/ui/input"
20
  import { PresetName, defaultPreset, nonRandomPresets, presets } from "@/app/engine/presets"
21
  import { useStore } from "@/app/store"
22
  import { Button } from "@/components/ui/button"
23
- import { LayoutName, allLayoutLabels, defaultLayout, nonRandomLayouts } from "@/app/layouts"
24
  import { Switch } from "@/components/ui/switch"
25
  import { useOAuth } from "@/lib/useOAuth"
 
26
 
27
- import layoutPreview0 from "../../../../public/layouts/layout0.jpg"
28
- import layoutPreview1 from "../../../../public/layouts/layout1.jpg"
29
- import layoutPreview2 from "../../../../public/layouts/layout2.jpg"
30
- import layoutPreview3 from "../../../../public/layouts/layout3.jpg"
31
  import { localStorageKeys } from "../settings-dialog/localStorageKeys"
32
  import { defaultSettings } from "../settings-dialog/defaultSettings"
33
  import { AuthWall } from "../auth-wall"
34
-
35
- const layoutIcons: Partial<Record<LayoutName, StaticImageData>> = {
36
- Layout0: layoutPreview0,
37
- Layout1: layoutPreview1,
38
- Layout2: layoutPreview2,
39
- Layout3: layoutPreview3,
40
- Layout4: undefined,
41
- }
42
 
43
  export function TopMenu() {
44
  const searchParams = useSearchParams()
@@ -49,25 +38,22 @@ export function TopMenu() {
49
  const requestedStoryPrompt = (searchParams?.get('storyPrompt') as string) || ""
50
  const requestedLayout = (searchParams?.get('layout') as LayoutName) || defaultLayout
51
 
52
- // const font = useStore(state => state.font)
53
- // const setFont = useStore(state => state.setFont)
54
- const preset = useStore(state => state.preset)
55
- const prompt = useStore(state => state.prompt)
56
- const layout = useStore(state => state.layout)
57
- const setLayout = useStore(state => state.setLayout)
58
-
59
- const setShowCaptions = useStore(state => state.setShowCaptions)
60
- const showCaptions = useStore(state => state.showCaptions)
61
 
62
- const currentNbPages = useStore(state => state.currentNbPages)
63
- const setCurrentNbPages = useStore(state => state.setCurrentNbPages)
64
 
65
- const generate = useStore(state => state.generate)
 
66
 
67
- const isGeneratingStory = useStore(state => state.isGeneratingStory)
68
- const atLeastOnePanelIsBusy = useStore(state => state.atLeastOnePanelIsBusy)
69
- const isBusy = isGeneratingStory || atLeastOnePanelIsBusy
70
 
 
71
 
72
  const [lastDraftPromptA, setLastDraftPromptA] = useLocalStorage<string>(
73
  "AI_COMIC_FACTORY_LAST_DRAFT_PROMPT_A",
@@ -167,36 +153,12 @@ export function TopMenu() {
167
 
168
  {/* <Label className="flex text-2xs md:text-sm md:w-24">Style:</Label> */}
169
 
170
- <Select
171
  defaultValue={defaultLayout}
172
- onValueChange={(value) => { setDraftLayout(value as LayoutName) }}
173
  disabled={isBusy}
174
- >
175
- <SelectTrigger className="flex-grow bg-gray-100 text-gray-700 dark:bg-gray-100 dark:text-gray-700">
176
- <SelectValue className="text-2xs md:text-sm" placeholder="Layout" />
177
- </SelectTrigger>
178
- <SelectContent>
179
- {nonRandomLayouts.map(key =>
180
- <SelectItem key={key} value={key} className="w-full">
181
- <div className="space-x-6 flex flex-row items-center justify-between">
182
- <div className="flex">{
183
- (allLayoutLabels as any)[key]
184
- }</div>
185
-
186
- {(layoutIcons as any)[key]
187
- ? <Image
188
- className="rounded-sm opacity-75"
189
- src={(layoutIcons as any)[key]}
190
- width={20}
191
- height={18}
192
- alt={key}
193
- /> : null}
194
-
195
- </div>
196
- </SelectItem>
197
- )}
198
- </SelectContent>
199
- </Select>
200
  </div>
201
  <div className="flex flex-row items-center space-x-3">
202
  <Switch
 
2
 
3
  import { useEffect, useState } from "react"
4
  import { useSearchParams } from "next/navigation"
 
5
  import { StaticImageData } from "next/image"
6
  import { useLocalStorage } from "usehooks-ts"
7
 
 
19
  import { PresetName, defaultPreset, nonRandomPresets, presets } from "@/app/engine/presets"
20
  import { useStore } from "@/app/store"
21
  import { Button } from "@/components/ui/button"
22
+ import { LayoutName, defaultLayout, nonRandomLayouts } from "@/app/layouts"
23
  import { Switch } from "@/components/ui/switch"
24
  import { useOAuth } from "@/lib/useOAuth"
25
+ import { useIsBusy } from "@/lib/useIsBusy"
26
 
 
 
 
 
27
  import { localStorageKeys } from "../settings-dialog/localStorageKeys"
28
  import { defaultSettings } from "../settings-dialog/defaultSettings"
29
  import { AuthWall } from "../auth-wall"
30
+ import { SelectLayout } from "../select-layout"
 
 
 
 
 
 
 
31
 
32
  export function TopMenu() {
33
  const searchParams = useSearchParams()
 
38
  const requestedStoryPrompt = (searchParams?.get('storyPrompt') as string) || ""
39
  const requestedLayout = (searchParams?.get('layout') as LayoutName) || defaultLayout
40
 
41
+ // const font = useStore(s => s.font)
42
+ // const setFont = useStore(s => s.setFont)
43
+ const preset = useStore(s => s.preset)
44
+ const prompt = useStore(s => s.prompt)
45
+ const layout = useStore(s => s.layout)
46
+ const setLayout = useStore(s => s.setLayout)
 
 
 
47
 
48
+ const setShowCaptions = useStore(s => s.setShowCaptions)
49
+ const showCaptions = useStore(s => s.showCaptions)
50
 
51
+ const currentNbPages = useStore(s => s.currentNbPages)
52
+ const setCurrentNbPages = useStore(s => s.setCurrentNbPages)
53
 
54
+ const generate = useStore(s => s.generate)
 
 
55
 
56
+ const isBusy = useIsBusy()
57
 
58
  const [lastDraftPromptA, setLastDraftPromptA] = useLocalStorage<string>(
59
  "AI_COMIC_FACTORY_LAST_DRAFT_PROMPT_A",
 
153
 
154
  {/* <Label className="flex text-2xs md:text-sm md:w-24">Style:</Label> */}
155
 
156
+ <SelectLayout
157
  defaultValue={defaultLayout}
158
+ onLayoutChange={setDraftLayout}
159
  disabled={isBusy}
160
+ layouts={nonRandomLayouts}
161
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  </div>
163
  <div className="flex flex-row items-center space-x-3">
164
  <Switch
src/app/layouts/index.tsx CHANGED
@@ -1,10 +1,17 @@
1
  "use client"
2
 
 
 
3
  import { Panel } from "@/app/interface/panel"
4
  import { pick } from "@/lib/pick"
5
  import { Grid } from "@/app/interface/grid"
6
  import { LayoutProps } from "@/types"
7
 
 
 
 
 
 
8
  export function Layout0({ page, nbPanels }: LayoutProps) {
9
  return (
10
  <Grid className="grid-cols-2 grid-rows-2">
@@ -440,3 +447,11 @@ export const getRandomLayoutName = (): LayoutName => {
440
  export function getRandomLayoutNames(): LayoutName[] {
441
  return nonRandomLayouts.sort(() => Math.random() - 0.5) as LayoutName[]
442
  }
 
 
 
 
 
 
 
 
 
1
  "use client"
2
 
3
+ import { StaticImageData } from "next/image"
4
+
5
  import { Panel } from "@/app/interface/panel"
6
  import { pick } from "@/lib/pick"
7
  import { Grid } from "@/app/interface/grid"
8
  import { LayoutProps } from "@/types"
9
 
10
+ import layoutPreview0 from "../../../public/layouts/layout0.jpg"
11
+ import layoutPreview1 from "../../../public/layouts/layout1.jpg"
12
+ import layoutPreview2 from "../../../public/layouts/layout2.jpg"
13
+ import layoutPreview3 from "../../../public/layouts/layout3.jpg"
14
+
15
  export function Layout0({ page, nbPanels }: LayoutProps) {
16
  return (
17
  <Grid className="grid-cols-2 grid-rows-2">
 
447
  export function getRandomLayoutNames(): LayoutName[] {
448
  return nonRandomLayouts.sort(() => Math.random() - 0.5) as LayoutName[]
449
  }
450
+
451
+ export const layoutIcons: Partial<Record<LayoutName, StaticImageData>> = {
452
+ Layout0: layoutPreview0,
453
+ Layout1: layoutPreview1,
454
+ Layout2: layoutPreview2,
455
+ Layout3: layoutPreview3,
456
+ Layout4: undefined,
457
+ }
src/app/layouts/settings.tsx ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ClapMediaOrientation } from "@aitube/clap"
2
+
3
+ import { LayoutName } from "."
4
+
5
+ export type LayoutSettings = {
6
+ panel: number
7
+ orientation: ClapMediaOrientation
8
+ width: number
9
+ height: number
10
+ }
11
+
12
+ export const layouts: Record<LayoutName, LayoutSettings[]> = {
13
+ random: [],
14
+ Layout0: [
15
+ { panel: 0, orientation: ClapMediaOrientation.SQUARE, width: 1024, height: 1024 },
16
+ { panel: 1, orientation: ClapMediaOrientation.SQUARE, width: 1024, height: 1024 },
17
+ { panel: 2, orientation: ClapMediaOrientation.SQUARE, width: 1024, height: 1024 },
18
+ { panel: 3, orientation: ClapMediaOrientation.SQUARE, width: 1024, height: 1024 },
19
+ ],
20
+ Layout1: [
21
+ { panel: 0, orientation: ClapMediaOrientation.LANDSCAPE, width: 1024, height: 768 },
22
+ { panel: 1, orientation: ClapMediaOrientation.PORTRAIT, width: 768, height: 1024 },
23
+ { panel: 2, orientation: ClapMediaOrientation.PORTRAIT, width: 768, height: 1024 },
24
+ { panel: 3, orientation: ClapMediaOrientation.LANDSCAPE, width: 1024, height: 768 },
25
+ ],
26
+ Layout2: [
27
+ { panel: 0, orientation: ClapMediaOrientation.PORTRAIT, width: 768, height: 1024 },
28
+ { panel: 1, orientation: ClapMediaOrientation.PORTRAIT, width: 768, height: 1024 },
29
+ { panel: 2, orientation: ClapMediaOrientation.PORTRAIT, width: 512, height: 1024 },
30
+ { panel: 3, orientation: ClapMediaOrientation.LANDSCAPE, width: 1024, height: 768 },
31
+ ],
32
+ Layout3: [
33
+ { panel: 0, orientation: ClapMediaOrientation.LANDSCAPE, width: 1024, height: 768 },
34
+ { panel: 1, orientation: ClapMediaOrientation.PORTRAIT, width: 768, height: 1024 },
35
+ { panel: 2, orientation: ClapMediaOrientation.PORTRAIT, width: 768, height: 1024 },
36
+ { panel: 3, orientation: ClapMediaOrientation.LANDSCAPE, width: 1024, height: 768 },
37
+ ],
38
+ Layout4: [
39
+ { panel: 0, orientation: ClapMediaOrientation.PORTRAIT, width: 512, height: 1024 },
40
+ { panel: 1, orientation: ClapMediaOrientation.LANDSCAPE, width: 1024, height: 768 },
41
+ { panel: 2, orientation: ClapMediaOrientation.PORTRAIT, width: 768, height: 1024 },
42
+ { panel: 3, orientation: ClapMediaOrientation.LANDSCAPE, width: 1024, height: 512 },
43
+ ],
44
+ }
45
+ /*
46
+ Layout5: [
47
+ { panel: 0, orientation: ClapMediaOrientation.SQUARE, width: 1024, height: 1024 },
48
+ { panel: 1, orientation: ClapMediaOrientation.SQUARE, width: 1024, height: 1024 },
49
+ { panel: 2, orientation: ClapMediaOrientation.SQUARE, width: 1024, height: 1024 },
50
+ { panel: 3, orientation: ClapMediaOrientation.SQUARE, width: 1024, height: 1024 },
51
+ ]
52
+ */
src/app/main.tsx CHANGED
@@ -63,7 +63,7 @@ export default function Main() {
63
  )
64
 
65
  const numberOfPanels = Object.keys(panels).length
66
- const panelGenerationStatus = useStore(state => state.panelGenerationStatus)
67
  const allStatus = Object.values(panelGenerationStatus)
68
  const numberOfPendingGenerations = allStatus.reduce((acc, s) => (acc + (s ? 1 : 0)), 0)
69
 
@@ -121,9 +121,12 @@ export default function Main() {
121
  // console.log(`main.tsx: asked to re-generate!!`)
122
  if (!prompt) { return }
123
 
 
124
  // a quick and dirty hack to skip prompt regeneration,
125
  // unless the prompt has really changed
126
- if (prompt === useStore.getState().currentClap?.meta.description) {
 
 
127
  console.log(`loading a pre-generated comic, so skipping prompt regeneration..`)
128
  return
129
  }
 
63
  )
64
 
65
  const numberOfPanels = Object.keys(panels).length
66
+ const panelGenerationStatus = useStore(s => s.panelGenerationStatus)
67
  const allStatus = Object.values(panelGenerationStatus)
68
  const numberOfPendingGenerations = allStatus.reduce((acc, s) => (acc + (s ? 1 : 0)), 0)
69
 
 
121
  // console.log(`main.tsx: asked to re-generate!!`)
122
  if (!prompt) { return }
123
 
124
+
125
  // a quick and dirty hack to skip prompt regeneration,
126
  // unless the prompt has really changed
127
+ if (
128
+ prompt === useStore.getState().currentClap?.meta.description
129
+ ) {
130
  console.log(`loading a pre-generated comic, so skipping prompt regeneration..`)
131
  return
132
  }
src/app/queries/getDynamicConfig.ts CHANGED
@@ -15,7 +15,10 @@ export async function getDynamicConfig(): Promise<DynamicConfig> {
15
  nbPanelsPerPage,
16
  nbTotalPanelsToGenerate,
17
  oauthClientId: getValidString(process.env.HUGGING_FACE_OAUTH_CLIENT_ID, ""),
 
 
18
  oauthRedirectUrl: getValidString(process.env.HUGGING_FACE_OAUTH_REDIRECT_URL, ""),
 
19
  oauthScopes: "openid profile inference-api",
20
  enableHuggingFaceOAuth: getValidBoolean(process.env.ENABLE_HUGGING_FACE_OAUTH, false),
21
  enableHuggingFaceOAuthWall: getValidBoolean(process.env.ENABLE_HUGGING_FACE_OAUTH_WALL, false),
 
15
  nbPanelsPerPage,
16
  nbTotalPanelsToGenerate,
17
  oauthClientId: getValidString(process.env.HUGGING_FACE_OAUTH_CLIENT_ID, ""),
18
+
19
+ // this doesn't work (conceptually)
20
  oauthRedirectUrl: getValidString(process.env.HUGGING_FACE_OAUTH_REDIRECT_URL, ""),
21
+
22
  oauthScopes: "openid profile inference-api",
23
  enableHuggingFaceOAuth: getValidBoolean(process.env.ENABLE_HUGGING_FACE_OAUTH, false),
24
  enableHuggingFaceOAuthWall: getValidBoolean(process.env.ENABLE_HUGGING_FACE_OAUTH_WALL, false),
src/app/store/index.ts CHANGED
@@ -10,6 +10,8 @@ import { getParam } from "@/lib/getParam"
10
 
11
  import { LayoutName, defaultLayout, getRandomLayoutName } from "../layouts"
12
  import { putTextInInput } from "@/lib/putTextInInput"
 
 
13
 
14
  export const useStore = create<{
15
  prompt: string
@@ -54,7 +56,7 @@ export const useStore = create<{
54
  setPanels: (panels: string[]) => void
55
  setPanelPrompt: (newPrompt: string, index: number) => void
56
  setShowCaptions: (showCaptions: boolean) => void
57
- setLayout: (layout: LayoutName) => void
58
  setLayouts: (layouts: LayoutName[]) => void
59
  setCaptions: (captions: string[]) => void
60
  setPanelCaption: (newCaption: string, index: number) => void
@@ -77,6 +79,8 @@ export const useStore = create<{
77
  convertClapToComic: (clap: ClapProject) => Promise<{
78
  currentNbPanels: number
79
  prompt: string
 
 
80
  storyPrompt: string
81
  stylePrompt: string
82
  panels: string[]
@@ -298,15 +302,19 @@ export const useStore = create<{
298
  ))
299
  })
300
  },
301
- setLayout: (layoutName: LayoutName) => {
302
- const { maxNbPages, currentNbPanelsPerPage } = get()
303
 
304
- const layouts: LayoutName[] = []
305
  for (let i = 0; i < maxNbPages; i++) {
306
- layouts.push(
307
- layoutName === "random"
308
- ? getRandomLayoutName()
309
- : layoutName)
 
 
 
 
 
310
  }
311
 
312
  set({
@@ -511,6 +519,8 @@ export const useStore = create<{
511
  convertClapToComic: async (clap: ClapProject): Promise<{
512
  currentNbPanels: number
513
  prompt: string
 
 
514
  storyPrompt: string
515
  stylePrompt: string
516
  panels: string[]
@@ -575,9 +585,12 @@ export const useStore = create<{
575
  captions.push(ui.prompt)
576
  })
577
 
578
- return {
 
579
  currentNbPanels: shots.length,
580
  prompt,
 
 
581
  storyPrompt,
582
  stylePrompt,
583
  panels,
@@ -595,6 +608,8 @@ export const useStore = create<{
595
  const {
596
  currentNbPanels,
597
  prompt,
 
 
598
  storyPrompt,
599
  stylePrompt,
600
  panels,
@@ -610,6 +625,8 @@ export const useStore = create<{
610
  currentClap,
611
  currentNbPanels,
612
  prompt,
 
 
613
  panels,
614
  renderedScenes,
615
  captions,
 
10
 
11
  import { LayoutName, defaultLayout, getRandomLayoutName } from "../layouts"
12
  import { putTextInInput } from "@/lib/putTextInInput"
13
+ import { parsePresetFromPrompts } from "@/lib/parsePresetFromPrompts"
14
+ import { parseLayoutFromStoryboards } from "@/lib/parseLayoutFromStoryboards"
15
 
16
  export const useStore = create<{
17
  prompt: string
 
56
  setPanels: (panels: string[]) => void
57
  setPanelPrompt: (newPrompt: string, index: number) => void
58
  setShowCaptions: (showCaptions: boolean) => void
59
+ setLayout: (layout: LayoutName, index?: number) => void
60
  setLayouts: (layouts: LayoutName[]) => void
61
  setCaptions: (captions: string[]) => void
62
  setPanelCaption: (newCaption: string, index: number) => void
 
79
  convertClapToComic: (clap: ClapProject) => Promise<{
80
  currentNbPanels: number
81
  prompt: string
82
+ preset: Preset
83
+ layout: LayoutName
84
  storyPrompt: string
85
  stylePrompt: string
86
  panels: string[]
 
302
  ))
303
  })
304
  },
305
+ setLayout: (layoutName: LayoutName, index?: number) => {
306
+ const { maxNbPages, currentNbPanelsPerPage, layouts } = get()
307
 
 
308
  for (let i = 0; i < maxNbPages; i++) {
309
+ let name = layoutName === "random" ? getRandomLayoutName() : layoutName
310
+
311
+ if (typeof index === "number" && !isNaN(index) && isFinite(index)) {
312
+ if (i === index) {
313
+ layouts[i] = name
314
+ }
315
+ } else {
316
+ layouts[i] = name
317
+ }
318
  }
319
 
320
  set({
 
519
  convertClapToComic: async (clap: ClapProject): Promise<{
520
  currentNbPanels: number
521
  prompt: string
522
+ preset: Preset
523
+ layout: LayoutName
524
  storyPrompt: string
525
  stylePrompt: string
526
  panels: string[]
 
585
  captions.push(ui.prompt)
586
  })
587
 
588
+
589
+ return {
590
  currentNbPanels: shots.length,
591
  prompt,
592
+ preset: parsePresetFromPrompts(panels),
593
+ layout: await parseLayoutFromStoryboards(shots.map(x => x.storyboard)),
594
  storyPrompt,
595
  stylePrompt,
596
  panels,
 
608
  const {
609
  currentNbPanels,
610
  prompt,
611
+ preset,
612
+ layout,
613
  storyPrompt,
614
  stylePrompt,
615
  panels,
 
625
  currentClap,
626
  currentNbPanels,
627
  prompt,
628
+ preset,
629
+ // layout,
630
  panels,
631
  renderedScenes,
632
  captions,
src/lib/getImageDimension.ts CHANGED
@@ -1,16 +1,26 @@
 
 
1
  export interface ImageDimension {
2
  width: number
3
  height: number
 
4
  }
5
 
6
  export async function getImageDimension(src: string): Promise<ImageDimension> {
7
  if (!src) {
8
- return { width: 0, height: 0 }
9
  }
10
  const img = new Image()
11
  img.src = src
12
  await img.decode()
13
  const width = img.width
14
  const height = img.height
15
- return { width, height }
 
 
 
 
 
 
 
16
  }
 
1
+ import { ClapMediaOrientation } from "@aitube/clap"
2
+
3
  export interface ImageDimension {
4
  width: number
5
  height: number
6
+ orientation: ClapMediaOrientation
7
  }
8
 
9
  export async function getImageDimension(src: string): Promise<ImageDimension> {
10
  if (!src) {
11
+ return { width: 0, height: 0, orientation: ClapMediaOrientation.SQUARE }
12
  }
13
  const img = new Image()
14
  img.src = src
15
  await img.decode()
16
  const width = img.width
17
  const height = img.height
18
+
19
+ let orientation = ClapMediaOrientation.SQUARE
20
+ if (width > height) {
21
+ orientation = ClapMediaOrientation.LANDSCAPE
22
+ } else if (width < height) {
23
+ orientation = ClapMediaOrientation.PORTRAIT
24
+ }
25
+ return { width, height, orientation }
26
  }
src/lib/getOAuthRedirectUrl.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export function getOAuthRedirectUrl(): string {
2
+ if (typeof window === "undefined") {
3
+ return "http://localhost:3000"
4
+ }
5
+
6
+ return (
7
+ window.location.hostname === "aicomicfactory.app" ? "https://aicomicfactory.app"
8
+ : window.location.hostname === "jbilcke-hf-ai-comic-factory.hf.space" ? "https://jbilcke-hf-ai-comic-factory.hf.space"
9
+ : "http://localhost:3000"
10
+ )
11
+ }
src/lib/isValidNumber.ts ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ export function isValidNumber(input?: any) {
2
+ return typeof input === "number" && !isNaN(input) && isFinite(input)
3
+ }
src/lib/parseLayoutFromStoryboards.ts ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ClapSegment } from "@aitube/clap"
2
+
3
+ import { LayoutName } from "@/app/layouts"
4
+ import { layouts } from "@/app/layouts/settings"
5
+ import { getImageDimension } from "./getImageDimension"
6
+
7
+ export async function parseLayoutFromStoryboards(storyboards: ClapSegment[]): Promise<LayoutName> {
8
+
9
+ let bestCandidate: LayoutName = "Layout0"
10
+
11
+ for (const [layoutName, layoutPanels] of Object.entries(layouts)) {
12
+
13
+ let nbMatchingStoryboards = 0
14
+ let i = 0
15
+
16
+ for (const { panel, orientation, width, height } of layoutPanels) {
17
+
18
+ const storyboard = storyboards[i]
19
+
20
+ if (!storyboard) { continue }
21
+ if (!storyboard?.assetUrl) { continue }
22
+
23
+ const imgDimension = await getImageDimension(storyboard.assetUrl)
24
+
25
+ if (orientation === imgDimension.orientation) {
26
+ nbMatchingStoryboards++
27
+ }
28
+
29
+ i++
30
+ }
31
+
32
+ if (nbMatchingStoryboards === 4) {
33
+ bestCandidate = layoutName as LayoutName
34
+ }
35
+ }
36
+
37
+ return bestCandidate
38
+ }
src/lib/parsePresetFromPrompts.ts ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { presets, PresetName, Preset } from "@/app/engine/presets"
2
+
3
+ /**
4
+ * Try to guess the preset from a list of prompts
5
+ *
6
+ * @param prompts
7
+ * @returns
8
+ */
9
+ export function parsePresetFromPrompts(prompts: string[]): Preset {
10
+
11
+ const presetToCount: Record<PresetName, number> = {}
12
+ const chunkToPresets: Record<string, PresetName[]> = {}
13
+
14
+ Object.values(presets).forEach(preset => {
15
+ preset.imagePrompt("").map(x => x.trim().toLowerCase()).forEach(chunk => {
16
+ chunkToPresets[chunk] = Array.isArray(chunkToPresets[chunk]) ? chunkToPresets[chunk] : []
17
+ if (!chunkToPresets[chunk].includes(preset.id)) {
18
+ chunkToPresets[chunk].push(preset.id)
19
+ }
20
+ })
21
+ })
22
+
23
+ prompts.forEach(prompt => {
24
+ prompt.split(",").map(x => x.trim().toLowerCase()).forEach(chunk => {
25
+ if (Array.isArray(chunkToPresets[chunk])) {
26
+ const presetNames = chunkToPresets[chunk] as PresetName[]
27
+ presetNames.forEach(preset => {
28
+ presetToCount[preset] = (presetToCount[preset] || 0) + 1
29
+ })
30
+ }
31
+ })
32
+ })
33
+
34
+ const bestMatch: PresetName | undefined = Object.entries(presetToCount).sort((a, b) => b[1] - a[1]).map(x => x[0]).at(0) as (PresetName | undefined)
35
+
36
+ return presets[bestMatch || "neutral"] || presets.neutral
37
+ }
src/lib/useImageDimension.ts CHANGED
@@ -1,11 +1,13 @@
1
  import { useEffect, useState } from "react"
2
 
3
  import { ImageDimension, getImageDimension } from "./getImageDimension"
 
4
 
5
  export function useImageDimension(src: string) {
6
  const [dimension, setDimension] = useState<ImageDimension>({
7
  width: 0,
8
  height: 0,
 
9
  })
10
 
11
  useEffect(() => {
 
1
  import { useEffect, useState } from "react"
2
 
3
  import { ImageDimension, getImageDimension } from "./getImageDimension"
4
+ import { ClapMediaOrientation } from "@aitube/clap"
5
 
6
  export function useImageDimension(src: string) {
7
  const [dimension, setDimension] = useState<ImageDimension>({
8
  width: 0,
9
  height: 0,
10
+ orientation: ClapMediaOrientation.SQUARE
11
  })
12
 
13
  useEffect(() => {
src/lib/useIsBusy.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import { useStore } from "@/app/store"
2
+
3
+ export function useIsBusy() {
4
+ const isGeneratingStory = useStore(s => s.isGeneratingStory)
5
+ const atLeastOnePanelIsBusy = useStore(s => s.atLeastOnePanelIsBusy)
6
+ const isBusy = isGeneratingStory || atLeastOnePanelIsBusy
7
+
8
+ return isBusy
9
+ }
src/lib/useOAuth.ts CHANGED
@@ -9,6 +9,7 @@ import { getValidOAuth } from "./getValidOAuth"
9
  import { useDynamicConfig } from "./useDynamicConfig"
10
  import { useLocalStorage } from "usehooks-ts"
11
  import { useShouldDisplayLoginWall } from "./useShouldDisplayLoginWall"
 
12
 
13
  export function useOAuth({
14
  debug = false
@@ -32,7 +33,10 @@ export function useOAuth({
32
  const [oauthResult, setOAuthResult] = usePersistedOAuth()
33
 
34
  const clientId = config.oauthClientId
35
- const redirectUrl = config.oauthRedirectUrl
 
 
 
36
  const scopes = config.oauthScopes
37
  const enableOAuth = config.enableHuggingFaceOAuth
38
 
 
9
  import { useDynamicConfig } from "./useDynamicConfig"
10
  import { useLocalStorage } from "usehooks-ts"
11
  import { useShouldDisplayLoginWall } from "./useShouldDisplayLoginWall"
12
+ import { getOAuthRedirectUrl } from "./getOAuthRedirectUrl"
13
 
14
  export function useOAuth({
15
  debug = false
 
33
  const [oauthResult, setOAuthResult] = usePersistedOAuth()
34
 
35
  const clientId = config.oauthClientId
36
+
37
+ // const redirectUrl = config.oauthRedirectUrl
38
+ const redirectUrl = getOAuthRedirectUrl()
39
+
40
  const scopes = config.oauthScopes
41
  const enableOAuth = config.enableHuggingFaceOAuth
42