Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- index.html +994 -19
index.html
CHANGED
|
@@ -1,19 +1,994 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:20-alpine AS base
|
| 2 |
+
|
| 3 |
+
FROM base AS deps
|
| 4 |
+
RUN apk add --no-cache libc6-compat
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
COPY package.json package-lock.json* ./
|
| 8 |
+
RUN npm ci
|
| 9 |
+
|
| 10 |
+
FROM base AS builder
|
| 11 |
+
WORKDIR /app
|
| 12 |
+
COPY --from=deps /app/node_modules ./node_modules
|
| 13 |
+
COPY . .
|
| 14 |
+
|
| 15 |
+
RUN npm run build
|
| 16 |
+
|
| 17 |
+
FROM base AS runner
|
| 18 |
+
WORKDIR /app
|
| 19 |
+
|
| 20 |
+
ENV NODE_ENV=production
|
| 21 |
+
ENV PORT=7860
|
| 22 |
+
ENV HOSTNAME="0.0.0.0"
|
| 23 |
+
|
| 24 |
+
RUN addgroup --system --gid 1001 nodejs
|
| 25 |
+
RUN adduser --system --uid 1001 nextjs
|
| 26 |
+
|
| 27 |
+
COPY --from=builder /app/public ./public
|
| 28 |
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
| 29 |
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
| 30 |
+
|
| 31 |
+
USER nextjs
|
| 32 |
+
|
| 33 |
+
EXPOSE 7860
|
| 34 |
+
|
| 35 |
+
CMD ["node", "server.js"]
|
| 36 |
+
|
| 37 |
+
=== package.json ===
|
| 38 |
+
{
|
| 39 |
+
"name": "ai-backend-generator",
|
| 40 |
+
"version": "1.0.0",
|
| 41 |
+
"private": true,
|
| 42 |
+
"scripts": {
|
| 43 |
+
"dev": "next dev",
|
| 44 |
+
"build": "next build",
|
| 45 |
+
"start": "next start",
|
| 46 |
+
"lint": "next lint"
|
| 47 |
+
},
|
| 48 |
+
"dependencies": {
|
| 49 |
+
"next": "14.2.3",
|
| 50 |
+
"react": "18.3.1",
|
| 51 |
+
"react-dom": "18.3.1",
|
| 52 |
+
"openai": "4.52.0",
|
| 53 |
+
"zod": "3.23.8",
|
| 54 |
+
"prismjs": "1.29.0",
|
| 55 |
+
"react-flow-renderer": "10.3.17",
|
| 56 |
+
"lucide-react": "0.378.0",
|
| 57 |
+
"jszip": "3.10.1",
|
| 58 |
+
"file-saver": "2.0.5"
|
| 59 |
+
},
|
| 60 |
+
"devDependencies": {
|
| 61 |
+
"@types/node": "20.12.12",
|
| 62 |
+
"@types/react": "18.3.2",
|
| 63 |
+
"@types/react-dom": "18.3.0",
|
| 64 |
+
"@types/prismjs": "1.26.4",
|
| 65 |
+
"@types/file-saver": "2.0.7",
|
| 66 |
+
"typescript": "5.4.5",
|
| 67 |
+
"tailwindcss": "3.4.3",
|
| 68 |
+
"postcss": "8.4.38",
|
| 69 |
+
"autoprefixer": "10.4.19"
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
=== next.config.js ===
|
| 74 |
+
/** @type {import('next').NextConfig} */
|
| 75 |
+
const nextConfig = {
|
| 76 |
+
output: 'standalone',
|
| 77 |
+
reactStrictMode: true,
|
| 78 |
+
swcMinify: true,
|
| 79 |
+
experimental: {
|
| 80 |
+
serverComponentsExternalPackages: ['openai']
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
module.exports = nextConfig
|
| 85 |
+
|
| 86 |
+
=== postcss.config.js ===
|
| 87 |
+
module.exports = {
|
| 88 |
+
plugins: {
|
| 89 |
+
tailwindcss: {},
|
| 90 |
+
autoprefixer: {},
|
| 91 |
+
},
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
=== tailwind.config.js ===
|
| 95 |
+
/** @type {import('tailwindcss').Config} */
|
| 96 |
+
module.exports = {
|
| 97 |
+
content: [
|
| 98 |
+
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
| 99 |
+
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
| 100 |
+
],
|
| 101 |
+
theme: {
|
| 102 |
+
extend: {
|
| 103 |
+
colors: {
|
| 104 |
+
primary: {
|
| 105 |
+
50: '#f0f9ff',
|
| 106 |
+
100: '#e0f2fe',
|
| 107 |
+
200: '#bae6fd',
|
| 108 |
+
300: '#7dd3fc',
|
| 109 |
+
400: '#38bdf8',
|
| 110 |
+
500: '#0ea5e9',
|
| 111 |
+
600: '#0284c7',
|
| 112 |
+
700: '#0369a1',
|
| 113 |
+
800: '#075985',
|
| 114 |
+
900: '#0c4a6e',
|
| 115 |
+
},
|
| 116 |
+
dark: {
|
| 117 |
+
50: '#f8fafc',
|
| 118 |
+
100: '#f1f5f9',
|
| 119 |
+
200: '#e2e8f0',
|
| 120 |
+
300: '#cbd5e1',
|
| 121 |
+
400: '#94a3b8',
|
| 122 |
+
500: '#64748b',
|
| 123 |
+
600: '#475569',
|
| 124 |
+
700: '#334155',
|
| 125 |
+
800: '#1e293b',
|
| 126 |
+
900: '#0f172a',
|
| 127 |
+
950: '#020617',
|
| 128 |
+
}
|
| 129 |
+
},
|
| 130 |
+
animation: {
|
| 131 |
+
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
| 132 |
+
'float': 'float 6s ease-in-out infinite',
|
| 133 |
+
'glow': 'glow 2s ease-in-out infinite alternate',
|
| 134 |
+
},
|
| 135 |
+
keyframes: {
|
| 136 |
+
float: {
|
| 137 |
+
'0%, 100%': { transform: 'translateY(0)' },
|
| 138 |
+
'50%': { transform: 'translateY(-10px)' },
|
| 139 |
+
},
|
| 140 |
+
glow: {
|
| 141 |
+
'0%': { boxShadow: '0 0 5px rgba(14, 165, 233, 0.5)' },
|
| 142 |
+
'100%': { boxShadow: '0 0 20px rgba(14, 165, 233, 0.8)' },
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
},
|
| 146 |
+
},
|
| 147 |
+
plugins: [],
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
=== tsconfig.json ===
|
| 151 |
+
{
|
| 152 |
+
"compilerOptions": {
|
| 153 |
+
"target": "es5",
|
| 154 |
+
"lib": ["dom", "dom.iterable", "esnext"],
|
| 155 |
+
"allowJs": true,
|
| 156 |
+
"skipLibCheck": true,
|
| 157 |
+
"strict": true,
|
| 158 |
+
"noEmit": true,
|
| 159 |
+
"esModuleInterop": true,
|
| 160 |
+
"module": "esnext",
|
| 161 |
+
"moduleResolution": "bundler",
|
| 162 |
+
"resolveJsonModule": true,
|
| 163 |
+
"isolatedModules": true,
|
| 164 |
+
"jsx": "preserve",
|
| 165 |
+
"incremental": true,
|
| 166 |
+
"paths": {
|
| 167 |
+
"@/*": ["./*"]
|
| 168 |
+
}
|
| 169 |
+
},
|
| 170 |
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
| 171 |
+
"exclude": ["node_modules"]
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
=== styles/globals.css ===
|
| 175 |
+
@tailwind base;
|
| 176 |
+
@tailwind components;
|
| 177 |
+
@tailwind utilities;
|
| 178 |
+
|
| 179 |
+
@layer base {
|
| 180 |
+
html {
|
| 181 |
+
scroll-behavior: smooth;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
body {
|
| 185 |
+
@apply bg-dark-950 text-dark-100 antialiased;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
::-webkit-scrollbar {
|
| 189 |
+
width: 8px;
|
| 190 |
+
height: 8px;
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
::-webkit-scrollbar-track {
|
| 194 |
+
@apply bg-dark-900;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
::-webkit-scrollbar-thumb {
|
| 198 |
+
@apply bg-dark-600 rounded-full;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
::-webkit-scrollbar-thumb:hover {
|
| 202 |
+
@apply bg-dark-500;
|
| 203 |
+
}
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
@layer components {
|
| 207 |
+
.glass-panel {
|
| 208 |
+
@apply bg-dark-900/80 backdrop-blur-xl border border-dark-700/50 rounded-2xl;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
.gradient-border {
|
| 212 |
+
@apply relative;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
.gradient-border::before {
|
| 216 |
+
content: '';
|
| 217 |
+
@apply absolute inset-0 rounded-2xl p-[1px];
|
| 218 |
+
background: linear-gradient(135deg, rgba(14, 165, 233, 0.5), rgba(139, 92, 246, 0.5));
|
| 219 |
+
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
| 220 |
+
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
| 221 |
+
-webkit-mask-composite: xor;
|
| 222 |
+
mask-composite: exclude;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
.btn-primary {
|
| 226 |
+
@apply px-6 py-3 bg-gradient-to-r from-primary-500 to-primary-600 text-white font-semibold rounded-xl
|
| 227 |
+
hover:from-primary-600 hover:to-primary-700 transition-all duration-300 shadow-lg shadow-primary-500/25
|
| 228 |
+
disabled:opacity-50 disabled:cursor-not-allowed;
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
.btn-secondary {
|
| 232 |
+
@apply px-6 py-3 bg-dark-800 text-dark-100 font-semibold rounded-xl border border-dark-600
|
| 233 |
+
hover:bg-dark-700 hover:border-dark-500 transition-all duration-300;
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
.input-field {
|
| 237 |
+
@apply w-full px-4 py-3 bg-dark-800/50 border border-dark-600 rounded-xl text-dark-100
|
| 238 |
+
placeholder-dark-400 focus:outline-none focus:ring-2 focus:ring-primary-500/50 focus:border-primary-500
|
| 239 |
+
transition-all duration-300;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
.code-block {
|
| 243 |
+
@apply bg-dark-900 rounded-xl p-4 font-mono text-sm overflow-x-auto border border-dark-700;
|
| 244 |
+
}
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
/* Prism.js custom theme */
|
| 248 |
+
code[class*="language-"],
|
| 249 |
+
pre[class*="language-"] {
|
| 250 |
+
@apply text-dark-100 font-mono text-sm;
|
| 251 |
+
text-shadow: none;
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
.token.comment,
|
| 255 |
+
.token.prolog,
|
| 256 |
+
.token.doctype,
|
| 257 |
+
.token.cdata {
|
| 258 |
+
@apply text-dark-500;
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
.token.punctuation {
|
| 262 |
+
@apply text-dark-300;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
.token.property,
|
| 266 |
+
.token.tag,
|
| 267 |
+
.token.boolean,
|
| 268 |
+
.token.number,
|
| 269 |
+
.token.constant,
|
| 270 |
+
.token.symbol,
|
| 271 |
+
.token.deleted {
|
| 272 |
+
@apply text-pink-400;
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
.token.selector,
|
| 276 |
+
.token.attr-name,
|
| 277 |
+
.token.string,
|
| 278 |
+
.token.char,
|
| 279 |
+
.token.builtin,
|
| 280 |
+
.token.inserted {
|
| 281 |
+
@apply text-green-400;
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
.token.operator,
|
| 285 |
+
.token.entity,
|
| 286 |
+
.token.url,
|
| 287 |
+
.language-css .token.string,
|
| 288 |
+
.style .token.string {
|
| 289 |
+
@apply text-yellow-400;
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
.token.atrule,
|
| 293 |
+
.token.attr-value,
|
| 294 |
+
.token.keyword {
|
| 295 |
+
@apply text-primary-400;
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
.token.function,
|
| 299 |
+
.token.class-name {
|
| 300 |
+
@apply text-purple-400;
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
.token.regex,
|
| 304 |
+
.token.important,
|
| 305 |
+
.token.variable {
|
| 306 |
+
@apply text-orange-400;
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
=== components/Header.jsx ===
|
| 310 |
+
import { Database, Sparkles } from 'lucide-react';
|
| 311 |
+
|
| 312 |
+
export default function Header() {
|
| 313 |
+
return (
|
| 314 |
+
<header className="fixed top-0 left-0 right-0 z-50 glass-panel border-t-0 rounded-t-none">
|
| 315 |
+
<div className="max-w-7xl mx-auto px-6 py-4">
|
| 316 |
+
<div className="flex items-center justify-between">
|
| 317 |
+
<div className="flex items-center gap-3">
|
| 318 |
+
<div className="relative">
|
| 319 |
+
<div className="w-10 h-10 bg-gradient-to-br from-primary-500 to-purple-600 rounded-xl flex items-center justify-center">
|
| 320 |
+
<Database className="w-5 h-5 text-white" />
|
| 321 |
+
</div>
|
| 322 |
+
<div className="absolute -top-1 -right-1 w-4 h-4 bg-gradient-to-br from-yellow-400 to-orange-500 rounded-full flex items-center justify-center">
|
| 323 |
+
<Sparkles className="w-2.5 h-2.5 text-white" />
|
| 324 |
+
</div>
|
| 325 |
+
</div>
|
| 326 |
+
<div>
|
| 327 |
+
<h1 className="text-xl font-bold bg-gradient-to-r from-white to-dark-300 bg-clip-text text-transparent">
|
| 328 |
+
AI Backend Generator
|
| 329 |
+
</h1>
|
| 330 |
+
<p className="text-xs text-dark-400">Database + API Design Copilot</p>
|
| 331 |
+
</div>
|
| 332 |
+
</div>
|
| 333 |
+
<div className="flex items-center gap-4">
|
| 334 |
+
<a
|
| 335 |
+
href="https://huggingface.co/spaces/akhaliq/anycoder"
|
| 336 |
+
target="_blank"
|
| 337 |
+
rel="noopener noreferrer"
|
| 338 |
+
className="text-sm text-dark-400 hover:text-primary-400 transition-colors"
|
| 339 |
+
>
|
| 340 |
+
Built with anycoder
|
| 341 |
+
</a>
|
| 342 |
+
</div>
|
| 343 |
+
</div>
|
| 344 |
+
</div>
|
| 345 |
+
</header>
|
| 346 |
+
);
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
=== components/ProjectWizard.jsx ===
|
| 350 |
+
import { useState } from 'react';
|
| 351 |
+
import { ArrowRight, Loader2, Wand2, FileCode, Database, Server } from 'lucide-react';
|
| 352 |
+
|
| 353 |
+
export default function ProjectWizard({ onGenerate, isLoading }) {
|
| 354 |
+
const [projectDescription, setProjectDescription] = useState('');
|
| 355 |
+
const [projectName, setProjectName] = useState('');
|
| 356 |
+
const [targetStack, setTargetStack] = useState('nestjs-prisma');
|
| 357 |
+
|
| 358 |
+
const handleSubmit = (e) => {
|
| 359 |
+
e.preventDefault();
|
| 360 |
+
if (!projectDescription.trim() || !projectName.trim()) return;
|
| 361 |
+
onGenerate({ projectName, projectDescription, targetStack });
|
| 362 |
+
};
|
| 363 |
+
|
| 364 |
+
const examples = [
|
| 365 |
+
{
|
| 366 |
+
name: 'E-commerce Platform',
|
| 367 |
+
description: 'An e-commerce platform with products, categories, users, orders, shopping cart, reviews, and payment tracking. Include inventory management and order status tracking.',
|
| 368 |
+
},
|
| 369 |
+
{
|
| 370 |
+
name: 'Project Management Tool',
|
| 371 |
+
description: 'A project management tool like Jira with workspaces, projects, tasks, sprints, users, comments, attachments, and time tracking. Include role-based permissions.',
|
| 372 |
+
},
|
| 373 |
+
{
|
| 374 |
+
name: 'Social Media App',
|
| 375 |
+
description: 'A social media application with users, posts, comments, likes, followers/following relationships, direct messages, and notifications.',
|
| 376 |
+
},
|
| 377 |
+
];
|
| 378 |
+
|
| 379 |
+
return (
|
| 380 |
+
<div className="glass-panel gradient-border p-8">
|
| 381 |
+
<div className="flex items-center gap-3 mb-6">
|
| 382 |
+
<div className="w-12 h-12 bg-gradient-to-br from-primary-500/20 to-purple-500/20 rounded-xl flex items-center justify-center">
|
| 383 |
+
<Wand2 className="w-6 h-6 text-primary-400" />
|
| 384 |
+
</div>
|
| 385 |
+
<div>
|
| 386 |
+
<h2 className="text-2xl font-bold text-white">Describe Your Project</h2>
|
| 387 |
+
<p className="text-dark-400">Tell us what you're building and we'll design the backend</p>
|
| 388 |
+
</div>
|
| 389 |
+
</div>
|
| 390 |
+
|
| 391 |
+
<form onSubmit={handleSubmit} className="space-y-6">
|
| 392 |
+
<div>
|
| 393 |
+
<label className="block text-sm font-medium text-dark-300 mb-2">
|
| 394 |
+
Project Name
|
| 395 |
+
</label>
|
| 396 |
+
<input
|
| 397 |
+
type="text"
|
| 398 |
+
value={projectName}
|
| 399 |
+
onChange={(e) => setProjectName(e.target.value)}
|
| 400 |
+
placeholder="my-awesome-app"
|
| 401 |
+
className="input-field"
|
| 402 |
+
disabled={isLoading}
|
| 403 |
+
/>
|
| 404 |
+
</div>
|
| 405 |
+
|
| 406 |
+
<div>
|
| 407 |
+
<label className="block text-sm font-medium text-dark-300 mb-2">
|
| 408 |
+
Project Description
|
| 409 |
+
</label>
|
| 410 |
+
<textarea
|
| 411 |
+
value={projectDescription}
|
| 412 |
+
onChange={(e) => setProjectDescription(e.target.value)}
|
| 413 |
+
placeholder="Describe your project in detail. What entities do you need? What are the main features? What relationships exist between data?"
|
| 414 |
+
rows={6}
|
| 415 |
+
className="input-field resize-none"
|
| 416 |
+
disabled={isLoading}
|
| 417 |
+
/>
|
| 418 |
+
</div>
|
| 419 |
+
|
| 420 |
+
<div>
|
| 421 |
+
<label className="block text-sm font-medium text-dark-300 mb-2">
|
| 422 |
+
Target Stack
|
| 423 |
+
</label>
|
| 424 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
| 425 |
+
<button
|
| 426 |
+
type="button"
|
| 427 |
+
onClick={() => setTargetStack('nestjs-prisma')}
|
| 428 |
+
disabled={isLoading}
|
| 429 |
+
className={`p-4 rounded-xl border transition-all duration-300 text-left ${
|
| 430 |
+
targetStack === 'nestjs-prisma'
|
| 431 |
+
? 'border-primary-500 bg-primary-500/10'
|
| 432 |
+
: 'border-dark-600 bg-dark-800/50 hover:border-dark-500'
|
| 433 |
+
}`}
|
| 434 |
+
>
|
| 435 |
+
<div className="flex items-center gap-3 mb-2">
|
| 436 |
+
<Server className="w-5 h-5 text-primary-400" />
|
| 437 |
+
<span className="font-semibold text-white">NestJS + Prisma</span>
|
| 438 |
+
</div>
|
| 439 |
+
<p className="text-sm text-dark-400">
|
| 440 |
+
TypeScript, NestJS framework, Prisma ORM, PostgreSQL
|
| 441 |
+
</p>
|
| 442 |
+
</button>
|
| 443 |
+
<button
|
| 444 |
+
type="button"
|
| 445 |
+
onClick={() => setTargetStack('express-drizzle')}
|
| 446 |
+
disabled={isLoading}
|
| 447 |
+
className={`p-4 rounded-xl border transition-all duration-300 text-left ${
|
| 448 |
+
targetStack === 'express-drizzle'
|
| 449 |
+
? 'border-primary-500 bg-primary-500/10'
|
| 450 |
+
: 'border-dark-600 bg-dark-800/50 hover:border-dark-500'
|
| 451 |
+
}`}
|
| 452 |
+
>
|
| 453 |
+
<div className="flex items-center gap-3 mb-2">
|
| 454 |
+
<FileCode className="w-5 h-5 text-green-400" />
|
| 455 |
+
<span className="font-semibold text-white">Express + Drizzle</span>
|
| 456 |
+
</div>
|
| 457 |
+
<p className="text-sm text-dark-400">
|
| 458 |
+
TypeScript, Express.js, Drizzle ORM, PostgreSQL
|
| 459 |
+
</p>
|
| 460 |
+
</button>
|
| 461 |
+
</div>
|
| 462 |
+
</div>
|
| 463 |
+
|
| 464 |
+
<div>
|
| 465 |
+
<p className="text-sm text-dark-400 mb-3">Quick examples:</p>
|
| 466 |
+
<div className="flex flex-wrap gap-2">
|
| 467 |
+
{examples.map((example) => (
|
| 468 |
+
<button
|
| 469 |
+
key={example.name}
|
| 470 |
+
type="button"
|
| 471 |
+
onClick={() => {
|
| 472 |
+
setProjectName(example.name.toLowerCase().replace(/\s+/g, '-'));
|
| 473 |
+
setProjectDescription(example.description);
|
| 474 |
+
}}
|
| 475 |
+
disabled={isLoading}
|
| 476 |
+
className="px-3 py-1.5 text-sm bg-dark-800 text-dark-300 rounded-lg border border-dark-600
|
| 477 |
+
hover:border-primary-500/50 hover:text-primary-400 transition-all duration-300"
|
| 478 |
+
>
|
| 479 |
+
{example.name}
|
| 480 |
+
</button>
|
| 481 |
+
))}
|
| 482 |
+
</div>
|
| 483 |
+
</div>
|
| 484 |
+
|
| 485 |
+
<button
|
| 486 |
+
type="submit"
|
| 487 |
+
disabled={isLoading || !projectDescription.trim() || !projectName.trim()}
|
| 488 |
+
className="btn-primary w-full flex items-center justify-center gap-2"
|
| 489 |
+
>
|
| 490 |
+
{isLoading ? (
|
| 491 |
+
<>
|
| 492 |
+
<Loader2 className="w-5 h-5 animate-spin" />
|
| 493 |
+
Generating Backend...
|
| 494 |
+
</>
|
| 495 |
+
) : (
|
| 496 |
+
<>
|
| 497 |
+
Generate Backend
|
| 498 |
+
<ArrowRight className="w-5 h-5" />
|
| 499 |
+
</>
|
| 500 |
+
)}
|
| 501 |
+
</button>
|
| 502 |
+
</form>
|
| 503 |
+
</div>
|
| 504 |
+
);
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
=== components/SchemaVisualizer.jsx ===
|
| 508 |
+
import { useCallback, useMemo } from 'react';
|
| 509 |
+
import ReactFlow, {
|
| 510 |
+
Background,
|
| 511 |
+
Controls,
|
| 512 |
+
MiniMap,
|
| 513 |
+
useNodesState,
|
| 514 |
+
useEdgesState,
|
| 515 |
+
} from 'react-flow-renderer';
|
| 516 |
+
import { Database, Key, Link2 } from 'lucide-react';
|
| 517 |
+
|
| 518 |
+
const EntityNode = ({ data }) => {
|
| 519 |
+
return (
|
| 520 |
+
<div className="bg-dark-800 border border-dark-600 rounded-xl shadow-xl min-w-[200px] overflow-hidden">
|
| 521 |
+
<div className="bg-gradient-to-r from-primary-600 to-purple-600 px-4 py-2 flex items-center gap-2">
|
| 522 |
+
<Database className="w-4 h-4 text-white" />
|
| 523 |
+
<span className="font-bold text-white text-sm">{data.name}</span>
|
| 524 |
+
</div>
|
| 525 |
+
<div className="p-3 space-y-1.5">
|
| 526 |
+
{data.fields?.map((field, idx) => (
|
| 527 |
+
<div key={idx} className="flex items-center gap-2 text-xs">
|
| 528 |
+
{field.isPrimary && <Key className="w-3 h-3 text-yellow-400" />}
|
| 529 |
+
{field.isRelation && <Link2 className="w-3 h-3 text-primary-400" />}
|
| 530 |
+
<span className={`${field.isPrimary ? 'text-yellow-400' : 'text-dark-200'}`}>
|
| 531 |
+
{field.name}
|
| 532 |
+
</span>
|
| 533 |
+
<span className="text-dark-500">{field.type}</span>
|
| 534 |
+
</div>
|
| 535 |
+
))}
|
| 536 |
+
</div>
|
| 537 |
+
</div>
|
| 538 |
+
);
|
| 539 |
+
};
|
| 540 |
+
|
| 541 |
+
const nodeTypes = {
|
| 542 |
+
entity: EntityNode,
|
| 543 |
+
};
|
| 544 |
+
|
| 545 |
+
export default function SchemaVisualizer({ schema }) {
|
| 546 |
+
const { nodes: initialNodes, edges: initialEdges } = useMemo(() => {
|
| 547 |
+
if (!schema?.entities) return { nodes: [], edges: [] };
|
| 548 |
+
|
| 549 |
+
const nodes = schema.entities.map((entity, idx) => ({
|
| 550 |
+
id: entity.name,
|
| 551 |
+
type: 'entity',
|
| 552 |
+
position: {
|
| 553 |
+
x: (idx % 3) * 300 + 50,
|
| 554 |
+
y: Math.floor(idx / 3) * 250 + 50,
|
| 555 |
+
},
|
| 556 |
+
data: {
|
| 557 |
+
name: entity.name,
|
| 558 |
+
fields: entity.fields,
|
| 559 |
+
},
|
| 560 |
+
}));
|
| 561 |
+
|
| 562 |
+
const edges = [];
|
| 563 |
+
schema.entities.forEach((entity) => {
|
| 564 |
+
entity.fields
|
| 565 |
+
?.filter((f) => f.isRelation && f.relationTo)
|
| 566 |
+
.forEach((field, idx) => {
|
| 567 |
+
edges.push({
|
| 568 |
+
id: `${entity.name}-${field.relationTo}-${idx}`,
|
| 569 |
+
source: entity.name,
|
| 570 |
+
target: field.relationTo,
|
| 571 |
+
type: 'smoothstep',
|
| 572 |
+
animated: true,
|
| 573 |
+
style: { stroke: '#0ea5e9', strokeWidth: 2 },
|
| 574 |
+
});
|
| 575 |
+
});
|
| 576 |
+
});
|
| 577 |
+
|
| 578 |
+
return { nodes, edges };
|
| 579 |
+
}, [schema]);
|
| 580 |
+
|
| 581 |
+
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
| 582 |
+
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
| 583 |
+
|
| 584 |
+
if (!schema?.entities?.length) {
|
| 585 |
+
return (
|
| 586 |
+
<div className="h-[400px] flex items-center justify-center text-dark-400">
|
| 587 |
+
<p>No schema to visualize yet</p>
|
| 588 |
+
</div>
|
| 589 |
+
);
|
| 590 |
+
}
|
| 591 |
+
|
| 592 |
+
return (
|
| 593 |
+
<div className="h-[500px] bg-dark-900 rounded-xl border border-dark-700 overflow-hidden">
|
| 594 |
+
<ReactFlow
|
| 595 |
+
nodes={nodes}
|
| 596 |
+
edges={edges}
|
| 597 |
+
onNodesChange={onNodesChange}
|
| 598 |
+
onEdgesChange={onEdgesChange}
|
| 599 |
+
nodeTypes={nodeTypes}
|
| 600 |
+
fitView
|
| 601 |
+
attributionPosition="bottom-left"
|
| 602 |
+
>
|
| 603 |
+
<Background color="#334155" gap={20} />
|
| 604 |
+
<Controls className="bg-dark-800 border-dark-600" />
|
| 605 |
+
<MiniMap
|
| 606 |
+
nodeColor="#0ea5e9"
|
| 607 |
+
maskColor="rgba(15, 23, 42, 0.8)"
|
| 608 |
+
className="bg-dark-800 border-dark-600"
|
| 609 |
+
/>
|
| 610 |
+
</ReactFlow>
|
| 611 |
+
</div>
|
| 612 |
+
);
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
+
=== components/CodeViewer.jsx ===
|
| 616 |
+
import { useEffect, useRef, useState } from 'react';
|
| 617 |
+
import Prism from 'prismjs';
|
| 618 |
+
import 'prismjs/components/prism-typescript';
|
| 619 |
+
import 'prismjs/components/prism-javascript';
|
| 620 |
+
import 'prismjs/components/prism-json';
|
| 621 |
+
import 'prismjs/components/prism-bash';
|
| 622 |
+
import 'prismjs/components/prism-sql';
|
| 623 |
+
import { Copy, Check, FileCode } from 'lucide-react';
|
| 624 |
+
|
| 625 |
+
export default function CodeViewer({ code, language = 'typescript', filename }) {
|
| 626 |
+
const codeRef = useRef(null);
|
| 627 |
+
const [copied, setCopied] = useState(false);
|
| 628 |
+
|
| 629 |
+
useEffect(() => {
|
| 630 |
+
if (codeRef.current) {
|
| 631 |
+
Prism.highlightElement(codeRef.current);
|
| 632 |
+
}
|
| 633 |
+
}, [code]);
|
| 634 |
+
|
| 635 |
+
const handleCopy = async () => {
|
| 636 |
+
await navigator.clipboard.writeText(code);
|
| 637 |
+
setCopied(true);
|
| 638 |
+
setTimeout(() => setCopied(false), 2000);
|
| 639 |
+
};
|
| 640 |
+
|
| 641 |
+
return (
|
| 642 |
+
<div className="rounded-xl border border-dark-700 overflow-hidden">
|
| 643 |
+
<div className="bg-dark-800 px-4 py-2 flex items-center justify-between border-b border-dark-700">
|
| 644 |
+
<div className="flex items-center gap-2">
|
| 645 |
+
<FileCode className="w-4 h-4 text-dark-400" />
|
| 646 |
+
<span className="text-sm text-dark-300 font-mono">{filename}</span>
|
| 647 |
+
</div>
|
| 648 |
+
<button
|
| 649 |
+
onClick={handleCopy}
|
| 650 |
+
className="p-1.5 rounded-lg hover:bg-dark-700 transition-colors"
|
| 651 |
+
>
|
| 652 |
+
{copied ? (
|
| 653 |
+
<Check className="w-4 h-4 text-green-400" />
|
| 654 |
+
) : (
|
| 655 |
+
<Copy className="w-4 h-4 text-dark-400" />
|
| 656 |
+
)}
|
| 657 |
+
</button>
|
| 658 |
+
</div>
|
| 659 |
+
<div className="code-block max-h-[400px] overflow-auto">
|
| 660 |
+
<pre className="!bg-transparent !p-0 !m-0">
|
| 661 |
+
<code ref={codeRef} className={`language-${language}`}>
|
| 662 |
+
{code}
|
| 663 |
+
</code>
|
| 664 |
+
</pre>
|
| 665 |
+
</div>
|
| 666 |
+
</div>
|
| 667 |
+
);
|
| 668 |
+
}
|
| 669 |
+
|
| 670 |
+
=== components/GeneratedOutput.jsx ===
|
| 671 |
+
import { useState } from 'react';
|
| 672 |
+
import { Download, Github, Database, Server, FileJson, Code2, Layers, ChevronDown, ChevronUp } from 'lucide-react';
|
| 673 |
+
import CodeViewer from './CodeViewer';
|
| 674 |
+
import SchemaVisualizer from './SchemaVisualizer';
|
| 675 |
+
|
| 676 |
+
export default function GeneratedOutput({ result, onDownload }) {
|
| 677 |
+
const [activeTab, setActiveTab] = useState('schema');
|
| 678 |
+
const [expandedFiles, setExpandedFiles] = useState({});
|
| 679 |
+
|
| 680 |
+
const tabs = [
|
| 681 |
+
{ id: 'schema', label: 'Schema Diagram', icon: Database },
|
| 682 |
+
{ id: 'prisma', label: 'Prisma Schema', icon: Layers },
|
| 683 |
+
{ id: 'api', label: 'API Routes', icon: Server },
|
| 684 |
+
{ id: 'openapi', label: 'OpenAPI Spec', icon: FileJson },
|
| 685 |
+
{ id: 'code', label: 'Generated Code', icon: Code2 },
|
| 686 |
+
];
|
| 687 |
+
|
| 688 |
+
const toggleFile = (filename) => {
|
| 689 |
+
setExpandedFiles((prev) => ({
|
| 690 |
+
...prev,
|
| 691 |
+
[filename]: !prev[filename],
|
| 692 |
+
}));
|
| 693 |
+
};
|
| 694 |
+
|
| 695 |
+
return (
|
| 696 |
+
<div className="space-y-6">
|
| 697 |
+
<div className="glass-panel gradient-border p-6">
|
| 698 |
+
<div className="flex items-center justify-between mb-6">
|
| 699 |
+
<h2 className="text-xl font-bold text-white">Generated Backend</h2>
|
| 700 |
+
<div className="flex gap-3">
|
| 701 |
+
<button onClick={onDownload} className="btn-secondary flex items-center gap-2">
|
| 702 |
+
<Download className="w-4 h-4" />
|
| 703 |
+
Download ZIP
|
| 704 |
+
</button>
|
| 705 |
+
</div>
|
| 706 |
+
</div>
|
| 707 |
+
|
| 708 |
+
<div className="flex gap-2 mb-6 overflow-x-auto pb-2">
|
| 709 |
+
{tabs.map((tab) => (
|
| 710 |
+
<button
|
| 711 |
+
key={tab.id}
|
| 712 |
+
onClick={() => setActiveTab(tab.id)}
|
| 713 |
+
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all whitespace-nowrap ${
|
| 714 |
+
activeTab === tab.id
|
| 715 |
+
? 'bg-primary-500/20 text-primary-400 border border-primary-500/50'
|
| 716 |
+
: 'bg-dark-800 text-dark-400 border border-dark-700 hover:border-dark-500'
|
| 717 |
+
}`}
|
| 718 |
+
>
|
| 719 |
+
<tab.icon className="w-4 h-4" />
|
| 720 |
+
{tab.label}
|
| 721 |
+
</button>
|
| 722 |
+
))}
|
| 723 |
+
</div>
|
| 724 |
+
|
| 725 |
+
<div className="min-h-[400px]">
|
| 726 |
+
{activeTab === 'schema' && (
|
| 727 |
+
<SchemaVisualizer schema={result.schema} />
|
| 728 |
+
)}
|
| 729 |
+
|
| 730 |
+
{activeTab === 'prisma' && result.prismaSchema && (
|
| 731 |
+
<CodeViewer
|
| 732 |
+
code={result.prismaSchema}
|
| 733 |
+
language="typescript"
|
| 734 |
+
filename="prisma/schema.prisma"
|
| 735 |
+
/>
|
| 736 |
+
)}
|
| 737 |
+
|
| 738 |
+
{activeTab === 'api' && result.apiRoutes && (
|
| 739 |
+
<div className="space-y-4">
|
| 740 |
+
{result.apiRoutes.map((route, idx) => (
|
| 741 |
+
<div
|
| 742 |
+
key={idx}
|
| 743 |
+
className="bg-dark-800/50 rounded-xl border border-dark-700 p-4"
|
| 744 |
+
>
|
| 745 |
+
<div className="flex items-center gap-3 mb-2">
|
| 746 |
+
<span
|
| 747 |
+
className={`px-2 py-0.5 rounded text-xs font-bold ${
|
| 748 |
+
route.method === 'GET'
|
| 749 |
+
? 'bg-green-500/20 text-green-400'
|
| 750 |
+
: route.method === 'POST'
|
| 751 |
+
? 'bg-blue-500/20 text-blue-400'
|
| 752 |
+
: route.method === 'PUT' || route.method === 'PATCH'
|
| 753 |
+
? 'bg-yellow-500/20 text-yellow-400'
|
| 754 |
+
: 'bg-red-500/20 text-red-400'
|
| 755 |
+
}`}
|
| 756 |
+
>
|
| 757 |
+
{route.method}
|
| 758 |
+
</span>
|
| 759 |
+
<code className="text-dark-200 font-mono text-sm">
|
| 760 |
+
{route.path}
|
| 761 |
+
</code>
|
| 762 |
+
</div>
|
| 763 |
+
<p className="text-dark-400 text-sm">{route.description}</p>
|
| 764 |
+
</div>
|
| 765 |
+
))}
|
| 766 |
+
</div>
|
| 767 |
+
)}
|
| 768 |
+
|
| 769 |
+
{activeTab === 'openapi' && result.openApiSpec && (
|
| 770 |
+
<CodeViewer
|
| 771 |
+
code={JSON.stringify(result.openApiSpec, null, 2)}
|
| 772 |
+
language="json"
|
| 773 |
+
filename="openapi.json"
|
| 774 |
+
/>
|
| 775 |
+
)}
|
| 776 |
+
|
| 777 |
+
{activeTab === 'code' && result.generatedFiles && (
|
| 778 |
+
<div className="space-y-3">
|
| 779 |
+
{result.generatedFiles.map((file, idx) => (
|
| 780 |
+
<div
|
| 781 |
+
key={idx}
|
| 782 |
+
className="bg-dark-800/50 rounded-xl border border-dark-700 overflow-hidden"
|
| 783 |
+
>
|
| 784 |
+
<button
|
| 785 |
+
onClick={() => toggleFile(file.filename)}
|
| 786 |
+
className="w-full px-4 py-3 flex items-center justify-between hover:bg-dark-700/50 transition-colors"
|
| 787 |
+
>
|
| 788 |
+
<div className="flex items-center gap-2">
|
| 789 |
+
<Code2 className="w-4 h-4 text-primary-400" />
|
| 790 |
+
<span className="text-dark-200 font-mono text-sm">
|
| 791 |
+
{file.filename}
|
| 792 |
+
</span>
|
| 793 |
+
</div>
|
| 794 |
+
{expandedFiles[file.filename] ? (
|
| 795 |
+
<ChevronUp className="w-4 h-4 text-dark-400" />
|
| 796 |
+
) : (
|
| 797 |
+
<ChevronDown className="w-4 h-4 text-dark-400" />
|
| 798 |
+
)}
|
| 799 |
+
</button>
|
| 800 |
+
{expandedFiles[file.filename] && (
|
| 801 |
+
<div className="border-t border-dark-700">
|
| 802 |
+
<CodeViewer
|
| 803 |
+
code={file.content}
|
| 804 |
+
language={file.language || 'typescript'}
|
| 805 |
+
filename={file.filename}
|
| 806 |
+
/>
|
| 807 |
+
</div>
|
| 808 |
+
)}
|
| 809 |
+
</div>
|
| 810 |
+
))}
|
| 811 |
+
</div>
|
| 812 |
+
)}
|
| 813 |
+
</div>
|
| 814 |
+
</div>
|
| 815 |
+
</div>
|
| 816 |
+
);
|
| 817 |
+
}
|
| 818 |
+
|
| 819 |
+
=== components/LoadingState.jsx ===
|
| 820 |
+
import { Database, Server, FileCode, Sparkles, Check } from 'lucide-react';
|
| 821 |
+
|
| 822 |
+
export default function LoadingState({ currentStep }) {
|
| 823 |
+
const steps = [
|
| 824 |
+
{ id: 'analyzing', label: 'Analyzing requirements', icon: Sparkles },
|
| 825 |
+
{ id: 'schema', label: 'Designing database schema', icon: Database },
|
| 826 |
+
{ id: 'api', label: 'Generating API routes', icon: Server },
|
| 827 |
+
{ id: 'code', label: 'Writing backend code', icon: FileCode },
|
| 828 |
+
];
|
| 829 |
+
|
| 830 |
+
const currentIndex = steps.findIndex((s) => s.id === currentStep);
|
| 831 |
+
|
| 832 |
+
return (
|
| 833 |
+
<div className="glass-panel gradient-border p-8">
|
| 834 |
+
<div className="text-center mb-8">
|
| 835 |
+
<div className="w-16 h-16 mx-auto mb-4 relative">
|
| 836 |
+
<div className="absolute inset-0 bg-gradient-to-r from-primary-500 to-purple-500 rounded-full animate-pulse-slow" />
|
| 837 |
+
<div className="absolute inset-1 bg-dark-900 rounded-full flex items-center justify-center">
|
| 838 |
+
<Sparkles className="w-6 h-6 text-primary-400 animate-float" />
|
| 839 |
+
</div>
|
| 840 |
+
</div>
|
| 841 |
+
<h3 className="text-xl font-bold text-white mb-2">AI is working its magic</h3>
|
| 842 |
+
<p className="text-dark-400">Designing your backend architecture...</p>
|
| 843 |
+
</div>
|
| 844 |
+
|
| 845 |
+
<div className="space-y-4">
|
| 846 |
+
{steps.map((step, idx) => {
|
| 847 |
+
const isCompleted = idx < currentIndex;
|
| 848 |
+
const isCurrent = idx === currentIndex;
|
| 849 |
+
|
| 850 |
+
return (
|
| 851 |
+
<div
|
| 852 |
+
key={step.id}
|
| 853 |
+
className={`flex items-center gap-4 p-4 rounded-xl transition-all duration-500 ${
|
| 854 |
+
isCurrent
|
| 855 |
+
? 'bg-primary-500/10 border border-primary-500/30'
|
| 856 |
+
: isCompleted
|
| 857 |
+
? 'bg-green-500/10 border border-green-500/30'
|
| 858 |
+
: 'bg-dark-800/50 border border-dark-700'
|
| 859 |
+
}`}
|
| 860 |
+
>
|
| 861 |
+
<div
|
| 862 |
+
className={`w-10 h-10 rounded-xl flex items-center justify-center ${
|
| 863 |
+
isCurrent
|
| 864 |
+
? 'bg-primary-500/20'
|
| 865 |
+
: isCompleted
|
| 866 |
+
? 'bg-green-500/20'
|
| 867 |
+
: 'bg-dark-700'
|
| 868 |
+
}`}
|
| 869 |
+
>
|
| 870 |
+
{isCompleted ? (
|
| 871 |
+
<Check className="w-5 h-5 text-green-400" />
|
| 872 |
+
) : (
|
| 873 |
+
<step.icon
|
| 874 |
+
className={`w-5 h-5 ${
|
| 875 |
+
isCurrent ? 'text-primary-400 animate-pulse' : 'text-dark-500'
|
| 876 |
+
}`}
|
| 877 |
+
/>
|
| 878 |
+
)}
|
| 879 |
+
</div>
|
| 880 |
+
<span
|
| 881 |
+
className={`font-medium ${
|
| 882 |
+
isCurrent
|
| 883 |
+
? 'text-primary-400'
|
| 884 |
+
: isCompleted
|
| 885 |
+
? 'text-green-400'
|
| 886 |
+
: 'text-dark-500'
|
| 887 |
+
}`}
|
| 888 |
+
>
|
| 889 |
+
{step.label}
|
| 890 |
+
</span>
|
| 891 |
+
{isCurrent && (
|
| 892 |
+
<div className="ml-auto">
|
| 893 |
+
<div className="w-5 h-5 border-2 border-primary-400 border-t-transparent rounded-full animate-spin" />
|
| 894 |
+
</div>
|
| 895 |
+
)}
|
| 896 |
+
</div>
|
| 897 |
+
);
|
| 898 |
+
})}
|
| 899 |
+
</div>
|
| 900 |
+
</div>
|
| 901 |
+
);
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
=== components/FeatureCards.jsx ===
|
| 905 |
+
import { Database, Server, Code2, Zap, Shield, Layers } from 'lucide-react';
|
| 906 |
+
|
| 907 |
+
export default function FeatureCards() {
|
| 908 |
+
const features = [
|
| 909 |
+
{
|
| 910 |
+
icon: Database,
|
| 911 |
+
title: 'Smart Schema Design',
|
| 912 |
+
description: 'AI analyzes your requirements and designs optimal database schemas with proper relationships.',
|
| 913 |
+
color: 'from-blue-500 to-cyan-500',
|
| 914 |
+
},
|
| 915 |
+
{
|
| 916 |
+
icon: Server,
|
| 917 |
+
title: 'Auto API Generation',
|
| 918 |
+
description: 'Generates RESTful APIs with CRUD operations, validation, and error handling built-in.',
|
| 919 |
+
color: 'from-purple-500 to-pink-500',
|
| 920 |
+
},
|
| 921 |
+
{
|
| 922 |
+
icon: Code2,
|
| 923 |
+
title: 'Production-Ready Code',
|
| 924 |
+
description: 'Clean, typed TypeScript code following best practices with NestJS or Express.',
|
| 925 |
+
color: 'from-green-500 to-emerald-500',
|
| 926 |
+
},
|
| 927 |
+
{
|
| 928 |
+
icon: Zap,
|
| 929 |
+
title: 'Instant Setup',
|
| 930 |
+
description: 'Download and run immediately with Docker support and migration scripts included.',
|
| 931 |
+
color: 'from-yellow-500 to-orange-500',
|
| 932 |
+
},
|
| 933 |
+
{
|
| 934 |
+
icon: Shield,
|
| 935 |
+
title: 'Type Safety',
|
| 936 |
+
description: 'Full TypeScript support with Prisma/Drizzle for type-safe database operations.',
|
| 937 |
+
color: 'from-red-500 to-rose-500',
|
| 938 |
+
},
|
| 939 |
+
{
|
| 940 |
+
icon: Layers,
|
| 941 |
+
title: 'OpenAPI Docs',
|
| 942 |
+
description: 'Auto-generated OpenAPI/Swagger documentation for all your endpoints.',
|
| 943 |
+
color: 'from-indigo-500 to-violet-500',
|
| 944 |
+
},
|
| 945 |
+
];
|
| 946 |
+
|
| 947 |
+
return (
|
| 948 |
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
| 949 |
+
{features.map((feature, idx) => (
|
| 950 |
+
<div
|
| 951 |
+
key={idx}
|
| 952 |
+
className="glass-panel p-6 hover:border-dark-500 transition-all duration-300 group"
|
| 953 |
+
>
|
| 954 |
+
<div
|
| 955 |
+
className={`w-12 h-12 rounded-xl bg-gradient-to-br ${feature.color} p-0.5 mb-4
|
| 956 |
+
group-hover:scale-110 transition-transform duration-300`}
|
| 957 |
+
>
|
| 958 |
+
<div className="w-full h-full bg-dark-900 rounded-[10px] flex items-center justify-center">
|
| 959 |
+
<feature.icon className="w-5 h-5 text-white" />
|
| 960 |
+
</div>
|
| 961 |
+
</div>
|
| 962 |
+
<h3 className="text-lg font-semibold text-white mb-2">{feature.title}</h3>
|
| 963 |
+
<p className="text-dark-400 text-sm">{feature.description}</p>
|
| 964 |
+
</div>
|
| 965 |
+
))}
|
| 966 |
+
</div>
|
| 967 |
+
);
|
| 968 |
+
}
|
| 969 |
+
|
| 970 |
+
=== pages/_app.js ===
|
| 971 |
+
import '../styles/globals.css';
|
| 972 |
+
|
| 973 |
+
export default function App({ Component, pageProps }) {
|
| 974 |
+
return <Component {...pageProps} />;
|
| 975 |
+
}
|
| 976 |
+
|
| 977 |
+
=== pages/index.js ===
|
| 978 |
+
import { useState, useCallback } from 'react';
|
| 979 |
+
import Head from 'next/head';
|
| 980 |
+
import Header from '../components/Header';
|
| 981 |
+
import ProjectWizard from '../components/ProjectWizard';
|
| 982 |
+
import GeneratedOutput from '../components/GeneratedOutput';
|
| 983 |
+
import LoadingState from '../components/LoadingState';
|
| 984 |
+
import FeatureCards from '../components/FeatureCards';
|
| 985 |
+
|
| 986 |
+
export default function Home() {
|
| 987 |
+
const [isLoading, setIsLoading] = useState(false);
|
| 988 |
+
const [currentStep, setCurrentStep] = useState('');
|
| 989 |
+
const [result, setResult] = useState(null);
|
| 990 |
+
const [error, setError] = useState(null);
|
| 991 |
+
|
| 992 |
+
const handleGenerate = useCallback(async (projectData) => {
|
| 993 |
+
setIsLoading(true);
|
| 994 |
+
setError(
|