5 Commits

Author SHA1 Message Date
Kevin Dang
4aadea4611 Summary Command (#194) 2026-06-02 15:34:48 -07:00
Kevin Dang
c0e29b3bbe Pipeline Trigger Change and Newline Format (#193) 2026-05-11 11:42:48 -07:00
Kevin Dang
32137dacb0 Add: Filter out think tags in bot message (#190) 2025-10-04 20:17:26 -07:00
Kevin Dang
c00ea5de98 Additional Channel Awareness (#186) 2025-07-31 19:04:51 -07:00
Kevin Dang
b27cdfc162 Update Documentation with New Features (#185) 2025-06-22 20:43:50 -07:00
18 changed files with 486 additions and 284 deletions

View File

@@ -8,10 +8,10 @@ assignees: ''
--- ---
## Issue ## Issue
A clear and concise description of what the problem/feature is. A clear and concise description of what the problem/feature is. PLease describe it as best as possible.
## Solution ## Solution
* Provide steps or ideals to how to implement or investigate this new feature. * Provide steps or ideas to how to implement or investigate this new feature.
## References ## References
* Provide additional context and external references here * Provide additional context and external references here

View File

@@ -1,75 +1,75 @@
name: Builds name: Builds
run-name: Validate Node and Docker Builds run-name: Validate Node and Docker Builds
on: on:
push: push:
branches: branches:
- master # runs after Pull Request is merged - master # runs after Pull Request is merged
jobs: jobs:
Discord-Node-Build: # test if the node install and run Discord-Node-Build: # test if the node install and run
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 2 timeout-minutes: 2
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Node Environment lts/jod - name: Set up Node Environment lts/jod
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: lts/jod node-version: lts/jod
cache: "npm" cache: "npm"
- name: Install Project Dependencies - name: Install Project Dependencies
run: | run: |
npm install npm install
- name: Build Application - name: Build Application
run: | run: |
npm run build npm run build
- name: Create Environment Variables - name: Create Environment Variables
run: | run: |
touch .env touch .env
echo CLIENT_TOKEN = ${{ secrets.BOT_TOKEN }} >> .env echo CLIENT_TOKEN = ${{ secrets.BOT_TOKEN }} >> .env
echo OLLAMA_IP = ${{ secrets.OLLAMA_IP }} >> .env echo OLLAMA_IP = ${{ secrets.OLLAMA_IP }} >> .env
echo OLLAMA_PORT = ${{ secrets.OLLAMA_PORT }} >> .env echo OLLAMA_PORT = ${{ secrets.OLLAMA_PORT }} >> .env
echo MODEL = ${{ secrets.MODEL }} >> .env echo MODEL = ${{ secrets.MODEL }} >> .env
# set -e ensures if nohup fails, this section fails # set -e ensures if nohup fails, this section fails
- name: Startup Discord Bot Client - name: Startup Discord Bot Client
run: | run: |
set -e set -e
nohup npm run prod & nohup npm run prod &
Discord-Ollama-Container-Build: # test docker build and run Discord-Ollama-Container-Build: # test docker build and run
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 2 timeout-minutes: 2
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Node Environment lts/jod - name: Set up Node Environment lts/jod
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: lts/jod node-version: lts/jod
cache: "npm" cache: "npm"
- name: Create Environment Variables - name: Create Environment Variables
run: | run: |
touch .env touch .env
echo CLIENT_TOKEN = ${{ secrets.BOT_TOKEN }} >> .env echo CLIENT_TOKEN = ${{ secrets.BOT_TOKEN }} >> .env
echo OLLAMA_IP = ${{ secrets.OLLAMA_IP }} >> .env echo OLLAMA_IP = ${{ secrets.OLLAMA_IP }} >> .env
echo OLLAMA_PORT = ${{ secrets.OLLAMA_PORT }} >> .env echo OLLAMA_PORT = ${{ secrets.OLLAMA_PORT }} >> .env
echo MODEL = ${{ secrets.MODEL }} >> .env echo MODEL = ${{ secrets.MODEL }} >> .env
- name: Setup Docker Network and Images - name: Setup Docker Network and Images
run: | run: |
npm run docker:start-cpu npm run docker:start-cpu
- name: Check Images Exist - name: Check Images Exist
run: | run: |
(docker images | grep -q 'kevinthedang/discord-ollama' && docker images | grep -qE 'ollama/ollama') || exit 1 (docker images | grep -q 'kevinthedang/discord-ollama' && docker images | grep -qE 'ollama/ollama') || exit 1
- name: Check Containers Exist - name: Check Containers Exist
run: | run: |
(docker ps | grep -q 'ollama' && docker ps | grep -q 'discord') || exit 1 (docker ps | grep -q 'ollama' && docker ps | grep -q 'discord') || exit 1

View File

@@ -1,9 +1,10 @@
name: Deploy name: Deploy
run-name: Deploy Application Latest run-name: Deploy Application Latest
on: # on:
push: # push:
tags: # tags:
- 'v*' # - 'v*'
on: workflow_dispatch
jobs: jobs:
Deploy-Application: Deploy-Application:

View File

@@ -1,48 +1,48 @@
name: Tests name: Tests
run-name: Unit Tests run-name: Unit Tests
on: on:
pull_request: pull_request:
branches: branches:
- master - master
paths: paths:
- '*' - '*'
- 'package*.json' - 'package*.json'
- 'src/**' - 'src/**'
- 'tests/**' - 'tests/**'
- '!docs/**' - '!docs/**'
- '!imgs/**' - '!imgs/**'
- '!.github/**' - '!.github/**'
- '.github/workflows/**' - '.github/workflows/**'
- '!.gitignore' - '!.gitignore'
- '!LICENSE' - '!LICENSE'
- '!README' - '!README'
jobs: jobs:
Discord-Node-Test: Discord-Node-Test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 2 timeout-minutes: 2
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Node Environment lts/jod - name: Set up Node Environment lts/jod
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: lts/jod node-version: lts/jod
cache: "npm" cache: "npm"
- name: Install Project Dependencies - name: Install Project Dependencies
run: | run: |
npm install npm install
- name: Create Environment Variables - name: Create Environment Variables
run: | run: |
touch .env touch .env
echo CLIENT_TOKEN = ${{ secrets.BOT_TOKEN }} >> .env echo CLIENT_TOKEN = ${{ secrets.BOT_TOKEN }} >> .env
echo OLLAMA_IP = ${{ secrets.OLLAMA_IP }} >> .env echo OLLAMA_IP = ${{ secrets.OLLAMA_IP }} >> .env
echo OLLAMA_PORT = ${{ secrets.OLLAMA_PORT }} >> .env echo OLLAMA_PORT = ${{ secrets.OLLAMA_PORT }} >> .env
echo MODEL = ${{ secrets.MODEL }} >> .env echo MODEL = ${{ secrets.MODEL }} >> .env
- name: Test Application - name: Test Application
run: | run: |
npm run tests npm run tests

View File

@@ -4,9 +4,9 @@
<h3><a href="#"></a>Ollama as your Discord AI Assistant</h3> <h3><a href="#"></a>Ollama as your Discord AI Assistant</h3>
<p><a href="#"></a><a href="https://creativecommons.org/licenses/by/4.0/"><img alt="License" src="https://img.shields.io/badge/License-CC_BY_4.0-darkgreen.svg" /></a> <p><a href="#"></a><a href="https://creativecommons.org/licenses/by/4.0/"><img alt="License" src="https://img.shields.io/badge/License-CC_BY_4.0-darkgreen.svg" /></a>
<a href="#"></a><a href="https://github.com/kevinthedang/discord-ollama/releases/latest"><img alt="Release" src="https://img.shields.io/github/v/release/kevinthedang/discord-ollama?logo=github" /></a> <a href="#"></a><a href="https://github.com/kevinthedang/discord-ollama/releases/latest"><img alt="Release" src="https://img.shields.io/github/v/release/kevinthedang/discord-ollama?logo=github" /></a>
<a href="#"></a><a href="https://github.com/kevinthedang/discord-ollama/actions/workflows/build.yml"><img alt="Build Status" src="https://github.com/kevinthedang/discord-ollama/actions/workflows/build.yml/badge.svg" /></a> <a href="#"></a><a href="https://github.com/kevinthedang/discord-ollama/actions/workflows/build.yml"><img alt="Builds" src="https://github.com/kevinthedang/discord-ollama/actions/workflows/build.yml/badge.svg" /></a>
<a href="#"></a><a href="https://github.com/kevinthedang/discord-ollama/actions/workflows/deploy.yml"><img alt="Deploy Status" src="https://github.com/kevinthedang/discord-ollama/actions/workflows/deploy.yml/badge.svg" /></a> <!-- <a href="#"></a><a href="https://github.com/kevinthedang/discord-ollama/actions/workflows/deploy.yml"><img alt="Deploy Status" src="https://github.com/kevinthedang/discord-ollama/actions/workflows/deploy.yml/badge.svg" /></a> -->
<a href="#"></a><a href="https://github.com/kevinthedang/discord-ollama/actions/workflows/test.yml"><img alt="Testing Status" src="https://github.com/kevinthedang/discord-ollama/actions/workflows/test.yml/badge.svg" /></a> <a href="#"></a><a href="https://github.com/kevinthedang/discord-ollama/actions/workflows/test.yml"><img alt="Tests" src="https://github.com/kevinthedang/discord-ollama/actions/workflows/test.yml/badge.svg" /></a>
<a href="#"></a><a href="https://github.com/kevinthedang/discord-ollama/actions/workflows/coverage.yml"><img alt="Code Coverage" src="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/kevinthedang/bc7b5dcfa16561ab02bb3df67a99b22d/raw/coverage.json"></a> <a href="#"></a><a href="https://github.com/kevinthedang/discord-ollama/actions/workflows/coverage.yml"><img alt="Code Coverage" src="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/kevinthedang/bc7b5dcfa16561ab02bb3df67a99b22d/raw/coverage.json"></a>
</div> </div>
@@ -14,18 +14,28 @@
Ollama is an AI model management tool that allows users to install and use custom large language models locally. Ollama is an AI model management tool that allows users to install and use custom large language models locally.
The project aims to: The project aims to:
* [x] Create a Discord bot that will utilize Ollama and chat to chat with users! * [x] Create a Discord bot that will utilize Ollama and chat to chat with users!
* [x] User Preferences on Chat * [x] User and Server Preferences
* [x] Message Persistance on Channels and Threads * [x] Message Persistance
* [x] Threads
* [x] Channels
* [x] Containerization with Docker * [x] Containerization with Docker
* [x] Slash Commands Compatible * [x] Slash Commands Compatible
* [x] Summary Command
* [ ] Model Info Command
* [ ] List Models Command
* [x] Pull Model Command
* [x] Switch Model Command
* [x] Delete Model Command
* [x] Create Thread Command
* [x] Create Private Thread Command
* [x] Message Stream Command
* [x] Change Message History Size Command
* [x] Clear Channel History Command (User Only)
* [x] Administrator Role Compatible
* [x] Generated Token Length Handling for >2000 * [x] Generated Token Length Handling for >2000
* [x] Token Length Handling of any message size * [x] Token Length Handling of any message size
* [x] User vs. Server Preferences * [x] Multi-User Chat Generation - This was built in from Ollama `v0.2.1+`
* [x] Administrator Role Compatible * [ ] Ollama Tool Support Implementation
* [x] Multi-User Chat Generation (Multiple users chatting at the same time) - This was built in from Ollama `v0.2.1+` * [ ] Enhanced Channel Context Awareness
* [x] Automatic and Manual model pulling through the Discord client * [ ] Improved User Replied Triggers
Further, Ollama provides the functionality to utilize custom models or provide context for the top-layer of any model available through the Ollama model library. Further, Ollama provides the functionality to utilize custom models or provide context for the top-layer of any model available through the Ollama model library.
* [Customize a model](https://github.com/ollama/ollama#customize-a-model) * [Customize a model](https://github.com/ollama/ollama#customize-a-model)

View File

@@ -7,7 +7,7 @@ services:
build: ./ # find docker file in designated path build: ./ # find docker file in designated path
container_name: discord container_name: discord
restart: always # rebuild container always restart: always # rebuild container always
image: kevinthedang/discord-ollama:0.8.6 image: kevinthedang/discord-ollama:0.9.0
environment: environment:
CLIENT_TOKEN: ${CLIENT_TOKEN} CLIENT_TOKEN: ${CLIENT_TOKEN}
OLLAMA_IP: ${OLLAMA_IP} OLLAMA_IP: ${OLLAMA_IP}

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "discord-ollama", "name": "discord-ollama",
"version": "0.8.5", "version": "0.9.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "discord-ollama", "name": "discord-ollama",
"version": "0.8.5", "version": "0.9.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"discord.js": "^14.20.0", "discord.js": "^14.20.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "discord-ollama", "name": "discord-ollama",
"version": "0.8.6", "version": "0.9.0",
"description": "Ollama Integration into discord", "description": "Ollama Integration into discord",
"main": "build/index.js", "main": "build/index.js",
"exports": "./build/index.js", "exports": "./build/index.js",

View File

@@ -1,41 +1,44 @@
import { Client, GatewayIntentBits } from 'discord.js' import { Client, GatewayIntentBits } from 'discord.js'
import { Ollama } from 'ollama' import { Ollama } from 'ollama'
import { Queue } from './queues/queue.js' import { Queue } from './queues/queue.js'
import { UserMessage, registerEvents } from './utils/index.js' import { UserMessage, registerEvents } from './utils/index.js'
import Events from './events/index.js' import Events from './events/index.js'
import Keys from './keys.js' import Keys from './keys.js'
// initialize the client with the following permissions when logging in // initialize the client with the following permissions when logging in
const client = new Client({ const client = new Client({
intents: [ intents: [
GatewayIntentBits.Guilds, GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent GatewayIntentBits.MessageContent
] ]
}) })
// initialize connection to ollama container // initialize connection to ollama container
export const ollama = new Ollama({ export const ollama = new Ollama({
host: `http://${Keys.ipAddress}:${Keys.portAddress}`, host: `http://${Keys.ipAddress}:${Keys.portAddress}`,
}) })
// Create Queue managed by Events // Create Queue managed by Events
const messageHistory: Queue<UserMessage> = new Queue<UserMessage> const messageHistory: Queue<UserMessage> = new Queue<UserMessage>
// register all events // Create Channel History Queue managed by Events
registerEvents(client, Events, messageHistory, ollama, Keys.defaultModel) const channelMessageHistory: Queue<UserMessage> = new Queue<UserMessage>
// Try to log in the client // register all events
await client.login(Keys.clientToken) registerEvents(client, Events, messageHistory, channelMessageHistory, ollama, Keys.defaultModel)
.catch((error) => {
console.error('[Login Error]', error) // Try to log in the client
process.exit(1) await client.login(Keys.clientToken)
}) .catch((error) => {
console.error('[Login Error]', error)
// queue up bots name process.exit(1)
messageHistory.enqueue({ })
role: 'assistant',
content: `My name is ${client.user?.username}`, // queue up bots name
images: [] messageHistory.enqueue({
role: 'assistant',
content: `My name is ${client.user?.username}`,
images: []
}) })

View File

@@ -9,6 +9,7 @@ import { ClearUserChannelHistory } from './cleanUserChannelHistory.js'
import { PullModel } from './pullModel.js' import { PullModel } from './pullModel.js'
import { SwitchModel } from './switchModel.js' import { SwitchModel } from './switchModel.js'
import { DeleteModel } from './deleteModel.js' import { DeleteModel } from './deleteModel.js'
import { Summary } from './summary.js'
export default [ export default [
ThreadCreate, ThreadCreate,
@@ -20,5 +21,6 @@ export default [
ClearUserChannelHistory, ClearUserChannelHistory,
PullModel, PullModel,
SwitchModel, SwitchModel,
DeleteModel DeleteModel,
Summary
] as SlashCommand[] ] as SlashCommand[]

50
src/commands/summary.ts Normal file
View File

@@ -0,0 +1,50 @@
import { Client, CommandInteraction, Message, MessageFlags, SendableChannels } from "discord.js";
import { accessChannelContext, normalMessage, SlashCommand, summarizeContextHistory, UserCommand, UserMessage } from "../utils/index.js";
import { ollama } from "../client.js"
export const Summary: SlashCommand = {
name: 'summary',
description: 'provides a summary of the chat history.',
// Generate Summary from additional context
run: async (client: Client, interaction: CommandInteraction) => {
// fetch channel
const channel = await client.channels.fetch(interaction.channelId)
if (!channel || !UserCommand.includes(channel.type)) return
// Defer
await interaction.deferReply({ flags: MessageFlags.Ephemeral })
// create summary using context
let channelContext: UserMessage[] = await new Promise((resolve) => {
accessChannelContext(interaction.channelId, (additionalContext) => {
if (additionalContext?.messages)
resolve(additionalContext.messages)
else
resolve([])
})
})
if (channelContext.length === 0) {
interaction.reply({
content: `There are no recent chat messages in this channel.`,
flags: MessageFlags.Ephemeral
})
return
}
// Push summarize prompt
channelContext.push({
role: "user",
content: "Please Summarize everything from the prior messages, provide in bullet point fashion on what people have said prior. For example: \"Someone mentioned/talked about ...\"",
images: []
})
// todo: instead of a default of codellama, we can user default model somehow. Look into later.
const response: string = await summarizeContextHistory(ollama, "codellama", channelContext)
console.log(response)
interaction.editReply({ content: response })
}
}

View File

@@ -1,5 +1,5 @@
import { TextChannel } from 'discord.js' import { TextChannel } from 'discord.js'
import { event, Events, normalMessage, UserMessage, clean } from '../utils/index.js' import { event, Events, normalMessage, UserMessage, clean, addToChannelContext } from '../utils/index.js'
import { import {
getChannelInfo, getServerConfig, getUserConfig, openChannelInfo, getChannelInfo, getServerConfig, getUserConfig, openChannelInfo,
openConfig, UserConfig, getAttachmentData, getTextFileAttachmentData openConfig, UserConfig, getAttachmentData, getTextFileAttachmentData
@@ -11,7 +11,7 @@ import {
* *
* @param message the message received from the channel * @param message the message received from the channel
*/ */
export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client, defaultModel }, message) => { export default event(Events.MessageCreate, async ({ log, msgHist, channelHistory, 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(`Message \"${cleanedMessage}\" from ${message.author.tag} in channel/thread ${message.channelId}.`)
@@ -19,6 +19,61 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
// Do not respond if bot talks in the chat // Do not respond if bot talks in the chat
if (message.author.username === message.client.user.username) return if (message.author.username === message.client.user.username) return
// Save User Chat even if not for the bot
let channelContextHistory: UserMessage[] = await new Promise((resolve) => {
getChannelInfo(`${message.channelId}-context.json`, (channelInfo) => {
if (channelInfo?.messages)
resolve(channelInfo.messages)
else {
log(`Channel/Thread ${message.channel}-context does not exist. File will be created shortly...`)
resolve([])
}
})
})
if (channelContextHistory.length === 0) {
channelContextHistory = await new Promise((resolve) => {
addToChannelContext(message.channelId,
message.channel as TextChannel
)
getChannelInfo(`${message.channelId}-context.json`, (channelInfo) => {
if (channelInfo?.messages)
resolve(channelInfo.messages)
else {
log(`Channel/Thread ${message.channel}-context does not exist. File will be created shortly...`)
}
})
})
}
// Set Channel History Queue
channelHistory.setQueue(channelContextHistory)
// get message attachment if exists
const attachment = message.attachments.first()
let messageAttachment: string[] = []
if (attachment && attachment.name?.endsWith(".txt"))
cleanedMessage += ' ' + await getTextFileAttachmentData(attachment)
else if (attachment)
messageAttachment = await getAttachmentData(attachment)
while (channelHistory.size() >= channelHistory.capacity) channelHistory.dequeue()
// push user response to channel history
console.log
channelHistory.enqueue({
role: 'user',
content: cleanedMessage,
images: messageAttachment || []
})
// Store in Channel Context
addToChannelContext(message.channelId,
message.channel as TextChannel,
channelHistory.getItems()
)
// Only respond if message mentions the bot // Only respond if message mentions the bot
if (!message.mentions.has(clientId)) return if (!message.mentions.has(clientId)) return
@@ -139,15 +194,6 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
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.`)
// get message attachment if exists
const attachment = message.attachments.first()
let messageAttachment: string[] = []
if (attachment && attachment.name?.endsWith(".txt"))
cleanedMessage += await getTextFileAttachmentData(attachment)
else if (attachment)
messageAttachment = await getAttachmentData(attachment)
const model: string = userConfig.options['switch-model'] const model: string = userConfig.options['switch-model']
// set up new queue // set up new queue

View File

@@ -1 +1 @@
import('./client.js') import('./client.js')

View File

@@ -1,10 +1,10 @@
import { getEnvVar } from './utils/index.js' import { getEnvVar } from './utils/index.js'
export const Keys = { export const Keys = {
clientToken: getEnvVar('CLIENT_TOKEN'), clientToken: getEnvVar('CLIENT_TOKEN'),
ipAddress: getEnvVar('OLLAMA_IP', '127.0.0.1'), // default ollama ip if none ipAddress: getEnvVar('OLLAMA_IP', '127.0.0.1'), // default ollama ip if none
portAddress: getEnvVar('OLLAMA_PORT', '11434'), // default ollama port if none portAddress: getEnvVar('OLLAMA_PORT', '11434'), // default ollama port if none
defaultModel: getEnvVar('MODEL', 'llama3.2') defaultModel: getEnvVar('MODEL', 'llama3.2')
} as const // readonly keys } as const // readonly keys
export default Keys export default Keys

View File

@@ -37,6 +37,7 @@ export interface EventProps {
client: Client, client: Client,
log: LogMethod, log: LogMethod,
msgHist: Queue<UserMessage>, msgHist: Queue<UserMessage>,
channelHistory: Queue<UserMessage>,
ollama: Ollama, ollama: Ollama,
defaultModel: String defaultModel: String
} }
@@ -78,6 +79,7 @@ export function registerEvents(
client: Client, client: Client,
events: Event[], events: Event[],
msgHist: Queue<UserMessage>, msgHist: Queue<UserMessage>,
channelHistory: Queue<UserMessage>,
ollama: Ollama, ollama: Ollama,
defaultModel: String defaultModel: String
): void { ): void {
@@ -88,7 +90,7 @@ export function registerEvents(
// Handle Errors, call callback, log errors as needed // Handle Errors, call callback, log errors as needed
try { try {
callback({ client, log, msgHist, ollama, defaultModel }, ...args) callback({ client, log, msgHist, channelHistory, ollama, defaultModel }, ...args)
} catch (error) { } catch (error) {
log('[Uncaught Error]', error) log('[Uncaught Error]', error)
} }

View File

@@ -56,6 +56,54 @@ export async function clearChannelInfo(filename: string, channel: TextChannel, u
return cleanedHistory return cleanedHistory
} }
export async function addToChannelContext(filename: string, channel : TextChannel | ThreadChannel, messages: UserMessage[] = []): Promise<void> {
const fullFileName = `data/${filename}-context.json`
if (fs.existsSync(fullFileName)) {
fs.readFile(fullFileName, 'utf8', (error, data) => {
if (error)
console.log(`[Error: addToChannelContext] Incorrect file format`)
else {
const object = JSON.parse(data)
if (object['messages'].length === 0)
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))
}
})
} else { // channel context does not exist, create it
const object: Configuration = JSON.parse(
`{
\"id\": \"${channel?.id}\",
\"name\": \"${channel?.name}\",
\"messages\": []
}`
)
const directory = path.dirname(fullFileName)
if (!fs.existsSync(directory))
fs.mkdirSync(directory, { recursive: true })
fs.writeFileSync(fullFileName, JSON.stringify(object, null, 2))
console.log(`[Util: addToChannelContext] Created '${fullFileName}' in working directory`)
}
}
export async function accessChannelContext(filename: string, callback: (config: Channel | undefined) => void): Promise<void> {
const fullFileName = `data/${filename}-context.json`
if (fs.existsSync(fullFileName)) {
fs.readFile(fullFileName, 'utf8', (error, data) => {
if (error) {
callback(undefined)
return // something went wrong... stop
}
callback(JSON.parse(data))
})
} else {
callback(undefined) // file not found
}
}
/** /**
* Method to open the channel history * Method to open the channel history
* *

View File

@@ -56,6 +56,10 @@ export async function normalMessage(
response = await blockResponse(params) response = await blockResponse(params)
result = response.message.content result = response.message.content
// check if there is a <think>...</think> sequence from the bot.
if (hasThinking(result))
result = result.replace(/<think>[\s\S]*?<\/think>/g, '').trim()
// check if message length > discord max for normal messages // check if message length > discord max for normal messages
if (result.length > 2000) { if (result.length > 2000) {
sentMessage.edit(result.slice(0, 2000)) sentMessage.edit(result.slice(0, 2000))
@@ -85,3 +89,39 @@ export async function normalMessage(
// return the string representation of ollama query response // return the string representation of ollama query response
return result return result
} }
export async function summarizeContextHistory(
ollama: Ollama,
model: string,
msgHist: UserMessage[],
): Promise<string> {
// todo: this stuff to create a response in a simpler way!
try {
const params: ChatParams = {
model: model,
ollama: ollama,
msgHist: msgHist
}
const response: ChatResponse = await blockResponse(params)
let result = response.message.content
if (hasThinking(result))
result = result.replace(/<think>[\s\S]*?<\/think>/g, '').trim()
return result
} catch (error: any) {
console.log(`[Util: messageNormal] Error creating message: ${error.message}`)
if (error.message.includes('fetch failed'))
error.message = 'Missing ollama service on machine'
else if (error.message.includes('try pulling it first'))
error.message = `You do not have the ${model} downloaded. Ask an admin to pull it using the \`pull-model\` command.`
return `**Response generation failed.**\n\nReason: ${error.message}`
}
}
// Region: Helpers
function hasThinking(message: string): boolean {
return /<think>[\s\S]*?<\/think>/i.test(message)
}
// End Region: Helpers

View File

@@ -1,78 +1,78 @@
// describe marks a test suite // describe marks a test suite
// expect takes a value from an expression // expect takes a value from an expression
// it marks a test case // it marks a test case
import { describe, expect, it, vi } from 'vitest' import { describe, expect, it, vi } from 'vitest'
import commands from '../src/commands/index.js' import commands from '../src/commands/index.js'
/** /**
* Mocking client.ts because of the commands * Mocking client.ts because of the commands
*/ */
vi.mock('../src/client.js', () => ({})) vi.mock('../src/client.js', () => ({}))
/** /**
* Commands test suite, tests the commands object * Commands test suite, tests the commands object
* Each command is to be tested elsewhere, this file * Each command is to be tested elsewhere, this file
* is to ensure that the commands object is defined. * is to ensure that the commands object is defined.
* *
* @param name name of the test suite * @param name name of the test suite
* @param fn function holding tests to run * @param fn function holding tests to run
*/ */
describe('Commands Existence', () => { describe('Commands Existence', () => {
// test definition of commands object // test definition of commands object
it('references defined object', () => { it('references defined object', () => {
// toBe compares the value to the expected value // toBe compares the value to the expected value
expect(typeof commands).toBe('object') expect(typeof commands).toBe('object')
}) })
// test specific commands in the object // test specific commands in the object
it('references specific commands', () => { it('references specific commands', () => {
const commandsString = commands.map(e => e.name).join(', ') const commandsString = commands.map(e => e.name).join(', ')
const expectedCommands = ['thread', 'private-thread', 'message-stream', 'toggle-chat', 'shutoff', 'modify-capacity', 'clear-user-channel-history', 'pull-model', 'switch-model', 'delete-model'] const expectedCommands = ['thread', 'private-thread', 'message-stream', 'toggle-chat', 'shutoff', 'modify-capacity', 'clear-user-channel-history', 'pull-model', 'switch-model', 'delete-model', 'summary']
expect(commandsString).toBe(expectedCommands.join(', ')) expect(commandsString).toBe(expectedCommands.join(', '))
}) })
}) })
/** /**
* User Commands Test suite for testing out commands * User Commands Test suite for testing out commands
* that would be run by users when using the application. * that would be run by users when using the application.
*/ */
describe('User Command Tests', () => { describe('User Command Tests', () => {
// test capacity command // test capacity command
it('run modify-capacity command', () => { it('run modify-capacity command', () => {
}) })
it('run clear-user-channel-history command', () => { it('run clear-user-channel-history command', () => {
}) })
it('run message-stream command', () => { it('run message-stream command', () => {
}) })
it('run message-style command', () => { it('run message-style command', () => {
}) })
it('run thread command', () => { it('run thread command', () => {
}) })
it('run private-thread command', () => { it('run private-thread command', () => {
}) })
}) })
/** /**
* Admin Commands Test suite for running administrative * Admin Commands Test suite for running administrative
* commands with the application. * commands with the application.
*/ */
describe('Admin Command Tests', () => { describe('Admin Command Tests', () => {
it('run shutoff command', () => { it('run shutoff command', () => {
}) })
it('run toggle-chat command', () => { it('run toggle-chat command', () => {
}) })
}) })