Spaces:
Running
Running
Commit
Β·
e429049
0
Parent(s):
initial commit
Browse filesThis view is limited to 50 files because it contains too many changes. Β
See raw diff
- .env +12 -0
- .eslintrc.json +3 -0
- .gitignore +35 -0
- .nvmrc +1 -0
- Dockerfile +65 -0
- README.md +11 -0
- components.json +16 -0
- next.config.js +11 -0
- package-lock.json +0 -0
- package.json +77 -0
- postcss.config.js +6 -0
- public/bubble.jpg +0 -0
- public/favicon.ico +0 -0
- public/favicon/Icon/r +0 -0
- public/favicon/favicon-114-precomposed.png +0 -0
- public/favicon/favicon-120-precomposed.png +0 -0
- public/favicon/favicon-144-precomposed.png +0 -0
- public/favicon/favicon-152-precomposed.png +0 -0
- public/favicon/favicon-180-precomposed.png +0 -0
- public/favicon/favicon-192.png +0 -0
- public/favicon/favicon-32.png +0 -0
- public/favicon/favicon-36.png +0 -0
- public/favicon/favicon-48.png +0 -0
- public/favicon/favicon-57.png +0 -0
- public/favicon/favicon-60.png +0 -0
- public/favicon/favicon-72-precomposed.png +0 -0
- public/favicon/favicon-72.png +0 -0
- public/favicon/favicon-76.png +0 -0
- public/favicon/favicon-96.png +0 -0
- public/favicon/favicon.ico +0 -0
- public/favicon/manifest.json +41 -0
- public/icon.png +0 -0
- public/layouts/layout0.jpg +0 -0
- public/layouts/layout0_hd.jpg +0 -0
- public/layouts/layout1.jpg +0 -0
- public/layouts/layout1_hd.jpg +0 -0
- public/layouts/layout2.jpg +0 -0
- public/layouts/layout2_hd.jpg +0 -0
- public/layouts/layout3 hd.jpg +0 -0
- public/layouts/layout3.jpg +0 -0
- public/mask.png +0 -0
- public/next.svg +1 -0
- public/vercel.svg +1 -0
- src/app/engine/render.ts +249 -0
- src/app/favicon.ico +0 -0
- src/app/globals.css +39 -0
- src/app/icon.png +0 -0
- src/app/interface/about/index.tsx +40 -0
- src/app/interface/bottom-bar/index.tsx +56 -0
- 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 |
+
}
|