diff --git a/src/app/api/gemini-pro/route.ts b/src/app/api/gemini-pro/route.ts index 43792fe..1c39510 100644 --- a/src/app/api/gemini-pro/route.ts +++ b/src/app/api/gemini-pro/route.ts @@ -6,6 +6,7 @@ import { HarmCategory, HarmBlockThreshold, GenerateContentRequest, + Content, } from "@google/generative-ai"; import { GeneralSettings } from "@/types"; @@ -43,22 +44,28 @@ export async function POST(req: Request) { // console.log("safety_settings", safety_settings); // console.log("messages =================>", messages); + // consecutive user messages need to be merged into the same content, 2 consecutive Content objects will error with the Gemini api const reqContent: GenerateContentRequest = { - contents: messages.map((m: Message) => { + contents: messages.reduce((acc: Content[], m: Message) => { if (m.role === "user") { - return { - role: "user", - parts: [{ text: m.content }], - }; - } - if (m.role === "assistant") { - return { + const lastContent = acc[acc.length - 1]; + if (lastContent && lastContent.role === "user") { + lastContent.parts.push({ text: m.content }); + } else { + acc.push({ + role: "user", + parts: [{ text: m.content }], + }); + } + } else if (m.role === "assistant") { + acc.push({ role: "model", parts: [{ text: m.content }], - }; + }); } - return undefined; - }), + + return acc; + }, []), }; const incomingSafetySettings = safety_settings || defaultSafetySettings; diff --git a/src/components/ChatContainer.tsx b/src/components/ChatContainer.tsx index 7d434aa..3b68956 100644 --- a/src/components/ChatContainer.tsx +++ b/src/components/ChatContainer.tsx @@ -1,41 +1,35 @@ "use client"; // components/ChatContainer.tsx -import React, { - useRef, - useEffect, - useState, - FormEvent, - useCallback, -} from "react"; +import React, { useRef, useEffect, FormEvent, useCallback } from "react"; import { Message, useChat } from "ai/react"; import { Card } from "./ui/card"; -import { MarkdownViewer } from "./markdown-viewer/MarkdownViewer"; import { useControlContext } from "@/providers/ControlContext"; import { CommonForm } from "./CommonForm"; import { TypingBubble } from "./TypingBubble"; -import { MessageCircleX, User, Bot } from "lucide-react"; +import { MessageCircleX } from "lucide-react"; import { Button } from "./ui/button"; +import { MessageItem } from "./MessageItem"; export const ChatContainer = () => { const { generalSettings, safetySettings } = useControlContext(); - const [loading, setLoading] = useState(false); - const { messages, setMessages, input, handleInputChange, handleSubmit } = - useChat({ - id: "gemini-pro", - api: `/api/gemini-pro`, - body: { - general_settings: generalSettings, - safety_settings: safetySettings, - }, - onError: () => { - setLoading(false); - }, - onFinish: () => { - setLoading(false); - }, - }); + const { + messages, + setMessages, + input, + handleInputChange, + handleSubmit, + reload, + isLoading, + } = useChat({ + id: "gemini-pro", + api: `/api/gemini-pro`, + body: { + general_settings: generalSettings, + safety_settings: safetySettings, + }, + }); const messagesEndRef = useRef(null); @@ -49,13 +43,32 @@ export const ChatContainer = () => { const handleFormSubmit = async (event: FormEvent) => { event.preventDefault(); - setLoading(true); handleSubmit(event); }; - const handleClearChat = () => { + const handleClearChat = useCallback(() => { setMessages([]); - }; + }, [setMessages]); + + const handleRefreshMessage = useCallback(() => { + reload(); + }, [reload]); + + const messagesRef = useRef(messages); + + useEffect(() => { + messagesRef.current = messages; + }, [messages]); + + const handleRemoveMessage = useCallback( + (id: string) => { + const newMessages = messagesRef.current.filter( + (message) => message.id !== id + ); + setMessages(newMessages); + }, + [setMessages] + ); return (
@@ -73,39 +86,24 @@ export const ChatContainer = () => {
)}
- {messages.map((message: Message) => ( -
-
-
- {message.role === "user" ? ( -
- -
You
-
- ) : ( -
- -
Gemini Pro
-
- )} -
- -
-
+ {messages.map((message, index) => ( + handleRemoveMessage(message.id)} + /> ))} +
- {loading && } + {isLoading && }
void; + onRemove: () => void; +}; + +export const MessageItem: React.FC = memo( + function MessageItem({ + message, + isLastMessage, + isLoading, + onRefresh, + onRemove, + }) { + const isUser = message.role === "user"; + + return ( +
+
+
+
+ {isUser ? : } +
{isUser ? "You" : "Gemini Pro"}
+
+
+ {!isLoading && isLastMessage && isUser && ( + + )} + {!isLoading && isLastMessage && ( + + )} +
+
+ +
+
+ ); + } +); diff --git a/src/components/control/ControlContainer.tsx b/src/components/control/ControlContainer.tsx index 3d54982..46026bb 100644 --- a/src/components/control/ControlContainer.tsx +++ b/src/components/control/ControlContainer.tsx @@ -12,12 +12,12 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import ImageUploadComponent from "../ImageUploadComponent"; import { useControlContext } from "@/providers/ControlContext"; import { ChevronDown } from "lucide-react"; import { SettingsSelector } from "./SettingsSelector"; import { ExtraButtons } from "./ExtraButtons"; +import ImageUploadComponent from "./ImageUploadComponent"; import { settings_data } from "./settings_data"; diff --git a/src/components/ImageUploadComponent.tsx b/src/components/control/ImageUploadComponent.tsx similarity index 96% rename from src/components/ImageUploadComponent.tsx rename to src/components/control/ImageUploadComponent.tsx index 403b4c5..43bd3f9 100644 --- a/src/components/ImageUploadComponent.tsx +++ b/src/components/control/ImageUploadComponent.tsx @@ -1,7 +1,7 @@ "use client"; // components/ImageUploadComponent.tsx import React, { useState, useCallback, memo } from "react"; -import { Card } from "./ui/card"; +import { Card } from "../ui/card"; import { useDropzone } from "react-dropzone"; import { Import, Upload, XCircle } from "lucide-react"; diff --git a/src/components/markdown-viewer/code.tsx b/src/components/markdown-viewer/code.tsx index 0da5a4c..aad43ce 100644 --- a/src/components/markdown-viewer/code.tsx +++ b/src/components/markdown-viewer/code.tsx @@ -50,22 +50,34 @@ interface CodeBlockProps { children?: string | React.ReactNode; } -export const CodeBlock: React.FC = ({ ...props }) => { - const { children, className, ...rest } = props; - const match = /language-(\w+)/.exec(className as string) || []; +export const CodeBlock: React.FC = ({ + children, + className, + ...rest +}) => { + const match = /language-(\w+)/.exec(className || ""); + const language = match?.[1]; - return match ? ( - - {String(children).replace(/\n$/, "")} - - ) : ( - - {children} - - ); + if (language) { + return ( + + {String(children).replace(/\n$/, "")} + + ); + } + + if (typeof children === "string") { + return ( + + {children} + + ); + } + + return null; };