From 7177d27229944801a90b0db93c08f5c54beaa6c7 Mon Sep 17 00:00:00 2001 From: ali Date: Sat, 13 Jul 2024 02:23:08 +0200 Subject: [PATCH 01/11] gitignore --- examples/ai-master-chef/.gitignore | 54 ++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 examples/ai-master-chef/.gitignore diff --git a/examples/ai-master-chef/.gitignore b/examples/ai-master-chef/.gitignore new file mode 100644 index 0000000..54a02ee --- /dev/null +++ b/examples/ai-master-chef/.gitignore @@ -0,0 +1,54 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env +.env*.local +.copy.local.env +.copy.remote.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# Supabase +seed.sql +xseed.sql +xxseed.sql +-seed.sql +/supabase/seed.sql +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ + +# No lock files. +package-lock.json +yarn.lock +dist From 48b21e3a1126bb1a898317ddcad1d4bb867dfea6 Mon Sep 17 00:00:00 2001 From: ali Date: Sat, 13 Jul 2024 02:23:42 +0200 Subject: [PATCH 02/11] opening for ai master chef pipe --- .../ai-master-chef/components/opening.tsx | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100755 examples/ai-master-chef/components/opening.tsx diff --git a/examples/ai-master-chef/components/opening.tsx b/examples/ai-master-chef/components/opening.tsx new file mode 100755 index 0000000..713e9a5 --- /dev/null +++ b/examples/ai-master-chef/components/opening.tsx @@ -0,0 +1,81 @@ +import Link from 'next/link' + +export function Opening() { + return ( +
+
+
+
+ + Chatbot Example + +
+ +
+
+

+ AI Master Chef by a + + pipe on ⌘ Langbase + +

+
+ Ship hyper-personalized AI assistants with memory. +
+
+ +
+

Learn more by checking out:

+
+ + 1. + Fork this AI Master Chef Pipe on ⌘ Langbase + + + 2. + Use LangUI.dev's open source code compnents + + + + 3. + Go through Documentaion: Pipe Quickstart + + + 4. + + Learn more about Pipes & Memory features on ⌘ Langbase + + +
+
+
+
+
+ ) +} + +// Description Link +function Dlink({ + href, + children, + ...props +}: { + href: string + children: React.ReactNode + [key: string]: any +}) { + return ( + + {children} + + ) +} From f415fdfab2cc9fd5b6159d0abde6028003949dc0 Mon Sep 17 00:00:00 2001 From: ali Date: Sat, 13 Jul 2024 02:23:57 +0200 Subject: [PATCH 03/11] header for ai master chef pipe --- examples/ai-master-chef/components/header.tsx | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100755 examples/ai-master-chef/components/header.tsx diff --git a/examples/ai-master-chef/components/header.tsx b/examples/ai-master-chef/components/header.tsx new file mode 100755 index 0000000..457b1d0 --- /dev/null +++ b/examples/ai-master-chef/components/header.tsx @@ -0,0 +1,47 @@ +import { buttonVariants } from '@/components/ui/button' +import cn from 'mxcn' +import Link from 'next/link' +import { IconFork, IconGitHub } from './ui/icons' + +export async function Header() { + return ( +
+
+

+ + + Langbase + +

+
+ +
+ + + GitHub + + + + + Fork on Langbase + + +
+
+ ) +} From 3a2a591a5f02da66f24552f158f0dc1b1bbe33e4 Mon Sep 17 00:00:00 2001 From: ali Date: Sat, 13 Jul 2024 02:25:48 +0200 Subject: [PATCH 04/11] readme for ai master chef pipe --- examples/ai-master-chef/README.md | 77 +++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100755 examples/ai-master-chef/README.md diff --git a/examples/ai-master-chef/README.md b/examples/ai-master-chef/README.md new file mode 100755 index 0000000..6dfee08 --- /dev/null +++ b/examples/ai-master-chef/README.md @@ -0,0 +1,77 @@ +![License: MIT][mit] [![Fork to ⌘ Langbase][fork]][pipe] + +## Build AI Master Chef with Pipes — ⌘ Langbase + +This chatbot is built by using an AI Pipe on Langbase, it works with 30+ LLMs (OpenAI, Gemini, Mistral, Llama, Gemma, etc), any Data (10M+ context with Memory sets), and any Framework (standard web API you can use with any software). + +Check out the live demo [here][demo]. + +## Features + +- 💬 [AI Master Chef][demo] — Built with an [AI Pipe on ⌘ Langbase][pipe] +- ⚡️ Streaming — Real-time chat experience with streamed responses +- 🗣️ Q/A — Ask questions and get pre-defined answers with your preferred AI model and tone +- 🔋 Responsive and open source — Works on all devices and platforms + +## Learn more + +1. Check the [AI Chatbot Pipe on ⌘ Langbase][pipe] +2. Read the [source code on GitHub][gh] for this example +3. Go through Documentaion: [Pipe Quick Start][qs] +4. Learn more about [Pipes & Memory features on ⌘ Langbase][docs] + +## Get started + +Let's get started with the project: + +To get started with Langbase, you'll need to [create a free personal account on Langbase.com][signup] and verify your email address. _Done? Cool, cool!_ + +1. Fork the [AI Master Chef][pipe] Pipe on ⌘ Langbase. +2. Go to the API tab to copy the Pipe's API key (to be used on server-side only). +3. Download the example project folder from [here][download] or clone the reppository. +4. `cd` into the project directory and open it in your code editor. +5. Duplicate the `.env.example` file in this project and rename it to `.env.local`. +6. Add the following environment variables: + +```sh +# Replace `PIPE_API_KEY` with the copied API key. +NEXT_LB_PIPE_API_KEY="PIPE_API_KEY" + +# Install the dependencies using the following command: +npm install + +# Run the project using the following command: +npm run dev +``` + +Your app template should now be running on [localhost:3000][local]. + +> NOTE: +> This is a Next.js project, so you can build and deploy it to any platform of your choice, like Vercel, Netlify, Cloudflare, etc. + +--- + +## Authors + +This project is created by [Langbase][lb] team members, with contributions from: + +- Ahmad Awais ([@MrAhmadAwais][xaa]) - Founder & CEO, [Langbase][lb] +- Ahmad Bilal ([@AhmadBilalDev][xab]) - Founding Engineer, [Langbase][lb] +- Muhammad-Ali Danish - Software Engineer, [Langbase][lb] + +**_Built by ⌘ [Langbase.com][lb] — Ship hyper-personalized AI assistants with memory!_** + + +[demo]: https://ai-master-chef.langbase.dev +[lb]: https://langbase.com +[pipe]: https://beta.langbase.com/examples/ai-master-chef +[gh]: https://github.com/LangbaseInc/langbase-examples/tree/main/examples/ai-master-chef +[download]:https://download-directory.github.io/?url=https://github.com/LangbaseInc/langbase-examples/tree/main/examples/ai-master-chef +[signup]: https://langbase.fyi/io +[qs]:https://langbase.com/docs/pipe/quickstart +[docs]:https://langbase.com/docs +[xaa]:https://x.com/MrAhmadAwais +[xab]:https://x.com/AhmadBilalDev +[local]:http://localhost:3000 +[mit]: https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge&color=%23000000 +[fork]: https://img.shields.io/badge/FORK%20ON-%E2%8C%98%20Langbase-000000.svg?style=for-the-badge&logo=%E2%8C%98%20Langbase&logoColor=000000 From 6d6bcab794afc4003e9c204b14750a8b42f169bd Mon Sep 17 00:00:00 2001 From: ali Date: Sat, 13 Jul 2024 02:26:37 +0200 Subject: [PATCH 05/11] nextjs chatui --- examples/ai-master-chef/app/api/chat/route.ts | 57 +++ examples/ai-master-chef/app/favicon.ico | Bin 0 -> 15086 bytes examples/ai-master-chef/app/globals.css | 99 +++++ examples/ai-master-chef/app/layout.tsx | 36 ++ examples/ai-master-chef/app/page.tsx | 7 + .../ai-master-chef/components/chat-input.tsx | 75 ++++ .../ai-master-chef/components/chat-list.tsx | 27 ++ .../components/chat-message-actions.tsx | 40 +++ .../components/chat-message.tsx | 77 ++++ .../components/chatbot-page.tsx | 57 +++ .../ai-master-chef/components/markdown.tsx | 9 + .../ai-master-chef/components/prompt-form.tsx | 95 +++++ .../ai-master-chef/components/ui/button.tsx | 70 ++++ .../components/ui/codeblock.tsx | 145 ++++++++ .../ai-master-chef/components/ui/icons.tsx | 339 ++++++++++++++++++ .../components/ui/separator.tsx | 32 ++ .../ai-master-chef/components/ui/textarea.tsx | 23 ++ .../lib/hooks/use-copy-to-clipboard.tsx | 33 ++ .../lib/hooks/use-enter-submit.tsx | 23 ++ examples/ai-master-chef/lib/types.ts | 11 + examples/ai-master-chef/lib/utils.ts | 8 + examples/ai-master-chef/next.config.js | 13 + examples/ai-master-chef/package.json | 49 +++ examples/ai-master-chef/postcss.config.js | 6 + examples/ai-master-chef/prettier.config.cjs | 34 ++ examples/ai-master-chef/public/chatbot.jpg | Bin 0 -> 318121 bytes examples/ai-master-chef/tailwind.config.ts | 177 +++++++++ examples/ai-master-chef/tsconfig.json | 29 ++ 28 files changed, 1571 insertions(+) create mode 100755 examples/ai-master-chef/app/api/chat/route.ts create mode 100644 examples/ai-master-chef/app/favicon.ico create mode 100755 examples/ai-master-chef/app/globals.css create mode 100755 examples/ai-master-chef/app/layout.tsx create mode 100755 examples/ai-master-chef/app/page.tsx create mode 100755 examples/ai-master-chef/components/chat-input.tsx create mode 100755 examples/ai-master-chef/components/chat-list.tsx create mode 100755 examples/ai-master-chef/components/chat-message-actions.tsx create mode 100755 examples/ai-master-chef/components/chat-message.tsx create mode 100755 examples/ai-master-chef/components/chatbot-page.tsx create mode 100755 examples/ai-master-chef/components/markdown.tsx create mode 100755 examples/ai-master-chef/components/prompt-form.tsx create mode 100755 examples/ai-master-chef/components/ui/button.tsx create mode 100755 examples/ai-master-chef/components/ui/codeblock.tsx create mode 100755 examples/ai-master-chef/components/ui/icons.tsx create mode 100755 examples/ai-master-chef/components/ui/separator.tsx create mode 100755 examples/ai-master-chef/components/ui/textarea.tsx create mode 100755 examples/ai-master-chef/lib/hooks/use-copy-to-clipboard.tsx create mode 100755 examples/ai-master-chef/lib/hooks/use-enter-submit.tsx create mode 100755 examples/ai-master-chef/lib/types.ts create mode 100755 examples/ai-master-chef/lib/utils.ts create mode 100755 examples/ai-master-chef/next.config.js create mode 100755 examples/ai-master-chef/package.json create mode 100755 examples/ai-master-chef/postcss.config.js create mode 100755 examples/ai-master-chef/prettier.config.cjs create mode 100644 examples/ai-master-chef/public/chatbot.jpg create mode 100755 examples/ai-master-chef/tailwind.config.ts create mode 100755 examples/ai-master-chef/tsconfig.json diff --git a/examples/ai-master-chef/app/api/chat/route.ts b/examples/ai-master-chef/app/api/chat/route.ts new file mode 100755 index 0000000..95af6ed --- /dev/null +++ b/examples/ai-master-chef/app/api/chat/route.ts @@ -0,0 +1,57 @@ +import { OpenAIStream, StreamingTextResponse } from 'ai' + +export const runtime = 'edge' + +/** + * Stream AI Chat Messages from Langbase + * + * @param req + * @returns + */ +export async function POST(req: Request) { + try { + if (!process.env.NEXT_LB_PIPE_API_KEY) { + throw new Error( + 'Please set NEXT_LB_PIPE_API_KEY in your environment variables.' + ) + } + + const endpointUrl = 'https://api.langbase.com/beta/chat' + + const headers = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${process.env.NEXT_LB_PIPE_API_KEY}` + } + + // Get chat prompt messages and threadId from the client. + const body = await req.json() + const { messages, threadId } = body + + const requestBody = { + messages, + ...(threadId && { threadId }) // Only include threadId if it exists + } + + // Send the request to Langbase API. + const response = await fetch(endpointUrl, { + method: 'POST', + headers, + body: JSON.stringify(requestBody) + }) + + if (!response.ok) { + const res = await response.json() + throw new Error(`Error ${res.error.status}: ${res.error.message}`) + } + + // Handle Langbase response, which is a stream in OpenAI format. + const stream = OpenAIStream(response) + // Respond with a text stream. + return new StreamingTextResponse(stream, { + headers: response.headers + }) + } catch (error: any) { + console.error('Uncaught API Error:', error) + return new Response(JSON.stringify(error), { status: 500 }) + } +} diff --git a/examples/ai-master-chef/app/favicon.ico b/examples/ai-master-chef/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7c6d4a8ab54ca2d2c0415c9a405185a1305dcc9d GIT binary patch literal 15086 zcmdU#M~oCn7KXE7p|L~b&1H}PF(9#k@J={k1;X4A-g_@V2q7A`5(5_&??JdZfMBoK z0))Bj1qmcy7dp|6jp9gkROxahYz1K{Y(BcNfvuuS8LFs@QcUT)QeiQslE((N zu!-%$B%KYMF-pbR=0WwrCbq$lPAaED3zXg}1=%R>d~AaOECoqrl^MAYO2v&am3}aQ z1x!&B{Z^9g?ynPA#zq9+RryN7-~E{V|G@;du*AG|Udb=}SNRXNu(_4Fm-XbYg4d4& zY+y{5SoF3HWCHzgK}$^7~ArACb!fjZKW3BUwa@7hNE6uIr|)TtxKj~|yGKYqyf@89L~ z=g;o@;lqcediCl*)dB3sjvaIPyfTLl9ZD(_fF0dn1+z`WfIry`%3HQ9B+ zzkYqWefze&fB#E%VB>YuC=r$7YWnJ><)mFLM3* zb*WjiW5uMml@;Y(k$|*^nog?Vc||XNZj9TfTg`v6-8jD^sRSkv47GNXwQjWz3i{ zW-Q;oe_wj{?k%lawKCMVZ(n)%@S%M8@Igk57-4vknK^T&JbCgYaLlB_q2+eJS|I#& z>(-T%Cr=u?-@bj5ckkYrwgsP^lWZPs&O7rIW$=)P|B(kBJ$f{B-VLb-`{`#P<$?90 z4$MQ0WqbpA{P?lKh(G7#efS-g|Z_$>r> z>J9eNjz4Yxq3wvQG*SIwM0qkjw zUvMp2v`8*oxM0>R)+~IS`6MqdPpAWN9mIHGOjAZZ;DyegKQD9V&Mj!{;~g(xXAbh_ zrF{3sV$6xGxvX2nWF&CfJo0&m4}!?f9%+?hL>2w8}p?RzkwO-X~z$&z217IGyfDKezWld zF;J1mZ{GOF8^5(}+cu%jMG(IcgA$7-=neW`ty(p?cI}$+bNt22$5qAL`JdgUp^i+r(mk#3xi|0@*R+m6<+w=H! z`2O4di)Nd>SmgbqH-2OOU_Rjq1;t|QIZvE8Vdfye%#kBU3L3xp_m2hMzk`Q8A^R%k zdiL=P7cLBq#nO@SwYlw6|Nfmm;7lTNAE1}mix`al;2b4NES8Q8wmbwz&U%ui(tLhJ zeM+ApqDY?O*r4OHlNCBDZB zvlmu|EIQCtkknq8FDc;$Vfv$#p#xp$jFNgP(=DYax(mXCPIQMQFcybxixb^J?LZ%0 zRIID)jhx9;s8GSL&)l(g>eR{b zG-=XAkWrmMge`267@ymelJc@IpDfC z9?pVBjT)6ypOuN*#CCI~98Z3N9Ow1ir|;jtU$~>?3@J$EpQLbD$DPO+4@NLBD)2Ge71w7o>zj*QDr+e@u zcCZ~f-nqNqwQHBWdGkgtUc6}R(Ei-do;r2P5Z^c87hvGL3f|3|HygZ3Y2;xqt9(}S;jYQIo~ym9z8nDHf5}N*e0c1e_4ly4jn42vB}n7+MaoT$dDmstuDa&i~W+0 zKfq9u@fY?&tWGedrr9YG&E3tk-ypu;6Jjqf$&HSg* z8>Jv0q6{8*y@~(-QVNpu=fi7tl-GB3x;)A5C6nYKgDiD=W#!phWM^l~v}x1Ky34)( z^y$-O#flZus8J)=MqCEjqgIyZ%9Sh49%=2`wbH9sFX_{#kE~w3+I%aC4li$9_K`P_ zwHE%}yLX%QaLbl0vUBH7S+i!1!Q|!DcVrV{4B`mv5?ifbzg{+M*dSZCZk3)rdnSCN z5Z7b-*TqGB`Njg7b?ep{9bo9*y}KKScrYNJfS>&gScxCev2EKnQy1F9|NU`XHlh7i zty(1`M~*Z)_;$;-AM1WV`z7=ru?c-ZpRsmRA289U0sU84|Ium~Urg73ktY>q{N!uM iADgsCq!uoSl(RR+;~4%i%=^+5Z8{p@M$^ literal 0 HcmV?d00001 diff --git a/examples/ai-master-chef/app/globals.css b/examples/ai-master-chef/app/globals.css new file mode 100755 index 0000000..36084b7 --- /dev/null +++ b/examples/ai-master-chef/app/globals.css @@ -0,0 +1,99 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* Zinc */ +/* --background: 240 10% 3.9%; */ +/* --muted: 240 3.7% 15.9%; */ +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + /* --destructive: 0 84.2% 60.2%; */ + --destructive: 2.74 92.59% 62.94%; + --destructive-foreground: 0 0% 98%; + --warning: 46.38 70.61% 48.04%; + --warning-foreground: 120 12.5% 3.14%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 240 5.9% 10%; + --radius: 6px; + --danger: 2.74 92.59% 62.94%; + } + + .dark { + /* --background: 120 12.5% 3.14%; */ + --background: 240, 3%, 9%; + --foreground: 0 0% 98%; + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + /* --muted: 165 10% 7.84%; */ + --muted: 240 3.45% 11.37%; + --muted-foreground: 240 5% 64.9%; + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + /* --destructive: 0 62.8% 30.6%; */ + --destructive: 356.18 70.61% 48.04%; + --destructive-foreground: 0 0% 98%; + --warning: 46.38 70.61% 48.04%; + --warning-foreground: 120 12.5% 3.14%; + /* --border: 240 3.7% 15.9%; */ + --border: 240 2% 14%; + --border-muted: 240 2% 14%; + --input: 240 3.7% 15.9%; + --ring: 240 4.9% 83.9%; + --danger: 356.18 70.61% 48.04%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} + +::selection { + color: hsl(var(--background)); + background: hsl(var(--foreground)); +} + +.google { + display: inline-block; + width: 20px; + height: 20px; + position: relative; + background-size: contain; + background-repeat: no-repeat; + background-position: 50%; + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 48 48'%3E%3Cdefs%3E%3Cpath id='a' d='M44.5 20H24v8.5h11.8C34.7 33.9 30.1 37 24 37c-7.2 0-13-5.8-13-13s5.8-13 13-13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 11.8 2 2 11.8 2 24s9.8 22 22 22c11 0 21-8 21-22 0-1.3-.2-2.7-.5-4z'/%3E%3C/defs%3E%3CclipPath id='b'%3E%3Cuse xlink:href='%23a' overflow='visible'/%3E%3C/clipPath%3E%3Cpath clip-path='url(%23b)' fill='%23FBBC05' d='M0 37V11l17 13z'/%3E%3Cpath clip-path='url(%23b)' fill='%23EA4335' d='M0 11l17 13 7-6.1L48 14V0H0z'/%3E%3Cpath clip-path='url(%23b)' fill='%2334A853' d='M0 37l30-23 7.9 1L48 0v48H0z'/%3E%3Cpath clip-path='url(%23b)' fill='%234285F4' d='M48 48L17 24l-4-3 35-10z'/%3E%3C/svg%3E"); +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/examples/ai-master-chef/app/layout.tsx b/examples/ai-master-chef/app/layout.tsx new file mode 100755 index 0000000..f0fbdaf --- /dev/null +++ b/examples/ai-master-chef/app/layout.tsx @@ -0,0 +1,36 @@ +import { Header } from '@/components/header' +import cn from 'mxcn' +import type { Metadata } from 'next' +import { Inter } from 'next/font/google' +import { Toaster } from 'sonner' +import './globals.css' + +const inter = Inter({ subsets: ['latin'] }) + +export const metadata: Metadata = { + title: 'AI Chatbot - Langbase', + description: 'Build an AI Chatbot with ⌘ Langbase using any LLM model.', + keywords: ['AI', 'Chatbot', 'Langbase'] +} + +export default function RootLayout({ + children +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + +
+
+ +
+
+ {children} +
+
+
+ + + ) +} diff --git a/examples/ai-master-chef/app/page.tsx b/examples/ai-master-chef/app/page.tsx new file mode 100755 index 0000000..f278d23 --- /dev/null +++ b/examples/ai-master-chef/app/page.tsx @@ -0,0 +1,7 @@ +import { Chatbot } from '@/components/chatbot-page' + +export const runtime = 'edge' + +export default function ChatPage() { + return +} diff --git a/examples/ai-master-chef/components/chat-input.tsx b/examples/ai-master-chef/components/chat-input.tsx new file mode 100755 index 0000000..569fbc6 --- /dev/null +++ b/examples/ai-master-chef/components/chat-input.tsx @@ -0,0 +1,75 @@ +import { type UseChatHelpers } from 'ai/react' + +import { PromptForm } from '@/components/prompt-form' +import { Button } from '@/components/ui/button' +import { IconRegenerate, IconStop } from '@/components/ui/icons' + +export interface ChatInputProps + extends Pick< + UseChatHelpers, + | 'append' + | 'isLoading' + | 'reload' + | 'messages' + | 'stop' + | 'input' + | 'setInput' + > { + id?: string +} + +export function ChatInput({ + id, + isLoading, + stop, + append, + reload, + input, + setInput, + messages +}: ChatInputProps) { + return ( +
+
+
+ {isLoading ? ( + + ) : ( + messages?.length > 0 && ( + + ) + )} +
+
+ { + await append({ + content: value, + role: 'user' + }) + }} + input={input} + setInput={setInput} + isLoading={isLoading} + /> +
+
+
+ ) +} diff --git a/examples/ai-master-chef/components/chat-list.tsx b/examples/ai-master-chef/components/chat-list.tsx new file mode 100755 index 0000000..3207492 --- /dev/null +++ b/examples/ai-master-chef/components/chat-list.tsx @@ -0,0 +1,27 @@ +import { type Message } from 'ai' + +import { Separator } from '@/components/ui/separator' +import { ChatMessage } from '@/components/chat-message' + +export interface ChatList { + messages: Message[] +} + +export function ChatList({ messages }: ChatList) { + if (!messages.length) { + return null + } + + return ( +
+ {messages.map((message, index) => ( +
+ + {index < messages.length - 1 && ( + + )} +
+ ))} +
+ ) +} diff --git a/examples/ai-master-chef/components/chat-message-actions.tsx b/examples/ai-master-chef/components/chat-message-actions.tsx new file mode 100755 index 0000000..37f68b2 --- /dev/null +++ b/examples/ai-master-chef/components/chat-message-actions.tsx @@ -0,0 +1,40 @@ +'use client' + +import { type Message } from 'ai' + +import { Button } from '@/components/ui/button' +import { IconCheck, IconCopy } from '@/components/ui/icons' +import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard' +import cn from 'mxcn' + +interface ChatMessageActionsProps extends React.ComponentProps<'div'> { + message: Message +} + +export function ChatMessageActions({ + message, + className, + ...props +}: ChatMessageActionsProps) { + const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 }) + + const onCopy = () => { + if (isCopied) return + copyToClipboard(message.content) + } + + return ( +
+ +
+ ) +} diff --git a/examples/ai-master-chef/components/chat-message.tsx b/examples/ai-master-chef/components/chat-message.tsx new file mode 100755 index 0000000..c359618 --- /dev/null +++ b/examples/ai-master-chef/components/chat-message.tsx @@ -0,0 +1,77 @@ +import { Message } from 'ai' +import remarkGfm from 'remark-gfm' +import remarkMath from 'remark-math' + +import { ChatMessageActions } from '@/components/chat-message-actions' +import { MemoizedReactMarkdown } from '@/components/markdown' +import { CodeBlock } from '@/components/ui/codeblock' +import { IconSparkles, IconUser } from '@/components/ui/icons' +import cn from 'mxcn' + +export interface ChatMessageProps { + message: Message +} + +export function ChatMessage({ message, ...props }: ChatMessageProps) { + return ( +
+
+ {message.role === 'user' ? : } +
+
+ {children}

+ }, + code({ node, inline, className, children, ...props }) { + if (children.length) { + if (children[0] == '▍') { + return ( + + ) + } + + children[0] = (children[0] as string).replace('`▍`', '▍') + } + + const match = /language-(\w+)/.exec(className || '') + + if (inline) { + return ( + + {children} + + ) + } + + return ( + + ) + } + }} + > + {message.content} +
+ +
+
+ ) +} diff --git a/examples/ai-master-chef/components/chatbot-page.tsx b/examples/ai-master-chef/components/chatbot-page.tsx new file mode 100755 index 0000000..8020337 --- /dev/null +++ b/examples/ai-master-chef/components/chatbot-page.tsx @@ -0,0 +1,57 @@ +'use client' + +import { ChatList } from '@/components/chat-list' +import { useChat, type Message } from 'ai/react' +import cn from 'mxcn' +import { useState } from 'react' +import { toast } from 'sonner' +import { ChatInput } from './chat-input' +import { Opening } from './opening' + +export interface ChatProps extends React.ComponentProps<'div'> { + id?: string // Optional: Thread ID if you want to persist the chat in a DB + initialMessages?: Message[] // Optional: Messages to pre-populate the chat from DB +} + +export function Chatbot({ id, initialMessages, className }: ChatProps) { + const [threadId, setThreadId] = useState(null) + const { messages, append, reload, stop, isLoading, input, setInput } = + useChat({ + api: '/api/chat', + initialMessages, + body: { threadId }, + onResponse(response) { + if (response.status !== 200) { + console.log('✨ ~ response:', response) + toast.error(response.statusText) + } + + // Get Thread ID from response header + const lbThreadId = response.headers.get('lb-thread-id') + setThreadId(lbThreadId) + } + }) + return ( +
+
+ {messages.length ? ( + <> + + + ) : ( + + )} +
+ +
+ ) +} diff --git a/examples/ai-master-chef/components/markdown.tsx b/examples/ai-master-chef/components/markdown.tsx new file mode 100755 index 0000000..d449146 --- /dev/null +++ b/examples/ai-master-chef/components/markdown.tsx @@ -0,0 +1,9 @@ +import { FC, memo } from 'react' +import ReactMarkdown, { Options } from 'react-markdown' + +export const MemoizedReactMarkdown: FC = memo( + ReactMarkdown, + (prevProps, nextProps) => + prevProps.children === nextProps.children && + prevProps.className === nextProps.className +) diff --git a/examples/ai-master-chef/components/prompt-form.tsx b/examples/ai-master-chef/components/prompt-form.tsx new file mode 100755 index 0000000..49b6cb7 --- /dev/null +++ b/examples/ai-master-chef/components/prompt-form.tsx @@ -0,0 +1,95 @@ +import { Button } from '@/components/ui/button' +import { IconChat, IconCommand, IconSpinner } from '@/components/ui/icons' +import { useEnterSubmit } from '@/lib/hooks/use-enter-submit' +import { UseChatHelpers } from 'ai/react' +import * as React from 'react' +import Textarea from 'react-textarea-autosize' + +export interface PromptProps + extends Pick { + onSubmit: (value: string) => Promise + isLoading: boolean +} + +export function PromptForm({ + onSubmit, + input, + setInput, + isLoading +}: PromptProps) { + const { formRef, onKeyDown } = useEnterSubmit() + const inputRef = React.useRef(null) + + React.useEffect(() => { + if (inputRef.current) { + inputRef.current.focus() + } + }, []) + + return ( +
{ + e.preventDefault() + if (!input?.trim()) { + return + } + setInput('') + await onSubmit(input) + }} + ref={formRef} + > +
+
+ +
+