8 Commits

Author SHA1 Message Date
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
Jonathan Smoley
1074fe2270 Removed Redis Dependency (#184) 2025-06-20 17:04:56 -07:00
Jonathan Smoley
4236582cf4 Upgrade discord.js from 14.18.0 to 14.20.0 (#177)
* fix: upgrade discord.js from 14.18.0 to 14.19.3

Snyk has created this PR to upgrade discord.js from 14.18.0 to 14.19.3.

See this package in npm:
discord.js

See this project in Snyk:
https://app.snyk.io/org/jt2m0l3y/project/d8b070a3-e4a3-457a-977b-7eb6a4a48346?utm_source=github&utm_medium=referral&page=upgrade-pr

* Update: discordjs to latest

* Fix: Broken commands

* Fix: Ollama offline failsafes trigger

---------

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
Co-authored-by: Kevin Dang <kevinthedang_1@outlook.com>
2025-06-20 08:34:10 -07:00
Kevin Dang
e07e8fbf89 Fix Missing Redis Connections and Error Messages (#182)
* Update: simplify npm command to run tests

* Fix: redis workaround for local non docker

* Update: error message and config creation

* Fix: Better Messages for Ollama service being offline

* Update: version increment

* Fix: verion typo

* Update: Use built-in catch method for logging

* Update: same catch method for redis
2025-06-18 07:54:53 -07:00
Jonathan Smoley
6d0a537540 Upgrade dotenv to 16.5.0 (#173)
Snyk has created this PR to upgrade dotenv from 16.4.7 to 16.5.0.

See this package in npm:
dotenv

See this project in Snyk:
https://app.snyk.io/org/jt2m0l3y/project/d8b070a3-e4a3-457a-977b-7eb6a4a48346?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
Co-authored-by: Kevin Dang <77701718+kevinthedang@users.noreply.github.com>
2025-05-15 07:07:06 -07:00
Jonathan Smoley
0ddd59aea1 Upgrade ollama package to 0.5.15 (#174)
Snyk has created this PR to upgrade ollama from 0.5.14 to 0.5.15.

See this package in npm:
ollama

See this project in Snyk:
https://app.snyk.io/org/jt2m0l3y/project/d8b070a3-e4a3-457a-977b-7eb6a4a48346?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2025-05-09 09:22:14 -07:00
24 changed files with 243 additions and 310 deletions

View File

@@ -13,7 +13,3 @@ DISCORD_IP = IP_ADDRESS
# subnet address, ex. 172.18.0.0 as we use /16.
SUBNET_ADDRESS = ADDRESS
# redis port and ip, default redis port is 6379
REDIS_IP = IP_ADDRESS
REDIS_PORT = PORT

View File

@@ -34,8 +34,6 @@ jobs:
echo OLLAMA_IP = ${{ secrets.OLLAMA_IP }} >> .env
echo OLLAMA_PORT = ${{ secrets.OLLAMA_PORT }} >> .env
echo MODEL = ${{ secrets.MODEL }} >> .env
echo REDIS_IP = ${{ secrets.REDIS_IP }} >> .env
echo REDIS_PORT = ${{ secrets.REDIS_PORT }} >> .env
# set -e ensures if nohup fails, this section fails
- name: Startup Discord Bot Client
@@ -63,8 +61,6 @@ jobs:
echo OLLAMA_IP = ${{ secrets.OLLAMA_IP }} >> .env
echo OLLAMA_PORT = ${{ secrets.OLLAMA_PORT }} >> .env
echo MODEL = ${{ secrets.MODEL }} >> .env
echo REDIS_IP = ${{ secrets.REDIS_IP }} >> .env
echo REDIS_PORT = ${{ secrets.REDIS_PORT }} >> .env
- name: Setup Docker Network and Images
run: |
@@ -72,8 +68,8 @@ jobs:
- name: Check Images Exist
run: |
(docker images | grep -q 'kevinthedang/discord-ollama' && docker images | grep -qE 'ollama/ollama' | docker images | grep -qE 'redis') || exit 1
(docker images | grep -q 'kevinthedang/discord-ollama' && docker images | grep -qE 'ollama/ollama') || exit 1
- name: Check Containers Exist
run: |
(docker ps | grep -q 'ollama' && docker ps | grep -q 'discord' && docker ps | grep -q 'redis') || exit 1
(docker ps | grep -q 'ollama' && docker ps | grep -q 'discord') || exit 1

View File

@@ -31,8 +31,6 @@ jobs:
echo OLLAMA_IP = ${{ secrets.OLLAMA_IP }} >> .env
echo OLLAMA_PORT = ${{ secrets.OLLAMA_PORT }} >> .env
echo MODEL = ${{ secrets.MODEL }} >> .env
echo REDIS_IP = ${{ secrets.REDIS_IP }} >> .env
echo REDIS_PORT = ${{ secrets.REDIS_PORT }} >> .env
- name: Collect Code Coverage
run: |

View File

@@ -24,8 +24,6 @@ jobs:
echo MODEL = ${{ secrets.MODEL }} >> .env
echo DISCORD_IP = ${{ secrets.DISCORD_IP }} >> .env
echo SUBNET_ADDRESS = ${{ secrets.SUBNET_ADDRESS }} >> .env
echo REDIS_IP = ${{ secrets.REDIS_IP }} >> .env
echo REDIS_PORT = ${{ secrets.REDIS_PORT }} >> .env
- name: Check if directory exists and delete it
run: |
@@ -59,7 +57,6 @@ jobs:
npm install
IMAGE="kevinthedang/discord-ollama"
REDIS="redis"
OLLAMA="ollama/ollama"
if docker images | grep -q $IMAGE; then
@@ -75,19 +72,6 @@ jobs:
echo "Old $IMAGE Image Removed"
fi
if docker images | grep -q $REDIS; then
IMAGE_ID=$(docker images -q $REDIS)
CONTAINER_IDS=$(docker ps -q --filter "ancestor=$IMAGE_ID")
if [ ! -z "$CONTAINER_IDS" ]; then
# Stop and remove the running containers
docker stop $CONTAINER_IDS
echo "Stopped and removed the containers using the image $REDIS"
fi
docker rmi $IMAGE_ID
echo "Old $REDIS Image Removed"
fi
if docker images | grep -q $OLLAMA; then
IMAGE_ID=$(docker images -q $OLLAMA)
CONTAINER_IDS=$(docker ps -q --filter "ancestor=$IMAGE_ID")
@@ -117,14 +101,6 @@ jobs:
--ip ${{ secrets.OLLAMA_IP }} \
ollama/ollama:latest
docker run --rm -d \
-v redis:/root/.redis \
-p ${{ secrets.REDIS_PORT }}:${{ secrets.REDIS_PORT }} \
--name redis \
--network ollama-net \
--ip ${{ secrets.REDIS_IP }} \
redis:latest
docker run --rm -d \
-v discord:/src/app \
--name discord \

View File

@@ -42,8 +42,6 @@ jobs:
echo OLLAMA_IP = ${{ secrets.OLLAMA_IP }} >> .env
echo OLLAMA_PORT = ${{ secrets.OLLAMA_PORT }} >> .env
echo MODEL = ${{ secrets.MODEL }} >> .env
echo REDIS_IP = ${{ secrets.REDIS_IP }} >> .env
echo REDIS_PORT = ${{ secrets.REDIS_PORT }} >> .env
- name: Test Application
run: |

View File

@@ -14,19 +14,28 @@
Ollama is an AI model management tool that allows users to install and use custom large language models locally.
The project aims to:
* [x] Create a Discord bot that will utilize Ollama and chat to chat with users!
* [x] User Preferences on Chat
* [x] Message Persistance on Channels and Threads
* [x] Threads
* [x] Channels
* [x] User and Server Preferences
* [x] Message Persistance
* [x] Containerization with Docker
* [x] Slash Commands Compatible
* [ ] 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] Token Length Handling of any message size
* [x] User vs. Server Preferences
* [ ] Redis Caching
* [x] Administrator Role Compatible
* [x] Multi-User Chat Generation (Multiple users chatting at the same time) - This was built in from Ollama `v0.2.1+`
* [x] Automatic and Manual model pulling through the Discord client
* [x] Multi-User Chat Generation - This was built in from Ollama `v0.2.1+`
* [ ] Ollama Tool Support Implementation
* [ ] Enhanced Channel Context Awareness
* [ ] 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.
* [Customize a model](https://github.com/ollama/ollama#customize-a-model)
@@ -54,8 +63,6 @@ These are guides to the features and capabilities of this app.
* This project requires the use of npm version `10.9.0` or above.
* [Ollama](https://ollama.com/)
* [Ollama Docker Image](https://hub.docker.com/r/ollama/ollama)
* [Redis](https://redis.io/)
* [Redis Docker Image](https://hub.docker.com/_/redis)
* [Discord.js Docs](https://discord.js.org/docs/packages/discord.js/main)
* [Setting up Docker (Ubuntu 20.04)](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-20-04)
* [Setting up Nvidia Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html)

View File

@@ -7,14 +7,12 @@ services:
build: ./ # find docker file in designated path
container_name: discord
restart: always # rebuild container always
image: kevinthedang/discord-ollama:0.8.5s
image: kevinthedang/discord-ollama:0.8.7
environment:
CLIENT_TOKEN: ${CLIENT_TOKEN}
OLLAMA_IP: ${OLLAMA_IP}
OLLAMA_PORT: ${OLLAMA_PORT}
MODEL: ${MODEL}
REDIS_IP: ${REDIS_IP}
REDIS_PORT: ${REDIS_PORT}
networks:
ollama-net:
ipv4_address: ${DISCORD_IP}
@@ -37,19 +35,6 @@ services:
ports:
- ${OLLAMA_PORT}:${OLLAMA_PORT}
# setup redis container
redis:
image: redis:latest
container_name: redis
restart: always
networks:
ollama-net:
ipv4_address: ${REDIS_IP}
volumes:
- redis:/root/.redis
ports:
- ${REDIS_PORT}:${REDIS_PORT}
# create a network that supports giving addresses withing a specific subnet
networks:
ollama-net:
@@ -62,4 +47,3 @@ networks:
volumes:
ollama:
discord:
redis:

View File

@@ -43,13 +43,11 @@ sudo systemctl restart docker
* [GitHub repository](https://github.com/NVIDIA/nvidia-container-toolkit?tab=readme-ov-file) for Nvidia Container Toolkit
## To Run (with Docker and Docker Compose)
* With the inclusion of subnets in the `docker-compose.yml`, you will need to set the `SUBNET_ADDRESS`, `OLLAMA_IP`, `OLLAMA_PORT`, `REDIS_IP`, `REDIS_PORT`, and `DISCORD_IP`. Here are some default values if you don't care:
* With the inclusion of subnets in the `docker-compose.yml`, you will need to set the `SUBNET_ADDRESS`, `OLLAMA_IP`, `OLLAMA_PORT`, and `DISCORD_IP`. Here are some default values if you don't care:
* `SUBNET_ADDRESS = 172.18.0.0`
* `OLLAMA_IP = 172.18.0.2`
* `OLLAMA_PORT = 11434`
* `DISCORD_IP = 172.18.0.3`
* `REDIS_IP = 172.18.0.4`
* `REDIS_PORT = 6379`
* Don't understand any of this? watch a Networking video to understand subnetting.
* You also need all environment variables shown in [`.env.sample`](../.env.sample)
* Otherwise, there is no need to install any npm packages for this, you just need to run `npm run start` to pull the containers and spin them up.

215
package-lock.json generated
View File

@@ -1,18 +1,17 @@
{
"name": "discord-ollama",
"version": "0.8.5",
"version": "0.8.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "discord-ollama",
"version": "0.8.5",
"version": "0.8.7",
"license": "ISC",
"dependencies": {
"discord.js": "^14.18.0",
"dotenv": "^16.4.7",
"ollama": "^0.5.14",
"redis": "^4.7.0"
"discord.js": "^14.20.0",
"dotenv": "^16.5.0",
"ollama": "^0.5.15"
},
"devDependencies": {
"@types/node": "^22.13.14",
@@ -126,15 +125,15 @@
}
},
"node_modules/@discordjs/builders": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.10.1.tgz",
"integrity": "sha512-OWo1fY4ztL1/M/DUyRPShB4d/EzVfuUvPTRRHRIt/YxBrUYSz0a+JicD5F5zHFoNs2oTuWavxCOVFV1UljHTng==",
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.11.2.tgz",
"integrity": "sha512-F1WTABdd8/R9D1icJzajC4IuLyyS8f3rTOz66JsSI3pKvpCAtsMBweu8cyNYsIyvcrKAVn9EPK+Psoymq+XC0A==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/formatters": "^0.6.0",
"@discordjs/formatters": "^0.6.1",
"@discordjs/util": "^1.1.1",
"@sapphire/shapeshift": "^4.0.0",
"discord-api-types": "^0.37.119",
"discord-api-types": "^0.38.1",
"fast-deep-equal": "^3.1.3",
"ts-mixer": "^6.0.4",
"tslib": "^2.6.3"
@@ -156,12 +155,12 @@
}
},
"node_modules/@discordjs/formatters": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.0.tgz",
"integrity": "sha512-YIruKw4UILt/ivO4uISmrGq2GdMY6EkoTtD0oS0GvkJFRZbTSdPhzYiUILbJ/QslsvC9H9nTgGgnarnIl4jMfw==",
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.1.tgz",
"integrity": "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg==",
"license": "Apache-2.0",
"dependencies": {
"discord-api-types": "^0.37.114"
"discord-api-types": "^0.38.1"
},
"engines": {
"node": ">=16.11.0"
@@ -171,9 +170,9 @@
}
},
"node_modules/@discordjs/rest": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.3.tgz",
"integrity": "sha512-+SO4RKvWsM+y8uFHgYQrcTl/3+cY02uQOH7/7bKbVZsTfrfpoE62o5p+mmV+s7FVhTX82/kQUGGbu4YlV60RtA==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.5.1.tgz",
"integrity": "sha512-Tg9840IneBcbrAjcGaQzHUJWFNq1MMWZjTdjJ0WS/89IffaNKc++iOvffucPxQTF/gviO9+9r8kEPea1X5J2Dw==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/collection": "^2.1.1",
@@ -181,10 +180,10 @@
"@sapphire/async-queue": "^1.5.3",
"@sapphire/snowflake": "^3.5.3",
"@vladfrangu/async_event_emitter": "^2.4.6",
"discord-api-types": "^0.37.119",
"discord-api-types": "^0.38.1",
"magic-bytes.js": "^1.10.0",
"tslib": "^2.6.3",
"undici": "6.21.1"
"undici": "6.21.3"
},
"engines": {
"node": ">=18"
@@ -218,18 +217,18 @@
}
},
"node_modules/@discordjs/ws": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.1.tgz",
"integrity": "sha512-PBvenhZG56a6tMWF/f4P6f4GxZKJTBG95n7aiGSPTnodmz4N5g60t79rSIAq7ywMbv8A4jFtexMruH+oe51aQQ==",
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz",
"integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/collection": "^2.1.0",
"@discordjs/rest": "^2.4.3",
"@discordjs/rest": "^2.5.1",
"@discordjs/util": "^1.1.0",
"@sapphire/async-queue": "^1.5.2",
"@types/ws": "^8.5.10",
"@vladfrangu/async_event_emitter": "^2.2.4",
"discord-api-types": "^0.37.119",
"discord-api-types": "^0.38.1",
"tslib": "^2.6.2",
"ws": "^8.17.0"
},
@@ -769,65 +768,6 @@
"node": ">=14"
}
},
"node_modules/@redis/bloom": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz",
"integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==",
"license": "MIT",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/client": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz",
"integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==",
"license": "MIT",
"dependencies": {
"cluster-key-slot": "1.1.2",
"generic-pool": "3.9.0",
"yallist": "4.0.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/@redis/graph": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz",
"integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==",
"license": "MIT",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/json": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz",
"integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==",
"license": "MIT",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/search": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz",
"integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==",
"license": "MIT",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/time-series": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz",
"integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==",
"license": "MIT",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.37.0.tgz",
@@ -1186,9 +1126,9 @@
}
},
"node_modules/@types/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==",
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
@@ -1473,15 +1413,6 @@
"node": ">= 16"
}
},
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1563,29 +1494,33 @@
}
},
"node_modules/discord-api-types": {
"version": "0.37.119",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.119.tgz",
"integrity": "sha512-WasbGFXEB+VQWXlo6IpW3oUv73Yuau1Ig4AZF/m13tXcTKnMpc/mHjpztIlz4+BM9FG9BHQkEXiPto3bKduQUg==",
"license": "MIT"
"version": "0.38.8",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.8.tgz",
"integrity": "sha512-xuRXPD44FcbKHrQK15FS1HFlMRNJtsaZou/SVws18vQ7zHqmlxyDktMkZpyvD6gE2ctGOVYC/jUyoMMAyBWfcw==",
"license": "MIT",
"workspaces": [
"scripts/actions/documentation"
]
},
"node_modules/discord.js": {
"version": "14.18.0",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.18.0.tgz",
"integrity": "sha512-SvU5kVUvwunQhN2/+0t55QW/1EHfB1lp0TtLZUSXVHDmyHTrdOj5LRKdR0zLcybaA15F+NtdWuWmGOX9lE+CAw==",
"version": "14.20.0",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.20.0.tgz",
"integrity": "sha512-5fRTptK2vpuz+bTuAEUQLSo/3AgCSLHl6Mm9+/ofb+8cbbnjWllhtaqRBq7XcpzlBnfNEugKv8HvCwcOtIHpCg==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/builders": "^1.10.1",
"@discordjs/builders": "^1.11.2",
"@discordjs/collection": "1.5.3",
"@discordjs/formatters": "^0.6.0",
"@discordjs/rest": "^2.4.3",
"@discordjs/formatters": "^0.6.1",
"@discordjs/rest": "^2.5.1",
"@discordjs/util": "^1.1.1",
"@discordjs/ws": "^1.2.1",
"@discordjs/ws": "^1.2.3",
"@sapphire/snowflake": "3.5.3",
"discord-api-types": "^0.37.119",
"discord-api-types": "^0.38.1",
"fast-deep-equal": "3.1.3",
"lodash.snakecase": "4.1.1",
"magic-bytes.js": "^1.10.0",
"tslib": "^2.6.3",
"undici": "6.21.1"
"undici": "6.21.3"
},
"engines": {
"node": ">=18"
@@ -1595,9 +1530,9 @@
}
},
"node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
"version": "16.5.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
@@ -1726,15 +1661,6 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/generic-pool": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
"integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/get-tsconfig": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz",
@@ -1900,9 +1826,9 @@
"license": "ISC"
},
"node_modules/magic-bytes.js": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz",
"integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==",
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz",
"integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==",
"license": "MIT"
},
"node_modules/magic-string": {
@@ -2003,9 +1929,9 @@
}
},
"node_modules/ollama": {
"version": "0.5.14",
"resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.14.tgz",
"integrity": "sha512-pvOuEYa2WkkAumxzJP0RdEYHkbZ64AYyyUszXVX7ruLvk5L+EiO2G71da2GqEQ4IAk4j6eLoUbGk5arzFT1wJA==",
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.15.tgz",
"integrity": "sha512-TSaZSJyP7MQJFjSmmNsoJiriwa3U+/UJRw6+M8aucs5dTsaWNZsBIGpDb5rXnW6nXxJBB/z79gZY8IaiIQgelQ==",
"license": "MIT",
"dependencies": {
"whatwg-fetch": "^3.6.20"
@@ -2098,23 +2024,6 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/redis": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz",
"integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==",
"license": "MIT",
"workspaces": [
"./packages/*"
],
"dependencies": {
"@redis/bloom": "1.2.0",
"@redis/client": "1.6.0",
"@redis/graph": "1.1.1",
"@redis/json": "1.0.7",
"@redis/search": "1.2.0",
"@redis/time-series": "1.1.0"
}
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
@@ -2519,9 +2428,9 @@
}
},
"node_modules/undici": {
"version": "6.21.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
"integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==",
"version": "6.21.3",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
"license": "MIT",
"engines": {
"node": ">=18.17"
@@ -2843,9 +2752,9 @@
}
},
"node_modules/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
"version": "8.18.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@@ -2863,12 +2772,6 @@
}
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"license": "ISC"
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "discord-ollama",
"version": "0.8.5",
"version": "0.8.7",
"description": "Ollama Integration into discord",
"main": "build/index.js",
"exports": "./build/index.js",
@@ -13,24 +13,22 @@
"client": "npm run build && npm run prod",
"clean": "docker compose down && docker rmi $(docker images | grep kevinthedang | tr -s ' ' | cut -d ' ' -f 3) && docker rmi $(docker images --filter \"dangling=true\" -q --no-trunc)",
"start": "docker compose build --no-cache && docker compose up -d",
"docker:clean": "docker rm -f discord && docker rm -f ollama && docker rm -f redis && docker network prune -f && docker rmi $(docker images | grep kevinthedang | tr -s ' ' | cut -d ' ' -f 3) && docker rmi $(docker images --filter \"dangling=true\" -q --no-trunc)",
"docker:clean": "docker rm -f discord && docker rm -f ollama && docker network prune -f && docker rmi $(docker images | grep kevinthedang | tr -s ' ' | cut -d ' ' -f 3) && docker rmi $(docker images --filter \"dangling=true\" -q --no-trunc)",
"docker:network": "docker network create --subnet=172.18.0.0/16 ollama-net",
"docker:build": "docker build --no-cache -t kevinthedang/discord-ollama:$(node -p \"require('./package.json').version\") .",
"docker:build-latest": "docker build --no-cache -t kevinthedang/discord-ollama:latest .",
"docker:client": "docker run -d -v discord:/src/app --name discord --network ollama-net --ip 172.18.0.3 kevinthedang/discord-ollama:$(node -p \"require('./package.json').version\")",
"docker:redis": "docker run -d -v redis:/root/.redis -p 6379:6379 --name redis --network ollama-net --ip 172.18.0.4 redis:latest",
"docker:ollama": "docker run -d --gpus=all -v ollama:/root/.ollama -p 11434:11434 --name ollama --network ollama-net --ip 172.18.0.2 ollama/ollama:latest",
"docker:ollama-cpu": "docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama --network ollama-net --ip 172.18.0.2 ollama/ollama:latest",
"docker:start": "docker network prune -f && npm run docker:network && npm run docker:build && npm run docker:redis && npm run docker:client && npm run docker:ollama",
"docker:start-cpu": "docker network prune -f && npm run docker:network && npm run docker:build && npm run docker:redis && npm run docker:client && npm run docker:ollama-cpu"
"docker:start": "docker network prune -f && npm run docker:network && npm run docker:build && npm run docker:client && npm run docker:ollama",
"docker:start-cpu": "docker network prune -f && npm run docker:network && npm run docker:build && npm run docker:client && npm run docker:ollama-cpu"
},
"author": "Kevin Dang",
"license": "ISC",
"dependencies": {
"discord.js": "^14.18.0",
"dotenv": "^16.4.7",
"ollama": "^0.5.14",
"redis": "^4.7.0"
"discord.js": "^14.20.0",
"dotenv": "^16.5.0",
"ollama": "^0.5.15"
},
"devDependencies": {
"@types/node": "^22.13.14",

View File

@@ -1,6 +1,5 @@
import { Client, GatewayIntentBits } from 'discord.js'
import { Ollama } from 'ollama'
import { createClient } from 'redis'
import { Queue } from './queues/queue.js'
import { UserMessage, registerEvents } from './utils/index.js'
import Events from './events/index.js'
@@ -16,11 +15,6 @@ const client = new Client({
]
})
// initialize connection to redis
const redis = createClient({
url: `redis://${Keys.redisHost}:${Keys.redisPort}`,
})
// initialize connection to ollama container
export const ollama = new Ollama({
host: `http://${Keys.ipAddress}:${Keys.portAddress}`,
@@ -29,19 +23,11 @@ export const ollama = new Ollama({
// Create Queue managed by Events
const messageHistory: Queue<UserMessage> = new Queue<UserMessage>
// register all events
registerEvents(client, Events, messageHistory, ollama, Keys.defaultModel)
// Create Channel History Queue managed by Events
const channelMessageHistory: Queue<UserMessage> = new Queue<UserMessage>
// Try to connect to redis
try {
await redis.connect()
console.log('[Redis] Successfully Connected')
} catch(error) {
console.error('[Redis] Connection Error. See error below:\n', error)
console.warn('[Redis] Failed to connect to Redis Database, using local system')
// TODO: create boolean flag that will probably be used in messageCreate.ts if redis database is down
// When implementing this boolean flag, move connection to database BEFORE the registerEvents method
}
// register all events
registerEvents(client, Events, messageHistory, channelMessageHistory, ollama, Keys.defaultModel)
// Try to log in the client
await client.login(Keys.clientToken)

View File

@@ -1,4 +1,4 @@
import { Client, CommandInteraction, ApplicationCommandOptionType, MessageFlags } from 'discord.js'
import { Client, ChatInputCommandInteraction, ApplicationCommandOptionType, MessageFlags } from 'discord.js'
import { openConfig, SlashCommand, UserCommand } from '../utils/index.js'
export const Capacity: SlashCommand = {
@@ -16,14 +16,14 @@ export const Capacity: SlashCommand = {
],
// Query for message information and set the style
run: async (client: Client, interaction: CommandInteraction) => {
run: async (client: Client, interaction: ChatInputCommandInteraction) => {
// fetch channel and message
const channel = await client.channels.fetch(interaction.channelId)
if (!channel || !UserCommand.includes(channel.type)) return
// set state of bot chat features
openConfig(`${interaction.user.username}-config.json`, interaction.commandName,
interaction.options.get('context-capacity')?.value
interaction.options.getNumber('context-capacity')
)
interaction.reply({

View File

@@ -1,4 +1,4 @@
import { ApplicationCommandOptionType, Client, CommandInteraction, MessageFlags } from 'discord.js'
import { ApplicationCommandOptionType, ChatInputCommandInteraction, Client, CommandInteraction, MessageFlags } from 'discord.js'
import { UserCommand, SlashCommand } from '../utils/index.js'
import { ollama } from '../client.js'
import { ModelResponse } from 'ollama'
@@ -18,11 +18,11 @@ export const DeleteModel: SlashCommand = {
],
// Delete Model locally stored
run: async (client: Client, interaction: CommandInteraction) => {
run: async (client: Client, interaction: ChatInputCommandInteraction) => {
// defer reply to avoid timeout
await interaction.deferReply()
const modelInput: string = interaction.options.get('model-name')!!.value as string
let modelExists: boolean
const modelInput: string = interaction.options.getString('model-name') as string
let ollamaOffline: boolean = false
// fetch channel and message
const channel = await client.channels.fetch(interaction.channelId)
@@ -38,10 +38,15 @@ export const DeleteModel: SlashCommand = {
}
// check if model exists
try {
modelExists = await ollama.list()
.then(response => response.models.some((model: ModelResponse) => model.name.startsWith(modelInput)))
} catch (error) {
const modelExists = await ollama.list()
.then(response => response.models.some((model: ModelResponse) => model.name.startsWith(modelInput)))
.catch(error => {
ollamaOffline = true
console.error(`[Command: delete-model] Failed to connect with Ollama service. Error: ${error.message}`)
})
// Validate for any issue or if service is running
if (ollamaOffline) {
interaction.editReply({
content: `The Ollama service is not running. Please turn on/download the [service](https://ollama.com/).`
})

View File

@@ -1,4 +1,4 @@
import { Client, CommandInteraction, ApplicationCommandOptionType, MessageFlags } from 'discord.js'
import { Client, ChatInputCommandInteraction, ApplicationCommandOptionType, MessageFlags } from 'discord.js'
import { AdminCommand, openConfig, SlashCommand } from '../utils/index.js'
export const Disable: SlashCommand = {
@@ -16,7 +16,7 @@ export const Disable: SlashCommand = {
],
// Query for message information and set the style
run: async (client: Client, interaction: CommandInteraction) => {
run: async (client: Client, interaction: ChatInputCommandInteraction) => {
// fetch channel and message
const channel = await client.channels.fetch(interaction.channelId)
if (!channel || !AdminCommand.includes(channel.type)) return
@@ -32,11 +32,11 @@ export const Disable: SlashCommand = {
// set state of bot chat features
openConfig(`${interaction.guildId}-config.json`, interaction.commandName,
interaction.options.get('enabled')?.value
interaction.options.getBoolean('enabled')
)
interaction.reply({
content: `${client.user?.username} is now **${interaction.options.get('enabled')?.value ? "enabled" : "disabled"}**.`,
content: `${client.user?.username} is now **${interaction.options.getBoolean('enabled') ? "enabled" : "disabled"}**.`,
flags: MessageFlags.Ephemeral
})
}

View File

@@ -1,4 +1,4 @@
import { ApplicationCommandOptionType, Client, CommandInteraction, MessageFlags } from 'discord.js'
import { ApplicationCommandOptionType, Client, ChatInputCommandInteraction, MessageFlags } from 'discord.js'
import { openConfig, SlashCommand, UserCommand } from '../utils/index.js'
export const MessageStream: SlashCommand = {
@@ -16,18 +16,18 @@ export const MessageStream: SlashCommand = {
],
// change preferences based on command
run: async (client: Client, interaction: CommandInteraction) => {
run: async (client: Client, interaction: ChatInputCommandInteraction) => {
// verify channel
const channel = await client.channels.fetch(interaction.channelId)
if (!channel || !UserCommand.includes(channel.type)) return
// save value to json and write to it
openConfig(`${interaction.user.username}-config.json`, interaction.commandName,
interaction.options.get('stream')?.value
interaction.options.getBoolean('stream')
)
interaction.reply({
content: `Message streaming is now set to: \`${interaction.options.get('stream')?.value}\``,
content: `Message streaming is now set to: \`${interaction.options.getBoolean('stream')}\``,
flags: MessageFlags.Ephemeral
})
}

View File

@@ -1,4 +1,4 @@
import { ApplicationCommandOptionType, Client, CommandInteraction, MessageFlags } from "discord.js"
import { ApplicationCommandOptionType, Client, ChatInputCommandInteraction, MessageFlags } from "discord.js"
import { ollama } from "../client.js"
import { ModelResponse } from "ollama"
import { UserCommand, SlashCommand } from "../utils/index.js"
@@ -18,11 +18,11 @@ export const PullModel: SlashCommand = {
],
// Pull for model from Ollama library
run: async (client: Client, interaction: CommandInteraction) => {
run: async (client: Client, interaction: ChatInputCommandInteraction) => {
// defer reply to avoid timeout
await interaction.deferReply()
const modelInput: string = interaction.options.get('model-to-pull')!!.value as string
let modelExists: boolean
const modelInput: string = interaction.options.getString('model-to-pull') as string
let ollamaOffline: boolean = false
// fetch channel and message
const channel = await client.channels.fetch(interaction.channelId)
@@ -38,10 +38,15 @@ export const PullModel: SlashCommand = {
}
// check if model was already pulled, if the ollama service isn't running throw error
try {
modelExists = await ollama.list()
.then(response => response.models.some((model: ModelResponse) => model.name.startsWith(modelInput)))
} catch (error) {
const modelExists = await ollama.list()
.then(response => response.models.some((model: ModelResponse) => model.name.startsWith(modelInput)))
.catch(error => {
ollamaOffline = true
console.error(`[Command: pull-model] Failed to connect with Ollama service. Error: ${error.message}`)
})
// Validate for any issue or if service is running
if (ollamaOffline) {
interaction.editReply({
content: `The Ollama service is not running. Please turn on/download the [service](https://ollama.com/).`
})

View File

@@ -1,4 +1,4 @@
import { ApplicationCommandOptionType, Client, CommandInteraction } from "discord.js"
import { ApplicationCommandOptionType, Client, ChatInputCommandInteraction } from "discord.js"
import { ollama } from "../client.js"
import { ModelResponse } from "ollama"
import { openConfig, UserCommand, SlashCommand } from "../utils/index.js"
@@ -18,10 +18,10 @@ export const SwitchModel: SlashCommand = {
],
// Switch user preferred model if available in local library
run: async (client: Client, interaction: CommandInteraction) => {
run: async (client: Client, interaction: ChatInputCommandInteraction) => {
await interaction.deferReply()
const modelInput: string = interaction.options.get('model-to-use')!!.value as string
const modelInput: string = interaction.options.getString('model-to-use') as string
// fetch channel and message
const channel = await client.channels.fetch(interaction.channelId)
@@ -45,6 +45,9 @@ export const SwitchModel: SlashCommand = {
}
}
})
.catch(error => {
console.error(`[Command: switch-model] Failed to connect with Ollama service. Error: ${error.message}`)
})
// todo: problem can be here if async messes up
if (switchSuccess) {
// set model now that it exists

View File

@@ -1,5 +1,5 @@
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 {
getChannelInfo, getServerConfig, getUserConfig, openChannelInfo,
openConfig, UserConfig, getAttachmentData, getTextFileAttachmentData
@@ -11,7 +11,7 @@ import {
*
* @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
let cleanedMessage = clean(message.content, clientId)
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
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
if (!message.mentions.has(clientId)) return
@@ -139,15 +194,6 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
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.`)
// 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']
// set up new queue

View File

@@ -4,9 +4,7 @@ export const Keys = {
clientToken: getEnvVar('CLIENT_TOKEN'),
ipAddress: getEnvVar('OLLAMA_IP', '127.0.0.1'), // default ollama ip if none
portAddress: getEnvVar('OLLAMA_PORT', '11434'), // default ollama port if none
defaultModel: getEnvVar('MODEL', 'llama3.2'),
redisHost: getEnvVar('REDIS_IP', '172.18.0.4'), // default redis host if none
redisPort: parseInt(getEnvVar('REDIS_PORT', '6379')) // default redis port if none
defaultModel: getEnvVar('MODEL', 'llama3.2')
} as const // readonly keys
export default Keys

View File

@@ -1,4 +1,4 @@
import { CommandInteraction, ChatInputApplicationCommandData, Client, ApplicationCommandOption } from 'discord.js'
import { ChatInputCommandInteraction, ChatInputApplicationCommandData, Client, ApplicationCommandOption } from 'discord.js'
/**
* interface for how slash commands should be run
@@ -6,7 +6,7 @@ import { CommandInteraction, ChatInputApplicationCommandData, Client, Applicatio
export interface SlashCommand extends ChatInputApplicationCommandData {
run: (
client: Client,
interaction: CommandInteraction,
interaction: ChatInputCommandInteraction,
options?: ApplicationCommandOption[]
) => void
}

View File

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

View File

@@ -56,6 +56,39 @@ export async function clearChannelInfo(filename: string, channel: TextChannel, u
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`)
}
}
/**
* Method to open the channel history
*

View File

@@ -56,6 +56,10 @@ export async function normalMessage(
response = await blockResponse(params)
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
if (result.length > 2000) {
sentMessage.edit(result.slice(0, 2000))
@@ -85,3 +89,7 @@ export async function normalMessage(
// return the string representation of ollama query response
return result
}
function hasThinking(message: string): boolean {
return /<think>[\s\S]*?<\/think>/i.test(message)
}

View File

@@ -5,16 +5,9 @@ import { describe, expect, it, vi } from 'vitest'
import commands from '../src/commands/index.js'
/**
* Mocking redis found in client.ts because of the commands
* Mocking client.ts because of the commands
*/
vi.mock('../src/client.js', () => ({
redis: {
createClient: vi.fn(),
connect: vi.fn(),
get: vi.fn(),
set: vi.fn()
}
}))
vi.mock('../src/client.js', () => ({}))
/**
* Commands test suite, tests the commands object