Add variables to .env
Section titled “Add variables to .env”INCONVO_API_KEY="$YOUR_API_KEY"INCONVO_API_BASE_URL="https://app.inconvo.ai/api/v1"
Integrate an analytics assistant into your application with Inconvo.
Build your in-app analytics assistant using Inconvo, a backend API that manages database connection, semantic translation, multi-tennancy and more.
This example uses a React frontend and a Node.js backend, but you can use any frontend and backend technology you like.
INCONVO_API_KEY="$YOUR_API_KEY"INCONVO_API_BASE_URL="https://app.inconvo.ai/api/v1"
Add an endpoint on your server to create a conversation.
POST /api/v1/conversations
responds with an id
which is used to create conversation responses.
app.post("/create-conversation", async (_req, res) => { // This is placeholder context, // You should set your own based on the request const context = { organisationId: 1, };
try { const response = await fetch( `${process.env.INCONVO_API_BASE_URL}/conversations/`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.INCONVO_API_KEY}`, }, body: JSON.stringify({ context, }), } ); if (!response.ok) { throw new Error(`Failed to create conversation: ${response.status}`); } const data = await response.json(); res.json(data); } catch (error) { console.error("Error creating conversation:", error); res.status(500).json({ error: "Failed to create conversation" }); }});
Add an endpoint on your server to create a response.
POST /api/v1/conversations/answer
responds with an answer which includes:
id
which you can use when attaching feedback to an answer.conversationId
which you can optionally pass to the next call to continue the conversation.See the response type reference here.
Return the answer in your response.
app.post("/create-response", async (req, res) => { const { message, conversationId } = req.body;
try { const response = await fetch( `${process.env.INCONVO_API_BASE_URL}/conversations/${conversationId}/response/`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.INCONVO_API_KEY}`, }, body: JSON.stringify({ message, }), } );
if (!response.ok) { throw new Error(`Failed to create response: ${response.status}`); }
const inconvoResponse = await response.json(); res.json(inconvoResponse); } catch (error) { console.error("Error from Inconvo AI:", error); res.status(500).json({ error: "Failed to get response from Inconvo AI" }); }});
Initialize some state to keep track of the message-response pairs, and manage the user interface.
const Assistant = () => { const [message, setMessage] = useState(""); const [messageResponsePairs, setMessageResponsePairs] = useState([]); const [conversationId, setConversationId] = useState(null); const [isLoading, setIsLoading] = useState(false);}
const createNewConversation = async () => { setConversationId(null); setMessage(""); setMessageResponsePairs([]); try { const res = await fetch(`http://localhost:4242/create-conversation`, { method: "POST", }); const conversation = await res.json(); setConversationId(conversation.id); } catch (err) { console.error("Error creating conversation:", err); }};
const MessageInput = ({ message, setMessage, isLoading, conversationId }) => { const handleChange = (e) => { setMessage(e.target.value); };
return ( <label> Enter message: <input id="message" type="text" disabled={!conversationId || isLoading} value={message} onChange={handleChange} /> </label> );};
Put the MessageInput
and submit button in a form.
<form onSubmit={handleSubmit}> <MessageInput message={message} setMessage={setMessage} isLoading={isLoading} conversationId={conversationId} /> <button disabled={isLoading || !conversationId} id="submit"> {isLoading ? `Thinking ...` : `Submit`} </button></form>
Listen to the form’s submit event to know when to send the users’ message to your server which will securely create a response using the Inconvo API.
Take the message
from the response and add it to the messageResponsePairs
array.
const handleSubmit = async (e) => { e.preventDefault(); setIsLoading(true);
try { const res = await fetch(`http://localhost:4242/create-response`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ message, ...(conversationId ? { conversationId } : {}), }), });
if (!res.ok) { throw new Error(`Server responded with status: ${res.status}`); }
const data = await res.json();
if (data.conversationId && !conversationId) { setConversationId(data.conversationId); }
setMessageResponsePairs((prevMessageResponsePairs) => [ ...prevMessageResponsePairs, { message, response: data }, ]); } catch (err) { console.error("Error submitting message:", err); } finally { setIsLoading(false); setMessage(""); }};
Create a component to render the response. This component takes an Inconvo Answer and renders it based on its type [text, chart, table]
.
const ResponseOutput = ({ response }) => { if (!response || Object.keys(response).length === 0) { return <div>Send a message to see a response here</div>; }
switch (response.type) { case "text": return <div>{response.message}</div>;
case "table": return ( <table> <caption>{response.message}</caption> <thead> <tr> {response.table.head.map((h, i) => ( <th key={i}>{h}</th> ))} </tr> </thead> <tbody> {response.table.body.map((row, i) => ( <tr key={i}> {row.map((cell, j) => ( <td key={j}>{cell}</td> ))} </tr> ))} </tbody> </table> );
case "chart": { const data = response.chart.data.map((item) => [item.label, item.value]); switch (response.chart.type) { case "bar": return ( <div className="chart-container"> <div>{response.message}</div> <BarChart data={data} round={2} thousands="," width="400px" /> </div> ); case "line": return ( <div className="chart-container"> <div>{response.message}</div> <LineChart data={data} round={2} thousands="," width="400px" /> </div> ); default: return <div>Unsupported chart type</div>; } }
default: return <div>Unsupported response type</div>; }};
Finally, display the conversation by mapping over the messageResponsePairs
state and rendering the question and answer.
<section> {messageResponsePairs.length > 0 && ( <div className="conversation-history"> {messageResponsePairs.map((messageResponsePair, index) => ( <div key={index} className="message-response-pair"> <div className="message-container"> <p className="message">{messageResponsePair.message}</p> </div> <div className="response-container"> <ResponseOutput response={messageResponsePair.response} /> </div> </div> ))} </div> )}</section>