From 9d824a1ab3f31bf0e1ede89af19168314918a6b5 Mon Sep 17 00:00:00 2001 From: Thenujan-Nagaratnam Date: Mon, 1 Apr 2024 13:41:03 +0530 Subject: [PATCH 1/7] added predefined messages, moved api card to below chat message, clear chat history when logout --- .../Apis/Chat/AISearchAssistant.jsx | 43 +++++---- .../app/components/Apis/Chat/ChatMessage.jsx | 96 +++++++++++-------- .../app/components/Apis/Chat/ChatWindow.jsx | 12 ++- .../src/app/components/Apis/Chat/Header.jsx | 2 +- .../components/Apis/Chat/SimilaritySearch.js | 78 +++++++++++++++ .../source/src/app/components/Logout.jsx | 1 + 6 files changed, 174 insertions(+), 58 deletions(-) create mode 100644 portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/SimilaritySearch.js diff --git a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx index 77866e8e38b..7306cb9d0a4 100644 --- a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx +++ b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx @@ -36,13 +36,14 @@ function AISearchAssistant() { const [loading, setLoading] = useState(false); const [messages, setMessages] = useState(null); const [chatbotDisabled, setChatbotDisabled] = useState(!marketplaceAssistantEnabled); - const [user, setUser] = useState('You'); + const userRef = useRef('You'); const responseRef = useRef([]); const introMessage = { role: 'assistant', - content: 'Hi there! I\'m Marketplace Assistant. I can help you with finding APIs ' - + 'and providing information related to APIs. How can I help you?', + content: 'Hello! Welcome to Marketplace Assistant. I\'m here to assist you in discovering and obtaining information about APIs.' + + ' Please note that the responses provided are limited to publicly available APIs. During the trial period,' + + ' users are granted the ability to upload up to specific no of APIs and the number of tokens available for chat is limited.', }; const pathName = window.location.pathname; @@ -60,7 +61,7 @@ function AISearchAssistant() { const { apis } = result.body; const apiPaths = apis.map((api) => { - return { apiPath: `${origin}${pathName}/${api.apiId}/overview${search}`, name: api.apiName }; + return { apiPath: `${origin}${pathName}/${api.apiId}/overview${search}`, name: api.apiName, version: api.version }; }); responseRef.current = [...responseRef.current, { role: 'assistant', content: result.body.response, apis: apiPaths }]; setMessages(responseRef.current); @@ -69,18 +70,22 @@ function AISearchAssistant() { let content; try { switch (error.response.status) { - case 401: // Unauthorized - content = 'Provided token is invalid. Please use a valid token and try again.'; + case 401: + content = 'Apologies for the inconvenience. It appears that your token is invalid or expired. Please' + + ' provide a valid token or upgrade your subscription plan.'; break; case 429: // Token limit exceeded - content = 'Token limit is exceeded. Please try again later.'; + content = 'Apologies for the inconvenience. It appears that the token limit has been exceeded.' + + ' Please attempt your request again.'; break; default: - content = 'Something went wrong. Please try again later.'; + content = 'Apologies for the inconvenience. It seems that something went wrong with the' + + ' Marketplace Assistant. Please try again later.'; break; } } catch (err) { - content = 'Something went wrong. Please try again later.'; + content = 'Apologies for the inconvenience. It seems that something went wrong with the' + + ' Marketplace Assistant. Please try again later.'; } const errorMessage = { role: 'assistant', content }; @@ -105,7 +110,7 @@ function AISearchAssistant() { useEffect(() => { if (messages) { - const messagesJSON = JSON.stringify(messages); + const messagesJSON = JSON.stringify({ messages, timestamp: Date.now() }); localStorage.setItem('messages', messagesJSON); } }, [messages]); @@ -116,13 +121,19 @@ function AISearchAssistant() { if (loggedInUser) { setUser(loggedInUser); } - const messagesJSON = localStorage.getItem('messages'); - const loadedMessages = JSON.parse(messagesJSON); - if (loadedMessages) { - setMessages(loadedMessages); + const storedData = localStorage.getItem('messages'); + if (storedData) { + const { messages: storedMessages, timestamp } = JSON.parse(storedData); + if (Date.now() - timestamp > 60 * 60 * 1000) { + setMessages([introMessage]); + localStorage.setItem('messages', + JSON.stringify({ messages: [introMessage], timestamp: Date.now() })); + } else { + setMessages(storedMessages); + } } else { setMessages([introMessage]); - localStorage.setItem('messages', JSON.stringify([introMessage])); + localStorage.setItem('messages', JSON.stringify({ messages: [introMessage], timestamp: Date.now() })); } } catch (error) { console.error('Error loading messages from localStorage:', error); @@ -146,7 +157,7 @@ function AISearchAssistant() { messages={messages} setMessages={setMessages} introMessage={introMessage} - user={user} + user={userRef.current} loading={loading} responseRef={responseRef} apiCall={apiCall} diff --git a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatMessage.jsx b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatMessage.jsx index 6b492dc5b1c..b4ef9806c4b 100644 --- a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatMessage.jsx +++ b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatMessage.jsx @@ -112,45 +112,6 @@ function ChatMessage(props) { Assistant - {message.apis && ( - - {message.apis.map((api) => ( - - - - - {api.name} - - - - - ))} - - )} )} @@ -181,6 +142,60 @@ function ChatMessage(props) { + + {message.apis && ( + + {message.apis.map((api) => ( + + + + + {api.name} + + + Version: + {api.version} + + + + + ))} + + )} + ); } @@ -189,7 +204,8 @@ ChatMessage.propTypes = { message: PropTypes.shape({ role: PropTypes.string.isRequired, content: PropTypes.string.isRequired, - apis: PropTypes.arrayOf(PropTypes.string), // Added propType for apis + apis: PropTypes.arrayOf(PropTypes.string), }).isRequired, + user: PropTypes.string.isRequired, }; export default ChatMessage; diff --git a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatWindow.jsx b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatWindow.jsx index ca27984e55b..831a7efcbdc 100644 --- a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatWindow.jsx +++ b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatWindow.jsx @@ -28,6 +28,7 @@ import { ResizableBox } from 'react-resizable'; import 'react-resizable/css/styles.css'; import ChatMessages from './ChatMessages'; import Header from './Header'; +import findBestMatchingAnswer from './SimilaritySearch'; /** * Renders Chat Messages view.. @@ -63,7 +64,16 @@ function ChatWindow(props) { const handleSend = async (message) => { responseRef.current = [...responseRef.current, { role: 'user', content: message.content.trim() }]; setMessages(responseRef.current); - apiCall(message.content); + + const query = message.content.trim().toLowerCase(); + + const response = findBestMatchingAnswer(query); + if (response) { + responseRef.current = [...responseRef.current, { role: 'assistant', content: response.trim() }]; + setMessages(responseRef.current); + } else { + apiCall(message.content); + } }; const handleReset = () => { diff --git a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/Header.jsx b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/Header.jsx index 1b64f0bc916..a74c32b8426 100644 --- a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/Header.jsx +++ b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/Header.jsx @@ -98,7 +98,7 @@ function Header(props) { - Marketplace Assistant + API Marketplace Assistant Beta diff --git a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/SimilaritySearch.js b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/SimilaritySearch.js new file mode 100644 index 00000000000..939481d353a --- /dev/null +++ b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/SimilaritySearch.js @@ -0,0 +1,78 @@ +const predefinedAnswers = { + hi: 'Hello there!, How can I assist you today?', + hiii: 'Hello there!, How can I assist you today?', + hello: 'Hi! How can I assist you today?', + hey: 'Hey! How can I help you?', + greetings: 'Greetings! What can I assist you with?', + thanks: 'You\'re welcome!', + bye: 'Goodbye! Have a great day!', + goodbye: 'Goodbye! Have a great day!', + 'thank you': 'You\'re welcome!', + 'thank you for your help': 'You\'re welcome!', + 'good morning': 'Good morning! How can I assist you today?', + 'good afternoon': 'Good afternoon! What can I do for you?', + 'good evening': 'Good evening! How can I help?', + 'hey there': 'Hey! What can I assist you with?', + 'hi there': 'Hello! How may I assist you?', + 'hello there': 'Hello! How can I assist you today?', + 'how are you': 'I\'m doing well, thank you! How can I help you?', + 'can you help me': 'You can ask me anything related to APIs!', + 'what can I ask you': 'You can ask me anything related to APIs!', + 'what can you do': 'I can help you with finding APIs and providing information related to APIs.', + 'what do you do': 'I can help you with finding APIs and providing information related to APIs.', + 'what do you know': 'I know a lot about APIs! What are you looking for?', + 'what are you': 'I am the Marketplace Assistant. I can help you with finding APIs and providing information related to APIs.', + 'who are you': 'I am the Marketplace Assistant. I can help you with finding APIs and providing information related to APIs.', + 'what is your name': 'I am the Marketplace Assistant. I can help you with finding APIs and providing information related to APIs.', +}; + +const calculateStringSimilarity = (string1, string2) => { + const str1 = string1.toLowerCase(); + const str2 = string2.toLowerCase(); + + if (str1.length < 2 || str2.length < 2) return 0; + + const subsequenceMap = new Map(); + for (let i = 0; i < str1.length - 1; i++) { + const subsequence = str1.substr(i, 2); + subsequenceMap.set(subsequence, subsequenceMap.has(subsequence) ? subsequenceMap.get(subsequence) + 1 : 1); + } + + let matchCount = 0; + for (let j = 0; j < str2.length - 1; j++) { + const subsequence = str2.substr(j, 2); + const count = subsequenceMap.has(subsequence) ? subsequenceMap.get(subsequence) : 0; + if (count > 0) { + subsequenceMap.set(subsequence, count - 1); + matchCount++; + } + } + + return (matchCount * 2) / (str1.length + str2.length - 2); +}; + +const findBestMatchingAnswer = (inputString) => { + let bestMatch = null; + let bestScore = -1; + + const searchString = inputString.toLowerCase(); + + if (searchString.length < 2) return null; + + for (const [key] of Object.entries(predefinedAnswers)) { + const comparisonString = key.toLowerCase(); + + const score = calculateStringSimilarity(searchString, comparisonString); + + if (score > bestScore) { + bestScore = score; + bestMatch = key; + } + } + + if (bestScore < 0.8) return null; + + return predefinedAnswers[bestMatch]; +}; + +export default findBestMatchingAnswer; diff --git a/portals/devportal/src/main/webapp/source/src/app/components/Logout.jsx b/portals/devportal/src/main/webapp/source/src/app/components/Logout.jsx index 77f76291caa..cb6d3ddc8ce 100755 --- a/portals/devportal/src/main/webapp/source/src/app/components/Logout.jsx +++ b/portals/devportal/src/main/webapp/source/src/app/components/Logout.jsx @@ -47,6 +47,7 @@ class Logout extends Component { */ componentDidMount() { const environmentName = Utils.getEnvironment().label; + localStorage.removeItem('messages'); // Clear marketplace assistant chat history when logout localStorage.removeItem(`${User.CONST.LOCALSTORAGE_USER}_${environmentName}`); localStorage.removeItem('graphiql:tabState'); localStorage.removeItem('graphiql:queries'); From ce82ac37ef291675be5bcc9aaaccd9dd6d88d82a Mon Sep 17 00:00:00 2001 From: Thenujan-Nagaratnam Date: Mon, 1 Apr 2024 16:08:50 +0530 Subject: [PATCH 2/7] added limit to the info header --- .../source/src/app/components/Apis/Chat/ChatWindow.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatWindow.jsx b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatWindow.jsx index 831a7efcbdc..04df72295b3 100644 --- a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatWindow.jsx +++ b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatWindow.jsx @@ -43,6 +43,7 @@ function ChatWindow(props) { const [isClicked, setIsClicked] = useState(false); const [apiLimitExceeded, setApiLimitExceeded] = useState(false); const [apisCount, setApisCount] = useState(0); + const [limit, setLimit] = useState(null); const { settings: { marketplaceAssistantEnabled, aiAuthTokenProvided } } = useSettingsContext(); @@ -92,6 +93,7 @@ function ChatWindow(props) { const apiCount = data.body.count; const apiLimit = data.body.limit; setApisCount(apiCount); + setLimit(apiLimit); if (apiCount >= apiLimit) { setApiLimitExceeded(true); } @@ -173,11 +175,12 @@ function ChatWindow(props) { {marketplaceAssistantEnabled && aiAuthTokenProvided && ( apiLimitExceeded ? ( - You have reached your maximum number of apis. The answers will be limited to the first 1000 apis. + {`You have reached your maximum number of apis. The answers will be limited to the first ${limit} apis.`} ) : ( - {`The Assistant is using ${apisCount} apis to provide answers.`} + {`The Assistant is using ${apisCount} APIs to provide answers.`} + {limit ? `You can publish up to ${limit} APIs.` : ''} ) )} From 9063cc10198bd06be48b3a9a371ca2922e037a5c Mon Sep 17 00:00:00 2001 From: Thenujan-Nagaratnam Date: Mon, 1 Apr 2024 16:42:20 +0530 Subject: [PATCH 3/7] changed intro message --- .../source/src/app/components/Apis/Chat/AISearchAssistant.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx index 7306cb9d0a4..80158750ba9 100644 --- a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx +++ b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx @@ -42,8 +42,7 @@ function AISearchAssistant() { const introMessage = { role: 'assistant', content: 'Hello! Welcome to Marketplace Assistant. I\'m here to assist you in discovering and obtaining information about APIs.' - + ' Please note that the responses provided are limited to publicly available APIs. During the trial period,' - + ' users are granted the ability to upload up to specific no of APIs and the number of tokens available for chat is limited.', + + ' How can I help you?', }; const pathName = window.location.pathname; From 0a88052939b4ed41d6eeb1f9a9ecfe581f883405 Mon Sep 17 00:00:00 2001 From: Thenujan-Nagaratnam Date: Mon, 1 Apr 2024 16:54:19 +0530 Subject: [PATCH 4/7] changed llm subject text --- .../webapp/source/src/app/components/Apis/Chat/ChatMessages.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatMessages.jsx b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatMessages.jsx index 6cbb6d8b247..a41449ccf93 100644 --- a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatMessages.jsx +++ b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatMessages.jsx @@ -42,7 +42,7 @@ function ChatMessages(props) { const { settings: { aiAuthTokenProvided } } = useSettingsContext(); const messagesEndRef = useRef(null); - const subjectLine = 'Marketplace Assistant can make mistakes. Consider checking important information.'; + const subjectLine = 'API Marketplace Assistant is an early feature and can make mistakes. Verify its outputs.'; const style = { width: '30px', From 60265a7d251f078bfe31d80bfb1522ed4ed2e96c Mon Sep 17 00:00:00 2001 From: Thenujan-Nagaratnam Date: Mon, 1 Apr 2024 17:50:56 +0530 Subject: [PATCH 5/7] resolved conflicts --- .../source/src/app/components/Apis/Chat/AISearchAssistant.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx index 80158750ba9..1bcde57d4a9 100644 --- a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx +++ b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx @@ -36,7 +36,7 @@ function AISearchAssistant() { const [loading, setLoading] = useState(false); const [messages, setMessages] = useState(null); const [chatbotDisabled, setChatbotDisabled] = useState(!marketplaceAssistantEnabled); - const userRef = useRef('You'); + const [user, setUser] = useState('You'); const responseRef = useRef([]); const introMessage = { @@ -156,7 +156,7 @@ function AISearchAssistant() { messages={messages} setMessages={setMessages} introMessage={introMessage} - user={userRef.current} + user={user} loading={loading} responseRef={responseRef} apiCall={apiCall} From 0fe35dfcbc9a4deaced7f95dd29fa6a8a14c31ff Mon Sep 17 00:00:00 2001 From: Thenujan Nagaratnam <114144503+Thenujan-Nagaratnam@users.noreply.github.com> Date: Mon, 1 Apr 2024 18:28:47 +0530 Subject: [PATCH 6/7] Update portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx Co-authored-by: Ashera Silva <30475839+ashera96@users.noreply.github.com> --- .../source/src/app/components/Apis/Chat/AISearchAssistant.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx index 1bcde57d4a9..ffe676597dd 100644 --- a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx +++ b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/AISearchAssistant.jsx @@ -41,7 +41,7 @@ function AISearchAssistant() { const introMessage = { role: 'assistant', - content: 'Hello! Welcome to Marketplace Assistant. I\'m here to assist you in discovering and obtaining information about APIs.' + content: 'Hello! I\'m here to assist you in discovering and obtaining information about APIs.' + ' How can I help you?', }; From 7aaf3fcb1b0491a2a9ed4d8eaead8393f53758b0 Mon Sep 17 00:00:00 2001 From: Thenujan-Nagaratnam Date: Mon, 1 Apr 2024 21:55:05 +0530 Subject: [PATCH 7/7] Changed api count banner --- .../app/components/Apis/Chat/ChatWindow.jsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatWindow.jsx b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatWindow.jsx index 04df72295b3..298b3c10ca3 100644 --- a/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatWindow.jsx +++ b/portals/devportal/src/main/webapp/source/src/app/components/Apis/Chat/ChatWindow.jsx @@ -42,7 +42,7 @@ function ChatWindow(props) { const [isClicked, setIsClicked] = useState(false); const [apiLimitExceeded, setApiLimitExceeded] = useState(false); - const [apisCount, setApisCount] = useState(0); + const [apisCount, setApisCount] = useState(null); const [limit, setLimit] = useState(null); const { settings: { marketplaceAssistantEnabled, aiAuthTokenProvided } } = useSettingsContext(); @@ -94,7 +94,7 @@ function ChatWindow(props) { const apiLimit = data.body.limit; setApisCount(apiCount); setLimit(apiLimit); - if (apiCount >= apiLimit) { + if (apiCount >= apiLimit - 50) { setApiLimitExceeded(true); } }) @@ -172,15 +172,15 @@ function ChatWindow(props) { isClicked={isClicked} /> {/* Alert to show API count info */} - {marketplaceAssistantEnabled && aiAuthTokenProvided && ( - apiLimitExceeded ? ( - - {`You have reached your maximum number of apis. The answers will be limited to the first ${limit} apis.`} + {marketplaceAssistantEnabled && aiAuthTokenProvided && apiLimitExceeded && ( + (apisCount >= limit) ? ( + + {`You are reached your maximum limit (${limit} apis) for API usage.`} ) : ( - - {`The Assistant is using ${apisCount} APIs to provide answers.`} - {limit ? `You can publish up to ${limit} APIs.` : ''} + + {`You are approaching your maximum limit for API usage. You can utilize up to ${limit} APIs. + Currently, you have utilized ${apisCount} APIs.`} ) )}