everything is broken yay
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
# Existing Dockerfile content
|
|
||||||
FROM node:20.11.0-slim
|
FROM node:20.11.0-slim
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN mkdir -p /app/data && chown -R node:node /app/data
|
RUN mkdir -p /app/data && chown -R node:node /app/data && chmod -R u+rw /app/data
|
||||||
USER node
|
USER node
|
||||||
CMD ["npm", "run", "prod"]
|
CMD ["npm", "run", "prod"]
|
||||||
|
|||||||
0
discord-aidolls@0.1.0
Normal file
0
discord-aidolls@0.1.0
Normal file
@@ -15,7 +15,7 @@ services:
|
|||||||
redis_discord-net:
|
redis_discord-net:
|
||||||
ipv4_address: ${DISCORD_IP}
|
ipv4_address: ${DISCORD_IP}
|
||||||
volumes:
|
volumes:
|
||||||
- discord_data:/app/data
|
- ./discord_data:/app/data
|
||||||
- ./src:/app/src
|
- ./src:/app/src
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "redis-cli", "-h", "${REDIS_IP}", "-p", "${REDIS_PORT}", "PING"]
|
test: ["CMD", "redis-cli", "-h", "${REDIS_IP}", "-p", "${REDIS_PORT}", "PING"]
|
||||||
|
|||||||
@@ -46,3 +46,4 @@
|
|||||||
"node": ">=22.12.0"
|
"node": ">=22.12.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ export const Capacity: SlashCommand = {
|
|||||||
name: 'modify-capacity',
|
name: 'modify-capacity',
|
||||||
description: 'maximum amount messages bot will hold for context.',
|
description: 'maximum amount messages bot will hold for context.',
|
||||||
|
|
||||||
// set available user options to pass to the command
|
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: 'context-capacity',
|
name: 'context-capacity',
|
||||||
@@ -15,20 +14,23 @@ export const Capacity: SlashCommand = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
// Query for message information and set the style
|
|
||||||
run: async (client: Client, interaction: CommandInteraction) => {
|
run: async (client: Client, interaction: CommandInteraction) => {
|
||||||
// fetch channel and message
|
|
||||||
const channel = await client.channels.fetch(interaction.channelId)
|
const channel = await client.channels.fetch(interaction.channelId)
|
||||||
if (!channel || !UserCommand.includes(channel.type)) return
|
if (!channel || !UserCommand.includes(channel.type)) return
|
||||||
|
|
||||||
// set state of bot chat features
|
const capacity = interaction.options.get('context-capacity')?.value
|
||||||
openConfig(`${interaction.user.username}-config.json`, interaction.commandName,
|
if (typeof capacity !== 'number' || capacity <= 0) {
|
||||||
interaction.options.get('context-capacity')?.value
|
await interaction.reply({
|
||||||
)
|
content: 'Please provide a valid positive number for capacity.',
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
interaction.reply({
|
await openConfig(`${interaction.user.id}-config.json`, 'modify-capacity', capacity)
|
||||||
content: `Max message history is now set to \`${interaction.options.get('context-capacity')?.value}\``,
|
await interaction.reply({
|
||||||
|
content: `Max message history is now set to \`${capacity}\`.`,
|
||||||
flags: MessageFlags.Ephemeral
|
flags: MessageFlags.Ephemeral
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,31 +5,26 @@ export const ClearUserChannelHistory: SlashCommand = {
|
|||||||
name: 'clear-user-channel-history',
|
name: 'clear-user-channel-history',
|
||||||
description: 'clears history for user in the current channel',
|
description: 'clears history for user in the current channel',
|
||||||
|
|
||||||
// Clear channel history for intended user
|
|
||||||
run: async (client: Client, interaction: CommandInteraction) => {
|
run: async (client: Client, interaction: CommandInteraction) => {
|
||||||
// fetch current channel
|
|
||||||
const channel: Channel | null = await client.channels.fetch(interaction.channelId)
|
const channel: Channel | null = await client.channels.fetch(interaction.channelId)
|
||||||
|
|
||||||
// if not an existing channel or a GuildText, fail command
|
|
||||||
if (!channel || !UserCommand.includes(channel.type)) return
|
if (!channel || !UserCommand.includes(channel.type)) return
|
||||||
|
|
||||||
// clear channel info for user
|
|
||||||
const successfulWipe = await clearChannelInfo(
|
const successfulWipe = await clearChannelInfo(
|
||||||
interaction.channelId,
|
interaction.channelId,
|
||||||
interaction.channel as TextChannel,
|
interaction.channel as TextChannel,
|
||||||
interaction.user.username
|
interaction.user.id
|
||||||
)
|
)
|
||||||
|
|
||||||
// check result of clearing history
|
if (successfulWipe) {
|
||||||
if (successfulWipe)
|
await interaction.reply({
|
||||||
interaction.reply({
|
content: `History cleared in **this channel** for **${interaction.user.username}**.`,
|
||||||
content: `History cleared in **this channel** cleared for **${interaction.user.username}**.`,
|
|
||||||
flags: MessageFlags.Ephemeral
|
flags: MessageFlags.Ephemeral
|
||||||
})
|
})
|
||||||
else
|
} else {
|
||||||
interaction.reply({
|
await interaction.reply({
|
||||||
content: `History was not be found for **${interaction.user.username}** in **this channel**.\n\nPlease chat with **${client.user?.username}** to start a chat history.`,
|
content: `History was not found for **${interaction.user.username}** in **this channel**.\n\nPlease chat with **${client.user?.username}** to start a chat history.`,
|
||||||
flags: MessageFlags.Ephemeral
|
flags: MessageFlags.Ephemeral
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ export const MessageStream: SlashCommand = {
|
|||||||
name: 'message-stream',
|
name: 'message-stream',
|
||||||
description: 'change preference on message streaming from ollama. WARNING: can be very slow due to Discord limits.',
|
description: 'change preference on message streaming from ollama. WARNING: can be very slow due to Discord limits.',
|
||||||
|
|
||||||
// user option(s) for setting stream
|
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: 'stream',
|
name: 'stream',
|
||||||
@@ -15,20 +14,15 @@ export const MessageStream: SlashCommand = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
// change preferences based on command
|
|
||||||
run: async (client: Client, interaction: CommandInteraction) => {
|
run: async (client: Client, interaction: CommandInteraction) => {
|
||||||
// verify channel
|
|
||||||
const channel = await client.channels.fetch(interaction.channelId)
|
const channel = await client.channels.fetch(interaction.channelId)
|
||||||
if (!channel || !UserCommand.includes(channel.type)) return
|
if (!channel || !UserCommand.includes(channel.type)) return
|
||||||
|
|
||||||
// save value to json and write to it
|
const stream = interaction.options.get('stream')?.value
|
||||||
openConfig(`${interaction.user.username}-config.json`, interaction.commandName,
|
await openConfig(`${interaction.user.id}-config.json`, 'message-stream', stream)
|
||||||
interaction.options.get('stream')?.value
|
await interaction.reply({
|
||||||
)
|
content: `Message streaming is now set to: \`${stream}\``,
|
||||||
|
|
||||||
interaction.reply({
|
|
||||||
content: `Message streaming is now set to: \`${interaction.options.get('stream')?.value}\``,
|
|
||||||
flags: MessageFlags.Ephemeral
|
flags: MessageFlags.Ephemeral
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { ApplicationCommandOptionType, Client, CommandInteraction } from "discord.js"
|
import { ApplicationCommandOptionType, Client, CommandInteraction, MessageFlags } from 'discord.js'
|
||||||
import { ollama } from "../client.js"
|
import { ollama } from '../client.js'
|
||||||
import { ModelResponse } from "ollama"
|
import { ModelResponse } from 'ollama'
|
||||||
import { openConfig, UserCommand, SlashCommand } from "../utils/index.js"
|
import { openConfig, UserCommand, SlashCommand } from '../utils/index.js'
|
||||||
|
|
||||||
export const SwitchModel: SlashCommand = {
|
export const SwitchModel: SlashCommand = {
|
||||||
name: 'switch-model',
|
name: 'switch-model',
|
||||||
description: 'switches current model to use.',
|
description: 'switches current model to use.',
|
||||||
|
|
||||||
// set available user options to pass to the command
|
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: 'model-to-use',
|
name: 'model-to-use',
|
||||||
@@ -17,51 +16,38 @@ export const SwitchModel: SlashCommand = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
// Switch user preferred model if available in local library
|
|
||||||
run: async (client: Client, interaction: CommandInteraction) => {
|
run: async (client: Client, interaction: CommandInteraction) => {
|
||||||
await interaction.deferReply()
|
await interaction.deferReply()
|
||||||
|
const modelInput: string = interaction.options.get('model-to-use')!.value as string
|
||||||
|
|
||||||
const modelInput: string = interaction.options.get('model-to-use')!!.value as string
|
|
||||||
|
|
||||||
// fetch channel and message
|
|
||||||
const channel = await client.channels.fetch(interaction.channelId)
|
const channel = await client.channels.fetch(interaction.channelId)
|
||||||
if (!channel || !UserCommand.includes(channel.type)) return
|
if (!channel || !UserCommand.includes(channel.type)) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Phase 1: Switch to the model
|
|
||||||
let switchSuccess = false
|
let switchSuccess = false
|
||||||
await ollama.list()
|
await ollama.list().then(response => {
|
||||||
.then(response => {
|
for (const model of response.models) {
|
||||||
for (const model in response.models) {
|
if (model.name.startsWith(modelInput)) {
|
||||||
const currentModel: ModelResponse = response.models[model]
|
openConfig(`${interaction.user.id}-config.json`, 'switch-model', modelInput)
|
||||||
if (currentModel.name.startsWith(modelInput)) {
|
interaction.editReply({
|
||||||
openConfig(`${interaction.user.username}-config.json`, interaction.commandName, modelInput)
|
content: `Successfully switched to **${modelInput}** as the preferred model for ${interaction.user.username}.`
|
||||||
|
})
|
||||||
// successful switch
|
switchSuccess = true
|
||||||
interaction.editReply({
|
|
||||||
content: `Successfully switched to **${modelInput}** as the preferred model for ${interaction.user.username}.`
|
|
||||||
})
|
|
||||||
switchSuccess = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
// todo: problem can be here if async messes up
|
})
|
||||||
if (switchSuccess) {
|
|
||||||
// set model now that it exists
|
|
||||||
openConfig(`${interaction.user.username}-config.json`, interaction.commandName, modelInput)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 2: Notify user of failure to find model.
|
if (switchSuccess) return
|
||||||
interaction.editReply({
|
|
||||||
content: `Could not find **${modelInput}** in local model library.\n\nPlease contact an server admin for access to this model.`
|
await interaction.editReply({
|
||||||
|
content: `Could not find **${modelInput}** in local model library.\n\nPlease contact a server admin to pull this model using \`/pull-model ${modelInput}\`.`,
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// could not resolve user model switch
|
await interaction.editReply({
|
||||||
interaction.editReply({
|
content: `Unable to switch to **${modelInput}**.\n\n${error}\n\nTry asking an admin to run \`/pull-model ${modelInput}\`.`,
|
||||||
content: `Unable to switch user preferred model to **${modelInput}**.\n\n${error}\n\nPossible solution is to request an server admin run \`/pull-model ${modelInput}\` and try again.`
|
flags: MessageFlags.Ephemeral
|
||||||
})
|
})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ export const ThreadCreate: SlashCommand = {
|
|||||||
name: 'thread',
|
name: 'thread',
|
||||||
description: 'creates a thread and mentions user',
|
description: 'creates a thread and mentions user',
|
||||||
|
|
||||||
// Query for server information
|
|
||||||
run: async (client: Client, interaction: CommandInteraction) => {
|
run: async (client: Client, interaction: CommandInteraction) => {
|
||||||
// fetch the channel
|
|
||||||
const channel = await client.channels.fetch(interaction.channelId)
|
const channel = await client.channels.fetch(interaction.channelId)
|
||||||
if (!channel || !AdminCommand.includes(channel.type)) return
|
if (!channel || !AdminCommand.includes(channel.type)) return
|
||||||
|
|
||||||
@@ -17,16 +15,12 @@ export const ThreadCreate: SlashCommand = {
|
|||||||
type: ChannelType.PublicThread
|
type: ChannelType.PublicThread
|
||||||
})
|
})
|
||||||
|
|
||||||
// Send a message in the thread
|
await thread.send(`Hello ${interaction.user} and others! \n\nIt's nice to meet you. Please talk to me by typing **@${client.user?.username}** with your message.`);
|
||||||
thread.send(`Hello ${interaction.user} and others! \n\nIt's nice to meet you. Please talk to me by typing **@${client.user?.username}** with your message.`)
|
await openChannelInfo(thread.id, thread as ThreadChannel, interaction.user.id);
|
||||||
|
|
||||||
// handle storing this chat channel
|
await interaction.reply({
|
||||||
openChannelInfo(thread.id, thread as ThreadChannel, interaction.user.tag)
|
|
||||||
|
|
||||||
// user only reply
|
|
||||||
return interaction.reply({
|
|
||||||
content: `I can help you in <#${thread.id}> below.`,
|
content: `I can help you in <#${thread.id}> below.`,
|
||||||
flags: MessageFlags.Ephemeral
|
flags: MessageFlags.Ephemeral
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ export const PrivateThreadCreate: SlashCommand = {
|
|||||||
name: 'private-thread',
|
name: 'private-thread',
|
||||||
description: 'creates a private thread and mentions user',
|
description: 'creates a private thread and mentions user',
|
||||||
|
|
||||||
// Query for server information
|
|
||||||
run: async (client: Client, interaction: CommandInteraction) => {
|
run: async (client: Client, interaction: CommandInteraction) => {
|
||||||
// fetch the channel
|
|
||||||
const channel = await client.channels.fetch(interaction.channelId)
|
const channel = await client.channels.fetch(interaction.channelId)
|
||||||
if (!channel || !AdminCommand.includes(channel.type)) return
|
if (!channel || !AdminCommand.includes(channel.type)) return
|
||||||
|
|
||||||
@@ -17,17 +15,12 @@ export const PrivateThreadCreate: SlashCommand = {
|
|||||||
type: ChannelType.PrivateThread
|
type: ChannelType.PrivateThread
|
||||||
})
|
})
|
||||||
|
|
||||||
// Send a message in the thread
|
await thread.send(`Hello ${interaction.user}! \n\nIt's nice to meet you. Please talk to me by typing @${client.user?.username} with your prompt.`);
|
||||||
thread.send(`Hello ${interaction.user}! \n\nIt's nice to meet you. Please talk to me by typing @${client.user?.username} with your prompt.`)
|
await openChannelInfo(thread.id, thread as ThreadChannel, interaction.user.id);
|
||||||
|
|
||||||
// handle storing this chat channel
|
await interaction.reply({
|
||||||
// store: thread.id, thread.name
|
|
||||||
openChannelInfo(thread.id, thread as ThreadChannel, interaction.user.tag)
|
|
||||||
|
|
||||||
// user only reply
|
|
||||||
return interaction.reply({
|
|
||||||
content: `I can help you in <#${thread.id}>.`,
|
content: `I can help you in <#${thread.id}>.`,
|
||||||
flags: MessageFlags.Ephemeral
|
flags: MessageFlags.Ephemeral
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,52 +31,45 @@ interface ModelResponse {
|
|||||||
export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client, defaultModel }, message) => {
|
export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client, defaultModel }, message) => {
|
||||||
const clientId = client.user!.id
|
const clientId = client.user!.id
|
||||||
let cleanedMessage = clean(message.content, clientId)
|
let cleanedMessage = clean(message.content, clientId)
|
||||||
log('Message "${cleanedMessage}" from ${message.author.tag} in channel/thread ${message.channelId}.')
|
log(`[Event: messageCreate] Message "${cleanedMessage}" from ${message.author.tag} in channel/thread ${message.channelId}.`)
|
||||||
|
|
||||||
// Check if message mentions the bot or passes random chance (30%)
|
// Check if message mentions the bot or passes random chance (30%)
|
||||||
const isFromBot = message.author.bot && message.author.id !== clientId;
|
const isFromBot = message.author.bot && message.author.id !== clientId
|
||||||
const isMentioned = message.mentions.has(clientId)
|
const isMentioned = message.mentions.has(clientId)
|
||||||
const isCommand = message.content.startsWith('/')
|
const isCommand = message.content.startsWith('/')
|
||||||
|
|
||||||
if (isFromBot) {
|
if (isFromBot) {
|
||||||
// Check interaction key to prevent rapid back-and-forth
|
log(`[Event: messageCreate] Processing bot message from ${message.author.tag} (ID: ${message.author.id})`)
|
||||||
const otherBotId = message.author.id
|
const otherBotId = message.author.id
|
||||||
const interactionKey = 'bot_interaction:${clientId}:${otherBotId}'
|
const interactionKey = `bot_interaction:${clientId}:${otherBotId}`
|
||||||
const interactionExists = await redis.exists(interactionKey)
|
const interactionExists = await redis.exists(interactionKey)
|
||||||
if (interactionExists) {
|
if (interactionExists) {
|
||||||
log('Interaction cooldown active, not responding')
|
log(`[Event: messageCreate] Interaction cooldown active for ${interactionKey}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let respondProbability = isMentioned ? 0.9 : 0.2
|
let respondProbability = isMentioned ? 0.9 : 0.2
|
||||||
if (!isMentioned && Math.random() >= respondProbability) return
|
log(`[Event: messageCreate] Respond probability: ${respondProbability}`)
|
||||||
|
if (!isMentioned && Math.random() >= respondProbability) {
|
||||||
// Set shorter cooldown (e.g., 30s)
|
log(`[Event: messageCreate] Skipping response due to probability check`)
|
||||||
await redis.set(interactionKey, '1', { EX: 30 })
|
return
|
||||||
|
}
|
||||||
|
await redis.set(interactionKey, '1', { EX: 15 })
|
||||||
} else if (!message.author.bot) {
|
} else if (!message.author.bot) {
|
||||||
// Human message
|
|
||||||
const randomChance = Math.random() < 0.30
|
const randomChance = Math.random() < 0.30
|
||||||
if (!isMentioned && (isCommand || !randomChance)) return
|
if (!isMentioned && (isCommand || !randomChance)) return
|
||||||
} else {
|
} else {
|
||||||
// Message from self: ignore
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log response trigger
|
log(isMentioned ? '[Event: messageCreate] Responding to mention' : '[Event: messageCreate] Responding due to random chance')
|
||||||
log(isMentioned ? 'Responding to mention' : 'Responding due to random chance')
|
|
||||||
|
|
||||||
// Log response trigger
|
|
||||||
log(isFromBot ? 'Responding to bot message' : (isMentioned ? 'Responding to mention' : 'Responding due to random chance'))
|
|
||||||
|
|
||||||
// Load and process bot’s own history
|
|
||||||
const historyFile = '${message.channelId}-${isFromBot ? clientId : message.author.id}.json'
|
|
||||||
|
|
||||||
// Default stream to false
|
// Default stream to false
|
||||||
let shouldStream = false
|
let shouldStream = false
|
||||||
|
|
||||||
// Params for Preferences Fetching
|
// Params for Preferences Fetching
|
||||||
const maxRetries = 3
|
const maxRetries = 3
|
||||||
const delay = 1000 // in milliseconds
|
const delay = 1000
|
||||||
|
const defaultCapacity = 50
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Retrieve Server/Guild Preferences
|
// Retrieve Server/Guild Preferences
|
||||||
@@ -99,7 +92,7 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
++attempt
|
++attempt
|
||||||
if (attempt < maxRetries) {
|
if (attempt < maxRetries) {
|
||||||
log(`Attempt ${attempt} failed for Server Preferences. Retrying in ${delay}ms...`)
|
log(`[Event: messageCreate] Attempt ${attempt} failed for Server Preferences. Retrying in ${delay}ms...`)
|
||||||
await new Promise(ret => setTimeout(ret, delay))
|
await new Promise(ret => setTimeout(ret, delay))
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Could not retrieve Server Preferences, please try chatting again...`)
|
throw new Error(`Could not retrieve Server Preferences, please try chatting again...`)
|
||||||
@@ -115,33 +108,30 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
|
|||||||
userConfig = await new Promise((resolve, reject) => {
|
userConfig = await new Promise((resolve, reject) => {
|
||||||
getUserConfig(`${message.author.username}-config.json`, (config) => {
|
getUserConfig(`${message.author.username}-config.json`, (config) => {
|
||||||
if (config === undefined) {
|
if (config === undefined) {
|
||||||
openConfig(`${message.author.username}-config.json`, 'message-style', false)
|
openConfig(`${message.author.username}-config.json`, 'message-stream', false)
|
||||||
openConfig(`${message.author.username}-config.json`, 'switch-model', defaultModel)
|
openConfig(`${message.author.username}-config.json`, 'switch-model', defaultModel)
|
||||||
reject(new Error('No User Preferences is set up.\n\nCreating preferences file with `message-style` set as `false` for regular message style.\nPlease try chatting again.'))
|
reject(new Error('No User Preferences is set up.\n\nCreating preferences file with `message-stream` set as `false` for regular message style.\nPlease try chatting again.'))
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof config.options['modify-capacity'] === 'number') {
|
|
||||||
log(`New Capacity found. Setting Context Capacity to ${config.options['modify-capacity']}.`)
|
|
||||||
msgHist.capacity = config.options['modify-capacity']
|
|
||||||
} else {
|
} else {
|
||||||
log(`Capacity is undefined, using default capacity of ${msgHist.capacity}.`)
|
if (typeof config.options['modify-capacity'] === 'number') {
|
||||||
|
log(`[Event: messageCreate] New Capacity found. Setting Context Capacity to ${config.options['modify-capacity']}.`)
|
||||||
|
msgHist.capacity = config.options['modify-capacity']
|
||||||
|
} else {
|
||||||
|
log(`[Event: messageCreate] Capacity is undefined, using default capacity of ${defaultCapacity}.`)
|
||||||
|
msgHist.capacity = defaultCapacity
|
||||||
|
}
|
||||||
|
shouldStream = config.options['message-stream'] as boolean || false
|
||||||
|
if (typeof config.options['switch-model'] !== 'string') {
|
||||||
|
reject(new Error(`No Model was set. Please set a model by running \`/switch-model <model of choice>\`.\n\nIf you do not have any models. Run \`/pull-model <model name>\`.`))
|
||||||
|
}
|
||||||
|
resolve(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldStream = config.options['message-stream'] as boolean || false
|
|
||||||
|
|
||||||
if (typeof config.options['switch-model'] !== 'string') {
|
|
||||||
reject(new Error(`No Model was set. Please set a model by running \`/switch-model <model of choice>\`.\n\nIf you do not have any models. Run \`/pull-model <model name>\`.`))
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(config)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
++attempt
|
++attempt
|
||||||
if (attempt < maxRetries) {
|
if (attempt < maxRetries) {
|
||||||
log(`Attempt ${attempt} failed for User Preferences. Retrying in ${delay}ms...`)
|
log(`[Event: messageCreate] Attempt ${attempt} failed for User Preferences. Retrying in ${delay}ms...`)
|
||||||
await new Promise(ret => setTimeout(ret, delay))
|
await new Promise(ret => setTimeout(ret, delay))
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Could not retrieve User Preferences, please try chatting again...`)
|
throw new Error(`Could not retrieve User Preferences, please try chatting again...`)
|
||||||
@@ -150,29 +140,56 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve Channel Messages
|
// Retrieve Channel Messages
|
||||||
let chatMessages: UserMessage[] = await new Promise((resolve) => {
|
const safeAuthorId = isFromBot ? clientId : message.author.id
|
||||||
getChannelInfo(`${message.channelId}-${message.author.username}.json`, (channelInfo) => {
|
const fileName = `${message.channelId}-${safeAuthorId}.json`
|
||||||
if (channelInfo?.messages) {
|
let chatMessages: UserMessage[] = await new Promise((resolve, reject) => {
|
||||||
resolve(channelInfo.messages)
|
let attempts = 0
|
||||||
} else {
|
const maxAttempts = 3
|
||||||
log(`Channel/Thread ${message.channelId}-${message.author.username} does not exist. File will be created shortly...`)
|
const retryDelay = 500
|
||||||
resolve([])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if (chatMessages.length === 0) {
|
const tryGetChannelInfo = async () => {
|
||||||
chatMessages = await new Promise((resolve, reject) => {
|
log(`[Event: messageCreate] Attempt ${attempts + 1} to read ${fileName}`)
|
||||||
openChannelInfo(message.channelId, message.channel as TextChannel, isFromBot ? clientId : message.author.tag)
|
getChannelInfo(fileName, async (config) => {
|
||||||
getChannelInfo(`${message.channelId}-${isFromBot ? clientId : message.author.id}.json`, (config) => {
|
|
||||||
if (config?.messages) {
|
if (config?.messages) {
|
||||||
|
log(`[Event: messageCreate] Successfully loaded history from ${fileName}`)
|
||||||
resolve(config.messages)
|
resolve(config.messages)
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(`Failed to find history for ${isFromBot ? clientId : message.author.tag}. Try chatting again.`))
|
log(`[Event: messageCreate] Channel/Thread ${fileName} does not exist. Creating...`)
|
||||||
|
try {
|
||||||
|
await openChannelInfo(message.channelId, message.channel as TextChannel, safeAuthorId, [])
|
||||||
|
try {
|
||||||
|
const filePath = path.join('/app/data', fileName)
|
||||||
|
await fs.access(filePath, fs.constants.R_OK | fs.constants.W_OK)
|
||||||
|
log(`[Event: messageCreate] Confirmed ${fileName} created`)
|
||||||
|
const data = await fs.readFile(filePath, 'utf-8')
|
||||||
|
const config = JSON.parse(data)
|
||||||
|
if (config?.messages) {
|
||||||
|
resolve(config.messages)
|
||||||
|
} else {
|
||||||
|
log(`[Event: messageCreate] Created ${fileName} but contains invalid data: ${data}`)
|
||||||
|
if (++attempts < maxAttempts) {
|
||||||
|
setTimeout(tryGetChannelInfo, retryDelay)
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Failed to find or create valid history for ${safeAuthorId} after ${maxAttempts} attempts. Try chatting again.`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (accessError) {
|
||||||
|
log(`[Event: messageCreate] File ${fileName} not found or inaccessible after creation: ${accessError}`)
|
||||||
|
if (++attempts < maxAttempts) {
|
||||||
|
setTimeout(tryGetChannelInfo, retryDelay)
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Failed to verify ${fileName} after creation. Try chatting again.`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log(`[Event: messageCreate] Failed to create ${fileName}: ${error}`)
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
}
|
tryGetChannelInfo()
|
||||||
|
})
|
||||||
|
|
||||||
if (!userConfig) {
|
if (!userConfig) {
|
||||||
throw new Error(`Failed to initialize User Preference for **${message.author.username}**.\n\nIt's likely you do not have a model set. Please use the \`switch-model\` command to do that.`)
|
throw new Error(`Failed to initialize User Preference for **${message.author.username}**.\n\nIt's likely you do not have a model set. Please use the \`switch-model\` command to do that.`)
|
||||||
@@ -194,18 +211,24 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
|
|||||||
try {
|
try {
|
||||||
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, '../../src/personality.json')
|
let personalityFile = 'personality.json'
|
||||||
|
if (client.user!.username.includes('kuroki-tomoko')) {
|
||||||
|
personalityFile = 'personality-kuroki-tomoko.json'
|
||||||
|
} else if (client.user!.username.includes('nagatoro-hayase')) {
|
||||||
|
personalityFile = 'personality-nagatoro-hayase.json'
|
||||||
|
}
|
||||||
|
const personalityPath = path.join(__dirname, '../../src', personalityFile)
|
||||||
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.'
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(`Failed to load personality.json: ${error}`)
|
log(`[Event: messageCreate] Failed to load personality.json: ${error}`)
|
||||||
personality = 'You are a friendly and helpful AI assistant.'
|
personality = 'You are a friendly and helpful AI assistant.'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user and bot sentiment from Redis
|
// Get user and bot sentiment from Redis
|
||||||
const userSentimentKey = 'bot:${clientId}:user:${message.author.id}:sentiment'
|
const userSentimentKey = `bot:${clientId}:user:${message.author.id}:sentiment`
|
||||||
const botSentimentKey = 'bot:${clientId}:self_sentiment'
|
const botSentimentKey = `bot:${clientId}:self_sentiment`
|
||||||
let userSentiment: number
|
let userSentiment: number
|
||||||
let botSentiment: number
|
let botSentiment: number
|
||||||
|
|
||||||
@@ -213,15 +236,13 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
|
|||||||
const userSentimentRaw = await redis.get(userSentimentKey)
|
const userSentimentRaw = await redis.get(userSentimentKey)
|
||||||
userSentiment = parseFloat(userSentimentRaw || '0.50')
|
userSentiment = parseFloat(userSentimentRaw || '0.50')
|
||||||
if (isNaN(userSentiment) || userSentiment < 0 || userSentiment > 1) {
|
if (isNaN(userSentiment) || userSentiment < 0 || userSentiment > 1) {
|
||||||
log(`Invalid user sentiment for ${userSentimentKey}: ${userSentimentRaw}. Attempting to retrieve last valid value.`)
|
log(`[Event: messageCreate] Invalid user sentiment for ${userSentimentKey}: ${userSentimentRaw}. Using default.`)
|
||||||
userSentiment = parseFloat(await redis.get(userSentimentKey) || '0.50')
|
userSentiment = 0.50
|
||||||
if (isNaN(userSentiment)) userSentiment = 0.50
|
|
||||||
await redis.set(userSentimentKey, userSentiment.toFixed(2))
|
await redis.set(userSentimentKey, userSentiment.toFixed(2))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(`Failed to get user sentiment from Redis: ${error}`)
|
log(`[Event: messageCreate] Failed to get user sentiment from Redis: ${error}`)
|
||||||
userSentiment = parseFloat(await redis.get(userSentimentKey) || '0.50')
|
userSentiment = 0.50
|
||||||
if (isNaN(userSentiment)) userSentiment = 0.50
|
|
||||||
await redis.set(userSentimentKey, userSentiment.toFixed(2))
|
await redis.set(userSentimentKey, userSentiment.toFixed(2))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,26 +250,21 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
|
|||||||
const botSentimentRaw = await redis.get(botSentimentKey)
|
const botSentimentRaw = await redis.get(botSentimentKey)
|
||||||
botSentiment = parseFloat(botSentimentRaw || '0.50')
|
botSentiment = parseFloat(botSentimentRaw || '0.50')
|
||||||
if (isNaN(botSentiment) || botSentiment < 0 || botSentiment > 1) {
|
if (isNaN(botSentiment) || botSentiment < 0 || botSentiment > 1) {
|
||||||
log(`Invalid bot sentiment for ${botSentimentKey}: ${botSentimentRaw}. Attempting to retrieve last valid value.`)
|
log(`[Event: messageCreate] Invalid bot sentiment for ${botSentimentKey}: ${botSentimentRaw}. Using default.`)
|
||||||
botSentiment = parseFloat(await redis.get(botSentimentKey) || '0.50')
|
botSentiment = 0.50
|
||||||
if (isNaN(botSentiment)) botSentiment = 0.50
|
|
||||||
await redis.set(botSentimentKey, botSentiment.toFixed(2))
|
await redis.set(botSentimentKey, botSentiment.toFixed(2))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(`Failed to get bot sentiment from Redis: ${error}`)
|
log(`[Event: messageCreate] Failed to get bot sentiment from Redis: ${error}`)
|
||||||
botSentiment = parseFloat(await redis.get(botSentimentKey) || '0.50')
|
botSentiment = 0.50
|
||||||
if (isNaN(botSentiment)) botSentiment = 0.50
|
|
||||||
await redis.set(botSentimentKey, botSentiment.toFixed(2))
|
await redis.set(botSentimentKey, botSentiment.toFixed(2))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct sentiment data with bot ID
|
// Construct sentiment data with bot ID
|
||||||
const sentimentData = `User ${message.author.id} sentiment: ${userSentiment.toFixed(2)}, Bot ${clientId} sentiment: ${botSentiment.toFixed(2)}`
|
const sentimentData = `User ${message.author.id} sentiment: ${userSentiment.toFixed(2)}, Bot ${clientId} sentiment: ${botSentiment.toFixed(2)}`
|
||||||
|
|
||||||
// Log initial sentiments with two decimals
|
// Log initial sentiments
|
||||||
log(`Initial sentiments - User ${message.author.id}: ${userSentiment.toFixed(2)}, Bot: ${botSentiment.toFixed(2)}`)
|
log(`[Event: messageCreate] Retrieved sentiments - User ${message.author.id}: ${userSentiment.toFixed(2)}, Bot: ${botSentiment.toFixed(2)}`)
|
||||||
|
|
||||||
// Construct sentiment data for prompt
|
|
||||||
//const sentimentData = `User ${message.author.id} sentiment: ${userSentiment.toFixed(2)}, Bot sentiment: ${botSentiment.toFixed(2)}`
|
|
||||||
|
|
||||||
// Construct prompt with [CHARACTER] and [SENTIMENT]
|
// Construct prompt with [CHARACTER] and [SENTIMENT]
|
||||||
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}`
|
||||||
@@ -268,24 +284,30 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
|
|||||||
const response = await ollama.chat({
|
const response = await ollama.chat({
|
||||||
model,
|
model,
|
||||||
messages: [{ role: 'user', content: prompt }],
|
messages: [{ role: 'user', content: prompt }],
|
||||||
stream: shouldStream
|
stream: shouldStream,
|
||||||
|
options: { temperature: 0.5 }
|
||||||
})
|
})
|
||||||
|
|
||||||
// Parse JSON response
|
// Parse JSON response
|
||||||
let jsonResponse: ModelResponse
|
let jsonResponse: ModelResponse
|
||||||
|
let content: string = ''
|
||||||
try {
|
try {
|
||||||
// Log raw response for debugging
|
log(`[Event: messageCreate] Raw model response: ${response.message.content}`)
|
||||||
log(`Raw model response: ${response.message.content}`)
|
content = response.message.content
|
||||||
// Strip Markdown code fences if present
|
|
||||||
let content = response.message.content
|
|
||||||
content = content.replace(/```(?:json)?\n?/g, '').trim()
|
content = content.replace(/```(?:json)?\n?/g, '').trim()
|
||||||
|
if (!content.startsWith('{') || !content.endsWith('}')) {
|
||||||
|
log(`[Event: messageCreate] Invalid JSON format detected: ${content}`)
|
||||||
|
message.reply('Sorry, I’m having trouble thinking right now. Try again?')
|
||||||
|
msgHist.pop()
|
||||||
|
return
|
||||||
|
}
|
||||||
jsonResponse = JSON.parse(content)
|
jsonResponse = JSON.parse(content)
|
||||||
if (!jsonResponse.status || !jsonResponse.reply) {
|
if (!jsonResponse.status || !jsonResponse.reply) {
|
||||||
throw new Error('Missing status or reply in model response')
|
throw new Error('Missing status or reply in model response')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log('Failed to parse model response: ${error}')
|
log(`[Event: messageCreate] Failed to parse model response: ${error}\nRaw content: ${content}`)
|
||||||
message.reply('Sorry, I had a brain malfunction!')
|
message.reply('Sorry, I’m having trouble thinking right now. Try again?')
|
||||||
msgHist.pop()
|
msgHist.pop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -298,33 +320,33 @@ 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) {
|
||||||
log(`Model redis_ops: ${JSON.stringify(jsonResponse.metadata.redis_ops)}`)
|
log(`[Event: messageCreate] Model redis_ops output: ${JSON.stringify(jsonResponse.metadata.redis_ops)}`)
|
||||||
for (const op of jsonResponse.metadata.redis_ops) {
|
for (const op of jsonResponse.metadata.redis_ops) {
|
||||||
try {
|
try {
|
||||||
const key = op.key.replace('<bot_id>', clientId)
|
const key = op.key.replace('<bot_id>', clientId)
|
||||||
if (op.action === 'set' && op.value !== undefined) {
|
if (op.action === 'set' && op.value !== undefined) {
|
||||||
const value = parseFloat(op.value.toString())
|
const value = parseFloat(op.value.toString())
|
||||||
if (isNaN(value) || value < 0 || value > 1) {
|
if (isNaN(value) || value < 0 || value > 1) {
|
||||||
log(`Invalid sentiment value for ${key}: ${op.value}. Skipping.`)
|
log(`[Event: messageCreate] Invalid sentiment value for ${key}: ${op.value}. Skipping.`)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
await redis.set(key, value.toFixed(2))
|
await redis.set(key, value.toFixed(2))
|
||||||
log(`Set ${key} to ${value.toFixed(2)}`)
|
log(`[Event: messageCreate] Set ${key} to ${value.toFixed(2)}`)
|
||||||
} else if (op.action === 'get' && op.key) {
|
} else if (op.action === 'get' && op.key) {
|
||||||
const value = await redis.get(key)
|
const value = await redis.get(key)
|
||||||
log(`Got ${key}: ${value}`)
|
log(`[Event: messageCreate] Got ${key}: ${value}`)
|
||||||
} else {
|
} else {
|
||||||
log(`Invalid redis_op: ${JSON.stringify(op)}. Skipping.`)
|
log(`[Event: messageCreate] Invalid redis_op: ${JSON.stringify(op)}. Skipping.`)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(`Redis operation failed for ${op.key}: ${error}`)
|
log(`[Event: messageCreate] Redis operation failed for ${op.key}: ${error}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log updated sentiments with two decimals
|
// Log updated sentiments
|
||||||
if (jsonResponse.metadata) {
|
if (jsonResponse.metadata) {
|
||||||
log(`Updated sentiments - Self: ${(jsonResponse.metadata.self_sentiment || 0).toFixed(2)}, User ${message.author.id}: ${(jsonResponse.metadata.user_sentiment[message.author.id] || 0).toFixed(2)}`)
|
log(`[Event: messageCreate] Updated sentiments - Self: ${(jsonResponse.metadata.self_sentiment || 0).toFixed(2)}, User ${message.author.id}: ${(jsonResponse.metadata.user_sentiment[message.author.id] || 0).toFixed(2)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send reply to Discord
|
// Send reply to Discord
|
||||||
@@ -340,9 +362,9 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Save updated history
|
// Save updated history
|
||||||
openChannelInfo(message.channelId, message.channel as TextChannel, message.author.tag, msgHist.getItems())
|
await openChannelInfo(message.channelId, message.channel as TextChannel, safeAuthorId, msgHist.getItems())
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
msgHist.pop()
|
msgHist.pop()
|
||||||
message.reply(`**Error Occurred:**\n\n**Reason:** *${error.message}*`)
|
message.reply(`**Error Occurred:**\n\n**Reason:** *${error.message}*\n\nPlease try again or contact the server admin if this persists.`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,67 +1,119 @@
|
|||||||
import { ChannelType } from 'discord.js'
|
import { TextChannel, ThreadChannel } from 'discord.js'
|
||||||
import { UserMessage } from './index.js'
|
import { Channel, UserMessage } from '../index.js'
|
||||||
|
import fs from 'fs/promises'
|
||||||
export interface UserConfiguration {
|
import path from 'path'
|
||||||
'message-stream'?: boolean,
|
|
||||||
'modify-capacity': number,
|
|
||||||
'switch-model': string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ServerConfiguration {
|
|
||||||
'toggle-chat'?: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parent Configuration interface
|
* Method to check if a thread history file exists
|
||||||
*
|
*
|
||||||
* @see ServerConfiguration server settings per guild
|
* @param channel parent thread of the requested thread (can be GuildText)
|
||||||
* @see UserConfiguration user configurations (only for the user for any server)
|
* @returns true if channel exists, false otherwise
|
||||||
*/
|
*/
|
||||||
export interface Configuration {
|
async function checkChannelInfoExists(channel: TextChannel, user: string) {
|
||||||
readonly name: string
|
const doesExists: boolean = await new Promise((resolve) => {
|
||||||
options: UserConfiguration | ServerConfiguration
|
getChannelInfo(`${channel.id}-${user}.json`, (channelInfo) => {
|
||||||
|
if (channelInfo?.messages) {
|
||||||
|
resolve(true)
|
||||||
|
} else {
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return doesExists
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User config to use outside of this file
|
* Method to clear channel history for requesting user
|
||||||
|
*
|
||||||
|
* @param filename guild id string
|
||||||
|
* @param channel the TextChannel in the Guild
|
||||||
|
* @param user username or ID of user
|
||||||
|
* @returns true if history was cleared, false if already empty or not found
|
||||||
*/
|
*/
|
||||||
export interface UserConfig {
|
export async function clearChannelInfo(filename: string, channel: TextChannel, user: string): Promise<boolean> {
|
||||||
readonly name: string
|
const channelInfoExists: boolean = await checkChannelInfoExists(channel, user)
|
||||||
options: UserConfiguration
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ServerConfig {
|
// If thread does not exist, file can't be found
|
||||||
readonly name: string
|
if (!channelInfoExists) return false
|
||||||
options: ServerConfiguration
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Channel {
|
// Attempt to clear user channel history
|
||||||
readonly id: string
|
const fullFileName = path.join('/app/data', `${filename}-${user}.json`)
|
||||||
readonly name: string
|
try {
|
||||||
readonly user: string
|
const data = await fs.readFile(fullFileName, 'utf8')
|
||||||
messages: UserMessage[]
|
const object = JSON.parse(data)
|
||||||
|
if (object['messages'].length === 0) {
|
||||||
|
console.log(`[Util: clearChannelInfo] History already empty for ${fullFileName}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
object['messages'] = []
|
||||||
|
await fs.writeFile(fullFileName, JSON.stringify(object, null, 2), { flag: 'w', mode: 0o600 })
|
||||||
|
console.log(`[Util: clearChannelInfo] Cleared history for ${fullFileName}`)
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`[Util: clearChannelInfo] Failed to clear ${fullFileName}: ${error}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The following 2 types is allow for better readability in commands
|
* Method to open the channel history
|
||||||
* Admin Command -> Don't run in Threads
|
*
|
||||||
* User Command -> Used anywhere
|
* @param filename name of the json file for the channel by user (without path)
|
||||||
|
* @param channel the text channel info
|
||||||
|
* @param user the user's ID or clientId for bots
|
||||||
|
* @param messages their messages
|
||||||
*/
|
*/
|
||||||
export const AdminCommand = [
|
export async function openChannelInfo(filename: string, channel: TextChannel | ThreadChannel, user: string, messages: UserMessage[] = []): Promise<void> {
|
||||||
ChannelType.GuildText
|
const fullFileName = path.join('/app/data', `${filename}-${user}.json`)
|
||||||
]
|
console.log(`[Util: openChannelInfo] Attempting to create/open ${fullFileName}`)
|
||||||
|
try {
|
||||||
export const UserCommand = [
|
if (await fs.access(fullFileName).then(() => true).catch(() => false)) {
|
||||||
ChannelType.GuildText,
|
const data = await fs.readFile(fullFileName, 'utf8')
|
||||||
ChannelType.PublicThread,
|
const object = JSON.parse(data)
|
||||||
ChannelType.PrivateThread
|
if (messages.length > 0) {
|
||||||
]
|
object['messages'] = messages
|
||||||
|
}
|
||||||
|
await fs.writeFile(fullFileName, JSON.stringify(object, null, 2), { flag: 'w', mode: 0o600 })
|
||||||
|
console.log(`[Util: openChannelInfo] Updated ${fullFileName}`)
|
||||||
|
} else {
|
||||||
|
const object: Channel = {
|
||||||
|
id: channel?.id || filename,
|
||||||
|
name: channel?.name || 'unknown',
|
||||||
|
user,
|
||||||
|
messages: messages
|
||||||
|
}
|
||||||
|
const directory = path.dirname(fullFileName)
|
||||||
|
await fs.mkdir(directory, { recursive: true })
|
||||||
|
await fs.writeFile(fullFileName, JSON.stringify(object, null, 2), { flag: 'w', mode: 0o600 })
|
||||||
|
console.log(`[Util: openChannelInfo] Created '${fullFileName}' in working directory`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`[Util: openChannelInfo] Failed to write ${fullFileName}: ${error}`)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the configuration we are editing/taking from is a Server Config
|
* Method to get the channel information/history
|
||||||
* @param key name of command we ran
|
*
|
||||||
* @returns true if command is from Server Config, false otherwise
|
* @param filename name of the json file for the channel by user (without path)
|
||||||
|
* @param callback function to handle resolving message history
|
||||||
*/
|
*/
|
||||||
export function isServerConfigurationKey(key: string): key is keyof ServerConfiguration {
|
export async function getChannelInfo(filename: string, callback: (config: Channel | undefined) => void): Promise<void> {
|
||||||
return ['toggle-chat'].includes(key);
|
const fullFileName = path.join('/app/data', filename)
|
||||||
}
|
console.log(`[Util: getChannelInfo] Reading ${fullFileName}`)
|
||||||
|
try {
|
||||||
|
const data = await fs.readFile(fullFileName, 'utf8')
|
||||||
|
const config = JSON.parse(data)
|
||||||
|
if (!config || !Array.isArray(config.messages)) {
|
||||||
|
console.log(`[Util: getChannelInfo] Invalid or empty config in ${fullFileName}: ${JSON.stringify(config)}`)
|
||||||
|
callback(undefined)
|
||||||
|
} else {
|
||||||
|
console.log(`[Util: getChannelInfo] Successfully read ${fullFileName}`)
|
||||||
|
callback(config)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`[Util: getChannelInfo] Failed to read ${fullFileName}: ${error}`)
|
||||||
|
callback(undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
import { TextChannel, ThreadChannel } from 'discord.js'
|
import { TextChannel, ThreadChannel } from 'discord.js'
|
||||||
import { Configuration, Channel, UserMessage } from '../index.js'
|
import { Channel, UserMessage } from '../index.js'
|
||||||
import fs from 'fs'
|
import fs from 'fs/promises'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to check if a thread history file exists
|
* Method to check if a thread history file exists
|
||||||
*
|
*
|
||||||
* @param channel parent thread of the requested thread (can be GuildText)
|
* @param channel parent thread of the requested thread (can be GuildText)
|
||||||
* @returns true if channel does not exist, false otherwise
|
* @returns true if channel exists, false otherwise
|
||||||
*/
|
*/
|
||||||
async function checkChannelInfoExists(channel: TextChannel, user: string) {
|
async function checkChannelInfoExists(channel: TextChannel, user: string) {
|
||||||
const doesExists: boolean = await new Promise((resolve) => {
|
const doesExists: boolean = await new Promise((resolve) => {
|
||||||
getChannelInfo(`${channel.id}-${user}.json`, (channelInfo) => {
|
getChannelInfo(`${channel.id}-${user}.json`, (channelInfo) => {
|
||||||
if (channelInfo?.messages)
|
if (channelInfo?.messages) {
|
||||||
resolve(true)
|
resolve(true)
|
||||||
else
|
} else {
|
||||||
resolve(false)
|
resolve(false)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return doesExists
|
return doesExists
|
||||||
@@ -26,8 +27,8 @@ async function checkChannelInfoExists(channel: TextChannel, user: string) {
|
|||||||
*
|
*
|
||||||
* @param filename guild id string
|
* @param filename guild id string
|
||||||
* @param channel the TextChannel in the Guild
|
* @param channel the TextChannel in the Guild
|
||||||
* @param user username of user
|
* @param user username or ID of user
|
||||||
* @returns nothing
|
* @returns true if history was cleared, false if already empty or not found
|
||||||
*/
|
*/
|
||||||
export async function clearChannelInfo(filename: string, channel: TextChannel, user: string): Promise<boolean> {
|
export async function clearChannelInfo(filename: string, channel: TextChannel, user: string): Promise<boolean> {
|
||||||
const channelInfoExists: boolean = await checkChannelInfoExists(channel, user)
|
const channelInfoExists: boolean = await checkChannelInfoExists(channel, user)
|
||||||
@@ -36,86 +37,83 @@ export async function clearChannelInfo(filename: string, channel: TextChannel, u
|
|||||||
if (!channelInfoExists) return false
|
if (!channelInfoExists) return false
|
||||||
|
|
||||||
// Attempt to clear user channel history
|
// Attempt to clear user channel history
|
||||||
const fullFileName = `data/${filename}-${user}.json`
|
const fullFileName = path.join('/app/data', `${filename}-${user}.json`)
|
||||||
const cleanedHistory: boolean = await new Promise((resolve) => {
|
try {
|
||||||
fs.readFile(fullFileName, 'utf8', (error, data) => {
|
const data = await fs.readFile(fullFileName, 'utf8')
|
||||||
if (error)
|
const object = JSON.parse(data)
|
||||||
console.log(`[Error: openChannelInfo] Incorrect file format`)
|
if (object['messages'].length === 0) {
|
||||||
else {
|
console.log(`[Util: clearChannelInfo] History already empty for ${fullFileName}`)
|
||||||
const object = JSON.parse(data)
|
return false
|
||||||
if (object['messages'].length === 0) // already empty, let user know
|
}
|
||||||
resolve(false)
|
object['messages'] = []
|
||||||
else {
|
await fs.writeFile(fullFileName, JSON.stringify(object, null, 2), { flag: 'w', mode: 0o600 })
|
||||||
object['messages'] = [] // cleared history
|
console.log(`[Util: clearChannelInfo] Cleared history for ${fullFileName}`)
|
||||||
fs.writeFileSync(fullFileName, JSON.stringify(object, null, 2))
|
return true
|
||||||
resolve(true)
|
} catch (error) {
|
||||||
}
|
console.log(`[Util: clearChannelInfo] Failed to clear ${fullFileName}: ${error}`)
|
||||||
}
|
return false
|
||||||
})
|
}
|
||||||
})
|
|
||||||
return cleanedHistory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to open the channel history
|
* Method to open the channel history
|
||||||
*
|
*
|
||||||
* @param filename name of the json file for the channel by user
|
* @param filename name of the json file for the channel by user (without path)
|
||||||
* @param channel the text channel info
|
* @param channel the text channel info
|
||||||
* @param user the user's name
|
* @param user the user's ID or clientId for bots
|
||||||
* @param messages their messages
|
* @param messages their messages
|
||||||
*/
|
*/
|
||||||
export async function openChannelInfo(filename: string, channel: TextChannel | ThreadChannel, user: string, messages: UserMessage[] = []): Promise<void> {
|
export async function openChannelInfo(filename: string, channel: TextChannel | ThreadChannel, user: string, messages: UserMessage[] = []): Promise<void> {
|
||||||
const fullFileName = `data/${filename}-${user}.json`
|
const fullFileName = path.join('/app/data', `${filename}-${user}.json`)
|
||||||
if (fs.existsSync(fullFileName)) {
|
console.log(`[Util: openChannelInfo] Attempting to create/open ${fullFileName}`)
|
||||||
fs.readFile(fullFileName, 'utf8', (error, data) => {
|
try {
|
||||||
if (error)
|
if (await fs.access(fullFileName).then(() => true).catch(() => false)) {
|
||||||
console.log(`[Error: openChannelInfo] Incorrect file format`)
|
const data = await fs.readFile(fullFileName, 'utf8')
|
||||||
else {
|
const object = JSON.parse(data)
|
||||||
const object = JSON.parse(data)
|
if (messages.length > 0) {
|
||||||
if (object['messages'].length === 0)
|
object['messages'] = messages
|
||||||
object['messages'] = messages as []
|
|
||||||
else if (object['messages'].length !== 0 && messages.length !== 0)
|
|
||||||
object['messages'] = messages as []
|
|
||||||
fs.writeFileSync(fullFileName, JSON.stringify(object, null, 2))
|
|
||||||
}
|
}
|
||||||
})
|
await fs.writeFile(fullFileName, JSON.stringify(object, null, 2), { flag: 'w', mode: 0o600 })
|
||||||
} else { // file doesn't exist, create it
|
console.log(`[Util: openChannelInfo] Updated ${fullFileName}`)
|
||||||
const object: Configuration = JSON.parse(
|
} else {
|
||||||
`{
|
const object: Channel = {
|
||||||
\"id\": \"${channel?.id}\",
|
id: channel?.id || filename,
|
||||||
\"name\": \"${channel?.name}\",
|
name: channel?.name || 'unknown',
|
||||||
\"user\": \"${user}\",
|
user,
|
||||||
\"messages\": []
|
messages: messages
|
||||||
}`
|
}
|
||||||
)
|
const directory = path.dirname(fullFileName)
|
||||||
|
await fs.mkdir(directory, { recursive: true })
|
||||||
const directory = path.dirname(fullFileName)
|
await fs.writeFile(fullFileName, JSON.stringify(object, null, 2), { flag: 'w', mode: 0o600 })
|
||||||
if (!fs.existsSync(directory))
|
console.log(`[Util: openChannelInfo] Created '${fullFileName}' in working directory`)
|
||||||
fs.mkdirSync(directory, { recursive: true })
|
}
|
||||||
|
} catch (error) {
|
||||||
// only creating it, no need to add anything
|
console.log(`[Util: openChannelInfo] Failed to write ${fullFileName}: ${error}`)
|
||||||
fs.writeFileSync(fullFileName, JSON.stringify(object, null, 2))
|
throw error
|
||||||
console.log(`[Util: openChannelInfo] Created '${fullFileName}' in working directory`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to get the channel information/history
|
* Method to get the channel information/history
|
||||||
*
|
*
|
||||||
* @param filename name of the json file for the channel by user
|
* @param filename name of the json file for the channel by user (without path)
|
||||||
* @param callback function to handle resolving message history
|
* @param callback function to handle resolving message history
|
||||||
*/
|
*/
|
||||||
export async function getChannelInfo(filename: string, callback: (config: Channel | undefined) => void): Promise<void> {
|
export async function getChannelInfo(filename: string, callback: (config: Channel | undefined) => void): Promise<void> {
|
||||||
const fullFileName = `data/${filename}`
|
const fullFileName = path.join('/app/data', filename)
|
||||||
if (fs.existsSync(fullFileName)) {
|
console.log(`[Util: getChannelInfo] Reading ${fullFileName}`)
|
||||||
fs.readFile(fullFileName, 'utf8', (error, data) => {
|
try {
|
||||||
if (error) {
|
const data = await fs.readFile(fullFileName, 'utf8')
|
||||||
callback(undefined)
|
const config = JSON.parse(data)
|
||||||
return // something went wrong... stop
|
if (!config || !Array.isArray(config.messages)) {
|
||||||
}
|
console.log(`[Util: getChannelInfo] Invalid or empty config in ${fullFileName}: ${JSON.stringify(config)}`)
|
||||||
callback(JSON.parse(data))
|
callback(undefined)
|
||||||
})
|
} else {
|
||||||
} else {
|
console.log(`[Util: getChannelInfo] Successfully read ${fullFileName}`)
|
||||||
callback(undefined) // file not found
|
callback(config)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`[Util: getChannelInfo] Failed to read ${fullFileName}: ${error}`)
|
||||||
|
callback(undefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,92 +1,62 @@
|
|||||||
import { Configuration, ServerConfig, UserConfig, isServerConfigurationKey } from '../index.js'
|
import fs from 'fs/promises'
|
||||||
import fs from 'fs'
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { ServerConfig, UserConfig } from '../index.js'
|
||||||
|
|
||||||
/**
|
export async function openConfig(fileName: string, option: string, value: any) {
|
||||||
* Method to open a file in the working directory and modify/create it
|
const filePath = path.join('/app/data', fileName)
|
||||||
*
|
console.log(`[Util: openConfig] Creating/opening ${filePath}`)
|
||||||
* @param filename name of the file
|
try {
|
||||||
* @param key key value to access
|
let config = { name: fileName.replace('-config.json', ''), options: {} }
|
||||||
* @param value new value to assign
|
try {
|
||||||
*/
|
const data = await fs.readFile(filePath, 'utf-8')
|
||||||
// add type of change (server, user)
|
config = JSON.parse(data)
|
||||||
export function openConfig(filename: string, key: string, value: any) {
|
if (!config.options) config.options = {}
|
||||||
const fullFileName = `data/${filename}`
|
} catch (error) {
|
||||||
|
console.log(`[Util: openConfig] No existing config at ${filePath}, creating new`)
|
||||||
// check if the file exists, if not then make the config file
|
|
||||||
if (fs.existsSync(fullFileName)) {
|
|
||||||
fs.readFile(fullFileName, 'utf8', (error, data) => {
|
|
||||||
if (error)
|
|
||||||
console.log(`[Error: openConfig] Incorrect file format`)
|
|
||||||
else {
|
|
||||||
const object = JSON.parse(data)
|
|
||||||
object['options'][key] = value
|
|
||||||
fs.writeFileSync(fullFileName, JSON.stringify(object, null, 2))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else { // work on dynamic file creation
|
|
||||||
let object: Configuration
|
|
||||||
if (isServerConfigurationKey(key))
|
|
||||||
object = JSON.parse('{ \"name\": \"Server Confirgurations\" }')
|
|
||||||
else
|
|
||||||
object = JSON.parse('{ \"name\": \"User Confirgurations\" }')
|
|
||||||
|
|
||||||
// set standard information for config file and options
|
|
||||||
object['options'] = {
|
|
||||||
[key]: value
|
|
||||||
}
|
}
|
||||||
|
config.options[option] = value
|
||||||
const directory = path.dirname(fullFileName)
|
await fs.writeFile(filePath, JSON.stringify(config, null, 2), { flag: 'w', mode: 0o600 })
|
||||||
if (!fs.existsSync(directory))
|
console.log(`[Util: openConfig] Successfully wrote ${filePath} with ${option}=${value}`)
|
||||||
fs.mkdirSync(directory, { recursive: true })
|
} catch (error) {
|
||||||
|
console.log(`[Util: openConfig] Failed to write ${filePath}: ${error}`)
|
||||||
fs.writeFileSync(`data/${filename}`, JSON.stringify(object, null, 2))
|
throw error
|
||||||
console.log(`[Util: openConfig] Created '${filename}' in working directory`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export async function getServerConfig(fileName: string, callback: (config: ServerConfig | undefined) => void) {
|
||||||
* Method to obtain the configurations of the message chat/thread
|
const filePath = path.join('/app/data', fileName)
|
||||||
*
|
console.log(`[Util: getServerConfig] Reading ${filePath}`)
|
||||||
* @param filename name of the configuration file to get
|
try {
|
||||||
* @param callback function to allow a promise from getting the config
|
const data = await fs.readFile(filePath, 'utf-8')
|
||||||
*/
|
const config = JSON.parse(data)
|
||||||
export async function getServerConfig(filename: string, callback: (config: ServerConfig | undefined) => void): Promise<void> {
|
if (!config || !config.options || !config.name) {
|
||||||
const fullFileName = `data/${filename}`
|
console.log(`[Util: getServerConfig] Invalid or empty config in ${filePath}: ${JSON.stringify(config)}`)
|
||||||
|
callback({ name: fileName.replace('-config.json', ''), options: { 'toggle-chat': true } })
|
||||||
// attempt to read the file and get the configuration
|
} else {
|
||||||
if (fs.existsSync(fullFileName)) {
|
console.log(`[Util: getServerConfig] Successfully read ${filePath}`)
|
||||||
fs.readFile(fullFileName, 'utf8', (error, data) => {
|
callback(config)
|
||||||
if (error) {
|
}
|
||||||
callback(undefined)
|
} catch (error) {
|
||||||
return // something went wrong... stop
|
console.log(`[Util: getServerConfig] Failed to read ${filePath}: ${error}`)
|
||||||
}
|
callback({ name: fileName.replace('-config.json', ''), options: { 'toggle-chat': true } })
|
||||||
callback(JSON.parse(data))
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
callback(undefined) // file not found
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export async function getUserConfig(fileName: string, callback: (config: UserConfig | undefined) => void) {
|
||||||
* Method to obtain the configurations of the message chat/thread
|
const filePath = path.join('/app/data', fileName)
|
||||||
*
|
console.log(`[Util: getUserConfig] Reading ${filePath}`)
|
||||||
* @param filename name of the configuration file to get
|
try {
|
||||||
* @param callback function to allow a promise from getting the config
|
const data = await fs.readFile(filePath, 'utf-8')
|
||||||
*/
|
const config = JSON.parse(data)
|
||||||
export async function getUserConfig(filename: string, callback: (config: UserConfig | undefined) => void): Promise<void> {
|
if (!config || !config.options || !config.name) {
|
||||||
const fullFileName = `data/${filename}`
|
console.log(`[Util: getUserConfig] Invalid or empty config in ${filePath}: ${JSON.stringify(config)}`)
|
||||||
|
callback(undefined)
|
||||||
// attempt to read the file and get the configuration
|
} else {
|
||||||
if (fs.existsSync(fullFileName)) {
|
console.log(`[Util: getUserConfig] Successfully read ${filePath}`)
|
||||||
fs.readFile(fullFileName, 'utf8', (error, data) => {
|
callback(config)
|
||||||
if (error) {
|
}
|
||||||
callback(undefined)
|
} catch (error) {
|
||||||
return // something went wrong... stop
|
console.log(`[Util: getUserConfig] Failed to read ${filePath}: ${error}`)
|
||||||
}
|
callback(undefined)
|
||||||
callback(JSON.parse(data))
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
callback(undefined) // file not found
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
|
// src/utils/index.ts
|
||||||
// Centralized import index
|
// Centralized import index
|
||||||
export * from './env.js'
|
export * from './env.js'
|
||||||
export * from './events.js'
|
export * from './events.js'
|
||||||
export * from './messageNormal.js'
|
|
||||||
export * from './commands.js'
|
export * from './commands.js'
|
||||||
export * from './configInterfaces.js'
|
|
||||||
export * from './mentionClean.js'
|
export * from './mentionClean.js'
|
||||||
|
|
||||||
// handler imports
|
// Explicitly re-export messageNormal members
|
||||||
export * from './handlers/chatHistoryHandler.js'
|
export { normalMessage, UserMessage } from './messageNormal.js'
|
||||||
|
|
||||||
|
// Explicitly re-export configInterfaces members
|
||||||
|
export { UserConfig, ServerConfig, Channel } from './configInterfaces.js'
|
||||||
|
|
||||||
|
// Explicitly re-export handler functions
|
||||||
|
export {
|
||||||
|
clearChannelInfo,
|
||||||
|
getChannelInfo,
|
||||||
|
openChannelInfo
|
||||||
|
} from './handlers/chatHistoryHandler.js'
|
||||||
export * from './handlers/configHandler.js'
|
export * from './handlers/configHandler.js'
|
||||||
export * from './handlers/streamHandler.js'
|
export * from './handlers/streamHandler.js'
|
||||||
export * from './handlers/bufferHandler.js'
|
export * from './handlers/bufferHandler.js'
|
||||||
|
|||||||
@@ -1,9 +1,36 @@
|
|||||||
import { Message, SendableChannels } from 'discord.js'
|
import { Message, SendableChannels } from 'discord.js'
|
||||||
import { ChatResponse, Ollama } from 'ollama'
|
import { ChatResponse, Ollama } from 'ollama'
|
||||||
import { ChatParams, UserMessage, streamResponse, blockResponse } from './index.js'
|
|
||||||
import { Queue } from '../queues/queue.js'
|
import { Queue } from '../queues/queue.js'
|
||||||
import { AbortableAsyncIterator } from 'ollama/src/utils.js'
|
import { AbortableAsyncIterator } from 'ollama/src/utils.js'
|
||||||
|
|
||||||
|
export interface UserMessage {
|
||||||
|
role: 'user' | 'assistant'
|
||||||
|
content: string
|
||||||
|
images?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatParams {
|
||||||
|
model: string
|
||||||
|
ollama: Ollama
|
||||||
|
msgHist: UserMessage[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function streamResponse(params: ChatParams): Promise<AbortableAsyncIterator<ChatResponse>> {
|
||||||
|
return params.ollama.chat({
|
||||||
|
model: params.model,
|
||||||
|
messages: params.msgHist.map(msg => ({ role: msg.role, content: msg.content, images: msg.images })),
|
||||||
|
stream: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function blockResponse(params: ChatParams): Promise<ChatResponse> {
|
||||||
|
return params.ollama.chat({
|
||||||
|
model: params.model,
|
||||||
|
messages: params.msgHist.map(msg => ({ role: msg.role, content: msg.content, images: msg.images })),
|
||||||
|
stream: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to send replies as normal text on discord like any other user
|
* Method to send replies as normal text on discord like any other user
|
||||||
* @param message message sent by the user
|
* @param message message sent by the user
|
||||||
@@ -17,70 +44,58 @@ export async function normalMessage(
|
|||||||
msgHist: Queue<UserMessage>,
|
msgHist: Queue<UserMessage>,
|
||||||
stream: boolean
|
stream: boolean
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// bot's respnse
|
|
||||||
let response: ChatResponse | AbortableAsyncIterator<ChatResponse>
|
|
||||||
let result: string = ''
|
let result: string = ''
|
||||||
const channel = message.channel as SendableChannels
|
const channel = message.channel as SendableChannels
|
||||||
|
|
||||||
await channel.send('Generating Response . . .').then(async sentMessage => {
|
const sentMessage = await channel.send('Generating Response . . .')
|
||||||
try {
|
try {
|
||||||
const params: ChatParams = {
|
const params: ChatParams = {
|
||||||
model: model,
|
model: model,
|
||||||
ollama: ollama,
|
ollama: ollama,
|
||||||
msgHist: msgHist.getItems()
|
msgHist: msgHist.getItems()
|
||||||
}
|
|
||||||
|
|
||||||
// run query based on stream preference, true = stream, false = block
|
|
||||||
if (stream) {
|
|
||||||
let messageBlock: Message = sentMessage
|
|
||||||
response = await streamResponse(params) // THIS WILL BE SLOW due to discord limits!
|
|
||||||
for await (const portion of response) {
|
|
||||||
// check if over discord message limit
|
|
||||||
if (result.length + portion.message.content.length > 2000) {
|
|
||||||
result = portion.message.content
|
|
||||||
|
|
||||||
// new message block, wait for it to send and assign new block to respond.
|
|
||||||
await channel.send("Creating new stream block...")
|
|
||||||
.then(sentMessage => { messageBlock = sentMessage })
|
|
||||||
} else {
|
|
||||||
result += portion.message.content
|
|
||||||
|
|
||||||
// ensure block is not empty
|
|
||||||
if (result.length > 5)
|
|
||||||
messageBlock.edit(result)
|
|
||||||
}
|
|
||||||
console.log(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
response = await blockResponse(params)
|
|
||||||
result = response.message.content
|
|
||||||
|
|
||||||
// check if message length > discord max for normal messages
|
|
||||||
if (result.length > 2000) {
|
|
||||||
sentMessage.edit(result.slice(0, 2000))
|
|
||||||
result = result.slice(2000)
|
|
||||||
|
|
||||||
// handle for rest of message that is >2000
|
|
||||||
while (result.length > 2000) {
|
|
||||||
channel.send(result.slice(0, 2000))
|
|
||||||
result = result.slice(2000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// last part of message
|
|
||||||
channel.send(result)
|
|
||||||
} else // edit the 'generic' response to new message since <2000
|
|
||||||
sentMessage.edit(result)
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.log(`[Util: messageNormal] Error creating message: ${error.message}`)
|
|
||||||
if (error.message.includes('try pulling it first'))
|
|
||||||
sentMessage.edit(`**Response generation failed.**\n\nReason: You do not have the ${model} downloaded. Ask an admin to pull it using the \`pull-model\` command.`)
|
|
||||||
else
|
|
||||||
sentMessage.edit(`**Response generation failed.**\n\nReason: ${error.message}`)
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
// return the string representation of ollama query response
|
if (stream) {
|
||||||
|
let messageBlock = sentMessage
|
||||||
|
const response = await streamResponse(params)
|
||||||
|
for await (const portion of response) {
|
||||||
|
if (result.length + portion.message.content.length > 2000) {
|
||||||
|
result = portion.message.content
|
||||||
|
messageBlock = await channel.send("Creating new stream block...")
|
||||||
|
} else {
|
||||||
|
result += portion.message.content
|
||||||
|
if (result.length > 5) {
|
||||||
|
await messageBlock.edit(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const response = await blockResponse(params)
|
||||||
|
result = response.message.content
|
||||||
|
|
||||||
|
if (result.length > 2000) {
|
||||||
|
await sentMessage.edit(result.slice(0, 2000))
|
||||||
|
result = result.slice(2000)
|
||||||
|
while (result.length > 2000) {
|
||||||
|
await channel.send(result.slice(0, 2000))
|
||||||
|
result = result.slice(2000)
|
||||||
|
}
|
||||||
|
await channel.send(result)
|
||||||
|
} else {
|
||||||
|
await sentMessage.edit(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log(`[Util: normalMessage] Error creating message: ${error.message}`)
|
||||||
|
let errorMessage = '**Response generation failed.**\n\nReason: ';
|
||||||
|
if (error.message.includes('try pulling it first')) {
|
||||||
|
errorMessage += `You do not have the ${model} downloaded. Ask an admin to pull it using the \`pull-model\` command.`;
|
||||||
|
} else {
|
||||||
|
errorMessage += error.message;
|
||||||
|
}
|
||||||
|
await sentMessage.edit(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user