From a85a6de5690cf8aaf3610adf4a81cb870b884f72 Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Thu, 13 Mar 2025 12:14:46 -0300 Subject: [PATCH 1/6] feat: add chat ai first version --- website/package.json | 1 + website/src/components/ChatIA.js | 11 + website/src/components/FloatingChat/index.jsx | 139 +++++++++++++ website/src/css/custom.scss | 191 +++++++++++++++++- website/src/theme/DocItem/Layout/index.js | 4 + 5 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 website/src/components/ChatIA.js create mode 100644 website/src/components/FloatingChat/index.jsx diff --git a/website/package.json b/website/package.json index 46bbd3b06e3..6b9e78b7659 100644 --- a/website/package.json +++ b/website/package.json @@ -54,6 +54,7 @@ "react-bootstrap-typeahead": "^6.3.2", "react-dom": "^18.2.0", "react-is": "^18.2.0", + "react-markdown": "^10.1.0", "react-monaco-editor": "^0.54.0", "sass": "^1.69.5", "url": "^0.11.3" diff --git a/website/src/components/ChatIA.js b/website/src/components/ChatIA.js new file mode 100644 index 00000000000..5ba452ecc1e --- /dev/null +++ b/website/src/components/ChatIA.js @@ -0,0 +1,11 @@ +const ChatIA = () => { + return ( + <> + // + ); +} +export default ChatIA; \ No newline at end of file diff --git a/website/src/components/FloatingChat/index.jsx b/website/src/components/FloatingChat/index.jsx new file mode 100644 index 00000000000..69373e0e9e9 --- /dev/null +++ b/website/src/components/FloatingChat/index.jsx @@ -0,0 +1,139 @@ +import '@generated/client-modules'; +import React, { useState, useRef, useEffect } from 'react'; +import { Button, Card, Form, InputGroup } from 'react-bootstrap'; +import axios from 'axios'; +import ReactMarkdown from 'react-markdown'; + +const FloatingChat = () => { + const [isOpen, setIsOpen] = useState(false); + const [messages, setMessages] = useState([]); + const [inputMessage, setInputMessage] = useState('how I can create NFT?'); + const [isLoading, setIsLoading] = useState(false); + const [threadId, setThreadId] = useState(null); + const messagesEndRef = useRef(null); + + const getAIResponse = async (userMessage) => { + setIsLoading(true); + + const response = await axios.post('http://localhost:5000/api/chat', { + messages: userMessage, + threadId: threadId + }, { + headers: { + 'Content-Type': 'application/json' + } + }); + + setIsLoading(false); + return response.data; + }; + + const handleSendMessage = async (e) => { + e.preventDefault(); + + if (!inputMessage.trim()) return; + const userMessage = { id: Date.now(), text: inputMessage, sender: 'user' }; + setMessages([...messages, userMessage]); + setInputMessage('How I can add image?'); + + + const aiResponseText = await getAIResponse(inputMessage); + setThreadId(aiResponseText.threadId); + + const aiMessage = { id: Date.now() + 1, text: aiResponseText.message, sender: 'ai' }; + setMessages(prevMessages => [...prevMessages, aiMessage]); + }; + + useEffect(() => { + if (messagesEndRef.current) { + messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }); + } + }, [messages]); + + const toggleChat = () => { + + setIsOpen(!isOpen); + }; + + return ( +
+ {isOpen ? ( + + +
+ + Chat IA +
+ +
+ + +
+ {messages.length === 0 ? ( +
+ What can I help you with today? +
+ ) : ( + messages.map((msg) => ( +
+ {msg.text} +
+ )) + )} + {isLoading && ( +
+
+ + + +
+
+ )} +
+
+ + + +
+ + setInputMessage(e.target.value)} + /> + + +
+
+ + ) : ( + + )} +
+ ); +}; + +export default FloatingChat; \ No newline at end of file diff --git a/website/src/css/custom.scss b/website/src/css/custom.scss index 6e3f6cb5415..bf19b48ac3a 100644 --- a/website/src/css/custom.scss +++ b/website/src/css/custom.scss @@ -473,4 +473,193 @@ div[class^="announcementBar_"] { .moving-forward__support-title { margin-top: 10px !important; -} \ No newline at end of file +} + +.floating-chat-container { + position: fixed; + bottom: 20px; + right: 80px; + z-index: 1000; + + .card:hover{ + transform: translateY(0); + } + + .chat-toggle-button { + width: 60px; + height: 60px; + border-radius: 50%; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + display: flex; + justify-content: center; + align-items: center; + background-color: #343a40; + color: white; + cursor: pointer; + border: none; + i { + font-size: 1.5rem; + } + + &:hover { + transform: scale(1.05); + transition: transform 0.2s ease; + } + } + + .chat-card { + margin: 0; + padding: 0; + width: 450px; + height: 500px; + border-radius: 15px; + overflow: hidden; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + display: flex; + flex-direction: column; + background-color: #212529; + + .chat-header { + background-color: #343a40; + color: white; + padding: 0.75rem 1rem; + display: flex; + justify-content: space-between; + align-items: center; + + .chat-title { + font-weight: 600; + display: flex; + align-items: center; + } + + .close-button { + color: white; + padding: 0; + + &:hover { + color: rgba(255, 255, 255, 0.8); + } + } + + &:hover{ + transform: translateY(0); + } + } + + .chat-body { + flex: 1; + overflow-y: auto; + padding: 1rem; + background-color: #343a40; + + .messages-container { + display: flex; + flex-direction: column; + gap: 12px; + + .welcome-message { + text-align: center; + color: #adb5bd; + margin: 2rem 0; + } + + .message { + max-width: 80%; + padding: 10px 15px; + border-radius: 15px; + word-break: break-word; + + &.user-message { + align-self: flex-end; + background-color: #007bff; + color: white; + border-bottom-right-radius: 5px; + } + + &.ai-message { + align-self: flex-start; + background-color: #495057; + color: white; + border: 1px solid #6c757d; + border-bottom-left-radius: 5px; + } + + &.loading { + display: flex; + justify-content: center; + align-items: center; + padding: 15px; + + } + } + } + } + + .chat-footer { + background-color: #343a40; + padding: 1rem; + border-top: 1px solid #495057; + border: none; + .input-group { + display: flex; + + .form-control { + flex: 1; + padding: 12px; + background-color: #495057; + color: white; + border-radius: 4px 0 0 4px; + border: none; + + &:focus { + box-shadow: none; + border-color: #007bff; + } + } + + .btn { + padding: 12px; + background-color: #007bff; + color: white; + border-radius: 0 4px 4px 0; + border: none; + } + } + } + } +} + +.dot-typing { + display: flex; + justify-content: center; + align-items: center; + height: 20px; + + span { + display: inline-block; + width: 6px; + height: 6px; + margin: 0 2px; + background-color: #fff; + border-radius: 50%; + animation: dotTyping 1.4s infinite ease-in-out both; + } + + span:nth-child(1) { + animation-delay: -0.32s; + } + + span:nth-child(2) { + animation-delay: -0.16s; + } +} + +@keyframes dotTyping { + 0%, 80%, 100% { + transform: scale(0); + } + 40% { + transform: scale(1); + } +} diff --git a/website/src/theme/DocItem/Layout/index.js b/website/src/theme/DocItem/Layout/index.js index 816835508a7..a189039814b 100644 --- a/website/src/theme/DocItem/Layout/index.js +++ b/website/src/theme/DocItem/Layout/index.js @@ -15,6 +15,8 @@ import styles from './styles.module.css'; import { HelpComponent } from '../../../components/helpcomponent'; import { FeedbackComponent } from '../../../components/FeedbackComponent'; +import ChatIA from '../../../components/ChatIA'; +import FloatingChat from '../../../components/FloatingChat'; /** * Decide if the toc should be rendered, on mobile or desktop viewports @@ -52,6 +54,8 @@ export default function DocItemLayout({ children }) { + +
From ef80ae51871b5b5aefd3f6652a24a6b3db1ed345 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Thu, 13 Mar 2025 18:00:15 +0100 Subject: [PATCH 2/6] fix: minor improvements --- website/src/components/AIChat/Chat.jsx | 120 +++++++++++++++ website/src/components/AIChat/index.jsx | 22 +++ website/src/components/ChatIA.js | 11 -- website/src/components/FloatingChat/index.jsx | 139 ------------------ website/src/css/custom.scss | 67 +++++---- website/src/theme/DocItem/Layout/index.js | 6 +- 6 files changed, 182 insertions(+), 183 deletions(-) create mode 100644 website/src/components/AIChat/Chat.jsx create mode 100644 website/src/components/AIChat/index.jsx delete mode 100644 website/src/components/ChatIA.js delete mode 100644 website/src/components/FloatingChat/index.jsx diff --git a/website/src/components/AIChat/Chat.jsx b/website/src/components/AIChat/Chat.jsx new file mode 100644 index 00000000000..aed4d70ab17 --- /dev/null +++ b/website/src/components/AIChat/Chat.jsx @@ -0,0 +1,120 @@ +import '@generated/client-modules'; +import React, { useState, useRef, useEffect } from 'react'; +import { Button, Card, Form, InputGroup } from 'react-bootstrap'; +import axios from 'axios'; +import ReactMarkdown from 'react-markdown'; + +export const Chat = ({toggleChat}) => { + const [messages, setMessages] = useState([]); + const [inputMessage, setInputMessage] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [threadId, setThreadId] = useState(null); + const messagesEndRef = useRef(null); + + const getAIResponse = async (userMessage) => { + setIsLoading(true); + + const response = await axios.post('https://tmp-docs-ai-service.onrender.com/api/chat', { + messages: userMessage, + threadId: threadId + }, { + headers: { + 'Content-Type': 'application/json' + } + }); + + setIsLoading(false); + return response.data; + }; + + const handleSendMessage = async (e) => { + e.preventDefault(); + + if (!inputMessage.trim()) return; + const userMessage = { id: Date.now(), text: inputMessage, sender: 'user' }; + setMessages([...messages, userMessage]); + setInputMessage(''); + + const aiResponseText = await getAIResponse(inputMessage); + setThreadId(aiResponseText.threadId); + + const aiMessage = { id: Date.now() + 1, text: aiResponseText.message, sender: 'ai' }; + setMessages(prevMessages => [...prevMessages, aiMessage]); + }; + + useEffect(() => { + if (messagesEndRef.current) { + messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }); + } + }, [messages]); + + + return <> +
+ + +
+ + Chat IA +
+ + X + +
+ + +
+ {messages.length === 0 ? ( +
+ How can I help you today? +
+ ) : ( + messages.map((msg) => ( +
+ {msg.text} +
+ )) + )} + {isLoading && ( +
+
+ + + +
+
+ )} +
+
+ + + +
+ + setInputMessage(e.target.value)} + /> + + +
+
+ +
+ +} diff --git a/website/src/components/AIChat/index.jsx b/website/src/components/AIChat/index.jsx new file mode 100644 index 00000000000..9c5ca1ee4e3 --- /dev/null +++ b/website/src/components/AIChat/index.jsx @@ -0,0 +1,22 @@ +import '@generated/client-modules'; +import React, { useState, useRef, useEffect } from 'react'; +import { Button, } from 'react-bootstrap'; +import { Chat } from './Chat'; + +const AIChat = () => { + const [isOpen, setIsOpen] = useState(false); + const toggleChat = () => { setIsOpen(!isOpen); }; + + return isOpen ? + : ( + + ) +}; + +export default AIChat; \ No newline at end of file diff --git a/website/src/components/ChatIA.js b/website/src/components/ChatIA.js deleted file mode 100644 index 5ba452ecc1e..00000000000 --- a/website/src/components/ChatIA.js +++ /dev/null @@ -1,11 +0,0 @@ -const ChatIA = () => { - return ( - <> - // - ); -} -export default ChatIA; \ No newline at end of file diff --git a/website/src/components/FloatingChat/index.jsx b/website/src/components/FloatingChat/index.jsx deleted file mode 100644 index 69373e0e9e9..00000000000 --- a/website/src/components/FloatingChat/index.jsx +++ /dev/null @@ -1,139 +0,0 @@ -import '@generated/client-modules'; -import React, { useState, useRef, useEffect } from 'react'; -import { Button, Card, Form, InputGroup } from 'react-bootstrap'; -import axios from 'axios'; -import ReactMarkdown from 'react-markdown'; - -const FloatingChat = () => { - const [isOpen, setIsOpen] = useState(false); - const [messages, setMessages] = useState([]); - const [inputMessage, setInputMessage] = useState('how I can create NFT?'); - const [isLoading, setIsLoading] = useState(false); - const [threadId, setThreadId] = useState(null); - const messagesEndRef = useRef(null); - - const getAIResponse = async (userMessage) => { - setIsLoading(true); - - const response = await axios.post('http://localhost:5000/api/chat', { - messages: userMessage, - threadId: threadId - }, { - headers: { - 'Content-Type': 'application/json' - } - }); - - setIsLoading(false); - return response.data; - }; - - const handleSendMessage = async (e) => { - e.preventDefault(); - - if (!inputMessage.trim()) return; - const userMessage = { id: Date.now(), text: inputMessage, sender: 'user' }; - setMessages([...messages, userMessage]); - setInputMessage('How I can add image?'); - - - const aiResponseText = await getAIResponse(inputMessage); - setThreadId(aiResponseText.threadId); - - const aiMessage = { id: Date.now() + 1, text: aiResponseText.message, sender: 'ai' }; - setMessages(prevMessages => [...prevMessages, aiMessage]); - }; - - useEffect(() => { - if (messagesEndRef.current) { - messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }); - } - }, [messages]); - - const toggleChat = () => { - - setIsOpen(!isOpen); - }; - - return ( -
- {isOpen ? ( - - -
- - Chat IA -
- -
- - -
- {messages.length === 0 ? ( -
- What can I help you with today? -
- ) : ( - messages.map((msg) => ( -
- {msg.text} -
- )) - )} - {isLoading && ( -
-
- - - -
-
- )} -
-
- - - -
- - setInputMessage(e.target.value)} - /> - - -
-
- - ) : ( - - )} -
- ); -}; - -export default FloatingChat; \ No newline at end of file diff --git a/website/src/css/custom.scss b/website/src/css/custom.scss index bf19b48ac3a..a5e6a7ad3cf 100644 --- a/website/src/css/custom.scss +++ b/website/src/css/custom.scss @@ -475,43 +475,52 @@ div[class^="announcementBar_"] { margin-top: 10px !important; } -.floating-chat-container { + +.chat-toggle-button { + width: 60px; + height: 60px; + border-radius: 50%; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); position: fixed; bottom: 20px; - right: 80px; - z-index: 1000; + right: 20px; + display: flex; + font-size: 36px; + justify-content: center; + align-items: center; + background-color: #343a40; + color: white; + cursor: pointer; + border: none; + i { + font-size: 1.5rem; + } - .card:hover{ - transform: translateY(0); + &:hover { + transform: scale(1.05); + transition: transform 0.2s ease; } +} - .chat-toggle-button { - width: 60px; - height: 60px; - border-radius: 50%; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - display: flex; - justify-content: center; - align-items: center; - background-color: #343a40; - color: white; - cursor: pointer; - border: none; - i { - font-size: 1.5rem; - } +.floating-chat-container { + position: fixed; + top: 0; + left: 0; + height: 100vh; + width: 100vw; + background-color: #00000066; + align-content: center; - &:hover { - transform: scale(1.05); - transition: transform 0.2s ease; - } + z-index: 1000; + + .card:hover{ + transform: translateY(0); } .chat-card { - margin: 0; - padding: 0; - width: 450px; - height: 500px; + margin: auto; + width: 70vw; + height: 60vh; border-radius: 15px; overflow: hidden; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); @@ -614,7 +623,7 @@ div[class^="announcementBar_"] { &:focus { box-shadow: none; - border-color: #007bff; + border-color: none; } } diff --git a/website/src/theme/DocItem/Layout/index.js b/website/src/theme/DocItem/Layout/index.js index a189039814b..d5fc34e401a 100644 --- a/website/src/theme/DocItem/Layout/index.js +++ b/website/src/theme/DocItem/Layout/index.js @@ -15,8 +15,7 @@ import styles from './styles.module.css'; import { HelpComponent } from '../../../components/helpcomponent'; import { FeedbackComponent } from '../../../components/FeedbackComponent'; -import ChatIA from '../../../components/ChatIA'; -import FloatingChat from '../../../components/FloatingChat'; +import AIChat from '../../../components/AIChat'; /** * Decide if the toc should be rendered, on mobile or desktop viewports @@ -54,8 +53,7 @@ export default function DocItemLayout({ children }) { - - +
From dabbbda58344b64bd49e04335ccc0ab7f1250c89 Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Fri, 14 Mar 2025 20:28:07 -0300 Subject: [PATCH 3/6] feat(chat): improve interface and usability --- website/package.json | 4 +- website/src/components/AIChat/Chat.jsx | 204 ++++++++++++------ .../components/AIChat/MarkdownRenderer.jsx | 63 ++++++ website/src/components/AIChat/index.jsx | 20 +- website/src/css/custom.scss | 151 ++++++++++--- website/yarn.lock | 27 +++ 6 files changed, 358 insertions(+), 111 deletions(-) create mode 100644 website/src/components/AIChat/MarkdownRenderer.jsx diff --git a/website/package.json b/website/package.json index 6b9e78b7659..ba4eea494d6 100644 --- a/website/package.json +++ b/website/package.json @@ -45,6 +45,7 @@ "gleap": "^13.7.3", "https-browserify": "^1.0.0", "lodash": "^4.17.21", + "lucide-react": "^0.482.0", "monaco-editor": "^0.44.0", "near-api-js": "^2.1.4", "near-social-vm": "github:NearSocial/VM#2.5.5", @@ -57,6 +58,7 @@ "react-markdown": "^10.1.0", "react-monaco-editor": "^0.54.0", "sass": "^1.69.5", - "url": "^0.11.3" + "url": "^0.11.3", + "react-syntax-highlighter": "^15.6.1" } } diff --git a/website/src/components/AIChat/Chat.jsx b/website/src/components/AIChat/Chat.jsx index aed4d70ab17..e0c2a072cdb 100644 --- a/website/src/components/AIChat/Chat.jsx +++ b/website/src/components/AIChat/Chat.jsx @@ -2,14 +2,84 @@ import '@generated/client-modules'; import React, { useState, useRef, useEffect } from 'react'; import { Button, Card, Form, InputGroup } from 'react-bootstrap'; import axios from 'axios'; -import ReactMarkdown from 'react-markdown'; +import { useColorMode } from '@docusaurus/theme-common'; +import MarkdownRenderer from './MarkdownRenderer'; +import { Send, X } from 'lucide-react'; -export const Chat = ({toggleChat}) => { - const [messages, setMessages] = useState([]); + +function splitTextIntoParts(text) { + const regex = /(```[\s\S]*?```)/g; + return text.split(regex).filter(part => part !== ''); +} + +export const Chat = ({ toggleChat }) => { + const { colorMode } = useColorMode(); + const [messages, setMessages] = useState([ + { + "id": 1741973644462, + "text": "give me the code of a hello word smart contract in rust", + "sender": "user" + }, + { + "id": 1741973659720, + "text": "The given Rust code defines a smart contract named `hello_world` using the `ink_lang` library. The contract has a constructor `new` that initializes a message, and two functions: `get_message` to retrieve the message and `set_message` to update the message. The `ink` macro is used to mark the contract and its functions for compilation. This contract can be used as a basic example for creating and interacting with smart contracts on a blockchain platform.\n\nFind more information here:\n\n- [https://docs.near.org/docs/build/smart-contracts/anatomy/best-practices](https://docs.near.org/docs/build/smart-contracts/anatomy/best-practices)\n- [https://docs.near.org/docs/build/smart-contracts/anatomy/serialization-protocols](https://docs.near.org/docs/build/smart-contracts/anatomy/serialization-protocols)\n- [https://docs.near.org/docs/build/smart-contracts/anatomy/functions](https://docs.near.org/docs/build/smart-contracts/anatomy/functions)\n", + "sender": "ai" + }, + { + "id": 1741973699798, + "text": "can you show the code?", + "sender": "user" + }, + { + "id": 1741973708945, + "text": "Here is a simple \"Hello World\" smart contract written in Rust: ```rust \n// Define the contract structure\n#[near(contract_state)]\npub struct Contract {\n greeting: String,\n}\n\n// Define the default, which automatically initializes the contract\nimpl Default for Contract {\n fn default() -> Self {\n Self {\n greeting: \"Hello\".to_string(),\n }\n }\n}\n\n// Implement the contract structure\n#[near]\nimpl Contract {\n // Public method - returns the greeting saved, defaulting to DEFAULT_GREETING\n pub fn get_greeting(&self) -> String {\n self.greeting.clone()\n }\n\n // Public method - accepts a greeting, such as \"howdy\", and records it\n pub fn set_greeting(&mut self, greeting: String) {\n log!(\"Saving greeting: {}\", greeting);\n self.greeting = greeting;\n }\n}\n```\n\nFind more information here:\n\n- [https://docs.near.org/docs/build/smart-contracts/quickstart](https://docs.near.org/docs/build/smart-contracts/quickstart)\n", + "sender": "ai" + } + ]); const [inputMessage, setInputMessage] = useState(''); const [isLoading, setIsLoading] = useState(false); const [threadId, setThreadId] = useState(null); const messagesEndRef = useRef(null); + const chatRef = useRef(null); + const inputRef = useRef(null); + + const isDarkTheme = colorMode === 'dark'; + + useEffect(() => { + document.documentElement.setAttribute('data-theme', colorMode); + }, [colorMode]); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, []); + + useEffect(() => { + const handleKeyDown = (event) => { + if (event.key === 'Escape') { + toggleChat(); + } + }; + + document.addEventListener('keydown', handleKeyDown); + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [toggleChat]); + + useEffect(() => { + const handleClickOutside = (event) => { + if (chatRef.current && !chatRef.current.contains(event.target)) { + toggleChat(); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [toggleChat]); const getAIResponse = async (userMessage) => { setIsLoading(true); @@ -48,73 +118,69 @@ export const Chat = ({toggleChat}) => { } }, [messages]); + return
+ + +
+ + Near IA +
+ +
- return <> -
- - -
- - Chat IA -
- - X - -
- - -
- {messages.length === 0 ? ( -
- How can I help you today? + +
+ {messages.length === 0 ? ( +
+ How can I help you today? +
+ ) : ( + messages.map((msg) => ( +
+ {splitTextIntoParts(msg.text).map((part, index) => { + return () + })}
- ) : ( - messages.map((msg) => ( -
- {msg.text} -
- )) - )} - {isLoading && ( -
-
- - - -
+ )) + )} + {isLoading && ( +
+
+ + +
- )} -
-
- - - -
- - setInputMessage(e.target.value)} - /> - - -
-
- -
- +
+ )} +
+
+ + + +
+ + setInputMessage(e.target.value)} + ref={inputRef} + /> + + +
+
+ +
} diff --git a/website/src/components/AIChat/MarkdownRenderer.jsx b/website/src/components/AIChat/MarkdownRenderer.jsx new file mode 100644 index 00000000000..bd9498feb3c --- /dev/null +++ b/website/src/components/AIChat/MarkdownRenderer.jsx @@ -0,0 +1,63 @@ +import React, { useState } from 'react'; +import ReactMarkdown from 'react-markdown'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { oneDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'; +import { ClipboardCopy, Check } from 'lucide-react'; + +const CodeBlock = ({ node, inline, className, children, isDarkTheme, ...props }) => { + const match = /language-(\w+)/.exec(className || ''); + const codeContent = String(children).replace(/\n$/, ''); + const [isCopied, setIsCopied] = useState(false); + + const copyToClipboard = () => { + navigator.clipboard.writeText(codeContent); + setIsCopied(true); + setTimeout(() => { + setIsCopied(false); + }, 2000); + }; + + return !inline && match ? ( +
+ +
{children}
} + {...props} + > + {codeContent} +
+
+ ) : ( + + {children} + + ); +}; + +const MarkdownRenderer = ({ part, isDarkTheme }) => { + return ( + , + a: ({ node, ...props }) => ( + + {props.children} + + ) + }} + + > + {part} + + ); +}; + +export default MarkdownRenderer; \ No newline at end of file diff --git a/website/src/components/AIChat/index.jsx b/website/src/components/AIChat/index.jsx index 9c5ca1ee4e3..1ab45b5127f 100644 --- a/website/src/components/AIChat/index.jsx +++ b/website/src/components/AIChat/index.jsx @@ -2,21 +2,21 @@ import '@generated/client-modules'; import React, { useState, useRef, useEffect } from 'react'; import { Button, } from 'react-bootstrap'; import { Chat } from './Chat'; - +import { MessageCircleCode } from 'lucide-react'; const AIChat = () => { const [isOpen, setIsOpen] = useState(false); const toggleChat = () => { setIsOpen(!isOpen); }; return isOpen ? - : ( - - ) + : ( + + ) }; export default AIChat; \ No newline at end of file diff --git a/website/src/css/custom.scss b/website/src/css/custom.scss index a5e6a7ad3cf..ecdd0fbb8b6 100644 --- a/website/src/css/custom.scss +++ b/website/src/css/custom.scss @@ -8,6 +8,19 @@ --near-font-headlines: Inter, sans-serif; --near-font-body: Inter, sans-serif; + + --chat-bg: #f8f9fa; + --chat-header-bg: #e9ecef; + --chat-input-bg: #f3f4f6; + --chat-user-message-bg: #007bff; + --chat-ai-message-bg: #f1f3f5; + --chat-ai-message-border: #dee2e6; + --chat-text-color: #212529; + --chat-ai-text-color: #212529; + --chat-welcome-text: #6c757d; + --chat-input-text: #212529; + --chat-input-bg-focus:#fff; + --chat-toggle-button-bg: #dee2e6; } [data-theme="dark"] { @@ -17,6 +30,19 @@ hr { background-color: #2d2d2d; } + + --chat-bg: #212529; + --chat-header-bg: #343a40; + --chat-input-bg: #495057; + --chat-user-message-bg: #007bff; + --chat-ai-message-bg: #495057; + --chat-ai-message-border: #6c757d; + --chat-text-color: #ffffff; + --chat-ai-text-color: #ffffff; + --chat-welcome-text: #adb5bd; + --chat-input-text: #ffffff; + --chat-input-bg-focus:#5a6268; + --chat-toggle-button-bg: #343a40; } [data-theme='light'] li hr { @@ -483,13 +509,13 @@ div[class^="announcementBar_"] { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); position: fixed; bottom: 20px; - right: 20px; + right: 80px; display: flex; font-size: 36px; justify-content: center; align-items: center; - background-color: #343a40; - color: white; + background-color: var(--chat-ai-message-bg); + color: var(--chat-text-color); cursor: pointer; border: none; i { @@ -510,7 +536,6 @@ div[class^="announcementBar_"] { width: 100vw; background-color: #00000066; align-content: center; - z-index: 1000; .card:hover{ @@ -520,17 +545,17 @@ div[class^="announcementBar_"] { .chat-card { margin: auto; width: 70vw; - height: 60vh; + height: 80vh; border-radius: 15px; overflow: hidden; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); display: flex; flex-direction: column; - background-color: #212529; + background-color: var(--chat-bg); .chat-header { - background-color: #343a40; - color: white; + background-color: var(--chat-header-bg); + color: var(--chat-text-color); padding: 0.75rem 1rem; display: flex; justify-content: space-between; @@ -543,11 +568,12 @@ div[class^="announcementBar_"] { } .close-button { - color: white; + color: var(--chat-text-color); padding: 0; &:hover { - color: rgba(255, 255, 255, 0.8); + opacity: 0.8; + cursor: pointer; } } @@ -560,7 +586,7 @@ div[class^="announcementBar_"] { flex: 1; overflow-y: auto; padding: 1rem; - background-color: #343a40; + background-color: var(--chat-bg); .messages-container { display: flex; @@ -569,29 +595,29 @@ div[class^="announcementBar_"] { .welcome-message { text-align: center; - color: #adb5bd; + color: var(--chat-welcome-text); margin: 2rem 0; } .message { - max-width: 80%; + max-width: 90%; padding: 10px 15px; border-radius: 15px; word-break: break-word; &.user-message { align-self: flex-end; - background-color: #007bff; + background-color: var(--chat-user-message-bg); color: white; - border-bottom-right-radius: 5px; + border-bottom-right-radius: 3px; } &.ai-message { align-self: flex-start; - background-color: #495057; - color: white; - border: 1px solid #6c757d; - border-bottom-left-radius: 5px; + background-color: var(--chat-ai-message-bg); + color: var(--chat-ai-text-color); + border: 1px solid var(--chat-ai-message-border); + border-bottom-left-radius: 3px; } &.loading { @@ -599,40 +625,60 @@ div[class^="announcementBar_"] { justify-content: center; align-items: center; padding: 15px; - } } } } .chat-footer { - background-color: #343a40; + background-color: var(--chat-header-bg); padding: 1rem; - border-top: 1px solid #495057; + border-top: 1px solid var(--chat-ai-message-border); border: none; + .input-group { display: flex; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); .form-control { flex: 1; - padding: 12px; - background-color: #495057; - color: white; - border-radius: 4px 0 0 4px; + padding: 14px 16px; + background-color: var(--chat-input-bg); + color: var(--chat-input-text); + border-radius: 8px 0 0 8px; border: none; + font-size: 1rem; + transition: background-color 0.2s ease; &:focus { + outline: none; box-shadow: none; - border-color: none; + background-color: var(--chat-input-bg-focus); } } .btn { - padding: 12px; - background-color: #007bff; - color: white; - border-radius: 0 4px 4px 0; + display: flex; + align-items: center; + justify-content: center; + padding: 12px 16px; + background-color: var(--chat-user-message-bg); + color: white; + border-radius: 0 8px 8px 0; border: none; + transition: background-color 0.2s ease; + + &:hover { + background-color: #0069d9; + cursor: pointer; + } + + svg { + width: 20px; + height: 20px; + } } } } @@ -672,3 +718,46 @@ div[class^="announcementBar_"] { transform: scale(1); } } + +.code-block-container { + position: relative; +} + +.code-copy-button { + position: absolute; + right: 10px; + top: 10px; + background: none; + border: none; + cursor: pointer; + color: var(--chat-text-color); + z-index: 2; + padding: 4px; + border-radius: 4px; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.code-copy-button:hover { + background-color: rgba(255, 255, 255, 0.1); + transform: scale(1.1); +} + +.code-copy-button.copied { + color: #10b981; + animation: pulse 0.5s ease-in-out; +} + +@keyframes pulse { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.2); + } + 100% { + transform: scale(1); + } +} diff --git a/website/yarn.lock b/website/yarn.lock index 6f18e38f78a..c72a4ad3e67 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -8025,6 +8025,11 @@ html-tags@^3.3.1: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== +html-url-attributes@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz#83b052cd5e437071b756cd74ae70f708870c2d87" + integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== + html-void-elements@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7" @@ -9021,6 +9026,11 @@ lru-cache@^6.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.1.0.tgz#2098d41c2dc56500e6c88584aa656c84de7d0484" integrity sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag== +lucide-react@^0.482.0: + version "0.482.0" + resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.482.0.tgz#0277ec1c728bfcacab0ea8c4fb4aa5cc2f155623" + integrity sha512-XM8PzHzSrg8ATmmO+fzf+JyYlVVdQnJjuyLDj2p4V2zEtcKeBNAqAoJIGFv1x2HSBa7kT8gpYUxwdQ0g7nypfw== + markdown-extensions@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-2.0.0.tgz#34bebc83e9938cae16e0e017e4a9814a8330d3c4" @@ -11660,6 +11670,23 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1: dependencies: "@types/react" "*" +react-markdown@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-10.1.0.tgz#e22bc20faddbc07605c15284255653c0f3bad5ca" + integrity sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + hast-util-to-jsx-runtime "^2.0.0" + html-url-attributes "^3.0.0" + mdast-util-to-hast "^13.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.0.0" + unified "^11.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + react-markdown@^7.1.0: version "7.1.2" resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-7.1.2.tgz#c9fa9d1c87e24529f028e1cdf731e81ccdd8e547" From 055030f8d9e611e99340e5b0bf404de901f567e9 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Mon, 17 Mar 2025 16:54:08 +0100 Subject: [PATCH 4/6] small changes to ux --- website/src/components/AIChat/Chat.jsx | 47 +++++++++++--------------- website/yarn.lock | 17 ++++++++++ 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/website/src/components/AIChat/Chat.jsx b/website/src/components/AIChat/Chat.jsx index e0c2a072cdb..91872616fa6 100644 --- a/website/src/components/AIChat/Chat.jsx +++ b/website/src/components/AIChat/Chat.jsx @@ -8,37 +8,19 @@ import { Send, X } from 'lucide-react'; function splitTextIntoParts(text) { + console.log(text); + if(!text) return []; const regex = /(```[\s\S]*?```)/g; return text.split(regex).filter(part => part !== ''); } export const Chat = ({ toggleChat }) => { const { colorMode } = useColorMode(); - const [messages, setMessages] = useState([ - { - "id": 1741973644462, - "text": "give me the code of a hello word smart contract in rust", - "sender": "user" - }, - { - "id": 1741973659720, - "text": "The given Rust code defines a smart contract named `hello_world` using the `ink_lang` library. The contract has a constructor `new` that initializes a message, and two functions: `get_message` to retrieve the message and `set_message` to update the message. The `ink` macro is used to mark the contract and its functions for compilation. This contract can be used as a basic example for creating and interacting with smart contracts on a blockchain platform.\n\nFind more information here:\n\n- [https://docs.near.org/docs/build/smart-contracts/anatomy/best-practices](https://docs.near.org/docs/build/smart-contracts/anatomy/best-practices)\n- [https://docs.near.org/docs/build/smart-contracts/anatomy/serialization-protocols](https://docs.near.org/docs/build/smart-contracts/anatomy/serialization-protocols)\n- [https://docs.near.org/docs/build/smart-contracts/anatomy/functions](https://docs.near.org/docs/build/smart-contracts/anatomy/functions)\n", - "sender": "ai" - }, - { - "id": 1741973699798, - "text": "can you show the code?", - "sender": "user" - }, - { - "id": 1741973708945, - "text": "Here is a simple \"Hello World\" smart contract written in Rust: ```rust \n// Define the contract structure\n#[near(contract_state)]\npub struct Contract {\n greeting: String,\n}\n\n// Define the default, which automatically initializes the contract\nimpl Default for Contract {\n fn default() -> Self {\n Self {\n greeting: \"Hello\".to_string(),\n }\n }\n}\n\n// Implement the contract structure\n#[near]\nimpl Contract {\n // Public method - returns the greeting saved, defaulting to DEFAULT_GREETING\n pub fn get_greeting(&self) -> String {\n self.greeting.clone()\n }\n\n // Public method - accepts a greeting, such as \"howdy\", and records it\n pub fn set_greeting(&mut self, greeting: String) {\n log!(\"Saving greeting: {}\", greeting);\n self.greeting = greeting;\n }\n}\n```\n\nFind more information here:\n\n- [https://docs.near.org/docs/build/smart-contracts/quickstart](https://docs.near.org/docs/build/smart-contracts/quickstart)\n", - "sender": "ai" - } - ]); + const [messages, setMessages] = useState([]); const [inputMessage, setInputMessage] = useState(''); const [isLoading, setIsLoading] = useState(false); const [threadId, setThreadId] = useState(null); + const [seconds, setSeconds] = useState(1); const messagesEndRef = useRef(null); const chatRef = useRef(null); const inputRef = useRef(null); @@ -49,6 +31,19 @@ export const Chat = ({ toggleChat }) => { document.documentElement.setAttribute('data-theme', colorMode); }, [colorMode]); + useEffect(() => { + let interval; + if (isLoading) { + interval = setInterval(() => { + setSeconds((seconds) => seconds + 1); + }, 1000); + } else { + setSeconds(1); + } + + return () => clearInterval(interval); + }, [isLoading]) + useEffect(() => { if (inputRef.current) { inputRef.current.focus(); @@ -149,12 +144,8 @@ export const Chat = ({ toggleChat }) => { )) )} {isLoading && ( -
-
- - - -
+
+ Thinking... ({seconds}s)
)}
diff --git a/website/yarn.lock b/website/yarn.lock index 1a4e8e16e6a..57947ab371d 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -7941,6 +7941,11 @@ highlight.js@^10.4.1, highlight.js@~10.7.0: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== +highlightjs-vue@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz#fdfe97fbea6354e70ee44e3a955875e114db086d" + integrity sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA== + history@^4.9.0: version "4.10.1" resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" @@ -11815,6 +11820,18 @@ react-syntax-highlighter@^15.5.0: prismjs "^1.27.0" refractor "^3.6.0" +react-syntax-highlighter@^15.6.1: + version "15.6.1" + resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz#fa567cb0a9f96be7bbccf2c13a3c4b5657d9543e" + integrity sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg== + dependencies: + "@babel/runtime" "^7.3.1" + highlight.js "^10.4.1" + highlightjs-vue "^1.0.0" + lowlight "^1.17.0" + prismjs "^1.27.0" + refractor "^3.6.0" + react-transition-group@^4.4.5: version "4.4.5" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" From 4b3c8e7bd07297cede51b34ed4cd84f751b72675 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Wed, 19 Mar 2025 15:31:56 +0100 Subject: [PATCH 5/6] minor changes to css --- website/src/components/AIChat/Chat.jsx | 5 +- website/src/components/AIChat/index.jsx | 16 +-- website/src/css/custom.scss | 151 +++++++++++++++++------- 3 files changed, 120 insertions(+), 52 deletions(-) diff --git a/website/src/components/AIChat/Chat.jsx b/website/src/components/AIChat/Chat.jsx index 91872616fa6..712eb786f65 100644 --- a/website/src/components/AIChat/Chat.jsx +++ b/website/src/components/AIChat/Chat.jsx @@ -8,7 +8,6 @@ import { Send, X } from 'lucide-react'; function splitTextIntoParts(text) { - console.log(text); if(!text) return []; const regex = /(```[\s\S]*?```)/g; return text.split(regex).filter(part => part !== ''); @@ -89,7 +88,7 @@ export const Chat = ({ toggleChat }) => { }); setIsLoading(false); - return response.data; + return response.data || "I was not able to process your request. Please try again."; }; const handleSendMessage = async (e) => { @@ -118,7 +117,7 @@ export const Chat = ({ toggleChat }) => {
- Near IA + Docs AI (Beta)
{ const [isOpen, setIsOpen] = useState(false); const toggleChat = () => { setIsOpen(!isOpen); }; return isOpen ? : ( - + ) }; diff --git a/website/src/css/custom.scss b/website/src/css/custom.scss index ecdd0fbb8b6..17c1535ee21 100644 --- a/website/src/css/custom.scss +++ b/website/src/css/custom.scss @@ -19,7 +19,7 @@ --chat-ai-text-color: #212529; --chat-welcome-text: #6c757d; --chat-input-text: #212529; - --chat-input-bg-focus:#fff; + --chat-input-bg-focus: #fff; --chat-toggle-button-bg: #dee2e6; } @@ -30,7 +30,7 @@ hr { background-color: #2d2d2d; } - + --chat-bg: #212529; --chat-header-bg: #343a40; --chat-input-bg: #495057; @@ -275,7 +275,7 @@ li hr { border-width: 0; } -li > ul { +li>ul { margin-top: 0.4rem; } @@ -486,7 +486,7 @@ div[class^="announcementBar_"] { h2 { margin-top: 4rem; } -} +} #__blog-post-container { padding-top: 1rem; @@ -505,19 +505,17 @@ div[class^="announcementBar_"] { .chat-toggle-button { width: 60px; height: 60px; - border-radius: 50%; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); position: fixed; - bottom: 20px; - right: 80px; + bottom: 2rem; + right: 2.5rem; display: flex; - font-size: 36px; justify-content: center; align-items: center; background-color: var(--chat-ai-message-bg); color: var(--chat-text-color); cursor: pointer; border: none; + i { font-size: 1.5rem; } @@ -528,6 +526,71 @@ div[class^="announcementBar_"] { } } +.animated-border-box { + max-height: 60px; + max-width: 60px; + overflow: hidden; + border-radius: 10px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.animated-border-box:before { + content: ''; + z-index: -2; + text-align: center; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) rotate(0deg); + position: absolute; + width: 100px; + height: 100px; + background-repeat: no-repeat; + background-position: 0 0; + /*border color, change middle color*/ + background-image: conic-gradient(rgba(0, 0, 0, 0), #1976ed, rgba(0, 0, 0, 0) 25%); + /* change speed here */ + animation: rotate 4s linear infinite; +} + +.animated-border-box:after { + content: ''; + position: absolute; + z-index: -1; + /* border width */ + left: 5px; + top: 5px; + /* double the px from the border width left */ + width: calc(100% - 10px); + height: calc(100% - 10px); + /*bg color*/ + background: var(--chat-ai-message-bg); + /*box border radius*/ + border-radius: 7px; +} + +@keyframes rotate { + 100% { + transform: translate(-50%, -50%) rotate(1turn); + } +} + +/*// Border Animation END//*/ + + + +/*// Ignore This //*/ +body { + margin: 0px; +} + +.center-box { + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background-color: #1D1E22; +} + .floating-chat-container { position: fixed; top: 0; @@ -538,7 +601,7 @@ div[class^="announcementBar_"] { align-content: center; z-index: 1000; - .card:hover{ + .card:hover { transform: translateY(0); } @@ -577,7 +640,7 @@ div[class^="announcementBar_"] { } } - &:hover{ + &:hover { transform: translateY(0); } } @@ -635,7 +698,7 @@ div[class^="announcementBar_"] { padding: 1rem; border-top: 1px solid var(--chat-ai-message-border); border: none; - + .input-group { display: flex; border-radius: 8px; @@ -686,37 +749,41 @@ div[class^="announcementBar_"] { } .dot-typing { - display: flex; - justify-content: center; - align-items: center; - height: 20px; - - span { - display: inline-block; - width: 6px; - height: 6px; - margin: 0 2px; - background-color: #fff; - border-radius: 50%; - animation: dotTyping 1.4s infinite ease-in-out both; - } - - span:nth-child(1) { - animation-delay: -0.32s; - } - - span:nth-child(2) { - animation-delay: -0.16s; - } + display: flex; + justify-content: center; + align-items: center; + height: 20px; + + span { + display: inline-block; + width: 6px; + height: 6px; + margin: 0 2px; + background-color: #fff; + border-radius: 50%; + animation: dotTyping 1.4s infinite ease-in-out both; + } + + span:nth-child(1) { + animation-delay: -0.32s; + } + + span:nth-child(2) { + animation-delay: -0.16s; + } } @keyframes dotTyping { - 0%, 80%, 100% { - transform: scale(0); - } - 40% { - transform: scale(1); - } + + 0%, + 80%, + 100% { + transform: scale(0); + } + + 40% { + transform: scale(1); + } } .code-block-container { @@ -754,10 +821,12 @@ div[class^="announcementBar_"] { 0% { transform: scale(1); } + 50% { transform: scale(1.2); } + 100% { transform: scale(1); } -} +} \ No newline at end of file From 12d86abd7b8ce2752838ec7fb1f34390a20f6c26 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Wed, 19 Mar 2025 15:41:54 +0100 Subject: [PATCH 6/6] minor fixes --- website/src/components/AIChat/Chat.jsx | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/website/src/components/AIChat/Chat.jsx b/website/src/components/AIChat/Chat.jsx index 712eb786f65..c291ace6f88 100644 --- a/website/src/components/AIChat/Chat.jsx +++ b/website/src/components/AIChat/Chat.jsx @@ -76,8 +76,6 @@ export const Chat = ({ toggleChat }) => { }, [toggleChat]); const getAIResponse = async (userMessage) => { - setIsLoading(true); - const response = await axios.post('https://tmp-docs-ai-service.onrender.com/api/chat', { messages: userMessage, threadId: threadId @@ -86,9 +84,7 @@ export const Chat = ({ toggleChat }) => { 'Content-Type': 'application/json' } }); - - setIsLoading(false); - return response.data || "I was not able to process your request. Please try again."; + return response.data; }; const handleSendMessage = async (e) => { @@ -99,11 +95,19 @@ export const Chat = ({ toggleChat }) => { setMessages([...messages, userMessage]); setInputMessage(''); - const aiResponseText = await getAIResponse(inputMessage); - setThreadId(aiResponseText.threadId); + setIsLoading(true); - const aiMessage = { id: Date.now() + 1, text: aiResponseText.message, sender: 'ai' }; - setMessages(prevMessages => [...prevMessages, aiMessage]); + try { + const aiResponseText = await getAIResponse(inputMessage); + setThreadId(aiResponseText.threadId); + + const aiMessage = { id: Date.now() + 1, text: aiResponseText.message, sender: 'ai' }; + setMessages(prevMessages => [...prevMessages, aiMessage]); + } catch (error) { + const aiMessage = { id: Date.now() + 1, text: "I was not able to process your request, please try again", sender: 'ai' }; + setMessages(prevMessages => [...prevMessages, aiMessage]); + } + setIsLoading(false); }; useEffect(() => {