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 ( -
+
{show_tutorial && (