Introduction
Incorporating an AI chatbot into your static web app has never been easier, thanks to the seamless integration offered by Azure's ecosystem. Azure Static Web Apps, paired with the robust capabilities of Azure Functions and OpenAI's sophisticated AI models, simplifies the deployment and management of conversational AI agents. This powerful combination removes the traditional complexities associated with developing and integrating AI chatbots, allowing developers to enhance their websites with intelligent, interactive conversations effortlessly. Whether you're looking to improve user engagement, provide instant support, or gather user feedback through natural language interactions, Azure provides an all-in-one solution to bring your chatbot to life with minimal hassle.
Prerequisites
- .NET 6.0 or greater (Download here: Download .NET 6.0)
- Node.js installed on your machine
- An Azure Account
- Azure OpenAI resource or an OpenAI account (If you are using Azure OpenAI follow these steps: Create and deploy an Azure OpenAI Service resource)
- Azure Storage emulator (e.g., Azurite setup guide here: Use the Azurite emulator for local Azure Storage development)
Step-by-Step Guide
For this walkthrough, we will showcase how to seamlessly integrate a chatbot into a bookshop website, elevating user engagement by offering personalized book recommendations. 🪄
Deploy an Azure Function App with OpenAI Extension
This guide will help you deploy a chatbot using the Azure Functions OpenAI Extension. Developed by Azure and available as a NuGet package, this extension simplifies the process of getting your chatbot APIs operational within minutes.
Getting Started
1. Clone the Extension and Sample Repository: Start by cloning the repository from https://github.com/Azure/azure-functions-openai-extension/. To better understand how this integration works I also recommend reading the docs regarding the general requirements as well as the chatbot sample specific instructions.
2. Navigate to the Sample Chatbot Project: Once cloned, navigate to the sample chatbot project located at samples/chat/nodejs
. We will use this sample project to deploy to our Azure Function App and expose our chatbot endpoints that will later on be consumed by our Static Web App.
3. Configure Environment Variables for testing: To connect to your OpenAI resource locally during testing, configure the required environment variables in the samples/chat/nodejs/local.settings.json
file. Depending on your setup, use one of the following templates:
- For Azure OpenAI resource you will have to add CHAT_MODEL_DEPLOYMENT_NAME, AZURE_OPENAI_KEY and AZURE_OPENAI_ENDPOINT:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
"FUNCTIONS_WORKER_RUNTIME": "node",
"CHAT_MODEL_DEPLOYMENT_NAME": "<your-deployment_name>",
"AZURE_OPENAI_KEY": "<your_key_here>",
"AZURE_OPENAI_ENDPOINT": "https://<your-openai-endpoint>.openai.azure.com/"
}
}
- Or, for direct OpenAI API access you will have to add OPENAI_KEY. You can also override the CHAT_MODEL_DEPLOYMENT_NAME if needed:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
"FUNCTIONS_WORKER_RUNTIME": "node",
"CHAT_MODEL_DEPLOYMENT_NAME": "gpt-3.5-turbo"
"OPENAI_KEY": "<your_openai_key_here>"
}
}
4. Start the Application Locally: Test your chatbot by running it locally. Use the following commands:
azurite --silent --location c:\azurite --debug c:\azurite\debug.log --blobPort 8888 --queuePort 9999 --tablePort 11111
npm install && dotnet build --output bin && npm run build && npm run start
This will start up the chat bot for you and should log the following:
5. Interact with Your Chatbot via REST API: You can now interact with your chatbot via the REST API. To create a new chat session, send messages and list all messages with responses, use the following examples:
- Create a chat session:
PUT http://localhost:7071/api/chats/TailoredTales
{
"instructions": "You are TailoredTales, a guide in the quest for the perfect read. Respond with brief, engaging hints, leading seekers to books that fit as if destined, blending curiosity with the promise of a great discovery."
}
- To send messages to the bot, execute the following request:
POST http://localhost:7071/api/chats/TailoredTales
What's a captivating book that blends mystery with a touch of magic, ideal for someone who loves both genres and is looking for a new book?
- To list all questions with answers for later use in populating your chatbot window, execute the following:
GET http://localhost:7071/api/chats/TailoredTales?timestampUTC=2023-11-9T22:00
Now, you're all set to deploy your chatbot to an Azure Functions App.
Deploy to an Azure Function App
Now that we have our code ready, it's time to deploy it to an Azure Function app. The process is straightforward, with just a few additional steps to ensure everything runs smoothly. Here's how you can deploy to a Function app:
1. Change function auth level to anonymous: Auth will fully be handled by the Static Web Apps Linked Backends integration. Change all authLevel in
samples/chat/src/functions/app.ts
from function to anonymous.2. Choose Your Deployment Method: Azure offers various deployment methods, making it flexible to deploy your code. You can explore different options in the Azure Functions Deployment Technologies documentation. You will simply want to deploy the chatbot sample project.
3. Setting Environment Variables: When deploying, it's essential to configure the necessary environment variables for your Function app. You will need to add any variables also needed for local testing like the AZURE_OPENAI_ENDPOINT as well as the CHAT_MODEL_DEPLOYMENT_NAME. Learn how to set application settings like these in the Azure Function App Settings guide. :light_bulb: Instead of setting the AZURE_OPEN_AI_KEY or uploading the secret to the a Key Vault you can simply add a System Managed Identity and assign the user/function app
Cognitive Services OpenAI User
role on the Azure OpenAI resource.4. Testing on Your Function: Once deployed, you can perform the same operations as tested locally, but this time against your function host in Azure. The setup ensures that your chatbot works seamlessly in the Azure environment.
By following these steps, you'll have your chatbot up and running on Azure Function App, ready to interact with users in a production environment.
Develop and deploy the front end to Azure Static Web Apps
The next thing we want to do is create a simple React.js app that uses these endpoints to render a chat window into our web app.
Let's create a new React app
1. Set Up the Project: Create a new project for your front-end app. Initialize it with the chosen framework or tools. For this demo we will be using React.js. We will also install axios to make HTTP requests to interact with our chatbot backend. Feel free to use any other technology you are comfortable with.
npx create-react-app chatbot-app
cd chatbot-app
npm install axios
2. Create the ChatService.js: This file is responsible for facilitating the integration between the frontend application and the chatbot's backend API using the axios library.
- It can create a new chat session by calling the
createChat
API. - It can post messages to an existing chat session using the
postMessage
API. - It can retrieve the state of a chat session at a specific timestamp using the
getChatState
API. - The
<function-hostname>
in the BASE_URL is only required for local testing. As soon as we have linked the deployed Static Web App to our Function App the integration will fully handle routing and authentication for us and we can simple make calls to the Static Web Apps hostname by changing the BASE_URL to/api/chats
. For now replace the value with either the local host running the chatbot app or your functions hostname (In that case you will have to also configure CORS on the function app).
import axios from 'axios';
const BASE_URL = '<function-hostname>/api/chats/';
export const createChat = async (chatId, instructions) => {
try {
const response = await axios.put(`${BASE_URL}${chatId}`, {
instructions,
});
return response.data;
} catch (error) {
console.error("Error creating chat:", error);
throw error;
}
};
export const postMessage = async (chatId, message) => {
try {
await axios.post(`${BASE_URL}${chatId}`, message, {
headers: {
'Content-Type': 'text/plain',
},
});
} catch (error) {
console.error("Error posting message:", error);
throw error;
}
};
export const getChatState = async (chatId, timestampUTC) => {
try {
const response = await axios.get(`${BASE_URL}${chatId}`, {
params: { timestampUTC },
});
return response.data;
} catch (error) {
console.error("Error getting chat state:", error);
throw error;
}
};
3. Creating the Chat Box Component: We are creating a component responsible for creating a chat interface and handling chat interactions with a chatbot backend. Here's a summary of its functionality:
- State Management: It uses React state hooks to manage the following state variables:
chatId
: Stores the unique identifier for the chat session.message
: Stores the user's input message.messages
: Stores the chat messages exchanged between the user and the chatbot.lastUpdate
: Keeps track of the timestamp of the last chat message update.
- Initialization: When the component mounts, it initializes a chat session by calling the
createChat
function from theChatService
and sets thechatId
. - Polling for Messages: It sets up a polling mechanism (
pollForMessages
) to check for new chatbot responses at regular intervals. When new responses are received, they are added to themessages
state. - Message Submission: When the user submits a message, it adds the user's message to the chat interface, calls the
postMessage
function to send the message to the chatbot backend, and triggers the polling for new chatbot responses. - Scrolling: It ensures that the chat interface automatically scrolls to display the latest messages at the bottom.
- Rendered Elements: It renders a chat interface with the chat messages and an input field for users to type their messages.
- Conditional Rendering: Messages from the chatbot are displayed with an "Assistant" label.
import React, { useState, useEffect, useRef } from 'react';
import { createChat, postMessage, getChatState } from './ChatService';
import './ChatBox.css';
const ChatBox = () => {
const [chatId, setChatId] = useState('');
const [message, setMessage] = useState('');
const [messages, setMessages] = useState([]);
const [lastUpdate, setLastUpdate] = useState(new Date().toISOString());
const messagesEndRef = useRef(null);
useEffect(() => {
const initChat = async () => {
const chatSessionId = `chat_${new Date().getTime()}`;
const instructions = "You are TailoredTales, a guide in the quest for the perfect read. Respond with brief, engaging hints, leading seekers to books that fit as if destined, blending curiosity with the promise of a great discovery.";
setChatId(chatSessionId);
await createChat(chatSessionId, instructions);
};
initChat();
}, []);
const pollForMessages = async () => {
const maxPollingDuration = 10000;
const pollingInterval = 200;
let totalPollingTime = 0;
const poll = setInterval(() => {
getChatState(chatId, lastUpdate).then(data => {
if (data !== null) {
const assistantMessages = data.RecentMessages.filter(msg => msg.Role === 'assistant');
if (assistantMessages.length > 0) {
setMessages(prevMessages => [...prevMessages, ...assistantMessages]);
setLastUpdate(new Date().toISOString());
clearInterval(poll);
}
}
});
totalPollingTime += pollingInterval;
if (totalPollingTime >= maxPollingDuration) {
clearInterval(poll);
}
}, pollingInterval);
};
const handleSubmit = async (e) => {
e.preventDefault();
if (message) {
const tempMessage = {
Content: message,
Role: 'user',
id: new Date().getTime(),
};
setMessages(prevMessages => [...prevMessages, tempMessage]);
await postMessage(chatId, message);
setMessage('');
pollForMessages();
}
};
useEffect(() => {
const messagesContainer = messagesEndRef.current;
if (messagesContainer) {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
}, [messages]);
return (
<div className="chatbox-container">
<div className="chatbox-header">
Tailored Tales
</div>
<div className="chatbox-messages" ref={messagesEndRef}>
{messages.map((msg, index) => (
<div key={index} className={`message ${msg.Role}`}>
{msg.Role === 'assistant' && <div className="message-role">Assistant</div>}
<span>{msg.Content}</span>
</div>
))}
</div>
<form onSubmit={handleSubmit} className="chatbox-form">
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Type here..."
className="chatbox-input"
/>
</form>
</div>
);
};
export default ChatBox;
4. Render the Chat Box: Don't forget to add your new chat box component and render it in you App.js.
import React from 'react';
import ChatBox from './ChatBox';
function App() {
return (
<div className="App">
<ChatBox />
</div>
);
}
export default App;
5. Add some styling: To make our chatbot also look good we can add this CSS file to pin the chat box to the bottom right corner and add some basic styling.
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
* {
font-family: 'Inter', sans-serif;
}
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
.chatbox-container {
position: fixed;
bottom: 10px;
right: 10px;
width: 320px;
height: 600px;
display: flex;
flex-direction: column;
justify-content: space-between;
background-color: #fff;
border-radius: 16px;
box-shadow: rgba(9, 30, 66, 0.25) 0px 1px 1px, rgba(9, 30, 66, 0.13) 0px 0px 1px 1px;
font-family: 'Inter', sans-serif;
padding-top: 0;
}
.chatbox-header {
padding: 12px 20px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
font-size: 1.2em;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.05);
text-align: left;
font-weight: 900;
}
.chatbox-messages {
flex-grow: 1;
padding: 15px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 10px;
overflow-y: auto;
margin-top: 0;
scrollbar-width: none;
}
.message {
max-width: 75%;
word-wrap: break-word;
padding: 10px 14px;
border-radius: 18px;
line-height: 1.4;
position: relative;
margin-bottom: 4px;
}
.message.user {
align-self: flex-end;
background-color: #5851ff;
color: #fff;
border-bottom-right-radius: 4px;
}
.message.assistant {
align-self: flex-start;
background-color: #efefef;
color: #333;
border-bottom-left-radius: 4px;
}
.chatbox-form {
display: flex;
padding: 10px 15px;
background-color: #ffffff;
box-shadow: 0 -2px 2px rgba(0, 0, 0, 0.1);
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
}
.chatbox-input {
flex-grow: 1;
margin-right: 8px;
padding: 10px;
border: 0px solid #d1d1d4;
border-radius: 18px;
background-color: #ffffff;
outline: none;
}
.message-role {
font-size: 0.7rem;
color: #6c757d;
margin-bottom: 2px;
}
Deploy Your React App to Azure Static Web Apps
To make your bookshop website accessible to your users, you'll need to deploy your React app to Azure Static Web Apps (Quickstart: Build your first static web app). Follow these steps, including creating a GitHub repository and setting up continuous integration
1. Create a GitHub Repository: If your React app isn't already in a GitHub repository, create a new one. You can follow GitHub's guide on creating a new repository.
2. Push Your Code to GitHub: Push your React app's code to the newly created GitHub repository. You can use Git commands or a Git client for this.
4. Create a Static Web App: Navigate to Azure Static Web Apps in the Azure Portal and create a new static web app by specifying the source control as your GitHub repository.
5. Access you site: Once the deployment is finished you can access your site through the automatically created default hostname. You can check teh status of your deployment in your GitHub action run.
Link your Function App to your Static Web Apps
Simplifying the integration process, Azure offers the "Azure Static Web Apps Linked Backends" feature (Bring your own functions to Azure Static Web Apps documentation), allowing you to seamlessly link your static web application to backend services, including your Azure Function App where the chatbot logic resides. Follow these steps to set up the integration effortlessly:
2. Select Your Static Web App: Locate and select the Azure Static Web App that hosts your chatbot website.
3. Navigate to APIs: In the Static Web App settings, find the "APIs" section.
4. Add a Backend: Click the "Link" button to configure the integration.
5. Select Your Function App: Choose your Azure Function App from the available list of backends.
You are all done! 🥳
You can now go to your static web app and start asking your Tailored Tales bot anything about books.
Links
- Final React Project: https://github.com/annikel/chatbot-app
Annina Keller is a software engineer on the Azure Static Web Apps team. (Twitter: @anninake)
Updated Mar 12, 2024
Version 1.0anninakeller
Microsoft
Joined June 07, 2022
Apps on Azure Blog
Follow this blog board to get notified when there's new activity