diff --git a/README.md b/README.md index 7016a4e..9bc5089 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,11 @@ # Gemini Pro Vision Playground - This project is a simple playground for using the Gemini Pro Vision and Gemini Pro AI models. - +I created this to help others building apps with the gemini-pro and gemin-pro-vision models. If you find it helpful, please give a ⭐ https://github.com/e-roy/gemini-pro-vision-playground/assets/70700747/e0e80929-285c-441f-87f5-34d68593b787 - - - ## Getting Started Get your Google AI API key [here](https://ai.google.dev/tutorials/setup) diff --git a/src/app/page.tsx b/src/app/page.tsx index 0b2a9a7..e8aeb8a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -9,11 +9,11 @@ export default function Home() { const { selectedModel } = useControlContext(); return ( -
+
-
+
{selectedModel === "gemini-pro" ? ( ) : ( diff --git a/src/components/ChatContainer.tsx b/src/components/ChatContainer.tsx index 6ac6836..7d434aa 100644 --- a/src/components/ChatContainer.tsx +++ b/src/components/ChatContainer.tsx @@ -9,16 +9,13 @@ import React, { } from "react"; import { Message, useChat } from "ai/react"; import { Card } from "./ui/card"; -import { - HoverCard, - HoverCardContent, - HoverCardTrigger, -} from "@/components/ui/hover-card"; -import { MarkdownViewer } from "./MarkdownViewer"; + +import { MarkdownViewer } from "./markdown-viewer/MarkdownViewer"; import { useControlContext } from "@/providers/ControlContext"; import { CommonForm } from "./CommonForm"; import { TypingBubble } from "./TypingBubble"; -import { MessageCircleX } from "lucide-react"; +import { MessageCircleX, User, Bot } from "lucide-react"; +import { Button } from "./ui/button"; export const ChatContainer = () => { const { generalSettings, safetySettings } = useControlContext(); @@ -63,39 +60,41 @@ export const ChatContainer = () => { return (
+ {messages.length > 0 && ( +
+ +
+ )}
- {messages.length > 0 && ( -
- - -
- -
-
- - Clear chat history - -
-
- )} {messages.map((message: Message) => ( -
+
+
+ {message.role === "user" ? ( +
+ +
You
+
+ ) : ( +
+ +
Gemini Pro
+
+ )} +
diff --git a/src/components/MarkdownViewer.tsx b/src/components/MarkdownViewer.tsx deleted file mode 100644 index 5516efb..0000000 --- a/src/components/MarkdownViewer.tsx +++ /dev/null @@ -1,163 +0,0 @@ -"use client"; -// components/MarkdownViewer.tsx -import React, { useMemo, useState } from "react"; -import ReactMarkdown from "react-markdown"; -import remarkGfm from "remark-gfm"; -import rehypeRaw from "rehype-raw"; -import rehypeSanitize from "rehype-sanitize"; -import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; -import { a11yDark } from "react-syntax-highlighter/dist/cjs/styles/prism"; - -type AnchorProps = { - href: string; - children: React.ReactNode; -}; - -const Anchor: React.FC = ({ href, children }) => { - return ( - - {children} - - ); -}; - -const UlComponent: React.FC> = ({ - children, - ...props -}) => { - return ( -
    - {children} -
- ); -}; - -const OlComponent: React.FC> = ({ - children, - ...props -}) => { - return ( -
    - {children} -
- ); -}; - -const LiComponent: React.FC> = ({ - children, - ...props -}) => { - return ( -
  • - {children} -
  • - ); -}; - -interface IPreProps { - children: React.ReactElement<{ className?: string; children: string }>; -} - -const PreComponent: React.FC = ({ children }) => { - const [hasCopied, setHasCopied] = useState(false); - const language = React.isValidElement(children) - ? children.props.className?.split("-")[1] || "" - : ""; - - const copyToClipboard = React.useCallback(async () => { - if ("clipboard" in navigator) { - try { - await navigator.clipboard.writeText(children?.props?.children); - setHasCopied(true); - setTimeout(() => setHasCopied(false), 2000); - } catch (err) { - console.error("Failed to copy!", err); - } - } - }, [children.props.children]); - - return ( -
    -      
    -
    - {language} -
    - -
    - - {children} -
    - ); -}; - -interface CodeBlockProps { - className?: string; - children?: string | React.ReactNode; -} - -const CodeBlock: React.FC = ({ ...props }) => { - const { children, className, ...rest } = props; - const match = /language-(\w+)/.exec(className as string) || []; - - return match ? ( - - {String(children).replace(/\n$/, "")} - - ) : ( - - {children} - - ); -}; - -const MemoizedAnchor = React.memo(Anchor); -const MemoizedUlComponent = React.memo(UlComponent); -const MemoizedOlComponent = React.memo(OlComponent); -const MemoizedLiComponent = React.memo(LiComponent); -const MemoizedPreComponent = React.memo(PreComponent); -const MemoizedCodeBlock = React.memo(CodeBlock); - -interface IMarkdownViewerProps { - text: string; -} - -export const MarkdownViewer: React.FC = ({ text }) => { - const components = useMemo( - () => ({ - a: MemoizedAnchor, - ul: MemoizedUlComponent, - ol: MemoizedOlComponent, - li: MemoizedLiComponent, - pre: MemoizedPreComponent, - code: MemoizedCodeBlock, - }), - [] - ); - - return ( - - {text} - - ); -}; diff --git a/src/components/VisionContainer.tsx b/src/components/VisionContainer.tsx index c840eab..f3756cf 100644 --- a/src/components/VisionContainer.tsx +++ b/src/components/VisionContainer.tsx @@ -4,7 +4,7 @@ import React, { useState, useCallback } from "react"; import { useControlContext } from "@/providers/ControlContext"; import { Card } from "@/components/ui/card"; -import { MarkdownViewer } from "./MarkdownViewer"; +import { MarkdownViewer } from "./markdown-viewer/MarkdownViewer"; import { CommonForm } from "./CommonForm"; import { TypingBubble } from "./TypingBubble"; diff --git a/src/components/markdown-viewer/MarkdownViewer.tsx b/src/components/markdown-viewer/MarkdownViewer.tsx new file mode 100644 index 0000000..dcd1d69 --- /dev/null +++ b/src/components/markdown-viewer/MarkdownViewer.tsx @@ -0,0 +1,92 @@ +"use client"; +// components/MarkdownViewer.tsx +import React, { useMemo } from "react"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import rehypeRaw from "rehype-raw"; +import rehypeSanitize from "rehype-sanitize"; + +import { UlComponent, OlComponent, LiComponent } from "./list"; + +import { PreComponent, CodeBlock } from "./code"; + +import { + TableComponent, + TheadComponent, + TbodyComponent, + TrComponent, + ThComponent, + TdComponent, +} from "./table"; + +type AnchorProps = { + href: string; + children: React.ReactNode; +}; + +const Anchor: React.FC = ({ href, children }) => { + return ( + + {children} + + ); +}; + +const MemoizedAnchor = React.memo(Anchor); + +const MemoizedUlComponent = React.memo(UlComponent); +const MemoizedOlComponent = React.memo(OlComponent); +const MemoizedLiComponent = React.memo(LiComponent); + +const MemoizedPreComponent = React.memo(PreComponent); +const MemoizedCodeBlock = React.memo(CodeBlock); + +const MemoizedTableComponent = React.memo(TableComponent); +const MemoizedTheadComponent = React.memo(TheadComponent); +const MemoizedTbodyComponent = React.memo(TbodyComponent); +const MemoizedTrComponent = React.memo(TrComponent); +const MemoizedThComponent = React.memo(ThComponent); +const MemoizedTdComponent = React.memo(TdComponent); + +interface IMarkdownViewerProps { + text: string; +} + +export const MarkdownViewer: React.FC = ({ text }) => { + const components = useMemo( + () => ({ + a: MemoizedAnchor, + + ul: MemoizedUlComponent, + ol: MemoizedOlComponent, + li: MemoizedLiComponent, + + pre: MemoizedPreComponent, + code: MemoizedCodeBlock, + + table: MemoizedTableComponent, + thead: MemoizedTheadComponent, + tbody: MemoizedTbodyComponent, + tr: MemoizedTrComponent, + th: MemoizedThComponent, + td: MemoizedTdComponent, + }), + [] + ); + + return ( + + {text} + + ); +}; diff --git a/src/components/markdown-viewer/code.tsx b/src/components/markdown-viewer/code.tsx new file mode 100644 index 0000000..0da5a4c --- /dev/null +++ b/src/components/markdown-viewer/code.tsx @@ -0,0 +1,71 @@ +"use client"; +import React, { useState } from "react"; +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +import { a11yDark } from "react-syntax-highlighter/dist/cjs/styles/prism"; + +interface IPreProps { + children: React.ReactElement<{ className?: string; children: string }>; +} + +export const PreComponent: React.FC = ({ children }) => { + const [hasCopied, setHasCopied] = useState(false); + const language = React.isValidElement(children) + ? children.props.className?.split("-")[1] || "" + : ""; + + const copyToClipboard = React.useCallback(async () => { + if ("clipboard" in navigator) { + try { + await navigator.clipboard.writeText(children?.props?.children); + setHasCopied(true); + setTimeout(() => setHasCopied(false), 2000); + } catch (err) { + console.error("Failed to copy!", err); + } + } + }, [children.props.children]); + + return ( +
    +      
    +
    + {language} +
    + +
    + + {children} +
    + ); +}; + +interface CodeBlockProps { + className?: string; + children?: string | React.ReactNode; +} + +export const CodeBlock: React.FC = ({ ...props }) => { + const { children, className, ...rest } = props; + const match = /language-(\w+)/.exec(className as string) || []; + + return match ? ( + + {String(children).replace(/\n$/, "")} + + ) : ( + + {children} + + ); +}; diff --git a/src/components/markdown-viewer/list.tsx b/src/components/markdown-viewer/list.tsx new file mode 100644 index 0000000..7bbfaaa --- /dev/null +++ b/src/components/markdown-viewer/list.tsx @@ -0,0 +1,32 @@ +export const UlComponent: React.FC> = ({ + children, + ...props +}) => { + return ( +
      + {children} +
    + ); +}; + +export const OlComponent: React.FC> = ({ + children, + ...props +}) => { + return ( +
      + {children} +
    + ); +}; + +export const LiComponent: React.FC> = ({ + children, + ...props +}) => { + return ( +
  • + {children} +
  • + ); +}; diff --git a/src/components/markdown-viewer/table.tsx b/src/components/markdown-viewer/table.tsx new file mode 100644 index 0000000..c3294bf --- /dev/null +++ b/src/components/markdown-viewer/table.tsx @@ -0,0 +1,67 @@ +// components/markdown-viewer/table.tsx +export const TableComponent: React.FC< + React.TableHTMLAttributes +> = ({ children, ...props }) => { + return ( +
    + + {children} +
    +
    + ); +}; + +export const TheadComponent: React.FC< + React.HTMLAttributes +> = ({ children, ...props }) => { + return ( + + {children} + + ); +}; + +export const TbodyComponent: React.FC< + React.HTMLAttributes +> = ({ children, ...props }) => { + return ( + + {children} + + ); +}; + +export const TrComponent: React.FC< + React.HTMLAttributes +> = ({ children, ...props }) => { + return {children}; +}; + +export const ThComponent: React.FC< + React.ThHTMLAttributes +> = ({ children, ...props }) => { + return ( + + {children} + + ); +}; + +export const TdComponent: React.FC< + React.TdHTMLAttributes +> = ({ children, ...props }) => { + return ( + + {children} + + ); +}; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index a0518ae..411e369 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -9,7 +9,7 @@ const Card = React.forwardRef<