[
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"extends\": \"next/core-web-vitals\"\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n.yarn/install-state.gz\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env*.local\n.env\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).\n\n## Getting Started\n\nFirst, run the development server:\n\n```bash\nnpm run dev\n# or\nyarn dev\n# or\npnpm dev\n# or\nbun dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000) with your browser to see the result.\n\nYou can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.\n\nThis project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.\n\n## Learn More\n\nTo learn more about Next.js, take a look at the following resources:\n\n- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.\n- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.\n\nYou can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!\n\n## Deploy on Vercel\n\nThe easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.\n\nCheck out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.\n\n## Tech\n\n- NextJS 14, RSC, Server actions, useFormStatus,useOptimistic\n- Shadcn-ui\n- Prisma\n- NextAuth\n- OpenAI\n- Postgres\n\n## Preview\n\n<img src=\"./images/dark-hero.png\" alt=\"\" width=\"100%\"/>\n<img src=\"./images/dark-greet.png\" alt=\"\" width=\"100%\"/>\n<img src=\"./images/dark-chat.png\" alt=\"\" width=\"100%\"/>\n<img src=\"./images/light-chat.png\" alt=\"\" width=\"100%\"/>\n\nOld version url - https://nextgpt-old-xyz.vercel.app/auth/login\n"
  },
  {
    "path": "actions/chat.ts",
    "content": "\"use server\";\n\nimport { getUser } from \"@/lib/auth\";\nimport { generateRandomId } from \"@/lib/utils\";\nimport prisma from \"@/prisma/client\";\nimport { JsonMessagesArraySchema } from \"@/types\";\nimport { revalidatePath } from \"next/cache\";\nimport { redirect } from \"next/navigation\";\nimport OpenAI from \"openai\";\n\nexport type Message = {\n  message: string;\n  apiKey: string;\n  conversationId: string;\n};\n\nexport type NewMessage = Omit<Message, \"conversationId\">;\n\nexport async function newChat(params: NewMessage) {\n  const session = await getUser();\n  if (!session?.user) redirect(\"/login\");\n  let id: string | undefined;\n  let error: undefined | { message: string };\n  try {\n    const responseMessage = await createCompletion(\n      params.apiKey,\n      params.message\n    );\n    const newConversationId = generateRandomId(8);\n    const newMessageJson = [\n      {\n        id: newConversationId,\n        question: params.message,\n        answer: responseMessage.message.content,\n      },\n    ];\n    const dataRef = await prisma.conversation.create({\n      data: {\n        messages: newMessageJson,\n        name: params.message,\n        userId: session.user.id,\n      },\n    });\n    id = dataRef.id;\n  } catch (err) {\n    if (err instanceof Error) error = { message: err.message };\n  }\n  console.log(error);\n\n  if (error) return error;\n  redirect(`/chat/${id}`);\n}\n\nexport async function chat(params: Message) {\n  let error: undefined | { message: string };\n  try {\n    const responseMessage = await createCompletion(\n      params.apiKey,\n      params.message\n    );\n    const newConversationId = generateRandomId(8);\n    const dataRef = await prisma.conversation.findUnique({\n      where: {\n        id: params.conversationId,\n      },\n    });\n    const updatedMessageJson = [\n      ...JsonMessagesArraySchema.parse(dataRef?.messages),\n      {\n        id: newConversationId,\n        question: params.message,\n        answer: responseMessage.message.content,\n      },\n    ];\n    await prisma.conversation.update({\n      where: {\n        id: params.conversationId,\n      },\n      data: {\n        messages: updatedMessageJson,\n      },\n    });\n  } catch (err) {\n    if (err instanceof Error) error = { message: err.message };\n  }\n  console.log(error);\n\n  if (error) return error;\n  revalidatePath(`/chat/${params.conversationId}`);\n}\n\ndeclare global {\n  var ai_map: undefined | Map<string, OpenAI>;\n}\n\nconst map = globalThis.ai_map ?? new Map<string, OpenAI>();\n\nasync function createCompletion(apiKey: string, message: string) {\n  let ai: OpenAI;\n  if (map.has(apiKey)) {\n    ai = map.get(apiKey)!;\n  } else {\n    ai = new OpenAI({\n      apiKey,\n    });\n    map.set(apiKey, ai);\n  }\n  const chatCompletion = await ai.chat.completions.create({\n    messages: [{ role: \"user\", content: message }],\n    model: \"gpt-3.5-turbo\",\n  });\n  return chatCompletion.choices[0];\n}\n"
  },
  {
    "path": "app/(private-layout)/chat/[id]/chat.tsx",
    "content": "\"use client\";\n\nimport { chat } from \"@/actions/chat\";\nimport Submit from \"@/components/submit\";\nimport { Input } from \"@/components/ui/input\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { useToast } from \"@/components/ui/use-toast\";\nimport { generateRandomId } from \"@/lib/utils\";\nimport { JSONMessage } from \"@/types\";\nimport { useRouter } from \"next/navigation\";\nimport { ElementRef, useEffect, useOptimistic, useRef } from \"react\";\n\ntype ChatProps = {\n  messages: JSONMessage[];\n  id: string;\n};\n\nexport default function Chat({ messages, id }: ChatProps) {\n  const scrollRef = useRef<ElementRef<\"div\">>(null);\n  const [optimisticMessages, addOptimisticMessage] = useOptimistic(\n    messages,\n    (state, newMessage: string) => [\n      ...state,\n      {\n        answer: undefined,\n        id: generateRandomId(4),\n        question: newMessage,\n      },\n    ]\n  );\n\n  useEffect(() => {\n    scrollRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [optimisticMessages]);\n\n  return (\n    <div className=\"grow\">\n      <div className=\"flex flex-col items-start gap-12 pb-10 min-h-[75vh] sm:w-[95%]\">\n        {optimisticMessages.map((message) => (\n          <div className=\"flex flex-col items-start gap-4 \" key={message.id}>\n            <h4 className=\"text-xl font-medium dark:text-sky-200 text-sky-700\">\n              {message.question}\n            </h4>\n            {!message.answer ? (\n              <div className=\"w-96 flex flex-col gap-3\">\n                <Skeleton className=\"w-[90%] h-[20px] rounded-md\" />\n                <Skeleton className=\"w-[60%] h-[20px] rounded-md\" />\n              </div>\n            ) : (\n              <p className=\"dark:text-slate-300 text-slate-900 whitespace-pre-wrap\">\n                {message.answer}\n              </p>\n            )}\n          </div>\n        ))}\n      </div>\n      <div ref={scrollRef}></div>\n      <div className=\"mt-5 bottom-0 sticky pb-8 pt-1 bg-background\">\n        <ChatInput id={id} addMessage={addOptimisticMessage} />\n      </div>\n    </div>\n  );\n}\n\ntype ConversationComponent = {\n  id: string;\n  addMessage: (msg: string) => void;\n};\n\nfunction ChatInput({ addMessage, id }: ConversationComponent) {\n  const inputRef = useRef<ElementRef<\"input\">>(null);\n  const router = useRouter();\n  const { toast } = useToast();\n\n  async function handleSubmit(formData: FormData) {\n    const message = formData.get(\"message\") as string;\n    if (!message) return;\n    const apiKey = localStorage.getItem(\"apiKey\");\n    if (!apiKey) {\n      toast({\n        title: \"No API key found!\",\n        description: 'Please add API key from \"My account\" section',\n      });\n      return;\n    }\n    if (inputRef.current) {\n      inputRef.current.value = \"\";\n    }\n    addMessage(message);\n    const err = await chat({\n      apiKey,\n      conversationId: id,\n      message,\n    });\n\n    if (err?.message) {\n      toast({\n        title: err.message,\n      });\n    }\n  }\n\n  return (\n    <form\n      action={handleSubmit}\n      className=\"flex flex-row items-center gap-2 sm:pr-5\"\n    >\n      <Input\n        ref={inputRef}\n        autoComplete=\"off\"\n        name=\"message\"\n        placeholder=\"Ask me something...\"\n        className=\"h-12\"\n      />\n      <Submit />\n    </form>\n  );\n}\n"
  },
  {
    "path": "app/(private-layout)/chat/[id]/loading.tsx",
    "content": "import { Skeleton } from \"@/components/ui/skeleton\";\n\nexport default function ChatLoading() {\n  return (\n    <div>\n      <div className=\"flex flex-col gap-12 min-h-[80vh]\">\n        <div className=\"flex flex-col gap-4\">\n          <Skeleton className=\"max-w-[700px] h-[40px] rounded-md\" />\n          <Skeleton className=\"w-[70%] h-[20px] rounded-md\" />\n          <Skeleton className=\"w-[30%] h-[20px] rounded-md\" />\n        </div>\n        <div className=\"flex flex-col gap-4\">\n          <Skeleton className=\"max-w-[700px] h-[40px] rounded-md\" />\n          <Skeleton className=\"w-[82%] h-[20px] rounded-md\" />\n          <Skeleton className=\"w-[45%] h-[20px] rounded-md\" />\n        </div>\n      </div>\n      <div className=\" flex flex-row items-center gap-4 mt-5 bottom-0 sticky pb-8 pt-1 bg-background\">\n        <Skeleton className=\"w-[95%] h-[55px] rounded-md\" />\n        <Skeleton className=\"w-[3%] h-[55px] rounded-md\" />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(private-layout)/chat/[id]/page.tsx",
    "content": "import prisma from \"@/prisma/client\";\nimport { notFound } from \"next/navigation\";\nimport Chat from \"./chat\";\nimport { JsonMessagesArraySchema } from \"@/types\";\n\ntype PageParams = {\n  params: {\n    id: string;\n  };\n};\n\nexport default async function ChatSpecificPage({ params: { id } }: PageParams) {\n  const res = await prisma.conversation.findUnique({\n    where: {\n      id,\n    },\n  });\n  if (!res) return notFound();\n  const parseResult = JsonMessagesArraySchema.parse(res.messages);\n  return <Chat id={id} messages={parseResult} />;\n}\n"
  },
  {
    "path": "app/(private-layout)/chat/input.tsx",
    "content": "\"use client\";\n\nimport Submit from \"@/components/submit\";\nimport { Input } from \"@/components/ui/input\";\nimport { newChat } from \"@/actions/chat\";\nimport { useToast } from \"@/components/ui/use-toast\";\nimport { useRouter } from \"next/navigation\";\n\nexport default function ChatInput() {\n  const router = useRouter();\n  const { toast } = useToast();\n\n  async function handleSubmit(formData: FormData) {\n    const message = formData.get(\"message\") as string;\n    if (!message) return;\n    const apiKey = localStorage.getItem(\"apiKey\");\n    if (!apiKey) {\n      toast({\n        title: \"No API key found!\",\n        description: 'Please add API key from \"My account\" section',\n      });\n      return;\n    }\n    const { message: err } = await newChat({\n      apiKey,\n      message,\n    });\n    if (err) {\n      toast({\n        title: err,\n      });\n    }\n  }\n\n  return (\n    <form\n      action={handleSubmit}\n      className=\"flex flex-row items-center gap-2 sm:pr-5\"\n    >\n      <Input\n        autoComplete=\"off\"\n        name=\"message\"\n        placeholder=\"Ask me something...\"\n        className=\"h-12\"\n      />\n      <Submit />\n    </form>\n  );\n}\n"
  },
  {
    "path": "app/(private-layout)/chat/layout.tsx",
    "content": "import LeftPanel from \"@/components/left-panel\";\nimport { PropsWithChildren } from \"react\";\n\nexport default function ChatLayout({ children }: PropsWithChildren) {\n  return (\n    <div className=\"flex sm:flex-row flex-col items-start sm:gap-12 gap-4 w-full\">\n      <div className=\"sm:sticky bg-background sm:w-fit w-full sm:top-32 sm:mb-0 mb-4\">\n        <LeftPanel />\n      </div>\n      <div className=\"w-full\">{children}</div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(private-layout)/chat/page.tsx",
    "content": "import ChatInput from \"./input\";\n\nexport default function Chat() {\n  return (\n    <div className=\"grow\">\n      <div className=\"flex flex-col items-start gap-4 pb-10 min-h-[75vh] sm:w-[95%]\">\n        <div className=\"text-xl font-medium dark:text-sky-200 text-sky-700\">\n          How can I help you today?\n        </div>\n        <div className=\"dark:text-slate-300 text-slate-900\">\n          ChatGPT can make mistakes. Consider checking important information.\n        </div>\n      </div>\n      <div className=\"mt-5 bottom-0 sticky pb-8 pt-1 bg-background\">\n        <ChatInput />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(private-layout)/layout.tsx",
    "content": "import Navbar from \"@/components/navbar\";\nimport { getUser } from \"@/lib/auth\";\nimport { redirect } from \"next/navigation\";\nimport { PropsWithChildren } from \"react\";\n\nexport default async function PrivateLayout({ children }: PropsWithChildren) {\n  const session = await getUser();\n  if (!session?.user) redirect(\"/login\");\n  return (\n    <div>\n      <Navbar />\n      {children}\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(public-layout)/(auth)/login/page.tsx",
    "content": "import GoogleLogin from \"@/components/google-login\";\n\nexport default function Login() {\n  return (\n    <div className=\"flex flex-col items-center gap-5 justify-center h-[70vh]\">\n      <h3 className=\"text-2xl font-bold text-center\">Login to OpenChat</h3>\n      <GoogleLogin />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(public-layout)/(auth)/register/page.tsx",
    "content": "import GoogleLogin from \"@/components/google-login\";\n\nexport default function Login() {\n  return (\n    <div className=\"flex flex-col items-center gap-5 justify-center h-[70vh]\">\n      <h3 className=\"text-2xl font-bold text-center\">Register to OpenChat</h3>\n      <GoogleLogin />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(public-layout)/(hero)/page.tsx",
    "content": "import { buttonVariants } from \"@/components/ui/button\";\nimport Link from \"next/link\";\n\nexport default function Home() {\n  return (\n    <div className=\"flex flex-col items-center gap-5 justify-center h-[70vh]\">\n      <h3 className=\"text-4xl font-bold text-center\">\n        Welcome to OpenChat! Your Gateway to Intelligent Conversations, Powered\n        by OpenAPI\n      </h3>\n      <p className=\"sm:w-[75%] mx-auto text-center text-muted-foreground \">\n        OpenChat redefines the chat experience by seamlessly integrating the\n        robust capabilities of the OpenAPI. This platform takes conversational\n        AI to new heights, offering you a dynamic and intelligent chat companion\n        designed for a wide array of applications.\n      </p>\n      <Link href=\"/register\" className={buttonVariants({ size: \"lg\" })}>\n        Get started\n      </Link>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(public-layout)/layout.tsx",
    "content": "import HeroNav from \"@/components/hero-nav\";\nimport { getUser } from \"@/lib/auth\";\nimport { redirect } from \"next/navigation\";\nimport { PropsWithChildren } from \"react\";\n\nexport default async function PublicLayout({ children }: PropsWithChildren) {\n  const session = await getUser();\n  if (session?.user) redirect(\"/chat\");\n  return (\n    <div>\n      <HeroNav />\n      {children}\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/api/auth/[...nextauth]/route.ts",
    "content": "import { authConfig } from \"@/lib/auth\";\nimport NextAuth from \"next-auth/next\";\n\nconst handler = NextAuth(authConfig);\n\nexport { handler as GET, handler as POST };\n"
  },
  {
    "path": "app/error.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { useEffect } from \"react\";\n\nexport default function Error({\n  error,\n  reset,\n}: {\n  error: Error & { digest?: string };\n  reset: () => void;\n}) {\n  useEffect(() => {\n    console.error(error.message);\n  }, [error]);\n\n  return (\n    <div className=\"flex flex-col gap-3 items-start pt-6\">\n      <h2 className=\"text-2xl font-semibold\">Something went wrong!</h2>\n      <Button\n        onClick={\n          // Attempt to recover by trying to re-render the segment\n          () => reset()\n        }\n      >\n        Try again\n      </Button>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/globals.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n \n@layer base {\n  :root {\n    --background: 0 0% 100%;\n    --foreground: 222.2 84% 4.9%;\n\n    --card: 0 0% 100%;\n    --card-foreground: 222.2 84% 4.9%;\n \n    --popover: 0 0% 100%;\n    --popover-foreground: 222.2 84% 4.9%;\n \n    --primary: 222.2 47.4% 11.2%;\n    --primary-foreground: 210 40% 98%;\n \n    --secondary: 210 40% 96.1%;\n    --secondary-foreground: 222.2 47.4% 11.2%;\n \n    --muted: 210 40% 96.1%;\n    --muted-foreground: 215.4 16.3% 46.9%;\n \n    --accent: 210 40% 96.1%;\n    --accent-foreground: 222.2 47.4% 11.2%;\n \n    --destructive: 0 84.2% 60.2%;\n    --destructive-foreground: 210 40% 98%;\n\n    --border: 214.3 31.8% 91.4%;\n    --input: 214.3 31.8% 91.4%;\n    --ring: 222.2 84% 4.9%;\n \n    --radius: 0.5rem;\n  }\n \n  .dark {\n    --background: 222.2 84% 4.9%;\n    --foreground: 210 40% 98%;\n \n    --card: 222.2 84% 4.9%;\n    --card-foreground: 210 40% 98%;\n \n    --popover: 222.2 84% 4.9%;\n    --popover-foreground: 210 40% 98%;\n \n    --primary: 210 40% 98%;\n    --primary-foreground: 222.2 47.4% 11.2%;\n \n    --secondary: 217.2 32.6% 17.5%;\n    --secondary-foreground: 210 40% 98%;\n \n    --muted: 217.2 32.6% 17.5%;\n    --muted-foreground: 215 20.2% 65.1%;\n \n    --accent: 217.2 32.6% 17.5%;\n    --accent-foreground: 210 40% 98%;\n \n    --destructive: 0 62.8% 30.6%;\n    --destructive-foreground: 210 40% 98%;\n \n    --border: 217.2 32.6% 17.5%;\n    --input: 217.2 32.6% 17.5%;\n    --ring: 212.7 26.8% 83.9%;\n  }\n}\n \n@layer base {\n  * {\n    @apply border-border;\n  }\n  body {\n    @apply bg-background text-foreground;\n  }\n}"
  },
  {
    "path": "app/layout.tsx",
    "content": "import type { Metadata } from \"next\";\nimport { Space_Grotesk } from \"next/font/google\";\nimport { ThemeProvider } from \"@/components/theme-provider\";\nimport NextAuthProvider from \"@/components/session-provider\";\nimport { Toaster } from \"@/components/ui/toaster\";\nimport \"./globals.css\";\n\nconst font = Space_Grotesk({ subsets: [\"latin\"], weight: \"400\" });\n\nexport const metadata: Metadata = {\n  title: \"OpenChat\",\n  description: \"An OpenAI based chat system\",\n};\n\nexport default function RootLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n  return (\n    <NextAuthProvider>\n      <html lang=\"en\" suppressHydrationWarning>\n        <body className={`${font.className}`} suppressHydrationWarning>\n          <ThemeProvider\n            attribute=\"class\"\n            defaultTheme=\"dark\"\n            enableSystem\n            disableTransitionOnChange\n          >\n            <main className=\"sm:px-10 px-5\">{children}</main>\n            <Toaster />\n          </ThemeProvider>\n        </body>\n      </html>\n    </NextAuthProvider>\n  );\n}\n"
  },
  {
    "path": "app/not-found.tsx",
    "content": "import { buttonVariants } from \"@/components/ui/button\";\nimport Link from \"next/link\";\n\nexport default function NotFoundPage() {\n  return (\n    <div className=\"pt-6\">\n      <div className=\"flex flex-row items-end gap-2 my-2\">\n        <div className=\"text-2xl font-semibold\">404</div>\n        <div>Not found</div>\n      </div>\n      <Link href=\"/\" className={buttonVariants()}>\n        Back to homepage\n      </Link>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/google-login.tsx",
    "content": "\"use client\";\n\nimport { signIn } from \"next-auth/react\";\nimport { Button } from \"./ui/button\";\n\nexport default function GoogleLogin() {\n  return <Button onClick={() => signIn(\"google\")}>Continue with Google</Button>;\n}\n"
  },
  {
    "path": "components/hero-nav.tsx",
    "content": "import Link from \"next/link\";\nimport { NamedLogoWithLink } from \"./logo\";\nimport { buttonVariants } from \"./ui/button\";\nimport ToggleTheme from \"./toggle\";\n\nexport default function HeroNav() {\n  return (\n    <nav className=\"w-full flex flex-row items-center justify-between h-24 mb-7 top-0 sticky bg-background\">\n      <NamedLogoWithLink />\n      <div className=\"flex flex-row items-center\">\n        <ToggleTheme />\n        <Link\n          href=\"/login\"\n          className={buttonVariants({\n            variant: \"link\",\n            className: \"text-base sm:ml-3\",\n            size: \"sm\",\n          })}\n        >\n          Login\n        </Link>\n        <Link\n          href=\"/register\"\n          className={buttonVariants({\n            variant: \"link\",\n            className: \"text-base\",\n            size: \"sm\",\n          })}\n        >\n          Register\n        </Link>\n      </div>\n    </nav>\n  );\n}\n"
  },
  {
    "path": "components/left-panel.tsx",
    "content": "import {\n  Sheet,\n  SheetContent,\n  SheetTrigger,\n  SheetClose,\n} from \"@/components/ui/sheet\";\nimport { PanelLeftIcon } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { Suspense } from \"react\";\nimport { buttonVariants } from \"./ui/button\";\nimport prisma from \"@/prisma/client\";\nimport { getUser } from \"@/lib/auth\";\nimport { ScrollArea } from \"./ui/scroll-area\";\n\nexport default function LeftPanel() {\n  return (\n    <Sheet>\n      <SheetTrigger>\n        <div className=\"flex flex-row items-center gap-2\">\n          <PanelLeftIcon className=\"w-5 h-5 mt-1\" />\n          <span className=\"mt-1 sm:hidden flex\">Menu</span>\n        </div>\n      </SheetTrigger>\n      <SheetContent side=\"left\" className=\"min-w-[390px] px-0\">\n        <div>\n          <h3 className=\"px-7 text-xl font-semibold\">Conversations</h3>\n          <Suspense\n            fallback={\n              <p className={buttonVariants({ variant: \"link\" })}>Loading...</p>\n            }\n          >\n            <ConversationList />\n          </Suspense>\n        </div>\n      </SheetContent>\n    </Sheet>\n  );\n}\n\nasync function ConversationList() {\n  const session = await getUser();\n  if (!session?.user) return null;\n  const res = await prisma.user.findUnique({\n    where: {\n      id: session.user.id,\n    },\n    include: {\n      conversations: {\n        orderBy: {\n          createdAt: \"desc\",\n        },\n      },\n    },\n  });\n\n  if(!res) return null;\n  const { conversations } = res;\n  return (\n    <ScrollArea className=\"flex flex-col mt-7 items-start overflow-y-auto h-[90vh] pb-5\">\n      {conversations.map((cn) => (\n        <SheetClose asChild key={cn.id}>\n          <Link\n            href={`/chat/${cn.id}`}\n            className=\"w-full my-3 px-8 hover:underline underline-offset-2\"\n          >\n            {cn.name.length > 35 ? cn.name.slice(0, 35) + \"...\" : cn.name}\n          </Link>\n        </SheetClose>\n      ))}\n    </ScrollArea>\n  );\n}\n"
  },
  {
    "path": "components/logo.tsx",
    "content": "import Link from \"next/link\";\n\nexport function Logo({\n  height = \"32\",\n  width = \"32\",\n}: {\n  height?: string;\n  width?: string;\n}) {\n  return (\n    <svg\n      width={width}\n      height={height}\n      viewBox=\"0 0 41 41\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      strokeWidth=\"1.5\"\n    >\n      <path\n        d=\"M37.5324 16.8707C37.9808 15.5241 38.1363 14.0974 37.9886 12.6859C37.8409 11.2744 37.3934 9.91076 36.676 8.68622C35.6126 6.83404 33.9882 5.3676 32.0373 4.4985C30.0864 3.62941 27.9098 3.40259 25.8215 3.85078C24.8796 2.7893 23.7219 1.94125 22.4257 1.36341C21.1295 0.785575 19.7249 0.491269 18.3058 0.500197C16.1708 0.495044 14.0893 1.16803 12.3614 2.42214C10.6335 3.67624 9.34853 5.44666 8.6917 7.47815C7.30085 7.76286 5.98686 8.3414 4.8377 9.17505C3.68854 10.0087 2.73073 11.0782 2.02839 12.312C0.956464 14.1591 0.498905 16.2988 0.721698 18.4228C0.944492 20.5467 1.83612 22.5449 3.268 24.1293C2.81966 25.4759 2.66413 26.9026 2.81182 28.3141C2.95951 29.7256 3.40701 31.0892 4.12437 32.3138C5.18791 34.1659 6.8123 35.6322 8.76321 36.5013C10.7141 37.3704 12.8907 37.5973 14.9789 37.1492C15.9208 38.2107 17.0786 39.0587 18.3747 39.6366C19.6709 40.2144 21.0755 40.5087 22.4946 40.4998C24.6307 40.5054 26.7133 39.8321 28.4418 38.5772C30.1704 37.3223 31.4556 35.5506 32.1119 33.5179C33.5027 33.2332 34.8167 32.6547 35.9659 31.821C37.115 30.9874 38.0728 29.9178 38.7752 28.684C39.8458 26.8371 40.3023 24.6979 40.0789 22.5748C39.8556 20.4517 38.9639 18.4544 37.5324 16.8707ZM22.4978 37.8849C20.7443 37.8874 19.0459 37.2733 17.6994 36.1501C17.7601 36.117 17.8666 36.0586 17.936 36.0161L25.9004 31.4156C26.1003 31.3019 26.2663 31.137 26.3813 30.9378C26.4964 30.7386 26.5563 30.5124 26.5549 30.2825V19.0542L29.9213 20.998C29.9389 21.0068 29.9541 21.0198 29.9656 21.0359C29.977 21.052 29.9842 21.0707 29.9867 21.0902V30.3889C29.9842 32.375 29.1946 34.2791 27.7909 35.6841C26.3872 37.0892 24.4838 37.8806 22.4978 37.8849ZM6.39227 31.0064C5.51397 29.4888 5.19742 27.7107 5.49804 25.9832C5.55718 26.0187 5.66048 26.0818 5.73461 26.1244L13.699 30.7248C13.8975 30.8408 14.1233 30.902 14.3532 30.902C14.583 30.902 14.8088 30.8408 15.0073 30.7248L24.731 25.1103V28.9979C24.7321 29.0177 24.7283 29.0376 24.7199 29.0556C24.7115 29.0736 24.6988 29.0893 24.6829 29.1012L16.6317 33.7497C14.9096 34.7416 12.8643 35.0097 10.9447 34.4954C9.02506 33.9811 7.38785 32.7263 6.39227 31.0064ZM4.29707 13.6194C5.17156 12.0998 6.55279 10.9364 8.19885 10.3327C8.19885 10.4013 8.19491 10.5228 8.19491 10.6071V19.808C8.19351 20.0378 8.25334 20.2638 8.36823 20.4629C8.48312 20.6619 8.64893 20.8267 8.84863 20.9404L18.5723 26.5542L15.206 28.4979C15.1894 28.5089 15.1703 28.5155 15.1505 28.5173C15.1307 28.5191 15.1107 28.516 15.0924 28.5082L7.04046 23.8557C5.32135 22.8601 4.06716 21.2235 3.55289 19.3046C3.03862 17.3858 3.30624 15.3413 4.29707 13.6194ZM31.955 20.0556L22.2312 14.4411L25.5976 12.4981C25.6142 12.4872 25.6333 12.4805 25.6531 12.4787C25.6729 12.4769 25.6928 12.4801 25.7111 12.4879L33.7631 17.1364C34.9967 17.849 36.0017 18.8982 36.6606 20.1613C37.3194 21.4244 37.6047 22.849 37.4832 24.2684C37.3617 25.6878 36.8382 27.0432 35.9743 28.1759C35.1103 29.3086 33.9415 30.1717 32.6047 30.6641C32.6047 30.5947 32.6047 30.4733 32.6047 30.3889V21.188C32.6066 20.9586 32.5474 20.7328 32.4332 20.5338C32.319 20.3348 32.154 20.1698 31.955 20.0556ZM35.3055 15.0128C35.2464 14.9765 35.1431 14.9142 35.069 14.8717L27.1045 10.2712C26.906 10.1554 26.6803 10.0943 26.4504 10.0943C26.2206 10.0943 25.9948 10.1554 25.7963 10.2712L16.0726 15.8858V11.9982C16.0715 11.9783 16.0753 11.9585 16.0837 11.9405C16.0921 11.9225 16.1048 11.9068 16.1207 11.8949L24.1719 7.25025C25.4053 6.53903 26.8158 6.19376 28.2383 6.25482C29.6608 6.31589 31.0364 6.78077 32.2044 7.59508C33.3723 8.40939 34.2842 9.53945 34.8334 10.8531C35.3826 12.1667 35.5464 13.6095 35.3055 15.0128ZM14.2424 21.9419L10.8752 19.9981C10.8576 19.9893 10.8423 19.9763 10.8309 19.9602C10.8195 19.9441 10.8122 19.9254 10.8098 19.9058V10.6071C10.8107 9.18295 11.2173 7.78848 11.9819 6.58696C12.7466 5.38544 13.8377 4.42659 15.1275 3.82264C16.4173 3.21869 17.8524 2.99464 19.2649 3.1767C20.6775 3.35876 22.0089 3.93941 23.1034 4.85067C23.0427 4.88379 22.937 4.94215 22.8668 4.98473L14.9024 9.58517C14.7025 9.69878 14.5366 9.86356 14.4215 10.0626C14.3065 10.2616 14.2466 10.4877 14.2479 10.7175L14.2424 21.9419ZM16.071 17.9991L20.4018 15.4978L24.7325 17.9975V22.9985L20.4018 25.4983L16.071 22.9985V17.9991Z\"\n        fill=\"currentColor\"\n      ></path>\n    </svg>\n  );\n}\n\nexport function NamedLogoWithLink() {\n  return (\n    <Link href=\"/\" className=\"flex flex-row items-center gap-3\">\n      <Logo height=\"24\" width=\"24\" />\n      <h3 className=\"font-semibold text-lg\">OpenChat</h3>\n    </Link>\n  );\n}\n"
  },
  {
    "path": "components/navbar.tsx",
    "content": "import Link from \"next/link\";\nimport { buttonVariants } from \"./ui/button\";\nimport ToggleTheme from \"./toggle\";\nimport { NamedLogoWithLink } from \"./logo\";\nimport Profile from \"./profile\";\nimport { SquarePen } from \"lucide-react\";\n\nconst btnVariant = buttonVariants({\n  variant: \"link\",\n  className: \"text-base flex flex-row item-center\",\n  size: \"sm\",\n});\n\nexport default function Navbar() {\n  return (\n    <nav className=\"w-full flex flex-row items-center justify-between h-24 sm:mb-7 mmb-2 top-0 sticky bg-background\">\n      <NamedLogoWithLink />\n      <div className=\"flex-row items-center flex\">\n        <ToggleTheme />\n        <div className=\"sm:ml-3 flex flex-row items-center\">\n          <Link href=\"/chat\" className={btnVariant}>\n            <SquarePen className=\"w-5 h-5 sm:hidden flex\" />\n            <span className=\"sm:flex hidden\">New chat</span>\n          </Link>\n          <Profile />\n        </div>\n      </div>\n    </nav>\n  );\n}\n"
  },
  {
    "path": "components/profile.tsx",
    "content": "import { getUser } from \"@/lib/auth\";\nimport { Button } from \"./ui/button\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport UserApi from \"./user-api\";\nimport { UserIcon } from \"lucide-react\";\n\nexport default async function Profile() {\n  const session = await getUser();\n  if (!session?.user) return null;\n  return (\n    <div>\n      <Dialog>\n        <DialogTrigger asChild>\n          <Button variant=\"link\" size=\"sm\">\n            <UserIcon className=\"w-5 h-5 sm:hidden flex\" />\n            <span className=\"sm:flex hidden\">My account</span>\n          </Button>\n        </DialogTrigger>\n        <DialogContent>\n          <DialogHeader>\n            <DialogTitle>My account</DialogTitle>\n            <div className=\"pt-8 pb-4 flex flex-col gap-6\">\n              <div className=\"grid w-full items-center gap-1.5\">\n                <Label htmlFor=\"email\">Email</Label>\n                <Input id=\"email\" disabled value={session.user.email ?? \"\"} />\n              </div>\n              <div className=\"grid w-full items-center gap-1.5\">\n                <Label htmlFor=\"username\">Username</Label>\n                <Input disabled id=\"username\" value={session.user.name ?? \"\"} />\n              </div>\n              <UserApi />\n            </div>\n          </DialogHeader>\n        </DialogContent>\n      </Dialog>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/session-provider.tsx",
    "content": "\"use client\";\n\nimport { SessionProvider } from \"next-auth/react\";\nimport { PropsWithChildren } from \"react\";\n\nexport default function NextAuthProvider({ children }: PropsWithChildren) {\n  return <SessionProvider>{children}</SessionProvider>;\n}\n"
  },
  {
    "path": "components/signout-btn.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { signOut } from \"next-auth/react\";\n\nexport default function SignOutButton() {\n  return (\n    <Button\n      className=\"w-24\"\n      variant=\"destructive\"\n      onClick={() => {\n        signOut();\n        localStorage.removeItem(\"apiKey\");\n      }}\n    >\n      Signout\n    </Button>\n  );\n}\n"
  },
  {
    "path": "components/submit.tsx",
    "content": "\"use client\";\n\nimport { useFormStatus } from \"react-dom\";\nimport { Button } from \"./ui/button\";\nimport { Loader2Icon, SendHorizonalIcon } from \"lucide-react\";\n\nexport default function Submit() {\n  const { pending } = useFormStatus();\n  return (\n    <Button type=\"submit\" variant=\"secondary\" size=\"icon\" className=\"h-12 w-12\">\n      {pending ? (\n        <Loader2Icon className=\"w-5 h-5 animate-spin\" />\n      ) : (\n        <SendHorizonalIcon className=\"w-5 h-5\" />\n      )}\n    </Button>\n  );\n}\n"
  },
  {
    "path": "components/theme-provider.tsx",
    "content": "\"use client\";\n\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\";\nimport { type ThemeProviderProps } from \"next-themes/dist/types\";\n\nexport function ThemeProvider({ children, ...props }: ThemeProviderProps) {\n  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;\n}\n"
  },
  {
    "path": "components/toggle.tsx",
    "content": "\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport { Button } from \"@/components/ui/button\";\nimport { MoonIcon, SunIcon } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\n\nexport default function ToggleTheme() {\n  const [mounted, setMounted] = useState(false);\n  const { setTheme, theme } = useTheme();\n\n  useEffect(() => {\n    setMounted(true);\n  }, []);\n\n  if (!mounted) return null;\n\n  return (\n    <>\n      {theme == \"light\" ? (\n        <Button variant=\"ghost\" size=\"icon\" onClick={() => setTheme(\"dark\")}>\n          <MoonIcon className=\"w-5 h-5\" />\n        </Button>\n      ) : (\n        <Button variant=\"ghost\" size=\"icon\" onClick={() => setTheme(\"light\")}>\n          <SunIcon className=\"w-5 h-5\" />\n        </Button>\n      )}\n    </>\n  );\n}\n"
  },
  {
    "path": "components/ui/button.tsx",
    "content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst buttonVariants = cva(\n  \"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n        outline:\n          \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost: \"hover:bg-accent hover:text-accent-foreground\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-10 px-4 py-2\",\n        sm: \"h-9 rounded-md px-3\",\n        lg: \"h-11 rounded-md px-8\",\n        icon: \"h-10 w-10\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nexport interface ButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof buttonVariants> {\n  asChild?: boolean\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, variant, size, asChild = false, ...props }, ref) => {\n    const Comp = asChild ? Slot : \"button\"\n    return (\n      <Comp\n        className={cn(buttonVariants({ variant, size, className }))}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants }\n"
  },
  {
    "path": "components/ui/dialog.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { X } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Dialog = DialogPrimitive.Root;\n\nconst DialogTrigger = DialogPrimitive.Trigger;\n\nconst DialogPortal = DialogPrimitive.Portal;\n\nconst DialogClose = DialogPrimitive.Close;\n\nconst DialogOverlay = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Overlay\n    ref={ref}\n    className={cn(\n      \"fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n      className\n    )}\n    {...props}\n  />\n));\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName;\n\nconst DialogContent = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n  <DialogPortal>\n    <DialogOverlay />\n    <DialogPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <DialogPrimitive.Close className=\"absolute right-4 top-7 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground\">\n        <X className=\"h-4 w-4\" />\n        <span className=\"sr-only\">Close</span>\n      </DialogPrimitive.Close>\n    </DialogPrimitive.Content>\n  </DialogPortal>\n));\nDialogContent.displayName = DialogPrimitive.Content.displayName;\n\nconst DialogHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-1.5 text-center sm:text-left\",\n      className\n    )}\n    {...props}\n  />\n);\nDialogHeader.displayName = \"DialogHeader\";\n\nconst DialogFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className\n    )}\n    {...props}\n  />\n);\nDialogFooter.displayName = \"DialogFooter\";\n\nconst DialogTitle = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Title\n    ref={ref}\n    className={cn(\n      \"text-lg font-semibold leading-none tracking-tight\",\n      className\n    )}\n    {...props}\n  />\n));\nDialogTitle.displayName = DialogPrimitive.Title.displayName;\n\nconst DialogDescription = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n));\nDialogDescription.displayName = DialogPrimitive.Description.displayName;\n\nexport {\n  Dialog,\n  DialogPortal,\n  DialogOverlay,\n  DialogClose,\n  DialogTrigger,\n  DialogContent,\n  DialogHeader,\n  DialogFooter,\n  DialogTitle,\n  DialogDescription,\n};\n"
  },
  {
    "path": "components/ui/input.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface InputProps\n  extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n  ({ className, type, ...props }, ref) => {\n    return (\n      <input\n        type={type}\n        className={cn(\n          \"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\",\n          className\n        )}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nInput.displayName = \"Input\"\n\nexport { Input }\n"
  },
  {
    "path": "components/ui/label.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst labelVariants = cva(\n  \"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n)\n\nconst Label = React.forwardRef<\n  React.ElementRef<typeof LabelPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &\n    VariantProps<typeof labelVariants>\n>(({ className, ...props }, ref) => (\n  <LabelPrimitive.Root\n    ref={ref}\n    className={cn(labelVariants(), className)}\n    {...props}\n  />\n))\nLabel.displayName = LabelPrimitive.Root.displayName\n\nexport { Label }\n"
  },
  {
    "path": "components/ui/scroll-area.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst ScrollArea = React.forwardRef<\n  React.ElementRef<typeof ScrollAreaPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>\n>(({ className, children, ...props }, ref) => (\n  <ScrollAreaPrimitive.Root\n    ref={ref}\n    className={cn(\"relative overflow-hidden\", className)}\n    {...props}\n  >\n    <ScrollAreaPrimitive.Viewport className=\"h-full w-full rounded-[inherit]\">\n      {children}\n    </ScrollAreaPrimitive.Viewport>\n    <ScrollBar />\n    <ScrollAreaPrimitive.Corner />\n  </ScrollAreaPrimitive.Root>\n))\nScrollArea.displayName = ScrollAreaPrimitive.Root.displayName\n\nconst ScrollBar = React.forwardRef<\n  React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,\n  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>\n>(({ className, orientation = \"vertical\", ...props }, ref) => (\n  <ScrollAreaPrimitive.ScrollAreaScrollbar\n    ref={ref}\n    orientation={orientation}\n    className={cn(\n      \"flex touch-none select-none transition-colors\",\n      orientation === \"vertical\" &&\n        \"h-full w-2.5 border-l border-l-transparent p-[1px]\",\n      orientation === \"horizontal\" &&\n        \"h-2.5 flex-col border-t border-t-transparent p-[1px]\",\n      className\n    )}\n    {...props}\n  >\n    <ScrollAreaPrimitive.ScrollAreaThumb className=\"relative flex-1 rounded-full bg-border\" />\n  </ScrollAreaPrimitive.ScrollAreaScrollbar>\n))\nScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName\n\nexport { ScrollArea, ScrollBar }\n"
  },
  {
    "path": "components/ui/sheet.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { X } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Sheet = SheetPrimitive.Root;\n\nconst SheetTrigger = SheetPrimitive.Trigger;\n\nconst SheetClose = SheetPrimitive.Close;\n\nconst SheetPortal = SheetPrimitive.Portal;\n\nconst SheetOverlay = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <SheetPrimitive.Overlay\n    className={cn(\n      \"fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  />\n));\nSheetOverlay.displayName = SheetPrimitive.Overlay.displayName;\n\nconst sheetVariants = cva(\n  \"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\",\n  {\n    variants: {\n      side: {\n        top: \"inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n        bottom:\n          \"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n        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\",\n        right:\n          \"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\",\n      },\n    },\n    defaultVariants: {\n      side: \"right\",\n    },\n  }\n);\n\ninterface SheetContentProps\n  extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,\n    VariantProps<typeof sheetVariants> {}\n\nconst SheetContent = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Content>,\n  SheetContentProps\n>(({ side = \"right\", className, children, ...props }, ref) => (\n  <SheetPortal>\n    <SheetOverlay />\n    <SheetPrimitive.Content\n      ref={ref}\n      className={cn(sheetVariants({ side }), className)}\n      {...props}\n    >\n      {children}\n      <SheetPrimitive.Close className=\"absolute right-4 top-8 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary\">\n        <X className=\"h-4 w-4\" />\n        <span className=\"sr-only\">Close</span>\n      </SheetPrimitive.Close>\n    </SheetPrimitive.Content>\n  </SheetPortal>\n));\nSheetContent.displayName = SheetPrimitive.Content.displayName;\n\nconst SheetHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-2 text-center sm:text-left\",\n      className\n    )}\n    {...props}\n  />\n);\nSheetHeader.displayName = \"SheetHeader\";\n\nconst SheetFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className\n    )}\n    {...props}\n  />\n);\nSheetFooter.displayName = \"SheetFooter\";\n\nconst SheetTitle = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <SheetPrimitive.Title\n    ref={ref}\n    className={cn(\"text-lg font-semibold text-foreground\", className)}\n    {...props}\n  />\n));\nSheetTitle.displayName = SheetPrimitive.Title.displayName;\n\nconst SheetDescription = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <SheetPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n));\nSheetDescription.displayName = SheetPrimitive.Description.displayName;\n\nexport {\n  Sheet,\n  SheetPortal,\n  SheetOverlay,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n};\n"
  },
  {
    "path": "components/ui/skeleton.tsx",
    "content": "import { cn } from \"@/lib/utils\"\n\nfunction Skeleton({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) {\n  return (\n    <div\n      className={cn(\"animate-pulse rounded-md bg-muted\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Skeleton }\n"
  },
  {
    "path": "components/ui/toast.tsx",
    "content": "import * as React from \"react\"\nimport * as ToastPrimitives from \"@radix-ui/react-toast\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst ToastProvider = ToastPrimitives.Provider\n\nconst ToastViewport = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Viewport>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Viewport\n    ref={ref}\n    className={cn(\n      \"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]\",\n      className\n    )}\n    {...props}\n  />\n))\nToastViewport.displayName = ToastPrimitives.Viewport.displayName\n\nconst toastVariants = cva(\n  \"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full\",\n  {\n    variants: {\n      variant: {\n        default: \"border bg-background text-foreground\",\n        destructive:\n          \"destructive group border-destructive bg-destructive text-destructive-foreground\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nconst Toast = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Root>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &\n    VariantProps<typeof toastVariants>\n>(({ className, variant, ...props }, ref) => {\n  return (\n    <ToastPrimitives.Root\n      ref={ref}\n      className={cn(toastVariants({ variant }), className)}\n      {...props}\n    />\n  )\n})\nToast.displayName = ToastPrimitives.Root.displayName\n\nconst ToastAction = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Action>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Action\n    ref={ref}\n    className={cn(\n      \"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive\",\n      className\n    )}\n    {...props}\n  />\n))\nToastAction.displayName = ToastPrimitives.Action.displayName\n\nconst ToastClose = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Close>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Close\n    ref={ref}\n    className={cn(\n      \"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600\",\n      className\n    )}\n    toast-close=\"\"\n    {...props}\n  >\n    <X className=\"h-4 w-4\" />\n  </ToastPrimitives.Close>\n))\nToastClose.displayName = ToastPrimitives.Close.displayName\n\nconst ToastTitle = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Title>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Title\n    ref={ref}\n    className={cn(\"text-sm font-semibold\", className)}\n    {...props}\n  />\n))\nToastTitle.displayName = ToastPrimitives.Title.displayName\n\nconst ToastDescription = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Description>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Description\n    ref={ref}\n    className={cn(\"text-sm opacity-90\", className)}\n    {...props}\n  />\n))\nToastDescription.displayName = ToastPrimitives.Description.displayName\n\ntype ToastProps = React.ComponentPropsWithoutRef<typeof Toast>\n\ntype ToastActionElement = React.ReactElement<typeof ToastAction>\n\nexport {\n  type ToastProps,\n  type ToastActionElement,\n  ToastProvider,\n  ToastViewport,\n  Toast,\n  ToastTitle,\n  ToastDescription,\n  ToastClose,\n  ToastAction,\n}\n"
  },
  {
    "path": "components/ui/toaster.tsx",
    "content": "\"use client\"\n\nimport {\n  Toast,\n  ToastClose,\n  ToastDescription,\n  ToastProvider,\n  ToastTitle,\n  ToastViewport,\n} from \"@/components/ui/toast\"\nimport { useToast } from \"@/components/ui/use-toast\"\n\nexport function Toaster() {\n  const { toasts } = useToast()\n\n  return (\n    <ToastProvider>\n      {toasts.map(function ({ id, title, description, action, ...props }) {\n        return (\n          <Toast key={id} {...props}>\n            <div className=\"grid gap-1\">\n              {title && <ToastTitle>{title}</ToastTitle>}\n              {description && (\n                <ToastDescription>{description}</ToastDescription>\n              )}\n            </div>\n            {action}\n            <ToastClose />\n          </Toast>\n        )\n      })}\n      <ToastViewport />\n    </ToastProvider>\n  )\n}\n"
  },
  {
    "path": "components/ui/use-toast.ts",
    "content": "// Inspired by react-hot-toast library\nimport * as React from \"react\"\n\nimport type {\n  ToastActionElement,\n  ToastProps,\n} from \"@/components/ui/toast\"\n\nconst TOAST_LIMIT = 1\nconst TOAST_REMOVE_DELAY = 1000000\n\ntype ToasterToast = ToastProps & {\n  id: string\n  title?: React.ReactNode\n  description?: React.ReactNode\n  action?: ToastActionElement\n}\n\nconst actionTypes = {\n  ADD_TOAST: \"ADD_TOAST\",\n  UPDATE_TOAST: \"UPDATE_TOAST\",\n  DISMISS_TOAST: \"DISMISS_TOAST\",\n  REMOVE_TOAST: \"REMOVE_TOAST\",\n} as const\n\nlet count = 0\n\nfunction genId() {\n  count = (count + 1) % Number.MAX_SAFE_INTEGER\n  return count.toString()\n}\n\ntype ActionType = typeof actionTypes\n\ntype Action =\n  | {\n      type: ActionType[\"ADD_TOAST\"]\n      toast: ToasterToast\n    }\n  | {\n      type: ActionType[\"UPDATE_TOAST\"]\n      toast: Partial<ToasterToast>\n    }\n  | {\n      type: ActionType[\"DISMISS_TOAST\"]\n      toastId?: ToasterToast[\"id\"]\n    }\n  | {\n      type: ActionType[\"REMOVE_TOAST\"]\n      toastId?: ToasterToast[\"id\"]\n    }\n\ninterface State {\n  toasts: ToasterToast[]\n}\n\nconst toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()\n\nconst addToRemoveQueue = (toastId: string) => {\n  if (toastTimeouts.has(toastId)) {\n    return\n  }\n\n  const timeout = setTimeout(() => {\n    toastTimeouts.delete(toastId)\n    dispatch({\n      type: \"REMOVE_TOAST\",\n      toastId: toastId,\n    })\n  }, TOAST_REMOVE_DELAY)\n\n  toastTimeouts.set(toastId, timeout)\n}\n\nexport const reducer = (state: State, action: Action): State => {\n  switch (action.type) {\n    case \"ADD_TOAST\":\n      return {\n        ...state,\n        toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),\n      }\n\n    case \"UPDATE_TOAST\":\n      return {\n        ...state,\n        toasts: state.toasts.map((t) =>\n          t.id === action.toast.id ? { ...t, ...action.toast } : t\n        ),\n      }\n\n    case \"DISMISS_TOAST\": {\n      const { toastId } = action\n\n      // ! Side effects ! - This could be extracted into a dismissToast() action,\n      // but I'll keep it here for simplicity\n      if (toastId) {\n        addToRemoveQueue(toastId)\n      } else {\n        state.toasts.forEach((toast) => {\n          addToRemoveQueue(toast.id)\n        })\n      }\n\n      return {\n        ...state,\n        toasts: state.toasts.map((t) =>\n          t.id === toastId || toastId === undefined\n            ? {\n                ...t,\n                open: false,\n              }\n            : t\n        ),\n      }\n    }\n    case \"REMOVE_TOAST\":\n      if (action.toastId === undefined) {\n        return {\n          ...state,\n          toasts: [],\n        }\n      }\n      return {\n        ...state,\n        toasts: state.toasts.filter((t) => t.id !== action.toastId),\n      }\n  }\n}\n\nconst listeners: Array<(state: State) => void> = []\n\nlet memoryState: State = { toasts: [] }\n\nfunction dispatch(action: Action) {\n  memoryState = reducer(memoryState, action)\n  listeners.forEach((listener) => {\n    listener(memoryState)\n  })\n}\n\ntype Toast = Omit<ToasterToast, \"id\">\n\nfunction toast({ ...props }: Toast) {\n  const id = genId()\n\n  const update = (props: ToasterToast) =>\n    dispatch({\n      type: \"UPDATE_TOAST\",\n      toast: { ...props, id },\n    })\n  const dismiss = () => dispatch({ type: \"DISMISS_TOAST\", toastId: id })\n\n  dispatch({\n    type: \"ADD_TOAST\",\n    toast: {\n      ...props,\n      id,\n      open: true,\n      onOpenChange: (open) => {\n        if (!open) dismiss()\n      },\n    },\n  })\n\n  return {\n    id: id,\n    dismiss,\n    update,\n  }\n}\n\nfunction useToast() {\n  const [state, setState] = React.useState<State>(memoryState)\n\n  React.useEffect(() => {\n    listeners.push(setState)\n    return () => {\n      const index = listeners.indexOf(setState)\n      if (index > -1) {\n        listeners.splice(index, 1)\n      }\n    }\n  }, [state])\n\n  return {\n    ...state,\n    toast,\n    dismiss: (toastId?: string) => dispatch({ type: \"DISMISS_TOAST\", toastId }),\n  }\n}\n\nexport { useToast, toast }\n"
  },
  {
    "path": "components/user-api.tsx",
    "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Input } from \"./ui/input\";\nimport { Label } from \"./ui/label\";\nimport { Button } from \"./ui/button\";\nimport SignOutButton from \"@/components/signout-btn\";\nimport { useToast } from \"./ui/use-toast\";\n\nexport default function UserApi() {\n  const [apiKey, setApiKey] = useState(\"\");\n  const { toast } = useToast();\n\n  useEffect(() => {\n    if (window) {\n      const value = localStorage.getItem(\"apiKey\") ?? \"\";\n      setApiKey(value);\n    }\n  }, []);\n\n  function handleSave() {\n    localStorage.setItem(\"apiKey\", apiKey);\n    toast({\n      title: \"Saved profile\",\n    });\n  }\n\n  return (\n    <>\n      <div className=\"grid w-full items-center gap-1.5\">\n        <Label htmlFor=\"api\">OpneAI Key</Label>\n        <Input\n          id=\"api\"\n          value={apiKey}\n          onChange={(e) => setApiKey(e.target.value)}\n        />\n      </div>\n      <div className=\"flex flex-row items-center gap-2\">\n        <Button className=\"w-24\" onClick={handleSave}>\n          Save\n        </Button>\n        <SignOutButton />\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "components.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"default\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"tailwind.config.ts\",\n    \"css\": \"app/globals.css\",\n    \"baseColor\": \"slate\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"aliases\": {\n    \"components\": \"@/components\",\n    \"utils\": \"@/lib/utils\"\n  }\n}"
  },
  {
    "path": "lib/auth.ts",
    "content": "import { AuthOptions, DefaultSession, getServerSession } from \"next-auth\";\nimport GoogleProvider from \"next-auth/providers/google\";\nimport prisma from \"../prisma/client\";\nimport { PrismaAdapter } from \"@next-auth/prisma-adapter\";\n\ndeclare module \"next-auth\" {\n  interface Session extends DefaultSession {\n    user: {\n      id: string;\n    } & DefaultSession[\"user\"];\n  }\n}\n\ndeclare module \"next-auth/jwt\" {\n  interface JWT {\n    id: string;\n  }\n}\n\nexport const authConfig: AuthOptions = {\n  secret: process.env.NEXTAUTH_SECRET!,\n  providers: [\n    GoogleProvider({\n      clientId: process.env.GOOGLE_ID!,\n      clientSecret: process.env.GOOGLE_SECRET!,\n    }),\n  ],\n  adapter: PrismaAdapter(prisma),\n  session: {\n    strategy: \"jwt\",\n  },\n  callbacks: {\n    jwt: async ({ token }) => {\n      const db_user = await prisma.user.findFirst({\n        where: {\n          email: token.email,\n        },\n      });\n      if (db_user) token.id = db_user.id;\n      return token;\n    },\n    session: ({ token, session }) => {\n      if (token) {\n        session.user.id = token.id;\n        session.user.name = token.name;\n        session.user.email = token.email;\n        session.user.image = token.picture;\n      }\n      return session;\n    },\n  },\n};\n\nexport async function getUser() {\n  return await getServerSession(authConfig);\n}\n"
  },
  {
    "path": "lib/utils.ts",
    "content": "import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n\n// for development purpose\nexport function delay(ms: number): Promise<void> {\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      resolve();\n    }, ms);\n  });\n}\n\nexport function generateRandomId(length: number) {\n  const characters =\n    \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\n  let randomId = \"\";\n\n  for (let i = 0; i < length; i++) {\n    const randomIndex = Math.floor(Math.random() * characters.length);\n    randomId += characters.charAt(randomIndex);\n  }\n  return randomId;\n}\n"
  },
  {
    "path": "next.config.mjs",
    "content": "/** @type {import('next').NextConfig} */\nconst nextConfig = {};\n\nexport default nextConfig;\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"chatgpt\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"postinstall\": \"prisma generate\",\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\"\n  },\n  \"dependencies\": {\n    \"@next-auth/prisma-adapter\": \"^1.0.7\",\n    \"@prisma/client\": \"^5.8.1\",\n    \"@radix-ui/react-dialog\": \"^1.0.5\",\n    \"@radix-ui/react-label\": \"^2.0.2\",\n    \"@radix-ui/react-scroll-area\": \"^1.0.5\",\n    \"@radix-ui/react-slot\": \"^1.0.2\",\n    \"@radix-ui/react-toast\": \"^1.1.5\",\n    \"class-variance-authority\": \"^0.7.0\",\n    \"clsx\": \"^2.1.0\",\n    \"lucide-react\": \"^0.314.0\",\n    \"next\": \"14.1.0\",\n    \"next-auth\": \"^4.24.5\",\n    \"next-themes\": \"^0.2.1\",\n    \"openai\": \"^4.25.0\",\n    \"react\": \"^18\",\n    \"react-dom\": \"^18\",\n    \"tailwind-merge\": \"^2.2.1\",\n    \"tailwindcss-animate\": \"^1.0.7\",\n    \"zod\": \"^3.22.4\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^20\",\n    \"@types/react\": \"^18\",\n    \"@types/react-dom\": \"^18\",\n    \"autoprefixer\": \"^10.0.1\",\n    \"eslint\": \"^8\",\n    \"eslint-config-next\": \"14.1.0\",\n    \"postcss\": \"^8\",\n    \"prisma\": \"^5.8.1\",\n    \"tailwindcss\": \"^3.3.0\",\n    \"typescript\": \"^5\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "prisma/client.ts",
    "content": "import { PrismaClient } from \"@prisma/client\";\n\nconst prismaClientSingleton = () => {\n  return new PrismaClient();\n};\n\ndeclare global {\n  var prisma: undefined | ReturnType<typeof prismaClientSingleton>;\n}\n\nconst prisma = globalThis.prisma ?? prismaClientSingleton();\n\nexport default prisma;\n\nif (process.env.NODE_ENV !== \"production\") globalThis.prisma = prisma;\n"
  },
  {
    "path": "prisma/schema.prisma",
    "content": "generator client {\n  provider = \"prisma-client-js\"\n}\n\ndatasource db {\n  provider  = \"postgresql\"\n  url       = env(\"POSTGRES_PRISMA_URL\")\n  directUrl = env(\"POSTGRES_URL_NON_POOLING\")\n}\n\nmodel Account {\n  id                String  @id @default(cuid())\n  userId            String\n  type              String\n  provider          String\n  providerAccountId String\n  refresh_token     String? @db.Text\n  access_token      String? @db.Text\n  expires_at        Int?\n  token_type        String?\n  scope             String?\n  id_token          String? @db.Text\n  session_state     String?\n  user              User    @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n  @@unique([provider, providerAccountId])\n}\n\nmodel User {\n  id            String    @id @default(cuid())\n  name          String?\n  email         String?   @unique\n  emailVerified DateTime?\n  image         String?\n\n  conversations Conversation[]\n  accounts      Account[]\n}\n\nmodel Conversation {\n  id       String @id @default(cuid())\n  name     String\n  messages Json\n\n  userId String\n  user   User   @relation(fields: [userId], references: [id])\n\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n}\n"
  },
  {
    "path": "tailwind.config.ts",
    "content": "import type { Config } from \"tailwindcss\"\n\nconst config = {\n  darkMode: [\"class\"],\n  content: [\n    './pages/**/*.{ts,tsx}',\n    './components/**/*.{ts,tsx}',\n    './app/**/*.{ts,tsx}',\n    './src/**/*.{ts,tsx}',\n\t],\n  prefix: \"\",\n  theme: {\n    container: {\n      center: true,\n      padding: \"2rem\",\n      screens: {\n        \"2xl\": \"1400px\",\n      },\n    },\n    extend: {\n      colors: {\n        border: \"hsl(var(--border))\",\n        input: \"hsl(var(--input))\",\n        ring: \"hsl(var(--ring))\",\n        background: \"hsl(var(--background))\",\n        foreground: \"hsl(var(--foreground))\",\n        primary: {\n          DEFAULT: \"hsl(var(--primary))\",\n          foreground: \"hsl(var(--primary-foreground))\",\n        },\n        secondary: {\n          DEFAULT: \"hsl(var(--secondary))\",\n          foreground: \"hsl(var(--secondary-foreground))\",\n        },\n        destructive: {\n          DEFAULT: \"hsl(var(--destructive))\",\n          foreground: \"hsl(var(--destructive-foreground))\",\n        },\n        muted: {\n          DEFAULT: \"hsl(var(--muted))\",\n          foreground: \"hsl(var(--muted-foreground))\",\n        },\n        accent: {\n          DEFAULT: \"hsl(var(--accent))\",\n          foreground: \"hsl(var(--accent-foreground))\",\n        },\n        popover: {\n          DEFAULT: \"hsl(var(--popover))\",\n          foreground: \"hsl(var(--popover-foreground))\",\n        },\n        card: {\n          DEFAULT: \"hsl(var(--card))\",\n          foreground: \"hsl(var(--card-foreground))\",\n        },\n      },\n      borderRadius: {\n        lg: \"var(--radius)\",\n        md: \"calc(var(--radius) - 2px)\",\n        sm: \"calc(var(--radius) - 4px)\",\n      },\n      keyframes: {\n        \"accordion-down\": {\n          from: { height: \"0\" },\n          to: { height: \"var(--radix-accordion-content-height)\" },\n        },\n        \"accordion-up\": {\n          from: { height: \"var(--radix-accordion-content-height)\" },\n          to: { height: \"0\" },\n        },\n      },\n      animation: {\n        \"accordion-down\": \"accordion-down 0.2s ease-out\",\n        \"accordion-up\": \"accordion-up 0.2s ease-out\",\n      },\n    },\n  },\n  plugins: [require(\"tailwindcss-animate\")],\n} satisfies Config\n\nexport default config"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\"./*\"]\n    }\n  },\n  \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "types/index.ts",
    "content": "import { z } from \"zod\";\n\nexport const JsonMessageSchema = z.object({\n  id: z.string(),\n  answer: z.string().optional(),\n  question: z.string(),\n});\n\nexport const JsonMessagesArraySchema = z.array(JsonMessageSchema);\n\nexport type JSONMessage = z.infer<typeof JsonMessageSchema>;\n"
  }
]