The AI speech should be written quickly and not in this way, so that the site stops for about 5 seconds until the AI finishes its speech in a strange way, as I will attach in the video. This is a very annoying reality. I have been in this problem for a week and I cannot find a solution. I am even starting to lose hope in the project. I will attach all the codes and all the pictures that are posted. What is happening?
[image]
/* eslint-disable @typescript-eslint/no-unused-vars */
"use client"
import { Chat } from "@/components/ui/chat";
import { AppSidebar } from "../components/app-sidebar"
import { Icon } from "@iconify/react";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import { Separator } from "@/components/ui/separator"
import { Form } from '@heroui/form';
import { Badge } from '@heroui/badge'
import { Button, Card, CardBody, CardFooter, CardHeader, Divider, Link } from '@heroui/react';
import Image from "next/image";
import { Input, Textarea } from '@heroui/input'
import { Avatar } from '@heroui/avatar';
import { Tooltip } from '@heroui/tooltip';
import ProseMirrorMarkdownEditor from "@/components/ProseMirrorMarkdownEditor";
import { SidebarInset, SidebarProvider, SidebarTrigger, useSidebar } from "@/components/ui/sidebar"
import { useChat } from "ai/react"
import { useSession } from "next-auth/react";
import { Spinner } from "@heroui/spinner"
import Cookies from "js-cookie";
import { useEffect, useState } from "react";
import { Logo, SideBarPhoneIcon, SideBarPhoneIcon2 } from "@/components/icons";
import SyntaxHighlighter from "react-syntax-highlighter";
import { dark } from "react-syntax-highlighter/dist/esm/styles/prism";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { ButtonGroup, Chip, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger } from "@heroui/react";
import { useTheme } from "next-themes";
import Header from "@/components/Header";
import { toast } from "sonner";
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard";
export default function Page() {
const {
messages,
input,
handleInputChange,
handleSubmit,
append,
reload,
error,
isLoading,
stop,
} = useChat()
const session = useSession()
const { open, toggleSidebar, isMobile } = useSidebar()
const [selectedAi, setSelectedAi] = useState("DeepSeek R1")
const { theme, setTheme } = useTheme();
const savedTheme = Cookies.get("theme");
const [copied, setcopy] = useState(false)
const [src, setsrc] = useState("/LogoBlack.png")
const handleCopy = (input: string) => {
if(copied) return;
navigator.clipboard.writeText(input)
.then(() => {
setcopy(true)
setTimeout(() => {
setcopy(false);
}, 5000);
})
.catch((err) => {
toast.error("Can't copy :(")
});
};
return (
<>
<AppSidebar />
<SidebarInset>
<header className="flex h-16 shrink-0 sticky top-0 z-30 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12 border-b-small bg-white dark:bg-background dark:border-sidebar-border border-gray-200">
<div className="flex items-center gap-2 px-4">
{!isMobile ? (
open ? null : (
<>
<Button
isIconOnly
variant='light'
onPress={toggleSidebar}
>
{
isMobile ? <SideBarPhoneIcon2 size={25} /> : <Icon icon="solar:siderbar-outline" width={25} />
}
</Button>
<Divider orientation="vertical" className="mr-2 h-4" />
</>
)
) : (
<>
<Button
isIconOnly
variant='light'
onPress={toggleSidebar}
>
{
isMobile ? <SideBarPhoneIcon2 size={25} /> : <Icon icon="solar:siderbar-outline" width={25} />
}
</Button>
<Separator orientation="vertical" className="mr-2 h-4" /></>
)}
<Dropdown>
<DropdownTrigger>
<Button className="sm:max-w-none max-w-24 text-foreground-400" startContent={<Icon icon="svg-spinners:blocks-scale" />} variant='light' >{selectedAi}</Button>
</DropdownTrigger>
<DropdownMenu aria-label="Example with disabled actions" selectedKeys={[selectedAi]}>
<DropdownItem onSelect={() => setSelectedAi("Gemini")} endContent={<Chip size='sm' radius='lg' color='warning' className="text-[#C9A9E9] bg-[#643b8d]">Pro</Chip>} key="gemini">Gemini</DropdownItem>
<DropdownItem onSelect={() => setSelectedAi("DeepSeek R1")} endContent={<Chip size='sm' radius='full' className="text-[#17C946] bg-[#17c94680]" color="success">New</Chip>} key="deepseek">DeepSeek R1</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</header>
{messages.length === 0 ? (
<div id="scroll-auto">
<div className="flex-grow flex flex-col items-center gap-4 justify-center min-h-[656px] pb-[5%]">
<div className="justify-start items-start">
<Logo size={150} />
</div>
<div className="text-center">
<h1 className="text-4xl font-bold ">Hi, I'm EgyAI.</h1>
<p className="text-xl text-gray-600 mt-4">How can I help you today?</p>
</div>
<div
id="search-bar"
className="relative w-full flex flex-col justify-center items-center gap-4"
>
<Textarea
className="max-w-[800px] flex flex-col justify-between w-full rounded-2xl border-medium px-3 py-2 pt-3 gap-1.5 hover:border-gray-400 transition-colors duration-200"
onChange={handleInputChange}
autoFocus
value={input}
placeholder="Ask EgyAI..."
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSubmit();
}
}}
endContent={
<>
<Button size="sm" onPress={() => handleSubmit()} variant='light' fullWidth isIconOnly><Icon width={32} icon="solar:link-circle-bold" /></Button>
<Tooltip closeDelay={3} content={`${input.length === 0 ? "Empty Message" : ""}`}>
<div style={{ cursor: `${input.length === 0 ? 'not-allowed' : ""}` }}>
<Button isDisabled={input.length === 0} size="sm" onPress={() => (isLoading ? stop() : handleSubmit())} variant='light' fullWidth isIconOnly><Icon width={32} icon={isLoading ? "solar:stop-circle-bold" : "solar:round-arrow-up-bold"} /></Button>
</div>
</Tooltip>
</>
}
classNames={{
base: "border-none"
}}
/>
</div>
</div>
</div>
) : (<>
<div className="flex flex-col overflow-auto px-3 w-full mx-auto h-full justify-between">
<div className="w-full md:max-w-4xl mx-auto pt-4 pb-2 px-4">
<div className="flex gap-12">
<div className="flex-grow flex flex-col gap-1 relative items-center max-w-full">
<div id="qa-container">
<div id="message" className="flex gap-12 flex-col min-h-0">
<div className="flex-grow flex flex-col gap-2">
<div className="flex-grow flex flex-col gap-2">
{messages.map(message => (
<div key={message.id} className="w-full">
{message.role === 'user' ?
<>
<article
className="w-full text-token-text-primary focus-visible:outline-2 focus-visible:outline-offset-[-4px]"
dir="auto"
data-testid="conversation-turn-2"
data-scroll-anchor="false"
>
<h5 className="sr-only">User said:</h5>
<div className="m-auto text-base py-[18px] px-6">
<div className="mx-auto flex flex-1 gap-4 text-base md:gap-5 lg:gap-6 md:max-w-3xl lg:max-w-[40rem] xl:max-w-[48rem]">
<div className="group/conversation-turn relative flex w-full min-w-0 flex-col @xs/thread:px-0 @sm/thread:px-1.5 @md/thread:px-4">
<div className="flex-col gap-1 md:gap-3">
<div className="flex max-w-full flex-col flex-grow">
<div
data-message-author-role="user"
data-message-id={message.id}
dir="auto"
className="min-h-8 text-message overflow-x-hidden flex w-full flex-col items-end gap-2 whitespace-normal break-words text-start [.text-message+&]:mt-5"
>
<div className="flex w-full flex-col gap-1 empty:hidden items-end rtl:items-start">
<Card className="relative w-fit max-w-[70vw] sm:max-w-[70%] rounded-3xl px-5 py-2.5">
{message.content}
</Card>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</article>
<div className="min-w-[70vw]" id="look-saver cuz i'm noob at frontend"></div>
</>
:
<article
className="w-full text-token-text-primary focus-visible:outline-2 focus-visible:outline-offset-[-4px]"
dir="auto"
data-testid="conversation-turn-3"
data-scroll-anchor="true"
>
<h6 className="sr-only">EgyAi said:</h6>
<div className="m-auto text-base py-[18px] px-6">
<div className="mx-auto flex flex-1 gap-4 text-base md:gap-5 lg:gap-6 md:max-w-3xl lg:max-w-[40rem] xl:max-w-[48rem]">
<div className="group/conversation-turn relative flex w-full min-w-0 flex-col agent-turn @xs/thread:px-0 @sm/thread:px-1.5 @md/thread:px-4">
<div className="flex-col gap-1 md:gap-3">
<div className="flex max-w-full flex-col flex-grow">
<div
data-message-author-role="assistant"
data-message-id={message.id}
dir="auto"
className="min-h-8 text-message flex w-full flex-col items-end gap-2 whitespace-normal break-words text-start [.text-message+&]:mt-5"
data-message-model-slug={selectedAi}
>
<ProseMirrorMarkdownEditor initialMarkdown={message.content } />
</div>
</div>
<div className="mb-2 flex gap-3 empty:hidden -ml-2">
<div className="items-center justify-start rounded-xl p-1 flex">
<div className="flex items-center">
<Tooltip content="SOON" placement='bottom' closeDelay={3}>
<Button disabled isIconOnly fullWidth size="sm" variant='light'><Icon width={23} icon="solar:volume-loud-linear" /></Button>
</Tooltip>
<Tooltip content="Copy" placement='bottom' closeDelay={3}>
<Button onPress={() => handleCopy(message.content)} isIconOnly fullWidth size="sm" variant='light'>{copied ? <Icon width={23} icon="solar:check-read-linear" /> : <Icon width={23} icon="solar:copy-linear" />}</Button>
</Tooltip>
<div className="flex">
<Tooltip content="Good response" placement='bottom' closeDelay={3}>
<Button disabled isIconOnly fullWidth size="sm" variant='light'><Icon width={23} icon="solar:like-linear" /></Button>
</Tooltip>
<Tooltip content="Bad response" placement='bottom' closeDelay={3}>
<Button disabled isIconOnly fullWidth size="sm" variant='light'><Icon width={23} icon="solar:dislike-linear" /></Button>
</Tooltip>
</div>
<span className="hidden" />
<Tooltip content="Reload response" placement='bottom' closeDelay={3}>
<Button onPress={() => reload()} isIconOnly fullWidth size="sm" variant='light'><Icon width={23} icon="solar:refresh-linear" /></Button>
</Tooltip>
</div>
</div>
</div>
<div className="pr-2 lg:pr-0" />
<div className="mt-3 w-full empty:hidden">
<div className="text-center" />
</div>
</div>
</div>
</div>
</div>
</article>}
</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="sticky bottom-0 mx-auto md:max-w-4xl flex justify-center z-30 w-full px-4 py-4 bg-background">
<Textarea
className="max-w-[800px] flex flex-col justify-between w-full rounded-2xl border-medium px-3 py-2 pt-3 gap-1.5 hover:border-gray-400 transition-colors duration-200"
onChange={handleInputChange}
autoFocus
placeholder="Ask EgyAI..."
value={input}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSubmit();
}
}}
description="Keep in mind EgyAI can make mistakes,Check important information"
endContent={
<>
<Button size="sm" onPress={() => handleSubmit()} variant='light' fullWidth isIconOnly><Icon width={32} icon="solar:link-circle-bold" /></Button>
<Tooltip closeDelay={3} content={`${(isLoading ? false : input.length === 0) ? "Empty Message" : ""}`}>
<div style={{ cursor: `${(isLoading ? false : input.length === 0) ? 'not-allowed' : ""}` }}>
<Button isDisabled={(isLoading ? false : input.length === 0)} size="sm" onPress={() => (isLoading ? stop() : handleSubmit())} variant='light' fullWidth isIconOnly><Icon width={32} icon={isLoading ? "solar:stop-circle-bold" : "solar:round-arrow-up-bold"} /></Button>
</div>
</Tooltip>
</>
}
classNames={{
description: "text-center",
base: "border-none"
}}
/>
</div>
</>)}
</SidebarInset >
</>
)
}
MarkDown
import React, { useEffect, useRef } from "react";
import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { schema } from "prosemirror-schema-basic";
import { defaultMarkdownParser } from "prosemirror-markdown";
import hljs from "highlight.js"; // Import highlight.js
import "highlight.js/styles/github-dark.css"; // Choose any theme
// Function to apply syntax highlighting to code blocks
function addCodeHighlighting(view) {
const codeBlocks = view.dom.querySelectorAll("pre code");
codeBlocks.forEach((block) => {
hljs.highlightElement(block); // Apply syntax highlighting
});
}
// Create a ProseMirror state
function createEditorState(markdownContent) {
const doc = defaultMarkdownParser.parse(markdownContent);
return EditorState.create({ doc });
}
// React component for a read-only ProseMirror Markdown viewer
function ProseMirrorMarkdownViewer({ initialMarkdown }) {
const editorRef = useRef(null);
const viewRef = useRef(null);
useEffect(() => {
// Initialize the editor
const state = createEditorState(initialMarkdown);
const view = new EditorView(editorRef.current, {
state,
dispatchTransaction(transaction) {
const newState = view.state.apply(transaction);
view.updateState(newState);
addCodeHighlighting(view); // Highlight after updates
},
editable: () => false, // Disable editing
});
viewRef.current = view;
addCodeHighlighting(view); // Initial highlighting
// Cleanup on unmount
return () => {
view.destroy();
};
}, [initialMarkdown]);
return <div ref={editorRef} className="prosemirror-container" />;
}
export default ProseMirrorMarkdownViewer;
{
"name": "egpyt-gpt-chat",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@ai-sdk/deepseek": "^0.1.8",
"@ai-sdk/fal": "^0.0.2",
"@ai-sdk/fireworks": "^0.1.8",
"@ai-sdk/google": "^1.1.8",
"@ai-sdk/groq": "^1.1.7",
"@ai-sdk/openai": "^1.1.9",
"@codemirror/highlight": "^0.19.8",
"@codemirror/lang-python": "^6.1.7",
"@codemirror/theme-one-dark": "^6.1.2",
"@fal-ai/client": "^1.2.3",
"@heroui/react": "^2.6.14",
"@iconify/react": "^5.2.0",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"@nanostores/react": "github:ai/react",
"@next/env": "^15.1.6",
"@next/mdx": "^15.1.6",
"@prose-ui/core": "^1.0.4",
"@prose-ui/next": "^1.0.4",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-collapsible": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.5",
"@radix-ui/react-dropdown-menu": "^2.1.5",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.7",
"@react-aria/ssr": "^3.9.7",
"@tabler/icons-react": "^3.29.0",
"@tiptap/core": "^2.11.5",
"@tiptap/react": "^2.11.5",
"@tiptap/starter-kit": "^2.11.5",
"@types/mdx": "^2.0.13",
"@types/react-syntax-highlighter": "^15.5.13",
"ai": "^4.1.16",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"codemirror": "^6.0.1",
"date-fns": "^4.1.0",
"framer-motion": "^11.18.2",
"geist": "^1.3.1",
"highlight.js": "^11.11.1",
"js-cookie": "^3.0.5",
"lucide-react": "^0.474.0",
"next": "14.2.16",
"next-auth": "^4.24.11",
"next-themes": "^0.4.4",
"react": "^18",
"react-dom": "^18",
"react-markdown": "^9.0.3",
"react-remarkable": "^1.1.3",
"react-resizable": "^3.0.5",
"react-syntax-highlighter": "^15.6.1",
"rehype-highlight": "^7.0.2",
"remark-gfm": "^4.0.0",
"remarkable-react": "^1.4.3",
"remeda": "^2.20.0",
"shiki": "^2.3.2",
"sonner": "^1.7.4",
"tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7",
"tiptap-markdown": "^0.3.4",
"usehooks-ts": "^3.1.1"
},
"devDependencies": {
"@iconify-json/solar": "^1.2.2",
"@types/js-cookie": "^3.0.6",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-resizable": "^3.0.8",
"@types/remarkable": "^2.0.8",
"eslint": "^8",
"eslint-config-next": "14.2.16",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}