jbilcke-hf HF Staff commited on
Commit
e429049
Β·
0 Parent(s):

initial commit

Browse files
This view is limited to 50 files because it contains too many changes. Β  See raw diff
Files changed (50) hide show
  1. .env +12 -0
  2. .eslintrc.json +3 -0
  3. .gitignore +35 -0
  4. .nvmrc +1 -0
  5. Dockerfile +65 -0
  6. README.md +11 -0
  7. components.json +16 -0
  8. next.config.js +11 -0
  9. package-lock.json +0 -0
  10. package.json +77 -0
  11. postcss.config.js +6 -0
  12. public/bubble.jpg +0 -0
  13. public/favicon.ico +0 -0
  14. public/favicon/Icon/r +0 -0
  15. public/favicon/favicon-114-precomposed.png +0 -0
  16. public/favicon/favicon-120-precomposed.png +0 -0
  17. public/favicon/favicon-144-precomposed.png +0 -0
  18. public/favicon/favicon-152-precomposed.png +0 -0
  19. public/favicon/favicon-180-precomposed.png +0 -0
  20. public/favicon/favicon-192.png +0 -0
  21. public/favicon/favicon-32.png +0 -0
  22. public/favicon/favicon-36.png +0 -0
  23. public/favicon/favicon-48.png +0 -0
  24. public/favicon/favicon-57.png +0 -0
  25. public/favicon/favicon-60.png +0 -0
  26. public/favicon/favicon-72-precomposed.png +0 -0
  27. public/favicon/favicon-72.png +0 -0
  28. public/favicon/favicon-76.png +0 -0
  29. public/favicon/favicon-96.png +0 -0
  30. public/favicon/favicon.ico +0 -0
  31. public/favicon/manifest.json +41 -0
  32. public/icon.png +0 -0
  33. public/layouts/layout0.jpg +0 -0
  34. public/layouts/layout0_hd.jpg +0 -0
  35. public/layouts/layout1.jpg +0 -0
  36. public/layouts/layout1_hd.jpg +0 -0
  37. public/layouts/layout2.jpg +0 -0
  38. public/layouts/layout2_hd.jpg +0 -0
  39. public/layouts/layout3 hd.jpg +0 -0
  40. public/layouts/layout3.jpg +0 -0
  41. public/mask.png +0 -0
  42. public/next.svg +1 -0
  43. public/vercel.svg +1 -0
  44. src/app/engine/render.ts +249 -0
  45. src/app/favicon.ico +0 -0
  46. src/app/globals.css +39 -0
  47. src/app/icon.png +0 -0
  48. src/app/interface/about/index.tsx +40 -0
  49. src/app/interface/bottom-bar/index.tsx +56 -0
  50. src/app/interface/display/index.tsx +12 -0
.env ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ------------- IMAGE API CONFIG --------------
2
+ # Supported values:
3
+ # - REPLICATE
4
+ # - VIDEOCHAIN
5
+ RENDERING_ENGINE="VIDEOCHAIN"
6
+
7
+ VIDEOCHAIN_API_URL="http://localhost:7860"
8
+ VIDEOCHAIN_API_TOKEN=
9
+
10
+ # Not supported yet
11
+ REPLICATE_API_TOKEN=
12
+ REPLICATE_API_MODEL="lucataco/sdxl-panoramic:76acc4075d0633dcb3823c1fed0419de21d42001b65c816c7b5b9beff30ec8cd"
.eslintrc.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "extends": "next/core-web-vitals"
3
+ }
.gitignore ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.js
7
+
8
+ # testing
9
+ /coverage
10
+
11
+ # next.js
12
+ /.next/
13
+ /out/
14
+
15
+ # production
16
+ /build
17
+
18
+ # misc
19
+ .DS_Store
20
+ *.pem
21
+
22
+ # debug
23
+ npm-debug.log*
24
+ yarn-debug.log*
25
+ yarn-error.log*
26
+
27
+ # local env files
28
+ .env*.local
29
+
30
+ # vercel
31
+ .vercel
32
+
33
+ # typescript
34
+ *.tsbuildinfo
35
+ next-env.d.ts
.nvmrc ADDED
@@ -0,0 +1 @@
 
 
1
+ v18.16.0
Dockerfile ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18-alpine AS base
2
+
3
+ # Install dependencies only when needed
4
+ FROM base AS deps
5
+ # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
6
+ RUN apk add --no-cache libc6-compat
7
+ WORKDIR /app
8
+
9
+ # Install dependencies based on the preferred package manager
10
+ COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
11
+ RUN \
12
+ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
13
+ elif [ -f package-lock.json ]; then npm ci; \
14
+ elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
15
+ else echo "Lockfile not found." && exit 1; \
16
+ fi
17
+
18
+ # Uncomment the following lines if you want to use a secret at buildtime,
19
+ # for example to access your private npm packages
20
+ # RUN --mount=type=secret,id=HF_EXAMPLE_SECRET,mode=0444,required=true \
21
+ # $(cat /run/secrets/HF_EXAMPLE_SECRET)
22
+
23
+ # Rebuild the source code only when needed
24
+ FROM base AS builder
25
+ WORKDIR /app
26
+ COPY --from=deps /app/node_modules ./node_modules
27
+ COPY . .
28
+
29
+ # Next.js collects completely anonymous telemetry data about general usage.
30
+ # Learn more here: https://nextjs.org/telemetry
31
+ # Uncomment the following line in case you want to disable telemetry during the build.
32
+ # ENV NEXT_TELEMETRY_DISABLED 1
33
+
34
+ # RUN yarn build
35
+
36
+ # If you use yarn, comment out this line and use the line above
37
+ RUN npm run build
38
+
39
+ # Production image, copy all the files and run next
40
+ FROM base AS runner
41
+ WORKDIR /app
42
+
43
+ ENV NODE_ENV production
44
+ # Uncomment the following line in case you want to disable telemetry during runtime.
45
+ # ENV NEXT_TELEMETRY_DISABLED 1
46
+
47
+ RUN addgroup --system --gid 1001 nodejs
48
+ RUN adduser --system --uid 1001 nextjs
49
+
50
+ COPY --from=builder /app/public ./public
51
+
52
+ # Automatically leverage output traces to reduce image size
53
+ # https://nextjs.org/docs/advanced-features/output-file-tracing
54
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
55
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
56
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/cache ./.next/cache
57
+ # COPY --from=builder --chown=nextjs:nodejs /app/.next/cache/fetch-cache ./.next/cache/fetch-cache
58
+
59
+ USER nextjs
60
+
61
+ EXPOSE 3000
62
+
63
+ ENV PORT 3000
64
+
65
+ CMD ["node", "server.js"]
README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Panoremix
3
+ emoji: πŸ‘©β€πŸŽ¨
4
+ colorFrom: blue
5
+ colorTo: yellow
6
+ sdk: docker
7
+ pinned: true
8
+ app_port: 3000
9
+ ---
10
+
11
+ # Panoremix
components.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.js",
8
+ "css": "app/globals.css",
9
+ "baseColor": "stone",
10
+ "cssVariables": false
11
+ },
12
+ "aliases": {
13
+ "components": "@/components",
14
+ "utils": "@/lib/utils"
15
+ }
16
+ }
next.config.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ output: 'standalone',
4
+
5
+ experimental: {
6
+ serverActions: true,
7
+ serverActionsBodySizeLimit: '8mb',
8
+ },
9
+ }
10
+
11
+ module.exports = nextConfig
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "@jbilcke/panoremix",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@huggingface/inference": "^2.6.1",
13
+ "@photo-sphere-viewer/core": "^5.1.7",
14
+ "@photo-sphere-viewer/markers-plugin": "^5.1.7",
15
+ "@photo-sphere-viewer/video-plugin": "^5.1.7",
16
+ "@photo-sphere-viewer/virtual-tour-plugin": "^5.1.7",
17
+ "@radix-ui/react-accordion": "^1.1.2",
18
+ "@radix-ui/react-avatar": "^1.0.3",
19
+ "@radix-ui/react-checkbox": "^1.0.4",
20
+ "@radix-ui/react-collapsible": "^1.0.3",
21
+ "@radix-ui/react-dialog": "^1.0.4",
22
+ "@radix-ui/react-dropdown-menu": "^2.0.5",
23
+ "@radix-ui/react-icons": "^1.3.0",
24
+ "@radix-ui/react-label": "^2.0.2",
25
+ "@radix-ui/react-menubar": "^1.0.3",
26
+ "@radix-ui/react-popover": "^1.0.6",
27
+ "@radix-ui/react-select": "^1.2.2",
28
+ "@radix-ui/react-separator": "^1.0.3",
29
+ "@radix-ui/react-slider": "^1.1.2",
30
+ "@radix-ui/react-slot": "^1.0.2",
31
+ "@radix-ui/react-switch": "^1.0.3",
32
+ "@radix-ui/react-toast": "^1.1.4",
33
+ "@radix-ui/react-tooltip": "^1.0.6",
34
+ "@react-pdf/renderer": "^3.1.12",
35
+ "@types/node": "20.4.2",
36
+ "@types/react": "18.2.15",
37
+ "@types/react-dom": "18.2.7",
38
+ "@types/uuid": "^9.0.2",
39
+ "autoprefixer": "10.4.14",
40
+ "class-variance-authority": "^0.6.1",
41
+ "clsx": "^2.0.0",
42
+ "cmdk": "^0.2.0",
43
+ "cookies-next": "^2.1.2",
44
+ "date-fns": "^2.30.0",
45
+ "eslint": "8.45.0",
46
+ "eslint-config-next": "13.4.10",
47
+ "html2canvas": "^1.4.1",
48
+ "lucide-react": "^0.260.0",
49
+ "next": "13.4.10",
50
+ "photo-sphere-viewer-lensflare-plugin": "^1.1.1",
51
+ "pick": "^0.0.1",
52
+ "postcss": "8.4.26",
53
+ "react": "18.2.0",
54
+ "react-circular-progressbar": "^2.1.0",
55
+ "react-dom": "18.2.0",
56
+ "react-photo-sphere-viewer": "^3.3.5-psv5.1.4",
57
+ "react-virtualized-auto-sizer": "^1.0.20",
58
+ "replicate": "^0.17.0",
59
+ "sbd": "^1.0.19",
60
+ "sharp": "^0.32.5",
61
+ "styled-components": "^6.0.7",
62
+ "tailwind-merge": "^1.13.2",
63
+ "tailwindcss": "3.3.3",
64
+ "tailwindcss-animate": "^1.0.6",
65
+ "tesseract.js": "^4.1.2",
66
+ "ts-node": "^10.9.1",
67
+ "typescript": "5.1.6",
68
+ "usehooks-ts": "^2.9.1",
69
+ "uuid": "^9.0.0",
70
+ "zustand": "^4.4.1"
71
+ },
72
+ "devDependencies": {
73
+ "@types/qs": "^6.9.7",
74
+ "@types/react-virtualized": "^9.21.22",
75
+ "@types/sbd": "^1.0.3"
76
+ }
77
+ }
postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
public/bubble.jpg ADDED
public/favicon.ico ADDED
public/favicon/Icon/r ADDED
File without changes
public/favicon/favicon-114-precomposed.png ADDED
public/favicon/favicon-120-precomposed.png ADDED
public/favicon/favicon-144-precomposed.png ADDED
public/favicon/favicon-152-precomposed.png ADDED
public/favicon/favicon-180-precomposed.png ADDED
public/favicon/favicon-192.png ADDED
public/favicon/favicon-32.png ADDED
public/favicon/favicon-36.png ADDED
public/favicon/favicon-48.png ADDED
public/favicon/favicon-57.png ADDED
public/favicon/favicon-60.png ADDED
public/favicon/favicon-72-precomposed.png ADDED
public/favicon/favicon-72.png ADDED
public/favicon/favicon-76.png ADDED
public/favicon/favicon-96.png ADDED
public/favicon/favicon.ico ADDED
public/favicon/manifest.json ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "pollo",
3
+ "icons": [
4
+ {
5
+ "src": "\/favicon-36.png",
6
+ "sizes": "36x36",
7
+ "type": "image\/png",
8
+ "density": 0.75
9
+ },
10
+ {
11
+ "src": "\/favicon-48.png",
12
+ "sizes": "48x48",
13
+ "type": "image\/png",
14
+ "density": 1
15
+ },
16
+ {
17
+ "src": "\/favicon-72.png",
18
+ "sizes": "72x72",
19
+ "type": "image\/png",
20
+ "density": 1.5
21
+ },
22
+ {
23
+ "src": "\/favicon-96.png",
24
+ "sizes": "96x96",
25
+ "type": "image\/png",
26
+ "density": 2
27
+ },
28
+ {
29
+ "src": "\/favicon-144.png",
30
+ "sizes": "144x144",
31
+ "type": "image\/png",
32
+ "density": 3
33
+ },
34
+ {
35
+ "src": "\/favicon-192.png",
36
+ "sizes": "192x192",
37
+ "type": "image\/png",
38
+ "density": 4
39
+ }
40
+ ]
41
+ }
public/icon.png ADDED
public/layouts/layout0.jpg ADDED
public/layouts/layout0_hd.jpg ADDED
public/layouts/layout1.jpg ADDED
public/layouts/layout1_hd.jpg ADDED
public/layouts/layout2.jpg ADDED
public/layouts/layout2_hd.jpg ADDED
public/layouts/layout3 hd.jpg ADDED
public/layouts/layout3.jpg ADDED
public/mask.png ADDED
public/next.svg ADDED
public/vercel.svg ADDED
src/app/engine/render.ts ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server"
2
+
3
+ import Replicate, { Prediction } from "replicate"
4
+
5
+ import { RenderRequest, RenderedScene, RenderingEngine } from "@/types"
6
+ import { generateSeed } from "@/lib/generateSeed"
7
+ import { sleep } from "@/lib/sleep"
8
+
9
+ const renderingEngine = `${process.env.RENDERING_ENGINE || ""}` as RenderingEngine
10
+
11
+ const replicateToken = `${process.env.REPLICATE_API_TOKEN || ""}`
12
+ const replicateModel = `${process.env.REPLICATE_API_MODEL || ""}`
13
+
14
+ // note: there is no / at the end in the variable
15
+ // so we have to add it ourselves if needed
16
+ const apiUrl = process.env.VIDEOCHAIN_API_URL
17
+
18
+ export async function newRender({
19
+ prompt,
20
+ clearCache,
21
+ }: {
22
+ prompt: string
23
+ clearCache: boolean
24
+ }) {
25
+ if (!prompt) {
26
+ console.error(`cannot call the rendering API without a prompt, aborting..`)
27
+ throw new Error(`cannot call the rendering API without a prompt, aborting..`)
28
+ }
29
+
30
+ prompt = [
31
+ `hdri view`,
32
+ prompt
33
+ ].join(', ')
34
+
35
+ // return await Gorgon.get(cacheKey, async () => {
36
+
37
+ let defaulResult: RenderedScene = {
38
+ renderId: "",
39
+ status: "error",
40
+ assetUrl: "",
41
+ alt: prompt || "",
42
+ maskUrl: "",
43
+ error: "failed to fetch the data",
44
+ segments: []
45
+ }
46
+
47
+ try {
48
+ console.log(`calling POST ${apiUrl}/render with prompt: ${prompt}`)
49
+
50
+ const request = {
51
+ prompt,
52
+ nbFrames: 1, // when nbFrames is 1, we will only generate static images
53
+ nbSteps: 35, // 20 = fast, 30 = better, 50 = best
54
+ actionnables: [],
55
+ segmentation: "disabled", // one day we will remove this param, to make it automatic
56
+ width: 1024,
57
+ height: 768,
58
+
59
+ // on VideoQuest we use an aggressive setting: 4X upscaling
60
+ // this generates images that can be slow to load, but that's
61
+ // not too much of an issue since we use async loading
62
+ upscalingFactor: 1,
63
+
64
+ // note that we never disable the cache completely for VideoQuest
65
+ // that's because in the feedbacks people prefer speed to avoid frustration
66
+ cache: clearCache ? "renew" : "use",
67
+
68
+ } as Partial<RenderRequest>
69
+
70
+ console.table(request)
71
+
72
+ if (renderingEngine === "REPLICATE") {
73
+ if (!replicateToken) {
74
+ throw new Error(`you need to configure your REPLICATE_API_TOKEN in order to use the REPLICATE rendering engine`)
75
+ }
76
+ if (!replicateModel) {
77
+ throw new Error(`you need to configure your REPLICATE_API_MODEL in order to use the REPLICATE rendering engine`)
78
+ }
79
+
80
+ const replicate = new Replicate({ auth: replicateToken })
81
+
82
+ // console.log("Calling replicate..")
83
+ const seed = generateSeed()
84
+ const prediction = await replicate.predictions.create({
85
+ version: "76acc4075d0633dcb3823c1fed0419de21d42001b65c816c7b5b9beff30ec8cd",
86
+ input: { prompt, seed }
87
+ })
88
+
89
+ // console.log("prediction:", prediction)
90
+
91
+ // no need to reply straight away: good things take time
92
+ // also our friends at Replicate won't like it if we spam them with requests
93
+ await sleep(12000)
94
+
95
+ return {
96
+ renderId: prediction.id,
97
+ status: "pending",
98
+ assetUrl: "",
99
+ alt: prompt,
100
+ error: prediction.error,
101
+ maskUrl: "",
102
+ segments:[]
103
+ } as RenderedScene
104
+ } else {
105
+
106
+ const res = await fetch(`${apiUrl}/render`, {
107
+ method: "POST",
108
+ headers: {
109
+ Accept: "application/json",
110
+ "Content-Type": "application/json",
111
+ // Authorization: `Bearer ${process.env.VC_SECRET_ACCESS_TOKEN}`,
112
+ },
113
+ body: JSON.stringify(request),
114
+ cache: 'no-store',
115
+ // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
116
+ // next: { revalidate: 1 }
117
+ })
118
+
119
+ // console.log("res:", res)
120
+ // The return value is *not* serialized
121
+ // You can return Date, Map, Set, etc.
122
+
123
+ // Recommendation: handle errors
124
+ if (res.status !== 200) {
125
+ // This will activate the closest `error.js` Error Boundary
126
+ throw new Error('Failed to fetch data')
127
+ }
128
+
129
+ const response = (await res.json()) as RenderedScene
130
+ // console.log("response:", response)
131
+ return response
132
+ }
133
+ } catch (err) {
134
+ console.log("request failed:", err)
135
+ console.error(err)
136
+ // Gorgon.clear(cacheKey)
137
+ return defaulResult
138
+ }
139
+
140
+ // }, cacheDurationInSec * 1000)
141
+ }
142
+
143
+ export async function getRender(renderId: string) {
144
+ if (!renderId) {
145
+ console.error(`cannot call the rendering API without a renderId, aborting..`)
146
+ throw new Error(`cannot call the rendering API without a renderId, aborting..`)
147
+ }
148
+
149
+ let defaulResult: RenderedScene = {
150
+ renderId: "",
151
+ status: "pending",
152
+ assetUrl: "",
153
+ alt: "",
154
+ maskUrl: "",
155
+ error: "",
156
+ segments: []
157
+ }
158
+
159
+ try {
160
+
161
+
162
+ if (renderingEngine === "REPLICATE") {
163
+ if (!replicateToken) {
164
+ throw new Error(`you need to configure your REPLICATE_API_TOKEN in order to use the REPLICATE rendering engine`)
165
+ }
166
+ if (!replicateModel) {
167
+ throw new Error(`you need to configure your REPLICATE_API_MODEL in order to use the REPLICATE rendering engine`)
168
+ }
169
+
170
+ // const replicate = new Replicate({ auth: replicateToken })
171
+
172
+ // console.log("Calling replicate..")
173
+ // const prediction = await replicate.predictions.get(renderId)
174
+ // console.log("Prediction:", prediction)
175
+
176
+ // console.log(`calling GET https://api.replicate.com/v1/predictions/${renderId}`)
177
+ const res = await fetch(`https://api.replicate.com/v1/predictions/${renderId}`, {
178
+ method: "GET",
179
+ headers: {
180
+ // Accept: "application/json",
181
+ // "Content-Type": "application/json",
182
+ Authorization: `Token ${replicateToken}`,
183
+ },
184
+ cache: 'no-store',
185
+ // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
186
+ // next: { revalidate: 1 }
187
+ })
188
+
189
+ // console.log("res:", res)
190
+ // The return value is *not* serialized
191
+ // You can return Date, Map, Set, etc.
192
+
193
+ // Recommendation: handle errors
194
+ if (res.status !== 200) {
195
+ // This will activate the closest `error.js` Error Boundary
196
+ throw new Error('Failed to fetch data')
197
+ }
198
+
199
+ const response = (await res.json()) as any
200
+ // console.log("response:", response)
201
+
202
+ return {
203
+ renderId,
204
+ status: response?.error ? "error" : response?.status === "succeeded" ? "completed" : "pending",
205
+ assetUrl: `${response?.output || ""}`,
206
+ alt: `${response?.input?.prompt || ""}`,
207
+ error: `${response?.error || ""}`,
208
+ maskUrl: "",
209
+ segments: []
210
+ } as RenderedScene
211
+ } else {
212
+
213
+ // console.log(`calling GET ${apiUrl}/render with renderId: ${renderId}`)
214
+ const res = await fetch(`${apiUrl}/render/${renderId}`, {
215
+ method: "GET",
216
+ headers: {
217
+ Accept: "application/json",
218
+ "Content-Type": "application/json",
219
+ Authorization: `Bearer ${process.env.VC_SECRET_ACCESS_TOKEN}`,
220
+ },
221
+ cache: 'no-store',
222
+ // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
223
+ // next: { revalidate: 1 }
224
+ })
225
+
226
+ // console.log("res:", res)
227
+ // The return value is *not* serialized
228
+ // You can return Date, Map, Set, etc.
229
+
230
+ // Recommendation: handle errors
231
+ if (res.status !== 200) {
232
+ // This will activate the closest `error.js` Error Boundary
233
+ throw new Error('Failed to fetch data')
234
+ }
235
+
236
+ const response = (await res.json()) as RenderedScene
237
+ // console.log("response:", response)
238
+ return response
239
+ }
240
+ } catch (err) {
241
+ console.error(err)
242
+ defaulResult.status = "error"
243
+ defaulResult.error = `${err}`
244
+ // Gorgon.clear(cacheKey)
245
+ return defaulResult
246
+ }
247
+
248
+ // }, cacheDurationInSec * 1000)
249
+ }
src/app/favicon.ico ADDED
src/app/globals.css ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ :root {
6
+ --foreground-rgb: 0, 0, 0;
7
+ --background-start-rgb: 214, 219, 220;
8
+ --background-end-rgb: 255, 255, 255;
9
+ }
10
+
11
+ @media (prefers-color-scheme: dark) {
12
+ :root {
13
+ --foreground-rgb: 255, 255, 255;
14
+ --background-start-rgb: 0, 0, 0;
15
+ --background-end-rgb: 0, 0, 0;
16
+ }
17
+ }
18
+
19
+ body {
20
+ color: rgb(var(--foreground-rgb));
21
+ background: linear-gradient(
22
+ to bottom,
23
+ transparent,
24
+ rgb(var(--background-end-rgb))
25
+ )
26
+ rgb(var(--background-start-rgb));
27
+ }
28
+
29
+
30
+ /* this is the trick to bypass the style={{}} attribute when printing */
31
+ @media print {
32
+ .comic-page[style] { width: 100vw !important; }
33
+ }
34
+
35
+
36
+ .render-to-image .comic-panel {
37
+ height: auto !important;
38
+ /* max-width: fit-content !important; */
39
+ }
src/app/icon.png ADDED
src/app/interface/about/index.tsx ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Button } from "@/components/ui/button"
2
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
3
+ import { useState } from "react"
4
+
5
+ export function About() {
6
+ const [isOpen, setOpen] = useState(false)
7
+
8
+ return (
9
+ <Dialog open={isOpen} onOpenChange={setOpen}>
10
+ <DialogTrigger asChild>
11
+ <Button variant="outline">
12
+ <span className="hidden md:inline">About this project</span>
13
+ <span className="inline md:hidden">About</span>
14
+ </Button>
15
+ </DialogTrigger>
16
+ <DialogContent className="sm:max-w-[425px]">
17
+ <DialogHeader>
18
+ <DialogTitle>The Panoremix</DialogTitle>
19
+ <DialogDescription className="w-full text-center text-lg font-bold text-stone-800">
20
+ What is Panoremix?
21
+ </DialogDescription>
22
+ </DialogHeader>
23
+ <div className="grid gap-4 py-4 text-stone-800">
24
+ <p className="">
25
+ Panoremix is a free and open-source application made to generate panoramas.
26
+ </p>
27
+ <p>
28
+ πŸ‘‰ The stable diffusion model used to generate the images is the base <a className="text-stone-600 underline" href="https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0" target="_blank">SDXL 1.0</a>.
29
+ </p>
30
+ <p>
31
+ πŸ‘‰ The LoRA model used to generate the descriptions of each panel is <a className="text-stone-600 underline" href="https://replicate.com/lucataco/sdxl-panoramic" target="_blank">sdxl-panoramic</a>, a seamless variant of <a className="text-stone-600 underline" href="https://replicate.com/jbilcke/sdxl-panorama" target="_blank">sdxl-panorama</a>.
32
+ </p>
33
+ </div>
34
+ <DialogFooter>
35
+ <Button type="submit" onClick={() => setOpen(false)}>Got it</Button>
36
+ </DialogFooter>
37
+ </DialogContent>
38
+ </Dialog>
39
+ )
40
+ }
src/app/interface/bottom-bar/index.tsx ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useStore } from "@/app/store"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ import { About } from "../about"
5
+
6
+ export function BottomBar() {
7
+ // const prompt = useStore(state => state.prompt)
8
+ // const renderedScene = useStore(state => state.renderedScene)
9
+ // const setRendered = useStore(state => state.setRendered)
10
+
11
+ const isLoading = false
12
+
13
+ return (
14
+ <div className={cn(
15
+ `print:hidden`,
16
+ `fixed bottom-0 md:bottom-0 left-2 right-0 md:left-3 md:right-1`,
17
+ `flex flex-row`,
18
+ `justify-between`
19
+ )}>
20
+ <div className={cn(
21
+ `flex flex-row`,
22
+ `items-end`,
23
+ `animation-all duration-300 ease-in-out`,
24
+ isLoading ? `scale-0 opacity-0` : ``,
25
+ `space-x-3`,
26
+ `scale-[0.9]`
27
+ )}>
28
+ {/*<About />*/}
29
+ </div>
30
+ <div className={cn(
31
+ `flex flex-row`,
32
+ `animation-all duration-300 ease-in-out`,
33
+ isLoading ? `scale-0 opacity-0` : ``,
34
+ `space-x-3`,
35
+ `scale-[0.9]`
36
+ )}>
37
+ {/*
38
+ <div>
39
+ <Button
40
+ onClick={handleShare}
41
+ disabled={!prompt?.length}
42
+ className="space-x-2"
43
+ >
44
+ <div className="scale-105"><HuggingClap /></div>
45
+ <div>
46
+ <span className="hidden md:inline">Share to community</span>
47
+ <span className="inline md:hidden">Share</span>
48
+ </div>
49
+ </Button>
50
+ </div>
51
+ */}
52
+ <About />
53
+ </div>
54
+ </div>
55
+ )
56
+ }
src/app/interface/display/index.tsx ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { RenderedScene } from "@/types"
2
+
3
+ export function Display ({ rendered }: { rendered: RenderedScene }) {
4
+ return (
5
+ <>
6
+ <img
7
+ src={rendered.assetUrl || undefined}
8
+ className="fixed w-screen top-0 left-0 right-0"
9
+ />
10
+ </>
11
+ )
12
+ }