├── .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 | Demo 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 | - [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fsuzushi-tw%2Fcelestialchat&env=ANTHROPIC_API_KEY,TAVILY_API_KEY) 50 | 51 | - [![Deploy to DigitalOcean](https://www.deploytodo.com/do-btn-blue.svg)](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 |
154 |

Bot

155 | 156 |
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 |
182 | 188 |
189 |
190 | 193 | 207 | 208 | 209 |
210 | 217 |
218 |
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 |