Spaces:
Running
Running
viktor
commited on
Commit
·
869a182
1
Parent(s):
637cbf7
feat. improve ui
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +35 -0
- .gitignore +42 -0
- Dockerfile +25 -0
- README.md +13 -0
- components.json +21 -0
- next.config.mjs +14 -0
- next.config.ts +7 -0
- package-lock.json +1627 -0
- package.json +72 -0
- pnpm-lock.yaml +0 -0
- postcss.config.mjs +8 -0
- public/placeholder-logo.png +0 -0
- public/placeholder-logo.svg +1 -0
- public/placeholder-user.jpg +0 -0
- public/placeholder.jpg +0 -0
- public/placeholder.svg +1 -0
- src/app/api/auth/login/route.ts +52 -0
- src/app/api/auth/logout/route.ts +18 -0
- src/app/api/generate-code/route.ts +123 -0
- src/app/api/improve-prompt/route.ts +88 -0
- src/app/api/login/route.ts +12 -0
- src/app/api/user/route.ts +42 -0
- src/app/globals.css +68 -0
- src/app/layout.tsx +34 -0
- src/app/page.tsx +15 -0
- src/components/app-container.tsx +250 -0
- src/components/auth-error-popup.tsx +40 -0
- src/components/code-editor.tsx +52 -0
- src/components/color-panel.tsx +196 -0
- src/components/header.tsx +37 -0
- src/components/logo.tsx +14 -0
- src/components/model-selector.tsx +72 -0
- src/components/preview.tsx +239 -0
- src/components/prompt-input.tsx +188 -0
- src/components/theme-provider.tsx +11 -0
- src/components/ui/accordion.tsx +58 -0
- src/components/ui/alert-dialog.tsx +141 -0
- src/components/ui/alert.tsx +59 -0
- src/components/ui/aspect-ratio.tsx +7 -0
- src/components/ui/avatar.tsx +50 -0
- src/components/ui/badge.tsx +36 -0
- src/components/ui/breadcrumb.tsx +115 -0
- src/components/ui/button.tsx +47 -0
- src/components/ui/calendar.tsx +66 -0
- src/components/ui/card.tsx +79 -0
- src/components/ui/carousel.tsx +262 -0
- src/components/ui/chart.tsx +365 -0
- src/components/ui/checkbox.tsx +30 -0
- src/components/ui/collapsible.tsx +11 -0
- src/components/ui/command.tsx +153 -0
.gitattributes
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
2 |
+
|
3 |
+
# dependencies
|
4 |
+
node_modules/
|
5 |
+
.pnp
|
6 |
+
.pnp.*
|
7 |
+
.yarn/*
|
8 |
+
!.yarn/patches
|
9 |
+
!.yarn/plugins
|
10 |
+
!.yarn/releases
|
11 |
+
!.yarn/versions
|
12 |
+
|
13 |
+
# testing
|
14 |
+
coverage/
|
15 |
+
|
16 |
+
# next.js
|
17 |
+
.next/
|
18 |
+
out/
|
19 |
+
|
20 |
+
# production
|
21 |
+
build/
|
22 |
+
|
23 |
+
# misc
|
24 |
+
.DS_Store
|
25 |
+
*.pem
|
26 |
+
.cursor/
|
27 |
+
|
28 |
+
# debug
|
29 |
+
npm-debug.log*
|
30 |
+
yarn-debug.log*
|
31 |
+
yarn-error.log*
|
32 |
+
.pnpm-debug.log*
|
33 |
+
|
34 |
+
# env files (can opt-in for committing if needed)
|
35 |
+
.env*
|
36 |
+
|
37 |
+
# vercel
|
38 |
+
.vercel
|
39 |
+
|
40 |
+
# typescript
|
41 |
+
*.tsbuildinfo
|
42 |
+
next-env.d.ts
|
Dockerfile
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Dockerfile
|
2 |
+
# Use an official Node.js runtime as the base image
|
3 |
+
FROM node:22.1.0
|
4 |
+
USER root
|
5 |
+
|
6 |
+
RUN apt-get update \
|
7 |
+
&& npm install -g pnpm
|
8 |
+
|
9 |
+
USER 1000
|
10 |
+
WORKDIR /usr/src/app
|
11 |
+
# Copy package.json and package-lock.json to the container
|
12 |
+
COPY --chown=1000 package.json package-lock.json pnpm-lock.yaml ./
|
13 |
+
|
14 |
+
# Copy the rest of the application files to the container
|
15 |
+
COPY --chown=1000 . .
|
16 |
+
|
17 |
+
RUN rm -rf node_modules .next .next-env.d.ts \
|
18 |
+
&& pnpm install \
|
19 |
+
&& pnpm build
|
20 |
+
|
21 |
+
# Expose the application port (assuming your app runs on port 3000)
|
22 |
+
EXPOSE 5001
|
23 |
+
|
24 |
+
# Start the application
|
25 |
+
CMD ["pnpm", "start"]
|
README.md
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: Novita Vibe Coding
|
3 |
+
emoji: 🐨
|
4 |
+
colorFrom: yellow
|
5 |
+
colorTo: yellow
|
6 |
+
sdk: docker
|
7 |
+
pinned: false
|
8 |
+
license: mit
|
9 |
+
app_port: 5001
|
10 |
+
short_description: This is a demo showing the use of Novita LLMs to vibe coding
|
11 |
+
---
|
12 |
+
|
13 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
components.json
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"$schema": "https://ui.shadcn.com/schema.json",
|
3 |
+
"style": "default",
|
4 |
+
"rsc": true,
|
5 |
+
"tsx": true,
|
6 |
+
"tailwind": {
|
7 |
+
"config": "tailwind.config.ts",
|
8 |
+
"css": "app/globals.css",
|
9 |
+
"baseColor": "neutral",
|
10 |
+
"cssVariables": true,
|
11 |
+
"prefix": ""
|
12 |
+
},
|
13 |
+
"aliases": {
|
14 |
+
"components": "@/components",
|
15 |
+
"utils": "@/lib/utils",
|
16 |
+
"ui": "@/components/ui",
|
17 |
+
"lib": "@/lib",
|
18 |
+
"hooks": "@/hooks"
|
19 |
+
},
|
20 |
+
"iconLibrary": "lucide"
|
21 |
+
}
|
next.config.mjs
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/** @type {import('next').NextConfig} */
|
2 |
+
const nextConfig = {
|
3 |
+
eslint: {
|
4 |
+
ignoreDuringBuilds: true,
|
5 |
+
},
|
6 |
+
typescript: {
|
7 |
+
ignoreBuildErrors: true,
|
8 |
+
},
|
9 |
+
images: {
|
10 |
+
unoptimized: true,
|
11 |
+
},
|
12 |
+
}
|
13 |
+
|
14 |
+
export default nextConfig
|
next.config.ts
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { NextConfig } from "next";
|
2 |
+
|
3 |
+
const nextConfig: NextConfig = {
|
4 |
+
/* config options here */
|
5 |
+
};
|
6 |
+
|
7 |
+
export default nextConfig;
|
package-lock.json
ADDED
@@ -0,0 +1,1627 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "novita-anysite",
|
3 |
+
"version": "0.1.0",
|
4 |
+
"lockfileVersion": 3,
|
5 |
+
"requires": true,
|
6 |
+
"packages": {
|
7 |
+
"": {
|
8 |
+
"name": "novita-anysite",
|
9 |
+
"version": "0.1.0",
|
10 |
+
"dependencies": {
|
11 |
+
"next": "15.3.2",
|
12 |
+
"react": "^19.0.0",
|
13 |
+
"react-dom": "^19.0.0"
|
14 |
+
},
|
15 |
+
"devDependencies": {
|
16 |
+
"@tailwindcss/postcss": "^4",
|
17 |
+
"@types/node": "^20",
|
18 |
+
"@types/react": "^19",
|
19 |
+
"@types/react-dom": "^19",
|
20 |
+
"tailwindcss": "^4",
|
21 |
+
"typescript": "^5"
|
22 |
+
}
|
23 |
+
},
|
24 |
+
"node_modules/@alloc/quick-lru": {
|
25 |
+
"version": "5.2.0",
|
26 |
+
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
27 |
+
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
28 |
+
"dev": true,
|
29 |
+
"engines": {
|
30 |
+
"node": ">=10"
|
31 |
+
},
|
32 |
+
"funding": {
|
33 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
34 |
+
}
|
35 |
+
},
|
36 |
+
"node_modules/@ampproject/remapping": {
|
37 |
+
"version": "2.3.0",
|
38 |
+
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
39 |
+
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
40 |
+
"dev": true,
|
41 |
+
"dependencies": {
|
42 |
+
"@jridgewell/gen-mapping": "^0.3.5",
|
43 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
44 |
+
},
|
45 |
+
"engines": {
|
46 |
+
"node": ">=6.0.0"
|
47 |
+
}
|
48 |
+
},
|
49 |
+
"node_modules/@emnapi/runtime": {
|
50 |
+
"version": "1.4.3",
|
51 |
+
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
|
52 |
+
"integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==",
|
53 |
+
"optional": true,
|
54 |
+
"dependencies": {
|
55 |
+
"tslib": "^2.4.0"
|
56 |
+
}
|
57 |
+
},
|
58 |
+
"node_modules/@img/sharp-darwin-arm64": {
|
59 |
+
"version": "0.34.1",
|
60 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.1.tgz",
|
61 |
+
"integrity": "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==",
|
62 |
+
"cpu": [
|
63 |
+
"arm64"
|
64 |
+
],
|
65 |
+
"optional": true,
|
66 |
+
"os": [
|
67 |
+
"darwin"
|
68 |
+
],
|
69 |
+
"engines": {
|
70 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
71 |
+
},
|
72 |
+
"funding": {
|
73 |
+
"url": "https://opencollective.com/libvips"
|
74 |
+
},
|
75 |
+
"optionalDependencies": {
|
76 |
+
"@img/sharp-libvips-darwin-arm64": "1.1.0"
|
77 |
+
}
|
78 |
+
},
|
79 |
+
"node_modules/@img/sharp-darwin-x64": {
|
80 |
+
"version": "0.34.1",
|
81 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.1.tgz",
|
82 |
+
"integrity": "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==",
|
83 |
+
"cpu": [
|
84 |
+
"x64"
|
85 |
+
],
|
86 |
+
"optional": true,
|
87 |
+
"os": [
|
88 |
+
"darwin"
|
89 |
+
],
|
90 |
+
"engines": {
|
91 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
92 |
+
},
|
93 |
+
"funding": {
|
94 |
+
"url": "https://opencollective.com/libvips"
|
95 |
+
},
|
96 |
+
"optionalDependencies": {
|
97 |
+
"@img/sharp-libvips-darwin-x64": "1.1.0"
|
98 |
+
}
|
99 |
+
},
|
100 |
+
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
101 |
+
"version": "1.1.0",
|
102 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz",
|
103 |
+
"integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==",
|
104 |
+
"cpu": [
|
105 |
+
"arm64"
|
106 |
+
],
|
107 |
+
"optional": true,
|
108 |
+
"os": [
|
109 |
+
"darwin"
|
110 |
+
],
|
111 |
+
"funding": {
|
112 |
+
"url": "https://opencollective.com/libvips"
|
113 |
+
}
|
114 |
+
},
|
115 |
+
"node_modules/@img/sharp-libvips-darwin-x64": {
|
116 |
+
"version": "1.1.0",
|
117 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz",
|
118 |
+
"integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==",
|
119 |
+
"cpu": [
|
120 |
+
"x64"
|
121 |
+
],
|
122 |
+
"optional": true,
|
123 |
+
"os": [
|
124 |
+
"darwin"
|
125 |
+
],
|
126 |
+
"funding": {
|
127 |
+
"url": "https://opencollective.com/libvips"
|
128 |
+
}
|
129 |
+
},
|
130 |
+
"node_modules/@img/sharp-libvips-linux-arm": {
|
131 |
+
"version": "1.1.0",
|
132 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz",
|
133 |
+
"integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==",
|
134 |
+
"cpu": [
|
135 |
+
"arm"
|
136 |
+
],
|
137 |
+
"optional": true,
|
138 |
+
"os": [
|
139 |
+
"linux"
|
140 |
+
],
|
141 |
+
"funding": {
|
142 |
+
"url": "https://opencollective.com/libvips"
|
143 |
+
}
|
144 |
+
},
|
145 |
+
"node_modules/@img/sharp-libvips-linux-arm64": {
|
146 |
+
"version": "1.1.0",
|
147 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz",
|
148 |
+
"integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==",
|
149 |
+
"cpu": [
|
150 |
+
"arm64"
|
151 |
+
],
|
152 |
+
"optional": true,
|
153 |
+
"os": [
|
154 |
+
"linux"
|
155 |
+
],
|
156 |
+
"funding": {
|
157 |
+
"url": "https://opencollective.com/libvips"
|
158 |
+
}
|
159 |
+
},
|
160 |
+
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
161 |
+
"version": "1.1.0",
|
162 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz",
|
163 |
+
"integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==",
|
164 |
+
"cpu": [
|
165 |
+
"ppc64"
|
166 |
+
],
|
167 |
+
"optional": true,
|
168 |
+
"os": [
|
169 |
+
"linux"
|
170 |
+
],
|
171 |
+
"funding": {
|
172 |
+
"url": "https://opencollective.com/libvips"
|
173 |
+
}
|
174 |
+
},
|
175 |
+
"node_modules/@img/sharp-libvips-linux-s390x": {
|
176 |
+
"version": "1.1.0",
|
177 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz",
|
178 |
+
"integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==",
|
179 |
+
"cpu": [
|
180 |
+
"s390x"
|
181 |
+
],
|
182 |
+
"optional": true,
|
183 |
+
"os": [
|
184 |
+
"linux"
|
185 |
+
],
|
186 |
+
"funding": {
|
187 |
+
"url": "https://opencollective.com/libvips"
|
188 |
+
}
|
189 |
+
},
|
190 |
+
"node_modules/@img/sharp-libvips-linux-x64": {
|
191 |
+
"version": "1.1.0",
|
192 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz",
|
193 |
+
"integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==",
|
194 |
+
"cpu": [
|
195 |
+
"x64"
|
196 |
+
],
|
197 |
+
"optional": true,
|
198 |
+
"os": [
|
199 |
+
"linux"
|
200 |
+
],
|
201 |
+
"funding": {
|
202 |
+
"url": "https://opencollective.com/libvips"
|
203 |
+
}
|
204 |
+
},
|
205 |
+
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
206 |
+
"version": "1.1.0",
|
207 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz",
|
208 |
+
"integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==",
|
209 |
+
"cpu": [
|
210 |
+
"arm64"
|
211 |
+
],
|
212 |
+
"optional": true,
|
213 |
+
"os": [
|
214 |
+
"linux"
|
215 |
+
],
|
216 |
+
"funding": {
|
217 |
+
"url": "https://opencollective.com/libvips"
|
218 |
+
}
|
219 |
+
},
|
220 |
+
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
221 |
+
"version": "1.1.0",
|
222 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz",
|
223 |
+
"integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==",
|
224 |
+
"cpu": [
|
225 |
+
"x64"
|
226 |
+
],
|
227 |
+
"optional": true,
|
228 |
+
"os": [
|
229 |
+
"linux"
|
230 |
+
],
|
231 |
+
"funding": {
|
232 |
+
"url": "https://opencollective.com/libvips"
|
233 |
+
}
|
234 |
+
},
|
235 |
+
"node_modules/@img/sharp-linux-arm": {
|
236 |
+
"version": "0.34.1",
|
237 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.1.tgz",
|
238 |
+
"integrity": "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==",
|
239 |
+
"cpu": [
|
240 |
+
"arm"
|
241 |
+
],
|
242 |
+
"optional": true,
|
243 |
+
"os": [
|
244 |
+
"linux"
|
245 |
+
],
|
246 |
+
"engines": {
|
247 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
248 |
+
},
|
249 |
+
"funding": {
|
250 |
+
"url": "https://opencollective.com/libvips"
|
251 |
+
},
|
252 |
+
"optionalDependencies": {
|
253 |
+
"@img/sharp-libvips-linux-arm": "1.1.0"
|
254 |
+
}
|
255 |
+
},
|
256 |
+
"node_modules/@img/sharp-linux-arm64": {
|
257 |
+
"version": "0.34.1",
|
258 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.1.tgz",
|
259 |
+
"integrity": "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==",
|
260 |
+
"cpu": [
|
261 |
+
"arm64"
|
262 |
+
],
|
263 |
+
"optional": true,
|
264 |
+
"os": [
|
265 |
+
"linux"
|
266 |
+
],
|
267 |
+
"engines": {
|
268 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
269 |
+
},
|
270 |
+
"funding": {
|
271 |
+
"url": "https://opencollective.com/libvips"
|
272 |
+
},
|
273 |
+
"optionalDependencies": {
|
274 |
+
"@img/sharp-libvips-linux-arm64": "1.1.0"
|
275 |
+
}
|
276 |
+
},
|
277 |
+
"node_modules/@img/sharp-linux-s390x": {
|
278 |
+
"version": "0.34.1",
|
279 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.1.tgz",
|
280 |
+
"integrity": "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==",
|
281 |
+
"cpu": [
|
282 |
+
"s390x"
|
283 |
+
],
|
284 |
+
"optional": true,
|
285 |
+
"os": [
|
286 |
+
"linux"
|
287 |
+
],
|
288 |
+
"engines": {
|
289 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
290 |
+
},
|
291 |
+
"funding": {
|
292 |
+
"url": "https://opencollective.com/libvips"
|
293 |
+
},
|
294 |
+
"optionalDependencies": {
|
295 |
+
"@img/sharp-libvips-linux-s390x": "1.1.0"
|
296 |
+
}
|
297 |
+
},
|
298 |
+
"node_modules/@img/sharp-linux-x64": {
|
299 |
+
"version": "0.34.1",
|
300 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.1.tgz",
|
301 |
+
"integrity": "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==",
|
302 |
+
"cpu": [
|
303 |
+
"x64"
|
304 |
+
],
|
305 |
+
"optional": true,
|
306 |
+
"os": [
|
307 |
+
"linux"
|
308 |
+
],
|
309 |
+
"engines": {
|
310 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
311 |
+
},
|
312 |
+
"funding": {
|
313 |
+
"url": "https://opencollective.com/libvips"
|
314 |
+
},
|
315 |
+
"optionalDependencies": {
|
316 |
+
"@img/sharp-libvips-linux-x64": "1.1.0"
|
317 |
+
}
|
318 |
+
},
|
319 |
+
"node_modules/@img/sharp-linuxmusl-arm64": {
|
320 |
+
"version": "0.34.1",
|
321 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.1.tgz",
|
322 |
+
"integrity": "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==",
|
323 |
+
"cpu": [
|
324 |
+
"arm64"
|
325 |
+
],
|
326 |
+
"optional": true,
|
327 |
+
"os": [
|
328 |
+
"linux"
|
329 |
+
],
|
330 |
+
"engines": {
|
331 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
332 |
+
},
|
333 |
+
"funding": {
|
334 |
+
"url": "https://opencollective.com/libvips"
|
335 |
+
},
|
336 |
+
"optionalDependencies": {
|
337 |
+
"@img/sharp-libvips-linuxmusl-arm64": "1.1.0"
|
338 |
+
}
|
339 |
+
},
|
340 |
+
"node_modules/@img/sharp-linuxmusl-x64": {
|
341 |
+
"version": "0.34.1",
|
342 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.1.tgz",
|
343 |
+
"integrity": "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==",
|
344 |
+
"cpu": [
|
345 |
+
"x64"
|
346 |
+
],
|
347 |
+
"optional": true,
|
348 |
+
"os": [
|
349 |
+
"linux"
|
350 |
+
],
|
351 |
+
"engines": {
|
352 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
353 |
+
},
|
354 |
+
"funding": {
|
355 |
+
"url": "https://opencollective.com/libvips"
|
356 |
+
},
|
357 |
+
"optionalDependencies": {
|
358 |
+
"@img/sharp-libvips-linuxmusl-x64": "1.1.0"
|
359 |
+
}
|
360 |
+
},
|
361 |
+
"node_modules/@img/sharp-wasm32": {
|
362 |
+
"version": "0.34.1",
|
363 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.1.tgz",
|
364 |
+
"integrity": "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==",
|
365 |
+
"cpu": [
|
366 |
+
"wasm32"
|
367 |
+
],
|
368 |
+
"optional": true,
|
369 |
+
"dependencies": {
|
370 |
+
"@emnapi/runtime": "^1.4.0"
|
371 |
+
},
|
372 |
+
"engines": {
|
373 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
374 |
+
},
|
375 |
+
"funding": {
|
376 |
+
"url": "https://opencollective.com/libvips"
|
377 |
+
}
|
378 |
+
},
|
379 |
+
"node_modules/@img/sharp-win32-ia32": {
|
380 |
+
"version": "0.34.1",
|
381 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.1.tgz",
|
382 |
+
"integrity": "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==",
|
383 |
+
"cpu": [
|
384 |
+
"ia32"
|
385 |
+
],
|
386 |
+
"optional": true,
|
387 |
+
"os": [
|
388 |
+
"win32"
|
389 |
+
],
|
390 |
+
"engines": {
|
391 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
392 |
+
},
|
393 |
+
"funding": {
|
394 |
+
"url": "https://opencollective.com/libvips"
|
395 |
+
}
|
396 |
+
},
|
397 |
+
"node_modules/@img/sharp-win32-x64": {
|
398 |
+
"version": "0.34.1",
|
399 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.1.tgz",
|
400 |
+
"integrity": "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==",
|
401 |
+
"cpu": [
|
402 |
+
"x64"
|
403 |
+
],
|
404 |
+
"optional": true,
|
405 |
+
"os": [
|
406 |
+
"win32"
|
407 |
+
],
|
408 |
+
"engines": {
|
409 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
410 |
+
},
|
411 |
+
"funding": {
|
412 |
+
"url": "https://opencollective.com/libvips"
|
413 |
+
}
|
414 |
+
},
|
415 |
+
"node_modules/@isaacs/fs-minipass": {
|
416 |
+
"version": "4.0.1",
|
417 |
+
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
418 |
+
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
|
419 |
+
"dev": true,
|
420 |
+
"dependencies": {
|
421 |
+
"minipass": "^7.0.4"
|
422 |
+
},
|
423 |
+
"engines": {
|
424 |
+
"node": ">=18.0.0"
|
425 |
+
}
|
426 |
+
},
|
427 |
+
"node_modules/@jridgewell/gen-mapping": {
|
428 |
+
"version": "0.3.8",
|
429 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
430 |
+
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
431 |
+
"dev": true,
|
432 |
+
"dependencies": {
|
433 |
+
"@jridgewell/set-array": "^1.2.1",
|
434 |
+
"@jridgewell/sourcemap-codec": "^1.4.10",
|
435 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
436 |
+
},
|
437 |
+
"engines": {
|
438 |
+
"node": ">=6.0.0"
|
439 |
+
}
|
440 |
+
},
|
441 |
+
"node_modules/@jridgewell/resolve-uri": {
|
442 |
+
"version": "3.1.2",
|
443 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
444 |
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
445 |
+
"dev": true,
|
446 |
+
"engines": {
|
447 |
+
"node": ">=6.0.0"
|
448 |
+
}
|
449 |
+
},
|
450 |
+
"node_modules/@jridgewell/set-array": {
|
451 |
+
"version": "1.2.1",
|
452 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
453 |
+
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
454 |
+
"dev": true,
|
455 |
+
"engines": {
|
456 |
+
"node": ">=6.0.0"
|
457 |
+
}
|
458 |
+
},
|
459 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
460 |
+
"version": "1.5.0",
|
461 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
462 |
+
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
463 |
+
"dev": true
|
464 |
+
},
|
465 |
+
"node_modules/@jridgewell/trace-mapping": {
|
466 |
+
"version": "0.3.25",
|
467 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
468 |
+
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
469 |
+
"dev": true,
|
470 |
+
"dependencies": {
|
471 |
+
"@jridgewell/resolve-uri": "^3.1.0",
|
472 |
+
"@jridgewell/sourcemap-codec": "^1.4.14"
|
473 |
+
}
|
474 |
+
},
|
475 |
+
"node_modules/@next/env": {
|
476 |
+
"version": "15.3.2",
|
477 |
+
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.2.tgz",
|
478 |
+
"integrity": "sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g=="
|
479 |
+
},
|
480 |
+
"node_modules/@next/swc-darwin-arm64": {
|
481 |
+
"version": "15.3.2",
|
482 |
+
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.2.tgz",
|
483 |
+
"integrity": "sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g==",
|
484 |
+
"cpu": [
|
485 |
+
"arm64"
|
486 |
+
],
|
487 |
+
"optional": true,
|
488 |
+
"os": [
|
489 |
+
"darwin"
|
490 |
+
],
|
491 |
+
"engines": {
|
492 |
+
"node": ">= 10"
|
493 |
+
}
|
494 |
+
},
|
495 |
+
"node_modules/@next/swc-darwin-x64": {
|
496 |
+
"version": "15.3.2",
|
497 |
+
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.2.tgz",
|
498 |
+
"integrity": "sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w==",
|
499 |
+
"cpu": [
|
500 |
+
"x64"
|
501 |
+
],
|
502 |
+
"optional": true,
|
503 |
+
"os": [
|
504 |
+
"darwin"
|
505 |
+
],
|
506 |
+
"engines": {
|
507 |
+
"node": ">= 10"
|
508 |
+
}
|
509 |
+
},
|
510 |
+
"node_modules/@next/swc-linux-arm64-gnu": {
|
511 |
+
"version": "15.3.2",
|
512 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.2.tgz",
|
513 |
+
"integrity": "sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA==",
|
514 |
+
"cpu": [
|
515 |
+
"arm64"
|
516 |
+
],
|
517 |
+
"optional": true,
|
518 |
+
"os": [
|
519 |
+
"linux"
|
520 |
+
],
|
521 |
+
"engines": {
|
522 |
+
"node": ">= 10"
|
523 |
+
}
|
524 |
+
},
|
525 |
+
"node_modules/@next/swc-linux-arm64-musl": {
|
526 |
+
"version": "15.3.2",
|
527 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.2.tgz",
|
528 |
+
"integrity": "sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg==",
|
529 |
+
"cpu": [
|
530 |
+
"arm64"
|
531 |
+
],
|
532 |
+
"optional": true,
|
533 |
+
"os": [
|
534 |
+
"linux"
|
535 |
+
],
|
536 |
+
"engines": {
|
537 |
+
"node": ">= 10"
|
538 |
+
}
|
539 |
+
},
|
540 |
+
"node_modules/@next/swc-linux-x64-gnu": {
|
541 |
+
"version": "15.3.2",
|
542 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.2.tgz",
|
543 |
+
"integrity": "sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==",
|
544 |
+
"cpu": [
|
545 |
+
"x64"
|
546 |
+
],
|
547 |
+
"optional": true,
|
548 |
+
"os": [
|
549 |
+
"linux"
|
550 |
+
],
|
551 |
+
"engines": {
|
552 |
+
"node": ">= 10"
|
553 |
+
}
|
554 |
+
},
|
555 |
+
"node_modules/@next/swc-linux-x64-musl": {
|
556 |
+
"version": "15.3.2",
|
557 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.2.tgz",
|
558 |
+
"integrity": "sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==",
|
559 |
+
"cpu": [
|
560 |
+
"x64"
|
561 |
+
],
|
562 |
+
"optional": true,
|
563 |
+
"os": [
|
564 |
+
"linux"
|
565 |
+
],
|
566 |
+
"engines": {
|
567 |
+
"node": ">= 10"
|
568 |
+
}
|
569 |
+
},
|
570 |
+
"node_modules/@next/swc-win32-arm64-msvc": {
|
571 |
+
"version": "15.3.2",
|
572 |
+
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.2.tgz",
|
573 |
+
"integrity": "sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ==",
|
574 |
+
"cpu": [
|
575 |
+
"arm64"
|
576 |
+
],
|
577 |
+
"optional": true,
|
578 |
+
"os": [
|
579 |
+
"win32"
|
580 |
+
],
|
581 |
+
"engines": {
|
582 |
+
"node": ">= 10"
|
583 |
+
}
|
584 |
+
},
|
585 |
+
"node_modules/@next/swc-win32-x64-msvc": {
|
586 |
+
"version": "15.3.2",
|
587 |
+
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.2.tgz",
|
588 |
+
"integrity": "sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA==",
|
589 |
+
"cpu": [
|
590 |
+
"x64"
|
591 |
+
],
|
592 |
+
"optional": true,
|
593 |
+
"os": [
|
594 |
+
"win32"
|
595 |
+
],
|
596 |
+
"engines": {
|
597 |
+
"node": ">= 10"
|
598 |
+
}
|
599 |
+
},
|
600 |
+
"node_modules/@swc/counter": {
|
601 |
+
"version": "0.1.3",
|
602 |
+
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
603 |
+
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="
|
604 |
+
},
|
605 |
+
"node_modules/@swc/helpers": {
|
606 |
+
"version": "0.5.15",
|
607 |
+
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
608 |
+
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
609 |
+
"dependencies": {
|
610 |
+
"tslib": "^2.8.0"
|
611 |
+
}
|
612 |
+
},
|
613 |
+
"node_modules/@tailwindcss/node": {
|
614 |
+
"version": "4.1.6",
|
615 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.6.tgz",
|
616 |
+
"integrity": "sha512-ed6zQbgmKsjsVvodAS1q1Ld2BolEuxJOSyyNc+vhkjdmfNUDCmQnlXBfQkHrlzNmslxHsQU/bFmzcEbv4xXsLg==",
|
617 |
+
"dev": true,
|
618 |
+
"dependencies": {
|
619 |
+
"@ampproject/remapping": "^2.3.0",
|
620 |
+
"enhanced-resolve": "^5.18.1",
|
621 |
+
"jiti": "^2.4.2",
|
622 |
+
"lightningcss": "1.29.2",
|
623 |
+
"magic-string": "^0.30.17",
|
624 |
+
"source-map-js": "^1.2.1",
|
625 |
+
"tailwindcss": "4.1.6"
|
626 |
+
}
|
627 |
+
},
|
628 |
+
"node_modules/@tailwindcss/oxide": {
|
629 |
+
"version": "4.1.6",
|
630 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.6.tgz",
|
631 |
+
"integrity": "sha512-0bpEBQiGx+227fW4G0fLQ8vuvyy5rsB1YIYNapTq3aRsJ9taF3f5cCaovDjN5pUGKKzcpMrZst/mhNaKAPOHOA==",
|
632 |
+
"dev": true,
|
633 |
+
"hasInstallScript": true,
|
634 |
+
"dependencies": {
|
635 |
+
"detect-libc": "^2.0.4",
|
636 |
+
"tar": "^7.4.3"
|
637 |
+
},
|
638 |
+
"engines": {
|
639 |
+
"node": ">= 10"
|
640 |
+
},
|
641 |
+
"optionalDependencies": {
|
642 |
+
"@tailwindcss/oxide-android-arm64": "4.1.6",
|
643 |
+
"@tailwindcss/oxide-darwin-arm64": "4.1.6",
|
644 |
+
"@tailwindcss/oxide-darwin-x64": "4.1.6",
|
645 |
+
"@tailwindcss/oxide-freebsd-x64": "4.1.6",
|
646 |
+
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.6",
|
647 |
+
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.6",
|
648 |
+
"@tailwindcss/oxide-linux-arm64-musl": "4.1.6",
|
649 |
+
"@tailwindcss/oxide-linux-x64-gnu": "4.1.6",
|
650 |
+
"@tailwindcss/oxide-linux-x64-musl": "4.1.6",
|
651 |
+
"@tailwindcss/oxide-wasm32-wasi": "4.1.6",
|
652 |
+
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.6",
|
653 |
+
"@tailwindcss/oxide-win32-x64-msvc": "4.1.6"
|
654 |
+
}
|
655 |
+
},
|
656 |
+
"node_modules/@tailwindcss/oxide-android-arm64": {
|
657 |
+
"version": "4.1.6",
|
658 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.6.tgz",
|
659 |
+
"integrity": "sha512-VHwwPiwXtdIvOvqT/0/FLH/pizTVu78FOnI9jQo64kSAikFSZT7K4pjyzoDpSMaveJTGyAKvDjuhxJxKfmvjiQ==",
|
660 |
+
"cpu": [
|
661 |
+
"arm64"
|
662 |
+
],
|
663 |
+
"dev": true,
|
664 |
+
"optional": true,
|
665 |
+
"os": [
|
666 |
+
"android"
|
667 |
+
],
|
668 |
+
"engines": {
|
669 |
+
"node": ">= 10"
|
670 |
+
}
|
671 |
+
},
|
672 |
+
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
673 |
+
"version": "4.1.6",
|
674 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.6.tgz",
|
675 |
+
"integrity": "sha512-weINOCcqv1HVBIGptNrk7c6lWgSFFiQMcCpKM4tnVi5x8OY2v1FrV76jwLukfT6pL1hyajc06tyVmZFYXoxvhQ==",
|
676 |
+
"cpu": [
|
677 |
+
"arm64"
|
678 |
+
],
|
679 |
+
"dev": true,
|
680 |
+
"optional": true,
|
681 |
+
"os": [
|
682 |
+
"darwin"
|
683 |
+
],
|
684 |
+
"engines": {
|
685 |
+
"node": ">= 10"
|
686 |
+
}
|
687 |
+
},
|
688 |
+
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
689 |
+
"version": "4.1.6",
|
690 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.6.tgz",
|
691 |
+
"integrity": "sha512-3FzekhHG0ww1zQjQ1lPoq0wPrAIVXAbUkWdWM8u5BnYFZgb9ja5ejBqyTgjpo5mfy0hFOoMnMuVDI+7CXhXZaQ==",
|
692 |
+
"cpu": [
|
693 |
+
"x64"
|
694 |
+
],
|
695 |
+
"dev": true,
|
696 |
+
"optional": true,
|
697 |
+
"os": [
|
698 |
+
"darwin"
|
699 |
+
],
|
700 |
+
"engines": {
|
701 |
+
"node": ">= 10"
|
702 |
+
}
|
703 |
+
},
|
704 |
+
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
705 |
+
"version": "4.1.6",
|
706 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.6.tgz",
|
707 |
+
"integrity": "sha512-4m5F5lpkBZhVQJq53oe5XgJ+aFYWdrgkMwViHjRsES3KEu2m1udR21B1I77RUqie0ZYNscFzY1v9aDssMBZ/1w==",
|
708 |
+
"cpu": [
|
709 |
+
"x64"
|
710 |
+
],
|
711 |
+
"dev": true,
|
712 |
+
"optional": true,
|
713 |
+
"os": [
|
714 |
+
"freebsd"
|
715 |
+
],
|
716 |
+
"engines": {
|
717 |
+
"node": ">= 10"
|
718 |
+
}
|
719 |
+
},
|
720 |
+
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
721 |
+
"version": "4.1.6",
|
722 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.6.tgz",
|
723 |
+
"integrity": "sha512-qU0rHnA9P/ZoaDKouU1oGPxPWzDKtIfX7eOGi5jOWJKdxieUJdVV+CxWZOpDWlYTd4N3sFQvcnVLJWJ1cLP5TA==",
|
724 |
+
"cpu": [
|
725 |
+
"arm"
|
726 |
+
],
|
727 |
+
"dev": true,
|
728 |
+
"optional": true,
|
729 |
+
"os": [
|
730 |
+
"linux"
|
731 |
+
],
|
732 |
+
"engines": {
|
733 |
+
"node": ">= 10"
|
734 |
+
}
|
735 |
+
},
|
736 |
+
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
737 |
+
"version": "4.1.6",
|
738 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.6.tgz",
|
739 |
+
"integrity": "sha512-jXy3TSTrbfgyd3UxPQeXC3wm8DAgmigzar99Km9Sf6L2OFfn/k+u3VqmpgHQw5QNfCpPe43em6Q7V76Wx7ogIQ==",
|
740 |
+
"cpu": [
|
741 |
+
"arm64"
|
742 |
+
],
|
743 |
+
"dev": true,
|
744 |
+
"optional": true,
|
745 |
+
"os": [
|
746 |
+
"linux"
|
747 |
+
],
|
748 |
+
"engines": {
|
749 |
+
"node": ">= 10"
|
750 |
+
}
|
751 |
+
},
|
752 |
+
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
753 |
+
"version": "4.1.6",
|
754 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.6.tgz",
|
755 |
+
"integrity": "sha512-8kjivE5xW0qAQ9HX9reVFmZj3t+VmljDLVRJpVBEoTR+3bKMnvC7iLcoSGNIUJGOZy1mLVq7x/gerVg0T+IsYw==",
|
756 |
+
"cpu": [
|
757 |
+
"arm64"
|
758 |
+
],
|
759 |
+
"dev": true,
|
760 |
+
"optional": true,
|
761 |
+
"os": [
|
762 |
+
"linux"
|
763 |
+
],
|
764 |
+
"engines": {
|
765 |
+
"node": ">= 10"
|
766 |
+
}
|
767 |
+
},
|
768 |
+
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
769 |
+
"version": "4.1.6",
|
770 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.6.tgz",
|
771 |
+
"integrity": "sha512-A4spQhwnWVpjWDLXnOW9PSinO2PTKJQNRmL/aIl2U/O+RARls8doDfs6R41+DAXK0ccacvRyDpR46aVQJJCoCg==",
|
772 |
+
"cpu": [
|
773 |
+
"x64"
|
774 |
+
],
|
775 |
+
"dev": true,
|
776 |
+
"optional": true,
|
777 |
+
"os": [
|
778 |
+
"linux"
|
779 |
+
],
|
780 |
+
"engines": {
|
781 |
+
"node": ">= 10"
|
782 |
+
}
|
783 |
+
},
|
784 |
+
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
785 |
+
"version": "4.1.6",
|
786 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.6.tgz",
|
787 |
+
"integrity": "sha512-YRee+6ZqdzgiQAHVSLfl3RYmqeeaWVCk796MhXhLQu2kJu2COHBkqlqsqKYx3p8Hmk5pGCQd2jTAoMWWFeyG2A==",
|
788 |
+
"cpu": [
|
789 |
+
"x64"
|
790 |
+
],
|
791 |
+
"dev": true,
|
792 |
+
"optional": true,
|
793 |
+
"os": [
|
794 |
+
"linux"
|
795 |
+
],
|
796 |
+
"engines": {
|
797 |
+
"node": ">= 10"
|
798 |
+
}
|
799 |
+
},
|
800 |
+
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
801 |
+
"version": "4.1.6",
|
802 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.6.tgz",
|
803 |
+
"integrity": "sha512-qAp4ooTYrBQ5pk5jgg54/U1rCJ/9FLYOkkQ/nTE+bVMseMfB6O7J8zb19YTpWuu4UdfRf5zzOrNKfl6T64MNrQ==",
|
804 |
+
"bundleDependencies": [
|
805 |
+
"@napi-rs/wasm-runtime",
|
806 |
+
"@emnapi/core",
|
807 |
+
"@emnapi/runtime",
|
808 |
+
"@tybys/wasm-util",
|
809 |
+
"@emnapi/wasi-threads",
|
810 |
+
"tslib"
|
811 |
+
],
|
812 |
+
"cpu": [
|
813 |
+
"wasm32"
|
814 |
+
],
|
815 |
+
"dev": true,
|
816 |
+
"optional": true,
|
817 |
+
"dependencies": {
|
818 |
+
"@emnapi/core": "^1.4.3",
|
819 |
+
"@emnapi/runtime": "^1.4.3",
|
820 |
+
"@emnapi/wasi-threads": "^1.0.2",
|
821 |
+
"@napi-rs/wasm-runtime": "^0.2.9",
|
822 |
+
"@tybys/wasm-util": "^0.9.0",
|
823 |
+
"tslib": "^2.8.0"
|
824 |
+
},
|
825 |
+
"engines": {
|
826 |
+
"node": ">=14.0.0"
|
827 |
+
}
|
828 |
+
},
|
829 |
+
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
830 |
+
"version": "4.1.6",
|
831 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.6.tgz",
|
832 |
+
"integrity": "sha512-nqpDWk0Xr8ELO/nfRUDjk1pc9wDJ3ObeDdNMHLaymc4PJBWj11gdPCWZFKSK2AVKjJQC7J2EfmSmf47GN7OuLg==",
|
833 |
+
"cpu": [
|
834 |
+
"arm64"
|
835 |
+
],
|
836 |
+
"dev": true,
|
837 |
+
"optional": true,
|
838 |
+
"os": [
|
839 |
+
"win32"
|
840 |
+
],
|
841 |
+
"engines": {
|
842 |
+
"node": ">= 10"
|
843 |
+
}
|
844 |
+
},
|
845 |
+
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
846 |
+
"version": "4.1.6",
|
847 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.6.tgz",
|
848 |
+
"integrity": "sha512-5k9xF33xkfKpo9wCvYcegQ21VwIBU1/qEbYlVukfEIyQbEA47uK8AAwS7NVjNE3vHzcmxMYwd0l6L4pPjjm1rQ==",
|
849 |
+
"cpu": [
|
850 |
+
"x64"
|
851 |
+
],
|
852 |
+
"dev": true,
|
853 |
+
"optional": true,
|
854 |
+
"os": [
|
855 |
+
"win32"
|
856 |
+
],
|
857 |
+
"engines": {
|
858 |
+
"node": ">= 10"
|
859 |
+
}
|
860 |
+
},
|
861 |
+
"node_modules/@tailwindcss/postcss": {
|
862 |
+
"version": "4.1.6",
|
863 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.6.tgz",
|
864 |
+
"integrity": "sha512-ELq+gDMBuRXPJlpE3PEen+1MhnHAQQrh2zF0dI1NXOlEWfr2qWf2CQdr5jl9yANv8RErQaQ2l6nIFO9OSCVq/g==",
|
865 |
+
"dev": true,
|
866 |
+
"dependencies": {
|
867 |
+
"@alloc/quick-lru": "^5.2.0",
|
868 |
+
"@tailwindcss/node": "4.1.6",
|
869 |
+
"@tailwindcss/oxide": "4.1.6",
|
870 |
+
"postcss": "^8.4.41",
|
871 |
+
"tailwindcss": "4.1.6"
|
872 |
+
}
|
873 |
+
},
|
874 |
+
"node_modules/@types/node": {
|
875 |
+
"version": "20.17.47",
|
876 |
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.47.tgz",
|
877 |
+
"integrity": "sha512-3dLX0Upo1v7RvUimvxLeXqwrfyKxUINk0EAM83swP2mlSUcwV73sZy8XhNz8bcZ3VbsfQyC/y6jRdL5tgCNpDQ==",
|
878 |
+
"dev": true,
|
879 |
+
"dependencies": {
|
880 |
+
"undici-types": "~6.19.2"
|
881 |
+
}
|
882 |
+
},
|
883 |
+
"node_modules/@types/react": {
|
884 |
+
"version": "19.1.4",
|
885 |
+
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.4.tgz",
|
886 |
+
"integrity": "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==",
|
887 |
+
"dev": true,
|
888 |
+
"dependencies": {
|
889 |
+
"csstype": "^3.0.2"
|
890 |
+
}
|
891 |
+
},
|
892 |
+
"node_modules/@types/react-dom": {
|
893 |
+
"version": "19.1.5",
|
894 |
+
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz",
|
895 |
+
"integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==",
|
896 |
+
"dev": true,
|
897 |
+
"peerDependencies": {
|
898 |
+
"@types/react": "^19.0.0"
|
899 |
+
}
|
900 |
+
},
|
901 |
+
"node_modules/busboy": {
|
902 |
+
"version": "1.6.0",
|
903 |
+
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
904 |
+
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
905 |
+
"dependencies": {
|
906 |
+
"streamsearch": "^1.1.0"
|
907 |
+
},
|
908 |
+
"engines": {
|
909 |
+
"node": ">=10.16.0"
|
910 |
+
}
|
911 |
+
},
|
912 |
+
"node_modules/caniuse-lite": {
|
913 |
+
"version": "1.0.30001718",
|
914 |
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz",
|
915 |
+
"integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==",
|
916 |
+
"funding": [
|
917 |
+
{
|
918 |
+
"type": "opencollective",
|
919 |
+
"url": "https://opencollective.com/browserslist"
|
920 |
+
},
|
921 |
+
{
|
922 |
+
"type": "tidelift",
|
923 |
+
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
924 |
+
},
|
925 |
+
{
|
926 |
+
"type": "github",
|
927 |
+
"url": "https://github.com/sponsors/ai"
|
928 |
+
}
|
929 |
+
]
|
930 |
+
},
|
931 |
+
"node_modules/chownr": {
|
932 |
+
"version": "3.0.0",
|
933 |
+
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
934 |
+
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
|
935 |
+
"dev": true,
|
936 |
+
"engines": {
|
937 |
+
"node": ">=18"
|
938 |
+
}
|
939 |
+
},
|
940 |
+
"node_modules/client-only": {
|
941 |
+
"version": "0.0.1",
|
942 |
+
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
943 |
+
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
944 |
+
},
|
945 |
+
"node_modules/color": {
|
946 |
+
"version": "4.2.3",
|
947 |
+
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
948 |
+
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
949 |
+
"optional": true,
|
950 |
+
"dependencies": {
|
951 |
+
"color-convert": "^2.0.1",
|
952 |
+
"color-string": "^1.9.0"
|
953 |
+
},
|
954 |
+
"engines": {
|
955 |
+
"node": ">=12.5.0"
|
956 |
+
}
|
957 |
+
},
|
958 |
+
"node_modules/color-convert": {
|
959 |
+
"version": "2.0.1",
|
960 |
+
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
961 |
+
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
962 |
+
"optional": true,
|
963 |
+
"dependencies": {
|
964 |
+
"color-name": "~1.1.4"
|
965 |
+
},
|
966 |
+
"engines": {
|
967 |
+
"node": ">=7.0.0"
|
968 |
+
}
|
969 |
+
},
|
970 |
+
"node_modules/color-name": {
|
971 |
+
"version": "1.1.4",
|
972 |
+
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
973 |
+
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
974 |
+
"optional": true
|
975 |
+
},
|
976 |
+
"node_modules/color-string": {
|
977 |
+
"version": "1.9.1",
|
978 |
+
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
979 |
+
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
980 |
+
"optional": true,
|
981 |
+
"dependencies": {
|
982 |
+
"color-name": "^1.0.0",
|
983 |
+
"simple-swizzle": "^0.2.2"
|
984 |
+
}
|
985 |
+
},
|
986 |
+
"node_modules/csstype": {
|
987 |
+
"version": "3.1.3",
|
988 |
+
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
989 |
+
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
990 |
+
"dev": true
|
991 |
+
},
|
992 |
+
"node_modules/detect-libc": {
|
993 |
+
"version": "2.0.4",
|
994 |
+
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
995 |
+
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
|
996 |
+
"devOptional": true,
|
997 |
+
"engines": {
|
998 |
+
"node": ">=8"
|
999 |
+
}
|
1000 |
+
},
|
1001 |
+
"node_modules/enhanced-resolve": {
|
1002 |
+
"version": "5.18.1",
|
1003 |
+
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
1004 |
+
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
|
1005 |
+
"dev": true,
|
1006 |
+
"dependencies": {
|
1007 |
+
"graceful-fs": "^4.2.4",
|
1008 |
+
"tapable": "^2.2.0"
|
1009 |
+
},
|
1010 |
+
"engines": {
|
1011 |
+
"node": ">=10.13.0"
|
1012 |
+
}
|
1013 |
+
},
|
1014 |
+
"node_modules/graceful-fs": {
|
1015 |
+
"version": "4.2.11",
|
1016 |
+
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
1017 |
+
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
1018 |
+
"dev": true
|
1019 |
+
},
|
1020 |
+
"node_modules/is-arrayish": {
|
1021 |
+
"version": "0.3.2",
|
1022 |
+
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
1023 |
+
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
|
1024 |
+
"optional": true
|
1025 |
+
},
|
1026 |
+
"node_modules/jiti": {
|
1027 |
+
"version": "2.4.2",
|
1028 |
+
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
|
1029 |
+
"integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
|
1030 |
+
"dev": true,
|
1031 |
+
"bin": {
|
1032 |
+
"jiti": "lib/jiti-cli.mjs"
|
1033 |
+
}
|
1034 |
+
},
|
1035 |
+
"node_modules/lightningcss": {
|
1036 |
+
"version": "1.29.2",
|
1037 |
+
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
|
1038 |
+
"integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==",
|
1039 |
+
"dev": true,
|
1040 |
+
"dependencies": {
|
1041 |
+
"detect-libc": "^2.0.3"
|
1042 |
+
},
|
1043 |
+
"engines": {
|
1044 |
+
"node": ">= 12.0.0"
|
1045 |
+
},
|
1046 |
+
"funding": {
|
1047 |
+
"type": "opencollective",
|
1048 |
+
"url": "https://opencollective.com/parcel"
|
1049 |
+
},
|
1050 |
+
"optionalDependencies": {
|
1051 |
+
"lightningcss-darwin-arm64": "1.29.2",
|
1052 |
+
"lightningcss-darwin-x64": "1.29.2",
|
1053 |
+
"lightningcss-freebsd-x64": "1.29.2",
|
1054 |
+
"lightningcss-linux-arm-gnueabihf": "1.29.2",
|
1055 |
+
"lightningcss-linux-arm64-gnu": "1.29.2",
|
1056 |
+
"lightningcss-linux-arm64-musl": "1.29.2",
|
1057 |
+
"lightningcss-linux-x64-gnu": "1.29.2",
|
1058 |
+
"lightningcss-linux-x64-musl": "1.29.2",
|
1059 |
+
"lightningcss-win32-arm64-msvc": "1.29.2",
|
1060 |
+
"lightningcss-win32-x64-msvc": "1.29.2"
|
1061 |
+
}
|
1062 |
+
},
|
1063 |
+
"node_modules/lightningcss-darwin-arm64": {
|
1064 |
+
"version": "1.29.2",
|
1065 |
+
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz",
|
1066 |
+
"integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==",
|
1067 |
+
"cpu": [
|
1068 |
+
"arm64"
|
1069 |
+
],
|
1070 |
+
"dev": true,
|
1071 |
+
"optional": true,
|
1072 |
+
"os": [
|
1073 |
+
"darwin"
|
1074 |
+
],
|
1075 |
+
"engines": {
|
1076 |
+
"node": ">= 12.0.0"
|
1077 |
+
},
|
1078 |
+
"funding": {
|
1079 |
+
"type": "opencollective",
|
1080 |
+
"url": "https://opencollective.com/parcel"
|
1081 |
+
}
|
1082 |
+
},
|
1083 |
+
"node_modules/lightningcss-darwin-x64": {
|
1084 |
+
"version": "1.29.2",
|
1085 |
+
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz",
|
1086 |
+
"integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==",
|
1087 |
+
"cpu": [
|
1088 |
+
"x64"
|
1089 |
+
],
|
1090 |
+
"dev": true,
|
1091 |
+
"optional": true,
|
1092 |
+
"os": [
|
1093 |
+
"darwin"
|
1094 |
+
],
|
1095 |
+
"engines": {
|
1096 |
+
"node": ">= 12.0.0"
|
1097 |
+
},
|
1098 |
+
"funding": {
|
1099 |
+
"type": "opencollective",
|
1100 |
+
"url": "https://opencollective.com/parcel"
|
1101 |
+
}
|
1102 |
+
},
|
1103 |
+
"node_modules/lightningcss-freebsd-x64": {
|
1104 |
+
"version": "1.29.2",
|
1105 |
+
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz",
|
1106 |
+
"integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==",
|
1107 |
+
"cpu": [
|
1108 |
+
"x64"
|
1109 |
+
],
|
1110 |
+
"dev": true,
|
1111 |
+
"optional": true,
|
1112 |
+
"os": [
|
1113 |
+
"freebsd"
|
1114 |
+
],
|
1115 |
+
"engines": {
|
1116 |
+
"node": ">= 12.0.0"
|
1117 |
+
},
|
1118 |
+
"funding": {
|
1119 |
+
"type": "opencollective",
|
1120 |
+
"url": "https://opencollective.com/parcel"
|
1121 |
+
}
|
1122 |
+
},
|
1123 |
+
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
1124 |
+
"version": "1.29.2",
|
1125 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz",
|
1126 |
+
"integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==",
|
1127 |
+
"cpu": [
|
1128 |
+
"arm"
|
1129 |
+
],
|
1130 |
+
"dev": true,
|
1131 |
+
"optional": true,
|
1132 |
+
"os": [
|
1133 |
+
"linux"
|
1134 |
+
],
|
1135 |
+
"engines": {
|
1136 |
+
"node": ">= 12.0.0"
|
1137 |
+
},
|
1138 |
+
"funding": {
|
1139 |
+
"type": "opencollective",
|
1140 |
+
"url": "https://opencollective.com/parcel"
|
1141 |
+
}
|
1142 |
+
},
|
1143 |
+
"node_modules/lightningcss-linux-arm64-gnu": {
|
1144 |
+
"version": "1.29.2",
|
1145 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz",
|
1146 |
+
"integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==",
|
1147 |
+
"cpu": [
|
1148 |
+
"arm64"
|
1149 |
+
],
|
1150 |
+
"dev": true,
|
1151 |
+
"optional": true,
|
1152 |
+
"os": [
|
1153 |
+
"linux"
|
1154 |
+
],
|
1155 |
+
"engines": {
|
1156 |
+
"node": ">= 12.0.0"
|
1157 |
+
},
|
1158 |
+
"funding": {
|
1159 |
+
"type": "opencollective",
|
1160 |
+
"url": "https://opencollective.com/parcel"
|
1161 |
+
}
|
1162 |
+
},
|
1163 |
+
"node_modules/lightningcss-linux-arm64-musl": {
|
1164 |
+
"version": "1.29.2",
|
1165 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz",
|
1166 |
+
"integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==",
|
1167 |
+
"cpu": [
|
1168 |
+
"arm64"
|
1169 |
+
],
|
1170 |
+
"dev": true,
|
1171 |
+
"optional": true,
|
1172 |
+
"os": [
|
1173 |
+
"linux"
|
1174 |
+
],
|
1175 |
+
"engines": {
|
1176 |
+
"node": ">= 12.0.0"
|
1177 |
+
},
|
1178 |
+
"funding": {
|
1179 |
+
"type": "opencollective",
|
1180 |
+
"url": "https://opencollective.com/parcel"
|
1181 |
+
}
|
1182 |
+
},
|
1183 |
+
"node_modules/lightningcss-linux-x64-gnu": {
|
1184 |
+
"version": "1.29.2",
|
1185 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz",
|
1186 |
+
"integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==",
|
1187 |
+
"cpu": [
|
1188 |
+
"x64"
|
1189 |
+
],
|
1190 |
+
"dev": true,
|
1191 |
+
"optional": true,
|
1192 |
+
"os": [
|
1193 |
+
"linux"
|
1194 |
+
],
|
1195 |
+
"engines": {
|
1196 |
+
"node": ">= 12.0.0"
|
1197 |
+
},
|
1198 |
+
"funding": {
|
1199 |
+
"type": "opencollective",
|
1200 |
+
"url": "https://opencollective.com/parcel"
|
1201 |
+
}
|
1202 |
+
},
|
1203 |
+
"node_modules/lightningcss-linux-x64-musl": {
|
1204 |
+
"version": "1.29.2",
|
1205 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz",
|
1206 |
+
"integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==",
|
1207 |
+
"cpu": [
|
1208 |
+
"x64"
|
1209 |
+
],
|
1210 |
+
"dev": true,
|
1211 |
+
"optional": true,
|
1212 |
+
"os": [
|
1213 |
+
"linux"
|
1214 |
+
],
|
1215 |
+
"engines": {
|
1216 |
+
"node": ">= 12.0.0"
|
1217 |
+
},
|
1218 |
+
"funding": {
|
1219 |
+
"type": "opencollective",
|
1220 |
+
"url": "https://opencollective.com/parcel"
|
1221 |
+
}
|
1222 |
+
},
|
1223 |
+
"node_modules/lightningcss-win32-arm64-msvc": {
|
1224 |
+
"version": "1.29.2",
|
1225 |
+
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz",
|
1226 |
+
"integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==",
|
1227 |
+
"cpu": [
|
1228 |
+
"arm64"
|
1229 |
+
],
|
1230 |
+
"dev": true,
|
1231 |
+
"optional": true,
|
1232 |
+
"os": [
|
1233 |
+
"win32"
|
1234 |
+
],
|
1235 |
+
"engines": {
|
1236 |
+
"node": ">= 12.0.0"
|
1237 |
+
},
|
1238 |
+
"funding": {
|
1239 |
+
"type": "opencollective",
|
1240 |
+
"url": "https://opencollective.com/parcel"
|
1241 |
+
}
|
1242 |
+
},
|
1243 |
+
"node_modules/lightningcss-win32-x64-msvc": {
|
1244 |
+
"version": "1.29.2",
|
1245 |
+
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz",
|
1246 |
+
"integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==",
|
1247 |
+
"cpu": [
|
1248 |
+
"x64"
|
1249 |
+
],
|
1250 |
+
"dev": true,
|
1251 |
+
"optional": true,
|
1252 |
+
"os": [
|
1253 |
+
"win32"
|
1254 |
+
],
|
1255 |
+
"engines": {
|
1256 |
+
"node": ">= 12.0.0"
|
1257 |
+
},
|
1258 |
+
"funding": {
|
1259 |
+
"type": "opencollective",
|
1260 |
+
"url": "https://opencollective.com/parcel"
|
1261 |
+
}
|
1262 |
+
},
|
1263 |
+
"node_modules/magic-string": {
|
1264 |
+
"version": "0.30.17",
|
1265 |
+
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
1266 |
+
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
|
1267 |
+
"dev": true,
|
1268 |
+
"dependencies": {
|
1269 |
+
"@jridgewell/sourcemap-codec": "^1.5.0"
|
1270 |
+
}
|
1271 |
+
},
|
1272 |
+
"node_modules/minipass": {
|
1273 |
+
"version": "7.1.2",
|
1274 |
+
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
1275 |
+
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
1276 |
+
"dev": true,
|
1277 |
+
"engines": {
|
1278 |
+
"node": ">=16 || 14 >=14.17"
|
1279 |
+
}
|
1280 |
+
},
|
1281 |
+
"node_modules/minizlib": {
|
1282 |
+
"version": "3.0.2",
|
1283 |
+
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
|
1284 |
+
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
|
1285 |
+
"dev": true,
|
1286 |
+
"dependencies": {
|
1287 |
+
"minipass": "^7.1.2"
|
1288 |
+
},
|
1289 |
+
"engines": {
|
1290 |
+
"node": ">= 18"
|
1291 |
+
}
|
1292 |
+
},
|
1293 |
+
"node_modules/mkdirp": {
|
1294 |
+
"version": "3.0.1",
|
1295 |
+
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
|
1296 |
+
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
|
1297 |
+
"dev": true,
|
1298 |
+
"bin": {
|
1299 |
+
"mkdirp": "dist/cjs/src/bin.js"
|
1300 |
+
},
|
1301 |
+
"engines": {
|
1302 |
+
"node": ">=10"
|
1303 |
+
},
|
1304 |
+
"funding": {
|
1305 |
+
"url": "https://github.com/sponsors/isaacs"
|
1306 |
+
}
|
1307 |
+
},
|
1308 |
+
"node_modules/nanoid": {
|
1309 |
+
"version": "3.3.11",
|
1310 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
1311 |
+
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
1312 |
+
"funding": [
|
1313 |
+
{
|
1314 |
+
"type": "github",
|
1315 |
+
"url": "https://github.com/sponsors/ai"
|
1316 |
+
}
|
1317 |
+
],
|
1318 |
+
"bin": {
|
1319 |
+
"nanoid": "bin/nanoid.cjs"
|
1320 |
+
},
|
1321 |
+
"engines": {
|
1322 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
1323 |
+
}
|
1324 |
+
},
|
1325 |
+
"node_modules/next": {
|
1326 |
+
"version": "15.3.2",
|
1327 |
+
"resolved": "https://registry.npmjs.org/next/-/next-15.3.2.tgz",
|
1328 |
+
"integrity": "sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ==",
|
1329 |
+
"dependencies": {
|
1330 |
+
"@next/env": "15.3.2",
|
1331 |
+
"@swc/counter": "0.1.3",
|
1332 |
+
"@swc/helpers": "0.5.15",
|
1333 |
+
"busboy": "1.6.0",
|
1334 |
+
"caniuse-lite": "^1.0.30001579",
|
1335 |
+
"postcss": "8.4.31",
|
1336 |
+
"styled-jsx": "5.1.6"
|
1337 |
+
},
|
1338 |
+
"bin": {
|
1339 |
+
"next": "dist/bin/next"
|
1340 |
+
},
|
1341 |
+
"engines": {
|
1342 |
+
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
1343 |
+
},
|
1344 |
+
"optionalDependencies": {
|
1345 |
+
"@next/swc-darwin-arm64": "15.3.2",
|
1346 |
+
"@next/swc-darwin-x64": "15.3.2",
|
1347 |
+
"@next/swc-linux-arm64-gnu": "15.3.2",
|
1348 |
+
"@next/swc-linux-arm64-musl": "15.3.2",
|
1349 |
+
"@next/swc-linux-x64-gnu": "15.3.2",
|
1350 |
+
"@next/swc-linux-x64-musl": "15.3.2",
|
1351 |
+
"@next/swc-win32-arm64-msvc": "15.3.2",
|
1352 |
+
"@next/swc-win32-x64-msvc": "15.3.2",
|
1353 |
+
"sharp": "^0.34.1"
|
1354 |
+
},
|
1355 |
+
"peerDependencies": {
|
1356 |
+
"@opentelemetry/api": "^1.1.0",
|
1357 |
+
"@playwright/test": "^1.41.2",
|
1358 |
+
"babel-plugin-react-compiler": "*",
|
1359 |
+
"react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
|
1360 |
+
"react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
|
1361 |
+
"sass": "^1.3.0"
|
1362 |
+
},
|
1363 |
+
"peerDependenciesMeta": {
|
1364 |
+
"@opentelemetry/api": {
|
1365 |
+
"optional": true
|
1366 |
+
},
|
1367 |
+
"@playwright/test": {
|
1368 |
+
"optional": true
|
1369 |
+
},
|
1370 |
+
"babel-plugin-react-compiler": {
|
1371 |
+
"optional": true
|
1372 |
+
},
|
1373 |
+
"sass": {
|
1374 |
+
"optional": true
|
1375 |
+
}
|
1376 |
+
}
|
1377 |
+
},
|
1378 |
+
"node_modules/next/node_modules/postcss": {
|
1379 |
+
"version": "8.4.31",
|
1380 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
1381 |
+
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
1382 |
+
"funding": [
|
1383 |
+
{
|
1384 |
+
"type": "opencollective",
|
1385 |
+
"url": "https://opencollective.com/postcss/"
|
1386 |
+
},
|
1387 |
+
{
|
1388 |
+
"type": "tidelift",
|
1389 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
1390 |
+
},
|
1391 |
+
{
|
1392 |
+
"type": "github",
|
1393 |
+
"url": "https://github.com/sponsors/ai"
|
1394 |
+
}
|
1395 |
+
],
|
1396 |
+
"dependencies": {
|
1397 |
+
"nanoid": "^3.3.6",
|
1398 |
+
"picocolors": "^1.0.0",
|
1399 |
+
"source-map-js": "^1.0.2"
|
1400 |
+
},
|
1401 |
+
"engines": {
|
1402 |
+
"node": "^10 || ^12 || >=14"
|
1403 |
+
}
|
1404 |
+
},
|
1405 |
+
"node_modules/picocolors": {
|
1406 |
+
"version": "1.1.1",
|
1407 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
1408 |
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
1409 |
+
},
|
1410 |
+
"node_modules/postcss": {
|
1411 |
+
"version": "8.5.3",
|
1412 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
1413 |
+
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
|
1414 |
+
"dev": true,
|
1415 |
+
"funding": [
|
1416 |
+
{
|
1417 |
+
"type": "opencollective",
|
1418 |
+
"url": "https://opencollective.com/postcss/"
|
1419 |
+
},
|
1420 |
+
{
|
1421 |
+
"type": "tidelift",
|
1422 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
1423 |
+
},
|
1424 |
+
{
|
1425 |
+
"type": "github",
|
1426 |
+
"url": "https://github.com/sponsors/ai"
|
1427 |
+
}
|
1428 |
+
],
|
1429 |
+
"dependencies": {
|
1430 |
+
"nanoid": "^3.3.8",
|
1431 |
+
"picocolors": "^1.1.1",
|
1432 |
+
"source-map-js": "^1.2.1"
|
1433 |
+
},
|
1434 |
+
"engines": {
|
1435 |
+
"node": "^10 || ^12 || >=14"
|
1436 |
+
}
|
1437 |
+
},
|
1438 |
+
"node_modules/react": {
|
1439 |
+
"version": "19.1.0",
|
1440 |
+
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
1441 |
+
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
1442 |
+
"engines": {
|
1443 |
+
"node": ">=0.10.0"
|
1444 |
+
}
|
1445 |
+
},
|
1446 |
+
"node_modules/react-dom": {
|
1447 |
+
"version": "19.1.0",
|
1448 |
+
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
1449 |
+
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
1450 |
+
"dependencies": {
|
1451 |
+
"scheduler": "^0.26.0"
|
1452 |
+
},
|
1453 |
+
"peerDependencies": {
|
1454 |
+
"react": "^19.1.0"
|
1455 |
+
}
|
1456 |
+
},
|
1457 |
+
"node_modules/scheduler": {
|
1458 |
+
"version": "0.26.0",
|
1459 |
+
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
1460 |
+
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="
|
1461 |
+
},
|
1462 |
+
"node_modules/semver": {
|
1463 |
+
"version": "7.7.2",
|
1464 |
+
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
1465 |
+
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
1466 |
+
"optional": true,
|
1467 |
+
"bin": {
|
1468 |
+
"semver": "bin/semver.js"
|
1469 |
+
},
|
1470 |
+
"engines": {
|
1471 |
+
"node": ">=10"
|
1472 |
+
}
|
1473 |
+
},
|
1474 |
+
"node_modules/sharp": {
|
1475 |
+
"version": "0.34.1",
|
1476 |
+
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz",
|
1477 |
+
"integrity": "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==",
|
1478 |
+
"hasInstallScript": true,
|
1479 |
+
"optional": true,
|
1480 |
+
"dependencies": {
|
1481 |
+
"color": "^4.2.3",
|
1482 |
+
"detect-libc": "^2.0.3",
|
1483 |
+
"semver": "^7.7.1"
|
1484 |
+
},
|
1485 |
+
"engines": {
|
1486 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
1487 |
+
},
|
1488 |
+
"funding": {
|
1489 |
+
"url": "https://opencollective.com/libvips"
|
1490 |
+
},
|
1491 |
+
"optionalDependencies": {
|
1492 |
+
"@img/sharp-darwin-arm64": "0.34.1",
|
1493 |
+
"@img/sharp-darwin-x64": "0.34.1",
|
1494 |
+
"@img/sharp-libvips-darwin-arm64": "1.1.0",
|
1495 |
+
"@img/sharp-libvips-darwin-x64": "1.1.0",
|
1496 |
+
"@img/sharp-libvips-linux-arm": "1.1.0",
|
1497 |
+
"@img/sharp-libvips-linux-arm64": "1.1.0",
|
1498 |
+
"@img/sharp-libvips-linux-ppc64": "1.1.0",
|
1499 |
+
"@img/sharp-libvips-linux-s390x": "1.1.0",
|
1500 |
+
"@img/sharp-libvips-linux-x64": "1.1.0",
|
1501 |
+
"@img/sharp-libvips-linuxmusl-arm64": "1.1.0",
|
1502 |
+
"@img/sharp-libvips-linuxmusl-x64": "1.1.0",
|
1503 |
+
"@img/sharp-linux-arm": "0.34.1",
|
1504 |
+
"@img/sharp-linux-arm64": "0.34.1",
|
1505 |
+
"@img/sharp-linux-s390x": "0.34.1",
|
1506 |
+
"@img/sharp-linux-x64": "0.34.1",
|
1507 |
+
"@img/sharp-linuxmusl-arm64": "0.34.1",
|
1508 |
+
"@img/sharp-linuxmusl-x64": "0.34.1",
|
1509 |
+
"@img/sharp-wasm32": "0.34.1",
|
1510 |
+
"@img/sharp-win32-ia32": "0.34.1",
|
1511 |
+
"@img/sharp-win32-x64": "0.34.1"
|
1512 |
+
}
|
1513 |
+
},
|
1514 |
+
"node_modules/simple-swizzle": {
|
1515 |
+
"version": "0.2.2",
|
1516 |
+
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
1517 |
+
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
1518 |
+
"optional": true,
|
1519 |
+
"dependencies": {
|
1520 |
+
"is-arrayish": "^0.3.1"
|
1521 |
+
}
|
1522 |
+
},
|
1523 |
+
"node_modules/source-map-js": {
|
1524 |
+
"version": "1.2.1",
|
1525 |
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
1526 |
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
1527 |
+
"engines": {
|
1528 |
+
"node": ">=0.10.0"
|
1529 |
+
}
|
1530 |
+
},
|
1531 |
+
"node_modules/streamsearch": {
|
1532 |
+
"version": "1.1.0",
|
1533 |
+
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
1534 |
+
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
1535 |
+
"engines": {
|
1536 |
+
"node": ">=10.0.0"
|
1537 |
+
}
|
1538 |
+
},
|
1539 |
+
"node_modules/styled-jsx": {
|
1540 |
+
"version": "5.1.6",
|
1541 |
+
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
1542 |
+
"integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
|
1543 |
+
"dependencies": {
|
1544 |
+
"client-only": "0.0.1"
|
1545 |
+
},
|
1546 |
+
"engines": {
|
1547 |
+
"node": ">= 12.0.0"
|
1548 |
+
},
|
1549 |
+
"peerDependencies": {
|
1550 |
+
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
|
1551 |
+
},
|
1552 |
+
"peerDependenciesMeta": {
|
1553 |
+
"@babel/core": {
|
1554 |
+
"optional": true
|
1555 |
+
},
|
1556 |
+
"babel-plugin-macros": {
|
1557 |
+
"optional": true
|
1558 |
+
}
|
1559 |
+
}
|
1560 |
+
},
|
1561 |
+
"node_modules/tailwindcss": {
|
1562 |
+
"version": "4.1.6",
|
1563 |
+
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.6.tgz",
|
1564 |
+
"integrity": "sha512-j0cGLTreM6u4OWzBeLBpycK0WIh8w7kSwcUsQZoGLHZ7xDTdM69lN64AgoIEEwFi0tnhs4wSykUa5YWxAzgFYg==",
|
1565 |
+
"dev": true
|
1566 |
+
},
|
1567 |
+
"node_modules/tapable": {
|
1568 |
+
"version": "2.2.1",
|
1569 |
+
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
1570 |
+
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
|
1571 |
+
"dev": true,
|
1572 |
+
"engines": {
|
1573 |
+
"node": ">=6"
|
1574 |
+
}
|
1575 |
+
},
|
1576 |
+
"node_modules/tar": {
|
1577 |
+
"version": "7.4.3",
|
1578 |
+
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
|
1579 |
+
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
|
1580 |
+
"dev": true,
|
1581 |
+
"dependencies": {
|
1582 |
+
"@isaacs/fs-minipass": "^4.0.0",
|
1583 |
+
"chownr": "^3.0.0",
|
1584 |
+
"minipass": "^7.1.2",
|
1585 |
+
"minizlib": "^3.0.1",
|
1586 |
+
"mkdirp": "^3.0.1",
|
1587 |
+
"yallist": "^5.0.0"
|
1588 |
+
},
|
1589 |
+
"engines": {
|
1590 |
+
"node": ">=18"
|
1591 |
+
}
|
1592 |
+
},
|
1593 |
+
"node_modules/tslib": {
|
1594 |
+
"version": "2.8.1",
|
1595 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
1596 |
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
1597 |
+
},
|
1598 |
+
"node_modules/typescript": {
|
1599 |
+
"version": "5.8.3",
|
1600 |
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
1601 |
+
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
1602 |
+
"dev": true,
|
1603 |
+
"bin": {
|
1604 |
+
"tsc": "bin/tsc",
|
1605 |
+
"tsserver": "bin/tsserver"
|
1606 |
+
},
|
1607 |
+
"engines": {
|
1608 |
+
"node": ">=14.17"
|
1609 |
+
}
|
1610 |
+
},
|
1611 |
+
"node_modules/undici-types": {
|
1612 |
+
"version": "6.19.8",
|
1613 |
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
1614 |
+
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
1615 |
+
"dev": true
|
1616 |
+
},
|
1617 |
+
"node_modules/yallist": {
|
1618 |
+
"version": "5.0.0",
|
1619 |
+
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
1620 |
+
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
|
1621 |
+
"dev": true,
|
1622 |
+
"engines": {
|
1623 |
+
"node": ">=18"
|
1624 |
+
}
|
1625 |
+
}
|
1626 |
+
}
|
1627 |
+
}
|
package.json
ADDED
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "my-v0-project",
|
3 |
+
"version": "0.1.0",
|
4 |
+
"private": true,
|
5 |
+
"scripts": {
|
6 |
+
"dev": "next dev -p ${APP_PORT:-5001}",
|
7 |
+
"build": "next build",
|
8 |
+
"start": "next start -p ${APP_PORT:-5001}",
|
9 |
+
"lint": "next lint"
|
10 |
+
},
|
11 |
+
"dependencies": {
|
12 |
+
"@hookform/resolvers": "^3.9.1",
|
13 |
+
"@huggingface/hub": "^1.1.2",
|
14 |
+
"@huggingface/inference": "^3.13.1",
|
15 |
+
"@radix-ui/react-accordion": "1.2.2",
|
16 |
+
"@radix-ui/react-alert-dialog": "1.1.4",
|
17 |
+
"@radix-ui/react-aspect-ratio": "1.1.1",
|
18 |
+
"@radix-ui/react-avatar": "1.1.2",
|
19 |
+
"@radix-ui/react-checkbox": "1.1.3",
|
20 |
+
"@radix-ui/react-collapsible": "1.1.2",
|
21 |
+
"@radix-ui/react-context-menu": "2.2.4",
|
22 |
+
"@radix-ui/react-dialog": "1.1.4",
|
23 |
+
"@radix-ui/react-dropdown-menu": "2.1.4",
|
24 |
+
"@radix-ui/react-hover-card": "1.1.4",
|
25 |
+
"@radix-ui/react-label": "2.1.1",
|
26 |
+
"@radix-ui/react-menubar": "1.1.4",
|
27 |
+
"@radix-ui/react-navigation-menu": "1.2.3",
|
28 |
+
"@radix-ui/react-popover": "latest",
|
29 |
+
"@radix-ui/react-progress": "1.1.1",
|
30 |
+
"@radix-ui/react-radio-group": "1.2.2",
|
31 |
+
"@radix-ui/react-scroll-area": "1.2.2",
|
32 |
+
"@radix-ui/react-select": "2.1.4",
|
33 |
+
"@radix-ui/react-separator": "1.1.1",
|
34 |
+
"@radix-ui/react-slider": "1.2.2",
|
35 |
+
"@radix-ui/react-slot": "latest",
|
36 |
+
"@radix-ui/react-switch": "1.1.2",
|
37 |
+
"@radix-ui/react-tabs": "1.1.2",
|
38 |
+
"@radix-ui/react-toast": "1.2.4",
|
39 |
+
"@radix-ui/react-toggle": "1.1.1",
|
40 |
+
"@radix-ui/react-toggle-group": "1.1.1",
|
41 |
+
"@radix-ui/react-tooltip": "latest",
|
42 |
+
"autoprefixer": "^10.4.20",
|
43 |
+
"class-variance-authority": "^0.7.1",
|
44 |
+
"clsx": "^2.1.1",
|
45 |
+
"cmdk": "1.0.4",
|
46 |
+
"date-fns": "4.1.0",
|
47 |
+
"embla-carousel-react": "8.5.1",
|
48 |
+
"input-otp": "1.4.1",
|
49 |
+
"lucide-react": "^0.454.0",
|
50 |
+
"next": "15.2.4",
|
51 |
+
"next-themes": "^0.4.4",
|
52 |
+
"react": "^19",
|
53 |
+
"react-day-picker": "8.10.1",
|
54 |
+
"react-dom": "^19",
|
55 |
+
"react-hook-form": "^7.54.1",
|
56 |
+
"react-resizable-panels": "^2.1.7",
|
57 |
+
"recharts": "2.15.0",
|
58 |
+
"sonner": "^1.7.1",
|
59 |
+
"tailwind-merge": "^2.5.5",
|
60 |
+
"tailwindcss-animate": "^1.0.7",
|
61 |
+
"vaul": "^0.9.6",
|
62 |
+
"zod": "^3.24.1"
|
63 |
+
},
|
64 |
+
"devDependencies": {
|
65 |
+
"@types/node": "^22",
|
66 |
+
"@types/react": "^19",
|
67 |
+
"@types/react-dom": "^19",
|
68 |
+
"postcss": "^8",
|
69 |
+
"tailwindcss": "^3.4.17",
|
70 |
+
"typescript": "^5"
|
71 |
+
}
|
72 |
+
}
|
pnpm-lock.yaml
ADDED
The diff for this file is too large to render.
See raw diff
|
|
postcss.config.mjs
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/** @type {import('postcss-load-config').Config} */
|
2 |
+
const config = {
|
3 |
+
plugins: {
|
4 |
+
tailwindcss: {},
|
5 |
+
},
|
6 |
+
};
|
7 |
+
|
8 |
+
export default config;
|
public/placeholder-logo.png
ADDED
![]() |
public/placeholder-logo.svg
ADDED
|
public/placeholder-user.jpg
ADDED
![]() |
public/placeholder.jpg
ADDED
![]() |
public/placeholder.svg
ADDED
|
src/app/api/auth/login/route.ts
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { NextRequest, NextResponse } from "next/server";
|
2 |
+
|
3 |
+
export const dynamic = "force-dynamic";
|
4 |
+
|
5 |
+
export async function GET(request: NextRequest) {
|
6 |
+
const searchParams = request.nextUrl.searchParams;
|
7 |
+
const code = searchParams.get("code");
|
8 |
+
|
9 |
+
if (!code) {
|
10 |
+
return NextResponse.redirect(new URL("/", request.url));
|
11 |
+
}
|
12 |
+
|
13 |
+
const port = process.env.APP_PORT || 3000;
|
14 |
+
const redirectUri = process.env.REDIRECT_URI || `http://localhost:${port}/auth/login`;
|
15 |
+
const Authorization = `Basic ${Buffer.from(
|
16 |
+
`${process.env.OAUTH_CLIENT_ID}:${process.env.OAUTH_CLIENT_SECRET}`
|
17 |
+
).toString("base64")}`;
|
18 |
+
|
19 |
+
try {
|
20 |
+
const request_auth = await fetch("https://huggingface.co/oauth/token", {
|
21 |
+
method: "POST",
|
22 |
+
headers: {
|
23 |
+
"Content-Type": "application/x-www-form-urlencoded",
|
24 |
+
Authorization,
|
25 |
+
},
|
26 |
+
body: new URLSearchParams({
|
27 |
+
grant_type: "authorization_code",
|
28 |
+
code: code,
|
29 |
+
redirect_uri: redirectUri,
|
30 |
+
}),
|
31 |
+
});
|
32 |
+
|
33 |
+
const response = await request_auth.json();
|
34 |
+
|
35 |
+
if (!response.access_token) {
|
36 |
+
return NextResponse.redirect(new URL("/", request.url));
|
37 |
+
}
|
38 |
+
|
39 |
+
const res = NextResponse.redirect(new URL("/", request.url));
|
40 |
+
res.cookies.set("hf_token", response.access_token, {
|
41 |
+
httpOnly: false,
|
42 |
+
secure: true,
|
43 |
+
sameSite: "none",
|
44 |
+
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
|
45 |
+
path: "/",
|
46 |
+
});
|
47 |
+
|
48 |
+
return res;
|
49 |
+
} catch (error) {
|
50 |
+
return NextResponse.redirect(new URL("/", request.url));
|
51 |
+
}
|
52 |
+
}
|
src/app/api/auth/logout/route.ts
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { NextResponse } from "next/server";
|
2 |
+
import { cookies } from "next/headers";
|
3 |
+
|
4 |
+
export const dynamic = "force-dynamic";
|
5 |
+
|
6 |
+
export async function GET() {
|
7 |
+
const res = NextResponse.redirect(new URL("/", process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000"));
|
8 |
+
|
9 |
+
res.cookies.set("hf_token", "", {
|
10 |
+
httpOnly: false,
|
11 |
+
secure: true,
|
12 |
+
sameSite: "none",
|
13 |
+
maxAge: 0,
|
14 |
+
path: "/",
|
15 |
+
});
|
16 |
+
|
17 |
+
return res;
|
18 |
+
}
|
src/app/api/generate-code/route.ts
ADDED
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { NextRequest, NextResponse } from "next/server";
|
2 |
+
import { MODEL_CONFIG_CODE_GENERATION } from "@/lib/constants";
|
3 |
+
import {
|
4 |
+
getInferenceToken,
|
5 |
+
checkTokenLimit,
|
6 |
+
createInferenceClient,
|
7 |
+
createStreamResponse,
|
8 |
+
getInferenceOptions
|
9 |
+
} from "@/lib/inference-utils";
|
10 |
+
|
11 |
+
export const dynamic = "force-dynamic";
|
12 |
+
const NO_THINK_TAG = " /no_think";
|
13 |
+
|
14 |
+
export async function POST(request: NextRequest) {
|
15 |
+
try {
|
16 |
+
const { prompt, html, previousPrompt, colors, modelId } = await request.json();
|
17 |
+
|
18 |
+
if (!prompt) {
|
19 |
+
return NextResponse.json(
|
20 |
+
{
|
21 |
+
ok: false,
|
22 |
+
message: "Missing required fields",
|
23 |
+
},
|
24 |
+
{ status: 400 }
|
25 |
+
);
|
26 |
+
}
|
27 |
+
|
28 |
+
// Find the model config by modelId or use the first one as default
|
29 |
+
const modelConfig = modelId
|
30 |
+
? MODEL_CONFIG_CODE_GENERATION.find(config => config.id === modelId) || MODEL_CONFIG_CODE_GENERATION[0]
|
31 |
+
: MODEL_CONFIG_CODE_GENERATION[0];
|
32 |
+
|
33 |
+
// Get inference token
|
34 |
+
const tokenResult = await getInferenceToken(request);
|
35 |
+
if (tokenResult.error) {
|
36 |
+
return NextResponse.json(
|
37 |
+
{
|
38 |
+
ok: false,
|
39 |
+
openLogin: tokenResult.error.openLogin,
|
40 |
+
message: tokenResult.error.message,
|
41 |
+
},
|
42 |
+
{ status: tokenResult.error.status }
|
43 |
+
);
|
44 |
+
}
|
45 |
+
|
46 |
+
// Calculate the estimated number of tokens used, this is not accurate.
|
47 |
+
let TOKENS_USED = prompt?.length || 0;
|
48 |
+
if (previousPrompt) TOKENS_USED += previousPrompt.length;
|
49 |
+
if (html) TOKENS_USED += html.length;
|
50 |
+
|
51 |
+
// Check token limit
|
52 |
+
const tokenLimitError = checkTokenLimit(TOKENS_USED, modelConfig);
|
53 |
+
if (tokenLimitError) {
|
54 |
+
return NextResponse.json(tokenLimitError, { status: 400 });
|
55 |
+
}
|
56 |
+
|
57 |
+
const actualNoThinkTag = modelConfig.default_enable_thinking ? NO_THINK_TAG : "";
|
58 |
+
|
59 |
+
// Use streaming response
|
60 |
+
return createStreamResponse(async (controller) => {
|
61 |
+
const client = createInferenceClient(tokenResult.token);
|
62 |
+
let completeResponse = "";
|
63 |
+
|
64 |
+
const messages = [
|
65 |
+
{
|
66 |
+
role: "system",
|
67 |
+
content: modelConfig.system_prompt,
|
68 |
+
},
|
69 |
+
...(previousPrompt
|
70 |
+
? [
|
71 |
+
{
|
72 |
+
role: "user",
|
73 |
+
content: previousPrompt,
|
74 |
+
},
|
75 |
+
]
|
76 |
+
: []),
|
77 |
+
...(html
|
78 |
+
? [
|
79 |
+
{
|
80 |
+
role: "assistant",
|
81 |
+
content: `The current code is: ${html}.`,
|
82 |
+
},
|
83 |
+
]
|
84 |
+
: []),
|
85 |
+
...((colors && colors.length > 0)
|
86 |
+
? [
|
87 |
+
{
|
88 |
+
role: "user",
|
89 |
+
content: `Use the following color palette in your UI design: ${colors.join(', ')}`,
|
90 |
+
},
|
91 |
+
]
|
92 |
+
: []),
|
93 |
+
{
|
94 |
+
role: "user",
|
95 |
+
content: prompt + actualNoThinkTag,
|
96 |
+
},
|
97 |
+
];
|
98 |
+
|
99 |
+
const chatCompletion = client.chatCompletionStream(
|
100 |
+
getInferenceOptions(modelConfig, messages, modelConfig.max_tokens)
|
101 |
+
);
|
102 |
+
const encoder = new TextEncoder();
|
103 |
+
for await (const chunk of chatCompletion) {
|
104 |
+
const content = chunk.choices[0]?.delta?.content;
|
105 |
+
if (content) {
|
106 |
+
controller.enqueue(encoder.encode(content));
|
107 |
+
completeResponse += content;
|
108 |
+
if (completeResponse.includes("</html>")) {
|
109 |
+
break;
|
110 |
+
}
|
111 |
+
}
|
112 |
+
}
|
113 |
+
});
|
114 |
+
} catch (error) {
|
115 |
+
return NextResponse.json(
|
116 |
+
{
|
117 |
+
ok: false,
|
118 |
+
message: error instanceof Error ? error.message : "Unknown error",
|
119 |
+
},
|
120 |
+
{ status: 500 }
|
121 |
+
);
|
122 |
+
}
|
123 |
+
}
|
src/app/api/improve-prompt/route.ts
ADDED
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { NextRequest, NextResponse } from "next/server";
|
2 |
+
import { MODEL_CONFIG_PROMPT_IMPROVEMENT } from "@/lib/constants";
|
3 |
+
import {
|
4 |
+
getInferenceToken,
|
5 |
+
checkTokenLimit,
|
6 |
+
createInferenceClient,
|
7 |
+
createStreamResponse,
|
8 |
+
getInferenceOptions
|
9 |
+
} from "@/lib/inference-utils";
|
10 |
+
|
11 |
+
export const dynamic = "force-dynamic";
|
12 |
+
|
13 |
+
export async function POST(request: NextRequest) {
|
14 |
+
try {
|
15 |
+
const { prompt, provider } = await request.json();
|
16 |
+
|
17 |
+
if (!prompt) {
|
18 |
+
return NextResponse.json(
|
19 |
+
{
|
20 |
+
ok: false,
|
21 |
+
message: "Missing required fields",
|
22 |
+
},
|
23 |
+
{ status: 400 }
|
24 |
+
);
|
25 |
+
}
|
26 |
+
|
27 |
+
// Get inference token
|
28 |
+
const tokenResult = await getInferenceToken(request);
|
29 |
+
if (tokenResult.error) {
|
30 |
+
return NextResponse.json(
|
31 |
+
{
|
32 |
+
ok: false,
|
33 |
+
openLogin: tokenResult.error.openLogin,
|
34 |
+
message: tokenResult.error.message,
|
35 |
+
},
|
36 |
+
{ status: tokenResult.error.status }
|
37 |
+
);
|
38 |
+
}
|
39 |
+
|
40 |
+
let TOKENS_USED = prompt?.length || 0;
|
41 |
+
|
42 |
+
let modelConfig = MODEL_CONFIG_PROMPT_IMPROVEMENT;
|
43 |
+
|
44 |
+
// Check token limit
|
45 |
+
if (provider !== "auto") {
|
46 |
+
const tokenLimitError = checkTokenLimit(TOKENS_USED, modelConfig);
|
47 |
+
if (tokenLimitError) {
|
48 |
+
return NextResponse.json(tokenLimitError, { status: 400 });
|
49 |
+
}
|
50 |
+
}
|
51 |
+
|
52 |
+
// Use streaming response
|
53 |
+
return createStreamResponse(async (controller) => {
|
54 |
+
const client = createInferenceClient(tokenResult.token);
|
55 |
+
|
56 |
+
const messages = [
|
57 |
+
{
|
58 |
+
role: "system",
|
59 |
+
content: `You are an expert prompt engineer capable of enhancing prompts for generating precise HTML website development requests aimed at building visually stunning, intuitive UI/UX designs and fully functional components. KEEP IT CONCISE AND LESS THAN 256 TOKENS. GIVE ME THE IMPROVED PROMPT ONLY, NOTHING ELSE. DO NOT ENCLOSE THE PROMPT IN DOUBLE QUOTATION MARKS.`,
|
60 |
+
},
|
61 |
+
{
|
62 |
+
role: "user",
|
63 |
+
content: prompt,
|
64 |
+
},
|
65 |
+
];
|
66 |
+
|
67 |
+
const chatCompletion = client.chatCompletionStream(
|
68 |
+
getInferenceOptions(modelConfig, messages, modelConfig.max_tokens)
|
69 |
+
);
|
70 |
+
|
71 |
+
const encoder = new TextEncoder();
|
72 |
+
for await (const chunk of chatCompletion) {
|
73 |
+
const content = chunk.choices[0]?.delta?.content;
|
74 |
+
if (content) {
|
75 |
+
controller.enqueue(encoder.encode(content));
|
76 |
+
}
|
77 |
+
}
|
78 |
+
});
|
79 |
+
} catch (error) {
|
80 |
+
return NextResponse.json(
|
81 |
+
{
|
82 |
+
ok: false,
|
83 |
+
message: error instanceof Error ? error.message : "Unknown error",
|
84 |
+
},
|
85 |
+
{ status: 500 }
|
86 |
+
);
|
87 |
+
}
|
88 |
+
}
|
src/app/api/login/route.ts
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { NextResponse } from "next/server";
|
2 |
+
|
3 |
+
export const dynamic = "force-dynamic";
|
4 |
+
|
5 |
+
export async function GET() {
|
6 |
+
const port = process.env.APP_PORT || 3000;
|
7 |
+
const redirectUri = process.env.REDIRECT_URI || `http://localhost:${port}/auth/login`;
|
8 |
+
|
9 |
+
const url = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirectUri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`;
|
10 |
+
|
11 |
+
return NextResponse.redirect(url);
|
12 |
+
}
|
src/app/api/user/route.ts
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { cookies } from "next/headers";
|
2 |
+
import { NextRequest, NextResponse } from "next/server";
|
3 |
+
import { checkUser } from "@/lib/middlewares/checkUser";
|
4 |
+
|
5 |
+
export const dynamic = "force-dynamic";
|
6 |
+
|
7 |
+
export async function GET(request: NextRequest) {
|
8 |
+
const checkResult = await checkUser(request);
|
9 |
+
if (checkResult) return checkResult;
|
10 |
+
|
11 |
+
const cookieStore = await cookies();
|
12 |
+
const hf_token = cookieStore.get("hf_token")?.value;
|
13 |
+
|
14 |
+
try {
|
15 |
+
const request_user = await fetch("https://huggingface.co/oauth/userinfo", {
|
16 |
+
headers: {
|
17 |
+
Authorization: `Bearer ${hf_token}`,
|
18 |
+
},
|
19 |
+
});
|
20 |
+
|
21 |
+
const user = await request_user.json();
|
22 |
+
return NextResponse.json(user);
|
23 |
+
} catch (error) {
|
24 |
+
const res = NextResponse.json(
|
25 |
+
{
|
26 |
+
ok: false,
|
27 |
+
message: error instanceof Error ? error.message : "Unknown error",
|
28 |
+
},
|
29 |
+
{ status: 401 }
|
30 |
+
);
|
31 |
+
|
32 |
+
res.cookies.set("hf_token", "", {
|
33 |
+
httpOnly: false,
|
34 |
+
secure: true,
|
35 |
+
sameSite: "none",
|
36 |
+
maxAge: 0,
|
37 |
+
path: "/",
|
38 |
+
});
|
39 |
+
|
40 |
+
return res;
|
41 |
+
}
|
42 |
+
}
|
src/app/globals.css
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@tailwind base;
|
2 |
+
@tailwind components;
|
3 |
+
@tailwind utilities;
|
4 |
+
|
5 |
+
@layer base {
|
6 |
+
:root {
|
7 |
+
--background: 40 10% 15.3%;
|
8 |
+
--foreground: 0 0% 98%;
|
9 |
+
--card: 40 10% 15.3%;
|
10 |
+
--card-foreground: 0 0% 98%;
|
11 |
+
--popover: 40 10% 15.3%;
|
12 |
+
--popover-foreground: 0 0% 98%;
|
13 |
+
--primary: 142.1 68.8% 49.4%;
|
14 |
+
--primary-foreground: 0 0% 100%;
|
15 |
+
--secondary: 40 5.9% 31.4%;
|
16 |
+
--secondary-foreground: 0 0% 98%;
|
17 |
+
--muted: 40 5.9% 31.4%;
|
18 |
+
--muted-foreground: 40 5% 64.9%;
|
19 |
+
--accent: 142.1 68.8% 49.4%;
|
20 |
+
--accent-foreground: 0 0% 100%;
|
21 |
+
--destructive: 0 62.8% 30.6%;
|
22 |
+
--destructive-foreground: 0 0% 98%;
|
23 |
+
--border: 40 5.9% 31.4%;
|
24 |
+
--input: 40 5.9% 31.4%;
|
25 |
+
--ring: 142.1 68.8% 49.4%;
|
26 |
+
--radius: 0.5rem;
|
27 |
+
}
|
28 |
+
|
29 |
+
.light {
|
30 |
+
--background: 0 0% 94.1%;
|
31 |
+
--foreground: 40 10% 15.3%;
|
32 |
+
--card: 0 0% 100%;
|
33 |
+
--card-foreground: 40 10% 15.3%;
|
34 |
+
--popover: 0 0% 100%;
|
35 |
+
--popover-foreground: 40 10% 15.3%;
|
36 |
+
--primary: 142.1 68.8% 49.4%;
|
37 |
+
--primary-foreground: 0 0% 100%;
|
38 |
+
--secondary: 40 4.8% 95.9%;
|
39 |
+
--secondary-foreground: 40 5.9% 10%;
|
40 |
+
--muted: 40 4.8% 95.9%;
|
41 |
+
--muted-foreground: 40 3.8% 46.1%;
|
42 |
+
--accent: 142.1 68.8% 49.4%;
|
43 |
+
--accent-foreground: 0 0% 100%;
|
44 |
+
--destructive: 0 84.2% 60.2%;
|
45 |
+
--destructive-foreground: 0 0% 98%;
|
46 |
+
--border: 40 5.9% 90%;
|
47 |
+
--input: 40 5.9% 90%;
|
48 |
+
--ring: 142.1 68.8% 49.4%;
|
49 |
+
}
|
50 |
+
}
|
51 |
+
|
52 |
+
@layer base {
|
53 |
+
* {
|
54 |
+
@apply border-border;
|
55 |
+
}
|
56 |
+
body {
|
57 |
+
@apply bg-background text-foreground;
|
58 |
+
}
|
59 |
+
}
|
60 |
+
|
61 |
+
.code-area {
|
62 |
+
background-color: #1f1f1f;
|
63 |
+
color: #f0f0ef;
|
64 |
+
}
|
65 |
+
|
66 |
+
.code-area pre {
|
67 |
+
font-family: "Menlo", "Monaco", "Courier New", monospace;
|
68 |
+
}
|
src/app/layout.tsx
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type React from "react"
|
2 |
+
import type { Metadata } from "next"
|
3 |
+
import { Inter } from "next/font/google"
|
4 |
+
import "./globals.css"
|
5 |
+
import { ThemeProvider } from "@/components/theme-provider"
|
6 |
+
import { TooltipProvider } from "@/components/ui/tooltip"
|
7 |
+
import { Toaster } from "sonner"
|
8 |
+
import { ModelProvider } from "@/lib/contexts/model-context"
|
9 |
+
|
10 |
+
const inter = Inter({ subsets: ["latin"] })
|
11 |
+
|
12 |
+
export const metadata: Metadata = {
|
13 |
+
title: "Novita AnySite",
|
14 |
+
description: "Create stunning websites with cutting-edge AI models powered by Novita AI.",
|
15 |
+
}
|
16 |
+
|
17 |
+
export default function RootLayout({
|
18 |
+
children,
|
19 |
+
}: Readonly<{
|
20 |
+
children: React.ReactNode
|
21 |
+
}>) {
|
22 |
+
return (
|
23 |
+
<html lang="en" suppressHydrationWarning>
|
24 |
+
<body className={inter.className} suppressHydrationWarning>
|
25 |
+
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem disableTransitionOnChange>
|
26 |
+
<ModelProvider>
|
27 |
+
<TooltipProvider>{children}</TooltipProvider>
|
28 |
+
<Toaster richColors />
|
29 |
+
</ModelProvider>
|
30 |
+
</ThemeProvider>
|
31 |
+
</body>
|
32 |
+
</html>
|
33 |
+
)
|
34 |
+
}
|
src/app/page.tsx
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Metadata } from "next";
|
2 |
+
import { AppContainer } from "@/components/app-container";
|
3 |
+
|
4 |
+
export const metadata: Metadata = {
|
5 |
+
title: "DeepSite - Create AI-Generated Websites",
|
6 |
+
description: "Create beautiful websites using AI with DeepSite",
|
7 |
+
};
|
8 |
+
|
9 |
+
export default function Home() {
|
10 |
+
return (
|
11 |
+
<main className="flex min-h-screen flex-col">
|
12 |
+
<AppContainer />
|
13 |
+
</main>
|
14 |
+
);
|
15 |
+
}
|
src/components/app-container.tsx
ADDED
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import { useState, useRef, useEffect } from "react"
|
4 |
+
import { CodeEditor } from "./code-editor"
|
5 |
+
import { Preview } from "./preview"
|
6 |
+
import { PromptInput } from "./prompt-input"
|
7 |
+
import { Header } from "./header"
|
8 |
+
import { VersionDropdown } from "./version-dropdown"
|
9 |
+
import { AuthErrorPopup } from "./auth-error-popup"
|
10 |
+
import { toast } from "sonner"
|
11 |
+
import { DEFAULT_HTML } from "@/lib/constants"
|
12 |
+
import { Version, PreviewRef } from "@/lib/types"
|
13 |
+
import { ErrorMessage } from "./ui/error-message"
|
14 |
+
|
15 |
+
export function AppContainer() {
|
16 |
+
const [code, setCode] = useState<string>("");
|
17 |
+
const [prompt, setPrompt] = useState<string>("");
|
18 |
+
const [loading, setLoading] = useState(false);
|
19 |
+
const [initialLoading, setInitialLoading] = useState(true);
|
20 |
+
const [showAuthError, setShowAuthError] = useState(false);
|
21 |
+
const [currentVersionId, setCurrentVersionId] = useState<string | null>(null);
|
22 |
+
const [generationError, setGenerationError] = useState<string | null>(null);
|
23 |
+
const [improveError, setImproveError] = useState<string | null>(null);
|
24 |
+
const previewRef = useRef<PreviewRef>(null);
|
25 |
+
const currentPromptRef = useRef<string>("");
|
26 |
+
// Add a ref to track the latest created version ID
|
27 |
+
const latestVersionIdRef = useRef<string | null>(null);
|
28 |
+
|
29 |
+
// Keep a reference to the current prompt for use in callbacks
|
30 |
+
useEffect(() => {
|
31 |
+
currentPromptRef.current = prompt;
|
32 |
+
}, [prompt]);
|
33 |
+
|
34 |
+
// Initialize current version ID from localStorage on mount
|
35 |
+
useEffect(() => {
|
36 |
+
setInitialLoading(true);
|
37 |
+
|
38 |
+
const storedVersions = localStorage.getItem('novita-versions');
|
39 |
+
if (storedVersions) {
|
40 |
+
const parsedVersions = JSON.parse(storedVersions) as Version[];
|
41 |
+
if (parsedVersions.length > 0) {
|
42 |
+
// Sort by creation time, newest first
|
43 |
+
const sortedVersions = parsedVersions.sort((a, b) => b.createdAt - a.createdAt);
|
44 |
+
const newestVersion = sortedVersions[0];
|
45 |
+
|
46 |
+
// Set the current version ID and also load its code and prompt
|
47 |
+
setCurrentVersionId(newestVersion.id);
|
48 |
+
latestVersionIdRef.current = newestVersion.id;
|
49 |
+
setCode(newestVersion.code);
|
50 |
+
setPrompt(newestVersion.prompt);
|
51 |
+
currentPromptRef.current = newestVersion.prompt;
|
52 |
+
} else {
|
53 |
+
// No versions found, set default code but don't create a version
|
54 |
+
setCode(DEFAULT_HTML);
|
55 |
+
setCurrentVersionId(null);
|
56 |
+
latestVersionIdRef.current = null;
|
57 |
+
}
|
58 |
+
} else {
|
59 |
+
// No versions in localStorage, set default code but don't create a version
|
60 |
+
setCode(DEFAULT_HTML);
|
61 |
+
setCurrentVersionId(null);
|
62 |
+
latestVersionIdRef.current = null;
|
63 |
+
localStorage.setItem('novita-versions', JSON.stringify([]));
|
64 |
+
}
|
65 |
+
|
66 |
+
// Finish loading after a short delay to ensure everything renders properly
|
67 |
+
setTimeout(() => {
|
68 |
+
setInitialLoading(false);
|
69 |
+
}, 100);
|
70 |
+
}, []);
|
71 |
+
|
72 |
+
const handlePromptSubmit = async (newPrompt: string, colors: string[]) => {
|
73 |
+
// Store the current prompt as previous prompt
|
74 |
+
const oldPrompt = currentPromptRef.current;
|
75 |
+
|
76 |
+
// Update prompt state
|
77 |
+
setPrompt(newPrompt);
|
78 |
+
currentPromptRef.current = newPrompt; // Update the ref immediately
|
79 |
+
|
80 |
+
// Clear code and preview when submit is pressed
|
81 |
+
setCode("");
|
82 |
+
|
83 |
+
// Create a new version immediately with empty code when submit is clicked
|
84 |
+
const newVersionId = saveVersionInitial(newPrompt);
|
85 |
+
setCurrentVersionId(newVersionId);
|
86 |
+
|
87 |
+
if (previewRef.current) {
|
88 |
+
// Pass both the new prompt and old prompt for context
|
89 |
+
await previewRef.current.generateCode(newPrompt, colors, oldPrompt);
|
90 |
+
}
|
91 |
+
}
|
92 |
+
|
93 |
+
// Handle code change and save version
|
94 |
+
const handleCodeChange = (newCode: string, save = false) => {
|
95 |
+
setCode(newCode);
|
96 |
+
|
97 |
+
// Save version when code changes and we're not loading
|
98 |
+
// This will now only be called once with the final code
|
99 |
+
if (save && currentPromptRef.current) {
|
100 |
+
// Make sure we're updating the latest version
|
101 |
+
updateVersionWithFinalCode(newCode);
|
102 |
+
}
|
103 |
+
}
|
104 |
+
|
105 |
+
// Explicitly save version when loading completes
|
106 |
+
const handleLoadingChange = (isLoading: boolean) => {
|
107 |
+
setLoading(isLoading);
|
108 |
+
}
|
109 |
+
|
110 |
+
// Function to create a new version initially with empty code
|
111 |
+
const saveVersionInitial = (promptToSave: string) => {
|
112 |
+
// Get existing versions
|
113 |
+
const storedVersions = localStorage.getItem('novita-versions');
|
114 |
+
let versions: Version[] = storedVersions ? JSON.parse(storedVersions) : [];
|
115 |
+
|
116 |
+
// Create new version with empty code
|
117 |
+
const newVersion: Version = {
|
118 |
+
id: Date.now().toString(),
|
119 |
+
code: "", // Empty code field initially
|
120 |
+
prompt: promptToSave,
|
121 |
+
createdAt: Date.now()
|
122 |
+
};
|
123 |
+
|
124 |
+
// Add to beginning (newest first)
|
125 |
+
versions = [newVersion, ...versions];
|
126 |
+
|
127 |
+
// Save to localStorage
|
128 |
+
localStorage.setItem('novita-versions', JSON.stringify(versions));
|
129 |
+
|
130 |
+
// Update current version ID
|
131 |
+
setCurrentVersionId(newVersion.id);
|
132 |
+
// Also update the latest version ID ref
|
133 |
+
latestVersionIdRef.current = newVersion.id;
|
134 |
+
|
135 |
+
return newVersion.id;
|
136 |
+
}
|
137 |
+
|
138 |
+
// Function to update existing version with final code
|
139 |
+
const updateVersionWithFinalCode = (finalCode: string) => {
|
140 |
+
// Get existing versions
|
141 |
+
const storedVersions = localStorage.getItem('novita-versions');
|
142 |
+
if (!storedVersions) return;
|
143 |
+
|
144 |
+
let versions: Version[] = JSON.parse(storedVersions);
|
145 |
+
|
146 |
+
if (!versions.length) return;
|
147 |
+
|
148 |
+
// Prioritize using the latestVersionIdRef, then fall back to currentVersionId, then to the most recent version
|
149 |
+
const versionIdToUpdate = latestVersionIdRef.current || currentVersionId || versions[0].id;
|
150 |
+
|
151 |
+
if (!versionIdToUpdate) return;
|
152 |
+
|
153 |
+
// Find and update the current version
|
154 |
+
const updatedVersions = versions.map(version => {
|
155 |
+
if (version.id === versionIdToUpdate) {
|
156 |
+
return { ...version, code: finalCode };
|
157 |
+
}
|
158 |
+
return version;
|
159 |
+
});
|
160 |
+
|
161 |
+
// Save to localStorage
|
162 |
+
localStorage.setItem('novita-versions', JSON.stringify(updatedVersions));
|
163 |
+
|
164 |
+
// Show a subtle toast notification when code generation completes
|
165 |
+
toast.success('Generated code saved', {
|
166 |
+
position: 'top-right',
|
167 |
+
duration: 2000,
|
168 |
+
});
|
169 |
+
}
|
170 |
+
|
171 |
+
// Handle selecting a version from the dropdown
|
172 |
+
const handleVersionSelect = (version: Version) => {
|
173 |
+
setCode(version.code);
|
174 |
+
setPrompt(version.prompt);
|
175 |
+
currentPromptRef.current = version.prompt;
|
176 |
+
setCurrentVersionId(version.id);
|
177 |
+
latestVersionIdRef.current = version.id;
|
178 |
+
}
|
179 |
+
|
180 |
+
// Handle clearing all versions
|
181 |
+
const handleClearAll = () => {
|
182 |
+
// Reset to default HTML
|
183 |
+
setCode(DEFAULT_HTML);
|
184 |
+
setPrompt('');
|
185 |
+
currentPromptRef.current = '';
|
186 |
+
setCurrentVersionId(null);
|
187 |
+
latestVersionIdRef.current = null;
|
188 |
+
}
|
189 |
+
|
190 |
+
return (
|
191 |
+
<div className="flex flex-col h-screen bg-novita-dark text-white">
|
192 |
+
<Header
|
193 |
+
onVersionSelect={handleVersionSelect}
|
194 |
+
currentVersion={currentVersionId || undefined}
|
195 |
+
onClearAll={handleClearAll}
|
196 |
+
/>
|
197 |
+
<div className="flex flex-1 overflow-hidden relative">
|
198 |
+
<div className="w-1/3 border-r border-novita-gray/20 flex flex-col relative">
|
199 |
+
<CodeEditor code={code} isLoading={loading} />
|
200 |
+
|
201 |
+
<AuthErrorPopup
|
202 |
+
show={showAuthError}
|
203 |
+
onClose={() => setShowAuthError(false)}
|
204 |
+
/>
|
205 |
+
|
206 |
+
<PromptInput
|
207 |
+
onSubmit={handlePromptSubmit}
|
208 |
+
isLoading={loading}
|
209 |
+
initialPrompt={prompt}
|
210 |
+
onImproveError={setImproveError}
|
211 |
+
/>
|
212 |
+
</div>
|
213 |
+
<div className="w-2/3">
|
214 |
+
<Preview
|
215 |
+
ref={previewRef}
|
216 |
+
initialHtml={code}
|
217 |
+
onCodeChange={handleCodeChange}
|
218 |
+
onLoadingChange={handleLoadingChange}
|
219 |
+
onAuthErrorChange={setShowAuthError}
|
220 |
+
onErrorChange={setGenerationError}
|
221 |
+
currentVersion={currentVersionId || undefined}
|
222 |
+
/>
|
223 |
+
</div>
|
224 |
+
|
225 |
+
{initialLoading && (
|
226 |
+
<div className="absolute inset-0 bg-novita-dark/80 backdrop-blur-sm flex items-center justify-center z-[999]">
|
227 |
+
<div className="p-4 text-center">
|
228 |
+
<div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]" role="status">
|
229 |
+
<span className="!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]">Loading...</span>
|
230 |
+
</div>
|
231 |
+
<p className="mt-2">Preparing the workspace...</p>
|
232 |
+
</div>
|
233 |
+
</div>
|
234 |
+
)}
|
235 |
+
</div>
|
236 |
+
|
237 |
+
{/* 全局错误显示组件 */}
|
238 |
+
<ErrorMessage
|
239 |
+
message={improveError || generationError}
|
240 |
+
onClose={() => {
|
241 |
+
if (improveError) {
|
242 |
+
setImproveError(null);
|
243 |
+
} else if (generationError) {
|
244 |
+
setGenerationError(null);
|
245 |
+
}
|
246 |
+
}}
|
247 |
+
/>
|
248 |
+
</div>
|
249 |
+
)
|
250 |
+
}
|
src/components/auth-error-popup.tsx
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from 'react'
|
2 |
+
|
3 |
+
interface AuthErrorPopupProps {
|
4 |
+
show: boolean
|
5 |
+
authUrl?: string
|
6 |
+
onClose?: () => void
|
7 |
+
}
|
8 |
+
|
9 |
+
export function AuthErrorPopup({
|
10 |
+
show,
|
11 |
+
authUrl = '/api/auth/login',
|
12 |
+
onClose
|
13 |
+
}: AuthErrorPopupProps) {
|
14 |
+
if (!show) return null
|
15 |
+
|
16 |
+
return (
|
17 |
+
<div className="absolute right-0 bottom-20 mr-4 w-fit px-2.5 bg-gray-800 border border-gray-600 rounded-lg p-4 shadow-xl z-50">
|
18 |
+
{onClose && (
|
19 |
+
<button
|
20 |
+
onClick={onClose}
|
21 |
+
className="absolute top-1 right-1 text-gray-400 hover:text-white p-1 rounded-full hover:bg-gray-700 focus:outline-none"
|
22 |
+
aria-label="Close"
|
23 |
+
>
|
24 |
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
25 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
26 |
+
</svg>
|
27 |
+
</button>
|
28 |
+
)}
|
29 |
+
<div className="flex flex-col items-center">
|
30 |
+
<p className="mb-3 text-center">Login to continue</p>
|
31 |
+
<a href={authUrl} className="inline-block">
|
32 |
+
<img
|
33 |
+
src="https://huggingface.co/datasets/huggingface/badges/resolve/main/sign-in-with-huggingface-lg-dark.svg"
|
34 |
+
alt="Sign in with Hugging Face"
|
35 |
+
/>
|
36 |
+
</a>
|
37 |
+
</div>
|
38 |
+
</div>
|
39 |
+
)
|
40 |
+
}
|
src/components/code-editor.tsx
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import { useEffect, useRef, useState } from "react";
|
4 |
+
|
5 |
+
interface CodeEditorProps {
|
6 |
+
code: string;
|
7 |
+
isLoading?: boolean;
|
8 |
+
}
|
9 |
+
|
10 |
+
export function CodeEditor({ code, isLoading = false }: CodeEditorProps) {
|
11 |
+
const containerRef = useRef<HTMLDivElement>(null);
|
12 |
+
const [isAtBottom, setIsAtBottom] = useState(true);
|
13 |
+
|
14 |
+
// Check if user is near bottom when scrolling
|
15 |
+
const handleScroll = () => {
|
16 |
+
if (!containerRef.current) return;
|
17 |
+
|
18 |
+
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
|
19 |
+
// Consider "at bottom" if within 30px of the bottom
|
20 |
+
const isNearBottom = scrollHeight - scrollTop - clientHeight < 30;
|
21 |
+
setIsAtBottom(isNearBottom);
|
22 |
+
};
|
23 |
+
|
24 |
+
useEffect(() => {
|
25 |
+
const container = containerRef.current;
|
26 |
+
if (container) {
|
27 |
+
container.addEventListener('scroll', handleScroll);
|
28 |
+
return () => container.removeEventListener('scroll', handleScroll);
|
29 |
+
}
|
30 |
+
}, []);
|
31 |
+
|
32 |
+
useEffect(() => {
|
33 |
+
// Only auto-scroll if user was already at the bottom
|
34 |
+
if (isAtBottom && containerRef.current) {
|
35 |
+
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
36 |
+
}
|
37 |
+
}, [code, isLoading, isAtBottom]);
|
38 |
+
|
39 |
+
return (
|
40 |
+
<div className="flex-1 overflow-auto p-4">
|
41 |
+
<div
|
42 |
+
ref={containerRef}
|
43 |
+
className="code-area p-4 rounded-md font-mono text-sm h-full overflow-auto border border-novita-gray/20"
|
44 |
+
>
|
45 |
+
<pre className="whitespace-pre-wrap">
|
46 |
+
{code}
|
47 |
+
{isLoading && <span className="inline-block h-4 w-2 bg-white/70 animate-pulse"></span>}
|
48 |
+
</pre>
|
49 |
+
</div>
|
50 |
+
</div>
|
51 |
+
)
|
52 |
+
}
|
src/components/color-panel.tsx
ADDED
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import { useState, useEffect, useRef } from "react"
|
4 |
+
import { Plus, X } from "lucide-react"
|
5 |
+
import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from "@/components/ui/tooltip"
|
6 |
+
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
7 |
+
import { cn } from "@/lib/utils"
|
8 |
+
import { Button } from "@/components/ui/button"
|
9 |
+
|
10 |
+
interface ColorCircleProps {
|
11 |
+
color: string
|
12 |
+
onClick?: () => void
|
13 |
+
onDelete?: () => void
|
14 |
+
className?: string
|
15 |
+
size?: "sm" | "md"
|
16 |
+
showDeleteIcon?: boolean
|
17 |
+
}
|
18 |
+
|
19 |
+
function ColorCircle({ color, onClick, onDelete, className, size = "sm", showDeleteIcon = false }: ColorCircleProps) {
|
20 |
+
const sizeClasses = {
|
21 |
+
sm: "w-5 h-5",
|
22 |
+
md: "w-6 h-6",
|
23 |
+
}
|
24 |
+
|
25 |
+
return (
|
26 |
+
<div className="relative group h-5">
|
27 |
+
<button
|
28 |
+
type="button"
|
29 |
+
onClick={onClick}
|
30 |
+
className={cn(
|
31 |
+
sizeClasses[size],
|
32 |
+
"rounded-full border border-white/20 transition-transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-white/30",
|
33 |
+
className,
|
34 |
+
)}
|
35 |
+
style={{ backgroundColor: color }}
|
36 |
+
aria-label={`Color ${color}`}
|
37 |
+
/>
|
38 |
+
{showDeleteIcon && (
|
39 |
+
<button
|
40 |
+
type="button"
|
41 |
+
onClick={(e) => {
|
42 |
+
e.stopPropagation();
|
43 |
+
onDelete?.();
|
44 |
+
}}
|
45 |
+
className="absolute -top-1 -right-1 bg-novita-dark rounded-full w-3.5 h-3.5 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
|
46 |
+
aria-label={`Remove color ${color}`}
|
47 |
+
>
|
48 |
+
<X className="h-2 w-2 text-white" />
|
49 |
+
</button>
|
50 |
+
)}
|
51 |
+
</div>
|
52 |
+
)
|
53 |
+
}
|
54 |
+
|
55 |
+
const PRESET_COLORS = [
|
56 |
+
"#23d57c", // Novita green
|
57 |
+
"#f43f5e", // Rose
|
58 |
+
"#3b82f6", // Blue
|
59 |
+
"#eab308", // Yellow
|
60 |
+
"#8b5cf6", // Purple
|
61 |
+
"#ec4899", // Pink
|
62 |
+
"#06b6d4", // Cyan
|
63 |
+
"#ef4444", // Red
|
64 |
+
"#84cc16", // Lime
|
65 |
+
"#14b8a6", // Teal
|
66 |
+
"#f97316", // Orange
|
67 |
+
"#6366f1", // Indigo
|
68 |
+
]
|
69 |
+
|
70 |
+
interface ColorPanelProps {
|
71 |
+
onColorsChange?: (colors: string[]) => void;
|
72 |
+
}
|
73 |
+
|
74 |
+
export function ColorPanel({ onColorsChange }: ColorPanelProps) {
|
75 |
+
const maxColors = 6
|
76 |
+
const [colors, setColors] = useState<string[]>([])
|
77 |
+
const [isPopoverOpen, setIsPopoverOpen] = useState(false)
|
78 |
+
const [selectedColor, setSelectedColor] = useState<string | null>(null)
|
79 |
+
const [isAddingDisabled, setIsAddingDisabled] = useState(false)
|
80 |
+
const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
81 |
+
|
82 |
+
useEffect(() => {
|
83 |
+
return () => {
|
84 |
+
if (debounceTimeoutRef.current) {
|
85 |
+
clearTimeout(debounceTimeoutRef.current)
|
86 |
+
}
|
87 |
+
}
|
88 |
+
}, [])
|
89 |
+
|
90 |
+
useEffect(() => {
|
91 |
+
// Call onColorsChange when colors change
|
92 |
+
onColorsChange?.(colors);
|
93 |
+
}, [colors, onColorsChange]);
|
94 |
+
|
95 |
+
const addColor = () => {
|
96 |
+
if (colors.length >= maxColors || !selectedColor || isAddingDisabled) return
|
97 |
+
|
98 |
+
if (!colors.includes(selectedColor)) {
|
99 |
+
setIsAddingDisabled(true)
|
100 |
+
setColors([...colors, selectedColor])
|
101 |
+
setSelectedColor(null)
|
102 |
+
setIsPopoverOpen(false)
|
103 |
+
|
104 |
+
// Debounce to prevent rapid clicking
|
105 |
+
debounceTimeoutRef.current = setTimeout(() => {
|
106 |
+
setIsAddingDisabled(false)
|
107 |
+
}, 500)
|
108 |
+
}
|
109 |
+
}
|
110 |
+
|
111 |
+
const selectColor = (color: string) => {
|
112 |
+
setSelectedColor(color)
|
113 |
+
}
|
114 |
+
|
115 |
+
const removeColor = (indexToRemove: number) => {
|
116 |
+
setColors(colors.filter((_, index) => index !== indexToRemove))
|
117 |
+
}
|
118 |
+
|
119 |
+
return (
|
120 |
+
<TooltipProvider>
|
121 |
+
<div className="absolute top-2.5 left-3 flex items-center gap-1.5 z-10">
|
122 |
+
{colors.map((color, index) => (
|
123 |
+
<ColorCircle
|
124 |
+
key={`${color}-${index}`}
|
125 |
+
color={color}
|
126 |
+
onClick={() => removeColor(index)}
|
127 |
+
onDelete={() => removeColor(index)}
|
128 |
+
showDeleteIcon={true}
|
129 |
+
/>
|
130 |
+
))}
|
131 |
+
|
132 |
+
{colors.length < maxColors && (
|
133 |
+
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
|
134 |
+
<Tooltip>
|
135 |
+
<TooltipTrigger asChild>
|
136 |
+
<PopoverTrigger asChild>
|
137 |
+
<button
|
138 |
+
type="button"
|
139 |
+
className="w-5 h-5 rounded-full bg-novita-gray/30 border border-novita-gray/50 flex items-center justify-center transition-transform hover:scale-110 hover:bg-novita-gray/40 focus:outline-none focus:ring-2 focus:ring-white/30"
|
140 |
+
aria-label="Add color"
|
141 |
+
>
|
142 |
+
<Plus className="h-2.5 w-2.5 text-white" />
|
143 |
+
</button>
|
144 |
+
</PopoverTrigger>
|
145 |
+
</TooltipTrigger>
|
146 |
+
<TooltipContent className="bg-novita-dark border-novita-gray/30 text-white">
|
147 |
+
<p>Choose Site Color palette</p>
|
148 |
+
</TooltipContent>
|
149 |
+
</Tooltip>
|
150 |
+
<PopoverContent
|
151 |
+
className="w-64 p-3 bg-novita-dark border-novita-gray/30 rounded-md shadow-lg"
|
152 |
+
align="start"
|
153 |
+
sideOffset={5}
|
154 |
+
>
|
155 |
+
<div className="space-y-3">
|
156 |
+
<h3 className="text-sm font-medium text-white">Color Picker</h3>
|
157 |
+
<div className="grid grid-cols-6 gap-2">
|
158 |
+
{PRESET_COLORS.map((color) => (
|
159 |
+
<ColorCircle
|
160 |
+
key={color}
|
161 |
+
color={color}
|
162 |
+
size="md"
|
163 |
+
onClick={() => selectColor(color)}
|
164 |
+
className={cn(
|
165 |
+
selectedColor === color && "ring-2 ring-white/50",
|
166 |
+
colors.includes(color) && "opacity-50"
|
167 |
+
)}
|
168 |
+
/>
|
169 |
+
))}
|
170 |
+
</div>
|
171 |
+
<div className="pt-2 border-t border-novita-gray/30">
|
172 |
+
<label className="block text-xs text-novita-gray mb-1.5">Custom color</label>
|
173 |
+
<input
|
174 |
+
type="color"
|
175 |
+
className="w-full h-8 bg-transparent border border-novita-gray/30 rounded cursor-pointer"
|
176 |
+
onChange={(e) => selectColor(e.target.value)}
|
177 |
+
value={selectedColor || "#ffffff"}
|
178 |
+
/>
|
179 |
+
</div>
|
180 |
+
<div className="mt-3">
|
181 |
+
<Button
|
182 |
+
onClick={addColor}
|
183 |
+
disabled={!selectedColor || isAddingDisabled}
|
184 |
+
className="w-full h-6 bg-novita-white hover:bg-novita-gray/90 text-white rounded border border-novita-gray/90"
|
185 |
+
>
|
186 |
+
Add
|
187 |
+
</Button>
|
188 |
+
</div>
|
189 |
+
</div>
|
190 |
+
</PopoverContent>
|
191 |
+
</Popover>
|
192 |
+
)}
|
193 |
+
</div>
|
194 |
+
</TooltipProvider>
|
195 |
+
)
|
196 |
+
}
|
src/components/header.tsx
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
import { Logo } from "./logo"
|
3 |
+
import { ModelSelector } from "./model-selector"
|
4 |
+
import { VersionDropdown } from "./version-dropdown"
|
5 |
+
|
6 |
+
export function Header({
|
7 |
+
onVersionSelect,
|
8 |
+
currentVersion,
|
9 |
+
onClearAll
|
10 |
+
}: {
|
11 |
+
onVersionSelect?: (version: any) => void,
|
12 |
+
currentVersion?: string,
|
13 |
+
onClearAll?: () => void
|
14 |
+
}) {
|
15 |
+
return (
|
16 |
+
<header className="border-b border-novita-gray/20 p-3 flex justify-between items-center bg-novita-dark">
|
17 |
+
<div className="flex items-center gap-3">
|
18 |
+
<div>
|
19 |
+
<div className="flex items-center gap-2">
|
20 |
+
<span className="text-l text-white">Build any site using</span>
|
21 |
+
<div className="flex items-center">
|
22 |
+
<ModelSelector />
|
23 |
+
{onVersionSelect && (
|
24 |
+
<VersionDropdown
|
25 |
+
onVersionSelect={onVersionSelect}
|
26 |
+
currentVersion={currentVersion}
|
27 |
+
onClearAll={onClearAll}
|
28 |
+
/>
|
29 |
+
)}
|
30 |
+
</div>
|
31 |
+
</div>
|
32 |
+
<span className="text-sm text-white/40"><a href="https://novita.ai/models/llm" target="_blank" className="underline hover:text-white/70 transition-colors duration-200">Powered by novita.ai</a></span>
|
33 |
+
</div>
|
34 |
+
</div>
|
35 |
+
</header>
|
36 |
+
)
|
37 |
+
}
|
src/components/logo.tsx
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export function Logo() {
|
2 |
+
return (
|
3 |
+
<svg
|
4 |
+
width="32"
|
5 |
+
height="32"
|
6 |
+
viewBox="0 0 100 100"
|
7 |
+
fill="none"
|
8 |
+
xmlns="http://www.w3.org/2000/svg"
|
9 |
+
className="text-novita-green"
|
10 |
+
>
|
11 |
+
<path d="M50 0L100 50H50L0 0H50Z" fill="currentColor" />
|
12 |
+
</svg>
|
13 |
+
)
|
14 |
+
}
|
src/components/model-selector.tsx
ADDED
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import { useState, useEffect, useRef } from 'react'
|
4 |
+
import { MODEL_CONFIG_CODE_GENERATION } from '@/lib/constants'
|
5 |
+
import { useModel } from '@/lib/contexts/model-context'
|
6 |
+
import { ChevronDown } from 'lucide-react'
|
7 |
+
|
8 |
+
export function ModelSelector() {
|
9 |
+
const { selectedModelIndex, setSelectedModelIndex } = useModel()
|
10 |
+
const [isOpen, setIsOpen] = useState(false)
|
11 |
+
const selectorRef = useRef<HTMLDivElement>(null)
|
12 |
+
|
13 |
+
const handleSelect = (index: number) => {
|
14 |
+
setSelectedModelIndex(index)
|
15 |
+
setIsOpen(false)
|
16 |
+
}
|
17 |
+
|
18 |
+
useEffect(() => {
|
19 |
+
const handleClickOutside = (event: MouseEvent) => {
|
20 |
+
if (selectorRef.current && !selectorRef.current.contains(event.target as Node)) {
|
21 |
+
setIsOpen(false)
|
22 |
+
}
|
23 |
+
}
|
24 |
+
|
25 |
+
if (isOpen) {
|
26 |
+
document.addEventListener('mousedown', handleClickOutside)
|
27 |
+
}
|
28 |
+
|
29 |
+
return () => {
|
30 |
+
document.removeEventListener('mousedown', handleClickOutside)
|
31 |
+
}
|
32 |
+
}, [isOpen])
|
33 |
+
|
34 |
+
return (
|
35 |
+
<div className="relative inline-block text-left" ref={selectorRef}>
|
36 |
+
<div
|
37 |
+
onClick={() => setIsOpen(!isOpen)}
|
38 |
+
className={`
|
39 |
+
cursor-pointer flex items-center justify-between gap-2
|
40 |
+
text-white/90 hover:text-white
|
41 |
+
px-3 py-1.5 rounded-md
|
42 |
+
border border-novita-gray/30 hover:border-novita-gray/60
|
43 |
+
bg-novita-gray/5 hover:bg-novita-gray/20
|
44 |
+
transition-all duration-200 ease-in-out
|
45 |
+
${isOpen ? 'border-novita-gray/60 bg-novita-gray/20' : ''}
|
46 |
+
`}
|
47 |
+
>
|
48 |
+
<span className="text-sm">{MODEL_CONFIG_CODE_GENERATION[selectedModelIndex]?.id || 'Select model'}</span>
|
49 |
+
<ChevronDown className={`w-4 h-4 transition-transform duration-200 ${isOpen ? 'transform rotate-180' : ''}`} />
|
50 |
+
</div>
|
51 |
+
|
52 |
+
{isOpen && (
|
53 |
+
<div className="absolute z-40 mt-1 w-full origin-top-right rounded-md bg-novita-dark border border-novita-gray/20 shadow-lg">
|
54 |
+
<div className="py-1">
|
55 |
+
{MODEL_CONFIG_CODE_GENERATION.map((model, index) => (
|
56 |
+
<div
|
57 |
+
key={model.id}
|
58 |
+
onClick={() => handleSelect(index)}
|
59 |
+
className={`
|
60 |
+
px-4 py-2 text-xs cursor-pointer hover:bg-novita-gray/20
|
61 |
+
${selectedModelIndex === index ? 'text-white bg-novita-gray/40' : 'text-white/70'}
|
62 |
+
`}
|
63 |
+
>
|
64 |
+
{model.id}
|
65 |
+
</div>
|
66 |
+
))}
|
67 |
+
</div>
|
68 |
+
</div>
|
69 |
+
)}
|
70 |
+
</div>
|
71 |
+
)
|
72 |
+
}
|
src/components/preview.tsx
ADDED
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import { useState, forwardRef, useImperativeHandle, useEffect, useRef } from "react"
|
4 |
+
import { DEFAULT_HTML } from "@/lib/constants"
|
5 |
+
import { PreviewRef, Version } from "@/lib/types"
|
6 |
+
import { MinimizeIcon, MaximizeIcon, DownloadIcon } from "./ui/fullscreen-icons"
|
7 |
+
import { useModel } from "@/lib/contexts/model-context"
|
8 |
+
import { cn } from "@/lib/utils"
|
9 |
+
|
10 |
+
interface PreviewProps {
|
11 |
+
initialHtml?: string;
|
12 |
+
onCodeChange?: (html: string, save?: boolean) => void;
|
13 |
+
onAuthErrorChange?: (show: boolean) => void;
|
14 |
+
onLoadingChange?: (loading: boolean) => void;
|
15 |
+
onErrorChange?: (error: string | null) => void;
|
16 |
+
currentVersion?: string;
|
17 |
+
}
|
18 |
+
|
19 |
+
export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
|
20 |
+
{ initialHtml, onCodeChange, onAuthErrorChange, onLoadingChange, onErrorChange, currentVersion },
|
21 |
+
ref
|
22 |
+
) {
|
23 |
+
const [html, setHtml] = useState<string>(initialHtml || "");
|
24 |
+
const [isFullscreen, setIsFullscreen] = useState(false);
|
25 |
+
const [loading, setLoading] = useState(false);
|
26 |
+
const [error, setError] = useState<string | null>(null);
|
27 |
+
const [showAuthError, setShowAuthError] = useState(false);
|
28 |
+
const { selectedModelId } = useModel();
|
29 |
+
|
30 |
+
// Update html when initialHtml changes
|
31 |
+
useEffect(() => {
|
32 |
+
if (initialHtml) {
|
33 |
+
setHtml(initialHtml);
|
34 |
+
}
|
35 |
+
}, [initialHtml]);
|
36 |
+
|
37 |
+
// Update parent component when error changes
|
38 |
+
useEffect(() => {
|
39 |
+
if (onErrorChange) {
|
40 |
+
onErrorChange(error);
|
41 |
+
}
|
42 |
+
}, [error, onErrorChange]);
|
43 |
+
|
44 |
+
useImperativeHandle(ref, () => ({
|
45 |
+
generateCode: async (prompt: string, colors: string[] = [], previousPrompt?: string) => {
|
46 |
+
await generateCode(prompt, colors, previousPrompt);
|
47 |
+
}
|
48 |
+
}));
|
49 |
+
|
50 |
+
const toggleFullscreen = () => {
|
51 |
+
setIsFullscreen(!isFullscreen);
|
52 |
+
};
|
53 |
+
|
54 |
+
const downloadHtml = () => {
|
55 |
+
if (!html) return;
|
56 |
+
|
57 |
+
// Get current version and generate filename
|
58 |
+
// If we have a currentVersion, use it; otherwise omit version part
|
59 |
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
|
60 |
+
|
61 |
+
// Load current version from localStorage if we have an ID
|
62 |
+
let versionLabel = "";
|
63 |
+
if (currentVersion) {
|
64 |
+
versionLabel = `-${currentVersion}`;
|
65 |
+
}
|
66 |
+
|
67 |
+
// Format the filename with or without version
|
68 |
+
const filename = `novita-anysite-generated${versionLabel}-${timestamp}.html`;
|
69 |
+
|
70 |
+
const blob = new Blob([html], { type: 'text/html' });
|
71 |
+
const url = window.URL.createObjectURL(blob);
|
72 |
+
const a = document.createElement('a');
|
73 |
+
a.href = url;
|
74 |
+
a.download = filename;
|
75 |
+
document.body.appendChild(a);
|
76 |
+
a.click();
|
77 |
+
window.URL.revokeObjectURL(url);
|
78 |
+
document.body.removeChild(a);
|
79 |
+
};
|
80 |
+
|
81 |
+
const generateCode = async (prompt: string, colors: string[] = [], previousPrompt?: string) => {
|
82 |
+
setLoading(true);
|
83 |
+
if (onLoadingChange) {
|
84 |
+
onLoadingChange(true);
|
85 |
+
}
|
86 |
+
setError(null);
|
87 |
+
setShowAuthError(false);
|
88 |
+
if (onAuthErrorChange) {
|
89 |
+
onAuthErrorChange(false);
|
90 |
+
}
|
91 |
+
|
92 |
+
// Clear HTML content when generation starts
|
93 |
+
setHtml("");
|
94 |
+
|
95 |
+
// Initialize generated code variable at function scope so it's accessible in finally block
|
96 |
+
let generatedCode = '';
|
97 |
+
|
98 |
+
try {
|
99 |
+
// Only include html in the request if it's not DEFAULT_HTML
|
100 |
+
const isDefaultHtml = initialHtml === DEFAULT_HTML;
|
101 |
+
|
102 |
+
const response = await fetch('/api/generate-code', {
|
103 |
+
method: 'POST',
|
104 |
+
headers: {
|
105 |
+
'Content-Type': 'application/json',
|
106 |
+
},
|
107 |
+
body: JSON.stringify({
|
108 |
+
prompt,
|
109 |
+
html: isDefaultHtml ? undefined : html,
|
110 |
+
previousPrompt: isDefaultHtml ? undefined : previousPrompt,
|
111 |
+
colors,
|
112 |
+
modelId: selectedModelId
|
113 |
+
}),
|
114 |
+
});
|
115 |
+
|
116 |
+
if (!response.ok) {
|
117 |
+
// Check specifically for 403 error (authentication required)
|
118 |
+
if (response.status === 401 || response.status === 403) {
|
119 |
+
setShowAuthError(true);
|
120 |
+
if (onAuthErrorChange) {
|
121 |
+
onAuthErrorChange(true);
|
122 |
+
}
|
123 |
+
throw new Error('Authentication required');
|
124 |
+
}
|
125 |
+
|
126 |
+
const errorData = await response.json();
|
127 |
+
throw new Error(errorData.message || 'Failed to generate code');
|
128 |
+
}
|
129 |
+
|
130 |
+
// Handle streaming response
|
131 |
+
const reader = response.body?.getReader();
|
132 |
+
const decoder = new TextDecoder();
|
133 |
+
let lastRenderTime = 0;
|
134 |
+
|
135 |
+
if (reader) {
|
136 |
+
while (true) {
|
137 |
+
const { done, value } = await reader.read();
|
138 |
+
if (done) {
|
139 |
+
if (!generatedCode.includes('</html>')) {
|
140 |
+
generatedCode += '</html>';
|
141 |
+
}
|
142 |
+
const finalCode = generatedCode.match(
|
143 |
+
/<!DOCTYPE html>[\s\S]*<\/html>/
|
144 |
+
)?.[0];
|
145 |
+
if (finalCode) {
|
146 |
+
// Update state with the final code
|
147 |
+
setHtml(finalCode);
|
148 |
+
// Only call onCodeChange once with the final code
|
149 |
+
// Add a small delay to ensure all state updates have been applied
|
150 |
+
if (onCodeChange) {
|
151 |
+
setTimeout(() => {
|
152 |
+
onCodeChange(finalCode, true);
|
153 |
+
}, 50);
|
154 |
+
}
|
155 |
+
}
|
156 |
+
break;
|
157 |
+
}
|
158 |
+
|
159 |
+
const chunkText = decoder.decode(value, { stream: true });
|
160 |
+
let parsedChunk: any;
|
161 |
+
try {
|
162 |
+
// Try to parse as JSON
|
163 |
+
parsedChunk = JSON.parse(chunkText);
|
164 |
+
} catch (parseError) {
|
165 |
+
// If JSON parsing fails, treat it as plain text (backwards compatibility)
|
166 |
+
generatedCode += chunkText;
|
167 |
+
}
|
168 |
+
if (parsedChunk && parsedChunk.type === "error") {
|
169 |
+
throw new Error(parsedChunk.message || "An error occurred");
|
170 |
+
}
|
171 |
+
const newCode = generatedCode.match(/<!DOCTYPE html>[\s\S]*/)?.[0];
|
172 |
+
if (newCode) {
|
173 |
+
// Force-close the HTML tag so the iframe doesn't render half-finished markup
|
174 |
+
let partialDoc = newCode;
|
175 |
+
if (!partialDoc.endsWith("</html>")) {
|
176 |
+
partialDoc += "\n</html>";
|
177 |
+
}
|
178 |
+
|
179 |
+
// Throttle the re-renders to avoid flashing/flicker
|
180 |
+
const now = Date.now();
|
181 |
+
if (now - lastRenderTime > 200) {
|
182 |
+
// Update the UI with partial code, but don't call onCodeChange
|
183 |
+
setHtml(partialDoc);
|
184 |
+
if (onCodeChange) {
|
185 |
+
onCodeChange(partialDoc, false);
|
186 |
+
}
|
187 |
+
|
188 |
+
lastRenderTime = now;
|
189 |
+
}
|
190 |
+
}
|
191 |
+
}
|
192 |
+
}
|
193 |
+
} catch (err) {
|
194 |
+
const errorMessage = (err as Error).message || 'An error occurred while generating code';
|
195 |
+
setError(errorMessage);
|
196 |
+
if (onErrorChange) {
|
197 |
+
onErrorChange(errorMessage);
|
198 |
+
}
|
199 |
+
console.error('Error generating code:', err);
|
200 |
+
} finally {
|
201 |
+
setLoading(false);
|
202 |
+
if (onLoadingChange) {
|
203 |
+
onLoadingChange(false);
|
204 |
+
}
|
205 |
+
}
|
206 |
+
};
|
207 |
+
|
208 |
+
return (
|
209 |
+
<div className={`${isFullscreen ? 'fixed inset-0 z-10 bg-novita-dark' : 'h-full'} p-4`}>
|
210 |
+
<div className="bg-white text-black rounded-md h-full overflow-hidden relative isolation-auto">
|
211 |
+
<div className="absolute top-3 right-3 flex gap-2 z-[100]">
|
212 |
+
<button
|
213 |
+
onClick={downloadHtml}
|
214 |
+
className="bg-novita-gray/90 text-white p-2 rounded-md shadow-md hover:bg-novita-gray/70 transition-colors flex items-center justify-center"
|
215 |
+
aria-label="Download HTML"
|
216 |
+
title="Download HTML"
|
217 |
+
>
|
218 |
+
<DownloadIcon />
|
219 |
+
</button>
|
220 |
+
<button
|
221 |
+
onClick={toggleFullscreen}
|
222 |
+
className="bg-novita-gray/90 text-white p-2 rounded-md shadow-md hover:bg-novita-gray/70 transition-colors flex items-center justify-center"
|
223 |
+
aria-label={isFullscreen ? 'Exit Fullscreen' : 'Full Screen'}
|
224 |
+
title={isFullscreen ? 'Exit Fullscreen' : 'Full Screen'}
|
225 |
+
>
|
226 |
+
{isFullscreen ? <MinimizeIcon /> : <MaximizeIcon />}
|
227 |
+
</button>
|
228 |
+
</div>
|
229 |
+
<iframe
|
230 |
+
className={cn("relative z-10 w-full h-full select-none", {
|
231 |
+
"pointer-events-none": loading,
|
232 |
+
})}
|
233 |
+
srcDoc={html}
|
234 |
+
title="Preview"
|
235 |
+
/>
|
236 |
+
</div>
|
237 |
+
</div>
|
238 |
+
)
|
239 |
+
});
|
src/components/prompt-input.tsx
ADDED
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import type React from "react"
|
4 |
+
|
5 |
+
import { useState, useEffect } from "react"
|
6 |
+
import { Wand2, ArrowUp, Loader2, Maximize2, Minimize2 } from "lucide-react"
|
7 |
+
import { Button } from "@/components/ui/button"
|
8 |
+
import { Textarea } from "@/components/ui/textarea"
|
9 |
+
import { ColorPanel } from "./color-panel"
|
10 |
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
11 |
+
import { FullscreenToggle } from "./ui/fullscreen-toggle"
|
12 |
+
|
13 |
+
interface PromptInputProps {
|
14 |
+
onSubmit: (prompt: string, colors: string[]) => Promise<void>;
|
15 |
+
isLoading?: boolean;
|
16 |
+
initialPrompt?: string;
|
17 |
+
onImproveError?: (error: string | null) => void;
|
18 |
+
}
|
19 |
+
|
20 |
+
export function PromptInput({
|
21 |
+
onSubmit,
|
22 |
+
isLoading = false,
|
23 |
+
initialPrompt = "",
|
24 |
+
onImproveError
|
25 |
+
}: PromptInputProps) {
|
26 |
+
const [prompt, setPrompt] = useState(initialPrompt);
|
27 |
+
const [isImprovingPrompt, setIsImprovingPrompt] = useState(false);
|
28 |
+
const [isFullScreen, setIsFullScreen] = useState(false);
|
29 |
+
const [selectedColors, setSelectedColors] = useState<string[]>([]);
|
30 |
+
const [improveError, setImproveError] = useState<string | null>(null);
|
31 |
+
|
32 |
+
// Update prompt when initialPrompt changes
|
33 |
+
useEffect(() => {
|
34 |
+
setPrompt(initialPrompt);
|
35 |
+
}, [initialPrompt]);
|
36 |
+
|
37 |
+
// 当 improveError 改变时通知父组件
|
38 |
+
useEffect(() => {
|
39 |
+
if (onImproveError) {
|
40 |
+
onImproveError(improveError);
|
41 |
+
}
|
42 |
+
}, [improveError, onImproveError]);
|
43 |
+
|
44 |
+
const handleSubmit = async (e: React.FormEvent) => {
|
45 |
+
e.preventDefault();
|
46 |
+
if (prompt.trim() === '' || isLoading) return;
|
47 |
+
|
48 |
+
// Clear any previous errors
|
49 |
+
setImproveError(null);
|
50 |
+
await onSubmit(prompt, selectedColors);
|
51 |
+
}
|
52 |
+
|
53 |
+
const improvePrompt = async () => {
|
54 |
+
if (prompt.trim() === '' || isImprovingPrompt || isLoading) return;
|
55 |
+
|
56 |
+
// Clear previous errors
|
57 |
+
setImproveError(null);
|
58 |
+
setIsImprovingPrompt(true);
|
59 |
+
|
60 |
+
try {
|
61 |
+
const response = await fetch("/api/improve-prompt", {
|
62 |
+
method: "POST",
|
63 |
+
headers: {
|
64 |
+
"Content-Type": "application/json",
|
65 |
+
},
|
66 |
+
body: JSON.stringify({ prompt: prompt.trim() }),
|
67 |
+
})
|
68 |
+
|
69 |
+
if (!response.ok) {
|
70 |
+
const errorText = await response.text()
|
71 |
+
throw new Error(errorText || `Failed to improve prompt (${response.status})`);
|
72 |
+
}
|
73 |
+
|
74 |
+
if (!response.body) {
|
75 |
+
throw new Error("Response body is null");
|
76 |
+
}
|
77 |
+
|
78 |
+
// Handle streaming response
|
79 |
+
const reader = response.body.getReader();
|
80 |
+
let improvedPrompt = "";
|
81 |
+
|
82 |
+
let textDecoder = new TextDecoder();
|
83 |
+
while (true) {
|
84 |
+
const { done, value } = await reader.read();
|
85 |
+
if (done) break;
|
86 |
+
|
87 |
+
const chunkText = textDecoder.decode(value, { stream: true });
|
88 |
+
let parsedChunk: any;
|
89 |
+
try {
|
90 |
+
// Parse the JSON response
|
91 |
+
parsedChunk = JSON.parse(chunkText);
|
92 |
+
} catch (parseError) {
|
93 |
+
// If JSON parsing fails, treat it as plain text (backwards compatibility)
|
94 |
+
improvedPrompt += chunkText
|
95 |
+
setPrompt(improvedPrompt)
|
96 |
+
}
|
97 |
+
if (parsedChunk && parsedChunk.type === "error") {
|
98 |
+
throw new Error(parsedChunk.message || "An error occurred");
|
99 |
+
}
|
100 |
+
}
|
101 |
+
} catch (error) {
|
102 |
+
console.error("Error improving prompt:", error)
|
103 |
+
setImproveError(error instanceof Error ? error.message : "Failed to improve prompt")
|
104 |
+
} finally {
|
105 |
+
setIsImprovingPrompt(false)
|
106 |
+
}
|
107 |
+
}
|
108 |
+
|
109 |
+
const toggleFullScreen = () => {
|
110 |
+
setIsFullScreen(!isFullScreen)
|
111 |
+
}
|
112 |
+
|
113 |
+
const handleColorsChange = (colors: string[]) => {
|
114 |
+
setSelectedColors(colors);
|
115 |
+
}
|
116 |
+
|
117 |
+
const isPromptTooShort = prompt.length < 10
|
118 |
+
|
119 |
+
return (
|
120 |
+
<div className={`border-t border-novita-gray/20 p-4 relative transition-all duration-300 ease-in-out ${isFullScreen ? 'h-[50vh]' : ''}`}>
|
121 |
+
<div className="absolute top-1 right-1 z-10">
|
122 |
+
<FullscreenToggle
|
123 |
+
isFullScreen={isFullScreen}
|
124 |
+
onClick={toggleFullScreen}
|
125 |
+
/>
|
126 |
+
</div>
|
127 |
+
<form onSubmit={handleSubmit} className="flex flex-col gap-4 h-full">
|
128 |
+
<div className={`relative ${isFullScreen ? 'h-full' : ''}`}>
|
129 |
+
<ColorPanel onColorsChange={handleColorsChange} />
|
130 |
+
<Textarea
|
131 |
+
value={prompt}
|
132 |
+
onChange={(e) => {
|
133 |
+
setPrompt(e.target.value)
|
134 |
+
// Clear error when user types
|
135 |
+
if (improveError) setImproveError(null)
|
136 |
+
}}
|
137 |
+
placeholder="Describe what site you want to build. E.g., Create a landing page with a hero section, features grid, and a contact form..."
|
138 |
+
className={`min-h-24 pr-20 pt-12 bg-novita-gray/20 border-novita-gray/30 text-white placeholder:text-novita-gray/70 resize-none ${isFullScreen ? 'h-full' : ''}`}
|
139 |
+
disabled={isLoading || isImprovingPrompt}
|
140 |
+
/>
|
141 |
+
<div className="absolute bottom-3 right-3 flex gap-2">
|
142 |
+
<TooltipProvider>
|
143 |
+
<Tooltip>
|
144 |
+
<TooltipTrigger asChild>
|
145 |
+
<span>
|
146 |
+
<Button
|
147 |
+
type="button"
|
148 |
+
size="icon"
|
149 |
+
variant="outline"
|
150 |
+
className="h-8 w-8 bg-novita-gray/20 border-novita-gray/30 text-white hover:bg-novita-gray/30"
|
151 |
+
disabled={isPromptTooShort || isLoading || isImprovingPrompt}
|
152 |
+
onClick={improvePrompt}
|
153 |
+
>
|
154 |
+
{isImprovingPrompt ? (
|
155 |
+
<Loader2 className="h-4 w-4 animate-spin" />
|
156 |
+
) : (
|
157 |
+
<Wand2 className="h-4 w-4" />
|
158 |
+
)}
|
159 |
+
<span className="sr-only">Magic wand</span>
|
160 |
+
</Button>
|
161 |
+
</span>
|
162 |
+
</TooltipTrigger>
|
163 |
+
{isPromptTooShort && (
|
164 |
+
<TooltipContent className="bg-novita-gray text-white">
|
165 |
+
<p>Your prompt is too simple, we can't improve it.</p>
|
166 |
+
</TooltipContent>
|
167 |
+
)}
|
168 |
+
</Tooltip>
|
169 |
+
</TooltipProvider>
|
170 |
+
<Button
|
171 |
+
type="submit"
|
172 |
+
size="icon"
|
173 |
+
className="h-8 w-8 bg-novita-green text-black hover:bg-novita-green/90"
|
174 |
+
disabled={prompt.trim() === "" || isLoading || isImprovingPrompt}
|
175 |
+
>
|
176 |
+
{isLoading ? (
|
177 |
+
<Loader2 className="h-4 w-4 animate-spin" />
|
178 |
+
) : (
|
179 |
+
<ArrowUp className="h-4 w-4" />
|
180 |
+
)}
|
181 |
+
<span className="sr-only">Submit</span>
|
182 |
+
</Button>
|
183 |
+
</div>
|
184 |
+
</div>
|
185 |
+
</form>
|
186 |
+
</div>
|
187 |
+
)
|
188 |
+
}
|
src/components/theme-provider.tsx
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
'use client'
|
2 |
+
|
3 |
+
import * as React from 'react'
|
4 |
+
import {
|
5 |
+
ThemeProvider as NextThemesProvider,
|
6 |
+
type ThemeProviderProps,
|
7 |
+
} from 'next-themes'
|
8 |
+
|
9 |
+
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
10 |
+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
11 |
+
}
|
src/components/ui/accordion.tsx
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
5 |
+
import { ChevronDown } from "lucide-react"
|
6 |
+
|
7 |
+
import { cn } from "@/lib/utils"
|
8 |
+
|
9 |
+
const Accordion = AccordionPrimitive.Root
|
10 |
+
|
11 |
+
const AccordionItem = React.forwardRef<
|
12 |
+
React.ElementRef<typeof AccordionPrimitive.Item>,
|
13 |
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
14 |
+
>(({ className, ...props }, ref) => (
|
15 |
+
<AccordionPrimitive.Item
|
16 |
+
ref={ref}
|
17 |
+
className={cn("border-b", className)}
|
18 |
+
{...props}
|
19 |
+
/>
|
20 |
+
))
|
21 |
+
AccordionItem.displayName = "AccordionItem"
|
22 |
+
|
23 |
+
const AccordionTrigger = React.forwardRef<
|
24 |
+
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
25 |
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
26 |
+
>(({ className, children, ...props }, ref) => (
|
27 |
+
<AccordionPrimitive.Header className="flex">
|
28 |
+
<AccordionPrimitive.Trigger
|
29 |
+
ref={ref}
|
30 |
+
className={cn(
|
31 |
+
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
32 |
+
className
|
33 |
+
)}
|
34 |
+
{...props}
|
35 |
+
>
|
36 |
+
{children}
|
37 |
+
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
38 |
+
</AccordionPrimitive.Trigger>
|
39 |
+
</AccordionPrimitive.Header>
|
40 |
+
))
|
41 |
+
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
42 |
+
|
43 |
+
const AccordionContent = React.forwardRef<
|
44 |
+
React.ElementRef<typeof AccordionPrimitive.Content>,
|
45 |
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
46 |
+
>(({ className, children, ...props }, ref) => (
|
47 |
+
<AccordionPrimitive.Content
|
48 |
+
ref={ref}
|
49 |
+
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
50 |
+
{...props}
|
51 |
+
>
|
52 |
+
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
53 |
+
</AccordionPrimitive.Content>
|
54 |
+
))
|
55 |
+
|
56 |
+
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
57 |
+
|
58 |
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
src/components/ui/alert-dialog.tsx
ADDED
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
5 |
+
|
6 |
+
import { cn } from "@/lib/utils"
|
7 |
+
import { buttonVariants } from "@/components/ui/button"
|
8 |
+
|
9 |
+
const AlertDialog = AlertDialogPrimitive.Root
|
10 |
+
|
11 |
+
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
12 |
+
|
13 |
+
const AlertDialogPortal = AlertDialogPrimitive.Portal
|
14 |
+
|
15 |
+
const AlertDialogOverlay = React.forwardRef<
|
16 |
+
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
17 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
18 |
+
>(({ className, ...props }, ref) => (
|
19 |
+
<AlertDialogPrimitive.Overlay
|
20 |
+
className={cn(
|
21 |
+
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
22 |
+
className
|
23 |
+
)}
|
24 |
+
{...props}
|
25 |
+
ref={ref}
|
26 |
+
/>
|
27 |
+
))
|
28 |
+
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
29 |
+
|
30 |
+
const AlertDialogContent = React.forwardRef<
|
31 |
+
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
32 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
33 |
+
>(({ className, ...props }, ref) => (
|
34 |
+
<AlertDialogPortal>
|
35 |
+
<AlertDialogOverlay />
|
36 |
+
<AlertDialogPrimitive.Content
|
37 |
+
ref={ref}
|
38 |
+
className={cn(
|
39 |
+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
40 |
+
className
|
41 |
+
)}
|
42 |
+
{...props}
|
43 |
+
/>
|
44 |
+
</AlertDialogPortal>
|
45 |
+
))
|
46 |
+
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
47 |
+
|
48 |
+
const AlertDialogHeader = ({
|
49 |
+
className,
|
50 |
+
...props
|
51 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
52 |
+
<div
|
53 |
+
className={cn(
|
54 |
+
"flex flex-col space-y-2 text-center sm:text-left",
|
55 |
+
className
|
56 |
+
)}
|
57 |
+
{...props}
|
58 |
+
/>
|
59 |
+
)
|
60 |
+
AlertDialogHeader.displayName = "AlertDialogHeader"
|
61 |
+
|
62 |
+
const AlertDialogFooter = ({
|
63 |
+
className,
|
64 |
+
...props
|
65 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
66 |
+
<div
|
67 |
+
className={cn(
|
68 |
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
69 |
+
className
|
70 |
+
)}
|
71 |
+
{...props}
|
72 |
+
/>
|
73 |
+
)
|
74 |
+
AlertDialogFooter.displayName = "AlertDialogFooter"
|
75 |
+
|
76 |
+
const AlertDialogTitle = React.forwardRef<
|
77 |
+
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
78 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
79 |
+
>(({ className, ...props }, ref) => (
|
80 |
+
<AlertDialogPrimitive.Title
|
81 |
+
ref={ref}
|
82 |
+
className={cn("text-lg font-semibold", className)}
|
83 |
+
{...props}
|
84 |
+
/>
|
85 |
+
))
|
86 |
+
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
|
87 |
+
|
88 |
+
const AlertDialogDescription = React.forwardRef<
|
89 |
+
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
90 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
91 |
+
>(({ className, ...props }, ref) => (
|
92 |
+
<AlertDialogPrimitive.Description
|
93 |
+
ref={ref}
|
94 |
+
className={cn("text-sm text-muted-foreground", className)}
|
95 |
+
{...props}
|
96 |
+
/>
|
97 |
+
))
|
98 |
+
AlertDialogDescription.displayName =
|
99 |
+
AlertDialogPrimitive.Description.displayName
|
100 |
+
|
101 |
+
const AlertDialogAction = React.forwardRef<
|
102 |
+
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
103 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
104 |
+
>(({ className, ...props }, ref) => (
|
105 |
+
<AlertDialogPrimitive.Action
|
106 |
+
ref={ref}
|
107 |
+
className={cn(buttonVariants(), className)}
|
108 |
+
{...props}
|
109 |
+
/>
|
110 |
+
))
|
111 |
+
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
|
112 |
+
|
113 |
+
const AlertDialogCancel = React.forwardRef<
|
114 |
+
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
115 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
116 |
+
>(({ className, ...props }, ref) => (
|
117 |
+
<AlertDialogPrimitive.Cancel
|
118 |
+
ref={ref}
|
119 |
+
className={cn(
|
120 |
+
buttonVariants({ variant: "outline" }),
|
121 |
+
"mt-2 sm:mt-0",
|
122 |
+
className
|
123 |
+
)}
|
124 |
+
{...props}
|
125 |
+
/>
|
126 |
+
))
|
127 |
+
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
|
128 |
+
|
129 |
+
export {
|
130 |
+
AlertDialog,
|
131 |
+
AlertDialogPortal,
|
132 |
+
AlertDialogOverlay,
|
133 |
+
AlertDialogTrigger,
|
134 |
+
AlertDialogContent,
|
135 |
+
AlertDialogHeader,
|
136 |
+
AlertDialogFooter,
|
137 |
+
AlertDialogTitle,
|
138 |
+
AlertDialogDescription,
|
139 |
+
AlertDialogAction,
|
140 |
+
AlertDialogCancel,
|
141 |
+
}
|
src/components/ui/alert.tsx
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
3 |
+
|
4 |
+
import { cn } from "@/lib/utils"
|
5 |
+
|
6 |
+
const alertVariants = cva(
|
7 |
+
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
8 |
+
{
|
9 |
+
variants: {
|
10 |
+
variant: {
|
11 |
+
default: "bg-background text-foreground",
|
12 |
+
destructive:
|
13 |
+
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
14 |
+
},
|
15 |
+
},
|
16 |
+
defaultVariants: {
|
17 |
+
variant: "default",
|
18 |
+
},
|
19 |
+
}
|
20 |
+
)
|
21 |
+
|
22 |
+
const Alert = React.forwardRef<
|
23 |
+
HTMLDivElement,
|
24 |
+
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
25 |
+
>(({ className, variant, ...props }, ref) => (
|
26 |
+
<div
|
27 |
+
ref={ref}
|
28 |
+
role="alert"
|
29 |
+
className={cn(alertVariants({ variant }), className)}
|
30 |
+
{...props}
|
31 |
+
/>
|
32 |
+
))
|
33 |
+
Alert.displayName = "Alert"
|
34 |
+
|
35 |
+
const AlertTitle = React.forwardRef<
|
36 |
+
HTMLParagraphElement,
|
37 |
+
React.HTMLAttributes<HTMLHeadingElement>
|
38 |
+
>(({ className, ...props }, ref) => (
|
39 |
+
<h5
|
40 |
+
ref={ref}
|
41 |
+
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
42 |
+
{...props}
|
43 |
+
/>
|
44 |
+
))
|
45 |
+
AlertTitle.displayName = "AlertTitle"
|
46 |
+
|
47 |
+
const AlertDescription = React.forwardRef<
|
48 |
+
HTMLParagraphElement,
|
49 |
+
React.HTMLAttributes<HTMLParagraphElement>
|
50 |
+
>(({ className, ...props }, ref) => (
|
51 |
+
<div
|
52 |
+
ref={ref}
|
53 |
+
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
54 |
+
{...props}
|
55 |
+
/>
|
56 |
+
))
|
57 |
+
AlertDescription.displayName = "AlertDescription"
|
58 |
+
|
59 |
+
export { Alert, AlertTitle, AlertDescription }
|
src/components/ui/aspect-ratio.tsx
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
|
4 |
+
|
5 |
+
const AspectRatio = AspectRatioPrimitive.Root
|
6 |
+
|
7 |
+
export { AspectRatio }
|
src/components/ui/avatar.tsx
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
5 |
+
|
6 |
+
import { cn } from "@/lib/utils"
|
7 |
+
|
8 |
+
const Avatar = React.forwardRef<
|
9 |
+
React.ElementRef<typeof AvatarPrimitive.Root>,
|
10 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
11 |
+
>(({ className, ...props }, ref) => (
|
12 |
+
<AvatarPrimitive.Root
|
13 |
+
ref={ref}
|
14 |
+
className={cn(
|
15 |
+
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
16 |
+
className
|
17 |
+
)}
|
18 |
+
{...props}
|
19 |
+
/>
|
20 |
+
))
|
21 |
+
Avatar.displayName = AvatarPrimitive.Root.displayName
|
22 |
+
|
23 |
+
const AvatarImage = React.forwardRef<
|
24 |
+
React.ElementRef<typeof AvatarPrimitive.Image>,
|
25 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
26 |
+
>(({ className, ...props }, ref) => (
|
27 |
+
<AvatarPrimitive.Image
|
28 |
+
ref={ref}
|
29 |
+
className={cn("aspect-square h-full w-full", className)}
|
30 |
+
{...props}
|
31 |
+
/>
|
32 |
+
))
|
33 |
+
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
34 |
+
|
35 |
+
const AvatarFallback = React.forwardRef<
|
36 |
+
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
37 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
38 |
+
>(({ className, ...props }, ref) => (
|
39 |
+
<AvatarPrimitive.Fallback
|
40 |
+
ref={ref}
|
41 |
+
className={cn(
|
42 |
+
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
43 |
+
className
|
44 |
+
)}
|
45 |
+
{...props}
|
46 |
+
/>
|
47 |
+
))
|
48 |
+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
49 |
+
|
50 |
+
export { Avatar, AvatarImage, AvatarFallback }
|
src/components/ui/badge.tsx
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
3 |
+
|
4 |
+
import { cn } from "@/lib/utils"
|
5 |
+
|
6 |
+
const badgeVariants = cva(
|
7 |
+
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
8 |
+
{
|
9 |
+
variants: {
|
10 |
+
variant: {
|
11 |
+
default:
|
12 |
+
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
13 |
+
secondary:
|
14 |
+
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
15 |
+
destructive:
|
16 |
+
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
17 |
+
outline: "text-foreground",
|
18 |
+
},
|
19 |
+
},
|
20 |
+
defaultVariants: {
|
21 |
+
variant: "default",
|
22 |
+
},
|
23 |
+
}
|
24 |
+
)
|
25 |
+
|
26 |
+
export interface BadgeProps
|
27 |
+
extends React.HTMLAttributes<HTMLDivElement>,
|
28 |
+
VariantProps<typeof badgeVariants> {}
|
29 |
+
|
30 |
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
31 |
+
return (
|
32 |
+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
33 |
+
)
|
34 |
+
}
|
35 |
+
|
36 |
+
export { Badge, badgeVariants }
|
src/components/ui/breadcrumb.tsx
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { Slot } from "@radix-ui/react-slot"
|
3 |
+
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const Breadcrumb = React.forwardRef<
|
8 |
+
HTMLElement,
|
9 |
+
React.ComponentPropsWithoutRef<"nav"> & {
|
10 |
+
separator?: React.ReactNode
|
11 |
+
}
|
12 |
+
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
|
13 |
+
Breadcrumb.displayName = "Breadcrumb"
|
14 |
+
|
15 |
+
const BreadcrumbList = React.forwardRef<
|
16 |
+
HTMLOListElement,
|
17 |
+
React.ComponentPropsWithoutRef<"ol">
|
18 |
+
>(({ className, ...props }, ref) => (
|
19 |
+
<ol
|
20 |
+
ref={ref}
|
21 |
+
className={cn(
|
22 |
+
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
|
23 |
+
className
|
24 |
+
)}
|
25 |
+
{...props}
|
26 |
+
/>
|
27 |
+
))
|
28 |
+
BreadcrumbList.displayName = "BreadcrumbList"
|
29 |
+
|
30 |
+
const BreadcrumbItem = React.forwardRef<
|
31 |
+
HTMLLIElement,
|
32 |
+
React.ComponentPropsWithoutRef<"li">
|
33 |
+
>(({ className, ...props }, ref) => (
|
34 |
+
<li
|
35 |
+
ref={ref}
|
36 |
+
className={cn("inline-flex items-center gap-1.5", className)}
|
37 |
+
{...props}
|
38 |
+
/>
|
39 |
+
))
|
40 |
+
BreadcrumbItem.displayName = "BreadcrumbItem"
|
41 |
+
|
42 |
+
const BreadcrumbLink = React.forwardRef<
|
43 |
+
HTMLAnchorElement,
|
44 |
+
React.ComponentPropsWithoutRef<"a"> & {
|
45 |
+
asChild?: boolean
|
46 |
+
}
|
47 |
+
>(({ asChild, className, ...props }, ref) => {
|
48 |
+
const Comp = asChild ? Slot : "a"
|
49 |
+
|
50 |
+
return (
|
51 |
+
<Comp
|
52 |
+
ref={ref}
|
53 |
+
className={cn("transition-colors hover:text-foreground", className)}
|
54 |
+
{...props}
|
55 |
+
/>
|
56 |
+
)
|
57 |
+
})
|
58 |
+
BreadcrumbLink.displayName = "BreadcrumbLink"
|
59 |
+
|
60 |
+
const BreadcrumbPage = React.forwardRef<
|
61 |
+
HTMLSpanElement,
|
62 |
+
React.ComponentPropsWithoutRef<"span">
|
63 |
+
>(({ className, ...props }, ref) => (
|
64 |
+
<span
|
65 |
+
ref={ref}
|
66 |
+
role="link"
|
67 |
+
aria-disabled="true"
|
68 |
+
aria-current="page"
|
69 |
+
className={cn("font-normal text-foreground", className)}
|
70 |
+
{...props}
|
71 |
+
/>
|
72 |
+
))
|
73 |
+
BreadcrumbPage.displayName = "BreadcrumbPage"
|
74 |
+
|
75 |
+
const BreadcrumbSeparator = ({
|
76 |
+
children,
|
77 |
+
className,
|
78 |
+
...props
|
79 |
+
}: React.ComponentProps<"li">) => (
|
80 |
+
<li
|
81 |
+
role="presentation"
|
82 |
+
aria-hidden="true"
|
83 |
+
className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
|
84 |
+
{...props}
|
85 |
+
>
|
86 |
+
{children ?? <ChevronRight />}
|
87 |
+
</li>
|
88 |
+
)
|
89 |
+
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
|
90 |
+
|
91 |
+
const BreadcrumbEllipsis = ({
|
92 |
+
className,
|
93 |
+
...props
|
94 |
+
}: React.ComponentProps<"span">) => (
|
95 |
+
<span
|
96 |
+
role="presentation"
|
97 |
+
aria-hidden="true"
|
98 |
+
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
99 |
+
{...props}
|
100 |
+
>
|
101 |
+
<MoreHorizontal className="h-4 w-4" />
|
102 |
+
<span className="sr-only">More</span>
|
103 |
+
</span>
|
104 |
+
)
|
105 |
+
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
|
106 |
+
|
107 |
+
export {
|
108 |
+
Breadcrumb,
|
109 |
+
BreadcrumbList,
|
110 |
+
BreadcrumbItem,
|
111 |
+
BreadcrumbLink,
|
112 |
+
BreadcrumbPage,
|
113 |
+
BreadcrumbSeparator,
|
114 |
+
BreadcrumbEllipsis,
|
115 |
+
}
|
src/components/ui/button.tsx
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { Slot } from "@radix-ui/react-slot"
|
3 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const buttonVariants = cva(
|
8 |
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
9 |
+
{
|
10 |
+
variants: {
|
11 |
+
variant: {
|
12 |
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
13 |
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
14 |
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
15 |
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
16 |
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
17 |
+
link: "text-primary underline-offset-4 hover:underline",
|
18 |
+
},
|
19 |
+
size: {
|
20 |
+
default: "h-10 px-4 py-2",
|
21 |
+
sm: "h-9 rounded-md px-3",
|
22 |
+
lg: "h-11 rounded-md px-8",
|
23 |
+
icon: "h-10 w-10",
|
24 |
+
},
|
25 |
+
},
|
26 |
+
defaultVariants: {
|
27 |
+
variant: "default",
|
28 |
+
size: "default",
|
29 |
+
},
|
30 |
+
},
|
31 |
+
)
|
32 |
+
|
33 |
+
export interface ButtonProps
|
34 |
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
35 |
+
VariantProps<typeof buttonVariants> {
|
36 |
+
asChild?: boolean
|
37 |
+
}
|
38 |
+
|
39 |
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
40 |
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
41 |
+
const Comp = asChild ? Slot : "button"
|
42 |
+
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
|
43 |
+
},
|
44 |
+
)
|
45 |
+
Button.displayName = "Button"
|
46 |
+
|
47 |
+
export { Button, buttonVariants }
|
src/components/ui/calendar.tsx
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import { ChevronLeft, ChevronRight } from "lucide-react"
|
5 |
+
import { DayPicker } from "react-day-picker"
|
6 |
+
|
7 |
+
import { cn } from "@/lib/utils"
|
8 |
+
import { buttonVariants } from "@/components/ui/button"
|
9 |
+
|
10 |
+
export type CalendarProps = React.ComponentProps<typeof DayPicker>
|
11 |
+
|
12 |
+
function Calendar({
|
13 |
+
className,
|
14 |
+
classNames,
|
15 |
+
showOutsideDays = true,
|
16 |
+
...props
|
17 |
+
}: CalendarProps) {
|
18 |
+
return (
|
19 |
+
<DayPicker
|
20 |
+
showOutsideDays={showOutsideDays}
|
21 |
+
className={cn("p-3", className)}
|
22 |
+
classNames={{
|
23 |
+
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
24 |
+
month: "space-y-4",
|
25 |
+
caption: "flex justify-center pt-1 relative items-center",
|
26 |
+
caption_label: "text-sm font-medium",
|
27 |
+
nav: "space-x-1 flex items-center",
|
28 |
+
nav_button: cn(
|
29 |
+
buttonVariants({ variant: "outline" }),
|
30 |
+
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
31 |
+
),
|
32 |
+
nav_button_previous: "absolute left-1",
|
33 |
+
nav_button_next: "absolute right-1",
|
34 |
+
table: "w-full border-collapse space-y-1",
|
35 |
+
head_row: "flex",
|
36 |
+
head_cell:
|
37 |
+
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
|
38 |
+
row: "flex w-full mt-2",
|
39 |
+
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
|
40 |
+
day: cn(
|
41 |
+
buttonVariants({ variant: "ghost" }),
|
42 |
+
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
|
43 |
+
),
|
44 |
+
day_range_end: "day-range-end",
|
45 |
+
day_selected:
|
46 |
+
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
47 |
+
day_today: "bg-accent text-accent-foreground",
|
48 |
+
day_outside:
|
49 |
+
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
|
50 |
+
day_disabled: "text-muted-foreground opacity-50",
|
51 |
+
day_range_middle:
|
52 |
+
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
53 |
+
day_hidden: "invisible",
|
54 |
+
...classNames,
|
55 |
+
}}
|
56 |
+
components={{
|
57 |
+
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
|
58 |
+
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
|
59 |
+
}}
|
60 |
+
{...props}
|
61 |
+
/>
|
62 |
+
)
|
63 |
+
}
|
64 |
+
Calendar.displayName = "Calendar"
|
65 |
+
|
66 |
+
export { Calendar }
|
src/components/ui/card.tsx
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
|
3 |
+
import { cn } from "@/lib/utils"
|
4 |
+
|
5 |
+
const Card = React.forwardRef<
|
6 |
+
HTMLDivElement,
|
7 |
+
React.HTMLAttributes<HTMLDivElement>
|
8 |
+
>(({ className, ...props }, ref) => (
|
9 |
+
<div
|
10 |
+
ref={ref}
|
11 |
+
className={cn(
|
12 |
+
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
13 |
+
className
|
14 |
+
)}
|
15 |
+
{...props}
|
16 |
+
/>
|
17 |
+
))
|
18 |
+
Card.displayName = "Card"
|
19 |
+
|
20 |
+
const CardHeader = React.forwardRef<
|
21 |
+
HTMLDivElement,
|
22 |
+
React.HTMLAttributes<HTMLDivElement>
|
23 |
+
>(({ className, ...props }, ref) => (
|
24 |
+
<div
|
25 |
+
ref={ref}
|
26 |
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
27 |
+
{...props}
|
28 |
+
/>
|
29 |
+
))
|
30 |
+
CardHeader.displayName = "CardHeader"
|
31 |
+
|
32 |
+
const CardTitle = React.forwardRef<
|
33 |
+
HTMLDivElement,
|
34 |
+
React.HTMLAttributes<HTMLDivElement>
|
35 |
+
>(({ className, ...props }, ref) => (
|
36 |
+
<div
|
37 |
+
ref={ref}
|
38 |
+
className={cn(
|
39 |
+
"text-2xl font-semibold leading-none tracking-tight",
|
40 |
+
className
|
41 |
+
)}
|
42 |
+
{...props}
|
43 |
+
/>
|
44 |
+
))
|
45 |
+
CardTitle.displayName = "CardTitle"
|
46 |
+
|
47 |
+
const CardDescription = React.forwardRef<
|
48 |
+
HTMLDivElement,
|
49 |
+
React.HTMLAttributes<HTMLDivElement>
|
50 |
+
>(({ className, ...props }, ref) => (
|
51 |
+
<div
|
52 |
+
ref={ref}
|
53 |
+
className={cn("text-sm text-muted-foreground", className)}
|
54 |
+
{...props}
|
55 |
+
/>
|
56 |
+
))
|
57 |
+
CardDescription.displayName = "CardDescription"
|
58 |
+
|
59 |
+
const CardContent = React.forwardRef<
|
60 |
+
HTMLDivElement,
|
61 |
+
React.HTMLAttributes<HTMLDivElement>
|
62 |
+
>(({ className, ...props }, ref) => (
|
63 |
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
64 |
+
))
|
65 |
+
CardContent.displayName = "CardContent"
|
66 |
+
|
67 |
+
const CardFooter = React.forwardRef<
|
68 |
+
HTMLDivElement,
|
69 |
+
React.HTMLAttributes<HTMLDivElement>
|
70 |
+
>(({ className, ...props }, ref) => (
|
71 |
+
<div
|
72 |
+
ref={ref}
|
73 |
+
className={cn("flex items-center p-6 pt-0", className)}
|
74 |
+
{...props}
|
75 |
+
/>
|
76 |
+
))
|
77 |
+
CardFooter.displayName = "CardFooter"
|
78 |
+
|
79 |
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
src/components/ui/carousel.tsx
ADDED
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import useEmblaCarousel, {
|
5 |
+
type UseEmblaCarouselType,
|
6 |
+
} from "embla-carousel-react"
|
7 |
+
import { ArrowLeft, ArrowRight } from "lucide-react"
|
8 |
+
|
9 |
+
import { cn } from "@/lib/utils"
|
10 |
+
import { Button } from "@/components/ui/button"
|
11 |
+
|
12 |
+
type CarouselApi = UseEmblaCarouselType[1]
|
13 |
+
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
14 |
+
type CarouselOptions = UseCarouselParameters[0]
|
15 |
+
type CarouselPlugin = UseCarouselParameters[1]
|
16 |
+
|
17 |
+
type CarouselProps = {
|
18 |
+
opts?: CarouselOptions
|
19 |
+
plugins?: CarouselPlugin
|
20 |
+
orientation?: "horizontal" | "vertical"
|
21 |
+
setApi?: (api: CarouselApi) => void
|
22 |
+
}
|
23 |
+
|
24 |
+
type CarouselContextProps = {
|
25 |
+
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
|
26 |
+
api: ReturnType<typeof useEmblaCarousel>[1]
|
27 |
+
scrollPrev: () => void
|
28 |
+
scrollNext: () => void
|
29 |
+
canScrollPrev: boolean
|
30 |
+
canScrollNext: boolean
|
31 |
+
} & CarouselProps
|
32 |
+
|
33 |
+
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
|
34 |
+
|
35 |
+
function useCarousel() {
|
36 |
+
const context = React.useContext(CarouselContext)
|
37 |
+
|
38 |
+
if (!context) {
|
39 |
+
throw new Error("useCarousel must be used within a <Carousel />")
|
40 |
+
}
|
41 |
+
|
42 |
+
return context
|
43 |
+
}
|
44 |
+
|
45 |
+
const Carousel = React.forwardRef<
|
46 |
+
HTMLDivElement,
|
47 |
+
React.HTMLAttributes<HTMLDivElement> & CarouselProps
|
48 |
+
>(
|
49 |
+
(
|
50 |
+
{
|
51 |
+
orientation = "horizontal",
|
52 |
+
opts,
|
53 |
+
setApi,
|
54 |
+
plugins,
|
55 |
+
className,
|
56 |
+
children,
|
57 |
+
...props
|
58 |
+
},
|
59 |
+
ref
|
60 |
+
) => {
|
61 |
+
const [carouselRef, api] = useEmblaCarousel(
|
62 |
+
{
|
63 |
+
...opts,
|
64 |
+
axis: orientation === "horizontal" ? "x" : "y",
|
65 |
+
},
|
66 |
+
plugins
|
67 |
+
)
|
68 |
+
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
|
69 |
+
const [canScrollNext, setCanScrollNext] = React.useState(false)
|
70 |
+
|
71 |
+
const onSelect = React.useCallback((api: CarouselApi) => {
|
72 |
+
if (!api) {
|
73 |
+
return
|
74 |
+
}
|
75 |
+
|
76 |
+
setCanScrollPrev(api.canScrollPrev())
|
77 |
+
setCanScrollNext(api.canScrollNext())
|
78 |
+
}, [])
|
79 |
+
|
80 |
+
const scrollPrev = React.useCallback(() => {
|
81 |
+
api?.scrollPrev()
|
82 |
+
}, [api])
|
83 |
+
|
84 |
+
const scrollNext = React.useCallback(() => {
|
85 |
+
api?.scrollNext()
|
86 |
+
}, [api])
|
87 |
+
|
88 |
+
const handleKeyDown = React.useCallback(
|
89 |
+
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
90 |
+
if (event.key === "ArrowLeft") {
|
91 |
+
event.preventDefault()
|
92 |
+
scrollPrev()
|
93 |
+
} else if (event.key === "ArrowRight") {
|
94 |
+
event.preventDefault()
|
95 |
+
scrollNext()
|
96 |
+
}
|
97 |
+
},
|
98 |
+
[scrollPrev, scrollNext]
|
99 |
+
)
|
100 |
+
|
101 |
+
React.useEffect(() => {
|
102 |
+
if (!api || !setApi) {
|
103 |
+
return
|
104 |
+
}
|
105 |
+
|
106 |
+
setApi(api)
|
107 |
+
}, [api, setApi])
|
108 |
+
|
109 |
+
React.useEffect(() => {
|
110 |
+
if (!api) {
|
111 |
+
return
|
112 |
+
}
|
113 |
+
|
114 |
+
onSelect(api)
|
115 |
+
api.on("reInit", onSelect)
|
116 |
+
api.on("select", onSelect)
|
117 |
+
|
118 |
+
return () => {
|
119 |
+
api?.off("select", onSelect)
|
120 |
+
}
|
121 |
+
}, [api, onSelect])
|
122 |
+
|
123 |
+
return (
|
124 |
+
<CarouselContext.Provider
|
125 |
+
value={{
|
126 |
+
carouselRef,
|
127 |
+
api: api,
|
128 |
+
opts,
|
129 |
+
orientation:
|
130 |
+
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
131 |
+
scrollPrev,
|
132 |
+
scrollNext,
|
133 |
+
canScrollPrev,
|
134 |
+
canScrollNext,
|
135 |
+
}}
|
136 |
+
>
|
137 |
+
<div
|
138 |
+
ref={ref}
|
139 |
+
onKeyDownCapture={handleKeyDown}
|
140 |
+
className={cn("relative", className)}
|
141 |
+
role="region"
|
142 |
+
aria-roledescription="carousel"
|
143 |
+
{...props}
|
144 |
+
>
|
145 |
+
{children}
|
146 |
+
</div>
|
147 |
+
</CarouselContext.Provider>
|
148 |
+
)
|
149 |
+
}
|
150 |
+
)
|
151 |
+
Carousel.displayName = "Carousel"
|
152 |
+
|
153 |
+
const CarouselContent = React.forwardRef<
|
154 |
+
HTMLDivElement,
|
155 |
+
React.HTMLAttributes<HTMLDivElement>
|
156 |
+
>(({ className, ...props }, ref) => {
|
157 |
+
const { carouselRef, orientation } = useCarousel()
|
158 |
+
|
159 |
+
return (
|
160 |
+
<div ref={carouselRef} className="overflow-hidden">
|
161 |
+
<div
|
162 |
+
ref={ref}
|
163 |
+
className={cn(
|
164 |
+
"flex",
|
165 |
+
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
166 |
+
className
|
167 |
+
)}
|
168 |
+
{...props}
|
169 |
+
/>
|
170 |
+
</div>
|
171 |
+
)
|
172 |
+
})
|
173 |
+
CarouselContent.displayName = "CarouselContent"
|
174 |
+
|
175 |
+
const CarouselItem = React.forwardRef<
|
176 |
+
HTMLDivElement,
|
177 |
+
React.HTMLAttributes<HTMLDivElement>
|
178 |
+
>(({ className, ...props }, ref) => {
|
179 |
+
const { orientation } = useCarousel()
|
180 |
+
|
181 |
+
return (
|
182 |
+
<div
|
183 |
+
ref={ref}
|
184 |
+
role="group"
|
185 |
+
aria-roledescription="slide"
|
186 |
+
className={cn(
|
187 |
+
"min-w-0 shrink-0 grow-0 basis-full",
|
188 |
+
orientation === "horizontal" ? "pl-4" : "pt-4",
|
189 |
+
className
|
190 |
+
)}
|
191 |
+
{...props}
|
192 |
+
/>
|
193 |
+
)
|
194 |
+
})
|
195 |
+
CarouselItem.displayName = "CarouselItem"
|
196 |
+
|
197 |
+
const CarouselPrevious = React.forwardRef<
|
198 |
+
HTMLButtonElement,
|
199 |
+
React.ComponentProps<typeof Button>
|
200 |
+
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
201 |
+
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
202 |
+
|
203 |
+
return (
|
204 |
+
<Button
|
205 |
+
ref={ref}
|
206 |
+
variant={variant}
|
207 |
+
size={size}
|
208 |
+
className={cn(
|
209 |
+
"absolute h-8 w-8 rounded-full",
|
210 |
+
orientation === "horizontal"
|
211 |
+
? "-left-12 top-1/2 -translate-y-1/2"
|
212 |
+
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
213 |
+
className
|
214 |
+
)}
|
215 |
+
disabled={!canScrollPrev}
|
216 |
+
onClick={scrollPrev}
|
217 |
+
{...props}
|
218 |
+
>
|
219 |
+
<ArrowLeft className="h-4 w-4" />
|
220 |
+
<span className="sr-only">Previous slide</span>
|
221 |
+
</Button>
|
222 |
+
)
|
223 |
+
})
|
224 |
+
CarouselPrevious.displayName = "CarouselPrevious"
|
225 |
+
|
226 |
+
const CarouselNext = React.forwardRef<
|
227 |
+
HTMLButtonElement,
|
228 |
+
React.ComponentProps<typeof Button>
|
229 |
+
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
230 |
+
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
231 |
+
|
232 |
+
return (
|
233 |
+
<Button
|
234 |
+
ref={ref}
|
235 |
+
variant={variant}
|
236 |
+
size={size}
|
237 |
+
className={cn(
|
238 |
+
"absolute h-8 w-8 rounded-full",
|
239 |
+
orientation === "horizontal"
|
240 |
+
? "-right-12 top-1/2 -translate-y-1/2"
|
241 |
+
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
242 |
+
className
|
243 |
+
)}
|
244 |
+
disabled={!canScrollNext}
|
245 |
+
onClick={scrollNext}
|
246 |
+
{...props}
|
247 |
+
>
|
248 |
+
<ArrowRight className="h-4 w-4" />
|
249 |
+
<span className="sr-only">Next slide</span>
|
250 |
+
</Button>
|
251 |
+
)
|
252 |
+
})
|
253 |
+
CarouselNext.displayName = "CarouselNext"
|
254 |
+
|
255 |
+
export {
|
256 |
+
type CarouselApi,
|
257 |
+
Carousel,
|
258 |
+
CarouselContent,
|
259 |
+
CarouselItem,
|
260 |
+
CarouselPrevious,
|
261 |
+
CarouselNext,
|
262 |
+
}
|
src/components/ui/chart.tsx
ADDED
@@ -0,0 +1,365 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as RechartsPrimitive from "recharts"
|
5 |
+
|
6 |
+
import { cn } from "@/lib/utils"
|
7 |
+
|
8 |
+
// Format: { THEME_NAME: CSS_SELECTOR }
|
9 |
+
const THEMES = { light: "", dark: ".dark" } as const
|
10 |
+
|
11 |
+
export type ChartConfig = {
|
12 |
+
[k in string]: {
|
13 |
+
label?: React.ReactNode
|
14 |
+
icon?: React.ComponentType
|
15 |
+
} & (
|
16 |
+
| { color?: string; theme?: never }
|
17 |
+
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
18 |
+
)
|
19 |
+
}
|
20 |
+
|
21 |
+
type ChartContextProps = {
|
22 |
+
config: ChartConfig
|
23 |
+
}
|
24 |
+
|
25 |
+
const ChartContext = React.createContext<ChartContextProps | null>(null)
|
26 |
+
|
27 |
+
function useChart() {
|
28 |
+
const context = React.useContext(ChartContext)
|
29 |
+
|
30 |
+
if (!context) {
|
31 |
+
throw new Error("useChart must be used within a <ChartContainer />")
|
32 |
+
}
|
33 |
+
|
34 |
+
return context
|
35 |
+
}
|
36 |
+
|
37 |
+
const ChartContainer = React.forwardRef<
|
38 |
+
HTMLDivElement,
|
39 |
+
React.ComponentProps<"div"> & {
|
40 |
+
config: ChartConfig
|
41 |
+
children: React.ComponentProps<
|
42 |
+
typeof RechartsPrimitive.ResponsiveContainer
|
43 |
+
>["children"]
|
44 |
+
}
|
45 |
+
>(({ id, className, children, config, ...props }, ref) => {
|
46 |
+
const uniqueId = React.useId()
|
47 |
+
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
|
48 |
+
|
49 |
+
return (
|
50 |
+
<ChartContext.Provider value={{ config }}>
|
51 |
+
<div
|
52 |
+
data-chart={chartId}
|
53 |
+
ref={ref}
|
54 |
+
className={cn(
|
55 |
+
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
|
56 |
+
className
|
57 |
+
)}
|
58 |
+
{...props}
|
59 |
+
>
|
60 |
+
<ChartStyle id={chartId} config={config} />
|
61 |
+
<RechartsPrimitive.ResponsiveContainer>
|
62 |
+
{children}
|
63 |
+
</RechartsPrimitive.ResponsiveContainer>
|
64 |
+
</div>
|
65 |
+
</ChartContext.Provider>
|
66 |
+
)
|
67 |
+
})
|
68 |
+
ChartContainer.displayName = "Chart"
|
69 |
+
|
70 |
+
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
71 |
+
const colorConfig = Object.entries(config).filter(
|
72 |
+
([_, config]) => config.theme || config.color
|
73 |
+
)
|
74 |
+
|
75 |
+
if (!colorConfig.length) {
|
76 |
+
return null
|
77 |
+
}
|
78 |
+
|
79 |
+
return (
|
80 |
+
<style
|
81 |
+
dangerouslySetInnerHTML={{
|
82 |
+
__html: Object.entries(THEMES)
|
83 |
+
.map(
|
84 |
+
([theme, prefix]) => `
|
85 |
+
${prefix} [data-chart=${id}] {
|
86 |
+
${colorConfig
|
87 |
+
.map(([key, itemConfig]) => {
|
88 |
+
const color =
|
89 |
+
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
90 |
+
itemConfig.color
|
91 |
+
return color ? ` --color-${key}: ${color};` : null
|
92 |
+
})
|
93 |
+
.join("\n")}
|
94 |
+
}
|
95 |
+
`
|
96 |
+
)
|
97 |
+
.join("\n"),
|
98 |
+
}}
|
99 |
+
/>
|
100 |
+
)
|
101 |
+
}
|
102 |
+
|
103 |
+
const ChartTooltip = RechartsPrimitive.Tooltip
|
104 |
+
|
105 |
+
const ChartTooltipContent = React.forwardRef<
|
106 |
+
HTMLDivElement,
|
107 |
+
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
108 |
+
React.ComponentProps<"div"> & {
|
109 |
+
hideLabel?: boolean
|
110 |
+
hideIndicator?: boolean
|
111 |
+
indicator?: "line" | "dot" | "dashed"
|
112 |
+
nameKey?: string
|
113 |
+
labelKey?: string
|
114 |
+
}
|
115 |
+
>(
|
116 |
+
(
|
117 |
+
{
|
118 |
+
active,
|
119 |
+
payload,
|
120 |
+
className,
|
121 |
+
indicator = "dot",
|
122 |
+
hideLabel = false,
|
123 |
+
hideIndicator = false,
|
124 |
+
label,
|
125 |
+
labelFormatter,
|
126 |
+
labelClassName,
|
127 |
+
formatter,
|
128 |
+
color,
|
129 |
+
nameKey,
|
130 |
+
labelKey,
|
131 |
+
},
|
132 |
+
ref
|
133 |
+
) => {
|
134 |
+
const { config } = useChart()
|
135 |
+
|
136 |
+
const tooltipLabel = React.useMemo(() => {
|
137 |
+
if (hideLabel || !payload?.length) {
|
138 |
+
return null
|
139 |
+
}
|
140 |
+
|
141 |
+
const [item] = payload
|
142 |
+
const key = `${labelKey || item.dataKey || item.name || "value"}`
|
143 |
+
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
144 |
+
const value =
|
145 |
+
!labelKey && typeof label === "string"
|
146 |
+
? config[label as keyof typeof config]?.label || label
|
147 |
+
: itemConfig?.label
|
148 |
+
|
149 |
+
if (labelFormatter) {
|
150 |
+
return (
|
151 |
+
<div className={cn("font-medium", labelClassName)}>
|
152 |
+
{labelFormatter(value, payload)}
|
153 |
+
</div>
|
154 |
+
)
|
155 |
+
}
|
156 |
+
|
157 |
+
if (!value) {
|
158 |
+
return null
|
159 |
+
}
|
160 |
+
|
161 |
+
return <div className={cn("font-medium", labelClassName)}>{value}</div>
|
162 |
+
}, [
|
163 |
+
label,
|
164 |
+
labelFormatter,
|
165 |
+
payload,
|
166 |
+
hideLabel,
|
167 |
+
labelClassName,
|
168 |
+
config,
|
169 |
+
labelKey,
|
170 |
+
])
|
171 |
+
|
172 |
+
if (!active || !payload?.length) {
|
173 |
+
return null
|
174 |
+
}
|
175 |
+
|
176 |
+
const nestLabel = payload.length === 1 && indicator !== "dot"
|
177 |
+
|
178 |
+
return (
|
179 |
+
<div
|
180 |
+
ref={ref}
|
181 |
+
className={cn(
|
182 |
+
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
|
183 |
+
className
|
184 |
+
)}
|
185 |
+
>
|
186 |
+
{!nestLabel ? tooltipLabel : null}
|
187 |
+
<div className="grid gap-1.5">
|
188 |
+
{payload.map((item, index) => {
|
189 |
+
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
190 |
+
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
191 |
+
const indicatorColor = color || item.payload.fill || item.color
|
192 |
+
|
193 |
+
return (
|
194 |
+
<div
|
195 |
+
key={item.dataKey}
|
196 |
+
className={cn(
|
197 |
+
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
198 |
+
indicator === "dot" && "items-center"
|
199 |
+
)}
|
200 |
+
>
|
201 |
+
{formatter && item?.value !== undefined && item.name ? (
|
202 |
+
formatter(item.value, item.name, item, index, item.payload)
|
203 |
+
) : (
|
204 |
+
<>
|
205 |
+
{itemConfig?.icon ? (
|
206 |
+
<itemConfig.icon />
|
207 |
+
) : (
|
208 |
+
!hideIndicator && (
|
209 |
+
<div
|
210 |
+
className={cn(
|
211 |
+
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
212 |
+
{
|
213 |
+
"h-2.5 w-2.5": indicator === "dot",
|
214 |
+
"w-1": indicator === "line",
|
215 |
+
"w-0 border-[1.5px] border-dashed bg-transparent":
|
216 |
+
indicator === "dashed",
|
217 |
+
"my-0.5": nestLabel && indicator === "dashed",
|
218 |
+
}
|
219 |
+
)}
|
220 |
+
style={
|
221 |
+
{
|
222 |
+
"--color-bg": indicatorColor,
|
223 |
+
"--color-border": indicatorColor,
|
224 |
+
} as React.CSSProperties
|
225 |
+
}
|
226 |
+
/>
|
227 |
+
)
|
228 |
+
)}
|
229 |
+
<div
|
230 |
+
className={cn(
|
231 |
+
"flex flex-1 justify-between leading-none",
|
232 |
+
nestLabel ? "items-end" : "items-center"
|
233 |
+
)}
|
234 |
+
>
|
235 |
+
<div className="grid gap-1.5">
|
236 |
+
{nestLabel ? tooltipLabel : null}
|
237 |
+
<span className="text-muted-foreground">
|
238 |
+
{itemConfig?.label || item.name}
|
239 |
+
</span>
|
240 |
+
</div>
|
241 |
+
{item.value && (
|
242 |
+
<span className="font-mono font-medium tabular-nums text-foreground">
|
243 |
+
{item.value.toLocaleString()}
|
244 |
+
</span>
|
245 |
+
)}
|
246 |
+
</div>
|
247 |
+
</>
|
248 |
+
)}
|
249 |
+
</div>
|
250 |
+
)
|
251 |
+
})}
|
252 |
+
</div>
|
253 |
+
</div>
|
254 |
+
)
|
255 |
+
}
|
256 |
+
)
|
257 |
+
ChartTooltipContent.displayName = "ChartTooltip"
|
258 |
+
|
259 |
+
const ChartLegend = RechartsPrimitive.Legend
|
260 |
+
|
261 |
+
const ChartLegendContent = React.forwardRef<
|
262 |
+
HTMLDivElement,
|
263 |
+
React.ComponentProps<"div"> &
|
264 |
+
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
265 |
+
hideIcon?: boolean
|
266 |
+
nameKey?: string
|
267 |
+
}
|
268 |
+
>(
|
269 |
+
(
|
270 |
+
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
|
271 |
+
ref
|
272 |
+
) => {
|
273 |
+
const { config } = useChart()
|
274 |
+
|
275 |
+
if (!payload?.length) {
|
276 |
+
return null
|
277 |
+
}
|
278 |
+
|
279 |
+
return (
|
280 |
+
<div
|
281 |
+
ref={ref}
|
282 |
+
className={cn(
|
283 |
+
"flex items-center justify-center gap-4",
|
284 |
+
verticalAlign === "top" ? "pb-3" : "pt-3",
|
285 |
+
className
|
286 |
+
)}
|
287 |
+
>
|
288 |
+
{payload.map((item) => {
|
289 |
+
const key = `${nameKey || item.dataKey || "value"}`
|
290 |
+
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
291 |
+
|
292 |
+
return (
|
293 |
+
<div
|
294 |
+
key={item.value}
|
295 |
+
className={cn(
|
296 |
+
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
|
297 |
+
)}
|
298 |
+
>
|
299 |
+
{itemConfig?.icon && !hideIcon ? (
|
300 |
+
<itemConfig.icon />
|
301 |
+
) : (
|
302 |
+
<div
|
303 |
+
className="h-2 w-2 shrink-0 rounded-[2px]"
|
304 |
+
style={{
|
305 |
+
backgroundColor: item.color,
|
306 |
+
}}
|
307 |
+
/>
|
308 |
+
)}
|
309 |
+
{itemConfig?.label}
|
310 |
+
</div>
|
311 |
+
)
|
312 |
+
})}
|
313 |
+
</div>
|
314 |
+
)
|
315 |
+
}
|
316 |
+
)
|
317 |
+
ChartLegendContent.displayName = "ChartLegend"
|
318 |
+
|
319 |
+
// Helper to extract item config from a payload.
|
320 |
+
function getPayloadConfigFromPayload(
|
321 |
+
config: ChartConfig,
|
322 |
+
payload: unknown,
|
323 |
+
key: string
|
324 |
+
) {
|
325 |
+
if (typeof payload !== "object" || payload === null) {
|
326 |
+
return undefined
|
327 |
+
}
|
328 |
+
|
329 |
+
const payloadPayload =
|
330 |
+
"payload" in payload &&
|
331 |
+
typeof payload.payload === "object" &&
|
332 |
+
payload.payload !== null
|
333 |
+
? payload.payload
|
334 |
+
: undefined
|
335 |
+
|
336 |
+
let configLabelKey: string = key
|
337 |
+
|
338 |
+
if (
|
339 |
+
key in payload &&
|
340 |
+
typeof payload[key as keyof typeof payload] === "string"
|
341 |
+
) {
|
342 |
+
configLabelKey = payload[key as keyof typeof payload] as string
|
343 |
+
} else if (
|
344 |
+
payloadPayload &&
|
345 |
+
key in payloadPayload &&
|
346 |
+
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
347 |
+
) {
|
348 |
+
configLabelKey = payloadPayload[
|
349 |
+
key as keyof typeof payloadPayload
|
350 |
+
] as string
|
351 |
+
}
|
352 |
+
|
353 |
+
return configLabelKey in config
|
354 |
+
? config[configLabelKey]
|
355 |
+
: config[key as keyof typeof config]
|
356 |
+
}
|
357 |
+
|
358 |
+
export {
|
359 |
+
ChartContainer,
|
360 |
+
ChartTooltip,
|
361 |
+
ChartTooltipContent,
|
362 |
+
ChartLegend,
|
363 |
+
ChartLegendContent,
|
364 |
+
ChartStyle,
|
365 |
+
}
|
src/components/ui/checkbox.tsx
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
5 |
+
import { Check } from "lucide-react"
|
6 |
+
|
7 |
+
import { cn } from "@/lib/utils"
|
8 |
+
|
9 |
+
const Checkbox = React.forwardRef<
|
10 |
+
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
11 |
+
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
12 |
+
>(({ className, ...props }, ref) => (
|
13 |
+
<CheckboxPrimitive.Root
|
14 |
+
ref={ref}
|
15 |
+
className={cn(
|
16 |
+
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
17 |
+
className
|
18 |
+
)}
|
19 |
+
{...props}
|
20 |
+
>
|
21 |
+
<CheckboxPrimitive.Indicator
|
22 |
+
className={cn("flex items-center justify-center text-current")}
|
23 |
+
>
|
24 |
+
<Check className="h-4 w-4" />
|
25 |
+
</CheckboxPrimitive.Indicator>
|
26 |
+
</CheckboxPrimitive.Root>
|
27 |
+
))
|
28 |
+
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
29 |
+
|
30 |
+
export { Checkbox }
|
src/components/ui/collapsible.tsx
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
4 |
+
|
5 |
+
const Collapsible = CollapsiblePrimitive.Root
|
6 |
+
|
7 |
+
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
|
8 |
+
|
9 |
+
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
|
10 |
+
|
11 |
+
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
src/components/ui/command.tsx
ADDED
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import { type DialogProps } from "@radix-ui/react-dialog"
|
5 |
+
import { Command as CommandPrimitive } from "cmdk"
|
6 |
+
import { Search } from "lucide-react"
|
7 |
+
|
8 |
+
import { cn } from "@/lib/utils"
|
9 |
+
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
10 |
+
|
11 |
+
const Command = React.forwardRef<
|
12 |
+
React.ElementRef<typeof CommandPrimitive>,
|
13 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
14 |
+
>(({ className, ...props }, ref) => (
|
15 |
+
<CommandPrimitive
|
16 |
+
ref={ref}
|
17 |
+
className={cn(
|
18 |
+
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
19 |
+
className
|
20 |
+
)}
|
21 |
+
{...props}
|
22 |
+
/>
|
23 |
+
))
|
24 |
+
Command.displayName = CommandPrimitive.displayName
|
25 |
+
|
26 |
+
const CommandDialog = ({ children, ...props }: DialogProps) => {
|
27 |
+
return (
|
28 |
+
<Dialog {...props}>
|
29 |
+
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
30 |
+
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
31 |
+
{children}
|
32 |
+
</Command>
|
33 |
+
</DialogContent>
|
34 |
+
</Dialog>
|
35 |
+
)
|
36 |
+
}
|
37 |
+
|
38 |
+
const CommandInput = React.forwardRef<
|
39 |
+
React.ElementRef<typeof CommandPrimitive.Input>,
|
40 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
41 |
+
>(({ className, ...props }, ref) => (
|
42 |
+
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
43 |
+
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
44 |
+
<CommandPrimitive.Input
|
45 |
+
ref={ref}
|
46 |
+
className={cn(
|
47 |
+
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
48 |
+
className
|
49 |
+
)}
|
50 |
+
{...props}
|
51 |
+
/>
|
52 |
+
</div>
|
53 |
+
))
|
54 |
+
|
55 |
+
CommandInput.displayName = CommandPrimitive.Input.displayName
|
56 |
+
|
57 |
+
const CommandList = React.forwardRef<
|
58 |
+
React.ElementRef<typeof CommandPrimitive.List>,
|
59 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
60 |
+
>(({ className, ...props }, ref) => (
|
61 |
+
<CommandPrimitive.List
|
62 |
+
ref={ref}
|
63 |
+
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
64 |
+
{...props}
|
65 |
+
/>
|
66 |
+
))
|
67 |
+
|
68 |
+
CommandList.displayName = CommandPrimitive.List.displayName
|
69 |
+
|
70 |
+
const CommandEmpty = React.forwardRef<
|
71 |
+
React.ElementRef<typeof CommandPrimitive.Empty>,
|
72 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
73 |
+
>((props, ref) => (
|
74 |
+
<CommandPrimitive.Empty
|
75 |
+
ref={ref}
|
76 |
+
className="py-6 text-center text-sm"
|
77 |
+
{...props}
|
78 |
+
/>
|
79 |
+
))
|
80 |
+
|
81 |
+
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
82 |
+
|
83 |
+
const CommandGroup = React.forwardRef<
|
84 |
+
React.ElementRef<typeof CommandPrimitive.Group>,
|
85 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
86 |
+
>(({ className, ...props }, ref) => (
|
87 |
+
<CommandPrimitive.Group
|
88 |
+
ref={ref}
|
89 |
+
className={cn(
|
90 |
+
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
91 |
+
className
|
92 |
+
)}
|
93 |
+
{...props}
|
94 |
+
/>
|
95 |
+
))
|
96 |
+
|
97 |
+
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
98 |
+
|
99 |
+
const CommandSeparator = React.forwardRef<
|
100 |
+
React.ElementRef<typeof CommandPrimitive.Separator>,
|
101 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
102 |
+
>(({ className, ...props }, ref) => (
|
103 |
+
<CommandPrimitive.Separator
|
104 |
+
ref={ref}
|
105 |
+
className={cn("-mx-1 h-px bg-border", className)}
|
106 |
+
{...props}
|
107 |
+
/>
|
108 |
+
))
|
109 |
+
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
110 |
+
|
111 |
+
const CommandItem = React.forwardRef<
|
112 |
+
React.ElementRef<typeof CommandPrimitive.Item>,
|
113 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
114 |
+
>(({ className, ...props }, ref) => (
|
115 |
+
<CommandPrimitive.Item
|
116 |
+
ref={ref}
|
117 |
+
className={cn(
|
118 |
+
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
119 |
+
className
|
120 |
+
)}
|
121 |
+
{...props}
|
122 |
+
/>
|
123 |
+
))
|
124 |
+
|
125 |
+
CommandItem.displayName = CommandPrimitive.Item.displayName
|
126 |
+
|
127 |
+
const CommandShortcut = ({
|
128 |
+
className,
|
129 |
+
...props
|
130 |
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
131 |
+
return (
|
132 |
+
<span
|
133 |
+
className={cn(
|
134 |
+
"ml-auto text-xs tracking-widest text-muted-foreground",
|
135 |
+
className
|
136 |
+
)}
|
137 |
+
{...props}
|
138 |
+
/>
|
139 |
+
)
|
140 |
+
}
|
141 |
+
CommandShortcut.displayName = "CommandShortcut"
|
142 |
+
|
143 |
+
export {
|
144 |
+
Command,
|
145 |
+
CommandDialog,
|
146 |
+
CommandInput,
|
147 |
+
CommandList,
|
148 |
+
CommandEmpty,
|
149 |
+
CommandGroup,
|
150 |
+
CommandItem,
|
151 |
+
CommandShortcut,
|
152 |
+
CommandSeparator,
|
153 |
+
}
|