Skip to content

Commit

Permalink
Merge pull request #666 from Thenujan-Nagaratnam/marketplace-assistan…
Browse files Browse the repository at this point in the history
…t-ai

Marketplace assistant
  • Loading branch information
Arshardh committed Apr 1, 2024
2 parents 8f92546 + 7aaf3fc commit fbc7dee
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ function AISearchAssistant() {

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! I\'m here to assist you in discovering and obtaining information about APIs.'
+ ' How can I help you?',
};

const pathName = window.location.pathname;
Expand All @@ -60,7 +60,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);
Expand All @@ -69,18 +69,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 };
Expand All @@ -105,7 +109,7 @@ function AISearchAssistant() {

useEffect(() => {
if (messages) {
const messagesJSON = JSON.stringify(messages);
const messagesJSON = JSON.stringify({ messages, timestamp: Date.now() });
localStorage.setItem('messages', messagesJSON);
}
}, [messages]);
Expand All @@ -116,13 +120,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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,45 +112,6 @@ function ChatMessage(props) {
</div>
<Typography variant='body1' style={{ fontWeight: '500', fontSize: '12pt' }}>Assistant</Typography>
</Box>
{message.apis && (
<Box display='flex' flexDirection='row' flexWrap='wrap' marginLeft='26px' marginRight='16px' width='100%'>
{message.apis.map((api) => (
<a
key={api.id}
href={api.apiPath}
target='_blank'
rel='noopener noreferrer'
style={{
textDecoration: 'none',
color: 'inherit',
width: '33%',
}}
>
<Card style={{
margin: '0 10px 10px 0', width: '97%', height: '56px', backgroundColor: '#6694a7',
}}
>
<CardContent style={{ wordWrap: 'break-word' }}>
<Typography
variant='subtitle1'
gutterBottom
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
cursor: 'pointer',
margin: 0,
color: '#fff',
}}
>
{api.name}
</Typography>
</CardContent>
</Card>
</a>
))}
</Box>
)}
</Box>
)}

Expand Down Expand Up @@ -181,6 +142,60 @@ function ChatMessage(props) {
</MuiMarkdown>
</Typography>
</Box>
<Box display='flex-start' alignItems='center' flexDirection='column' width='90%'>
{message.apis && (
<Box display='flex' flexDirection='row' flexWrap='wrap' marginLeft='26px' marginRight='16px' width='100%'>
{message.apis.map((api) => (
<a
key={api.id}
href={api.apiPath}
target='_blank'
rel='noopener noreferrer'
style={{
textDecoration: 'none',
color: 'inherit',
width: '33%',
}}
>
<Card style={{
margin: '10px 10px 0 0', width: '97%', height: '70px', backgroundColor: '#6694a7',
}}
>
<CardContent style={{ wordWrap: 'break-word', alignItems: 'center', cursor: 'pointer' }}>
<Typography
variant='subtitle1'
gutterBottom
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
margin: 0,
color: '#fff',
}}
>
{api.name}
</Typography>
<Typography
variant='subtitle1'
gutterBottom
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
fontSize: '10px',
color: '#fff',
}}
>
Version:
{api.version}
</Typography>
</CardContent>
</Card>
</a>
))}
</Box>
)}
</Box>
</Box>
);
}
Expand All @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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..
Expand All @@ -41,7 +42,8 @@ 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();

Expand All @@ -63,7 +65,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 = () => {
Expand All @@ -82,7 +93,8 @@ function ChatWindow(props) {
const apiCount = data.body.count;
const apiLimit = data.body.limit;
setApisCount(apiCount);
if (apiCount >= apiLimit) {
setLimit(apiLimit);
if (apiCount >= apiLimit - 50) {
setApiLimitExceeded(true);
}
})
Expand Down Expand Up @@ -160,14 +172,15 @@ function ChatWindow(props) {
isClicked={isClicked}
/>
{/* Alert to show API count info */}
{marketplaceAssistantEnabled && aiAuthTokenProvided && (
apiLimitExceeded ? (
<Alert severity='warning' style={{ borderRadius: '0px', zIndex: 2999, padding: '0 10px 0 10px' }}>
You have reached your maximum number of apis. The answers will be limited to the first 1000 apis.
{marketplaceAssistantEnabled && aiAuthTokenProvided && apiLimitExceeded && (
(apisCount >= limit) ? (
<Alert severity='error' style={{ borderRadius: '0px', zIndex: 2999, padding: '0 10px 0 10px' }}>
{`You are reached your maximum limit (${limit} apis) for API usage.`}
</Alert>
) : (
<Alert severity='info' style={{ borderRadius: '0px', zIndex: 2999, padding: '0 10px 0 10px' }}>
{`The Assistant is using ${apisCount} apis to provide answers.`}
<Alert severity='warning' style={{ borderRadius: '0px', zIndex: 2999, padding: '0 10px 0 10px' }}>
{`You are approaching your maximum limit for API usage. You can utilize up to ${limit} APIs.
Currently, you have utilized ${apisCount} APIs.`}
</Alert>
)
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ function Header(props) {
</Box>
<Box display='flex'>
<Typography className={classes.chatbotName}>
Marketplace Assistant
API Marketplace Assistant
</Typography>
<Typography className={classes.betaChip}>
Beta
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down

0 comments on commit fbc7dee

Please sign in to comment.