Skip to content

Commit dd3fb9e

Browse files
Moved AI settings to the bot user level
1 parent f0ebfb9 commit dd3fb9e

File tree

6 files changed

+90
-16
lines changed

6 files changed

+90
-16
lines changed

electron/bot/bot.js

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const { PubSubClient } = require('@twurple/pubsub');
44
const { ApiClient } = require('@twurple/api');
55
const { EventSubWsListener } = require('@twurple/eventsub-ws');
66
const EventQueue = require('./components/base/eventQueue');
7+
const ollamaJs = require('ollama');
78

89
const readline = require('readline');
910

@@ -80,14 +81,54 @@ const performCustomCommand = (command, {type, coolDown, target}, botContext) =>
8081
}
8182
}
8283

84+
class OllamaClient {
85+
constructor(aiSettings) {
86+
this.aiSettings = aiSettings;
87+
this.messages = [];
88+
this.client = new ollamaJs.Ollama({ host: aiSettings.llmUrl });
89+
}
90+
91+
setup = async (setupPrompt) => {
92+
this.messages.push({
93+
role: "system",
94+
content: setupPrompt
95+
});
96+
await this.client.chat({
97+
stream: false,
98+
model: this.aiSettings.llmModel,
99+
messages: this.messages
100+
});
101+
}
102+
103+
send = async (username, message) => {
104+
this.messages.push({
105+
role: "user",
106+
content: `${username}: ${message}`
107+
});
108+
let response = await this.client.chat({
109+
stream: false,
110+
model: this.aiSettings.llmModel,
111+
messages: this.messages
112+
});
113+
this.messages.push(response.message);
114+
return response.message.content;
115+
}
116+
}
117+
83118
// Define configuration options for chat bot
84119
const startBot = async (botConfig, selectedBotUser) => {
85120
try {
86121
let {accessToken, clientId, twitchChannel, botUsers, devMode, broadcasterId} = botConfig;
87122
let botContext = {};
88-
let chatAccessToken = botUsers[selectedBotUser].accessToken;
123+
let {accessToken: chatAccessToken, aiSettings} = botUsers[selectedBotUser];
124+
let ollama;
89125

90-
console.log("STARTING BOT AS : " + selectedBotUser);
126+
if (aiSettings && aiSettings.aiEnabled) {
127+
ollama = new OllamaClient(aiSettings);
128+
ollama.setup(`You are a chatter in a Twitch stream. The following is a description of your personality: "${aiSettings.chatBotPersonalityPrompt}". Every prompt after this one is a message from one of the other people in Twitch chat preceded by their name. When you reply you do not need to append your username to the beginning of your response.`)
129+
}
130+
131+
console.log("STARTING A BOT AS : " + selectedBotUser);
91132

92133
let plugins = [deathCounterPlugin, requestPlugin, cameraObscura, modTools];
93134

@@ -142,6 +183,9 @@ const startBot = async (botConfig, selectedBotUser) => {
142183
console.error(e.message + ": " + e.stack);
143184
EventQueue.sendErrorToChat(new Error(e));
144185
}
186+
} else if (command.includes(selectedBotUser)) {
187+
let response = await ollama.send(context.username, command);
188+
EventQueue.sendInfoToChat(response);
145189
}
146190
}
147191

@@ -254,15 +298,14 @@ const startBot = async (botConfig, selectedBotUser) => {
254298
onMessageHandler(channel, {username, id: ""}, message);
255299
});
256300
client.onConnect(onConnectedHandler);
257-
client.onRaid((channel, username, {viewerCount}) => {onRaid(channel, username, viewerCount)});
258-
259-
let subListener = await pubSubClient.onSubscription(broadcasterId, onSubscription);
260-
let cheerListener = await pubSubClient.onBits(broadcasterId, onBits);
261-
let redemptionListener = await pubSubClient.onRedemption(broadcasterId, onRedemption);
262301

263-
eventListener.onChannelFollow(broadcasterId, broadcasterId, onFollow);
302+
let followListener = eventListener.onChannelFollow(broadcasterId, broadcasterId, onFollow);
303+
let raidListener = eventListener.onChannelRaidTo(broadcasterId, ({raidingBroadcasterName, viewers}) => onRaid(twitchChannel, raidingBroadcasterName, viewers));
304+
let redemptionListener = eventListener.onChannelRedemptionAdd(broadcasterId, onRedemption);
305+
let subListener = eventListener.onChannelSubscription(broadcasterId, onSubscription);
306+
let cheerListener = eventListener.onChannelCheer(broadcasterId, onBits);
264307

265-
listeners = [subListener, cheerListener, redemptionListener];
308+
listeners = [subListener, cheerListener, redemptionListener, followListener, raidListener];
266309

267310
// Connect to twitch chat and pubsub
268311
await client.connect();
@@ -277,7 +320,11 @@ const stopBot = () => {
277320
EventQueue.stopEventListener();
278321
client.quit();
279322
listeners.forEach((listener) => {
280-
listener.remove();
323+
if (listener.remove) {
324+
listener.remove();
325+
} else if (listener.stop) {
326+
listener.stop();
327+
}
281328
});
282329
};
283330

electron/bot/botPlugins/cameraObscura.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ const updateGauge = (gauges) => {
1414
}
1515

1616
const performAction = async (type, id, soundId, subPanel, message, botContext) => {
17-
let port = botContext.botConfig.imageServerPort;
18-
1917
if (type === "VIDEO") {
2018
let video;
2119
if (id === null) {

package-lock.json

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"axios": "^1.7.2",
1717
"electron-oauth2": "^3.0.0",
1818
"electron-squirrel-startup": "^1.0.1",
19+
"ollama": "^0.5.8",
1920
"portfinder": "^1.0.32",
2021
"react": "^18.3.1",
2122
"react-dom": "^18.3.1",

src/components/Bot.jsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import React, { useEffect, useState } from 'react';
22
import { useConfig } from '../hooks/BotConfigHook';
3+
import { toast } from'react-toastify';
34

45
const Settings = () => {
56
const [botConfig, updateBotConfig, refreshBotConfig] = useConfig();
67
const [animationSubPanel, setAnimationSubPanel] = useState('default');
8+
const [selectedBot, setSelectedBot] = useState(null);
79
const [aiSettings, setAiSettings] = useState({
810
aiEnabled: false,
911
aiModerationEnabled: false,
@@ -23,6 +25,11 @@ const Settings = () => {
2325
refreshBotConfig();
2426
}
2527

28+
const onChangeSelectedBotUser = (botUser) => {
29+
setSelectedBot(botUser);
30+
saveAiSettings();
31+
}
32+
2633
const deleteBotUser = async (botUser) => {
2734
await window.api.send('deleteBotUser', botUser);
2835
if (botUser === botConfig.defaultBotUser) {
@@ -38,14 +45,21 @@ const Settings = () => {
3845
}
3946

4047
const saveAiSettings = async () => {
41-
updateBotConfig({...botConfig, aiSettings});
48+
toast.info('Saving AI Settings...');
49+
let botUsers = {...botConfig.botUsers};
50+
botUsers[selectedBot] = {...botUsers[selectedBot], aiSettings};
51+
await updateBotConfig({...botConfig, botUsers});
52+
toast.info('AI settings saved!');
4253
}
4354

4455
useEffect(() => {
4556
if (botConfig?.aiSettings) {
46-
setAiSettings(botConfig?.aiSettings);
57+
setAiSettings(botConfig?.aiSettings[selectedBot]);
58+
}
59+
if (!selectedBot) {
60+
setSelectedBot(botConfig?.twitchChannel);
4761
}
48-
}, [botConfig]);
62+
}, [botConfig, selectedBot]);
4963

5064
return (
5165
<div>
@@ -92,6 +106,11 @@ const Settings = () => {
92106
<h2>AI Integration</h2>
93107
<div style={{display: 'flex', flexDirection:'column'}}>
94108
<h3>LLM Settings</h3>
109+
<select value={selectedBot} onChange={e => onChangeSelectedBotUser(e.target.value)}>
110+
{Object.keys(botConfig?.botUsers || {}).map(userName => {
111+
return (<option key={`llm-bot-${userName}`} value={userName}>{userName}</option>);
112+
})}
113+
</select>
95114
<div>
96115
<input type="checkbox" checked={aiSettings.aiEnabled} onChange={(e) => updateAiSetting('aiEnabled', e.target.checked)} />
97116
<label>Enable Bot Personality</label>

src/components/Home.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const Home = () => {
3838
<label>Launch bot as:</label>
3939
<select value={selectedBotUser} onChange={(e) => {onSelectBot(e.target.value)}}>
4040
{Object.keys(config?.botUsers || {}).map(userName => {
41-
return <option key={`bot-user-${userName}`} value={`${userName}`}>{userName}</option>;
41+
return <option key={`bot-user-${userName}`} value={userName}>{userName}</option>;
4242
})}
4343
</select>
4444
</div>

0 commit comments

Comments
 (0)