410 lines
21 KiB
Markdown
410 lines
21 KiB
Markdown
<!-- lead -->
|
||
How I built RayAI into this Very Blog.
|
||
|
||
In my previous post, <u>[Building a Feature-Rich Blog Platform with Node.js, Express, and Markdown](https://blog.raven-scott.fyi/building-a-feature-rich-blog-platform-with-nodejs-express-and-markdown)</u>, I walked through the technical architecture of a blog platform designed for flexibility, performance, and ease of content management. Today, I’m introducing a new feature: **RayAI** — an interactive AI chat system integrated directly into the blog.
|
||
|
||
RayAI allows visitors to ask questions, get real-time responses, and even interact with the blog content on a deeper level. Think of it as an intelligent assistant embedded within the blog, ready to help users understand, explore, and engage with the content. This is not just a simple Q&A bot; RayAI can handle markdown, provide code snippets, and even let users copy content for their own use.
|
||
|
||
In this post, I’ll take you through every step of integrating RayAI into the blog, from the front-end interface to the back-end API handling. Let's dive into the details of this integration and see how it all comes together.
|
||
|
||
# Source
|
||
|
||
## https://git.ssh.surf/snxraven/ravenscott-blog
|
||
|
||
## Why Implement RayAI?
|
||
|
||
The primary motivation behind RayAI is to enhance user engagement. Static blog posts are great, but they often leave the user with questions or areas where they might want further explanation. RayAI bridges that gap by offering:
|
||
|
||
1. **Real-time Chat**: Users can interact directly with the AI to get instant responses to questions.
|
||
2. **Markdown Support**: RayAI interprets markdown, making it perfect for developers who might want to see code snippets or explanations in a well-formatted way.
|
||
3. **Code Snippet Copying**: Users can easily copy code from AI responses, reducing the time they would spend manually copying and pasting.
|
||
4. **Interactive Tools**: RayAI includes utilities for checking logs, system stats, and even fetching specific data directly within the chat interface.
|
||
|
||
With these features in mind, let's explore how RayAI was integrated into the blog platform.
|
||
|
||
## The Front-End: Creating the Chat Interface
|
||
|
||
The **front-end** implementation of RayAI begins with the **`chat.ejs`** file, which defines the structure of the chat interface. This file includes the layout, user input area, and the section for displaying chat messages. It also leverages **Bootstrap** for styling and **FontAwesome** for icons, keeping the UI clean and responsive.
|
||
|
||
### Setting Up the HTML Structure
|
||
|
||
Here's the code for `chat.ejs`:
|
||
|
||
```html
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="description" content="<%= process.env.OWNER_NAME %>'s Blog">
|
||
<title><%= title %> | <%= process.env.OWNER_NAME %>'s Blog</title>
|
||
|
||
<!-- Bootstrap and Custom Styles -->
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||
<link rel="stylesheet" href="<%= process.env.HOST_URL %>/css/chat.css">
|
||
</head>
|
||
|
||
<body class="bg-dark text-white">
|
||
<!-- Chat Container -->
|
||
<div class="chat-container">
|
||
<!-- Navbar -->
|
||
<nav class="navbar navbar-expand-lg navbar-dark">
|
||
<div class="container-fluid">
|
||
<a class="navbar-brand" href="<%= process.env.HOST_URL %>"><%= process.env.SITE_NAME %></a>
|
||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||
<span class="navbar-toggler-icon"></span>
|
||
</button>
|
||
<div class="collapse navbar-collapse" id="navbarNav">
|
||
<ul class="navbar-nav ms-auto">
|
||
<% menuItems.forEach(item => { %>
|
||
<li class="nav-item">
|
||
<a class="nav-link" href="<%= item.url %>" <%= item.openNewPage ? 'target="_blank"' : '' %>><%= item.title %></a>
|
||
</li>
|
||
<% }) %>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- Chat Box -->
|
||
<div class="chat-box">
|
||
<div id="messages" class="messages"></div>
|
||
|
||
<!-- Input area -->
|
||
<div class="input-area">
|
||
<textarea id="messageInput" class="form-control mb-2" rows="3" placeholder="Type your message..." onkeydown="handleKeyDown(event)" autofocus></textarea>
|
||
|
||
<div class="d-flex justify-content-between">
|
||
<button class="btn btn-secondary" onclick="resetChat()">Reset Chat</button>
|
||
<div id="loading" class="spinner-border text-primary" role="status" style="display: none;">
|
||
<span class="visually-hidden">Loading...</span>
|
||
</div>
|
||
<button class="btn btn-primary" onclick="sendMessage()">Send Message</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bootstrap JS -->
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
||
<!-- Custom Chat JS -->
|
||
<script src="<%= process.env.HOST_URL %>/js/chat.js"></script>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
### Key Sections in the HTML
|
||
|
||
- **Navbar**: Displays the blog title and navigation items dynamically loaded from the `menuItems` array.
|
||
- **Chat Box**: This is where the chat messages between the user and RayAI are displayed. Each message is dynamically added via JavaScript.
|
||
- **Input Area**: Includes a textarea for input and two buttons—one for sending the message, and the other for resetting the chat.
|
||
|
||
#### Styling with Bootstrap and FontAwesome
|
||
|
||
The **Bootstrap CSS framework** is used to ensure the page is responsive and user-friendly. I also included **FontAwesome** for icons, like the send button and the loading spinner, ensuring the interface looks polished.
|
||
|
||
|
||
|
||
## Handling User Input and Chat Logic: `chat.js`
|
||
|
||
Now that the front-end structure is in place, let’s look at the **JavaScript** that powers the chat functionality. This file, **`chat.js`**, is responsible for:
|
||
|
||
- Handling key events (like sending messages when the user presses Enter)
|
||
- Interacting with the AI via API calls
|
||
- Displaying messages in the chat window
|
||
- Managing the loading indicator while waiting for responses
|
||
|
||
### Handling Key Events and Sending Messages
|
||
|
||
The **key event handler** ensures that when the user presses "Enter", the message is sent without needing to click the "Send" button.
|
||
|
||
```javascript
|
||
// Handles key down event to send message on Enter
|
||
function handleKeyDown(event) {
|
||
if (event.key === 'Enter' && !event.shiftKey) {
|
||
event.preventDefault();
|
||
sendMessage();
|
||
}
|
||
}
|
||
```
|
||
|
||
This function ensures that users can submit messages quickly by hitting Enter, making the chat experience smooth.
|
||
|
||
### Sending Messages to the API
|
||
|
||
The **`sendMessage`** function is the core of RayAI's interaction. It takes the user input, sends it to the AI backend, and displays the AI's response.
|
||
|
||
```javascript
|
||
// Sends a message to the chat API
|
||
async function sendMessage() {
|
||
const messageInput = document.getElementById('messageInput');
|
||
let message = messageInput.value.trim();
|
||
|
||
if (message === '') return;
|
||
|
||
// Encode the message to avoid XSS attacks
|
||
message = he.encode(message);
|
||
|
||
// Display the user's message in the chat
|
||
displayMessage(message, 'user');
|
||
messageInput.value = ''; // Clear the input
|
||
toggleLoading(true); // Show loading indicator
|
||
|
||
try {
|
||
const response = await fetch('https://infer.x64.world/chat', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({ message: message })
|
||
});
|
||
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
displayMessage(data.content, 'assistant');
|
||
} else {
|
||
handleErrorResponse(response.status);
|
||
}
|
||
} catch (error) {
|
||
displayMessage('Error: ' + error.message, 'assistant');
|
||
} finally {
|
||
toggleLoading(false); // Hide loading indicator
|
||
}
|
||
}
|
||
```
|
||
|
||
- **API Interaction**: This function sends the user’s message to the API endpoint `https://infer.x64.world/chat`, which handles the AI interaction.
|
||
- **XSS Protection**: The message is encoded using `he.encode()` to prevent cross-site scripting attacks.
|
||
- **Message Display**: After the message is sent, it’s displayed in the chat window and the loading spinner is shown while waiting for the response.
|
||
|
||
### Displaying Chat Messages
|
||
|
||
Once the API responds, the message is displayed in the chat box using the **`displayMessage`** function. This function takes care of rendering both user and AI messages in the chat window.
|
||
|
||
```javascript
|
||
// Displays a message in the chat window
|
||
function displayMessage(content, sender) {
|
||
const messages = document.getElementById('messages');
|
||
const messageElement = document.createElement('div');
|
||
messageElement.classList.add('message', sender);
|
||
|
||
// Decode HTML entities and
|
||
|
||
render Markdown
|
||
const decodedContent = he.decode(content);
|
||
const htmlContent = marked(decodedContent);
|
||
messageElement.innerHTML = htmlContent;
|
||
|
||
messages.appendChild(messageElement);
|
||
messages.scrollTop = messages.scrollHeight; // Scroll to the bottom of the chat
|
||
|
||
// Highlight code blocks if any
|
||
document.querySelectorAll('pre code').forEach((block) => {
|
||
hljs.highlightElement(block);
|
||
if (sender === 'assistant') {
|
||
addCopyButton(block); // Add copy button to code blocks
|
||
}
|
||
});
|
||
|
||
// Add "Copy Full Response" button after each assistant response
|
||
if (sender === 'assistant') {
|
||
addCopyFullResponseButton(messages, messageElement);
|
||
}
|
||
}
|
||
```
|
||
|
||
Key features of this function:
|
||
- **Markdown Support**: RayAI’s responses are parsed as Markdown using **`marked`**, allowing code snippets and formatted text to display beautifully.
|
||
- **Scroll Management**: Automatically scrolls the chat window to the bottom when a new message is added.
|
||
- **Code Highlighting**: If the AI responds with a code snippet, it’s highlighted using **highlight.js**, improving readability for developers.
|
||
- **Copy Functionality**: Adds a "Copy" button to code blocks, allowing users to copy snippets with a single click.
|
||
|
||
### Adding Utility Buttons
|
||
|
||
RayAI goes beyond just chat by offering interactive tools directly within the chat interface. Here's how we implement the "Copy Full Response" and "Copy Code" buttons.
|
||
|
||
```javascript
|
||
// Adds "Copy Full Response" button below the assistant response
|
||
function addCopyFullResponseButton(messagesContainer, messageElement) {
|
||
const copyFullResponseButton = document.createElement('button');
|
||
copyFullResponseButton.classList.add('copy-button');
|
||
copyFullResponseButton.textContent = 'Copy Full Response';
|
||
copyFullResponseButton.addEventListener('click', () => copyFullResponse(messageElement));
|
||
|
||
messagesContainer.appendChild(copyFullResponseButton);
|
||
}
|
||
```
|
||
|
||
This button copies the entire AI response, including any markdown or code blocks, into the user’s clipboard in Markdown format.
|
||
|
||
|
||
|
||
Certainly! Here's an extended and more detailed version of the section on **The Backend: RayAI API Integration**.
|
||
|
||
|
||
|
||
## The Backend: RayAI API Integration
|
||
|
||
The backbone of RayAI’s functionality lies in how it communicates with an external AI service, processes the user's inputs, and returns intelligent, contextual responses. In this section, I will break down how RayAI handles requests, the architecture behind it, and the measures taken to ensure smooth interaction between the blog platform and the AI API.
|
||
|
||
### API Overview and How It Works
|
||
|
||
At its core, the AI powering RayAI is hosted at an external endpoint, `https://infer.x64.world/chat`. This API accepts POST requests with user input and returns a generated response. To integrate this with the blog platform, the JavaScript on the front-end captures the user's message, sends it to the API, and processes the AI's response for display within the chat interface.
|
||
|
||
The **key objectives** of this integration are:
|
||
1. Seamlessly send user input to the AI API.
|
||
2. Receive and display the response from the API in real time.
|
||
3. Handle network issues or server overloads gracefully.
|
||
4. Secure communication to prevent potential vulnerabilities, such as cross-site scripting (XSS) or API abuse.
|
||
|
||
### Sending Requests to the AI API
|
||
|
||
The main logic for sending user messages to the API is contained in the **`sendMessage`** function. Let’s go deeper into the code to explain how the back-and-forth communication is handled.
|
||
|
||
Here’s the function once again:
|
||
|
||
```javascript
|
||
async function sendMessage() {
|
||
const messageInput = document.getElementById('messageInput');
|
||
let message = messageInput.value.trim();
|
||
|
||
if (message === '') return;
|
||
|
||
// Encode the message to avoid XSS attacks
|
||
message = he.encode(message);
|
||
|
||
// Display the user's message in the chat
|
||
displayMessage(message, 'user');
|
||
messageInput.value = ''; // Clear the input
|
||
toggleLoading(true); // Show loading indicator
|
||
|
||
try {
|
||
// Send the user's message to the AI API
|
||
const response = await fetch('https://infer.x64.world/chat', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({ message: message })
|
||
});
|
||
|
||
// Handle the API response
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
displayMessage(data.content, 'assistant');
|
||
} else {
|
||
handleErrorResponse(response.status);
|
||
}
|
||
} catch (error) {
|
||
displayMessage('Error: ' + error.message, 'assistant');
|
||
} finally {
|
||
toggleLoading(false); // Hide loading indicator
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Breaking Down the Code
|
||
|
||
1. **Input Validation**:
|
||
Before sending any request, the user’s input is first **trimmed** to remove unnecessary whitespace. If the input is empty (i.e., no text was entered), the function exits early to avoid sending unnecessary requests.
|
||
|
||
2. **XSS Protection**:
|
||
User input is **encoded** using `he.encode()`. This is crucial for preventing **cross-site scripting (XSS)** attacks, where malicious users could inject harmful code into the chat. By encoding the input, we ensure that any special characters are safely transformed, so the AI API only processes clean text.
|
||
|
||
3. **Displaying the User’s Message**:
|
||
Once the message is encoded, it is immediately displayed in the chat interface (before even getting a response from the AI). This gives the user a smooth, instant feedback loop and ensures that their input is visually confirmed.
|
||
|
||
4. **Sending the Request**:
|
||
The **`fetch()`** function sends the message as a **POST request** to the external API (`https://infer.x64.world/chat`). Here’s what happens in the background:
|
||
- The request contains a JSON object with the key `message` holding the user’s input.
|
||
- Headers are set to indicate that the request body is JSON (`'Content-Type': 'application/json'`), which is what the API expects.
|
||
|
||
5. **Handling the Response**:
|
||
- **Success**: If the response from the API is successful (status code 200), the response body (a JSON object) is parsed. The content generated by the AI is then passed to the `displayMessage` function to be shown as an AI response in the chat interface.
|
||
- **Error**: If the response is not successful (e.g., status codes like 400 or 500), the `handleErrorResponse` function is called to manage error feedback to the user.
|
||
|
||
6. **Loading Indicator**:
|
||
While waiting for the API to respond, a loading indicator (a spinner) is displayed, improving user experience by signaling that the system is processing their request. This indicator is hidden once the response is received.
|
||
|
||
### Error Handling and Rate Limiting
|
||
|
||
Integrating a third-party API like this comes with challenges, such as network errors, rate limits, and potential server overloads. These are handled through robust error-checking and failover mechanisms.
|
||
|
||
```javascript
|
||
function handleErrorResponse(status) {
|
||
if (status === 429) {
|
||
displayMessage('Sorry, I am currently too busy at the moment!', 'assistant');
|
||
} else {
|
||
displayMessage('Error: ' + status, 'assistant');
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Handling Rate Limiting (429 Status Code)
|
||
|
||
When multiple users interact with RayAI simultaneously, or when the API receives too many requests in a short span of time, it may respond with a **429 (Too Many Requests)** status. This is known as **rate limiting**, a mechanism used by APIs to prevent overloading.
|
||
|
||
In such cases, RayAI responds with a friendly message like:
|
||
> "Sorry, I am currently too busy at the moment!"
|
||
|
||
This ensures users know the service is temporarily unavailable, rather than simply failing silently. Additionally, **retry logic** could be implemented if needed, to automatically attempt a new request after a short delay.
|
||
|
||
#### Handling Network and Other Errors
|
||
|
||
Other common errors include server issues (status code 500) or connection issues (e.g., when the user’s internet connection is unstable). These are captured within the `catch` block of the `try-catch` structure. In case of failure, an error message such as the following is displayed:
|
||
> "Error: {error.message}"
|
||
|
||
This ensures that even if something goes wrong, the user receives a clear explanation of the issue.
|
||
|
||
### Securing the API
|
||
|
||
While RayAI’s front-end handles much of the logic, securing the API interaction is essential to prevent abuse, data breaches, or malicious attacks.
|
||
|
||
#### XSS and Input Validation
|
||
|
||
As mentioned earlier, we encode all user input using **`he.encode()`** before sending it to the API. This ensures that special characters (such as `<`, `>`, and `&`) are converted into their HTML-safe equivalents, protecting the application from cross-site scripting attacks.
|
||
|
||
#### Rate Limiting and Abuse Prevention
|
||
|
||
The external AI service includes built-in rate limiting (as indicated by the 429 error code), but further measures could be implemented to prevent API abuse:
|
||
1. **IP-Based Rate Limiting**: You can restrict the number of API requests allowed from a single IP address within a given time window. This prevents users or bots from spamming the service with excessive requests.
|
||
2. **Authentication**: Adding a layer of authentication (such as API keys or OAuth) could ensure that only authorized users have access to RayAI’s features.
|
||
|
||
|
||
|
||
### Enhancing AI Responses
|
||
|
||
RayAI is more than just a simple chatbot. The backend API offers several features that enhance the experience:
|
||
|
||
1. **Markdown Rendering**:
|
||
RayAI’s responses often include formatted text, especially when answering technical questions. The responses are processed using **marked.js** to ensure markdown syntax is rendered correctly in the chat.
|
||
|
||
2. **Code Highlighting**:
|
||
If the AI responds with a code snippet, it’s automatically highlighted using **highlight.js**, which provides syntax highlighting for over 180 programming languages. This is particularly useful for developers who interact with RayAI.
|
||
|
||
3. **Copy Buttons**:
|
||
Each response that includes code also has a "Copy" button. This feature is implemented using custom JavaScript, which allows users to copy entire code blocks or full responses to their clipboard in a single click.
|
||
|
||
|
||
|
||
### Additional Tools in RayAI
|
||
|
||
Apart from chat functionality, RayAI also provides access to **interactive tools** through the chat interface. These include:
|
||
- **Live Logs**: Users can view live system logs in real-time by clicking a "Live Log" button.
|
||
- **System Stats**: Displays system metrics like CPU, memory, and GPU stats through the "GPU Stats" button.
|
||
- **Reset Chat**: This feature allows users to clear the current chat history and reset the conversation.
|
||
It sends a POST request to `https://infer.x64.world/reset-conversation`, clearing the session data.
|
||
|
||
These tools are essential for advanced users who want to monitor system performance or interact with RayAI in more technical ways.
|
||
|
||
## Final Thoughts: The Power of RayAI
|
||
|
||
By integrating RayAI, the blog has evolved from a static platform into a dynamic, interactive experience. RayAI provides real-time interactions, makes technical content more accessible, and helps users engage with blog posts on a deeper level. Features like code highlighting, markdown rendering, and interactive tools bring immense value to both developers and casual readers.
|
||
|
||
In the future, RayAI could be extended even further with additional features like natural language search for blog posts, personalized recommendations, or even interactive tutorials. The possibilities are endless.
|
||
|
||
For now, I’m thrilled with how RayAI enhances the blog, and I can’t wait to see how users engage with it.
|
||
|
||
If you want to try it yourself, head over to the <u>[RayAI Chat Page](https://chat.raven.scott-fyi)</U> and start a conversation!
|