Compare commits

..

2 Commits

4 changed files with 25 additions and 37 deletions

View File

@@ -1,19 +1,8 @@
# use node LTS image for version 22
FROM node:jod-alpine FROM node:jod-alpine
# set working directory inside container
WORKDIR /app WORKDIR /app
COPY package.json package-lock.json ./
# copy package.json and the lock file into the container, and src files
COPY ./src ./src
COPY ./*.json ./
COPY ./.env ./
# install dependencies, breaks
RUN npm install RUN npm install
COPY src/ ./src/
# build the typescript code COPY src/personality.json ./src/
RUN npm run build RUN npm run build
# start the application
CMD ["npm", "run", "prod"] CMD ["npm", "run", "prod"]

View File

@@ -31,7 +31,8 @@ You are a Discord chatbot with a dynamic personality defined in [CHARACTER] befo
- user_sentiment: An object mapping user IDs to sentiment scores (0-1). A sentiment score of 0 is strong dislike, 0.5 is neutral, and 1.0 is strong like or love. - user_sentiment: An object mapping user IDs to sentiment scores (0-1). A sentiment score of 0 is strong dislike, 0.5 is neutral, and 1.0 is strong like or love.
- redis_ops: An array of objects with "action" ("set" or "get"), "key" (prefixed with "bot:" or "user:"), and optional "value" (for set operations). - redis_ops: An array of objects with "action" ("set" or "get"), "key" (prefixed with "bot:" or "user:"), and optional "value" (for set operations).
- need_help: Boolean indicating if the user needs assistance. - need_help: Boolean indicating if the user needs assistance.
Only use "set" or "get" for redis_ops actions. Ensure keys are prefixed with "bot:" or "user:". Do not include metadata or Redis commands in the reply field. Output ONLY the JSON object, with no Markdown, code fences, or extra text. Example:
{"status":"success","reply":"Hi","metadata":{"timestamp":"2025-05-18T16:00:00Z","self_sentiment":0.5,"user_sentiment":{"<user_id>":0.5},"redis_ops":[{"action":"set","key":"user:<user_id>:sentiment","value":0.5}],"need_help":false}}
[CHARACTER] [CHARACTER]
[SENTIMENT] [SENTIMENT]

View File

@@ -1,12 +1,9 @@
# creates the docker compose version: '3.8'
# build individual services
services: services:
# setup discord bot container
discord: discord:
build: ./ # find docker file in designated path build: ./
container_name: discord container_name: discord
restart: always # rebuild container always restart: always
image: gitea.matrixwide.com/alex/discord-aidolls:0.1.0 image: gitea.matrixwide.com/alex/discord-aidolls:0.1.0
environment: environment:
CLIENT_TOKEN: ${CLIENT_TOKEN} CLIENT_TOKEN: ${CLIENT_TOKEN}
@@ -19,22 +16,19 @@ services:
ollama-net: ollama-net:
ipv4_address: ${DISCORD_IP} ipv4_address: ${DISCORD_IP}
volumes: volumes:
- discord:/src/app # docker will not make this for you, make it yourself - discord:/app/data
- ./src:/app/src # Mount src/ to ensure personality.json is available
# setup redis container
redis: redis:
image: redis:latest image: redis:alpine # Use alpine for smaller footprint
container_name: redis container_name: redis
restart: always restart: always
networks: networks:
ollama-net: ollama-net:
ipv4_address: ${REDIS_IP} ipv4_address: ${REDIS_IP}
volumes: volumes:
- redis:/root/.redis - redis:/data
ports: ports:
- ${REDIS_PORT}:${REDIS_PORT} - ${REDIS_PORT}:${REDIS_PORT}
# create a network that supports giving addresses withing a specific subnet
networks: networks:
ollama-net: ollama-net:
driver: bridge driver: bridge
@@ -42,7 +36,6 @@ networks:
driver: default driver: default
config: config:
- subnet: ${SUBNET_ADDRESS}/16 - subnet: ${SUBNET_ADDRESS}/16
volumes: volumes:
discord: discord:
redis: redis:

View File

@@ -132,9 +132,9 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
if (chatMessages.length === 0) { if (chatMessages.length === 0) {
chatMessages = await new Promise((resolve, reject) => { chatMessages = await new Promise((resolve, reject) => {
openChannelInfo(message.channelId, message.channel as TextChannel, message.author.tag) openChannelInfo(message.channelId, message.channel as TextChannel, message.author.tag)
getChannelInfo(`${message.channelId}-${message.author.username}.json`, (channelInfo) => { getChannelInfo(`${message.channelId}-${message.author.username}.json`, (config) => {
if (channelInfo?.messages) { if (config?.messages) {
resolve(channelInfo.messages) resolve(config.messages)
} else { } else {
reject(new Error(`Failed to find ${message.author.username}'s history. Try chatting again.`)) reject(new Error(`Failed to find ${message.author.username}'s history. Try chatting again.`))
} }
@@ -160,10 +160,9 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
// Load personality // Load personality
let personality: string let personality: string
try { try {
// Fix __dirname for ESM by using import.meta.url
const __filename = fileURLToPath(import.meta.url) const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename) const __dirname = path.dirname(__filename)
const personalityPath = path.join(__dirname, '../../personality.json') const personalityPath = path.join(__dirname, '../personality.json')
const personalityData = await fs.readFile(personalityPath, 'utf-8') const personalityData = await fs.readFile(personalityPath, 'utf-8')
const personalityJson = JSON.parse(personalityData) const personalityJson = JSON.parse(personalityData)
personality = personalityJson.character || 'You are a friendly and helpful AI assistant.' personality = personalityJson.character || 'You are a friendly and helpful AI assistant.'
@@ -195,7 +194,11 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
try { try {
const botSentimentRaw = await redis.get(botSentimentKey) const botSentimentRaw = await redis.get(botSentimentKey)
botSentiment = parseFloat(botSentimentRaw || '0.5') botSentiment = parseFloat(botSentimentRaw || '0.5')
if (isNaN(botSentiment) || botSentiment < 0 || botSentiment > 1) { if (botSentimentRaw === null) {
log(`Bot sentiment not initialized. Setting to 0.5.`)
botSentiment = 0.5
await redis.set(botSentimentKey, '0.5').catch((err: Error) => log(`Failed to set default bot sentiment: ${err.message}`))
} else if (isNaN(botSentiment) || botSentiment < 0 || botSentiment > 1) {
log(`Invalid bot sentiment: ${botSentimentRaw}. Using default 0.5.`) log(`Invalid bot sentiment: ${botSentimentRaw}. Using default 0.5.`)
botSentiment = 0.5 botSentiment = 0.5
await redis.set(botSentimentKey, '0.5').catch((err: Error) => log(`Failed to set default bot sentiment: ${err.message}`)) await redis.set(botSentimentKey, '0.5').catch((err: Error) => log(`Failed to set default bot sentiment: ${err.message}`))
@@ -244,7 +247,9 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
} }
} catch (error) { } catch (error) {
log(`Failed to parse model response: ${error}`) log(`Failed to parse model response: ${error}`)
throw new Error(`Invalid JSON response from model: ${error}`) message.reply('Sorry, Im having trouble thinking right now. Try again?')
msgHist.pop()
return
} }
if (jsonResponse.status === 'error') { if (jsonResponse.status === 'error') {