diff --git a/app/actions.ts b/app/actions.ts index df5f11d..bd5d8a1 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -4,17 +4,12 @@ import { generateObject } from 'ai'; import { createOpenAI as createGroq } from '@ai-sdk/openai'; import { z } from 'zod'; -export interface Message { - role: 'user' | 'assistant'; - content: string; -} - const groq = createGroq({ baseURL: 'https://api.groq.com/openai/v1', apiKey: process.env.GROQ_API_KEY, }); -export async function suggestQuestions(history: Message[]) { +export async function suggestQuestions(history: any[]) { 'use server'; const { object } = await generateObject({ @@ -31,10 +26,7 @@ For location based conversations, always generate questions that are about the c 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.') + questions: z.array(z.string()).describe('The generated questions based on the message history.') }), }); diff --git a/app/page.tsx b/app/page.tsx index f83dce2..012b9a6 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -12,14 +12,13 @@ React, memo } from 'react'; import ReactMarkdown, { Components } from 'react-markdown'; -import { useRouter } from 'next/navigation'; 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 { suggestQuestions } from './actions'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { @@ -41,8 +40,6 @@ import { MapPin, Star, Plus, - Terminal, - ImageIcon, Download, } from 'lucide-react'; import { @@ -51,7 +48,6 @@ import { HoverCardTrigger, } from "@/components/ui/hover-card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" - import { Accordion, AccordionContent, @@ -85,7 +81,6 @@ import { import { GitHubLogoIcon } from '@radix-ui/react-icons'; import { Skeleton } from '@/components/ui/skeleton'; import Link from 'next/link'; -import { cn } from '@/lib/utils'; export const maxDuration = 60; @@ -97,26 +92,24 @@ declare global { } export default function Home() { - const router = useRouter(); const inputRef = useRef(null); const [lastSubmittedQuery, setLastSubmittedQuery] = useState(""); const [hasSubmitted, setHasSubmitted] = useState(false); - const [isAnimating, setIsAnimating] = useState(false); const bottomRef = useRef(null); const [suggestedQuestions, setSuggestedQuestions] = useState([]); const [showExamples, setShowExamples] = useState(false) - const [isEditingQuery, setIsEditingQuery] = useState(false); + const [isEditingMessage, setIsEditingMessage] = useState(false); + const [editingMessageIndex, setEditingMessageIndex] = useState(-1); const { isLoading, input, messages, setInput, append, handleSubmit, setMessages } = useChat({ api: '/api/chat', maxToolRoundtrips: 1, onFinish: async (message, { finishReason }) => { if (finishReason === 'stop') { - const newHistory: Message[] = [{ role: "user", content: lastSubmittedQuery, }, { role: "assistant", content: message.content }]; + const newHistory = [...messages, { 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); @@ -1007,6 +1000,14 @@ export default function Home() { MarkdownRenderer.displayName = "MarkdownRenderer"; + const lastUserMessageIndex = useMemo(() => { + for (let i = messages.length - 1; i >= 0; i--) { + if (messages[i].role === 'user') { + return i; + } + } + return -1; + }, [messages]); useEffect(() => { if (bottomRef.current) { @@ -1018,59 +1019,57 @@ export default function Home() { setLastSubmittedQuery(query.trim()); setHasSubmitted(true); setSuggestedQuestions([]); - setIsAnimating(true); await append({ content: query.trim(), role: 'user' }); }, [append]); + const handleSuggestedQuestionClick = useCallback(async (question: string) => { + setLastSubmittedQuery(question.trim()); + setHasSubmitted(true); + setSuggestedQuestions([]); + await append({ + content: question.trim(), + role: 'user' + }); + }, [append]); + const handleFormSubmit = useCallback((e: React.FormEvent) => { e.preventDefault(); if (input.trim()) { - setMessages([]); setLastSubmittedQuery(input.trim()); setHasSubmitted(true); - setIsAnimating(true); setSuggestedQuestions([]); handleSubmit(e); } else { toast.error("Please enter a search query."); } - }, [input, setMessages, handleSubmit]); + }, [input, 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 handleMessageEdit = useCallback((index: number) => { + setIsEditingMessage(true); + setEditingMessageIndex(index); + setInput(messages[index].content); + }, [messages, setInput]); - const handleQueryEdit = useCallback(() => { - setIsAnimating(true) - setIsEditingQuery(true); - setInput(lastSubmittedQuery); - }, [lastSubmittedQuery, setInput]); - - const handleQuerySubmit = useCallback((e: React.FormEvent) => { + const handleMessageUpdate = useCallback((e: React.FormEvent) => { e.preventDefault(); if (input.trim()) { - setLastSubmittedQuery(input.trim()); - setIsEditingQuery(false); - setMessages([]); - setHasSubmitted(true); - setIsAnimating(true); - setSuggestedQuestions([]); - handleSubmit(e); + const updatedMessages = [...messages]; + updatedMessages[editingMessageIndex] = { ...updatedMessages[editingMessageIndex], content: input.trim() }; + setMessages(updatedMessages); + setIsEditingMessage(false); + setEditingMessageIndex(-1); + setInput(''); + append({ + content: input.trim(), + role: 'user' + }); } else { - toast.error("Please enter a search query."); + toast.error("Please enter a valid message."); } - }, [input, setMessages, handleSubmit]); + }, [input, messages, editingMessageIndex, setMessages, setInput, append]); const exampleQueries = [ "Weather in Doha", @@ -1214,94 +1213,63 @@ export default function Home() { - - {hasSubmitted && ( - setIsAnimating(false)} - > -
- - - +
+ {messages.map((message, index) => ( +
+ {message.role === 'user' && ( - {isEditingQuery ? ( -
- setInput(e.target.value)} - className="flex-grow" - /> - - -
- ) : ( - - - -

- {lastSubmittedQuery} -

-
- -

{lastSubmittedQuery}

-
-
-
+ +
+ {isEditingMessage && editingMessageIndex === index ? ( +
+ setInput(e.target.value)} + className="flex-grow" + /> + + +
+ ) : ( +

+ {message.content} +

+ )} +
+ {!isEditingMessage && index === lastUserMessageIndex && ( + )}
- {!isEditingQuery && ( - - - )} -
- - )} - - -
- {messages.map((message, index) => ( -
+ )} {message.role === 'assistant' && message.content && ( -
+
@@ -1327,7 +1295,7 @@ export default function Home() { animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 20 }} transition={{ duration: 0.5 }} - className="w-full max-w-xl sm:max-w-2xl !mb-20 !sm:mb-18" + className="w-full max-w-xl sm:max-w-2xl" >
@@ -1352,7 +1320,7 @@ export default function Home() {
- {hasSubmitted && !isAnimating && ( + {hasSubmitted && (