Q: how do I create a chat bot that connects LLM to one of my chat rooms here?
A: I'll help you create a chat bot that connects an LLM to your XMPP chat rooms. Here's how you can implement it:
- First, create a new bot client class:
// src/bots/LLMBot.ts
import { Client, xml } from '@xmpp/client';
import { OpenAI } from 'openai';
export class LLMBot {
private xmppClient: Client;
private openai: OpenAI;
private roomJID: string;
constructor(config: {
botJID: string,
password: string,
roomJID: string,
openaiApiKey: string
}) {
this.roomJID = config.roomJID;
this.openai = new OpenAI({ apiKey: config.openaiApiKey });
// Initialize XMPP client for the bot
this.xmppClient = new Client({
service: `wss://${VITE_APP_XMPP_BASEDOMAIN}:5443/ws`,
domain: VITE_APP_XMPP_BASEDOMAIN,
resource: 'bot',
username: config.botJID,
password: config.password
});
this.setupListeners();
}
private async setupListeners() {
// Handle incoming messages
this.xmppClient.on('stanza', async (stanza) => {
if (stanza.is('message') && stanza.attrs.type === 'groupchat') {
const body = stanza.getChild('body')?.text();
if (body && !stanza.getChild('delay')) {
await this.handleMessage(body);
}
}
});
// Handle errors
this.xmppClient.on('error', (err) => {
console.error('Bot XMPP error:', err);
});
}
private async handleMessage(message: string) {
try {
// Get response from OpenAI
const completion = await this.openai.chat.completions.create({
messages: [{ role: 'user', content: message }],
model: 'gpt-3.5-turbo',
});
const response = completion.choices[0].message.content;
// Send response back to the room
if (response) {
this.sendMessage(response);
}
} catch (error) {
console.error('Error getting LLM response:', error);
}
}
private sendMessage(message: string) {
const stanza = xml(
'message',
{ to: this.roomJID, type: 'groupchat' },
xml('body', {}, message)
);
this.xmppClient.send(stanza);
}
public async connect() {
await this.xmppClient.start();
// Join the room
const presence = xml(
'presence',
{ to: `${this.roomJID}/LLMBot` }
);
this.xmppClient.send(presence);
}
}
Create a hook to manage the bot:
// src/hooks/useLLMBot.ts
import { useEffect, useRef } from 'react';
import { LLMBot } from '../bots/LLMBot';
export const useLLMBot = (roomJID: string) => {
const botRef = useRef<LLMBot | null>(null);
useEffect(() => {
const initBot = async () => {
const bot = new LLMBot({
botJID: 'llmbot', // Your bot's JID
password: 'botpassword', // Your bot's password
roomJID,
openaiApiKey: import.meta.env.VITE_OPENAI_API_KEY
});
await bot.connect();
botRef.current = bot;
};
initBot();
return () => {
if (botRef.current) {
// Cleanup if needed
}
};
}, [roomJID]);
return botRef.current;
};
Integrate the bot with your ChatWrapper component. Add this to the existing ChatWrapper component (reference lines 137-208 for context):
// In ChatWrapper.tsx
import { useLLMBot } from '../../hooks/useLLMBot';
// Add inside the component:
const bot = useLLMBot(roomJID);
Add the OpenAI API key to your environment variables:
# .env
VITE_OPENAI_API_KEY=your_openai_api_key_here
To use this bot:
- Install required dependencies:
npm install @xmpp/client openai
- Create a bot account on your XMPP server
- Update the bot credentials in the useLLMBot hook
- The bot will automatically:
- Connect to the specified room
- Listen for messages
- Generate responses using the LLM
- Send responses back to the room
The bot will process all messages in the room and respond using the OpenAI API. You can customize the prompt, model, and response handling in the handleMessage method of the LLMBot class.
N.B. (TF): here I skip attempt building it in Python and returned to Typescript
Let's create a TypeScript version of the XMPP bot based on your JavaScript implementation (referencing src/networking/xmppClient.ts lines 28-82 for the client setup). First, let's create the bot:
// src/bot.ts
import { client, xml } from '@xmpp/client'
import debug from '@xmpp/debug'
import dotenv from 'dotenv'
interface BotConfig {
jid: string
password: string
roomJid: string
openaiKey: string
}
class EthoraBot {
private client
private roomJid: string
private openaiKey: string
constructor(config: BotConfig) {
this.roomJid = config.roomJid
this.openaiKey = config.openaiKey
// Initialize XMPP client
this.client = client({
service: 'wss://xmpp.ethoradev.com:5443/ws',
username: config.jid,
password: config.password,
})
// Add event handlers
this.client.on('online', () => this.onOnline())
this.client.on('error', (err) => console.error('❌ Error:', err))
this.client.on('stanza', (stanza) => this.onStanza(stanza))
// Debug output
debug(this.client, true)
}
async start() {
try {
await this.client.start()
console.log('🟢 Bot connected')
} catch (err) {
console.error('Failed to start:', err)
}
}
private async onOnline() {
console.log('🟢 Bot is online')
// Send initial presence
const presence = xml(
'presence',
{ to: `${this.roomJid}/${this.client.jid!.local}` },
xml('x', { xmlns: 'http://jabber.org/protocol/muc' })
)
await this.client.send(presence)
console.log('🎯 Joined room:', this.roomJid)
// Send test message
const message = xml(
'message',
{
to: this.roomJid,
type: 'groupchat',
},
xml('body', {}, 'Bot initialization test'),
xml('data', {
xmlns: 'jabber:client',
fullName: 'AI Assistant',
showInChannel: 'true'
})
)
await this.client.send(message)
}
private async onStanza(stanza: any) {
if (stanza.is('message') && stanza.attrs.type === 'groupchat') {
const body = stanza.getChild('body')
if (body) {
console.log('📩 Message:', body.text())
// Handle message here
}
}
}
}
// Initialize bot
dotenv.config()
const bot = new EthoraBot({
jid: process.env.BOT_JID!,
password: process.env.BOT_PASSWORD!,
roomJid: process.env.ROOM_JID!,
openaiKey: process.env.OPENAI_API_KEY!
})
bot.start()
And create a package.json:
{
"name": "ethora-bot",
"version": "1.0.0",
"main": "dist/bot.js",
"scripts": {
"build": "tsc",
"start": "node dist/bot.js",
"dev": "ts-node src/bot.ts"
},
"dependencies": {
"@xmpp/client": "^0.13.1",
"@xmpp/debug": "^0.13.0",
"dotenv": "^16.0.3"
},
"devDependencies": {
"@types/node": "^18.0.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.5"
}
}
This implementation:
- Uses the same WebSocket connection as your JavaScript client
- Follows the same presence and message format from your XML examples (referencing lines 31-36)
- Includes proper error handling and logging
- Uses TypeScript for better type safety
To run it:
- npm install
- Create .env with the same credentials as before
- npm run dev
the file structure needs to be corrected. Let's organize the files properly:
- First, create this directory structure from the /src folder:
src/
├── bot/
│ └── bot.ts
├── package.json
├── tsconfig.json
└── .env
- Move the bot code to src/bot/bot.ts (same code as before)
- Create a tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["bot/**/*"],
"exclude": ["node_modules", "dist"]
}
Update the package.json scripts (referencing lines 3-7 from your package.json):
{
"scripts": {
"build": "tsc",
"start": "node dist/bot/bot.js",
"dev": "ts-node bot/bot.ts"
}
}
The rest of package.json and .env files remain the same.
Now you should be able to run:
cd src
npm install
npm run dev
Let's fix the TypeScript errors. First, let's create type declarations and update the bot code:
- Create a new file src/types/xmpp.d.ts:
declare module '@xmpp/client' {
export function client(config: {
service: string;
username: string;
password: string;
}): any;
export function xml(
name: string,
attrs?: Record<string, any>,
...children: any[]
): any;
}
declare module '@xmpp/debug' {
export default function debug(client: any, enabled: boolean): void;
}
Update tsconfig.json to include the types:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"typeRoots": ["./types", "./node_modules/@types"]
},
"include": ["bot/**/*", "types/**/*"],
"exclude": ["node_modules", "dist"]
}
This should resolve the TypeScript errors while maintaining the same functionality. The code matches our JavaScript client's implementation (referencing src/networking/xmppClient.ts lines 69-73 for client configuration).
TF: skipped some debugging, OpenAI and added ability to handle chat history.
Encountered TypeScript type issues with the OpenAI API. Let's fix the types and message handling:
import { ChatCompletionMessageParam } from 'openai/resources/chat/completions'
class EthoraBot {
private client: any
private roomJid: string
private openaiKey: string
private openai: OpenAI
private chatHistory: ChatCompletionMessageParam[] = []
constructor(config: BotConfig) {
// ... existing constructor code ...
// Initialize chat history with system message
this.chatHistory = [{
role: "system",
content: "You are a helpful AI assistant in a group chat. Keep responses concise and friendly."
} as ChatCompletionMessageParam]
}
private async onStanza(stanza: XMPPStanza) {
if (stanza.is('message') && stanza.attrs.type === 'groupchat') {
const body = stanza.getChild('body')
if (body) {
const messageText = body.text()
// Don't respond to our own messages
const from = stanza.attrs.from
if (from.includes(this.client.jid!.local)) {
return
}
try {
// Add user message to history
this.chatHistory.push({
role: "user",
content: messageText
} as ChatCompletionMessageParam)
// Keep history limited to last 10 messages
if (this.chatHistory.length > 11) {
this.chatHistory = [
this.chatHistory[0],
...this.chatHistory.slice(-10)
]
}
// Generate AI response with history
const completion = await this.openai.chat.completions.create({
messages: this.chatHistory,
model: "gpt-3.5-turbo",
temperature: 0.7
})
const response = completion.choices[0].message.content
if (response) {
// Add AI response to history
this.chatHistory.push({
role: "assistant",
content: response
} as ChatCompletionMessageParam)
// Send response with nickname
const message = xml(
'message',
{
to: this.roomJid,
type: 'groupchat',
},
xml('body', {}, response),
xml('data', {
xmlns: 'jabber:client',
fullName: 'Ethora AI',
senderFirstName: 'Ethora',
senderLastName: 'AI',
showInChannel: 'true'
})
)
await this.client.send(message)
}
} catch (error) {
console.error('Error generating response:', error)
}
}
}
}
}
Changes done:
- Added proper OpenAI types for chat messages
- Added type assertions for message objects
- Added null check for response
- Added temperature parameter to make responses more consistent
Another couple problems after testing this - bot's name has disappeared in chat, also it's not responding now, but it sends the first test message.
Let's fix the bot's name and response issues. Looking at your code, there are a few things to adjust:
- First, let's fix the initial presence message (referencing src/bot/bot.ts lines 71-77). The nickname needs to be set differently:
const presence = xml(
'presence',
{ to: `${this.roomJid}/${this.client.jid!.local}` },
xml('x', { xmlns: 'http://jabber.org/protocol/muc' }),
xml('data', {
xmlns: 'jabber:client',
fullName: 'Ethora AI',
senderFirstName: 'Ethora',
senderLastName: 'AI',
showInChannel: 'true'
})
)
For the test message (referencing src/bot/bot.ts lines 83-95), update the data attributes:
const message = xml(
'message',
{
to: this.roomJid,
type: 'groupchat',
},
xml('body', {}, 'Bot initialization test'),
xml('data', {
xmlns: 'jabber:client',
fullName: 'Ethora AI',
senderFirstName: 'Ethora',
senderLastName: 'AI',
showInChannel: 'true'
})
)
For debugging the response issue, add some console logs in the onStanza method (referencing src/bot/bot.ts lines 100-110):
private async onStanza(stanza: XMPPStanza) {
if (stanza.is('message') && stanza.attrs.type === 'groupchat') {
const body = stanza.getChild('body')
if (body) {
const messageText = body.text()
console.log('📩 Received message:', messageText)
console.log('📤 From:', stanza.attrs.from)
console.log('🤖 Bot JID:', this.client.jid!.local)
// Don't respond to our own messages
const from = stanza.attrs.from
if (from.includes(this.client.jid!.local)) {
console.log('⏭️ Skipping own message')
return
}
// ... rest of the method
These changes:
1. Fix the bot's name display in chat by using the correct XMPP attributes
2. Debugs bot responsiveness by adding detailed logging
3. Maintain consistency with our XMPP message format (referencing src/assets/test.xml lines 39-46)
Note the .env file format:
BOT_JID=
BOT_PASSWORD=
ROOM_JID=something@conference.xmpp.ethoradev.com
OPENAI_API_KEY=