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 |
+
}
|