2 Commits f4a91874e9 ... 26f16b3170

Author SHA1 Message Date
  BRama10 26f16b3170 mentions list 15 hours ago
  BRama10 4c2b15c752 mentions & conditional dark mode 22 hours ago

+ 175 - 94
agenthub/app/agentchat/page.tsx

@@ -8,115 +8,196 @@ import { Message, Chat } from '@/interfaces/agentchat';
 import { Sidebar } from '@/components/agentchat/Sidebar';
 import { Header } from '@/components/agentchat/Header';
 import { MessageList } from '@/components/agentchat/MessageList';
-
+import axios from 'axios';
+import { AgentCommand } from '@/components/chat/body/message-box';
+import { baseUrl, serverUrl } from '@/lib/env';
 
 
 
 const updateChatName = (chatId: number, newName: string) => {
-    // setChats(prevChats => 
-    //   prevChats.map(chat => 
-    //     chat.id === chatId ? { ...chat, name: newName } : chat
-    //   )
-    // );
-  };
+  // setChats(prevChats => 
+  //   prevChats.map(chat => 
+  //     chat.id === chatId ? { ...chat, name: newName } : chat
+  //   )
+  // );
+};
+
+
 
 
-  
 
 
 
-  
 
 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),
-                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(() => {
-                setMessages(prevMessages => [...prevMessages].map(message => {
-                    if (message.id == 3) {
-                        return { ...message, thinking: false };
-                    }
-                    return message;
-                }));
-            }, 3000);
+  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);
+
+  function parseText(input: string): string {
+    // Step 1: Replace mention spans with the custom format
+    let parsed = input.replace(/<span class="mention" data-type="mention" data-id="([^"]+)">@[^<]+<\/span>/g, '?>>$1/?>>');
+
+    // Step 2: Convert <br> tags to newlines
+    parsed = parsed.replace(/<br[^>]*>/g, '\n');
+
+    // Step 3: Remove all remaining HTML tags
+    parsed = parsed.replace(/<[^>]+>/g, '');
+
+    // Decode HTML entities (e.g., &quot;, &amp;)
+    parsed = parsed.replace(/&quot;/g, '"')
+      .replace(/&amp;/g, '&')
+      .replace(/&lt;/g, '<')
+      .replace(/&gt;/g, '>')
+      .replace(/&#39;/g, "'");
+
+    return parsed.trim();
+  }
+
+  interface MessageBundle {
+    name: string;
+    content: string;
+  }
+
+
+  function parseNamedContent(inputString: string) {
+    // Regular expression to match the pattern ?>>Name/?>>\s*Content
+    const regex = /\?>>(.*?)\/?>>([^?]*)/g;
+    const results = [];
+
+    // Find all matches
+    let match;
+    while ((match = regex.exec(inputString)) !== null) {
+      // Extract name and content, trim whitespace
+      const name = match[1].trim().slice(0, -2);
+      // Preserve newlines in content but trim surrounding whitespace
+      const content = match[2].replace(/^\s+|\s+$/g, '');
+
+      results.push({
+        name,
+        content
+      });
+    }
+
+    return results;
+  }
+
+  // Ex
+
+
+  useEffect(() => {
+    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+  }, [messages]);
+
+  const handleSend = async (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),
+        thinking: false
+      };
+      setMessages([...messages, newMessage]);
+
+      let messageId = Date.now();
+
+      // Handle file uploads here (e.g., to a server)
+      const botMessage: Message = {
+        id: messageId,
+        text: ``,
+        sender: 'bot',
+        timestamp: new Date(),
+        thinking: true
+      };
+
+      setMessages(prevMessages => [...prevMessages, botMessage]);
+
+      const res = await _(parseNamedContent(parseText(content))[0] as AgentCommand)
+
+      setMessages(prevMessages => [...prevMessages].map(message => {
+        if (message.id == messageId) {
+          return { ...message, thinking: false };
         }
-    };
+        return res.content;
+      }));
+    }
+  };
 
-    const addChat = () => {
-        const newChat: Chat = { id: Date.now(), name: `Chat ${chats.length + 1}` };
-        setChats([...chats, newChat]);
-        setActiveChat(newChat.id);
-    };
+  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}
-            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>
-            
-            <div ref={messagesEndRef} />
-          </div>
+  const _ = async (command: AgentCommand) => {
+    const addAgentResponse = await axios.post(`${baseUrl}/api/proxy`, {
+      type: 'POST',
+      url: `${serverUrl}/add_agent`,
+      payload: {
+        agent_name: command.name,
+        task_input: command.content,
+      }
+    });
+
+    console.log(addAgentResponse.data);
+
+    // Wait for 1050ms
+    await new Promise(resolve => setTimeout(resolve, 1050));
+
+    let recent_response: any;
+
+    try {
+      // Second request: Execute agent
+      const executeAgentResponse = await axios.post(`${baseUrl}/api/proxy`, {
+        type: 'GET',
+        url: `${serverUrl}/execute_agent?pid=${addAgentResponse.data.pid}`,
+      });
+
+      console.log(executeAgentResponse.data);
+      recent_response = executeAgentResponse.data.response.result.content;
+
+      if (typeof recent_response !== 'string') {
+        recent_response = "Agent Had Difficulty Thinking"
+      }
+    } catch (e) {
+      recent_response = "Agent Had Difficulty Thinking"
+    }
+
+
+    //return recent_response
+    return {
+      name: command.name,
+      content: recent_response
+    };
+  }
+
+  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}
+        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>
-      );
+
+        <div ref={messagesEndRef} />
+      </div>
+    </div>
+  );
 };
 
 

+ 54 - 0
agenthub/components/chat/editor/Editor.tsx

@@ -7,20 +7,68 @@ import Paragraph from '@tiptap/extension-paragraph';
 import Text from '@tiptap/extension-text';
 import HardBreak from '@tiptap/extension-hard-break';
 import Placeholder from '@tiptap/extension-placeholder';
+import Mention from '@tiptap/extension-mention'
 import { ActionIcon, Group, Paper, Text as MantineText, useMantineTheme, ScrollArea, Image, Box, Overlay } from '@mantine/core';
 import { Send, Plus, X, FileIcon } from 'lucide-react';
 
+import suggestion from './Suggestion'
+import { baseUrl, serverUrl } from '@/lib/env';
+import axios from 'axios';
+
 export interface ChatEditorProps {
   onSend: (content: string, attachments: File[]) => void;
   darkMode: boolean;
 }
 
+
+
 export const ChatEditor: React.FC<ChatEditorProps> = ({ onSend, darkMode }) => {
   const [attachments, setAttachments] = useState<File[]>([]);
   const [previews, setPreviews] = useState<string[]>([]);
   const [hoverIndex, setHoverIndex] = useState<number | null>(null);
   const fileInputRef = useRef<HTMLInputElement>(null);
   const theme = useMantineTheme();
+  const [agents, setAgents] = useState([]);
+
+  useEffect(() => {
+
+
+    const _ = async () => {
+      const response = await axios.post(`${baseUrl}/api/proxy`, {
+          type: 'GET',
+          url: `${serverUrl}/get_all_agents`,
+      });
+      setAgents(response.data.agents)
+  }
+  _()
+  }, [])
+
+  let currentStyleElement: HTMLStyleElement | null = null;
+
+  const loadStyle = async (isDarkMode: boolean) => {
+    // Remove the current style element if it exists
+    if (currentStyleElement && currentStyleElement.parentNode) {
+      currentStyleElement.parentNode.removeChild(currentStyleElement);
+    }
+
+    const fetchStyle = async (isDarkMode: boolean) => {
+      const response = await fetch(isDarkMode ? '/MentionListV2Dark.scss' : '/MentionListV2Light.scss');
+      return await response.text();
+    };
+
+    // Import the new style
+    const style = await fetchStyle(isDarkMode)
+
+    // Create a new style element
+    currentStyleElement = document.createElement('style');
+    currentStyleElement.textContent = style;
+    document.head.appendChild(currentStyleElement);
+  };
+
+  useEffect(() => {
+    loadStyle(darkMode);
+  }, [darkMode])
+  
 
   const editor = useEditor({
     extensions: [
@@ -35,6 +83,12 @@ export const ChatEditor: React.FC<ChatEditorProps> = ({ onSend, darkMode }) => {
       Placeholder.configure({
         placeholder: 'Type a message...',
       }),
+      Mention.configure({
+        HTMLAttributes: {
+          class: 'mention',
+        },
+        suggestion,
+      }),
     ],
     editorProps: {
       attributes: {

+ 71 - 0
agenthub/components/chat/editor/MentionListV2.tsx

@@ -0,0 +1,71 @@
+import React, {
+  forwardRef, useEffect, useImperativeHandle,
+  useState,
+} from 'react'
+
+export default forwardRef((props: any, ref: any) => {
+  const [selectedIndex, setSelectedIndex] = useState(0)
+
+  const selectItem = (index: any) => {
+    const item = props.items[index]
+
+    if (item) {
+      props.command({ id: item })
+    }
+  }
+
+  const upHandler = () => {
+    setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length)
+  }
+
+  const downHandler = () => {
+    setSelectedIndex((selectedIndex + 1) % props.items.length)
+  }
+
+  const enterHandler = (e: any) => {
+    e.stopImmediatePropagation();
+    e.preventDefault();
+    e.stopPropagation();
+    selectItem(selectedIndex)
+  }
+
+  useEffect(() => setSelectedIndex(0), [props.items])
+
+  useImperativeHandle(ref, () => ({
+    onKeyDown: ({ event }: {event: any}) => {
+      if (event.key === 'ArrowUp') {
+        upHandler()
+        return true
+      }
+
+      if (event.key === 'ArrowDown') {
+        downHandler()
+        return true
+      }
+
+      if (event.key === 'Enter') {
+        enterHandler(event)
+        return true
+      }
+
+      return false
+    },
+  }))
+
+  return (
+    <div className="dropdown-menu">
+      {props.items.length
+        ? props.items.map((item: any, index: any) => (
+          <button
+            className={index === selectedIndex ? 'is-selected' : ''}
+            key={index}
+            onClick={() => selectItem(index)}
+          >
+            {item}
+          </button>
+        ))
+        : <div className="item">No result</div>
+      }
+    </div>
+  )
+})

+ 93 - 0
agenthub/components/chat/editor/Suggestion.ts

@@ -0,0 +1,93 @@
+import { ReactRenderer } from '@tiptap/react'
+import tippy from 'tippy.js'
+
+import MentionListV2 from './MentionListV2'
+
+export default {
+  items: ({ query }: {query: any}) => {
+    return [
+      'Lea Thompson',
+      'Cyndi Lauper',
+      'Tom Cruise',
+      'Madonna',
+      'Jerry Hall',
+      'Joan Collins',
+      'Winona Ryder',
+      'Christina Applegate',
+      'Alyssa Milano',
+      'Molly Ringwald',
+      'Ally Sheedy',
+      'Debbie Harry',
+      'Olivia Newton-John',
+      'Elton John',
+      'Michael J. Fox',
+      'Axl Rose',
+      'Emilio Estevez',
+      'Ralph Macchio',
+      'Rob Lowe',
+      'Jennifer Grey',
+      'Mickey Rourke',
+      'John Cusack',
+      'Matthew Broderick',
+      'Justine Bateman',
+      'Lisa Bonet',
+    ]
+      .filter(item => item.toLowerCase().startsWith(query.toLowerCase()))
+      .slice(0, 5)
+  },
+
+  render: () => {
+    let component: any
+    let popup: any
+
+    return {
+      onStart: (props: any) => {
+        component = new ReactRenderer(MentionListV2, {
+          props,
+          editor: props.editor,
+        })
+
+        if (!props.clientRect) {
+          return
+        }
+
+        popup = tippy('body', {
+          getReferenceClientRect: props.clientRect,
+          appendTo: () => document.body,
+          content: component.element,
+          showOnCreate: true,
+          interactive: true,
+          trigger: 'manual',
+          placement: 'bottom-start',
+        })
+      },
+
+      onUpdate(props: any) {
+        component.updateProps(props)
+
+        if (!props.clientRect) {
+          return
+        }
+
+        popup[0].setProps({
+          getReferenceClientRect: props.clientRect,
+        })
+      },
+
+      onKeyDown(props: any) {
+        if (props.event.key === 'Escape') {
+          popup[0].hide()
+
+          return true
+        }
+
+        return component.ref?.onKeyDown(props)
+      },
+
+      onExit() {
+        popup[0].destroy()
+        component.destroy()
+      },
+    }
+  },
+}

+ 330 - 4
agenthub/package-lock.json

@@ -37,7 +37,7 @@
         "@tiptap/pm": "^2.8.0",
         "@tiptap/react": "^2.8.0",
         "@tiptap/starter-kit": "^2.8.0",
-        "@tiptap/suggestion": "^2.7.2",
+        "@tiptap/suggestion": "^2.8.0",
         "axios": "^1.7.7",
         "class-variance-authority": "^0.7.0",
         "classnames": "^2.5.1",
@@ -63,8 +63,10 @@
         "react-tooltip": "^5.28.0",
         "recharts": "^2.12.7",
         "rehype-raw": "^7.0.0",
+        "sass": "^1.80.3",
         "sonner": "^1.5.0",
         "supabase": "^1.192.5",
+        "tippy.js": "^6.3.7",
         "ui": "*"
       },
       "devDependencies": {
@@ -2151,6 +2153,266 @@
         "node": ">=12.4.0"
       }
     },
+    "node_modules/@parcel/watcher": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz",
+      "integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==",
+      "dependencies": {
+        "detect-libc": "^1.0.3",
+        "is-glob": "^4.0.3",
+        "micromatch": "^4.0.5",
+        "node-addon-api": "^7.0.0"
+      },
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      },
+      "optionalDependencies": {
+        "@parcel/watcher-android-arm64": "2.4.1",
+        "@parcel/watcher-darwin-arm64": "2.4.1",
+        "@parcel/watcher-darwin-x64": "2.4.1",
+        "@parcel/watcher-freebsd-x64": "2.4.1",
+        "@parcel/watcher-linux-arm-glibc": "2.4.1",
+        "@parcel/watcher-linux-arm64-glibc": "2.4.1",
+        "@parcel/watcher-linux-arm64-musl": "2.4.1",
+        "@parcel/watcher-linux-x64-glibc": "2.4.1",
+        "@parcel/watcher-linux-x64-musl": "2.4.1",
+        "@parcel/watcher-win32-arm64": "2.4.1",
+        "@parcel/watcher-win32-ia32": "2.4.1",
+        "@parcel/watcher-win32-x64": "2.4.1"
+      }
+    },
+    "node_modules/@parcel/watcher-android-arm64": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz",
+      "integrity": "sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-darwin-arm64": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.1.tgz",
+      "integrity": "sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-darwin-x64": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.1.tgz",
+      "integrity": "sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-freebsd-x64": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.1.tgz",
+      "integrity": "sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm-glibc": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.1.tgz",
+      "integrity": "sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm64-glibc": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.1.tgz",
+      "integrity": "sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm64-musl": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.1.tgz",
+      "integrity": "sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-x64-glibc": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz",
+      "integrity": "sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-x64-musl": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz",
+      "integrity": "sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-arm64": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz",
+      "integrity": "sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-ia32": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.1.tgz",
+      "integrity": "sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==",
+      "cpu": [
+        "ia32"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-x64": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz",
+      "integrity": "sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
     "node_modules/@pkgjs/parseargs": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -4911,9 +5173,9 @@
       }
     },
     "node_modules/@tiptap/suggestion": {
-      "version": "2.7.2",
-      "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-2.7.2.tgz",
-      "integrity": "sha512-ZJMNuorzQQiKyzoisyeHgPH3kywv0cvQnyz5guZWiAtFWCUbFyB9MSLNuoijubwHWfnZMe4XiW5EqVt1dBmxBw==",
+      "version": "2.8.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-2.8.0.tgz",
+      "integrity": "sha512-ENBgO7a92cZa4gESb0Da5e7PKnHiz77tZr226VLqEdMcp7Lve2jb3Q2uL+cWCJxs7P1l6ZhetUmUiJg+Ee7Wjg==",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
@@ -6361,6 +6623,17 @@
         "node": ">=6"
       }
     },
+    "node_modules/detect-libc": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+      "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+      "bin": {
+        "detect-libc": "bin/detect-libc.js"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
     "node_modules/detect-node-es": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -7927,6 +8200,11 @@
         "node": ">= 4"
       }
     },
+    "node_modules/immutable": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
+      "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw=="
+    },
     "node_modules/import-fresh": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -9586,6 +9864,11 @@
         "node": "^10 || ^12 || >=14"
       }
     },
+    "node_modules/node-addon-api": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+      "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="
+    },
     "node_modules/node-domexception": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
@@ -13473,6 +13756,49 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/sass": {
+      "version": "1.80.3",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.3.tgz",
+      "integrity": "sha512-ptDWyVmDMVielpz/oWy3YP3nfs7LpJTHIJZboMVs8GEC9eUmtZTZhMHlTW98wY4aEorDfjN38+Wr/XjskFWcfA==",
+      "dependencies": {
+        "@parcel/watcher": "^2.4.1",
+        "chokidar": "^4.0.0",
+        "immutable": "^4.0.0",
+        "source-map-js": ">=0.6.2 <2.0.0"
+      },
+      "bin": {
+        "sass": "sass.js"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass/node_modules/chokidar": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
+      "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==",
+      "dependencies": {
+        "readdirp": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 14.16.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/sass/node_modules/readdirp": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz",
+      "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==",
+      "engines": {
+        "node": ">= 14.16.0"
+      },
+      "funding": {
+        "type": "individual",
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
     "node_modules/scheduler": {
       "version": "0.23.2",
       "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",

+ 3 - 1
agenthub/package.json

@@ -38,7 +38,7 @@
     "@tiptap/pm": "^2.8.0",
     "@tiptap/react": "^2.8.0",
     "@tiptap/starter-kit": "^2.8.0",
-    "@tiptap/suggestion": "^2.7.2",
+    "@tiptap/suggestion": "^2.8.0",
     "axios": "^1.7.7",
     "class-variance-authority": "^0.7.0",
     "classnames": "^2.5.1",
@@ -64,8 +64,10 @@
     "react-tooltip": "^5.28.0",
     "recharts": "^2.12.7",
     "rehype-raw": "^7.0.0",
+    "sass": "^1.80.3",
     "sonner": "^1.5.0",
     "supabase": "^1.192.5",
+    "tippy.js": "^6.3.7",
     "ui": "*"
   },
   "devDependencies": {

+ 55 - 0
agenthub/public/MentionListV2Dark.scss

@@ -0,0 +1,55 @@
+/* Dropdown menu */
+.dropdown-menu {
+  background: rgb(31, 41, 55);
+  border: 1px solid var(--gray-4);
+  border-radius: 0.7rem;
+  box-shadow: var(--shadow);
+  display: flex;
+  flex-direction: column;
+  gap: 0.1rem;
+  overflow: auto;
+  padding: 0.4rem;
+  position: relative;
+  color: var(--white);
+
+  button {
+    align-items: center;
+    background-color: transparent;
+    display: flex;
+    gap: 0.25rem;
+    text-align: left;
+    width: 100%;
+
+    &:hover,
+    &:hover.is-selected {
+      background-color: #ccc6c631;
+    }
+
+    &.is-selected {
+      background-color: #ccc6c631;
+    }
+  }
+}
+
+.tiptap {
+  :first-child {
+    margin-top: 0;
+  }
+  .mention {
+    background-color: var(--purple-light-dark);
+    border-radius: 0.4rem;
+    box-decoration-break: clone;
+    color: var(--white);
+    // font-weight: 600;
+    padding: 0.1rem 0.3rem;
+  }
+}
+
+.mention {
+  background-color: var(--purple-light-dark);
+  border-radius: 0.4rem;
+  box-decoration-break: clone;
+  color: var(--white);
+  // font-weight: 600;
+  padding: 0.1rem 0.3rem;
+}

+ 53 - 0
agenthub/public/MentionListV2Light.scss

@@ -0,0 +1,53 @@
+/* Dropdown menu */
+.dropdown-menu {
+    background: var(--white);
+    border: 1px solid var(--gray-1);
+    border-radius: 0.7rem;
+    box-shadow: var(--shadow);
+    display: flex;
+    flex-direction: column;
+    gap: 0.1rem;
+    overflow: auto;
+    padding: 0.4rem;
+    position: relative;
+  
+    button {
+      align-items: center;
+      background-color: transparent;
+      display: flex;
+      gap: 0.25rem;
+      text-align: left;
+      width: 100%;
+  
+      &:hover,
+      &:hover.is-selected {
+        background-color: var(--gray-3);
+      }
+  
+      &.is-selected {
+        background-color: var(--gray-2);
+      }
+    }
+  }
+
+  .tiptap {
+    :first-child {
+      margin-top: 0;
+    }
+  
+    .mention {
+      background-color: var(--purple-light);
+      border-radius: 0.4rem;
+      box-decoration-break: clone;
+      color: var(--purple);
+      padding: 0.1rem 0.3rem;
+    }
+  }
+
+  .mention {
+    background-color: var(--purple-light);
+    border-radius: 0.4rem;
+    box-decoration-break: clone;
+    color: var(--purple);
+    padding: 0.1rem 0.3rem;
+  }

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

@@ -16902,4 +16902,26 @@ pre > .relative  > .rounded-b-md {
   border-radius: 0 !important;
   border-bottom-left-radius: 0.3em !important;
   border-bottom-right-radius: 0.3em !important;
+}
+
+:root {
+  --white: #FFF;
+  --black: #2E2B29;
+  --black-contrast: #110F0E;
+  --gray-1: rgba(61, 37, 20, .05);
+  --gray-2: rgba(61, 37, 20, .08);
+  --gray-3: rgba(61, 37, 20, .12);
+  --gray-4: rgba(53, 38, 28, .3);
+  --gray-5: rgba(28, 25, 23, .6);
+  --green: #22C55E;
+  --purple: #6A00F5;
+  --purple-contrast: #5800CC;
+  --purple-light: rgba(88, 5, 255, .05);
+  --purple-light-dark: rgba(88, 5, 255, .3);
+  --yellow-contrast: #FACC15;
+  --yellow: rgba(250, 204, 21, .4);
+  --yellow-light: #FFFAE5;
+  --red: #FF5C33;
+  --red-light: #FFEBE5;
+  --shadow: 0px 12px 33px 0px rgba(0, 0, 0, .06), 0px 3.618px 9.949px 0px rgba(0, 0, 0, .04)
 }