updated src/events/messageCreate.ts, src/index.ts; npx tsc no errors

This commit is contained in:
2025-05-18 11:30:01 -04:00
parent c8d35b9e75
commit 5683375649
3 changed files with 83 additions and 19 deletions

View File

@@ -4,10 +4,24 @@ import {
getChannelInfo, getServerConfig, getUserConfig, openChannelInfo, getChannelInfo, getServerConfig, getUserConfig, openChannelInfo,
openConfig, UserConfig, getAttachmentData, getTextFileAttachmentData openConfig, UserConfig, getAttachmentData, getTextFileAttachmentData
} from '../utils/index.js' } from '../utils/index.js'
import { redis } from '../../client.js' import { redis } from '../client.js' // Fixed import: added .ts extension
import fs from 'fs/promises' import fs from 'fs/promises'
import path from 'path' import path from 'path'
// Define interface for model response to improve type safety
interface ModelResponse {
status: 'success' | 'error'
reply: string
metadata?: {
timestamp: string
self_sentiment: number
user_sentiment: { [userId: string]: number }
redis_ops: Array<{ action: 'set' | 'get'; key: string; value?: number }>
need_help: boolean
}
}
/** /**
* Max Message length for free users is 2000 characters (bot or not). * Max Message length for free users is 2000 characters (bot or not).
* Bot supports infinite lengths for normal messages. * Bot supports infinite lengths for normal messages.
@@ -154,14 +168,45 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
personality = 'You are a friendly and helpful AI assistant.' personality = 'You are a friendly and helpful AI assistant.'
} }
// Get user sentiment from Redis // Get user and bot sentiment from Redis
const userSentimentKey = `user:${message.author.id}:sentiment` const userSentimentKey = `user:${message.author.id}:sentiment`
let userSentiment = await redis.get(userSentimentKey) || '0.5' const botSentimentKey = `bot:self_sentiment`
let userSentiment: number
let botSentiment: number
try {
const userSentimentRaw = await redis.get(userSentimentKey)
userSentiment = parseFloat(userSentimentRaw || '0.5')
if (isNaN(userSentiment) || userSentiment < 0 || userSentiment > 1) {
log(`Invalid user sentiment for ${message.author.id}: ${userSentimentRaw}. Using default 0.5.`)
userSentiment = 0.5
await redis.set(userSentimentKey, '0.5').catch((err: Error) => log(`Failed to set default user sentiment: ${err.message}`))
}
} catch (error) {
log(`Failed to get user sentiment from Redis: ${error}`)
userSentiment = 0.5
await redis.set(userSentimentKey, '0.5').catch((err: Error) => log(`Failed to set default user sentiment: ${err.message}`))
}
try {
const botSentimentRaw = await redis.get(botSentimentKey)
botSentiment = parseFloat(botSentimentRaw || '0.5')
if (isNaN(botSentiment) || botSentiment < 0 || botSentiment > 1) {
log(`Invalid bot sentiment: ${botSentimentRaw}. Using default 0.5.`)
botSentiment = 0.5
await redis.set(botSentimentKey, '0.5').catch((err: Error) => log(`Failed to set default bot sentiment: ${err.message}`))
}
} catch (error) {
log(`Failed to get bot sentiment from Redis: ${error}`)
botSentiment = 0.5
await redis.set(botSentimentKey, '0.5').catch((err: Error) => log(`Failed to set default bot sentiment: ${err.message}`))
}
// Construct sentiment data for prompt
const sentimentData = `User ${message.author.id} sentiment: ${userSentiment}, Bot sentiment: ${botSentiment}`
// Construct prompt with [CHARACTER] and [SENTIMENT] // Construct prompt with [CHARACTER] and [SENTIMENT]
//const sentimentData = `User ${message.author.id} sentiment: ${userSentiment}`
const prompt = `[CHARACTER]\n${personality}\n[SENTIMENT]\n${sentimentData}\n[USER_INPUT]\n${cleanedMessage}` const prompt = `[CHARACTER]\n${personality}\n[SENTIMENT]\n${sentimentData}\n[USER_INPUT]\n${cleanedMessage}`
//const prompt = `[CHARACTER]\n${personality}\n[SENTIMENT]\n[USER_INPUT]\n${cleanedMessage}`
// Set up message history queue // Set up message history queue
msgHist.setQueue(chatMessages) msgHist.setQueue(chatMessages)
@@ -182,11 +227,14 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
}) })
// Parse JSON response // Parse JSON response
let jsonResponse: any let jsonResponse: ModelResponse
try { try {
jsonResponse = JSON.parse(response.message.content) jsonResponse = JSON.parse(response.message.content)
if (!jsonResponse.status || !jsonResponse.reply) {
throw new Error('Missing status or reply in model response')
}
} catch (error) { } catch (error) {
throw new Error('Invalid JSON response from model') throw new Error(`Invalid JSON response from model: ${error}`)
} }
if (jsonResponse.status === 'error') { if (jsonResponse.status === 'error') {
@@ -198,10 +246,24 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
// Execute redis_ops // Execute redis_ops
if (jsonResponse.metadata?.redis_ops) { if (jsonResponse.metadata?.redis_ops) {
for (const op of jsonResponse.metadata.redis_ops) { for (const op of jsonResponse.metadata.redis_ops) {
try {
if (op.action === 'set' && op.key && op.value !== undefined) { if (op.action === 'set' && op.key && op.value !== undefined) {
await redis.set(op.key, op.value) // Validate sentiment value
const value = parseFloat(op.value.toString())
if (isNaN(value) || value < 0 || value > 1) {
log(`Invalid sentiment value for ${op.key}: ${op.value}. Skipping.`)
continue
}
await redis.set(op.key, value)
log(`Set ${op.key} to ${value}`)
} else if (op.action === 'get' && op.key) { } else if (op.action === 'get' && op.key) {
await redis.get(op.key) const value = await redis.get(op.key)
log(`Got ${op.key}: ${value}`)
} else {
log(`Invalid redis_op: ${JSON.stringify(op)}. Skipping.`)
}
} catch (error) {
log(`Redis operation failed for ${op.key}: ${error}`)
} }
} }
} }

View File

@@ -1,6 +1,14 @@
import { describe, expect, it, vi } from 'vitest' import { describe, expect, it, vi } from 'vitest'
import events from '../src/events/index.js' import events from '../src/events/index.js'
import { redis } from '../client.js';
jest.mock('../client.js', () => ({
redis: {
get: jest.fn().mockResolvedValue('0.5'),
set: jest.fn().mockResolvedValue('OK'),
},
}));
/** /**
* Mocking ollama found in client.ts because pullModel.ts * Mocking ollama found in client.ts because pullModel.ts
* relies on the existence on ollama. To prevent the mock, * relies on the existence on ollama. To prevent the mock,

View File

@@ -1,21 +1,16 @@
{ {
"compilerOptions": { "compilerOptions": {
// Dependent on node version
"target": "ES2020", "target": "ES2020",
"module": "NodeNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
"strict": true, "strict": true,
// We must set the type
"noImplicitAny": true, "noImplicitAny": true,
"declaration": false, "declaration": false,
// Will not go through node_modules
"skipDefaultLibCheck": true, "skipDefaultLibCheck": true,
"strictNullChecks": true, "strictNullChecks": true,
// We can import json files like JavaScript
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
// Decompile .ts to .js into a folder named build
"outDir": "build", "outDir": "build",
"rootDir": "src", "rootDir": "src",
"baseUrl": ".", "baseUrl": ".",
@@ -23,7 +18,6 @@
"*": ["node_modules/"] "*": ["node_modules/"]
} }
}, },
// environment for env vars
"include": ["src/**/*.ts"], "include": ["src/**/*.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }