diff --git a/autogpt_platform/frontend/package.json b/autogpt_platform/frontend/package.json
index f3fbd5385740..6a30c92cc896 100644
--- a/autogpt_platform/frontend/package.json
+++ b/autogpt_platform/frontend/package.json
@@ -67,6 +67,7 @@
"next": "^14.2.13",
"next-themes": "^0.4.4",
"react": "^18",
+ "react-chat-widget": "^3.1.4",
"react-day-picker": "^9.4.4",
"react-dom": "^18",
"react-hook-form": "^7.54.0",
diff --git a/autogpt_platform/frontend/src/app/layout.tsx b/autogpt_platform/frontend/src/app/layout.tsx
index 944e2301d395..ec8af71138b9 100644
--- a/autogpt_platform/frontend/src/app/layout.tsx
+++ b/autogpt_platform/frontend/src/app/layout.tsx
@@ -7,6 +7,7 @@ import { Navbar } from "@/components/agptui/Navbar";
import "./globals.css";
import TallyPopupSimple from "@/components/TallyPopup";
+import OttoChatWidget from "@/components/OttoChatWidget";
import { GoogleAnalytics } from "@next/third-parties/google";
import { Toaster } from "@/components/ui/toaster";
import { IconType } from "@/components/ui/icons";
@@ -93,6 +94,7 @@ export default async function RootLayout({
/>
{children}
+
diff --git a/autogpt_platform/frontend/src/components/OttoChatWidget.css b/autogpt_platform/frontend/src/components/OttoChatWidget.css
new file mode 100644
index 000000000000..1e4b48b7252d
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/OttoChatWidget.css
@@ -0,0 +1,103 @@
+.custom-launcher-button {
+ background-color: #8b5cf6 !important;
+ border: none !important;
+ border-radius: 50% !important;
+ color: white !important;
+ cursor: pointer !important;
+ height: 60px !important;
+ padding: 18px !important;
+ position: fixed !important;
+ right: 35px !important;
+ bottom: 15px !important;
+ transition: all 0.2s ease-in-out !important;
+ width: 60px !important;
+ z-index: 999 !important;
+ }
+
+ .custom-launcher-button:hover {
+ background-color: #7c3aed !important;
+ transform: scale(1.1) !important;
+ }
+
+ .rcw-launcher {
+ display: none !important;
+ }
+
+ .rcw-widget-container {
+ height: 65vh !important;
+ margin-bottom: 50px !important;
+ border-radius: 10px !important;
+ max-width: 610px !important;
+ width: 100% !important;
+ }
+
+ .rcw-conversation-container {
+ border-radius: 10px !important;
+ background-color: white !important;
+ border: none !important;
+ }
+
+ .rcw-header {
+ background-color: #8b5cf6 !important;
+ border-radius: 10px 10px 0 0 !important;
+ padding: 0px !important;
+ min-height: 0px !important;
+ }
+
+ .rcw-messages-container {
+ background-color: white !important;
+ padding: 12px 8px !important;
+ max-width: 100% !important;
+ overflow-x: hidden !important;
+ font-size: 0.9rem !important;
+ }
+
+ .rcw-message {
+ padding: 4px 8px !important;
+ width: auto !important;
+ max-width: 100% !important;
+ display: flex !important;
+ flex-direction: column !important;
+ margin: 4px 0 !important;
+ }
+
+ .rcw-message-text {
+ background-color: #f3f4f6 !important;
+ color: #1f2937 !important;
+ border-radius: 8px !important;
+ padding: 8px !important;
+ max-width: 100% !important;
+ word-wrap: break-word !important;
+ white-space: pre-wrap !important;
+ overflow-wrap: break-word !important;
+ line-height: 1.4 !important;
+ overflow-x: auto !important;
+ margin: 0 !important;
+ }
+
+ .rcw-message-text pre {
+ max-width: 100% !important;
+ overflow-x: auto !important;
+ white-space: pre-wrap !important;
+ word-wrap: break-word !important;
+ margin: 8px 0 !important;
+ }
+
+ .rcw-message-text code {
+ word-wrap: break-word !important;
+ white-space: pre-wrap !important;
+ display: block !important;
+ width: 100% !important;
+ }
+
+ .rcw-client .rcw-message-text {
+ background-color: #8b5cf6 !important;
+ color: white !important;
+ }
+
+ .rcw-sender {
+ background-color: white !important;
+ padding: 10px !important;
+ border-radius: 0 0 10px 10px !important;
+ border-top: 1px solid #e5e7eb !important;
+ }
diff --git a/autogpt_platform/frontend/src/components/OttoChatWidget.tsx b/autogpt_platform/frontend/src/components/OttoChatWidget.tsx
new file mode 100644
index 000000000000..ecfa98b6091e
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/OttoChatWidget.tsx
@@ -0,0 +1,145 @@
+"use client";
+
+import React, { useEffect, useState, useRef } from 'react';
+import { Widget, addResponseMessage, addLinkSnippet, deleteMessages } from 'react-chat-widget';
+import 'react-chat-widget/lib/styles.css';
+import './OttoChatWidget.css';
+import useSupabase from '../hooks/useSupabase';
+
+interface Document {
+ url: string;
+ relevance_score: number;
+}
+
+interface ApiResponse {
+ answer: string;
+ documents: Document[];
+ success: boolean;
+}
+
+interface Message {
+ query: string;
+ response: string;
+}
+
+interface ChatPayload {
+ query: string;
+ conversation_history: { query: string; response: string }[];
+ user_id: string;
+ message_id: string;
+}
+
+const OttoChatWidget = () => {
+ const [chatWindowOpen, setChatWindowOpen] = useState(false);
+ const [messages, setMessages] = useState([]);
+ const welcomeMessageSent = useRef(false);
+ const processingMessageId = useRef(null);
+ const { user } = useSupabase();
+
+ useEffect(() => {
+ if (!welcomeMessageSent.current) {
+ addResponseMessage('Hello im Otto! Ask me anything about AutoGPT!');
+ welcomeMessageSent.current = true;
+ }
+ }, []);
+
+ const formatResponse = (data: ApiResponse): void => {
+ const cleanedResponse = data.answer.replace(/####|###|\*|-/g, '');
+ addResponseMessage(cleanedResponse);
+ };
+
+ const handleNewUserMessage = async (newMessage: string) => {
+
+ // Generate a message ID with timestamp and 'web' suffix, this is used to identify the message in the database
+ const messageId = `${Date.now()}-web`;
+
+ setMessages(prev => [...prev, { query: newMessage, response: '' }]);
+
+ addResponseMessage('Processing your question...');
+
+ try {
+ const payload: ChatPayload = {
+ query: newMessage,
+ conversation_history: messages.map(msg => ({
+ query: msg.query,
+ response: msg.response
+ })),
+ user_id: user?.id || 'anonymous',
+ message_id: messageId
+ };
+
+ const response = await fetch('http://192.168.0.39:2344/ask', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ },
+ mode: 'cors',
+ credentials: 'omit',
+ body: JSON.stringify(payload)
+ });
+
+ if (!response.ok) {
+ throw new Error(`API request failed with status ${response.status}`);
+ }
+
+ const data: ApiResponse = await response.json();
+
+ deleteMessages(1);
+
+ if (data.success) {
+ formatResponse(data);
+ setMessages(prev => {
+ const newMessages = [...prev];
+ newMessages[newMessages.length - 1].response = data.answer;
+ return newMessages;
+ });
+ } else {
+ throw new Error('API request was not successful');
+ }
+
+ } catch (error) {
+ deleteMessages(1);
+
+ console.error('Error calling API:', error);
+ addResponseMessage('Sorry, there was an error processing your message. Please try again.');
+ }
+ };
+
+ const handleToggle = () => {
+ setChatWindowOpen(prev => !prev);
+ };
+
+ return (
+ void) => (
+
+ )}
+ />
+ );
+};
+
+export default OttoChatWidget;
\ No newline at end of file
diff --git a/autogpt_platform/frontend/src/components/TallyPopup.tsx b/autogpt_platform/frontend/src/components/TallyPopup.tsx
index 7e7543dd7dce..ad405fcd7644 100644
--- a/autogpt_platform/frontend/src/components/TallyPopup.tsx
+++ b/autogpt_platform/frontend/src/components/TallyPopup.tsx
@@ -56,7 +56,7 @@ const TallyPopupSimple = () => {
};
return (
-