浏览代码

feat: polished ui + typing animation

BRama10 1 周之前
父节点
当前提交
fdd8e45d3e

+ 0 - 278
agenthub/app/agentchat/_page.tsx

@@ -1,278 +0,0 @@
-'use client'
-
-import React, { useState, useRef, useEffect } from 'react';
-import { ChatEditor } from '@/components/chat/editor/Editor';
-import { useMounted } from '@/lib/mounted';
-
-import { TextInput, ActionIcon, Switch, useMantineTheme, Button, CopyButton, Loader } from '@mantine/core';
-import { Send, Sun, Moon, Plus, Hash, MessageCircle, Clipboard, Check } from 'lucide-react';
-import { SidebarProps, HeaderProps, MessageBubbleProps, MessageListProps, InputAreaProps, Message, Chat } from '@/interfaces/agentchat';
-import ReactMarkdown from 'react-markdown';
-import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
-import { atomDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
-import rehypeRaw from 'rehype-raw';
-
-
-const Sidebar: React.FC<SidebarProps> = ({ chats, activeChat, setActiveChat, addChat, darkMode }) => (
-    <div className={`w-60 flex-shrink-0 ${darkMode ? 'bg-gray-800' : 'bg-gray-200'} p-4 flex flex-col`}>
-        <Button
-            fullWidth
-            leftSection={<Plus size={18} />}
-            onClick={addChat}
-            className={`mb-4 ${darkMode ? 'bg-gray-700 hover:bg-gray-600' : 'bg-gray-300 hover:bg-gray-400'}`}
-        >
-            Add Chat
-        </Button>
-        <div className="space-y-2 overflow-y-auto flex-grow">
-            {chats.map((chat) => (
-                <Button
-                    key={chat.id}
-                    fullWidth
-                    variant={chat.id === activeChat ? 'filled' : 'subtle'}
-                    onClick={() => setActiveChat(chat.id)}
-                    leftSection={<MessageCircle size={18} />}
-                    className={`justify-start ${chat.id === activeChat
-                        ? (darkMode ? 'bg-gray-600' : 'bg-gray-400')
-                        : (darkMode ? 'hover:bg-gray-700' : 'hover:bg-gray-300')
-                        }`}
-                >
-                    {chat.name}
-                </Button>
-            ))}
-        </div>
-    </div>
-);
-
-const Header: React.FC<HeaderProps> = ({ darkMode, setDarkMode }) => {
-    const theme = useMantineTheme();
-    return (
-        <div className={`flex justify-between items-center p-4 border-b ${darkMode ? 'bg-gray-800 text-white border-gray-700' : 'bg-gray-100 text-black border-gray-200'}`}>
-            <div className="flex items-center space-x-3">
-                <Hash size={24} className="text-gray-500" />
-                <h1 className="text-xl font-semibold">General</h1>
-            </div>
-            <div className="flex items-center space-x-3">
-                <Sun size={18} className={darkMode ? 'text-gray-400' : 'text-yellow-500'} />
-                <Switch
-                    checked={darkMode}
-                    onChange={(event) => setDarkMode(event.currentTarget.checked)}
-                    size="md"
-                    color={theme.primaryColor}
-                />
-                <Moon size={18} className={darkMode ? 'text-blue-400' : 'text-gray-400'} />
-            </div>
-        </div>
-    );
-};
-
-
-
-// Improved Markdown component
-interface MarkdownProps {
-    content: string;
-    darkMode: boolean;
-}
-
-const Markdown: React.FC<MarkdownProps> = ({ content, darkMode }) => {
-    console.log(typeof content)
-    console.log(content)
-    return (
-        <ReactMarkdown
-            rehypePlugins={[rehypeRaw]}
-            components={{
-                code({ node, className, children, ...props }) {
-                    const match = /language-(\w+)/.exec(className || '');
-                    const isInline = !match && (props as any).inline;
-                    return isInline ? (
-                        <code className={`${className} ${darkMode ? 'bg-gray-700' : 'bg-gray-200'} rounded px-1 py-0.5`} {...props}>
-                            {children}
-                        </code>
-                    ) : (
-                        <div className="relative">
-                            <div className="flex justify-between items-center px-4 py-2 bg-gray-800 rounded-t-md">
-                                <span className="text-sm text-gray-400">{match ? match[1] : 'text'}</span>
-                                <CopyButton value={String(children).replace(/\n$/, '')}>
-                                    {({ copied, copy }) => (
-                                        <ActionIcon color={copied ? 'teal' : 'gray'} onClick={copy}>
-                                            {copied ? <Check size={16} /> : <Clipboard size={16} />}
-                                        </ActionIcon>
-                                    )}
-                                </CopyButton>
-                            </div>
-                            <SyntaxHighlighter
-                                language={match ? match[1] : 'text'}
-                                style={atomDark as any}
-                                PreTag="div"
-                                className="rounded-b-md"
-                            >
-                                {String(children).replace(/\n$/, '')}
-                            </SyntaxHighlighter>
-                        </div>
-                    );
-                },
-                p: ({ children, ...props }) => <p className="mb-4 last:mb-0" {...props}>{children}</p>,
-                br: ({ ...props }) => <br {...props} />,
-                ul: ({ children }) => <ul className="list-disc pl-6 mb-4">{children}</ul>,
-                ol: ({ children }) => <ol className="list-decimal pl-6 mb-4">{children}</ol>,
-                li: ({ children }) => <li className="mb-2">{children}</li>,
-                h1: ({ children }) => <h1 className="text-2xl font-bold mb-4">{children}</h1>,
-                h2: ({ children }) => <h2 className="text-xl font-bold mb-3">{children}</h2>,
-                h3: ({ children }) => <h3 className="text-lg font-bold mb-2">{children}</h3>,
-                blockquote: ({ children }) => (
-                    <blockquote className="border-l-4 border-gray-300 pl-4 italic my-4">{children}</blockquote>
-                ),
-            }}
-        >
-            {content}
-        </ReactMarkdown>
-    );
-};
-
-// Updated MessageBubble component
-const MessageBubble: React.FC<MessageBubbleProps> = ({ message, darkMode, index, isThinking = false }) => (
-    <div
-      className={`flex items-start space-x-3 py-2 px-6 ${
-        darkMode ? 'hover:bg-gray-600/30' : 'hover:bg-gray-200/50'
-      } transition-colors duration-200 animate-slideIn opacity-0`}
-      style={{ animationDelay: `${index * 0.05}s`, animationFillMode: 'forwards' }}
-    >
-      <div className={`w-10 h-10 rounded-full flex items-center justify-center text-white flex-shrink-0 ${
-        message.sender === 'user' ? 'bg-blue-500' : 'bg-green-500'
-      }`}>
-        {message.sender === 'user' ? 'U' : 'B'}
-      </div>
-      <div className="flex-1 min-w-0">
-        <div className="flex items-baseline space-x-2">
-          <span className={`font-semibold truncate ${darkMode ? 'text-white' : 'text-black'}`}>
-            {message.sender === 'user' ? 'User' : 'Bot'}
-          </span>
-          <span className={`text-xs ${darkMode ? 'text-gray-400' : 'text-gray-500'}`}>
-            {message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
-          </span>
-        </div>
-        <div className={`mt-1 break-words ${darkMode ? 'text-gray-300' : 'text-gray-700'}`}>
-          {isThinking ? (
-            <div className="flex items-center space-x-2 animate-pulse">
-              <Loader size="sm" />
-              <span>Agent is thinking...</span>
-            </div>
-          ) : (
-            <Markdown content={message.text} darkMode={darkMode} />
-          )}
-        </div>
-      </div>
-    </div>
-  );
-
-  const MessageList: React.FC<MessageListProps> = ({ messages, darkMode }) => (
-    <div className={`flex-grow overflow-y-auto ${darkMode ? 'bg-gray-700' : 'bg-white'}`}>
-      <div className="py-4 space-y-1">
-        {messages.map((message, index) => (
-          <MessageBubble 
-            key={message.id} 
-            message={message} 
-            darkMode={darkMode} 
-            index={index}
-            isThinking={message.sender === 'bot' && message.text === ''}
-          />
-        ))}
-      </div>
-    </div>
-  );
-
-const ChatInterface: React.FC = () => {
-    const [messages, setMessages] = useState<Message[]>([]);
-    const [darkMode, setDarkMode] = useState<boolean>(false);
-    const [chats, setChats] = useState<Chat[]>([{ id: 1, name: 'General' }]);
-    const [activeChat, setActiveChat] = useState<number>(1);
-    const messagesEndRef = useRef<HTMLDivElement>(null);
-
-    useEffect(() => {
-        messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
-    }, [messages]);
-
-    const handleSend = (content: string, attachments: File[]) => {
-        if (content.trim() || attachments.length > 0) {
-          const newMessage: Message = {
-            id: Date.now(),
-            text: content,
-            sender: 'user',
-            timestamp: new Date(),
-            attachments: attachments.map(file => file.name),
-          };
-          setMessages([...messages, newMessage]);
-      
-          // Immediately add a "thinking" message for the bot
-        //   const thinkingMessage: Message = {
-        //     id: Date.now() + 1,
-        //     text: '',
-        //     sender: 'bot',
-        //     timestamp: new Date(),
-        //   };
-        //   setMessages(prevMessages => [...prevMessages, thinkingMessage]);
-
-          const noInterpolation = (strings: any, ...values: any) => strings.join('');
-      
-          // Simulate bot response after a delay
-          setTimeout(() => {
-            const botMessage: Message = {
-              id: 1,
-              text: noInterpolation`Here's a sample response with Markdown:
-              
-              # Heading 1
-              ## Heading 2
-              
-              - List item 1
-              - List item 2
-              
-              \`\`\`python
-              def greet(name):
-                  print(f"Hello, {name}!")
-              
-              greet("World")
-              \`\`\`
-              
-              > This is a blockquote.
-              
-              **Bold text** and *italic text*.`,
-              sender: 'bot',
-              timestamp: new Date(),
-            };
-            // setMessages(prevMessages => 
-            //   prevMessages.map(msg => msg.id === thinkingMessage.id ? botMessage : msg)
-            // );
-            setMessages(prevMessages => [...prevMessages, botMessage]);
-            
-          }, 3000); // Increased delay to 3 seconds to make the loading state more noticeable
-        }
-      };
-
-    const addChat = () => {
-        const newChat: Chat = { id: Date.now(), name: `Chat ${chats.length + 1}` };
-        setChats([...chats, newChat]);
-        setActiveChat(newChat.id);
-    };
-
-    const mounted = useMounted();
-
-    return (
-        <div className={`flex h-screen ${darkMode ? 'bg-gray-900' : 'bg-gray-50'}`}>
-            <Sidebar
-                chats={chats}
-                activeChat={activeChat}
-                setActiveChat={setActiveChat}
-                addChat={addChat}
-                darkMode={darkMode}
-            />
-            <div className="flex flex-col flex-grow">
-                <Header darkMode={darkMode} setDarkMode={setDarkMode} />
-                <MessageList messages={messages} darkMode={darkMode} />
-                {mounted && <ChatEditor onSend={handleSend} darkMode={darkMode} />}
-                <div ref={messagesEndRef} />
-            </div>
-        </div>
-    );
-};
-
-
-export default ChatInterface;

+ 259 - 116
agenthub/app/agentchat/page.tsx

@@ -4,8 +4,8 @@ import React, { useState, useRef, useEffect } from 'react';
 import { ChatEditor } from '@/components/chat/editor/Editor';
 import { useMounted } from '@/lib/mounted';
 
-import { TextInput, ActionIcon, Switch, useMantineTheme, Button, CopyButton } from '@mantine/core';
-import { Send, Sun, Moon, Plus, Hash, MessageCircle, Clipboard, Check } from 'lucide-react';
+import { TextInput, ActionIcon, Switch, useMantineTheme, Button, CopyButton, Loader, Tooltip } from '@mantine/core';
+import { Send, Sun, Moon, Plus, Hash, MessageCircle, Clipboard, Check, Lock, Edit2, X } from 'lucide-react';
 import { SidebarProps, HeaderProps, MessageBubbleProps, MessageListProps, InputAreaProps, Message, Chat } from '@/interfaces/agentchat';
 import ReactMarkdown from 'react-markdown';
 import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
@@ -13,57 +13,136 @@ import { atomDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
 import rehypeRaw from 'rehype-raw';
 
 
-const Sidebar: React.FC<SidebarProps> = ({ chats, activeChat, setActiveChat, addChat, darkMode }) => (
-    <div className={`w-60 flex-shrink-0 ${darkMode ? 'bg-gray-800' : 'bg-gray-200'} p-4 flex flex-col`}>
-        <Button
-            fullWidth
-            leftSection={<Plus size={18} />}
-            onClick={addChat}
-            className={`mb-4 ${darkMode ? 'bg-gray-700 hover:bg-gray-600' : 'bg-gray-300 hover:bg-gray-400'}`}
-        >
-            Add Chat
-        </Button>
-        <div className="space-y-2 overflow-y-auto flex-grow">
-            {chats.map((chat) => (
-                <Button
-                    key={chat.id}
-                    fullWidth
-                    variant={chat.id === activeChat ? 'filled' : 'subtle'}
-                    onClick={() => setActiveChat(chat.id)}
-                    leftSection={<MessageCircle size={18} />}
-                    className={`justify-start ${chat.id === activeChat
-                        ? (darkMode ? 'bg-gray-600' : 'bg-gray-400')
-                        : (darkMode ? 'hover:bg-gray-700' : 'hover:bg-gray-300')
-                        }`}
-                >
-                    {chat.name}
-                </Button>
-            ))}
-        </div>
+const AgentLoader = () => {
+    return <div className="flex items-center space-x-2 animate-pulse">
+        <Loader size="sm" />
+        <span>Agent is thinking...</span>
     </div>
-);
+}
 
-const Header: React.FC<HeaderProps> = ({ darkMode, setDarkMode }) => {
-    const theme = useMantineTheme();
+const updateChatName = (chatId: number, newName: string) => {
+    // setChats(prevChats => 
+    //   prevChats.map(chat => 
+    //     chat.id === chatId ? { ...chat, name: newName } : chat
+    //   )
+    // );
+  };
+
+const Sidebar: React.FC<SidebarProps> = ({ chats, activeChat, setActiveChat, addChat, updateChatName, darkMode }) => {
+    const [editingId, setEditingId] = useState<number | null>(null);
+    const [editingName, setEditingName] = useState('');
+  
+    const categoryStyle = "text-xs font-semibold uppercase tracking-wide text-gray-500 mb-2 mt-4 px-2 flex justify-between items-center";
+    const channelStyle = `flex items-center justify-between rounded px-2 py-1.5 text-sm font-medium transition-colors duration-200 ease-in-out cursor-pointer`;
+    const activeChannelStyle = darkMode ? 'bg-gray-700 text-white' : 'bg-gray-300 text-gray-900';
+    const inactiveChannelStyle = darkMode ? 'text-gray-400 hover:bg-gray-700 hover:text-gray-200' : 'text-gray-700 hover:bg-gray-200 hover:text-gray-900';
+  
+    const startEditing = (chat: Chat) => {
+      setEditingId(chat.id);
+      setEditingName(chat.name);
+    };
+  
+    const cancelEditing = () => {
+      setEditingId(null);
+      setEditingName('');
+    };
+  
+    const saveEditing = () => {
+      if (editingId !== null && editingName.trim() !== '') {
+        updateChatName(editingId, editingName.trim());
+        setEditingId(null);
+      }
+    };
+  
     return (
-        <div className={`flex justify-between items-center p-4 border-b ${darkMode ? 'bg-gray-800 text-white border-gray-700' : 'bg-gray-100 text-black border-gray-200'}`}>
-            <div className="flex items-center space-x-3">
-                <Hash size={24} className="text-gray-500" />
-                <h1 className="text-xl font-semibold">General</h1>
-            </div>
-            <div className="flex items-center space-x-3">
-                <Sun size={18} className={darkMode ? 'text-gray-400' : 'text-yellow-500'} />
-                <Switch
-                    checked={darkMode}
-                    onChange={(event) => setDarkMode(event.currentTarget.checked)}
-                    size="md"
-                    color={theme.primaryColor}
-                />
-                <Moon size={18} className={darkMode ? 'text-blue-400' : 'text-gray-400'} />
+      <div className={`w-60 flex-shrink-0 ${darkMode ? 'bg-gray-800' : 'bg-gray-100'} p-3 flex flex-col`}>
+        <div className={`p-4 ${darkMode ? 'bg-gray-700' : 'bg-gray-200'} rounded-lg mb-4`}>
+          <h2 className={`font-bold ${darkMode ? 'text-white' : 'text-gray-800'}`}>Your Workspace</h2>
+        </div>
+        
+        <div className="flex-grow overflow-y-auto">
+          <div className={categoryStyle}>
+            Channels
+            <Tooltip label="Add Channel" position="right">
+              <ActionIcon 
+                onClick={addChat} 
+                variant="subtle" 
+                color={darkMode ? "gray" : "dark"}
+                className="hover:bg-gray-600"
+              >
+                <Plus size={16} />
+              </ActionIcon>
+            </Tooltip>
+          </div>
+          {chats.map((chat) => (
+            <div
+              key={chat.id}
+              className={`${channelStyle} ${chat.id === activeChat ? activeChannelStyle : inactiveChannelStyle}`}
+            >
+              {editingId === chat.id ? (
+                <div className="flex items-center w-full">
+                  <Hash size={16} className="mr-2 flex-shrink-0" />
+                  <TextInput
+                    value={editingName}
+                    onChange={(e) => setEditingName(e.target.value)}
+                    className="flex-grow"
+                    size="xs"
+                    autoFocus
+                    onKeyPress={(e) => e.key === 'Enter' && saveEditing()}
+                  />
+                  <ActionIcon size="sm" onClick={saveEditing} className="ml-1">
+                    <Check size={14} />
+                  </ActionIcon>
+                  <ActionIcon size="sm" onClick={cancelEditing} className="ml-1">
+                    <X size={14} />
+                  </ActionIcon>
+                </div>
+              ) : (
+                <>
+                  <div className="flex items-center flex-grow" onClick={() => setActiveChat(chat.id)}>
+                    <Hash size={16} className="mr-2 flex-shrink-0" />
+                    <span className="truncate">{chat.name}</span>
+                  </div>
+                  <Tooltip label="Rename Channel" position="right">
+                    <ActionIcon 
+                      onClick={() => startEditing(chat)} 
+                      variant="subtle" 
+                      color={darkMode ? "gray" : "dark"}
+                      className="opacity-0 group-hover:opacity-100 transition-opacity duration-200"
+                    >
+                      <Edit2 size={14} />
+                    </ActionIcon>
+                  </Tooltip>
+                </>
+              )}
             </div>
+          ))}
         </div>
+      </div>
     );
-};
+  };
+
+  const Header: React.FC<HeaderProps> = ({ darkMode, setDarkMode }) => {
+    const theme = useMantineTheme();
+    return (
+      <div className={`flex justify-between items-center p-3 border-b ${darkMode ? 'bg-gray-900 text-white border-gray-800' : 'bg-white text-black border-gray-200'}`}>
+        <div className="flex items-center space-x-2">
+          <Hash size={20} className="text-gray-500" />
+          <h1 className="text-lg font-medium">General</h1>
+        </div>
+        <div className="flex items-center space-x-2">
+          <Sun size={16} className={darkMode ? 'text-gray-400' : 'text-yellow-500'} />
+          <Switch
+            checked={darkMode}
+            onChange={(event) => setDarkMode(event.currentTarget.checked)}
+            size="sm"
+            color={theme.primaryColor}
+          />
+          <Moon size={16} className={darkMode ? 'text-blue-400' : 'text-gray-400'} />
+        </div>
+      </div>
+    );
+  };
 
 
 
@@ -74,6 +153,58 @@ interface MarkdownProps {
 }
 
 const Markdown: React.FC<MarkdownProps> = ({ content, darkMode }) => {
+    const [displayedText, setDisplayedText] = useState('');
+    const animationRef = useRef<number | null>(null);
+    const currentIndexRef = useRef(0);
+
+    useEffect(() => {
+        let lastTimestamp: number | null = null;
+    
+        const streamText = (timestamp: number) => {
+            if (lastTimestamp === null) {
+                lastTimestamp = timestamp;
+            }
+    
+            const elapsed = timestamp - lastTimestamp;
+    
+            if (elapsed >= 30) { // Minimum 30ms between updates
+                if (currentIndexRef.current < content.length) {
+                    const chunkSize = Math.floor(Math.random() * 3) + 1;
+                    const nextChunk = content.slice(currentIndexRef.current, currentIndexRef.current + chunkSize);
+    
+                    setDisplayedText(prevText => prevText + nextChunk);
+                    currentIndexRef.current += chunkSize;
+    
+                    // Determine the next delay
+                    let delay = Math.floor(Math.random() * 50) + 30;
+    
+                    if (nextChunk.includes('.') || nextChunk.includes('!') || nextChunk.includes('?')) {
+                        delay += Math.floor(Math.random() * 300) + 200;
+                    } else if (nextChunk.includes(',') || nextChunk.includes(';')) {
+                        delay += Math.floor(Math.random() * 150) + 100;
+                    }
+    
+                    if (nextChunk.length > 5) {
+                        delay += nextChunk.length * 10;
+                    }
+    
+                    lastTimestamp = timestamp + delay;
+                }
+            }
+    
+            animationRef.current = requestAnimationFrame(streamText);
+        };
+    
+        animationRef.current = requestAnimationFrame(streamText);
+
+        return () => {
+            if (animationRef.current !== null) {
+                cancelAnimationFrame(animationRef.current);
+            }
+        };
+    }, [content]);
+
+
     return (
         <ReactMarkdown
             rehypePlugins={[rehypeRaw]}
@@ -82,13 +213,13 @@ const Markdown: React.FC<MarkdownProps> = ({ content, darkMode }) => {
                     const match = /language-(\w+)/.exec(className || '');
                     const isInline = !match && (props as any).inline;
                     return isInline ? (
-                        <code className={`${className} ${darkMode ? 'bg-gray-700' : 'bg-gray-200'} rounded px-1 py-0.5`} {...props}>
+                        <code className={`${className} ${darkMode ? '!bg-gray-700' : '!bg-gray-200'} rounded px-1 py-0.5`} {...props}>
                             {children}
                         </code>
                     ) : (
                         <div className="relative">
                             <div className="flex justify-between items-center px-4 py-2 bg-gray-800 rounded-t-md">
-                                <span className="text-sm text-gray-400">{match ? match[1] : 'text'}</span>
+                                <span className="text-sm !text-[#e06c75]">{match ? match[1] : 'text'}</span>
                                 <CopyButton value={String(children).replace(/\n$/, '')}>
                                     {({ copied, copy }) => (
                                         <ActionIcon color={copied ? 'teal' : 'gray'} onClick={copy}>
@@ -121,46 +252,46 @@ const Markdown: React.FC<MarkdownProps> = ({ content, darkMode }) => {
                 ),
             }}
         >
-            {content}
+            {displayedText}
         </ReactMarkdown>
     );
 };
 
 // Updated MessageBubble component
-const MessageBubble: React.FC<MessageBubbleProps> = ({ message, darkMode, index }) => (
+const MessageBubble: React.FC<MessageBubbleProps> = ({ message, darkMode, index, isThinking = false }) => (
     <div
-        className={`flex items-start space-x-3 py-2 px-6 ${darkMode ? 'hover:bg-gray-600/30' : 'hover:bg-gray-200/50'
-            } transition-colors duration-200 animate-slideIn opacity-0`}
-        style={{ animationDelay: `${index * 0.05}s`, animationFillMode: 'forwards' }}
+      className={`flex items-start space-x-3 py-3 px-4 ${darkMode ? 'hover:bg-gray-800/50' : 'hover:bg-gray-100/50'
+        } transition-colors duration-200 animate-slideIn opacity-0`}
+      style={{ animationDelay: `${index * 0.05}s`, animationFillMode: 'forwards' }}
     >
-        <div className={`w-10 h-10 rounded-full flex items-center justify-center text-white flex-shrink-0 ${message.sender === 'user' ? 'bg-blue-500' : 'bg-green-500'}`}>
-            {message.sender === 'user' ? 'U' : 'B'}
+      <div className={`w-8 h-8 rounded-full flex items-center justify-center text-white text-sm flex-shrink-0 ${message.sender === 'user' ? 'bg-blue-500' : 'bg-green-500'}`}>
+        {message.sender === 'user' ? 'U' : 'B'}
+      </div>
+      <div className="flex-1 min-w-0">
+        <div className="flex items-baseline space-x-2">
+          <span className={`font-medium text-sm truncate ${darkMode ? 'text-gray-200' : 'text-gray-900'}`}>
+            {message.sender === 'user' ? 'User' : 'Bot'}
+          </span>
+          <span className={`text-xs ${darkMode ? 'text-gray-500' : 'text-gray-400'}`}>
+            {message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
+          </span>
         </div>
-        <div className="flex-1 min-w-0">
-            <div className="flex items-baseline space-x-2">
-                <span className={`font-semibold truncate ${darkMode ? 'text-white' : 'text-black'}`}>
-                    {message.sender === 'user' ? 'User' : 'Bot'}
-                </span>
-                <span className={`text-xs ${darkMode ? 'text-gray-400' : 'text-gray-500'}`}>
-                    {message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
-                </span>
-            </div>
-            <div className={`mt-1 break-words ${darkMode ? 'text-gray-300' : 'text-gray-700'}`}>
-                <Markdown content={message.text} darkMode={darkMode} />
-            </div>
+        <div className={`mt-1 text-sm break-words ${darkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+          {isThinking ? <AgentLoader /> : <Markdown content={message.text} darkMode={darkMode} />}
         </div>
+      </div>
     </div>
-);
-
-const MessageList: React.FC<MessageListProps> = ({ messages, darkMode }) => (
-    <div className={`flex-grow overflow-y-auto ${darkMode ? 'bg-gray-700' : 'bg-white'}`}>
-        <div className="py-4 space-y-1">
-            {messages.map((message, index) => (
-                <MessageBubble key={message.id} message={message} darkMode={darkMode} index={index} />
-            ))}
-        </div>
+  );
+
+  const MessageList: React.FC<MessageListProps> = ({ messages, darkMode }) => (
+    <div className={`flex-grow overflow-y-auto ${darkMode ? 'bg-gray-900' : 'bg-gray-50'}`}>
+      <div className="py-4 space-y-1">
+        {messages.map((message, index) => (
+          <MessageBubble key={message.id} message={message} darkMode={darkMode} index={index} isThinking={message.thinking} />
+        ))}
+      </div>
     </div>
-);
+  );
 
 const ChatInterface: React.FC = () => {
     const [messages, setMessages] = useState<Message[]>([]);
@@ -171,7 +302,7 @@ const ChatInterface: React.FC = () => {
 
     useEffect(() => {
         messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
-    }, [messages]);
+      }, [messages]);
 
     const handleSend = (content: string, attachments: File[]) => {
         if (content.trim() || attachments.length > 0) {
@@ -181,37 +312,45 @@ const ChatInterface: React.FC = () => {
                 sender: 'user',
                 timestamp: new Date(),
                 attachments: attachments.map(file => file.name),
+                thinking: false
             };
             setMessages([...messages, newMessage]);
 
             // Handle file uploads here (e.g., to a server)
+            const botMessage: Message = {
+                id: 3,
+                text: `Here's a sample response with Markdown:
+
+# Heading 1
+## Heading 2
+
+- List item 1
+- List item 2
+
+\`\`\`python
+def greet(name):
+  print(f"Hello, {name}!")
+
+greet("World")
+\`\`\`
+
+> This is a blockquote.
+
+**Bold text** and *italic text*.`,
+                sender: 'bot',
+                timestamp: new Date(),
+                thinking: true
+            };
+            setMessages(prevMessages => [...prevMessages, botMessage]);
 
             setTimeout(() => {
-                const botMessage: Message = {
-                    id: Date.now(),
-                    text: `Here's a sample response with Markdown:
-  
-  # Heading 1
-  ## Heading 2
-  
-  - List item 1
-  - List item 2
-  
-  \`\`\`python
-  def greet(name):
-      print(f"Hello, {name}!")
-  
-  greet("World")
-  \`\`\`
-  
-  > This is a blockquote.
-  
-  **Bold text** and *italic text*.`,
-                    sender: 'bot',
-                    timestamp: new Date(),
-                };
-                setMessages(prevMessages => [...prevMessages, botMessage]);
-            }, 1000);
+                setMessages(prevMessages => [...prevMessages].map(message => {
+                    if (message.id == 3) {
+                        return { ...message, thinking: false };
+                    }
+                    return message;
+                }));
+            }, 3000);
         }
     };
 
@@ -225,21 +364,25 @@ const ChatInterface: React.FC = () => {
 
     return (
         <div className={`flex h-screen ${darkMode ? 'bg-gray-900' : 'bg-gray-50'}`}>
-            <Sidebar
-                chats={chats}
-                activeChat={activeChat}
-                setActiveChat={setActiveChat}
-                addChat={addChat}
-                darkMode={darkMode}
-            />
-            <div className="flex flex-col flex-grow">
-                <Header darkMode={darkMode} setDarkMode={setDarkMode} />
-                <MessageList messages={messages} darkMode={darkMode} />
+          <Sidebar
+            chats={chats}
+            activeChat={activeChat}
+            setActiveChat={setActiveChat}
+            addChat={addChat}
+            updateChatName={updateChatName}
+            darkMode={darkMode}
+        />
+          <div className="flex flex-col flex-grow pb-4">
+            <Header darkMode={darkMode} setDarkMode={setDarkMode} />
+            <MessageList messages={messages} darkMode={darkMode} />
+            <div className='w-full flex h-fit justify-center'>
                 {mounted && <ChatEditor onSend={handleSend} darkMode={darkMode} />}
-                <div ref={messagesEndRef} />
             </div>
+            
+            <div ref={messagesEndRef} />
+          </div>
         </div>
-    );
+      );
 };
 
 

+ 2 - 1
agenthub/components/chat/body/markdown.tsx

@@ -78,7 +78,7 @@ export default function Markdown({ content }: IProps) {
                     return match ? (
                         <div className='w-full'>
                             <div className='flex w-full justify-between px-6 bg-white/5 p-2 rounded-t-md items-center'>
-                                <div className='text-base text-[#e06c75] '>
+                                <div className='text-base !text-[#e06c75] '>
                                     {match[1]}
                                 </div>
                                 <CopyButton 
@@ -91,6 +91,7 @@ export default function Markdown({ content }: IProps) {
                             <SyntaxHighlighter
                                 language={match[1]}
                                 style={gruvboxDark}
+                            
                             >
                                 {String(children).replace(/\n$/, '')}
                             </SyntaxHighlighter>

+ 1 - 1
agenthub/components/chat/editor/Editor.tsx

@@ -104,7 +104,7 @@ export const ChatEditor: React.FC<ChatEditorProps> = ({ onSend, darkMode }) => {
 
   return (
     <Paper 
-      className={`p-2 ${darkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg`}
+      className={`p-2 ${darkMode ? '!bg-gray-800' : '!bg-white'} rounded-lg w-[90%]`}
       style={{
         border: `1px solid ${darkMode ? theme.colors.gray[7] : theme.colors.gray[3]}`,
         boxShadow: `0 2px 10px ${darkMode ? 'rgba(0,0,0,0.3)' : 'rgba(0,0,0,0.1)'}`,

+ 2 - 0
agenthub/interfaces/agentchat/index.ts

@@ -1,4 +1,5 @@
 export interface Message {
+    thinking: boolean | undefined;
     id: number;
     text: string;
     sender: 'user' | 'bot';
@@ -39,5 +40,6 @@ export interface SidebarProps {
     activeChat: number;
     setActiveChat: (id: number) => void;
     addChat: () => void;
+    updateChatName: (chatId: number, newName: string) => void;
     darkMode: boolean;
 }

+ 11 - 0
agenthub/styles/global-stylesheet.css

@@ -16891,4 +16891,15 @@ table.inference-table tr th:last-child {
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
+}
+
+pre > .relative  > .rounded-b-md {
+  /* Your custom styles */
+  margin-top: 0px !important;
+  margin-left: 0px !important;
+  margin-right: 0px !important;
+  margin-bottom: 0.5em !important;
+  border-radius: 0 !important;
+  border-bottom-left-radius: 0.3em !important;
+  border-bottom-right-radius: 0.3em !important;
 }