├── .eslintrc.json
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── components.json
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── celestialchat.gif
├── celestialchat.mp4
├── cta.png
├── next.svg
└── vercel.svg
├── src
├── app
│ ├── api
│ │ └── chat
│ │ │ └── route.ts
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── components
│ ├── Chat.tsx
│ ├── Chatinput.tsx
│ ├── CopyToClipboard.tsx
│ ├── Dotlanding.tsx
│ ├── Landing.tsx
│ ├── MaxWidthWrapper.tsx
│ ├── Uploadsection.tsx
│ ├── data.tsx
│ ├── navbar.tsx
│ └── ui
│ │ ├── avatar.tsx
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── moving-border.tsx
│ │ ├── scroll-area.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ ├── switch.tsx
│ │ ├── textarea.tsx
│ │ ├── tooltip.tsx
│ │ └── typewriter-effect.tsx
├── hooks
│ └── use-clipboard.tsx
├── lib
│ ├── icon.tsx
│ └── utils.ts
└── utils
│ └── cn.ts
├── tailwind.config.ts
└── tsconfig.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.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 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 | .env
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18
2 |
3 | WORKDIR /app
4 |
5 | COPY package*.json ./
6 |
7 | RUN npm install
8 |
9 | COPY . .
10 |
11 | RUN npm run build
12 |
13 | EXPOSE 3000
14 |
15 | CMD [ "npm", "start" ]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Ian Huang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ## CelestialChat
5 |
6 | CelestialChat is a simple AI chat to deliver fast search results powered by Claude Haiku and Tavily search.
7 |
8 | ## Todo
9 |
10 | Scroll behavior, although with the speed of Claude, following the text might not offer optimal experience.
11 |
12 | Include Image in the search results, I haven't find an Image search api that delivers results matching the answers.
13 |
14 |
15 | ## Getting Started
16 |
17 | Set Up .env:
18 |
19 | ```bash
20 | ANTHROPIC_API_KEY=
21 | TAVILY_API_KEY=
22 | OPENAI_API_KEY=
23 |
24 | ```
25 | If you want to use moonshot api please check out the proxy branch by mutse : [moonshot api verison](https://github.com/suzushi-tw/celestialchat/tree/proxy)
26 |
27 | ## Docker
28 |
29 | ```bash
30 | docker build -t celestialchat .
31 | ```
32 |
33 | ```bash
34 | docker run -p 3000:3000 -d --name celestialchat-container celestialchat
35 | ```
36 |
37 | ## Stack
38 |
39 | - Nextjs
40 | - Lobe chat UI
41 | - Shadcn UI
42 | - Tavily search API
43 |
44 |
45 | ## Deployment
46 |
47 | The easiest way to deploy is
48 |
49 | - [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fsuzushi-tw%2Fcelestialchat&env=ANTHROPIC_API_KEY,TAVILY_API_KEY)
50 |
51 | - [](https://cloud.digitalocean.com/apps/new?repo=https://github.com/suzushi-tw/celestialchat/tree/main&refcode=11745b6395ca)
52 |
53 | - Cloudflare Pages (add Edge run time)
54 |
55 |
56 | ## Artwork
57 |
58 | Special thanks to all the artist for amazing artwork !
59 | Artworks used in the project falls under their 利用規約,
60 | which grants personal use and commercial use, however
61 | the copy right belongs to the original author and shall
62 | not be registered for trademark.
63 |
64 | - [ガーリー素材] (https://girlysozai.com/)
65 |
66 |
67 | ## License
68 |
69 | Open source under MIT License, you are welcome to
70 |
71 | - Self host
72 | - Make any modification
73 | - Commercialize it
74 |
75 | In the case of commerical use, This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Artwork and the Licensor.
76 |
77 |
--------------------------------------------------------------------------------
/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.ts",
8 | "css": "src/app/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {}
3 |
4 | module.exports = nextConfig
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "celestialchat",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@anthropic-ai/sdk": "^0.20.6",
13 | "@langchain/community": "^0.0.49",
14 | "@langchain/openai": "^0.0.28",
15 | "@lobehub/ui": "^1.138.4",
16 | "@nanostores/react": "github:ai/react",
17 | "@radix-ui/react-avatar": "^1.0.4",
18 | "@radix-ui/react-dialog": "^1.0.5",
19 | "@radix-ui/react-dropdown-menu": "^2.0.6",
20 | "@radix-ui/react-label": "^2.0.2",
21 | "@radix-ui/react-scroll-area": "^1.0.5",
22 | "@radix-ui/react-select": "^2.0.0",
23 | "@radix-ui/react-separator": "^1.0.3",
24 | "@radix-ui/react-slot": "^1.0.2",
25 | "@radix-ui/react-switch": "^1.0.3",
26 | "@radix-ui/react-tooltip": "^1.0.7",
27 | "@types/node": "20.11.27",
28 | "@types/react": "18.2.65",
29 | "@types/react-dom": "18.2.22",
30 | "ai": "^3.0.23",
31 | "autoprefixer": "10.4.18",
32 | "axios": "^1.6.8",
33 | "class-variance-authority": "^0.7.0",
34 | "clsx": "^2.1.0",
35 | "eslint": "8.57.0",
36 | "eslint-config-next": "14.1.3",
37 | "framer-motion": "^11.1.7",
38 | "lucide-react": "^0.357.0",
39 | "next": "14.1.3",
40 | "openai": "^4.36.0",
41 | "postcss": "8.4.35",
42 | "react": "18.2.0",
43 | "react-dom": "18.2.0",
44 | "react-dropzone": "^14.2.3",
45 | "react-element-to-jsx-string": "^15.0.0",
46 | "react-hot-toast": "^2.4.1",
47 | "tailwind-merge": "^2.3.0",
48 | "tailwindcss": "3.4.1",
49 | "tailwindcss-animate": "^1.0.7",
50 | "through2": "^4.0.2",
51 | "typescript": "5.4.2",
52 | "zod": "^3.22.4"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/celestialchat.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suzushi-tw/celestialchat/8247f2b0b95bd864daa50b3f3cc2f6eae79df0c0/public/celestialchat.gif
--------------------------------------------------------------------------------
/public/celestialchat.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suzushi-tw/celestialchat/8247f2b0b95bd864daa50b3f3cc2f6eae79df0c0/public/celestialchat.mp4
--------------------------------------------------------------------------------
/public/cta.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suzushi-tw/celestialchat/8247f2b0b95bd864daa50b3f3cc2f6eae79df0c0/public/cta.png
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/api/chat/route.ts:
--------------------------------------------------------------------------------
1 | import OpenAI from 'openai';
2 | import { OpenAIStream, StreamingTextResponse, StreamData } from 'ai';
3 |
4 | import Anthropic from '@anthropic-ai/sdk';
5 | import { AnthropicStream } from 'ai';
6 | import axios from 'axios'
7 |
8 |
9 |
10 | const anthropic = new Anthropic({
11 | apiKey: process.env.ANTHROPIC_API_KEY ,
12 | });
13 | const openai = new OpenAI({
14 | apiKey: process.env.OPENAI_API_KEY,
15 | });
16 |
17 | interface Message {
18 | role: string;
19 | content: string;
20 | }
21 | // // IMPORTANT! Set the runtime to edge
22 | export const runtime = 'edge';
23 |
24 |
25 |
26 | export async function POST(req: Request) {
27 | try {
28 | // const { messages } = await req.json();
29 | const body = await req.json();
30 | const { messages, previousmessage, aimodel, depth } = body;
31 |
32 | console.log(previousmessage)
33 |
34 | // const processedpreviousmessage = JSON.stringify(previousmessage);
35 | // console.log("previous message"+processedpreviousmessage)
36 | const reversedMessages = messages.reverse();
37 | let search_depth="";
38 | if(depth){
39 | search_depth="advanced"
40 | } else {
41 | search_depth="basic"
42 | }
43 | console.log(search_depth)
44 | // Find the latest user message
45 | const userMessage = reversedMessages.find((message: Message) => message.role === 'user');
46 | const query = userMessage ? userMessage.content : '';
47 | console.log(query);
48 | // Ask OpenAI for a streaming chat completion given the prompt
49 | if ((aimodel == 'claude-3-haiku-20240307' || aimodel == "claude-3-sonnet-20240229") && process.env.ANTHROPIC_API_KEY) {
50 | const initialresponse = await anthropic.messages.create({
51 | messages: [
52 | {
53 | role: 'user',
54 | content: `You are a professional web searcher, optimize the following input(and previous conversation if needed) as a query to find the best web search results,
55 | USER INPUT: ${query},Previous conversation: ${previousmessage}, strictly output the query only as it will be pasted into browser right away !`,
56 | },
57 | ],
58 | model: aimodel,
59 | stream: false,
60 | max_tokens: 4096,
61 | });
62 | console.log(initialresponse)
63 | const assistantContent = initialresponse.content[0] ? initialresponse.content[0].text : '';
64 | const optimizedQuery = assistantContent.replace('"', '').replace('"', '');
65 |
66 | const webresponse = await fetch('https://api.tavily.com/search', {
67 | method: 'POST',
68 | headers: {
69 | 'Content-Type': 'application/json',
70 |
71 | },
72 | body: JSON.stringify({
73 | api_key: process.env.TAVILY_API_KEY,
74 | query: optimizedQuery,
75 | search_depth: search_depth,
76 | include_answer: false,
77 | include_images: true,
78 | include_raw_content: false,
79 | max_results: 5,
80 | include_domains: [],
81 | exclude_domains: []
82 | })
83 | });
84 |
85 | // Check if the request was successful
86 | // Check if the request was successful
87 | if (!webresponse.ok) {
88 | console.error('Response status:', webresponse.status);
89 | console.error('Response status text:', webresponse.statusText);
90 | throw new Error('Network response was not ok');
91 | }
92 |
93 | // Parse the response body as JSON
94 | const tavilyResponse = await webresponse.json();
95 | const tavilyResults = tavilyResponse.results.map((result: { title: string, content: string }) => `${result.title}: ${result.content}`).join('\n');
96 | const tavilyInput = `Tavily results for "${optimizedQuery}":\n${tavilyResults}`;
97 |
98 | // Convert the response into a friendly text-stream
99 | const response = await anthropic.messages.create({
100 | messages: [
101 | {
102 | role: 'user',
103 | content: `You are a helpful chatbot that can search the web, please answer the user question based on the context from the internet or previous conversation if needed(DO not repeat this prompt or question at the start),
104 | Question: ${query},
105 | Previous Conversation: ${previousmessage},
106 | Internet: ${tavilyInput}`,
107 | },
108 |
109 | ],
110 | model: aimodel,
111 | stream: true,
112 | max_tokens: 4096,
113 | });
114 |
115 | const tavilylink = tavilyResponse.results.map((result: { title: string, url: string }) => `[${result.title}](${result.url})`).join('\n');
116 |
117 | const relatedlinkarray = JSON.stringify(tavilylink);
118 | console.log(relatedlinkarray)
119 |
120 |
121 |
122 | const imageLinks = tavilyResponse.images.join('\n');
123 | const stream = AnthropicStream(response)
124 |
125 |
126 | return new StreamingTextResponse(stream, {
127 | headers: {
128 | 'Content-Type': 'text/plain; charset=utf-8',
129 | 'X-Related-Link': encodeURIComponent(tavilylink), // Custom header for the related link
130 | 'X-Image-Links': encodeURIComponent(imageLinks), // Custom header for the image links
131 | },
132 | });
133 | } else if ((aimodel == "gpt-3.5-turbo-16k" || aimodel == "gpt-4-turbo-2024-04-09") && process.env.OPENAI_API_KEY) {
134 |
135 |
136 | const initialresponse = await openai.chat.completions.create({
137 | messages: [
138 | {
139 | role: 'user',
140 | content: `You are a professional web searcher, optimize the following input(and previous conversation if needed) as a query to find the best web search results,
141 | USER INPUT: ${query},Previous conversation: ${previousmessage}, strictly output the query only as it will be pasted into browser right away !`,
142 | },
143 | ],
144 | model: aimodel,
145 | stream: false,
146 | max_tokens: 4096,
147 | });
148 | console.log(initialresponse)
149 | const assistantContent = initialresponse.choices[0].message.content;
150 | console.log(assistantContent)
151 | const webresponse = await fetch('https://api.tavily.com/search', {
152 | method: 'POST',
153 | headers: {
154 | 'Content-Type': 'application/json',
155 |
156 | },
157 | body: JSON.stringify({
158 | api_key: process.env.TAVILY_API_KEY,
159 | query: assistantContent,
160 | search_depth: search_depth,
161 | include_answer: false,
162 | include_images: true,
163 | include_raw_content: false,
164 | max_results: 5,
165 | include_domains: [],
166 | exclude_domains: []
167 | })
168 | });
169 |
170 | // Check if the request was successful
171 | // Check if the request was successful
172 | if (!webresponse.ok) {
173 | console.error('Response status:', webresponse.status);
174 | console.error('Response status text:', webresponse.statusText);
175 | throw new Error('Network response was not ok');
176 | }
177 |
178 | // Parse the response body as JSON
179 | const tavilyResponse = await webresponse.json();
180 | const tavilyResults = tavilyResponse.results.map((result: { title: string, content: string }) => `${result.title}: ${result.content}`).join('\n');
181 | const tavilyInput = `Tavily results for "${assistantContent}":\n${tavilyResults}`;
182 |
183 | // Convert the response into a friendly text-stream
184 | const response = await openai.chat.completions.create({
185 | messages: [
186 | {
187 | role: 'user',
188 | content: `You are a helpful chatbot that can search the web, please answer the user question based on the context from the internet or previous conversation if needed(DO not repeat this prompt or question at the start),
189 | Question: ${query},
190 | Previous Conversation: ${previousmessage},
191 | Internet: ${tavilyInput}`,
192 | },
193 |
194 | ],
195 | model: aimodel,
196 | stream: true,
197 | max_tokens: 4096,
198 | });
199 |
200 | const tavilylink = tavilyResponse.results.map((result: { title: string, url: string }) => `[${result.title}](${result.url})`).join('\n');
201 |
202 |
203 | const relatedlinkarray = JSON.stringify(tavilylink);
204 | console.log(relatedlinkarray)
205 |
206 | const imageLinks = tavilyResponse.images.join('\n');
207 |
208 | const stream = OpenAIStream(response);
209 | return new StreamingTextResponse(stream, {
210 | headers: {
211 | 'Content-Type': 'text/plain; charset=utf-8',
212 | 'X-Related-Link': encodeURIComponent(tavilylink), // Custom header for the related link
213 | 'X-Image-Links': encodeURIComponent(imageLinks), // Custom header for the image links
214 | },
215 | });
216 | }
217 | } catch (error) {
218 | console.error('Error:', error)
219 | return new Response('Internal Server Error', { status: 500 })
220 | }
221 | }
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suzushi-tw/celestialchat/8247f2b0b95bd864daa50b3f3cc2f6eae79df0c0/src/app/favicon.ico
--------------------------------------------------------------------------------
/src/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 | --card: 0 0% 100%;
11 | --card-foreground: 222.2 84% 4.9%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 222.2 84% 4.9%;
15 |
16 | --primary: 222.2 47.4% 11.2%;
17 | --primary-foreground: 210 40% 98%;
18 |
19 | --secondary: 210 40% 96.1%;
20 | --secondary-foreground: 222.2 47.4% 11.2%;
21 |
22 | --muted: 210 40% 96.1%;
23 | --muted-foreground: 215.4 16.3% 46.9%;
24 |
25 | --accent: 210 40% 96.1%;
26 | --accent-foreground: 222.2 47.4% 11.2%;
27 |
28 | --destructive: 0 84.2% 60.2%;
29 | --destructive-foreground: 210 40% 98%;
30 |
31 | --border: 214.3 31.8% 91.4%;
32 | --input: 214.3 31.8% 91.4%;
33 | --ring: 222.2 84% 4.9%;
34 |
35 | --radius: 0.5rem;
36 | }
37 |
38 | .dark {
39 | --background: 222.2 84% 4.9%;
40 | --foreground: 210 40% 98%;
41 |
42 | --card: 222.2 84% 4.9%;
43 | --card-foreground: 210 40% 98%;
44 |
45 | --popover: 222.2 84% 4.9%;
46 | --popover-foreground: 210 40% 98%;
47 |
48 | --primary: 210 40% 98%;
49 | --primary-foreground: 222.2 47.4% 11.2%;
50 |
51 | --secondary: 217.2 32.6% 17.5%;
52 | --secondary-foreground: 210 40% 98%;
53 |
54 | --muted: 217.2 32.6% 17.5%;
55 | --muted-foreground: 215 20.2% 65.1%;
56 |
57 | --accent: 217.2 32.6% 17.5%;
58 | --accent-foreground: 210 40% 98%;
59 |
60 | --destructive: 0 62.8% 30.6%;
61 | --destructive-foreground: 210 40% 98%;
62 |
63 | --border: 217.2 32.6% 17.5%;
64 | --input: 217.2 32.6% 17.5%;
65 | --ring: 212.7 26.8% 83.9%;
66 | }
67 | }
68 |
69 | @layer base {
70 | * {
71 | @apply border-border;
72 | }
73 | body {
74 | @apply bg-background text-foreground;
75 | }
76 | }
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import './globals.css'
2 | import type { Metadata } from 'next'
3 | import { Inter } from 'next/font/google'
4 | import Navbar from "@/components/navbar";
5 | import toast, { Toaster } from 'react-hot-toast';
6 |
7 | const inter = Inter({ subsets: ['latin'] })
8 |
9 | export const metadata: Metadata = {
10 | title: 'CelestialChat',
11 | description: 'Ai powered websearch for blazing fast search results ...',
12 | }
13 |
14 | export default function RootLayout({
15 | children,
16 | }: {
17 | children: React.ReactNode
18 | }) {
19 | return (
20 |
21 |
22 |
23 |
24 |
25 | {children}
26 |
27 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import Uploadsection from '@/components/Uploadsection'
2 | import Image from 'next/image'
3 | import Landing from '@/components/Landing'
4 | import { DotBackgroundDemo } from '@/components/Dotlanding'
5 | import MaxWidthWrapper from '@/components/MaxWidthWrapper'
6 |
7 | export default function Home() {
8 | return (
9 |
10 | {/* */}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Chat.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import React, { use } from 'react'
3 | import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './ui/card'
4 | import Chatinput from './Chatinput'
5 | import { Textarea } from './ui/textarea'
6 | import { Button } from './ui/button'
7 | import { Label } from '@radix-ui/react-dropdown-menu'
8 | import { Switch } from './ui/switch'
9 | import { Separator } from './ui/separator'
10 | import { Select, SelectTrigger, SelectItem, SelectLabel, SelectGroup, SelectValue, SelectContent } from './ui/select'
11 | import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from './ui/tooltip'
12 | import { Send } from 'lucide-react'
13 | import { useChat } from 'ai/react';
14 | import toast, { Toaster } from 'react-hot-toast';
15 | import { ScrollArea } from './ui/scroll-area'
16 | import { Avatar, AvatarImage, AvatarFallback } from './ui/avatar'
17 | import { useState, useRef, useEffect } from 'react';
18 | import CopyToClipboard from './CopyToClipboard'
19 | import { Markdown, MarkdownProps, StoryBook, useControls, useCreateStore } from '@lobehub/ui';
20 | import { Input } from './ui/input'
21 | import { Cleansvg } from '@/lib/icon'
22 |
23 | interface ChatProps {
24 | onRelatedLinks: (links: string) => void;
25 | }
26 |
27 | function Chat({ onRelatedLinks }: ChatProps) {
28 | const [relatedLinks, setRelatedLinks] = useState('');
29 | const [depth, setdepth]=useState(false)
30 | const [isMessageComplete, setIsMessageComplete] = useState(false);
31 | const [isFinished, setIsFinished] = useState(false);
32 | const [previousmessage, setpreviosmessage] = useState('');
33 | const [model, setModel] = useState('claude-3-haiku-20240307'); // default value
34 | const { messages, input, handleInputChange, handleSubmit, isLoading, error, setMessages } =
35 | useChat({
36 | body: {
37 | previousmessage: JSON.stringify(previousmessage),
38 | aimodel: model,
39 | depth: depth
40 | },
41 | onResponse: response => {
42 | console.log(messages);
43 | console.log(response)
44 | if (!response.ok) {
45 | toast.error(error?.message || 'Something went wrong!')
46 | } else {
47 | const headers = response.headers;
48 | const relatedLink = headers.get("X-Related-Link");
49 | if (relatedLink) {
50 | const decodedlink = `\n${decodeURIComponent(relatedLink)}`;
51 | console.log("Related Link:", decodedlink);
52 | setRelatedLinks(decodedlink);
53 |
54 | }
55 |
56 | }
57 |
58 | },
59 | onFinish: () => {
60 | setIsMessageComplete(true);
61 | }
62 | })
63 |
64 | useEffect(() => {
65 | if (isMessageComplete && messages.length > 0) {
66 | // Assuming you have a way to determine the last AI message
67 | // For example, by checking the role or content of the last message
68 | const lastMessage = messages[messages.length - 1];
69 | if (lastMessage.role === 'assistant') {
70 | // Append related links to the last AI message
71 | // This is a simplified example; adjust according to your data structure
72 | const lastThreeMessages = messages.slice(-4);
73 | const previousmessage = lastThreeMessages.map(message => {
74 | return message.role === 'user' ? `User: ${message.content}` : `Assistant: ${message.content}`;
75 | }).join('\n');
76 | setpreviosmessage(previousmessage);
77 | console.log(previousmessage)
78 | lastMessage.content += `\n\nRelated Links: ${relatedLinks}`;
79 | // Reset the flag
80 | setIsMessageComplete(false);
81 |
82 | }
83 | }
84 | }, [messages, isMessageComplete, relatedLinks]);
85 |
86 | const clearchat = () => {
87 | setMessages([])
88 | }
89 | const toggledepth=()=>{
90 | setdepth(!depth)
91 | }
92 |
93 |
94 | const handleValueChange = async (selectedValue: string) => {
95 | console.log("Selected value: ", selectedValue);
96 | toast.success("AI model Updated !")
97 | setModel(selectedValue);
98 | };
99 |
100 |
101 | return (
102 |
103 |
104 | {/*
105 | Chat
106 | */}
107 |
108 | {messages.length === 0 || (messages.length === 1 && messages[0].role === 'system') ? (
109 |
110 |
111 |
112 | Hello,
113 |
114 |
115 |
How can I help you today?
116 |
117 |
118 | ) : (
119 |
123 |
124 |
125 |
126 | {messages.map((m) => (
127 |
128 |
129 | {m.role === 'user' && (
130 |
131 |
132 |
133 | U
134 |
135 |
136 |
You
137 |
138 | {m.content}
139 |
140 |
141 |
142 | )}
143 |
144 | {m.role === 'assistant' && (
145 |
146 |
147 |
148 |
149 | AI
150 |
151 |
152 |
153 |
157 |
158 |
159 | {/*
160 | {`${m.content} ${relatedLinks && `\n \n Related Links: ${relatedLinks}`}`}
161 | */}
162 |
163 | {m.content}
164 |
165 |
166 |
167 |
168 | )}
169 |
170 | ))}
171 |
172 |
173 |
174 |
175 |
176 |
177 | )}
178 |
179 |
180 |
181 |
219 |
220 |
221 |
222 |
223 |
224 | )
225 | }
226 |
227 | export default Chat
228 |
--------------------------------------------------------------------------------
/src/components/Chatinput.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Button } from './ui/button'
3 | import { Textarea } from './ui/textarea'
4 | import { Select, SelectTrigger, SelectItem, SelectLabel, SelectGroup, SelectValue, SelectContent } from './ui/select'
5 | import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from './ui/tooltip'
6 | import { Send } from 'lucide-react'
7 |
8 | function Chatinput() {
9 | return (
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
33 |
34 |
35 |
36 | New Chat
37 |
38 |
39 | New Chat
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | Models
50 | GPT 3.5
51 | Claude Haiku
52 | GPT 4 Turbo ⚡️
53 | Claude Sonnet ⚡️
54 |
55 |
56 |
57 |
58 |
59 |
60 |
65 |
66 |
67 |
68 |
69 |
70 | )
71 | }
72 |
73 | export default Chatinput
74 |
--------------------------------------------------------------------------------
/src/components/CopyToClipboard.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { cn } from '@/lib/utils'
4 | import { type Message } from 'ai'
5 |
6 | import { Button } from '@/components/ui/button'
7 | import { CheckIcon, CopyIcon } from 'lucide-react'
8 | import { useClipboard } from '@/hooks/use-clipboard'
9 |
10 | interface ChatMessageActionsProps extends React.ComponentProps<'div'> {
11 | message: Message
12 | }
13 |
14 | export default function CopyToClipboard({
15 | message,
16 | className,
17 | ...props
18 | }: ChatMessageActionsProps) {
19 | const { isCopied, copyToClipboard } = useClipboard({ timeout: 2000 })
20 |
21 | const onCopy = () => {
22 | if (isCopied) return
23 | copyToClipboard(message.content)
24 | }
25 |
26 | return (
27 |
28 |
34 | {isCopied ? (
35 |
36 | ) : (
37 |
38 | )}
39 | Copy message
40 |
41 |
42 | )
43 | }
--------------------------------------------------------------------------------
/src/components/Dotlanding.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export function DotBackgroundDemo() {
4 | return (
5 |
6 | {/* Radial gradient for the container to give a faded look */}
7 |
8 |
9 | Backgrounds
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/Landing.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import Link from "next/link"
3 | import {
4 | Bell,
5 | CircleUser,
6 | Home,
7 | LineChart,
8 | Menu,
9 | Package,
10 | Package2,
11 | Search,
12 | ShoppingCart,
13 | Users,
14 | Github
15 | } from "lucide-react"
16 |
17 | import { Badge } from "@/components/ui/badge"
18 | // import { Button } from "@/components/ui/button"
19 | import {
20 | Card,
21 | CardContent,
22 | CardDescription,
23 | CardHeader,
24 | CardTitle,
25 | } from "@/components/ui/card"
26 | import {
27 | DropdownMenu,
28 | DropdownMenuContent,
29 | DropdownMenuItem,
30 | DropdownMenuLabel,
31 | DropdownMenuSeparator,
32 | DropdownMenuTrigger,
33 | } from "@/components/ui/dropdown-menu"
34 | import { Input } from "@/components/ui/input"
35 | import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
36 | import Chat from "./Chat"
37 | import { useState, useEffect } from "react"
38 | import { ScrollArea } from "./ui/scroll-area"
39 | import Image from "next/image"
40 | import { Button } from "./ui/button"
41 |
42 | interface LinkItem {
43 | title: string;
44 | url: string;
45 | }
46 |
47 |
48 | const Landing = () => {
49 |
50 | const [relatedLinks, setRelatedLinks] = useState('');
51 | const [parsedLinks, setParsedLinks] = useState([]); // Updated useState
52 | const handleRelatedLinks = (links: string) => {
53 | console.log(links)
54 | setRelatedLinks(links);
55 |
56 | };
57 |
58 | // useEffect(() => {
59 | // if (relatedLinks) {
60 | // // Parse the links and update the state
61 | // const linksArray = relatedLinks.split('\n');
62 | // let parsed = [];
63 | // let currentTitle = '';
64 | // let currentLink = '';
65 |
66 | // linksArray.forEach(item => {
67 | // if (item.startsWith('Link:')) {
68 | // // If there's a current title without a link, update its link
69 | // if (currentTitle && !currentLink) {
70 | // currentLink = item.replace('Link: ', '');
71 | // parsed.push({ title: currentTitle, url: currentLink });
72 | // currentTitle = ''; // Reset currentTitle for the next title
73 | // } else {
74 | // // If there's no current title, this is likely an error in the input
75 | // console.error('Unexpected link without a preceding title:', item);
76 | // }
77 | // } else if (item !== '') {
78 | // // If there's a title without a link, set it as the current title
79 | // if (currentTitle && !currentLink) {
80 | // // This is likely an error in the input, as we have a title without a link
81 | // console.error('Unexpected title without a link:', item);
82 | // } else {
83 | // currentTitle = item;
84 | // }
85 | // }
86 | // });
87 |
88 | // // If there's a pending title without a link at the end, add it with an empty link
89 | // if (currentTitle && !currentLink) {
90 | // parsed.push({ title: currentTitle, url: '' });
91 | // }
92 |
93 | // setParsedLinks(parsed);
94 | // }
95 | // }, [relatedLinks]); // Dependency array to watch for changes in relatedLinks
96 |
97 |
98 | useEffect(() => {
99 | if (relatedLinks) {
100 | // Parse the links and update the state
101 | const linksArray = relatedLinks.split('\n');
102 | let parsed: LinkItem[] = [];
103 | let currentTitle = '';
104 | let currentUrl = '';
105 |
106 | linksArray.forEach((item, index) => {
107 | if (index % 2 === 0) {
108 | // This is a title
109 | currentTitle = item;
110 | } else {
111 | // This is a url
112 | currentUrl = item.replace('Link: ', '');
113 | parsed.push({ title: currentTitle, url: currentUrl });
114 | currentTitle = '';
115 | currentUrl = '';
116 | }
117 | });
118 |
119 | setParsedLinks(parsed);
120 | console.log(parsed)
121 | }
122 | }, [relatedLinks]);
123 |
124 |
125 |
126 |
127 | return (
128 | <>
129 |
130 |
131 |
132 |
133 |
134 |
135 |
139 |
140 | Tired of waiting for answers?
141 |
142 |
143 |
144 | {parsedLinks.length > 0 ? (
145 | parsedLinks.map((link, index) => (
146 | link && (
147 |
148 |
149 |
150 | {link.title}
151 | {link.url}
152 |
153 |
154 |
155 | )
156 | ))
157 | ) : (
158 |
159 |
160 |
Get your answer in seconds, not days !
161 |
162 | )}
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 | Deploy your own blazing fast AI chat
172 |
173 | Head over to Github to find out more details !
174 |
175 |
176 |
177 |
178 | Self Host
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
Chat
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 | >
201 | );
202 | };
203 |
204 | export default Landing;
--------------------------------------------------------------------------------
/src/components/MaxWidthWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 | import { FC, ReactNode } from "react";
3 |
4 | export interface MaxWidthWrapperProps {
5 | className?: string;
6 | children: ReactNode
7 | }
8 |
9 | const MaxWidthWrapper: FC = ({ className, children }) => {
10 | return (
11 |
12 | {children}
13 |
14 | );
15 | };
16 | export default MaxWidthWrapper
--------------------------------------------------------------------------------
/src/components/Uploadsection.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | // import { uploadToS3 } from '@/lib/s3'
4 | import { Inbox, Loader2 } from 'lucide-react'
5 | import { useState } from 'react'
6 | import { useDropzone } from 'react-dropzone'
7 | import { useRouter } from 'next/navigation'
8 | import { toast } from 'react-hot-toast'
9 |
10 | function Uploadsection() {
11 |
12 | const [isUploading, setIsUploading] = useState(false)
13 | const [isCreatingChat, setIsCreatingChat] = useState(false)
14 | const router = useRouter()
15 | // const storage = getStorage(app)
16 |
17 |
18 | const { getRootProps, getInputProps } = useDropzone({
19 | accept: {
20 | 'image/jpeg': ['.jpeg', '.jpg'],
21 | 'image/png': ['.png'],
22 | },
23 | maxFiles: 1,
24 | onDrop: async (acceptedFiles) => {
25 | const file = acceptedFiles[0];
26 | if (file.size > 10 * 1024 * 1024) {
27 | toast.error('File too large (limit: 10MB)');
28 | return;
29 | }
30 |
31 | try {
32 | setIsUploading(true);
33 | // Convert the file to a format suitable for your API
34 | // This example assumes your API accepts a base64-encoded string
35 | const reader = new FileReader();
36 | reader.onloadend = async () => {
37 | const base64data = reader.result;
38 | const response = await fetch('/api/detectDeepfake', {
39 | method: 'POST',
40 | headers: {
41 | 'Content-Type': 'application/json',
42 | },
43 | body: JSON.stringify({ image: base64data }),
44 | });
45 |
46 | if (response.ok) {
47 | const { isFake } = await response.json();
48 | toast.success(isFake ? 'The image is fake.' : 'The image is genuine.');
49 | } else {
50 | toast.error('Failed to check deepfake');
51 | }
52 | };
53 | reader.readAsDataURL(file);
54 | } catch (err) {
55 | toast.error('Something went wrong ...');
56 | console.log(err);
57 | } finally {
58 | setIsUploading(false);
59 | }
60 | },
61 | });
62 | return (
63 |
64 |
65 |
71 |
72 | {isUploading || isCreatingChat ? (
73 | <>
74 |
75 |
Uploading...
76 | >
77 | ) : (
78 | <>
79 |
80 |
Drop your file here !
81 | >
82 | )}
83 |
84 |
85 |
86 | )
87 | }
88 |
89 | export default Uploadsection
90 |
--------------------------------------------------------------------------------
/src/components/data.tsx:
--------------------------------------------------------------------------------
1 | import { type ActionIconGroupProps } from '@lobehub/ui';
2 | import { Copy, Edit, RotateCw, Trash } from 'lucide-react';
3 |
4 | // export const avatar: MetaData = {
5 | // avatar: '😎',
6 | // backgroundColor: '#E8DA5A',
7 | // title: 'Advertiser',
8 | // };
9 |
10 | export const items: ActionIconGroupProps['items'] = [
11 | {
12 | icon: Edit,
13 | key: 'edit',
14 | label: 'Edit',
15 | },
16 | ];
17 |
18 | export const dropdownMenu: ActionIconGroupProps['dropdownMenu'] = [
19 | {
20 | icon: Copy,
21 | key: 'copy',
22 | label: 'Copy',
23 | },
24 | {
25 | icon: RotateCw,
26 | key: 'regenerate',
27 | label: 'Regenerate',
28 | },
29 | {
30 | type: 'divider',
31 | },
32 | {
33 | icon: Trash,
34 | key: 'delete',
35 | label: 'Delete',
36 | },
37 | ];
38 |
--------------------------------------------------------------------------------
/src/components/navbar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import React from 'react'
3 | import Link from 'next/link'
4 | import MaxWidthWrapper from './MaxWidthWrapper'
5 | import { Loadingmoonsvg } from '@/lib/icon'
6 | // import { buttonVariants } from './ui/button'
7 | // import { useUser } from "@clerk/clerk-react";
8 | // import { UserButton } from '@clerk/nextjs';
9 |
10 | const Navbar = () => {
11 | // const { user } = useUser();
12 |
13 | return (
14 |
15 |
16 |
17 |
20 |
Celestial Chat
21 |
22 | {/*
*/}
23 |
24 |
25 | {/* {user ? (
26 |
27 | ) : (
28 |
34 | Get Started
35 |
36 | )} */}
37 |
38 |
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | export default Navbar
46 |
--------------------------------------------------------------------------------
/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | Avatar.displayName = AvatarPrimitive.Root.displayName
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ))
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49 |
50 | export { Avatar, AvatarImage, AvatarFallback }
51 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/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 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
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 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5 | import { Check, ChevronRight, Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const DropdownMenu = DropdownMenuPrimitive.Root
10 |
11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12 |
13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
14 |
15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16 |
17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
18 |
19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20 |
21 | const DropdownMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ))
40 | DropdownMenuSubTrigger.displayName =
41 | DropdownMenuPrimitive.SubTrigger.displayName
42 |
43 | const DropdownMenuSubContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, ...props }, ref) => (
47 |
55 | ))
56 | DropdownMenuSubContent.displayName =
57 | DropdownMenuPrimitive.SubContent.displayName
58 |
59 | const DropdownMenuContent = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, sideOffset = 4, ...props }, ref) => (
63 |
64 |
73 |
74 | ))
75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
76 |
77 | const DropdownMenuItem = React.forwardRef<
78 | React.ElementRef,
79 | React.ComponentPropsWithoutRef & {
80 | inset?: boolean
81 | }
82 | >(({ className, inset, ...props }, ref) => (
83 |
92 | ))
93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
94 |
95 | const DropdownMenuCheckboxItem = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, children, checked, ...props }, ref) => (
99 |
108 |
109 |
110 |
111 |
112 |
113 | {children}
114 |
115 | ))
116 | DropdownMenuCheckboxItem.displayName =
117 | DropdownMenuPrimitive.CheckboxItem.displayName
118 |
119 | const DropdownMenuRadioItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
140 |
141 | const DropdownMenuLabel = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef & {
144 | inset?: boolean
145 | }
146 | >(({ className, inset, ...props }, ref) => (
147 |
156 | ))
157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
158 |
159 | const DropdownMenuSeparator = React.forwardRef<
160 | React.ElementRef,
161 | React.ComponentPropsWithoutRef
162 | >(({ className, ...props }, ref) => (
163 |
168 | ))
169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
170 |
171 | const DropdownMenuShortcut = ({
172 | className,
173 | ...props
174 | }: React.HTMLAttributes) => {
175 | return (
176 |
180 | )
181 | }
182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
183 |
184 | export {
185 | DropdownMenu,
186 | DropdownMenuTrigger,
187 | DropdownMenuContent,
188 | DropdownMenuItem,
189 | DropdownMenuCheckboxItem,
190 | DropdownMenuRadioItem,
191 | DropdownMenuLabel,
192 | DropdownMenuSeparator,
193 | DropdownMenuShortcut,
194 | DropdownMenuGroup,
195 | DropdownMenuPortal,
196 | DropdownMenuSub,
197 | DropdownMenuSubContent,
198 | DropdownMenuSubTrigger,
199 | DropdownMenuRadioGroup,
200 | }
201 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/components/ui/moving-border.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import {
4 | motion,
5 | useAnimationFrame,
6 | useMotionTemplate,
7 | useMotionValue,
8 | useTransform,
9 | } from "framer-motion";
10 | import { useRef } from "react";
11 | import { cn } from "@/utils/cn";
12 |
13 | export function Button({
14 | borderRadius = "1.75rem",
15 | children,
16 | as: Component = "button",
17 | containerClassName,
18 | borderClassName,
19 | duration,
20 | className,
21 | ...otherProps
22 | }: {
23 | borderRadius?: string;
24 | children: React.ReactNode;
25 | as?: any;
26 | containerClassName?: string;
27 | borderClassName?: string;
28 | duration?: number;
29 | className?: string;
30 | [key: string]: any;
31 | }) {
32 | return (
33 |
43 |
56 |
57 |
66 | {children}
67 |
68 |
69 | );
70 | }
71 |
72 | export const MovingBorder = ({
73 | children,
74 | duration = 2000,
75 | rx,
76 | ry,
77 | ...otherProps
78 | }: {
79 | children: React.ReactNode;
80 | duration?: number;
81 | rx?: string;
82 | ry?: string;
83 | [key: string]: any;
84 | }) => {
85 | const pathRef = useRef();
86 | const progress = useMotionValue(0);
87 |
88 | useAnimationFrame((time) => {
89 | const length = pathRef.current?.getTotalLength();
90 | if (length) {
91 | const pxPerMillisecond = length / duration;
92 | progress.set((time * pxPerMillisecond) % length);
93 | }
94 | });
95 |
96 | const x = useTransform(
97 | progress,
98 | (val) => pathRef.current?.getPointAtLength(val).x
99 | );
100 | const y = useTransform(
101 | progress,
102 | (val) => pathRef.current?.getPointAtLength(val).y
103 | );
104 |
105 | const transform = useMotionTemplate`translateX(${x}px) translateY(${y}px) translateX(-50%) translateY(-50%)`;
106 |
107 | return (
108 | <>
109 |
117 |
125 |
126 |
135 | {children}
136 |
137 | >
138 | );
139 | };
140 |
--------------------------------------------------------------------------------
/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | ))
24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
25 |
26 | const ScrollBar = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, orientation = "vertical", ...props }, ref) => (
30 |
43 |
44 |
45 | ))
46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
47 |
48 | export { ScrollArea, ScrollBar }
49 |
--------------------------------------------------------------------------------
/src/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, ChevronUp } 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 | span]:line-clamp-1",
23 | className
24 | )}
25 | {...props}
26 | >
27 | {children}
28 |
29 |
30 |
31 |
32 | ))
33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34 |
35 | const SelectScrollUpButton = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 |
48 |
49 | ))
50 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
51 |
52 | const SelectScrollDownButton = React.forwardRef<
53 | React.ElementRef,
54 | React.ComponentPropsWithoutRef
55 | >(({ className, ...props }, ref) => (
56 |
64 |
65 |
66 | ))
67 | SelectScrollDownButton.displayName =
68 | SelectPrimitive.ScrollDownButton.displayName
69 |
70 | const SelectContent = React.forwardRef<
71 | React.ElementRef,
72 | React.ComponentPropsWithoutRef
73 | >(({ className, children, position = "popper", ...props }, ref) => (
74 |
75 |
86 |
87 |
94 | {children}
95 |
96 |
97 |
98 |
99 | ))
100 | SelectContent.displayName = SelectPrimitive.Content.displayName
101 |
102 | const SelectLabel = React.forwardRef<
103 | React.ElementRef,
104 | React.ComponentPropsWithoutRef
105 | >(({ className, ...props }, ref) => (
106 |
111 | ))
112 | SelectLabel.displayName = SelectPrimitive.Label.displayName
113 |
114 | const SelectItem = React.forwardRef<
115 | React.ElementRef,
116 | React.ComponentPropsWithoutRef
117 | >(({ className, children, ...props }, ref) => (
118 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | {children}
133 |
134 | ))
135 | SelectItem.displayName = SelectPrimitive.Item.displayName
136 |
137 | const SelectSeparator = React.forwardRef<
138 | React.ElementRef,
139 | React.ComponentPropsWithoutRef
140 | >(({ className, ...props }, ref) => (
141 |
146 | ))
147 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
148 |
149 | export {
150 | Select,
151 | SelectGroup,
152 | SelectValue,
153 | SelectTrigger,
154 | SelectContent,
155 | SelectLabel,
156 | SelectItem,
157 | SelectSeparator,
158 | SelectScrollUpButton,
159 | SelectScrollDownButton,
160 | }
161 |
--------------------------------------------------------------------------------
/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/src/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 = SheetPrimitive.Portal
17 |
18 | const SheetOverlay = React.forwardRef<
19 | React.ElementRef,
20 | React.ComponentPropsWithoutRef
21 | >(({ className, ...props }, ref) => (
22 |
30 | ))
31 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
32 |
33 | const sheetVariants = cva(
34 | "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",
35 | {
36 | variants: {
37 | side: {
38 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
39 | bottom:
40 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
41 | 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",
42 | right:
43 | "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",
44 | },
45 | },
46 | defaultVariants: {
47 | side: "right",
48 | },
49 | }
50 | )
51 |
52 | interface SheetContentProps
53 | extends React.ComponentPropsWithoutRef,
54 | VariantProps {}
55 |
56 | const SheetContent = React.forwardRef<
57 | React.ElementRef,
58 | SheetContentProps
59 | >(({ side = "right", className, children, ...props }, ref) => (
60 |
61 |
62 |
67 | {children}
68 |
69 |
70 | Close
71 |
72 |
73 |
74 | ))
75 | SheetContent.displayName = SheetPrimitive.Content.displayName
76 |
77 | const SheetHeader = ({
78 | className,
79 | ...props
80 | }: React.HTMLAttributes) => (
81 |
88 | )
89 | SheetHeader.displayName = "SheetHeader"
90 |
91 | const SheetFooter = ({
92 | className,
93 | ...props
94 | }: React.HTMLAttributes) => (
95 |
102 | )
103 | SheetFooter.displayName = "SheetFooter"
104 |
105 | const SheetTitle = React.forwardRef<
106 | React.ElementRef,
107 | React.ComponentPropsWithoutRef
108 | >(({ className, ...props }, ref) => (
109 |
114 | ))
115 | SheetTitle.displayName = SheetPrimitive.Title.displayName
116 |
117 | const SheetDescription = React.forwardRef<
118 | React.ElementRef,
119 | React.ComponentPropsWithoutRef
120 | >(({ className, ...props }, ref) => (
121 |
126 | ))
127 | SheetDescription.displayName = SheetPrimitive.Description.displayName
128 |
129 | export {
130 | Sheet,
131 | SheetPortal,
132 | SheetOverlay,
133 | SheetTrigger,
134 | SheetClose,
135 | SheetContent,
136 | SheetHeader,
137 | SheetFooter,
138 | SheetTitle,
139 | SheetDescription,
140 | }
141 |
--------------------------------------------------------------------------------
/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SwitchPrimitives from "@radix-ui/react-switch"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Switch = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
25 |
26 | ))
27 | Switch.displayName = SwitchPrimitives.Root.displayName
28 |
29 | export { Switch }
30 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider
9 |
10 | const Tooltip = TooltipPrimitive.Root
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
27 | ))
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
29 |
30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
31 |
--------------------------------------------------------------------------------
/src/components/ui/typewriter-effect.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { cn } from "@/lib/utils";
4 | import { motion, stagger, useAnimate, useInView } from "framer-motion";
5 | import { useEffect } from "react";
6 |
7 | export const TypewriterEffect = ({
8 | words,
9 | className,
10 | cursorClassName,
11 | }: {
12 | words: {
13 | text: string;
14 | className?: string;
15 | }[];
16 | className?: string;
17 | cursorClassName?: string;
18 | }) => {
19 | // split text inside of words into array of characters
20 | const wordsArray = words.map((word) => {
21 | return {
22 | ...word,
23 | text: word.text.split(""),
24 | };
25 | });
26 |
27 | const [scope, animate] = useAnimate();
28 | const isInView = useInView(scope);
29 | useEffect(() => {
30 | if (isInView) {
31 | animate(
32 | "span",
33 | {
34 | display: "inline-block",
35 | opacity: 1,
36 | },
37 | {
38 | duration: 0.3,
39 | delay: stagger(0.1),
40 | ease: "easeInOut",
41 | }
42 | );
43 | }
44 | }, [isInView]);
45 |
46 | const renderWords = () => {
47 | return (
48 |
49 | {wordsArray.map((word, idx) => {
50 | return (
51 |
52 | {word.text.map((char, index) => (
53 |
61 | {char}
62 |
63 | ))}
64 |
65 |
66 | );
67 | })}
68 |
69 | );
70 | };
71 | return (
72 |
78 | {renderWords()}
79 |
96 |
97 | );
98 | };
99 |
100 | export const TypewriterEffectSmooth = ({
101 | words,
102 | className,
103 | cursorClassName,
104 | }: {
105 | words: {
106 | text: string;
107 | className?: string;
108 | }[];
109 | className?: string;
110 | cursorClassName?: string;
111 | }) => {
112 | // split text inside of words into array of characters
113 | const wordsArray = words.map((word) => {
114 | return {
115 | ...word,
116 | text: word.text.split(""),
117 | };
118 | });
119 | const renderWords = () => {
120 | return (
121 |
122 | {wordsArray.map((word, idx) => {
123 | return (
124 |
125 | {word.text.map((char, index) => (
126 |
130 | {char}
131 |
132 | ))}
133 |
134 |
135 | );
136 | })}
137 |
138 | );
139 | };
140 |
141 | return (
142 |
143 |
157 |
163 | {renderWords()}{" "}
164 |
{" "}
165 |
166 |
184 |
185 | );
186 | };
187 |
--------------------------------------------------------------------------------
/src/hooks/use-clipboard.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useState } from 'react'
4 |
5 | export function useClipboard({ timeout = 2000 }: { timeout?: number }) {
6 | const [isCopied, setIsCopied] = useState(false)
7 |
8 | const copyToClipboard = (value: string) => {
9 | if (!value) return
10 | if (typeof window === 'undefined' || !navigator.clipboard?.writeText) return
11 |
12 | navigator.clipboard.writeText(value).then(() => {
13 | setIsCopied(true)
14 |
15 | setTimeout(() => {
16 | setIsCopied(false)
17 | }, timeout)
18 | })
19 | }
20 |
21 | return { isCopied, copyToClipboard }
22 | }
--------------------------------------------------------------------------------
/src/lib/icon.tsx:
--------------------------------------------------------------------------------
1 | export const Loadingmoonsvg: React.FunctionComponent> = ({
2 |
3 | }) => (
4 |
5 | );
6 |
7 | export const Cleansvg: React.FunctionComponent> = ({
8 |
9 | }) => (
10 |
11 | );
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/utils/cn.ts:
--------------------------------------------------------------------------------
1 | import { ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss"
2 |
3 | const {
4 | default: flattenColorPalette,
5 | } = require("tailwindcss/lib/util/flattenColorPalette");
6 |
7 | const config = {
8 | darkMode: ["class"],
9 | content: [
10 | './pages/**/*.{ts,tsx}',
11 | './components/**/*.{ts,tsx}',
12 | './app/**/*.{ts,tsx}',
13 | './src/**/*.{ts,tsx}',
14 | ],
15 | prefix: "",
16 | theme: {
17 | container: {
18 | center: true,
19 | padding: "2rem",
20 | screens: {
21 | "2xl": "1400px",
22 | },
23 | },
24 | extend: {
25 | colors: {
26 | border: "hsl(var(--border))",
27 | input: "hsl(var(--input))",
28 | ring: "hsl(var(--ring))",
29 | background: "hsl(var(--background))",
30 | foreground: "hsl(var(--foreground))",
31 | primary: {
32 | DEFAULT: "hsl(var(--primary))",
33 | foreground: "hsl(var(--primary-foreground))",
34 | },
35 | secondary: {
36 | DEFAULT: "hsl(var(--secondary))",
37 | foreground: "hsl(var(--secondary-foreground))",
38 | },
39 | destructive: {
40 | DEFAULT: "hsl(var(--destructive))",
41 | foreground: "hsl(var(--destructive-foreground))",
42 | },
43 | muted: {
44 | DEFAULT: "hsl(var(--muted))",
45 | foreground: "hsl(var(--muted-foreground))",
46 | },
47 | accent: {
48 | DEFAULT: "hsl(var(--accent))",
49 | foreground: "hsl(var(--accent-foreground))",
50 | },
51 | popover: {
52 | DEFAULT: "hsl(var(--popover))",
53 | foreground: "hsl(var(--popover-foreground))",
54 | },
55 | card: {
56 | DEFAULT: "hsl(var(--card))",
57 | foreground: "hsl(var(--card-foreground))",
58 | },
59 | },
60 | borderRadius: {
61 | lg: "var(--radius)",
62 | md: "calc(var(--radius) - 2px)",
63 | sm: "calc(var(--radius) - 4px)",
64 | },
65 | keyframes: {
66 | "accordion-down": {
67 | from: { height: "0" },
68 | to: { height: "var(--radix-accordion-content-height)" },
69 | },
70 | "accordion-up": {
71 | from: { height: "var(--radix-accordion-content-height)" },
72 | to: { height: "0" },
73 | },
74 | shimmer: {
75 | from: {
76 | backgroundPosition: "0 0",
77 | },
78 | to: {
79 | backgroundPosition: "-200% 0",
80 | },
81 | },
82 | },
83 | animation: {
84 | "accordion-down": "accordion-down 0.2s ease-out",
85 | "accordion-up": "accordion-up 0.2s ease-out",
86 | shimmer: "shimmer 2s linear infinite",
87 | },
88 | },
89 | },
90 | plugins: [require("tailwindcss-animate"), addVariablesForColors],
91 | } satisfies Config
92 |
93 | function addVariablesForColors({ addBase, theme }: any) {
94 | let allColors = flattenColorPalette(theme("colors"));
95 | let newVars = Object.fromEntries(
96 | Object.entries(allColors).map(([key, val]) => [`--${key}`, val])
97 | );
98 |
99 | addBase({
100 | ":root": newVars,
101 | });
102 | }
103 |
104 | export default config
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------