Skip to main content

Chat Bot(Local Server)

Build the basic bot function

I think besides the standard format output and auto calenlar generation. I need to talk to my bot directly. So I went to Ollama API and check there is already a completion there.

Like I did before, I check the Request format:

curl http://localhost:11434/api/chat -d '{
"model": "llama3",
"messages": [
{
"role": "user",
"content": "why is the sky blue?"
}
]
}'

(this is of course streaming, not the complete text, of course.)

and Response format:

{
"model": "llama3",
"created_at": "2023-08-04T08:52:19.385406455-07:00",
"message": {
"role": "assistant",
"content": "The",
"images": null
},
"done": false
}

(there is a final response as well, for the stop recognization)

This helps me so much and saved a lot of my time! I even begin to think how I can change the code before.

Work with GPT and present the tsx code

Later, I directly use GPT to generate the code. Here is the idea of my prompt:

  1. Use the previous code as an example
  2. Let GPT know the new input(Request) and new output (Response) look like
  3. Give GPT my idea of function: able to integrate GPT on my Docusaurus page
  4. Present the real look like for input and output.
  5. Using "``" etc to separate the code, text and different parts.

These are from the Prompt Learning.

The generated code takes some time and here is the idea of its progress:

  1. In the readStream function, we decode the received block of data and split it into multiple lines by line breaks.
  2. For each row, the JSON data deal with these:
    • If the JSON object contains a message field, we append its contents to assistantMessage.content.
    • If the JSON object contains the done field and the value is true, we treat it as the final response and output that response in the console.
  3. When we update the messages array, we check if the last message is already a reply from the assistant. If so, we replace it with the updated assistantMessage; Otherwise, we add assistantMessage as a new message. Finally, we display a full chat history on the page, including messages from the user and the assistant.

Add one more mark responseCompleted for JSON respond:

  1. In the readStream function, we initialize responseCompleted to false.
  2. When we receive a JSON object with the done field and the value is true, we set responseCompleted to true, indicating that the complete response has been received.
  3. If responseCompleted is true, we add the helper's complete message to the messages array and stop reading further from the data stream.
  4. If responseCompleted is false, we continue to call the readStream function recursively to read more data until we receive a complete response.
  5. At the end of the stream, if responseCompleted is still false, we add the helper's current message to the messages array.

At the last, he did a little modify:

  1. In the configuration of axios.post, we set responseType to 'text' to receive the raw text data instead of the parsed JSON.
  2. We split the response data into multiple lines by newlines and then process it line by line.
  3. For each row, we parse the JSON data. If the JSON object contains a message field, we append its contents to the assistantMessageContent string.
  4. If the JSON object contains the done field and the value is true, we add the complete helper message to the messages array and return it.

Finally, I have the code for basic function I want:

Code

Create a folder(ChatComponents) under the path of src/components/ and create the .js file:

import React, { useState } from 'react';
import axios from 'axios';
import styles from './ChatComponent.module.css';

const ChatComponent = () => {
const [inputText, setInputText] = useState('');
const [messages, setMessages] = useState([]);

const handleInputChange = (event) => {
setInputText(event.target.value);
};

const handleSubmit = async (event) => {
event.preventDefault();

const userMessage = {
role: 'user',
content: inputText,
};

setMessages((prevMessages) => [...prevMessages, userMessage]);
setInputText('');

try {
const response = await axios.post('http://localhost:11434/api/chat', {
model: 'gemma',
messages: [...messages, userMessage],
stream: true,
}, {
responseType: 'text',
});

let assistantMessageContent = '';

const lines = response.data.split('\n');
for (const line of lines) {
if (line.trim() === '') continue;

const data = JSON.parse(line);
if (data.message) {
assistantMessageContent += data.message.content;
}
if (data.done) {
setMessages((prevMessages) => [
...prevMessages,
{
role: 'assistant',
content: assistantMessageContent,
},
]);
return;
}
}
} catch (error) {
console.error('Error calling chat API:', error.message);
}
};

return (
<div className={styles.pageContainer}>
<div className={styles.chatContainer}>
<div className={styles.chatBox}>
<div className={styles.welcomeMessage}>Hello, Matthew!</div>
<div className={styles.messagesContainer}>
{messages.map((message, index) => (
<div key={index} className={styles.message}>
<strong>{message.role}:</strong> {message.content}
</div>
))}
</div>
<form onSubmit={handleSubmit} className={styles.inputForm}>
<input
type="text"
value={inputText}
onChange={handleInputChange}
placeholder="Enter your message"
className={styles.inputField}
/>
<button type="submit" className={styles.sendButton}>
Send
</button>
</form>
</div>
</div>
</div>
);
};

export default ChatComponent;

And then create the style(.css) of it, under the same path:

.pageContainer {
position: relative;
min-height: 100vh;
padding: 20px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

.chatContainer {
position: absolute;
bottom: 20%;
left: 50%;
transform: translateX(-50%);
width: 50%;
}

.chatBox {
background-color: #f5f5f5;
border-radius: 10px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.welcomeMessage {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}

.messagesContainer {
max-height: 400px;
overflow-y: auto;
margin-bottom: 10px;
}

.message {
margin-bottom: 10px;
}

.inputForm {
display: flex;
align-items: center;
}

.inputField {
flex: 1;
padding: 8px;
border: 1px solid #ccc;
border-radius: 20px;
margin-right: 10px;
width: 70%;
}

.sendButton {
padding: 8px 16px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}

Finally, under the path of src/pages, create a .jsx file and call the component:

import React from 'react';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Layout from '@theme/Layout';
import ChatComponent from '../components/ChatComponents/ChatComponent';
import Example from '../components/test_calendar/Example';

const MyPage = () => {
const { siteConfig } = useDocusaurusContext();
const { navbar } = siteConfig.themeConfig;

return (
<Layout title={siteConfig.title} description={siteConfig.tagline}>
<div style={{ position: 'relative', paddingTop: '20px' }}>
<div style={{ position: 'absolute', top: '20px', left: '20px' }}>
<img
src="img/final_project/anime_me.jpg"
alt="My Image"
style={{ width: '234px', height: '234px' }}
/>
<div style={{ marginTop: '20px' }}>
<Example />
</div>
</div>
</div>
{/* 其他页面内容 */}
<ChatComponent />
</Layout>
);
};

export default MyPage;

I did a little video about it, so interesting!

The UI actually is editting in the another page:operating_UI.