├── .prettierignore
├── commitlintrc.json
├── public
├── empty.png
├── favicon.ico
├── complogo.png
├── ui-screenshot
│ ├── pro.png
│ ├── login.png
│ ├── dashboard.png
│ └── landing-page.png
├── vercel.svg
├── next.svg
└── technical-support.svg
├── .husky
└── pre-commit
├── postcss.config.js
├── app
├── (auth)
│ ├── (routes)
│ │ ├── sign-in
│ │ │ └── [[...sign-in]]
│ │ │ │ └── page.tsx
│ │ └── sign-up
│ │ │ └── [[...sign-up]]
│ │ │ └── page.tsx
│ └── layout.tsx
├── (dashboard)
│ ├── (routes)
│ │ ├── chat
│ │ │ ├── constants.ts
│ │ │ └── page.tsx
│ │ ├── code
│ │ │ ├── constants.ts
│ │ │ └── page.tsx
│ │ ├── video
│ │ │ ├── constants.ts
│ │ │ └── page.tsx
│ │ ├── music
│ │ │ ├── constants.ts
│ │ │ └── page.tsx
│ │ ├── image
│ │ │ ├── constants.ts
│ │ │ └── page.tsx
│ │ ├── settings
│ │ │ └── page.tsx
│ │ └── dashboard
│ │ │ └── page.tsx
│ └── layout.tsx
├── (landing)
│ ├── layout.tsx
│ └── page.tsx
├── layout.tsx
├── api
│ ├── video
│ │ └── route.ts
│ ├── music
│ │ └── route.ts
│ ├── chat
│ │ └── route.ts
│ ├── image
│ │ └── route.ts
│ ├── code
│ │ └── route.ts
│ ├── webhook
│ │ └── route.ts
│ └── stripe
│ │ └── route.ts
└── globals.css
├── lib
├── stripe-connect.ts
├── prismadb.ts
├── utils.ts
├── subscription.ts
└── api-limit.ts
├── next.config.js
├── .editorconfig
├── components
├── ChatSupportProvider.tsx
├── ToastProvider.tsx
├── BotAvatar.tsx
├── ChatSupport.tsx
├── Loader.tsx
├── ModalProvider.tsx
├── UserAvatar.tsx
├── Empty.tsx
├── Navbar.tsx
├── ui
│ ├── label.tsx
│ ├── progress.tsx
│ ├── input.tsx
│ ├── badge.tsx
│ ├── avatar.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── dialog.tsx
│ ├── select.tsx
│ ├── form.tsx
│ └── sheet.tsx
├── Heading.tsx
├── MobileSidebar.tsx
├── SubscribeButton.tsx
├── LandingNavbar.tsx
├── FreeCounter.tsx
├── LandingHero.tsx
├── LandingPageContents.tsx
├── ProModal.tsx
└── Sidebar.tsx
├── docker-compose.yml
├── Dockerfile
├── hooks
└── useProModal.tsx
├── components.json
├── .prettierrc.json
├── middleware.ts
├── .gitignore
├── .env.example
├── tsconfig.json
├── .eslintrc.json
├── prisma
└── schema.prisma
├── constants.ts
├── .github
└── workflows
│ └── github-actions.yml
├── contributing.md
├── types
└── index.d.ts
├── package.json
├── tailwind.config.js
└── README.md
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | .next
4 | build
5 | .contentlayer
--------------------------------------------------------------------------------
/commitlintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["@commitlint/config-conventional"]
3 | }
--------------------------------------------------------------------------------
/public/empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drakkarrr/D-prompt-AI/HEAD/public/empty.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drakkarrr/D-prompt-AI/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npm test
5 |
--------------------------------------------------------------------------------
/public/complogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drakkarrr/D-prompt-AI/HEAD/public/complogo.png
--------------------------------------------------------------------------------
/public/ui-screenshot/pro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drakkarrr/D-prompt-AI/HEAD/public/ui-screenshot/pro.png
--------------------------------------------------------------------------------
/public/ui-screenshot/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drakkarrr/D-prompt-AI/HEAD/public/ui-screenshot/login.png
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/ui-screenshot/dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drakkarrr/D-prompt-AI/HEAD/public/ui-screenshot/dashboard.png
--------------------------------------------------------------------------------
/public/ui-screenshot/landing-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drakkarrr/D-prompt-AI/HEAD/public/ui-screenshot/landing-page.png
--------------------------------------------------------------------------------
/app/(auth)/(routes)/sign-in/[[...sign-in]]/page.tsx:
--------------------------------------------------------------------------------
1 | import { SignIn } from "@clerk/nextjs";
2 |
3 | export default function Page() {
4 | return ;
5 | }
--------------------------------------------------------------------------------
/app/(auth)/(routes)/sign-up/[[...sign-up]]/page.tsx:
--------------------------------------------------------------------------------
1 | import { SignUp } from "@clerk/nextjs";
2 |
3 | export default function Page() {
4 | return ;
5 | }
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/chat/constants.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 |
3 | export const formSchema = z.object({
4 | prompt: z.string().min(1, { message: 'Prompt is required' }),
5 | })
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/code/constants.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 |
3 | export const formSchema = z.object({
4 | prompt: z.string().min(1, { message: 'Prompt is required' }),
5 | })
--------------------------------------------------------------------------------
/lib/stripe-connect.ts:
--------------------------------------------------------------------------------
1 | import Stripe from 'stripe'
2 |
3 | export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
4 | apiVersion: '2022-11-15',
5 | typescript: true,
6 | })
7 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/video/constants.ts:
--------------------------------------------------------------------------------
1 | import * as z from "zod";
2 |
3 | export const formSchema = z.object({
4 | prompt: z.string().min(1, {
5 | message: "Prompt is required."
6 | }),
7 | });
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/music/constants.ts:
--------------------------------------------------------------------------------
1 | import * as z from "zod";
2 |
3 | export const formSchema = z.object({
4 | prompt: z.string().min(1, {
5 | message: "Music prompt is required"
6 | }),
7 | });
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | domains: ['oaidalleapiprodscus.blob.core.windows.net'],
5 | },
6 | };
7 |
8 | module.exports = nextConfig;
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
--------------------------------------------------------------------------------
/components/ChatSupportProvider.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import ChatSupport from '@/components/ChatSupport';
4 |
5 | const ChatSupportProvider = () => {
6 | return ;
7 | };
8 |
9 | export default ChatSupportProvider;
10 |
--------------------------------------------------------------------------------
/components/ToastProvider.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React from 'react';
4 | import { Toaster } from 'react-hot-toast';
5 |
6 | const ToastProvider = () => {
7 | return ;
8 | };
9 |
10 | export default ToastProvider;
11 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.8'
2 | services:
3 | web:
4 | build:
5 | context: ./
6 | target: runner
7 | volumes:
8 | - .:/app
9 | command: npm run dev
10 | ports:
11 | - "3000:3000"
12 | environment:
13 | NODE_ENV: development
14 |
--------------------------------------------------------------------------------
/lib/prismadb.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from '@prisma/client'
2 |
3 | declare global {
4 | var prisma: PrismaClient | undefined
5 | }
6 |
7 | export const prismadb = global.prisma || new PrismaClient()
8 | if (process.env.NODE_ENV === 'production') global.prisma = prismadb
9 |
10 |
--------------------------------------------------------------------------------
/components/BotAvatar.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar, AvatarImage } from '@/components/ui/avatar';
2 |
3 | const BotAvatar = () => {
4 | return (
5 |
6 |
7 |
8 | );
9 | };
10 |
11 | export default BotAvatar;
12 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
8 | export function absoluteUrl(path: string) {
9 | return `${process.env.NEXT_PUBLIC_HOST}${path}`
10 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:alpine
2 | RUN apk add --no-cache libc6-compat
3 |
4 | WORKDIR /app
5 | COPY package.json yarn.lock ./
6 |
7 | RUN yarn install
8 |
9 | COPY --from=deps /app/node_modules ./node_modules
10 | COPY . .
11 |
12 | RUN yarn build
13 | EXPOSE 3000
14 | ENV PORT 3000
15 |
16 | CMD ["yarn", "start"]
17 |
--------------------------------------------------------------------------------
/app/(auth)/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Props } from '@/types';
2 |
3 | const AuthLayout = ({ children }: Props) => {
4 | return (
5 |
8 | );
9 | };
10 |
11 | export default AuthLayout;
12 |
--------------------------------------------------------------------------------
/components/ChatSupport.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useEffect } from 'react';
4 | import { Crisp } from 'crisp-sdk-web';
5 |
6 | const ChatSupport = () => {
7 | useEffect(() => {
8 | Crisp.configure(process.env.NEXT_PUBLIC_CRISP_WEBSITE_ID!);
9 | }, []);
10 |
11 | return null;
12 | };
13 |
14 | export default ChatSupport;
15 |
--------------------------------------------------------------------------------
/app/(landing)/layout.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const LandingLayout = ({ children }: { children: React.ReactNode }) => {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | };
10 |
11 | export default LandingLayout;
12 |
--------------------------------------------------------------------------------
/hooks/useProModal.tsx:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand';
2 |
3 | interface useProModalStore {
4 | isOpen: boolean;
5 | onOpen: () => void;
6 | onClose: () => void;
7 | }
8 |
9 | export const useProModal = create((set) => ({
10 | isOpen: false,
11 | onOpen: () => set({ isOpen: true }),
12 | onClose: () => set({ isOpen: false }),
13 | }));
14 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "app/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true
11 | },
12 | "aliases": {
13 | "components": "@/components",
14 | "utils": "@/lib/utils"
15 | }
16 | }
--------------------------------------------------------------------------------
/components/Loader.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 |
3 | const Loader = () => {
4 | return (
5 |
6 |
7 |
8 |
9 |
Generating...
10 |
11 | );
12 | };
13 |
14 | export default Loader;
15 |
--------------------------------------------------------------------------------
/app/(landing)/page.tsx:
--------------------------------------------------------------------------------
1 | import LandingNavbar from '@/components/LandingNavbar';
2 | import LandingHero from '@/components/LandingHero';
3 | // import LandingPageContents from '@/components/LandingPageContents';
4 |
5 | const LandingPage = () => {
6 | return (
7 |
8 |
9 |
10 | {/* */}
11 |
12 | );
13 | };
14 |
15 | export default LandingPage;
16 |
--------------------------------------------------------------------------------
/components/ModalProvider.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useEffect, useState } from 'react';
4 | import ProModal from '@/components/ProModal';
5 |
6 | const ModalProvider = () => {
7 | const [isMounted, setIsMounted] = useState(false);
8 |
9 | useEffect(() => {
10 | setIsMounted(true);
11 | }, []);
12 |
13 | if (!isMounted) return null;
14 |
15 | return (
16 | <>
17 |
18 | >
19 | );
20 | };
21 |
22 | export default ModalProvider;
23 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "semi": true,
4 | "tabWidth": 2,
5 | "singleQuote": true,
6 | "jsxSingleQuote": true,
7 | "plugins": ["prettier-plugin-tailwindcss"],
8 | "endOfLine": "lf",
9 | "importOrderSeparation": false,
10 | "importOrderSortSpecifiers": true,
11 | "importOrderBuiltinModulesToTop": true,
12 | "importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"],
13 | "importOrderMergeDuplicateImports": true,
14 | "importOrderCombineTypeAndValueImports": true
15 | }
16 |
--------------------------------------------------------------------------------
/components/UserAvatar.tsx:
--------------------------------------------------------------------------------
1 | import { useUser } from '@clerk/nextjs';
2 |
3 | import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
4 |
5 | const UserAvatar = () => {
6 | const { user } = useUser();
7 |
8 | return (
9 |
10 |
11 |
12 | {user?.firstName?.charAt(0)}
13 | {user?.lastName?.charAt(0)}
14 |
15 |
16 | );
17 | };
18 |
19 | export default UserAvatar;
20 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | import { authMiddleware } from "@clerk/nextjs";
2 |
3 | //! This example protects all routes including api/trpc routes
4 | //! Please edit this to allow other routes to be public as needed.
5 | //! See https://clerk.com/docs/nextjs/middleware for more information about configuring your middleware
6 |
7 | export default authMiddleware({
8 | publicRoutes: [
9 | "/",
10 | '/api/webhook',
11 | ],
12 | });
13 |
14 | export const config = {
15 | matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
16 | };
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 | .vercel
39 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/Empty.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 |
3 | interface EmptyProps {
4 | label: string;
5 | }
6 |
7 | const Empty: React.FC = ({ label }) => {
8 | return (
9 |
10 |
11 |
17 |
18 |
{label}
19 |
20 | );
21 | };
22 |
23 | export default Empty;
24 |
--------------------------------------------------------------------------------
/components/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import { UserButton } from '@clerk/nextjs';
2 | import MobileSidebar from '@/components/MobileSidebar';
3 |
4 | import { getApiLimitCount } from '@/lib/api-limit';
5 | import { getSubscription } from '@/lib/subscription';
6 |
7 | const Navbar = async () => {
8 | const apiLimit = await getApiLimitCount();
9 | const isPro = await getSubscription();
10 |
11 | return (
12 |
18 | );
19 | };
20 |
21 | export default Navbar;
22 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 |
2 | #! CLERK CONFIGURATION KEYS
3 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
4 | CLERK_SECRET_KEY=
5 |
6 | NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
7 | NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
8 |
9 | NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
10 | NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard
11 |
12 | #! OPENAI SECRET KEYS
13 | OPENAI_API_KEY=
14 |
15 | #! REPLICATE AI SECRET KEYS
16 | REPLICATE_API_TOKEN=
17 |
18 | #! PRISMA DB URL
19 | DATABASE_URL=
20 |
21 | #! STRIPE ENV KEYS
22 | STRIPE_SECRET_KEY=
23 | STRIPE_WEBHOOK_SECRET=
24 |
25 | #! NEXT HOST PORT URL FOR ENVIRONMENT
26 | NEXT_PUBLIC_HOST=http://localhost:3000
27 |
28 | #! CRISP CHAT SUPPORT SECRET KEYS
29 | NEXT_PUBLIC_CRISP_WEBSITE_ID=
30 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "plugins": [
18 | {
19 | "name": "next"
20 | }
21 | ],
22 | "paths": {
23 | "@/*": ["./*"]
24 | }
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------
/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/lib/subscription.ts:
--------------------------------------------------------------------------------
1 | import { auth } from '@clerk/nextjs';
2 | import { prismadb } from '@/lib/prismadb';
3 |
4 | const DAY_IN_MS = 84_400_000;
5 |
6 | export const getSubscription = async () => {
7 | const { userId } = auth();
8 |
9 | if (!userId) return false;
10 | const userSubscription = await prismadb.userSubscription.findUnique({
11 | where: {
12 | userId,
13 | },
14 | select: {
15 | stripeSubscriptionId: true,
16 | stripeCurrentPeriodEnd: true,
17 | stripeCustomerId: true,
18 | stripePriceId: true
19 | },
20 | });
21 |
22 | if (!userSubscription) return false;
23 |
24 | const isValid = userSubscription.stripePriceId && userSubscription.stripeCurrentPeriodEnd?.getTime()! + DAY_IN_MS > Date.now();
25 |
26 | return !!isValid;
27 | }
--------------------------------------------------------------------------------
/components/Heading.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils';
2 | import { LucideIcon } from 'lucide-react';
3 |
4 | interface HeadingProps {
5 | title: string;
6 | description: string;
7 | icon: LucideIcon;
8 | color?: string;
9 | bgColor?: string;
10 | }
11 |
12 | const Heading: React.FC = ({
13 | title,
14 | description,
15 | icon: Icon,
16 | bgColor,
17 | color,
18 | }) => {
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
{title}
26 |
{description}
27 |
28 |
29 | );
30 | };
31 |
32 | export default Heading;
33 |
--------------------------------------------------------------------------------
/app/(dashboard)/layout.tsx:
--------------------------------------------------------------------------------
1 | import Navbar from '@/components/Navbar';
2 | import Sidebar from '@/components/Sidebar';
3 | import { Props } from '@/types';
4 |
5 | import { getApiLimitCount } from '@/lib/api-limit';
6 | import { getSubscription } from '@/lib/subscription';
7 |
8 | const DashboardLayout: React.FC = async ({ children }: Props) => {
9 | const apiLimit = await getApiLimitCount();
10 | const isPro = await getSubscription();
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 | {children}
20 |
21 |
22 | );
23 | };
24 |
25 | export default DashboardLayout;
26 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/eslintrc",
3 | "root": true,
4 | "extends": [
5 | "next",
6 | "prettier",
7 | "next/core-web-vitals",
8 | "plugin:tailwindcss/recommended"
9 | ],
10 | "plugins": ["tailwindcss"],
11 | "rules": {
12 | "@next/next/no-html-link-for-pages": "off",
13 | "react/jsx-key": "off",
14 | "tailwindcss/no-custom-classname": "off",
15 | "tailwindcss/classnames-order": "error",
16 | "react/no-unescaped-entities": "off",
17 | "@next/next/no-page-custom-font": "off"
18 | },
19 | "settings": {
20 | "tailwindcss": {
21 | "callees": ["cn"],
22 | "config": "tailwind.config.js"
23 | },
24 | "next": {
25 | "rootDir": true
26 | }
27 | },
28 | "overrides": [
29 | {
30 | "files": ["*.ts", "*.tsx"],
31 | "parser": "@typescript-eslint/parser"
32 | }
33 | ]
34 | }
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | generator client {
2 | provider = "prisma-client-js"
3 | }
4 |
5 | datasource db {
6 | provider = "mysql"
7 | url = env("DATABASE_URL")
8 | relationMode = "prisma"
9 | }
10 |
11 | model UserApiLimit {
12 | id String @id @default(cuid())
13 | userId String @unique
14 | count Int @default(0)
15 | createdAt DateTime @default(now())
16 | updatedAt DateTime @updatedAt
17 | }
18 |
19 | model UserSubscription {
20 | id String @id @default(cuid())
21 | userId String @unique
22 | stripeCustomerId String? @unique @map(name: "stripe_customer_id")
23 | stripeSubscriptionId String? @unique @map(name: "stripe_subscription_id")
24 | stripePriceId String? @map(name: "stripe_price_id")
25 | stripeCurrentPeriodEnd DateTime? @map(name: "stripe_current_period_end")
26 | }
27 |
--------------------------------------------------------------------------------
/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ProgressPrimitive from "@radix-ui/react-progress"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Progress = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, value, ...props }, ref) => (
12 |
20 |
24 |
25 | ))
26 | Progress.displayName = ProgressPrimitive.Root.displayName
27 |
28 | export { Progress }
29 |
--------------------------------------------------------------------------------
/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/components/MobileSidebar.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useState, useEffect } from 'react';
4 | import { Menu } from 'lucide-react';
5 | import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
6 | import Sidebar from '@/components/Sidebar';
7 |
8 | type MobileSidebarProps = {
9 | apiLimit: number;
10 | isPro: boolean;
11 | };
12 |
13 | const MobileSidebar: React.FC = ({
14 | apiLimit = 0,
15 | isPro = false,
16 | }) => {
17 | const [open, setOpen] = useState(false);
18 |
19 | useEffect(() => {
20 | setOpen(true);
21 | }, []);
22 |
23 | if (!open) return null;
24 |
25 | return (
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | export default MobileSidebar;
38 |
--------------------------------------------------------------------------------
/constants.ts:
--------------------------------------------------------------------------------
1 | import { Code, ImageIcon, MessageSquare, Music, VideoIcon } from 'lucide-react';
2 | import { useProModal } from '@/hooks/useProModal';
3 |
4 | export const MAX_FREE_COUNTS = 15;
5 |
6 | export const tools = [
7 | {
8 | label: 'Chat',
9 | icon: MessageSquare,
10 | href: '/chat',
11 | color: 'text-violet-500',
12 | bgColor: 'bg-violet-500/10',
13 | },
14 | {
15 | label: 'Image Generator',
16 | icon: ImageIcon,
17 | color: 'text-orange-700',
18 | bgColor: 'bg-orange-700/10',
19 | },
20 | {
21 | label: 'Video Generator',
22 | icon: VideoIcon,
23 | color: 'text-prink-700',
24 | bgColor: 'bg-pink-700/10',
25 | },
26 | {
27 | label: 'Music Generator',
28 | icon: Music,
29 | color: 'text-emerald-500',
30 | bgColor: 'bg-emerald-500/10',
31 | },
32 | {
33 | label: 'Code Generator',
34 | icon: Code,
35 | color: 'text-blue-700',
36 | bgColor: 'bg-blue-700/10',
37 | },
38 | ];
39 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import './globals.css';
2 | import type { Metadata } from 'next';
3 | import { Inter } from 'next/font/google';
4 |
5 | import { ClerkProvider } from '@clerk/nextjs';
6 |
7 | import ModalProvider from '@/components/ModalProvider';
8 | import ToastProvider from '@/components/ToastProvider';
9 | import ChatSupportProvider from '@/components/ChatSupportProvider';
10 |
11 | const inter = Inter({ subsets: ['latin'] });
12 |
13 | export const metadata: Metadata = {
14 | title: 'D-prompt AI',
15 | description: 'An open source 5-in-1 web based generative AI app.',
16 | };
17 |
18 | export default function RootLayout({
19 | children,
20 | }: {
21 | children: React.ReactNode;
22 | }) {
23 | return (
24 |
25 |
26 |
27 |
28 |
29 | {children}
30 |
31 |
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/image/constants.ts:
--------------------------------------------------------------------------------
1 | import * as z from "zod";
2 |
3 | export const formSchema = z.object({
4 | prompt: z.string().min(1, {
5 | message: "Photo prompt is required"
6 | }),
7 | amount: z.string().min(1),
8 | resolution: z.string().min(1),
9 | });
10 |
11 | export const amountOptions = [
12 | {
13 | value: "1",
14 | label: "1 Photo"
15 | },
16 | {
17 | value: "2",
18 | label: "2 Photos"
19 | },
20 | {
21 | value: "3",
22 | label: "3 Photos"
23 | },
24 | {
25 | value: "4",
26 | label: "4 Photos"
27 | },
28 | {
29 | value: "5",
30 | label: "5 Photos"
31 | }
32 | ];
33 |
34 | export const resolutionOptions = [
35 | {
36 | value: "256x256",
37 | label: "256x256",
38 | },
39 | {
40 | value: "512x512",
41 | label: "512x512",
42 | },
43 | {
44 | value: "1024x1024",
45 | label: "1024x1024",
46 | },
47 | ];
48 |
--------------------------------------------------------------------------------
/.github/workflows/github-actions.yml:
--------------------------------------------------------------------------------
1 | #! Point to my GitHub Actions Deployment (Drakkarrrr)
2 | name: GitHub Actions Deployment
3 | run-name: ${{ github.actor }} Automate CI/CD. 🚀
4 |
5 | on:
6 | push:
7 | branches: ['main']
8 | pull_request:
9 | branches: ['main']
10 |
11 | jobs:
12 | Explore-GitHub-Actions:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
16 | - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
17 | - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
18 | - name: Check out repository code
19 | uses: actions/checkout@v3
20 | - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
21 | - run: echo "🖥️ The workflow is now ready to test your code on the runner."
22 | - name: List files in the repository
23 | run: |
24 | ls ${{ github.workspace }}
25 | - run: echo "🍏 This job's status is ${{job.status}}."
26 |
--------------------------------------------------------------------------------
/components/SubscribeButton.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useState } from 'react';
4 | import axios from 'axios';
5 | import { toast } from 'react-hot-toast';
6 | import { Zap } from 'lucide-react';
7 | import { Button } from '@/components/ui/button';
8 |
9 | interface SubscribeButtonProps {
10 | isPro: boolean;
11 | }
12 |
13 | const SubsripeButton: React.FC = ({ isPro = false }) => {
14 | const [loading, setLoading] = useState(false);
15 |
16 | const handleSubscribe = async () => {
17 | try {
18 | setLoading(true);
19 | const response = await axios.get('/api/stripe');
20 | window.location.href = response.data.url;
21 | } catch (error: any) {
22 | toast.error('Something went wrong');
23 | } finally {
24 | setLoading(false);
25 | }
26 | };
27 |
28 | return (
29 |
34 | {isPro ? 'Manage Subscription' : 'Upgrade to Pro'}
35 | {!isPro && }
36 |
37 | );
38 | };
39 |
40 | export default SubsripeButton;
41 |
--------------------------------------------------------------------------------
/components/LandingNavbar.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useAuth } from '@clerk/nextjs';
4 | import { Montserrat } from 'next/font/google';
5 | import Image from 'next/image';
6 | import Link from 'next/link';
7 |
8 | import { cn } from '@/lib/utils';
9 | import { Button } from '@/components/ui/button';
10 |
11 | const font = Montserrat({
12 | display: 'swap',
13 | weight: '600',
14 | subsets: ['latin'],
15 | });
16 |
17 | const LandingNavbar = () => {
18 | const { isSignedIn, signOut } = useAuth();
19 |
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 | D-prompt AI
28 |
29 |
30 |
31 |
32 |
33 | Get Started
34 |
35 |
36 |
37 |
38 | );
39 | };
40 |
41 | export default LandingNavbar;
42 |
--------------------------------------------------------------------------------
/components/ui/badge.tsx:
--------------------------------------------------------------------------------
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,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
37 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/settings/page.tsx:
--------------------------------------------------------------------------------
1 | import Heading from '@/components/Heading';
2 | import SubscribeButton from '@/components/SubscribeButton';
3 | import { getSubscription } from '@/lib/subscription';
4 | import { Settings } from 'lucide-react';
5 |
6 | const SettingsPage = async () => {
7 | const isPro = await getSubscription();
8 |
9 | return (
10 | <>
11 |
18 |
19 |
20 | Gain access to advanced features, no limits, priority support, and
21 | exclusive content. Elevate your creative process to new heights with
22 | premium tools and resources, tailored to those who demand the utmost
23 | from their creative journey.
24 |
25 |
26 | {isPro ? 'Current Plan: Pro Plan' : 'Current Plan: Free Plan'}
27 |
28 |
29 |
30 | >
31 | );
32 | };
33 |
34 | export default SettingsPage;
35 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/api-limit.ts:
--------------------------------------------------------------------------------
1 | import { auth } from "@clerk/nextjs";
2 |
3 | import { prismadb } from "@/lib/prismadb";
4 | import { MAX_FREE_COUNTS } from "@/constants";
5 |
6 | export const incrementApiLimit = async () => {
7 | const { userId } = auth();
8 |
9 | if (!userId) return;
10 |
11 | const userApiLimit = await prismadb.userApiLimit.findUnique({
12 | where: { userId: userId },
13 | });
14 |
15 | if (userApiLimit) {
16 | await prismadb.userApiLimit.update({
17 | where: { userId: userId },
18 | data: { count: userApiLimit.count + 1 },
19 | });
20 | } else {
21 | await prismadb.userApiLimit.create({
22 | data: { userId: userId, count: 1 },
23 | });
24 | }
25 | };
26 |
27 | export const checkApiLimit = async () => {
28 | const { userId } = auth();
29 |
30 | if (!userId) return false;
31 | const userApiLimit = await prismadb.userApiLimit.findUnique({ where: { userId: userId } });
32 | if (!userApiLimit || userApiLimit.count < MAX_FREE_COUNTS) return true;
33 | else return false;
34 |
35 | };
36 |
37 | export const getApiLimitCount = async () => {
38 | const { userId } = auth();
39 |
40 | if (!userId) return 0;
41 |
42 | const userApiLimit = await prismadb.userApiLimit.findUnique({
43 | where: {
44 | userId
45 | }
46 | });
47 |
48 | if (!userApiLimit) return 0;
49 |
50 | return userApiLimit.count;
51 | };
--------------------------------------------------------------------------------
/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Welcome to D-prompt-AI! We're excited to have you on board. Contributions from the community help improve and enhance the project for everyone. Here's how you can get started:
4 |
5 | ## How to Contribute
6 |
7 | 1. **Fork** the repository on GitHub.
8 | 2. **Clone** the forked repository to your local machine.
9 | 3. Make your changes or additions in your local repository.
10 | 4. Test your changes to ensure they work as expected.
11 | 5. **Commit** your changes with descriptive commit messages.
12 | 6. **Push** your changes to your forked repository on GitHub.
13 | 7. Create a new **Pull Request** from your forked repository to the main project repository.
14 |
15 | ## Code of Conduct
16 |
17 | Please note that we have a [Code of Conduct](CODE_OF_CONDUCT.md). We expect all contributors to adhere to it to maintain a positive and respectful community.
18 |
19 | ## Report Issues
20 |
21 | If you find any issues or have suggestions for improvements, you can open an [issue](https://github.com/drakkarrr/d-prompt-ai/issues) on GitHub. Be sure to provide as much detail as possible to help us understand and address the problem.
22 |
23 | ## Get in Touch
24 |
25 | If you have questions or need help with the contribution process, feel free to reach out on our [community chat](https://gitter.im/yourproject/community) or by [email](mailto:drakkar.alpha00@gmail.com).
26 |
27 | We appreciate your interest in contributing to [Your Project Name]. Together, we can make this project even better!
28 |
--------------------------------------------------------------------------------
/app/api/video/route.ts:
--------------------------------------------------------------------------------
1 | import Replicate from "replicate";
2 | import { auth } from "@clerk/nextjs";
3 | import { NextResponse } from "next/server";
4 | import { incrementApiLimit, checkApiLimit } from "@/lib/api-limit";
5 | import { getSubscription } from "@/lib/subscription";
6 |
7 | const replicate = new Replicate({
8 | auth: process.env.REPLICATE_API_TOKEN!,
9 | });
10 |
11 | export async function POST(
12 | req: Request
13 | ) {
14 | try {
15 | const { userId } = auth();
16 | const body = await req.json();
17 | const { prompt } = body;
18 |
19 | if (!userId) return new NextResponse("Unauthorized", { status: 401 });
20 | if (!prompt) return new NextResponse("Prompt is required", { status: 400 });
21 |
22 | const freeTier = await checkApiLimit();
23 | const isPro = await getSubscription()
24 |
25 | if (!freeTier && !isPro) return new NextResponse('Free tier limit reached', { status: 403 });
26 |
27 | const response = await replicate.run(
28 | "anotherjesse/zeroscope-v2-xl:9f747673945c62801b13b84701c783929c0ee784e4748ec062204894dda1a351",
29 | {
30 | input: {
31 | prompt
32 | }
33 | }
34 | );
35 |
36 | if (!isPro) {
37 | await incrementApiLimit();
38 | }
39 |
40 | return NextResponse.json(response);
41 | } catch (error) {
42 | console.log('[VIDEO ERROR:]', error);
43 | return new NextResponse("Internal Error", { status: 500 });
44 | }
45 | };
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import { User } from "@prisma/client"
2 | import type { Icon } from "lucide-react"
3 |
4 | import { Icons } from "@/components/icons"
5 |
6 | export type Props = {
7 | children: React.ReactNode
8 | }
9 |
10 | export type NavItem = {
11 | title: string
12 | href: string
13 | disabled?: boolean
14 | }
15 |
16 | export type MainNavItem = NavItem
17 |
18 | export type SidebarNavItem = {
19 | title: string
20 | disabled?: boolean
21 | external?: boolean
22 | icon?: keyof typeof Icons
23 | } & (
24 | | {
25 | href: string
26 | items?: never
27 | }
28 | | {
29 | href?: string
30 | items: NavLink[]
31 | }
32 | )
33 |
34 | export type SiteConfig = {
35 | name: string
36 | description: string
37 | url: string
38 | ogImage: string
39 | links: {
40 | twitter: string
41 | github: string
42 | }
43 | }
44 |
45 | export type DocsConfig = {
46 | mainNav: MainNavItem[]
47 | sidebarNav: SidebarNavItem[]
48 | }
49 |
50 | export type MarketingConfig = {
51 | mainNav: MainNavItem[]
52 | }
53 |
54 | export type DashboardConfig = {
55 | mainNav: MainNavItem[]
56 | sidebarNav: SidebarNavItem[]
57 | }
58 |
59 | export type SubscriptionPlan = {
60 | name: string
61 | description: string
62 | stripePriceId: string
63 | }
64 |
65 | export type UserSubscriptionPlan = SubscriptionPlan &
66 | Pick & {
67 | stripeCurrentPeriodEnd: number
68 | isPro: boolean
69 | }
--------------------------------------------------------------------------------
/app/api/music/route.ts:
--------------------------------------------------------------------------------
1 | import Replicate from "replicate";
2 | import { auth } from "@clerk/nextjs";
3 | import { NextResponse } from "next/server";
4 | import { incrementApiLimit, checkApiLimit } from "@/lib/api-limit";
5 | import { getSubscription } from "@/lib/subscription";
6 | // import { checkSubscription } from "@/lib/subscription";
7 |
8 | const replicate = new Replicate({
9 | auth: process.env.REPLICATE_API_TOKEN!,
10 | });
11 |
12 | export async function POST(
13 | req: Request
14 | ) {
15 | try {
16 | const { userId } = auth();
17 | const body = await req.json();
18 | const { prompt } = body;
19 |
20 | if (!userId) return new NextResponse("Unauthorized", { status: 401 });
21 | if (!prompt) return new NextResponse("Prompt is required", { status: 400 });
22 |
23 | const freeTier = await checkApiLimit();
24 | const isPro = await getSubscription()
25 |
26 | if (!freeTier && !isPro) return new NextResponse('Free tier limit reached', { status: 403 });
27 |
28 | const response = await replicate.run(
29 | "riffusion/riffusion:8cf61ea6c56afd61d8f5b9ffd14d7c216c0a93844ce2d82ac1c9ecc9c7f24e05",
30 | {
31 | input: {
32 | prompt_a: prompt
33 | }
34 | }
35 | );
36 |
37 | if (!isPro) {
38 | await incrementApiLimit();
39 | }
40 |
41 | return NextResponse.json(response);
42 | } catch (error) {
43 | console.log('[MUSIC_ERROR]', error);
44 | return new NextResponse("Internal Error", { status: 500 });
45 | }
46 | };
--------------------------------------------------------------------------------
/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import * as React from 'react';
3 | import * as AvatarPrimitive from '@radix-ui/react-avatar';
4 | import { cn } from '@/lib/utils';
5 |
6 | const Avatar = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 | ));
19 | Avatar.displayName = AvatarPrimitive.Root.displayName;
20 |
21 | const AvatarImage = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, ...props }, ref) => (
25 |
30 | ));
31 | AvatarImage.displayName = AvatarPrimitive.Image.displayName;
32 |
33 | const AvatarFallback = React.forwardRef<
34 | React.ElementRef,
35 | React.ComponentPropsWithoutRef
36 | >(({ className, ...props }, ref) => (
37 |
45 | ));
46 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
47 | export { Avatar, AvatarImage, AvatarFallback };
48 |
--------------------------------------------------------------------------------
/app/api/chat/route.ts:
--------------------------------------------------------------------------------
1 | import { auth } from '@clerk/nextjs';
2 | import { NextResponse } from 'next/server';
3 | import { Configuration, OpenAIApi } from 'openai'
4 |
5 | import { checkApiLimit, incrementApiLimit } from '@/lib/api-limit';
6 | import { getSubscription } from '@/lib/subscription';
7 |
8 | const configuration = new Configuration({
9 | apiKey: process.env.OPENAI_API_KEY,
10 | })
11 |
12 | const openai = new OpenAIApi(configuration)
13 |
14 |
15 | export const POST = async (req: Request) => {
16 | console.log(req.body);
17 |
18 | try {
19 | const { userId } = auth()
20 | const body = await req.json()
21 | const { messages } = body
22 |
23 | if (!userId) return new NextResponse('Unauthorized', { status: 401 });
24 | if (!configuration.apiKey) return new NextResponse('Open AI API Keys not configured', { status: 500 });
25 | if (!messages) return new NextResponse('No messages found', { status: 400 });
26 |
27 | const freeTier = await checkApiLimit();
28 | const isPro = await getSubscription()
29 |
30 | if (!freeTier && !isPro) return new NextResponse('Free tier limit reached', { status: 403 });
31 |
32 | const response = await openai.createChatCompletion({
33 | model: 'gpt-3.5-turbo',
34 | messages
35 | })
36 |
37 | if (!isPro) {
38 | await incrementApiLimit();
39 | }
40 |
41 | return NextResponse.json(response.data.choices[0].message);
42 |
43 |
44 | } catch (err) {
45 | console.log(`Conversation error: ${err}`);
46 | return new NextResponse(`Internal error: ${err}`, { status: 500 });
47 | }
48 | }
--------------------------------------------------------------------------------
/components/FreeCounter.tsx:
--------------------------------------------------------------------------------
1 | import { Zap } from 'lucide-react';
2 | import { useEffect, useState } from 'react';
3 |
4 | import { MAX_FREE_COUNTS } from '@/constants';
5 | import { Card, CardContent } from '@/components/ui/card';
6 | import { Button } from '@/components/ui/button';
7 | import { Progress } from '@/components/ui/progress';
8 | import { useProModal } from '@/hooks/useProModal';
9 |
10 | interface FreeCounterProps {
11 | isPro: boolean;
12 | apiLimit: number;
13 | }
14 |
15 | const FreeCounter: React.FC = ({
16 | isPro = false,
17 | apiLimit = 0,
18 | }) => {
19 | const [mounted, setMounted] = useState(false);
20 | const proModal = useProModal();
21 |
22 | useEffect(() => {
23 | setMounted(true);
24 | }, []);
25 |
26 | if (!mounted) return null;
27 | if (isPro) return null;
28 |
29 | return (
30 |
31 |
32 |
33 |
34 |
35 | {apiLimit} / {MAX_FREE_COUNTS} Free Generations
36 |
37 |
41 |
42 |
47 | Upgrade
48 |
49 |
50 |
51 |
52 |
53 | );
54 | };
55 |
56 | export default FreeCounter;
57 |
--------------------------------------------------------------------------------
/app/api/image/route.ts:
--------------------------------------------------------------------------------
1 | import { auth } from '@clerk/nextjs';
2 | import { NextResponse } from 'next/server';
3 | import { Configuration, OpenAIApi } from 'openai'
4 | import { checkApiLimit, incrementApiLimit } from '@/lib/api-limit';
5 | import { getSubscription } from '@/lib/subscription';
6 |
7 | const configuration = new Configuration({
8 | apiKey: process.env.OPENAI_API_KEY,
9 | })
10 |
11 | const openai = new OpenAIApi(configuration)
12 |
13 | export const POST = async (req: Request) => {
14 | console.log(req.body);
15 |
16 | try {
17 | const { userId } = auth()
18 | const body = await req.json()
19 | const { prompt, amount = 1, resolution = '512x512' } = body
20 |
21 | if (!userId) return new NextResponse('Unauthorized', { status: 401 });
22 | if (!configuration.apiKey) return new NextResponse('Open AI API Keys not configured', { status: 500 });
23 |
24 | if (!prompt) return new NextResponse('No prompt found', { status: 400 });
25 | if (!amount) return new NextResponse('No amount found', { status: 400 });
26 | if (!resolution) return new NextResponse('No resolution found', { status: 400 });
27 |
28 | const freeTier = await checkApiLimit();
29 | const isPro = await getSubscription()
30 |
31 | if (!freeTier && !isPro) return new NextResponse('Free tier limit reached', { status: 403 });
32 |
33 | const response = await openai.createImage({
34 | prompt,
35 | n: parseInt(amount, 10),
36 | size: resolution,
37 | })
38 |
39 | if (!isPro) {
40 | await incrementApiLimit();
41 | }
42 |
43 | return NextResponse.json(response.data.data);
44 |
45 |
46 | } catch (err) {
47 | console.log(`Image Generator error: ${err}`);
48 | return new NextResponse(`Internal error: ${err}`, { status: 500 });
49 | }
50 | }
--------------------------------------------------------------------------------
/app/api/code/route.ts:
--------------------------------------------------------------------------------
1 | import { auth } from '@clerk/nextjs';
2 | import { NextResponse } from 'next/server';
3 | import { ChatCompletionRequestMessage, Configuration, OpenAIApi } from 'openai'
4 | import { checkApiLimit, incrementApiLimit } from '@/lib/api-limit';
5 | import { getSubscription } from '@/lib/subscription';
6 |
7 | const configuration = new Configuration({
8 | apiKey: process.env.OPENAI_API_KEY,
9 | })
10 |
11 | const openai = new OpenAIApi(configuration)
12 |
13 | const instructionMessage: ChatCompletionRequestMessage = {
14 | role: 'system',
15 | content: "You are a clever code generator. You must answer only in markdown code snippets. Use code comments for explanations."
16 | }
17 |
18 | export const POST = async (req: Request) => {
19 | console.log(req.body);
20 |
21 | try {
22 | const { userId } = auth()
23 | const body = await req.json()
24 | const { messages } = body
25 |
26 | if (!userId) return new NextResponse('Unauthorized', { status: 401 });
27 | if (!configuration.apiKey) return new NextResponse('Open AI API Keys not configured', { status: 500 });
28 | if (!messages) return new NextResponse('No messages found', { status: 400 });
29 |
30 | const freeTier = await checkApiLimit();
31 | const isPro = await getSubscription()
32 |
33 | if (!freeTier && !isPro) return new NextResponse('Free tier limit reached', { status: 403 });
34 |
35 | const response = await openai.createChatCompletion({
36 | model: 'gpt-3.5-turbo',
37 | messages: [instructionMessage, ...messages],
38 | })
39 |
40 | if (!isPro) {
41 | await incrementApiLimit();
42 | }
43 |
44 | return NextResponse.json(response.data.choices[0].message);
45 |
46 |
47 | } catch (err) {
48 | console.log(`Code error: ${err}`);
49 | return new NextResponse(`Code error: ${err}`, { status: 500 });
50 | }
51 | }
--------------------------------------------------------------------------------
/public/technical-support.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
11 |
14 |
21 |
22 |
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 222.2 84% 4.9%;
9 |
10 | --muted: 210 40% 96.1%;
11 | --muted-foreground: 215.4 16.3% 46.9%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 222.2 84% 4.9%;
15 |
16 | --card: 0 0% 100%;
17 | --card-foreground: 222.2 84% 4.9%;
18 |
19 | --border: 214.3 31.8% 91.4%;
20 | --input: 214.3 31.8% 91.4%;
21 |
22 | --primary: 222.2 47.4% 11.2%;
23 | --primary-foreground: 210 40% 98%;
24 |
25 | --secondary: 210 40% 96.1%;
26 | --secondary-foreground: 222.2 47.4% 11.2%;
27 |
28 | --accent: 210 40% 96.1%;
29 | --accent-foreground: 222.2 47.4% 11.2%;
30 |
31 | --destructive: 0 84.2% 60.2%;
32 | --destructive-foreground: 210 40% 98%;
33 |
34 | --ring: 215 20.2% 65.1%;
35 |
36 | --radius: 0.5rem;
37 | }
38 |
39 | .dark {
40 | --background: 222.2 84% 4.9%;
41 | --foreground: 210 40% 98%;
42 |
43 | --muted: 217.2 32.6% 17.5%;
44 | --muted-foreground: 215 20.2% 65.1%;
45 |
46 | --popover: 222.2 84% 4.9%;
47 | --popover-foreground: 210 40% 98%;
48 |
49 | --card: 222.2 84% 4.9%;
50 | --card-foreground: 210 40% 98%;
51 |
52 | --border: 217.2 32.6% 17.5%;
53 | --input: 217.2 32.6% 17.5%;
54 |
55 | --primary: 210 40% 98%;
56 | --primary-foreground: 222.2 47.4% 11.2%;
57 |
58 | --secondary: 217.2 32.6% 17.5%;
59 | --secondary-foreground: 210 40% 98%;
60 |
61 | --accent: 217.2 32.6% 17.5%;
62 | --accent-foreground: 210 40% 98%;
63 |
64 | --destructive: 0 62.8% 30.6%;
65 | --destructive-foreground: 0 85.7% 97.3%;
66 |
67 | --ring: 217.2 32.6% 17.5%;
68 | }
69 | }
70 |
71 | html,
72 | body,
73 | :root {
74 | height: 100%;
75 | }
76 |
77 | @layer base {
78 | * {
79 | @apply border-border;
80 | }
81 | body {
82 | @apply bg-background text-foreground;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/components/LandingHero.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Link from 'next/link';
4 | import TypewriterComponent from 'typewriter-effect';
5 | import { useAuth } from '@clerk/nextjs';
6 | import { Button } from '@/components/ui/button';
7 |
8 | const LandingHero = () => {
9 | const { isSignedIn } = useAuth();
10 |
11 | return (
12 |
13 |
14 |
D-prompt AI Generative for
15 |
16 |
29 |
30 |
31 |
32 | A Generative AI app that embarks on creative discovery and immerse
33 | yourself in dynamic conversations, captivating visuals, harmonious
34 | melodies, assistive code generation and more.
35 | No credit card required!
36 |
37 |
38 |
39 |
43 | Start Generating For Free
44 |
45 |
46 |
47 |
48 | Developed by
49 |
53 | Drakkar
54 |
55 |
56 |
57 | );
58 | };
59 |
60 | export default LandingHero;
61 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
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 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 | danger:
14 | 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
15 | outline:
16 | 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
17 | secondary:
18 | 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
19 | ghost: 'hover:bg-accent hover:text-accent-foreground',
20 | link: 'text-primary underline-offset-4 hover:underline',
21 | premium:
22 | 'bg-gradient-to-r from-[#61cfeb] via-[#9281d8d5] to-[#9076fc] text-white border-0',
23 | },
24 | size: {
25 | default: 'h-10 px-4 py-2',
26 | sm: 'h-9 rounded-md px-3',
27 | lg: 'h-11 rounded-md px-8',
28 | icon: 'h-10 w-10',
29 | },
30 | },
31 | defaultVariants: {
32 | variant: 'default',
33 | size: 'default',
34 | },
35 | }
36 | );
37 |
38 | export interface ButtonProps
39 | extends React.ButtonHTMLAttributes,
40 | VariantProps {
41 | asChild?: boolean;
42 | }
43 |
44 | const Button = React.forwardRef(
45 | ({ className, variant, size, asChild = false, ...props }, ref) => {
46 | const Comp = asChild ? Slot : 'button';
47 | return (
48 |
53 | );
54 | }
55 | );
56 | Button.displayName = 'Button';
57 |
58 | export { Button, buttonVariants };
59 |
--------------------------------------------------------------------------------
/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/app/api/webhook/route.ts:
--------------------------------------------------------------------------------
1 | import Stripe from "stripe"
2 | import { headers } from "next/headers"
3 | import { NextResponse } from "next/server"
4 | import { prismadb } from "@/lib/prismadb"
5 | import { stripe } from "@/lib/stripe-connect"
6 |
7 | export const POST = async (req: Request) => {
8 | const body = await req.text()
9 | const signature = headers().get("Stripe-Signature") as string
10 |
11 | let event: Stripe.Event
12 |
13 | try {
14 | event = stripe.webhooks.constructEvent(
15 | body,
16 | signature,
17 | process.env.STRIPE_WEBHOOK_SECRET!
18 | )
19 | } catch (error: any) {
20 | return new NextResponse(`Webhook Error: ${error.message}`, { status: 400 })
21 | }
22 |
23 | const session = event.data.object as Stripe.Checkout.Session
24 |
25 | if (event.type === "checkout.session.completed") {
26 | const subscription = await stripe.subscriptions.retrieve(
27 | session.subscription as string
28 | )
29 |
30 | if (!session?.metadata?.userId) return new NextResponse("User id is required", { status: 400 });
31 |
32 |
33 | await prismadb.userSubscription.create({
34 | data: {
35 | userId: session?.metadata?.userId,
36 | stripeSubscriptionId: subscription.id,
37 | stripeCustomerId: subscription.customer as string,
38 | stripePriceId: subscription.items.data[0].price.id,
39 | stripeCurrentPeriodEnd: new Date(subscription.current_period_end * 1000),
40 | },
41 | })
42 | }
43 |
44 | if (event.type === "invoice.payment_succeeded") {
45 | const subscription = await stripe.subscriptions.retrieve(session.subscription as string)
46 |
47 | await prismadb.userSubscription.update({
48 | where: { stripeSubscriptionId: subscription.id },
49 | data: {
50 | stripePriceId: subscription.items.data[0].price.id,
51 | stripeCurrentPeriodEnd: new Date(
52 | subscription.current_period_end * 1000
53 | ),
54 | },
55 | })
56 | }
57 |
58 | return new NextResponse(null, { status: 200 })
59 | };
--------------------------------------------------------------------------------
/components/LandingPageContents.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
4 |
5 | const testimonials = [
6 | {
7 | id: 1,
8 | name: 'John Doe',
9 | title: 'Teacher',
10 | org: 'Google',
11 | desc: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.',
12 | },
13 | {
14 | id: 2,
15 | name: 'Jane Doe',
16 | title: 'CEO',
17 | org: 'Facebook',
18 | desc: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.',
19 | },
20 | {
21 | id: 3,
22 | name: 'Drak Doe',
23 | title: 'CEO',
24 | org: 'Google',
25 | desc: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.',
26 | },
27 | {
28 | id: 4,
29 | name: 'Chris Doe',
30 | title: 'Sofware Developer',
31 | org: 'Google',
32 | desc: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.',
33 | },
34 | ];
35 |
36 | const LandingPageContents = () => {
37 | return (
38 | <>
39 |
40 |
41 | Testimonials
42 |
43 |
44 | {testimonials.map((testimonial) => (
45 |
49 |
50 |
51 | {testimonial.desc}
52 |
53 |
54 |
55 |
{testimonial.name}
56 |
{testimonial.title}
57 |
58 |
59 |
60 |
61 | ))}
62 |
63 |
64 |
65 | Developed by Drakkar
66 |
67 | >
68 | );
69 | };
70 |
71 | export default LandingPageContents;
72 |
--------------------------------------------------------------------------------
/app/api/stripe/route.ts:
--------------------------------------------------------------------------------
1 | import { auth, currentUser } from "@clerk/nextjs";
2 | import { NextResponse } from "next/server";
3 | import { stripe } from "@/lib/stripe-connect";
4 | import { prismadb } from "@/lib/prismadb";
5 | import { absoluteUrl } from "@/lib/utils";
6 |
7 |
8 | const settingsUrl = absoluteUrl("/settings");
9 | export const GET = async () => {
10 | try {
11 | const { userId } = auth();
12 | const user = await currentUser();
13 |
14 | if (!userId || !user) return new NextResponse("Unauthorized", { status: 401 });
15 |
16 | const userSubscription = await prismadb.userSubscription.findUnique({
17 | where: { userId }
18 | })
19 |
20 | if (userSubscription && userSubscription.stripeCustomerId) {
21 | const stripeSession = await stripe.billingPortal.sessions.create({
22 | customer: userSubscription.stripeCustomerId,
23 | return_url: settingsUrl,
24 | })
25 |
26 | return new NextResponse(JSON.stringify({ url: stripeSession.url }))
27 | }
28 |
29 | const stripeSession = await stripe.checkout.sessions.create({
30 | success_url: settingsUrl,
31 | cancel_url: settingsUrl,
32 | payment_method_types: ["card"],
33 | mode: "subscription",
34 | billing_address_collection: "auto",
35 | customer_email: user.emailAddresses[0].emailAddress,
36 | line_items: [
37 | {
38 | price_data: {
39 | currency: "USD",
40 | product_data: {
41 | name: "D-Prompt Pro",
42 | description: "Unlimited AI Generations"
43 | },
44 | unit_amount: 2000,
45 | recurring: {
46 | interval: "month"
47 | }
48 | },
49 | quantity: 1,
50 | },
51 | ],
52 | metadata: {
53 | userId,
54 | },
55 | })
56 |
57 | return new NextResponse(JSON.stringify({ url: stripeSession.url }))
58 | } catch (error) {
59 | console.log("[STRIPE_ERROR]", error);
60 | return new NextResponse("Internal Error", { status: 500 });
61 | }
62 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "d-prompt-ai",
3 | "version": "0.1.0",
4 | "private": true,
5 | "main": "index.js",
6 | "repository": "https://github.com/Drakkarrr/D-prompt-AI.git",
7 | "author": "Drakkarrr ",
8 | "license": "MIT",
9 | "scripts": {
10 | "dev": "next dev",
11 | "build": "next build",
12 | "start": "next start",
13 | "lint": "next lint --fix",
14 | "precommit": "lint-staged",
15 | "test": "yarn lint",
16 | "prepare": "husky install",
17 | "postinstall": "prisma generate"
18 | },
19 | "husky": {
20 | "hooks": {
21 | "pre-commit": "lint-staged"
22 | }
23 | },
24 | "lint-staged": {
25 | "*.tsx": "eslint --fix",
26 | "*.ts": "stylelint --fix"
27 | },
28 | "dependencies": {
29 | "@clerk/nextjs": "^4.29.2",
30 | "@hookform/resolvers": "^3.1.1",
31 | "@prisma/client": "^5.0.0",
32 | "@radix-ui/react-avatar": "^1.0.3",
33 | "@radix-ui/react-dialog": "^1.0.4",
34 | "@radix-ui/react-label": "^2.0.2",
35 | "@radix-ui/react-progress": "^1.0.3",
36 | "@radix-ui/react-select": "^1.2.2",
37 | "@radix-ui/react-slot": "^1.0.2",
38 | "@types/node": "20.4.2",
39 | "@types/react": "18.2.15",
40 | "@types/react-dom": "18.2.7",
41 | "add": "^2.0.6",
42 | "autoprefixer": "10.4.14",
43 | "axios": "^1.4.0",
44 | "button": "^1.1.1",
45 | "class-variance-authority": "^0.7.0",
46 | "clsx": "^2.0.0",
47 | "crisp-sdk-web": "^1.0.21",
48 | "eslint": "8.45.0",
49 | "eslint-config-next": "^13.4.10",
50 | "init": "^0.1.2",
51 | "lucide-react": "^0.262.0",
52 | "next": "13.4.10",
53 | "openai": "^3.3.0",
54 | "postcss": "8.4.26",
55 | "react": "18.2.0",
56 | "react-dom": "18.2.0",
57 | "react-hook-form": "^7.45.2",
58 | "react-hot-toast": "^2.4.1",
59 | "react-markdown": "^8.0.7",
60 | "replicate": "^0.14.0",
61 | "shadcn-ui": "^0.3.0",
62 | "stripe": "^12.16.0",
63 | "tailwind-merge": "^1.14.0",
64 | "tailwindcss": "3.3.3",
65 | "tailwindcss-animate": "^1.0.6",
66 | "typescript": "5.1.6",
67 | "typewriter-effect": "^2.20.1",
68 | "zod": "^3.21.4",
69 | "zustand": "^4.3.9"
70 | },
71 | "devDependencies": {
72 | "eslint-config-prettier": "^8.8.0",
73 | "eslint-plugin-tailwindcss": "^3.13.0",
74 | "husky": "^8.0.0",
75 | "prettier": "^3.0.0",
76 | "prettier-plugin-tailwindcss": "^0.4.1",
77 | "prisma": "^5.0.0"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ['class'],
4 | content: [
5 | './pages/**/*.{ts,tsx}',
6 | './components/**/*.{ts,tsx}',
7 | './app/**/*.{ts,tsx}',
8 | './src/**/*.{ts,tsx}',
9 | ],
10 | theme: {
11 | container: {
12 | center: true,
13 | padding: '2rem',
14 | screens: {
15 | '2xl': '1400px',
16 | '3xl': '1600px',
17 | '4xl': '1800px',
18 | },
19 | },
20 | extend: {
21 | colors: {
22 | border: 'hsl(var(--border))',
23 | input: 'hsl(var(--input))',
24 | ring: 'hsl(var(--ring))',
25 | background: 'hsl(var(--background))',
26 | foreground: 'hsl(var(--foreground))',
27 | primary: {
28 | DEFAULT: 'hsl(var(--primary))',
29 | foreground: 'hsl(var(--primary-foreground))',
30 | },
31 | secondary: {
32 | DEFAULT: 'hsl(var(--secondary))',
33 | foreground: 'hsl(var(--secondary-foreground))',
34 | },
35 | destructive: {
36 | DEFAULT: 'hsl(var(--destructive))',
37 | foreground: 'hsl(var(--destructive-foreground))',
38 | },
39 | muted: {
40 | DEFAULT: 'hsl(var(--muted))',
41 | foreground: 'hsl(var(--muted-foreground))',
42 | },
43 | accent: {
44 | DEFAULT: 'hsl(var(--accent))',
45 | foreground: 'hsl(var(--accent-foreground))',
46 | },
47 | popover: {
48 | DEFAULT: 'hsl(var(--popover))',
49 | foreground: 'hsl(var(--popover-foreground))',
50 | },
51 | card: {
52 | DEFAULT: 'hsl(var(--card))',
53 | foreground: 'hsl(var(--card-foreground))',
54 | },
55 | },
56 | borderRadius: {
57 | lg: 'var(--radius)',
58 | md: 'calc(var(--radius) - 2px)',
59 | sm: 'calc(var(--radius) - 4px)',
60 | },
61 | keyframes: {
62 | 'accordion-down': {
63 | from: { height: 0 },
64 | to: { height: 'var(--radix-accordion-content-height)' },
65 | },
66 | 'accordion-up': {
67 | from: { height: 'var(--radix-accordion-content-height)' },
68 | to: { height: 0 },
69 | },
70 | },
71 | animation: {
72 | 'accordion-down': 'accordion-down 0.2s ease-out',
73 | 'accordion-up': 'accordion-up 0.2s ease-out',
74 | },
75 | },
76 | },
77 | plugins: [require('tailwindcss-animate')],
78 | };
79 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/dashboard/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useRouter as Router } from 'next/navigation';
4 |
5 | import { Card } from '@/components/ui/card';
6 | import { cn } from '@/lib/utils';
7 |
8 | import {
9 | ArrowRight,
10 | CodeIcon,
11 | ImageIcon,
12 | MessageSquare,
13 | MusicIcon,
14 | VideoIcon,
15 | } from 'lucide-react';
16 |
17 | const tools = [
18 | {
19 | label: 'Chat Generator',
20 | icon: MessageSquare,
21 | href: '/chat',
22 | color: 'text-violet-500',
23 | bgColor: 'bg-blue-500/10',
24 | },
25 | {
26 | label: 'Image Generator',
27 | icon: ImageIcon,
28 | href: '/image',
29 | color: 'text-orange-700',
30 | bgColor: 'bg-orange-500/10',
31 | },
32 | {
33 | label: 'Video Generator',
34 | icon: VideoIcon,
35 | href: '/video',
36 | color: 'text-pink-300',
37 | bgColor: 'bg-pink-500/10',
38 | },
39 | {
40 | label: 'Music Generator',
41 | icon: MusicIcon,
42 | href: '/music',
43 | color: 'text-emerald-500',
44 | bgColor: 'bg-emerald-500/10',
45 | },
46 | {
47 | label: 'Code Generator',
48 | icon: CodeIcon,
49 | href: '/code',
50 | color: 'text-blue-700',
51 | bgColor: 'bg-blue-500/10',
52 | },
53 | ];
54 |
55 | const DashboardPage = () => {
56 | const router = Router();
57 |
58 | return (
59 | <>
60 |
61 |
62 | Get started with D-propmt AI
63 |
64 |
65 | Generate contents using OpenAI GPT3.5 and Replicate API
66 |
67 |
68 |
69 | {tools.map((tool) => (
70 |
router.push(tool.href)}
72 | key={tool.href}
73 | className='flex cursor-pointer items-center justify-between border-black/5 p-4 transition hover:shadow-md'
74 | >
75 |
76 |
77 |
78 |
79 |
{tool.label}
80 |
81 |
82 |
83 | ))}
84 |
85 | >
86 | );
87 | };
88 |
89 | export default DashboardPage;
90 |
--------------------------------------------------------------------------------
/components/ProModal.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import axios from 'axios';
4 | import { useState } from 'react';
5 | import { Check, Zap } from 'lucide-react';
6 | import { toast } from 'react-hot-toast';
7 |
8 | import {
9 | Dialog,
10 | DialogContent,
11 | DialogHeader,
12 | DialogTitle,
13 | DialogDescription,
14 | DialogFooter,
15 | } from '@/components/ui/dialog';
16 | import { Badge } from '@/components/ui/badge';
17 | import { Button } from '@/components/ui/button';
18 | import { useProModal } from '@/hooks/useProModal';
19 | import { tools } from '@/constants';
20 | import { Card } from '@/components/ui/card';
21 | import { cn } from '@/lib/utils';
22 |
23 | const ProModal = () => {
24 | const proModal = useProModal();
25 | const [loading, setLoading] = useState(false);
26 |
27 | const onSubscribe = async () => {
28 | try {
29 | setLoading(true);
30 | const response = await axios.get('/api/stripe');
31 |
32 | window.location.href = response.data.url;
33 | } catch (error) {
34 | toast.error('Something went wrong');
35 | } finally {
36 | setLoading(false);
37 | }
38 | };
39 |
40 | return (
41 |
42 |
43 |
44 |
45 |
46 | Upgrade to D-prompt
47 |
48 | pro
49 |
50 |
51 |
52 |
53 | {tools.map((tool) => (
54 |
58 |
59 |
60 |
61 |
62 |
{tool.label}
63 |
64 |
65 |
66 | ))}
67 |
68 |
69 |
70 |
77 | Upgrade to Pro
78 |
79 |
80 |
81 |
82 |
83 | );
84 | };
85 |
86 | export default ProModal;
87 |
--------------------------------------------------------------------------------
/components/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Image from 'next/image';
4 | import Link from 'next/link';
5 | import { usePathname } from 'next/navigation';
6 |
7 | import {
8 | CodeIcon,
9 | ImageIcon,
10 | LayoutDashboard,
11 | MessageSquare,
12 | MusicIcon,
13 | Settings,
14 | VideoIcon,
15 | } from 'lucide-react';
16 |
17 | import { Montserrat } from 'next/font/google';
18 | import { cn } from '@/lib/utils';
19 | import FreeCounter from '@/components/FreeCounter';
20 |
21 | const montserrat = Montserrat({
22 | weight: '700',
23 | subsets: ['latin'],
24 | });
25 |
26 | const routes = [
27 | {
28 | label: 'Dashboard',
29 | icon: LayoutDashboard,
30 | href: '/dashboard',
31 | color: 'text-sky-500',
32 | },
33 | {
34 | label: 'Chat Generator',
35 | icon: MessageSquare,
36 | href: '/chat',
37 | color: 'text-violet-500',
38 | },
39 | {
40 | label: 'Image Generator',
41 | icon: ImageIcon,
42 | href: '/image',
43 | color: 'text-orange-700',
44 | },
45 | {
46 | label: 'Video Generator',
47 | icon: VideoIcon,
48 | href: '/video',
49 | color: 'text-pink-300',
50 | },
51 | {
52 | label: 'Music Generator',
53 | icon: MusicIcon,
54 | href: '/music',
55 | color: 'text-emerald-500',
56 | },
57 | {
58 | label: 'Code Generator',
59 | icon: CodeIcon,
60 | href: '/code',
61 | color: 'text-blue-700',
62 | },
63 | {
64 | label: 'Settings',
65 | icon: Settings,
66 | href: '/settings',
67 | },
68 | ];
69 |
70 | interface SidebarProps {
71 | apiLimit: number;
72 | isPro: boolean;
73 | }
74 |
75 | const Sidebar: React.FC = ({ apiLimit = 0, isPro = false }) => {
76 | const pathname = usePathname();
77 |
78 | return (
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | D-prompt
87 |
88 |
89 |
90 | {routes.map((route) => (
91 |
99 |
100 |
101 | {route.label}
102 |
103 |
104 | ))}
105 |
106 |
107 |
108 |
109 | );
110 | };
111 |
112 | export default Sidebar;
113 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/music/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useState } from 'react';
4 | import { useRouter as Router } from 'next/navigation';
5 | import axios from 'axios';
6 |
7 | import Heading from '@/components/Heading';
8 | import { Form, FormControl, FormField, FormItem } from '@/components/ui/form';
9 | import { Input } from '@/components/ui/input';
10 | import { Button } from '@/components/ui/button';
11 | import Empty from '@/components/Empty';
12 | import Loader from '@/components/Loader';
13 |
14 | import { useForm } from 'react-hook-form';
15 | import { z } from 'zod';
16 | import { zodResolver } from '@hookform/resolvers/zod';
17 | import { useProModal } from '@/hooks/useProModal';
18 |
19 | import { Music } from 'lucide-react';
20 | import { formSchema } from './constants';
21 | import toast from 'react-hot-toast';
22 |
23 | const MusicGeneratorPage = () => {
24 | const proModal = useProModal();
25 | const router = Router();
26 | const [music, setMusic] = useState();
27 |
28 | const form = useForm>({
29 | resolver: zodResolver(formSchema),
30 | defaultValues: {
31 | prompt: '',
32 | },
33 | });
34 |
35 | const isLoading = form.formState.isSubmitting;
36 |
37 | const onSubmit = async (values: z.infer) => {
38 | try {
39 | setMusic(undefined);
40 |
41 | const response = await axios.post('/api/music', values);
42 | // console.log(response);
43 |
44 | setMusic(response.data.audio);
45 | form.reset();
46 | } catch (error: any) {
47 | if (error?.response?.status === 403) proModal.onOpen();
48 | else toast.error('Something went wrong!');
49 | } finally {
50 | router.refresh();
51 | }
52 | };
53 |
54 | return (
55 |
56 |
63 |
64 |
93 |
94 | {isLoading && (
95 |
96 |
97 |
98 | )}
99 | {!music && !isLoading &&
}
100 | {music && (
101 |
102 |
103 |
104 | )}
105 |
106 |
107 | );
108 | };
109 |
110 | export default MusicGeneratorPage;
111 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/video/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useState } from 'react';
4 | import { useRouter as Router } from 'next/navigation';
5 | import axios from 'axios';
6 | import toast from 'react-hot-toast';
7 |
8 | import Heading from '@/components/Heading';
9 | import { Form, FormControl, FormField, FormItem } from '@/components/ui/form';
10 | import { Input } from '@/components/ui/input';
11 | import { Button } from '@/components/ui/button';
12 | import Empty from '@/components/Empty';
13 | import Loader from '@/components/Loader';
14 |
15 | import { useForm } from 'react-hook-form';
16 | import { useProModal } from '@/hooks/useProModal';
17 | import { z } from 'zod';
18 | import { zodResolver } from '@hookform/resolvers/zod';
19 |
20 | import { Video } from 'lucide-react';
21 | import { formSchema } from './constants';
22 |
23 | const VideoGeneratorPage = () => {
24 | const proModal = useProModal();
25 | const router = Router();
26 | const [video, setVideo] = useState();
27 |
28 | const form = useForm>({
29 | resolver: zodResolver(formSchema),
30 | defaultValues: {
31 | prompt: '',
32 | },
33 | });
34 |
35 | const isLoading = form.formState.isSubmitting;
36 |
37 | const onSubmit = async (values: z.infer) => {
38 | try {
39 | setVideo(undefined);
40 |
41 | const response = await axios.post('/api/video', values);
42 | // console.log(response);
43 |
44 | setVideo(response.data[0]);
45 | form.reset();
46 | } catch (error: any) {
47 | if (error?.response?.status === 403) proModal.onOpen();
48 | else toast.error('Something went wrong!');
49 | } finally {
50 | router.refresh();
51 | }
52 | };
53 |
54 | return (
55 |
56 |
63 |
64 |
93 |
94 | {isLoading && (
95 |
96 |
97 |
98 | )}
99 | {!video && !isLoading &&
}
100 | {video && (
101 |
105 |
106 |
107 | )}
108 |
109 |
110 | );
111 | };
112 |
113 | export default VideoGeneratorPage;
114 |
--------------------------------------------------------------------------------
/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { X } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = ({
14 | className,
15 | ...props
16 | }: DialogPrimitive.DialogPortalProps) => (
17 |
18 | )
19 | DialogPortal.displayName = DialogPrimitive.Portal.displayName
20 |
21 | const DialogOverlay = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, ...props }, ref) => (
25 |
33 | ))
34 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
35 |
36 | const DialogContent = React.forwardRef<
37 | React.ElementRef,
38 | React.ComponentPropsWithoutRef
39 | >(({ className, children, ...props }, ref) => (
40 |
41 |
42 |
50 | {children}
51 |
52 |
53 | Close
54 |
55 |
56 |
57 | ))
58 | DialogContent.displayName = DialogPrimitive.Content.displayName
59 |
60 | const DialogHeader = ({
61 | className,
62 | ...props
63 | }: React.HTMLAttributes) => (
64 |
71 | )
72 | DialogHeader.displayName = "DialogHeader"
73 |
74 | const DialogFooter = ({
75 | className,
76 | ...props
77 | }: React.HTMLAttributes) => (
78 |
85 | )
86 | DialogFooter.displayName = "DialogFooter"
87 |
88 | const DialogTitle = React.forwardRef<
89 | React.ElementRef,
90 | React.ComponentPropsWithoutRef
91 | >(({ className, ...props }, ref) => (
92 |
100 | ))
101 | DialogTitle.displayName = DialogPrimitive.Title.displayName
102 |
103 | const DialogDescription = React.forwardRef<
104 | React.ElementRef,
105 | React.ComponentPropsWithoutRef
106 | >(({ className, ...props }, ref) => (
107 |
112 | ))
113 | DialogDescription.displayName = DialogPrimitive.Description.displayName
114 |
115 | export {
116 | Dialog,
117 | DialogTrigger,
118 | DialogContent,
119 | DialogHeader,
120 | DialogFooter,
121 | DialogTitle,
122 | DialogDescription,
123 | }
124 |
--------------------------------------------------------------------------------
/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SelectPrimitive from "@radix-ui/react-select"
5 | import { Check, ChevronDown } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Select = SelectPrimitive.Root
10 |
11 | const SelectGroup = SelectPrimitive.Group
12 |
13 | const SelectValue = SelectPrimitive.Value
14 |
15 | const SelectTrigger = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, children, ...props }, ref) => (
19 |
27 | {children}
28 |
29 |
30 |
31 |
32 | ))
33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34 |
35 | const SelectContent = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, children, position = "popper", ...props }, ref) => (
39 |
40 |
51 |
58 | {children}
59 |
60 |
61 |
62 | ))
63 | SelectContent.displayName = SelectPrimitive.Content.displayName
64 |
65 | const SelectLabel = React.forwardRef<
66 | React.ElementRef,
67 | React.ComponentPropsWithoutRef
68 | >(({ className, ...props }, ref) => (
69 |
74 | ))
75 | SelectLabel.displayName = SelectPrimitive.Label.displayName
76 |
77 | const SelectItem = React.forwardRef<
78 | React.ElementRef,
79 | React.ComponentPropsWithoutRef
80 | >(({ className, children, ...props }, ref) => (
81 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | {children}
96 |
97 | ))
98 | SelectItem.displayName = SelectPrimitive.Item.displayName
99 |
100 | const SelectSeparator = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, ...props }, ref) => (
104 |
109 | ))
110 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
111 |
112 | export {
113 | Select,
114 | SelectGroup,
115 | SelectValue,
116 | SelectTrigger,
117 | SelectContent,
118 | SelectLabel,
119 | SelectItem,
120 | SelectSeparator,
121 | }
122 |
--------------------------------------------------------------------------------
/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { Slot } from "@radix-ui/react-slot"
4 | import {
5 | Controller,
6 | ControllerProps,
7 | FieldPath,
8 | FieldValues,
9 | FormProvider,
10 | useFormContext,
11 | } from "react-hook-form"
12 |
13 | import { cn } from "@/lib/utils"
14 | import { Label } from "@/components/ui/label"
15 |
16 | const Form = FormProvider
17 |
18 | type FormFieldContextValue<
19 | TFieldValues extends FieldValues = FieldValues,
20 | TName extends FieldPath = FieldPath
21 | > = {
22 | name: TName
23 | }
24 |
25 | const FormFieldContext = React.createContext(
26 | {} as FormFieldContextValue
27 | )
28 |
29 | const FormField = <
30 | TFieldValues extends FieldValues = FieldValues,
31 | TName extends FieldPath = FieldPath
32 | >({
33 | ...props
34 | }: ControllerProps) => {
35 | return (
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | const useFormField = () => {
43 | const fieldContext = React.useContext(FormFieldContext)
44 | const itemContext = React.useContext(FormItemContext)
45 | const { getFieldState, formState } = useFormContext()
46 |
47 | const fieldState = getFieldState(fieldContext.name, formState)
48 |
49 | if (!fieldContext) {
50 | throw new Error("useFormField should be used within ")
51 | }
52 |
53 | const { id } = itemContext
54 |
55 | return {
56 | id,
57 | name: fieldContext.name,
58 | formItemId: `${id}-form-item`,
59 | formDescriptionId: `${id}-form-item-description`,
60 | formMessageId: `${id}-form-item-message`,
61 | ...fieldState,
62 | }
63 | }
64 |
65 | type FormItemContextValue = {
66 | id: string
67 | }
68 |
69 | const FormItemContext = React.createContext(
70 | {} as FormItemContextValue
71 | )
72 |
73 | const FormItem = React.forwardRef<
74 | HTMLDivElement,
75 | React.HTMLAttributes
76 | >(({ className, ...props }, ref) => {
77 | const id = React.useId()
78 |
79 | return (
80 |
81 |
82 |
83 | )
84 | })
85 | FormItem.displayName = "FormItem"
86 |
87 | const FormLabel = React.forwardRef<
88 | React.ElementRef,
89 | React.ComponentPropsWithoutRef
90 | >(({ className, ...props }, ref) => {
91 | const { error, formItemId } = useFormField()
92 |
93 | return (
94 |
100 | )
101 | })
102 | FormLabel.displayName = "FormLabel"
103 |
104 | const FormControl = React.forwardRef<
105 | React.ElementRef,
106 | React.ComponentPropsWithoutRef
107 | >(({ ...props }, ref) => {
108 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109 |
110 | return (
111 |
122 | )
123 | })
124 | FormControl.displayName = "FormControl"
125 |
126 | const FormDescription = React.forwardRef<
127 | HTMLParagraphElement,
128 | React.HTMLAttributes
129 | >(({ className, ...props }, ref) => {
130 | const { formDescriptionId } = useFormField()
131 |
132 | return (
133 |
139 | )
140 | })
141 | FormDescription.displayName = "FormDescription"
142 |
143 | const FormMessage = React.forwardRef<
144 | HTMLParagraphElement,
145 | React.HTMLAttributes
146 | >(({ className, children, ...props }, ref) => {
147 | const { error, formMessageId } = useFormField()
148 | const body = error ? String(error?.message) : children
149 |
150 | if (!body) {
151 | return null
152 | }
153 |
154 | return (
155 |
161 | {body}
162 |
163 | )
164 | })
165 | FormMessage.displayName = "FormMessage"
166 |
167 | export {
168 | useFormField,
169 | Form,
170 | FormItem,
171 | FormLabel,
172 | FormControl,
173 | FormDescription,
174 | FormMessage,
175 | FormField,
176 | }
177 |
--------------------------------------------------------------------------------
/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SheetPrimitive from "@radix-ui/react-dialog"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 | import { X } from "lucide-react"
7 |
8 | import { cn } from "@/lib/utils"
9 |
10 | const Sheet = SheetPrimitive.Root
11 |
12 | const SheetTrigger = SheetPrimitive.Trigger
13 |
14 | const SheetClose = SheetPrimitive.Close
15 |
16 | const SheetPortal = ({
17 | className,
18 | ...props
19 | }: SheetPrimitive.DialogPortalProps) => (
20 |
21 | )
22 | SheetPortal.displayName = SheetPrimitive.Portal.displayName
23 |
24 | const SheetOverlay = React.forwardRef<
25 | React.ElementRef,
26 | React.ComponentPropsWithoutRef
27 | >(({ className, ...props }, ref) => (
28 |
36 | ))
37 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
38 |
39 | const sheetVariants = cva(
40 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
41 | {
42 | variants: {
43 | side: {
44 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
45 | bottom:
46 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
47 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
48 | right:
49 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
50 | },
51 | },
52 | defaultVariants: {
53 | side: "right",
54 | },
55 | }
56 | )
57 |
58 | interface SheetContentProps
59 | extends React.ComponentPropsWithoutRef,
60 | VariantProps {}
61 |
62 | const SheetContent = React.forwardRef<
63 | React.ElementRef,
64 | SheetContentProps
65 | >(({ side = "right", className, children, ...props }, ref) => (
66 |
67 |
68 |
73 | {children}
74 |
75 |
76 | Close
77 |
78 |
79 |
80 | ))
81 | SheetContent.displayName = SheetPrimitive.Content.displayName
82 |
83 | const SheetHeader = ({
84 | className,
85 | ...props
86 | }: React.HTMLAttributes) => (
87 |
94 | )
95 | SheetHeader.displayName = "SheetHeader"
96 |
97 | const SheetFooter = ({
98 | className,
99 | ...props
100 | }: React.HTMLAttributes) => (
101 |
108 | )
109 | SheetFooter.displayName = "SheetFooter"
110 |
111 | const SheetTitle = React.forwardRef<
112 | React.ElementRef,
113 | React.ComponentPropsWithoutRef
114 | >(({ className, ...props }, ref) => (
115 |
120 | ))
121 | SheetTitle.displayName = SheetPrimitive.Title.displayName
122 |
123 | const SheetDescription = React.forwardRef<
124 | React.ElementRef,
125 | React.ComponentPropsWithoutRef
126 | >(({ className, ...props }, ref) => (
127 |
132 | ))
133 | SheetDescription.displayName = SheetPrimitive.Description.displayName
134 |
135 | export {
136 | Sheet,
137 | SheetTrigger,
138 | SheetClose,
139 | SheetContent,
140 | SheetHeader,
141 | SheetFooter,
142 | SheetTitle,
143 | SheetDescription,
144 | }
145 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/chat/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useState } from 'react';
4 | import { useRouter as Router } from 'next/navigation';
5 | import axios, { AxiosError } from 'axios';
6 | import { toast } from 'react-hot-toast';
7 |
8 | import Heading from '@/components/Heading';
9 | import { Form, FormControl, FormField, FormItem } from '@/components/ui/form';
10 | import { Input } from '@/components/ui/input';
11 | import { Button } from '@/components/ui/button';
12 | import Empty from '@/components/Empty';
13 | import Loader from '@/components/Loader';
14 | import UserAvatar from '@/components/UserAvatar';
15 | import BotAvatar from '@/components/BotAvatar';
16 |
17 | import { useForm } from 'react-hook-form';
18 | import { z } from 'zod';
19 | import { zodResolver } from '@hookform/resolvers/zod';
20 |
21 | import { MessageSquare } from 'lucide-react';
22 | import { formSchema } from './constants';
23 | import { cn } from '@/lib/utils';
24 |
25 | import { ChatCompletionRequestMessage } from 'openai';
26 | import { useProModal } from '@/hooks/useProModal';
27 |
28 | const ChatGeneratorPage = () => {
29 | const router = Router();
30 | const proModal = useProModal();
31 | const [messages, setMessages] = useState([]);
32 |
33 | const form = useForm>({
34 | resolver: zodResolver(formSchema),
35 | defaultValues: {
36 | prompt: '',
37 | },
38 | });
39 |
40 | const isLoading = form.formState.isSubmitting;
41 |
42 | const handleSubmit = async (data: z.infer) => {
43 | try {
44 | const userMessage: ChatCompletionRequestMessage = {
45 | role: 'user',
46 | content: data.prompt,
47 | };
48 | const newMessages: ChatCompletionRequestMessage[] = [
49 | ...messages,
50 | userMessage,
51 | ];
52 |
53 | const response = await axios.post('/api/chat', {
54 | messages: newMessages,
55 | });
56 | setMessages((current: ChatCompletionRequestMessage[]) => [
57 | ...current,
58 | userMessage,
59 | response.data,
60 | ]);
61 | form.reset();
62 | } catch (error: AxiosError | any) {
63 | if (error?.response?.status === 403) proModal.onOpen();
64 | else toast.error('Something went wrong!');
65 | } finally {
66 | router.refresh();
67 | form.reset();
68 | }
69 | };
70 |
71 | return (
72 | <>
73 |
80 |
81 |
82 |
83 |
112 |
113 |
114 |
115 |
116 | {isLoading && (
117 |
118 |
119 |
120 | )}
121 | {messages.length === 0 && !isLoading && (
122 |
123 | )}
124 |
125 | {messages.map((message: ChatCompletionRequestMessage) => (
126 |
135 | {message.role === 'user' ?
:
}
136 |
{message.content}
137 |
138 | ))}
139 |
140 |
141 |
142 | >
143 | );
144 | };
145 |
146 | export default ChatGeneratorPage;
147 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/code/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useState } from 'react';
4 | import { useRouter as Router } from 'next/navigation';
5 | import axios, { AxiosError } from 'axios';
6 |
7 | import Heading from '@/components/Heading';
8 | import { Form, FormControl, FormField, FormItem } from '@/components/ui/form';
9 | import { Input } from '@/components/ui/input';
10 | import { Button } from '@/components/ui/button';
11 | import Empty from '@/components/Empty';
12 | import Loader from '@/components/Loader';
13 | import UserAvatar from '@/components/UserAvatar';
14 | import BotAvatar from '@/components/BotAvatar';
15 |
16 | import { useForm } from 'react-hook-form';
17 | import { z } from 'zod';
18 | import { zodResolver } from '@hookform/resolvers/zod';
19 | import { useProModal } from '@/hooks/useProModal';
20 |
21 | import { Code } from 'lucide-react';
22 | import { formSchema } from './constants';
23 | import { cn } from '@/lib/utils';
24 |
25 | import { ChatCompletionRequestMessage } from 'openai';
26 | import { ReactMarkdown } from 'react-markdown/lib/react-markdown';
27 | import toast from 'react-hot-toast';
28 |
29 | const CodeGeneratorPage = () => {
30 | const router = Router();
31 | const proModal = useProModal();
32 | const [messages, setMessages] = useState([]);
33 |
34 | const form = useForm>({
35 | resolver: zodResolver(formSchema),
36 | defaultValues: {
37 | prompt: '',
38 | },
39 | });
40 |
41 | const isLoading = form.formState.isSubmitting;
42 |
43 | const handleSubmit = async (data: z.infer) => {
44 | try {
45 | const userMessage: ChatCompletionRequestMessage = {
46 | role: 'user',
47 | content: data.prompt,
48 | };
49 | const newMessages: ChatCompletionRequestMessage[] = [
50 | ...messages,
51 | userMessage,
52 | ];
53 |
54 | const response = await axios.post('/api/code', {
55 | messages: newMessages,
56 | });
57 | setMessages((current: ChatCompletionRequestMessage[]) => [
58 | ...current,
59 | userMessage,
60 | response.data,
61 | ]);
62 | form.reset();
63 | } catch (error: AxiosError | any) {
64 | if (error?.response?.status === 403) proModal.onOpen();
65 | else toast.error('Something went wrong!');
66 | } finally {
67 | router.refresh();
68 | form.reset();
69 | }
70 | };
71 |
72 | return (
73 | <>
74 |
81 |
82 |
83 |
84 |
113 |
114 |
115 |
116 |
117 | {isLoading && (
118 |
119 |
120 |
121 | )}
122 | {messages.length === 0 && !isLoading && (
123 |
124 | )}
125 |
126 | {messages.map((message: ChatCompletionRequestMessage) => (
127 |
136 | {message.role === 'user' ?
:
}
137 |
(
140 |
143 | ),
144 | code: ({ node, ...props }) => (
145 |
146 | ),
147 | }}
148 | className='overflow-hidden text-sm leading-7'
149 | >
150 | {message.content || ''}
151 |
152 |
153 | ))}
154 |
155 |
156 |
157 | >
158 | );
159 | };
160 |
161 | export default CodeGeneratorPage;
162 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Drakkar-prompt AI
2 |
3 | An Open source AI Generative web-based app, an unprecedented platform that harnesses the boundless potential of artificial intelligence to ignite your creative spark! From dynamic dialogues to spellbinding videos, captivating imagery, harmonious melodies, sophisticated code generation, and a host of other features, our app offers an expansive array of AI services tailored to amplify your creativity using OpenAI API and Replicate API LLM..
4 |
5 | # Screenshots
6 |
7 | 
8 | 
9 | 
10 | 
11 |
12 | # Demo
13 |
14 | https://drakkar-prompt-ai.vercel.app/
15 |
16 | # Key Features
17 |
18 | ### ChatGPT Service
19 |
20 | Immerse yourself in lifelike conversations with our AI-powered chat service, reminiscent of ChatGPT. Whether seeking companionship or intellectual exchange, our chat function is designed to cater to your conversational needs.
21 |
22 | ### Video Generation
23 |
24 | Embark on a journey of video creation like never before. Our AI-driven video generation service empowers you to craft bespoke videos aligned with your vision and ideas.
25 |
26 | ### Image Creation
27 |
28 | Unleash your inner artist with AI-generated images that defy conventional imagination. Craft visually stunning masterpieces that challenge the limits of artistic expression.
29 |
30 | ### Music Composition
31 |
32 | Let melodies flow seamlessly as our AI composes music resonating with your deepest emotions. From serene tunes to dynamic compositions, our music generation service paves the way for endless auditory exploration.
33 |
34 | ### Code Generation
35 |
36 | Experience the evolution of programming with our AI-backed code generation service. Whether initiating a new project or seeking coding inspiration, our app assists in generating code snippets to expedite your development endeavors.
37 |
38 | ### Pro Plan Upgrade
39 |
40 | Elevate your journey with our Pro plan, offering not only enhanced and unlimited features but also exclusive chat support to ensure you're fully equipped to bring your ideas to life.
41 |
42 | ### Real-time Chat Support
43 |
44 | With integrated real-time chat support, our team of experts is readily available to provide personalized assistance, answer your questions, and guide you through your creative journey.
45 |
46 | # Configuration
47 |
48 | To run D-propmt AI, you'll need to set up the following environment variables:
49 |
50 | ### Clerk Configuration
51 |
52 | For integrating with Clerk, you should provide these keys:
53 |
54 | - `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`: Clerk's public publishable key.
55 | - `CLERK_SECRET_KEY`: Clerk's secret key.
56 | - `NEXT_PUBLIC_CLERK_SIGN_IN_URL`: URL for signing in using Clerk.
57 | - `NEXT_PUBLIC_CLERK_SIGN_UP_URL`: URL for signing up using Clerk.
58 | - `NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL`: URL to redirect after successful sign-in.
59 | - `NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL`: URL to redirect after successful sign-up.
60 |
61 | ### OpenAI Configuration
62 |
63 | For working with OpenAI's API, you'll need:
64 |
65 | - `OPENAI_API_KEY`: Your OpenAI API key.
66 |
67 | ### Replicate AI Configuration
68 |
69 | If you plan to use Replicate AI, you'll need:
70 |
71 | - `REPLICATE_API_TOKEN`: Your Replicate AI API token.
72 |
73 | ### Prisma Database Configuration
74 |
75 | For connecting to your Prisma database, provide:
76 |
77 | - `DATABASE_URL`: URL for your Prisma database.
78 |
79 | ### Stripe Configuration
80 |
81 | If you are integrating Stripe payments, use:
82 |
83 | - `STRIPE_SECRET_KEY`: Your Stripe secret key.
84 | - `STRIPE_WEBHOOK_SECRET`: Your Stripe webhook secret.
85 |
86 | ### Next.js Host and Port Configuration
87 |
88 | For your Next.js application:
89 |
90 | - `NEXT_PUBLIC_HOST`: The URL of your Next.js application.
91 |
92 | ### Crisp Chat Support Configuration
93 |
94 | If you're using Crisp for chat support:
95 |
96 | - `NEXT_PUBLIC_CRISP_WEBSITE_ID`: Your Crisp website ID.
97 |
98 | Remember to keep your environment variables secure and avoid sharing them publicly.
99 |
100 | # Installation
101 |
102 | Install D-promt-AII with yarn
103 |
104 | ```bash
105 | clone this repo
106 | cd my-project
107 | run: yarn or yarn install
108 | run: yarn run dev (to start the project)
109 | ```
110 |
111 | ## Tech Stack
112 | Drakkarrr/D-prompt-AI is built on the following main stack:
113 | - [Stripe](https://stripe.com) – Payment Services
114 | - [Node.js](http://nodejs.org/) – Frameworks (Full Stack)
115 | - [React](https://reactjs.org/) – Javascript UI Libraries
116 | - [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) – Languages
117 | - [TypeScript](http://www.typescriptlang.org) – Languages
118 | - [Autoprefixer](https://github.com/postcss/autoprefixer) – CSS Pre-processors / Extensions
119 | - [ESLint](http://eslint.org/) – Code Review
120 | - [Shell](https://en.wikipedia.org/wiki/Shell_script) – Shells
121 | - [axios](https://github.com/mzabriskie/axios) – Javascript Utilities & Libraries
122 | - [Yarn](https://yarnpkg.com/) – Front End Package Manager
123 | - [Next.js](https://nextjs.org/) – Frameworks (Full Stack)
124 | - [Prettier](https://prettier.io/) – Code Review
125 | - [Prisma](https://www.prisma.io/) – Object Relational Mapper (ORM)
126 | - [Zustand](https://github.com/react-spring/zustand) – State Management Library
127 | - [GitHub Actions](https://github.com/features/actions) – Continuous Integration
128 | - [Docker](https://www.docker.com/) – Virtual Machine Platforms & Containers
129 |
130 |
131 |
132 | ## Contributing
133 |
134 | Contributions are always welcome!
135 |
136 | See `contributing.md` for ways to get started.
137 |
138 | Please adhere to this project's `code of conduct`.
139 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/image/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useState } from 'react';
4 | import { useRouter as Router } from 'next/navigation';
5 | import axios, { AxiosError } from 'axios';
6 |
7 | import Heading from '@/components/Heading';
8 | import { Form, FormControl, FormField, FormItem } from '@/components/ui/form';
9 | import { Input } from '@/components/ui/input';
10 | import { Button } from '@/components/ui/button';
11 | import Empty from '@/components/Empty';
12 | import Loader from '@/components/Loader';
13 | import {
14 | Select,
15 | SelectContent,
16 | SelectItem,
17 | SelectTrigger,
18 | SelectValue,
19 | } from '@/components/ui/select';
20 |
21 | import { useForm } from 'react-hook-form';
22 | import { z } from 'zod';
23 | import { zodResolver } from '@hookform/resolvers/zod';
24 | import { useProModal } from '@/hooks/useProModal';
25 |
26 | import { Download, ImageIcon } from 'lucide-react';
27 | import { amountOptions, formSchema, resolutionOptions } from './constants';
28 | import { Card, CardFooter } from '@/components/ui/card';
29 | import Image from 'next/image';
30 | import toast from 'react-hot-toast';
31 |
32 | const ImageGeneratorPage = () => {
33 | const router = Router();
34 | const proModal = useProModal();
35 | const [images, setImages] = useState([]);
36 |
37 | const form = useForm>({
38 | resolver: zodResolver(formSchema),
39 | defaultValues: {
40 | prompt: '',
41 | amount: '1',
42 | resolution: '512x512',
43 | },
44 | });
45 |
46 | const isLoading = form.formState.isSubmitting;
47 |
48 | const handleSubmit = async (data: z.infer) => {
49 | try {
50 | setImages([]);
51 |
52 | const response = await axios.post('/api/image', data);
53 | const urls = response.data.map((image: { url: string }) => image.url);
54 |
55 | setImages(urls);
56 | form.reset();
57 | } catch (error: AxiosError | any) {
58 | console.log(error);
59 | if (error?.response?.status === 403) proModal.onOpen();
60 | else toast.error('Something went wrong!');
61 | } finally {
62 | router.refresh();
63 | form.reset();
64 | }
65 | };
66 |
67 | return (
68 | <>
69 |
76 |
77 |
78 |
79 |
163 |
164 |
165 |
166 |
167 | {isLoading && (
168 |
169 |
170 |
171 | )}
172 | {images.length === 0 && !isLoading && (
173 |
174 | )}
175 |
176 | {images.map((src) => (
177 |
178 |
179 |
180 |
181 |
182 | window.open(src)}
184 | variant='secondary'
185 | className='w-full'
186 | >
187 |
188 | Download
189 |
190 |
191 |
192 | ))}
193 |
194 |
195 |
196 | >
197 | );
198 | };
199 |
200 | export default ImageGeneratorPage;
201 |
--------------------------------------------------------------------------------