Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Commit
β’
3b81d2d
1
Parent(s):
9ee3c2f
update for oauth
Browse files- .env +2 -0
- package-lock.json +34 -34
- package.json +1 -1
- src/app/interface/bottom-bar/bottom-bar.tsx +1 -1
- src/app/interface/grid/index.tsx +1 -1
- src/app/interface/page/index.tsx +5 -3
- src/app/interface/panel/bubble/index.tsx +2 -2
- src/app/interface/panel/index.tsx +13 -11
- src/app/interface/select-global-layout/index.tsx +39 -0
- src/app/interface/select-layout/index.tsx +56 -0
- src/app/interface/share/index.tsx +3 -3
- src/app/interface/top-menu/index.tsx +19 -57
- src/app/layouts/index.tsx +15 -0
- src/app/layouts/settings.tsx +52 -0
- src/app/main.tsx +5 -2
- src/app/queries/getDynamicConfig.ts +3 -0
- src/app/store/index.ts +26 -9
- src/lib/getImageDimension.ts +12 -2
- src/lib/getOAuthRedirectUrl.ts +11 -0
- src/lib/isValidNumber.ts +3 -0
- src/lib/parseLayoutFromStoryboards.ts +38 -0
- src/lib/parsePresetFromPrompts.ts +37 -0
- src/lib/useImageDimension.ts +2 -0
- src/lib/useIsBusy.ts +9 -0
- src/lib/useOAuth.ts +5 -1
.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.
|
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.
|
85 |
-
"resolved": "https://registry.npmjs.org/@aitube/clap/-/clap-0.0.
|
86 |
-
"integrity": "sha512-
|
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.
|
123 |
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.
|
124 |
-
"integrity": "sha512-
|
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.
|
231 |
-
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.
|
232 |
-
"integrity": "sha512-
|
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.
|
2894 |
-
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.
|
2895 |
-
"integrity": "sha512-
|
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.
|
3326 |
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.
|
3327 |
-
"integrity": "sha512-
|
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.
|
3652 |
-
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.
|
3653 |
-
"integrity": "sha512-
|
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.
|
3670 |
-
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.
|
3671 |
-
"integrity": "sha512-
|
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.
|
4558 |
-
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.
|
4559 |
-
"integrity": "sha512-
|
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.
|
4684 |
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.
|
4685 |
-
"integrity": "sha512-
|
4686 |
"dependencies": {
|
4687 |
"undici-types": "~5.26.4"
|
4688 |
}
|
@@ -8298,9 +8298,9 @@
|
|
8298 |
}
|
8299 |
},
|
8300 |
"node_modules/openai": {
|
8301 |
-
"version": "4.
|
8302 |
-
"resolved": "https://registry.npmjs.org/openai/-/openai-4.
|
8303 |
-
"integrity": "sha512-
|
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.
|
8320 |
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.
|
8321 |
-
"integrity": "sha512-
|
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.
|
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(
|
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(
|
11 |
-
const layouts = useStore(
|
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(
|
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(
|
18 |
-
const showCaptions = useStore(
|
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(
|
57 |
-
const preset = useStore(
|
58 |
|
59 |
-
const setGeneratingImages = useStore(
|
60 |
|
61 |
-
const panels = useStore(
|
62 |
const prompt = panels[panelIndex] || ""
|
63 |
|
64 |
-
const setPanelPrompt = useStore(
|
65 |
|
66 |
-
const captions = useStore(
|
67 |
const caption = captions[panelIndex] || ""
|
68 |
-
const setPanelCaption = useStore(
|
69 |
|
70 |
-
const zoomLevel = useStore(
|
71 |
|
72 |
-
const addToUpscaleQueue = useStore(
|
73 |
|
74 |
const [_isPending, startTransition] = useTransition()
|
75 |
-
const renderedScenes = useStore(
|
76 |
-
const setRendered = useStore(
|
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(
|
10 |
-
const prompt = useStore(
|
11 |
-
const panelGenerationStatus = useStore(
|
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,
|
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(
|
53 |
-
// const setFont = useStore(
|
54 |
-
const preset = useStore(
|
55 |
-
const prompt = useStore(
|
56 |
-
const layout = useStore(
|
57 |
-
const setLayout = useStore(
|
58 |
-
|
59 |
-
const setShowCaptions = useStore(state => state.setShowCaptions)
|
60 |
-
const showCaptions = useStore(state => state.showCaptions)
|
61 |
|
62 |
-
const
|
63 |
-
const
|
64 |
|
65 |
-
const
|
|
|
66 |
|
67 |
-
const
|
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 |
-
<
|
171 |
defaultValue={defaultLayout}
|
172 |
-
|
173 |
disabled={isBusy}
|
174 |
-
|
175 |
-
|
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(
|
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 (
|
|
|
|
|
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 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
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 |
|