From 2783bf7d68c4eeaffcdf481fb78c72226bf36baa Mon Sep 17 00:00:00 2001 From: zaidmukaddam Date: Fri, 9 Aug 2024 22:56:25 +0530 Subject: [PATCH] feat: Added suggestQuestions feature and improve overall UI --- app/actions.ts | 35 +++++++ app/favicon.ico | Bin 15406 -> 15406 bytes app/page.tsx | 246 ++++++++++++++++++++++++++++++++---------------- next.config.mjs | 11 ++- 4 files changed, 210 insertions(+), 82 deletions(-) create mode 100644 app/actions.ts diff --git a/app/actions.ts b/app/actions.ts new file mode 100644 index 0000000..24b9486 --- /dev/null +++ b/app/actions.ts @@ -0,0 +1,35 @@ +'use server'; + +import { generateObject } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +export interface Message { + role: 'user' | 'assistant'; + content: string; +} + +export async function suggestQuestions(history: Message[]) { + 'use server'; + + const { object } = await generateObject({ + model: openai('gpt-4o-mini'), + temperature: 0, + system: +`You are a search engine query generator. You 'have' to create 3 questions for the search engine based on the message history which has been provided to you. +The questions should be open-ended and should encourage further discussion while maintaining the whole context. Limit it to 5-10 words per question. +Always put the user input's context is some way so that the next search knows what to search for exactly. +Never use pronouns in the questions as they blur the context.`, + messages: history, + schema: z.object({ + questions: z.array( + z.string() + ) + .describe('The generated questions based on the message history.') + }), + }); + + return { + questions: object.questions + }; +} \ No newline at end of file diff --git a/app/favicon.ico b/app/favicon.ico index 250f01d78a562761c082b567e0bd84f7cef80cdd..8494e103835265a90b4ab4a7bcc43144b3d10281 100644 GIT binary patch delta 1599 zcmah}T}Wd`6mGnUA-iU?yGCV+q1>9^O%@xX23aZ%C4vw8(3fJ6g7;yebzKNbMGOm8 z@j*o~9^;ExVk;t6S4t4k5Jhm+BFYB*3mQR11fz%#q8riEnK22`mkykJXU=?c<~wtK zKGuJ%?-C@xvlAR?7FAPI6VA`i(b0h?Cnwn7-%m0APieU#YUy>VZL5S`1R>@3@Q2tW6B9cvM4}$yoje*} zFr`OuOX53jQPdw2XNdE+wzhC~b{2Pcck%Z27B4R^@$~eR>8O5VVlqfb*lIIe0X?xi0kX? zI5RWDI9RP#2x+vi@mOhUZf?fDzCLE+f9ye4##@!!?Z&mWHN3dENM0Mi54;%;ZgX=J zA{m7$`t%RS>gp=$^?HwBgrDRZRf77#!2#yy=QDKDl-$k{S`e1=rUWh)i(yVqPAcx+ z4M#^upnX!NUtC;d`c!vl7F1SNrk(}wlizX(U9706z_zwF78(jzSXl7$3WHcgBJ7s1 z2$PANMm9C}vSjT4IxiN%LY4tsV)0XgdQRjQBA%bFiwa3Z+shk{rbL_I4~NC}6oqbCyaV z5Wwv0YK&gpc5T>oCLR4Q2x zPfSeU%E}7TL`faAUMQad{lM43U=Zu->ae7w1eceWnQs)EzzBx7+}zw8{A(!Wwzs!I zWK=U>c%zoH+wG)vWO-Z$ZfR)=T8$RwLeLneWYkJ&DGd$|dW2#w6beDvOWxMi)s-X@ z_kz7_LP=jQJv|)(fTOiZjO(SLx<^S8==Rr^z)DpefjF@YAh-$VvUhv5`1{Gnrmoi0K3`25w6f`<`uQI zwXC}wA0J~+PY+C))h5b`Mvg=oCnqQGMI#!GVq;??=H}*7ykGR{TN!P*0oG$blZh-* kC$t1uU9h4dJQBsCP8Lf_C8d^|{9d{F&x^lHBb6!r4{6a2umAu6 delta 1301 zcma)6OGsO36gK`HLtE1@*dQV5aK}7*EjBor=%P}^%;2Jnfnl)9fLU}=M5JgXpn{{g zP>Ia-7z7`Kjt@{IP%@yS_yDaUqFC@%F=_-|h)TN<#n-y{B`FUJ2^Rl;2lSd&*vi<+U<7S+}yuD8f{9-k$VQfR(%V#j2_*Y;A4*7L%Zher_l)FUO^& zB|JVp{`yJqMZ4I_$_fOd9R`KM4M^TBEG(eaYL!AZ_(NkfDC~E3b}%_PnIo^+gHMye zT3TA*okkE9+}YU~#>U2me0_v6P{_ygaO{ zt3x^R{C;L;CLn07w*vW56{@ZeX)2i3Sp%>2n%lBulEK)IM$5p!08)l%^!N88*;mfA z_V)Io%olDa+St(05I7QlQ@dO)?CI$NZ=%40!63X&bcj>|1qB7DR;y8#VSRl)Qidq= zR53Kjwoa#m?~N~5LP7#m80}iZghC-aKR?H!q9PbIR$Si+EGsJu+$OzNP6c{A9`4h{ z#Rb%wp0d=`RCs3+zQ77*6YJ~igKYCN1HC7TB2^pLqlxT%diRsr%bL((1;H$avZ+UU0+{EtyYUv58+;P zTJ$mW8!?;BynE@+@ArdO{L|Uh)y1(iea+|c@)8RR3(@Ix;=#cI$0>Y-ql_p diff --git a/app/page.tsx b/app/page.tsx index 6fab615..41d3ad5 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,22 +1,32 @@ /* eslint-disable @next/next/no-img-element */ "use client"; -import React, { useRef, useCallback, useState, useEffect, ReactNode, useMemo } from 'react'; +import +React, +{ + useRef, + useCallback, + useState, + useEffect, + ReactNode, + useMemo +} from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { useChat } from 'ai/react'; import { ToolInvocation } from 'ai'; import { toast } from 'sonner'; import { motion, AnimatePresence } from 'framer-motion'; +import Image from 'next/image'; +import { suggestQuestions, Message } from './actions'; import { SearchIcon, - LinkIcon, - Loader2, ChevronDown, FastForward, Sparkles, ArrowRight, - Globe + Globe, + AlignLeft } from 'lucide-react'; import { HoverCard, @@ -33,7 +43,6 @@ import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; -import { ScrollArea } from '@/components/ui/scroll-area'; export default function Home() { const inputRef = useRef(null); @@ -41,7 +50,7 @@ export default function Home() { const [hasSubmitted, setHasSubmitted] = useState(false); const [isAnimating, setIsAnimating] = useState(false); const bottomRef = useRef(null); - const [showToolResults, setShowToolResults] = useState<{ [key: number]: boolean }>({}); + const [suggestedQuestions, setSuggestedQuestions] = useState([]); const [isModelSelectorOpen, setIsModelSelectorOpen] = useState(false); const [selectedModel, setSelectedModel] = useState('Speed'); const [showExamples, setShowExamples] = useState(false) @@ -52,6 +61,14 @@ export default function Home() { model: selectedModel === 'Speed' ? 'gpt-4o-mini' : selectedModel === 'Quality (GPT)' ? 'gpt-4o' : 'claude-3-5-sonnet-20240620', }, maxToolRoundtrips: 1, + onFinish: async (message, { finishReason }) => { + if (finishReason === 'stop') { + const newHistory: Message[] = [{ role: "user", content: lastSubmittedQuery, }, { role: "assistant", content: message.content }]; + const { questions } = await suggestQuestions(newHistory); + setSuggestedQuestions(questions); + } + setIsAnimating(false); + }, onError: (error) => { console.error("Chat error:", error); toast.error("An error occurred. Please try again."); @@ -67,66 +84,93 @@ export default function Home() { const renderToolInvocation = (toolInvocation: ToolInvocation, index: number) => { const args = JSON.parse(JSON.stringify(toolInvocation.args)); const result = 'result' in toolInvocation ? JSON.parse(JSON.stringify(toolInvocation.result)) : null; - + return ( - - - -
-
- - Web Search -
- {!result && ( -
- - Searching the web... -
- )} - {result && ( - {result.results.length} results - )} +
+ {!result ? ( +
+
+ + Searching the web...
- - - {args?.query && ( - - - {args.query} - - )} - {result && ( - -
- {result.results.map((item: any, itemIndex: number) => ( - - - {/* favicon here */} - Favicon - {item.title} - - -

{item.content}

-
- -
- ))} +
+ {[0, 1, 2].map((index) => ( + + ))} +
+
+ ) : + + + +
+
+ +

Web Search Results Found

+
+ {result && ( + {result.results.length} results + )}
-
- )} -
- - + + + {args?.query && ( + + + {args.query} + + )} + {result && ( +
+ {result.results.map((item: any, itemIndex: number) => ( + + + Favicon + {item.title} + + +

{item.content}

+
+ +
+ ))} +
+ )} +
+ + } +
); }; @@ -136,12 +180,12 @@ export default function Home() { return ( - + {index + 1} - Favicon + Favicon {citationLink} @@ -164,7 +208,7 @@ export default function Home() { text, link, })); - }, [content]); // Recompute only if content changes + }, [content]); return ( { setLastSubmittedQuery(query.trim()); setHasSubmitted(true); + setSuggestedQuestions([]); + setIsAnimating(true); await append({ content: query.trim(), role: 'user' @@ -212,19 +258,31 @@ export default function Home() { if (input.trim()) { setMessages([]); setLastSubmittedQuery(input.trim()); - handleSubmit(e); setHasSubmitted(true); setIsAnimating(true); - setShowToolResults({}); + setSuggestedQuestions([]); + handleSubmit(e); } else { toast.error("Please enter a search query."); } }, [input, setMessages, handleSubmit]); + const handleSuggestedQuestionClick = useCallback(async (question: string) => { + setMessages([]); + setLastSubmittedQuery(question.trim()); + setHasSubmitted(true); + setSuggestedQuestions([]); + setIsAnimating(true); + await append({ + content: question.trim(), + role: 'user' + }); + }, [append, setMessages]); + const exampleQueries = [ - "Best programming languages in 2024", - "How to build a responsive website", - "Latest trends in AI technology", + "Meta Llama 3.1 405B", + "Latest on Paris Olympics", + "What is Github Models?", "OpenAI GPT-4o mini" ]; @@ -271,7 +329,7 @@ export default function Home() { setSelectedModel(model.name); setIsModelSelectorOpen(false); }} - className="w-full text-left px-4 py-2 hover:bg-gray-100 flex items-center" + className={`w-full text-left px-4 py-2 hover:bg-gray-100 flex items-center ${models.indexOf(model) === 0 ? 'rounded-t-md' : models.indexOf(model) === models.length - 1 ? 'rounded-b-md' : ''}`} >
@@ -303,7 +361,7 @@ export default function Home() { value={input} onChange={(e) => setInput(e.target.value)} disabled={isLoading} - className="w-full min-h-12 py-3 px-4 bg-muted border border-input rounded-full pr-12 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 text-sm sm:text-base" + className="w-full min-h-12 py-3 px-4 bg-muted border border-input rounded-full pr-12 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-200 focus-visible:ring-offset-2 text-sm sm:text-base" onFocus={() => setShowExamples(true)} onBlur={() => setShowExamples(false)} /> @@ -385,15 +443,15 @@ export default function Home() {
{message.role === 'assistant' && message.content && (
- -

Answer

+ +

Answer

-
+
@@ -405,8 +463,34 @@ export default function Home() { ))}
))} -
+ {suggestedQuestions.length > 0 && ( + +
+ +

Suggested questions

+
+
+ {suggestedQuestions.map((question, index) => ( + + ))} +
+
+ )}
+
@@ -416,7 +500,7 @@ export default function Home() { animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 50 }} transition={{ duration: 0.5 }} - className="fixed bottom-4 transform -translate-x-1/2 w-full max-w-[90%] sm:max-w-md md:max-w-2xl mt-3" + className="fixed bottom-4 transform -translate-x-1/2 w-full max-w-xl md:max-w-2xl mt-3" >
@@ -427,7 +511,7 @@ export default function Home() { value={input} onChange={(e) => setInput(e.target.value)} disabled={isLoading} - className="w-full min-h-12 py-3 px-4 bg-muted border border-input rounded-full pr-12 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 text-sm sm:text-base" + className="w-full min-h-12 py-3 px-4 bg-muted border border-input rounded-full pr-12 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-200 focus-visible:ring-offset-2 text-sm sm:text-base" />