mirror of
https://github.com/kevinthedang/discord-ollama.git
synced 2025-12-12 11:56:06 -05:00
Compare commits
1 Commits
v0.8.7
...
9ad36bb144
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ad36bb144 |
@@ -13,3 +13,7 @@ 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
|
||||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -34,6 +34,8 @@ 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
|
||||
@@ -61,6 +63,8 @@ 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: |
|
||||
@@ -68,8 +72,8 @@ jobs:
|
||||
|
||||
- name: Check Images Exist
|
||||
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' | docker images | grep -qE 'redis') || exit 1
|
||||
|
||||
- name: Check Containers Exist
|
||||
run: |
|
||||
(docker ps | grep -q 'ollama' && docker ps | grep -q 'discord') || exit 1
|
||||
(docker ps | grep -q 'ollama' && docker ps | grep -q 'discord' && docker ps | grep -q 'redis') || exit 1
|
||||
|
||||
2
.github/workflows/coverage.yml
vendored
2
.github/workflows/coverage.yml
vendored
@@ -31,6 +31,8 @@ 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: |
|
||||
|
||||
24
.github/workflows/deploy.yml
vendored
24
.github/workflows/deploy.yml
vendored
@@ -24,6 +24,8 @@ 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: |
|
||||
@@ -57,6 +59,7 @@ jobs:
|
||||
npm install
|
||||
|
||||
IMAGE="kevinthedang/discord-ollama"
|
||||
REDIS="redis"
|
||||
OLLAMA="ollama/ollama"
|
||||
|
||||
if docker images | grep -q $IMAGE; then
|
||||
@@ -72,6 +75,19 @@ 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")
|
||||
@@ -101,6 +117,14 @@ 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 \
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -42,6 +42,8 @@ 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: |
|
||||
|
||||
29
README.md
29
README.md
@@ -14,28 +14,19 @@
|
||||
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 and Server Preferences
|
||||
* [x] Message Persistance
|
||||
* [x] User Preferences on Chat
|
||||
* [x] Message Persistance on Channels and Threads
|
||||
* [x] Threads
|
||||
* [x] Channels
|
||||
* [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] 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
|
||||
* [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
|
||||
|
||||
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)
|
||||
@@ -63,6 +54,8 @@ 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)
|
||||
|
||||
@@ -7,12 +7,14 @@ services:
|
||||
build: ./ # find docker file in designated path
|
||||
container_name: discord
|
||||
restart: always # rebuild container always
|
||||
image: kevinthedang/discord-ollama:0.8.7
|
||||
image: kevinthedang/discord-ollama:0.8.5
|
||||
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}
|
||||
@@ -35,6 +37,19 @@ 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:
|
||||
@@ -47,3 +62,4 @@ networks:
|
||||
volumes:
|
||||
ollama:
|
||||
discord:
|
||||
redis:
|
||||
|
||||
@@ -43,11 +43,13 @@ 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`, 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`, `REDIS_IP`, `REDIS_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.
|
||||
|
||||
201
package-lock.json
generated
201
package-lock.json
generated
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"name": "discord-ollama",
|
||||
"version": "0.8.7",
|
||||
"version": "0.8.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "discord-ollama",
|
||||
"version": "0.8.7",
|
||||
"version": "0.8.5",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"discord.js": "^14.20.0",
|
||||
"discord.js": "^14.18.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"ollama": "^0.5.15"
|
||||
"ollama": "^0.5.15",
|
||||
"redis": "^4.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.13.14",
|
||||
@@ -125,15 +126,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/builders": {
|
||||
"version": "1.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.11.2.tgz",
|
||||
"integrity": "sha512-F1WTABdd8/R9D1icJzajC4IuLyyS8f3rTOz66JsSI3pKvpCAtsMBweu8cyNYsIyvcrKAVn9EPK+Psoymq+XC0A==",
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.10.1.tgz",
|
||||
"integrity": "sha512-OWo1fY4ztL1/M/DUyRPShB4d/EzVfuUvPTRRHRIt/YxBrUYSz0a+JicD5F5zHFoNs2oTuWavxCOVFV1UljHTng==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/formatters": "^0.6.1",
|
||||
"@discordjs/formatters": "^0.6.0",
|
||||
"@discordjs/util": "^1.1.1",
|
||||
"@sapphire/shapeshift": "^4.0.0",
|
||||
"discord-api-types": "^0.38.1",
|
||||
"discord-api-types": "^0.37.119",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"ts-mixer": "^6.0.4",
|
||||
"tslib": "^2.6.3"
|
||||
@@ -155,12 +156,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/formatters": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.1.tgz",
|
||||
"integrity": "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg==",
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.0.tgz",
|
||||
"integrity": "sha512-YIruKw4UILt/ivO4uISmrGq2GdMY6EkoTtD0oS0GvkJFRZbTSdPhzYiUILbJ/QslsvC9H9nTgGgnarnIl4jMfw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"discord-api-types": "^0.38.1"
|
||||
"discord-api-types": "^0.37.114"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
@@ -170,9 +171,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/rest": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.5.1.tgz",
|
||||
"integrity": "sha512-Tg9840IneBcbrAjcGaQzHUJWFNq1MMWZjTdjJ0WS/89IffaNKc++iOvffucPxQTF/gviO9+9r8kEPea1X5J2Dw==",
|
||||
"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==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/collection": "^2.1.1",
|
||||
@@ -180,10 +181,10 @@
|
||||
"@sapphire/async-queue": "^1.5.3",
|
||||
"@sapphire/snowflake": "^3.5.3",
|
||||
"@vladfrangu/async_event_emitter": "^2.4.6",
|
||||
"discord-api-types": "^0.38.1",
|
||||
"discord-api-types": "^0.37.119",
|
||||
"magic-bytes.js": "^1.10.0",
|
||||
"tslib": "^2.6.3",
|
||||
"undici": "6.21.3"
|
||||
"undici": "6.21.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -217,18 +218,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/ws": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz",
|
||||
"integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.1.tgz",
|
||||
"integrity": "sha512-PBvenhZG56a6tMWF/f4P6f4GxZKJTBG95n7aiGSPTnodmz4N5g60t79rSIAq7ywMbv8A4jFtexMruH+oe51aQQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/collection": "^2.1.0",
|
||||
"@discordjs/rest": "^2.5.1",
|
||||
"@discordjs/rest": "^2.4.3",
|
||||
"@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.38.1",
|
||||
"discord-api-types": "^0.37.119",
|
||||
"tslib": "^2.6.2",
|
||||
"ws": "^8.17.0"
|
||||
},
|
||||
@@ -768,6 +769,65 @@
|
||||
"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.1",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz",
|
||||
"integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==",
|
||||
"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",
|
||||
@@ -1126,9 +1186,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz",
|
||||
"integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
@@ -1413,6 +1473,15 @@
|
||||
"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",
|
||||
@@ -1494,33 +1563,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/discord-api-types": {
|
||||
"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"
|
||||
]
|
||||
"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"
|
||||
},
|
||||
"node_modules/discord.js": {
|
||||
"version": "14.20.0",
|
||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.20.0.tgz",
|
||||
"integrity": "sha512-5fRTptK2vpuz+bTuAEUQLSo/3AgCSLHl6Mm9+/ofb+8cbbnjWllhtaqRBq7XcpzlBnfNEugKv8HvCwcOtIHpCg==",
|
||||
"version": "14.18.0",
|
||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.18.0.tgz",
|
||||
"integrity": "sha512-SvU5kVUvwunQhN2/+0t55QW/1EHfB1lp0TtLZUSXVHDmyHTrdOj5LRKdR0zLcybaA15F+NtdWuWmGOX9lE+CAw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^1.11.2",
|
||||
"@discordjs/builders": "^1.10.1",
|
||||
"@discordjs/collection": "1.5.3",
|
||||
"@discordjs/formatters": "^0.6.1",
|
||||
"@discordjs/rest": "^2.5.1",
|
||||
"@discordjs/formatters": "^0.6.0",
|
||||
"@discordjs/rest": "^2.4.3",
|
||||
"@discordjs/util": "^1.1.1",
|
||||
"@discordjs/ws": "^1.2.3",
|
||||
"@discordjs/ws": "^1.2.1",
|
||||
"@sapphire/snowflake": "3.5.3",
|
||||
"discord-api-types": "^0.38.1",
|
||||
"discord-api-types": "^0.37.119",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"lodash.snakecase": "4.1.1",
|
||||
"magic-bytes.js": "^1.10.0",
|
||||
"tslib": "^2.6.3",
|
||||
"undici": "6.21.3"
|
||||
"undici": "6.21.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -1661,6 +1726,15 @@
|
||||
"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",
|
||||
@@ -1826,9 +1900,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/magic-bytes.js": {
|
||||
"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==",
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz",
|
||||
"integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
@@ -2024,6 +2098,23 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/redis": {
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-4.7.1.tgz",
|
||||
"integrity": "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"./packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"@redis/bloom": "1.2.0",
|
||||
"@redis/client": "1.6.1",
|
||||
"@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",
|
||||
@@ -2428,9 +2519,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.21.3",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
|
||||
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
|
||||
"version": "6.21.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
|
||||
"integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
@@ -2752,9 +2843,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
|
||||
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
@@ -2772,6 +2863,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
||||
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "discord-ollama",
|
||||
"version": "0.8.7",
|
||||
"version": "0.8.5",
|
||||
"description": "Ollama Integration into discord",
|
||||
"main": "build/index.js",
|
||||
"exports": "./build/index.js",
|
||||
@@ -13,22 +13,24 @@
|
||||
"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 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 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: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: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"
|
||||
"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"
|
||||
},
|
||||
"author": "Kevin Dang",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"discord.js": "^14.20.0",
|
||||
"discord.js": "^14.18.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"ollama": "^0.5.15"
|
||||
"ollama": "^0.5.15",
|
||||
"redis": "^4.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.13.14",
|
||||
@@ -43,4 +45,4 @@
|
||||
"npm": ">=10.9.0",
|
||||
"node": ">=22.12.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
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'
|
||||
@@ -15,6 +16,11 @@ 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}`,
|
||||
@@ -23,11 +29,20 @@ export const ollama = new Ollama({
|
||||
// Create Queue managed by Events
|
||||
const messageHistory: Queue<UserMessage> = new Queue<UserMessage>
|
||||
|
||||
// Create Channel History Queue managed by Events
|
||||
const channelMessageHistory: Queue<UserMessage> = new Queue<UserMessage>
|
||||
|
||||
// register all events
|
||||
registerEvents(client, Events, messageHistory, channelMessageHistory, ollama, Keys.defaultModel)
|
||||
registerEvents(client, Events, messageHistory, ollama, Keys.defaultModel)
|
||||
|
||||
// Try to connect to redis
|
||||
await redis.connect()
|
||||
.then(response => {
|
||||
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
|
||||
})
|
||||
|
||||
// Try to log in the client
|
||||
await client.login(Keys.clientToken)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, ChatInputCommandInteraction, ApplicationCommandOptionType, MessageFlags } from 'discord.js'
|
||||
import { Client, CommandInteraction, 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: ChatInputCommandInteraction) => {
|
||||
run: async (client: Client, interaction: CommandInteraction) => {
|
||||
// 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.getNumber('context-capacity')
|
||||
interaction.options.get('context-capacity')?.value
|
||||
)
|
||||
|
||||
interaction.reply({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApplicationCommandOptionType, ChatInputCommandInteraction, Client, CommandInteraction, MessageFlags } from 'discord.js'
|
||||
import { ApplicationCommandOptionType, 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,10 @@ export const DeleteModel: SlashCommand = {
|
||||
],
|
||||
|
||||
// Delete Model locally stored
|
||||
run: async (client: Client, interaction: ChatInputCommandInteraction) => {
|
||||
run: async (client: Client, interaction: CommandInteraction) => {
|
||||
// defer reply to avoid timeout
|
||||
await interaction.deferReply()
|
||||
const modelInput: string = interaction.options.getString('model-name') as string
|
||||
let ollamaOffline: boolean = false
|
||||
const modelInput: string = interaction.options.get('model-name')!!.value as string
|
||||
|
||||
// fetch channel and message
|
||||
const channel = await client.channels.fetch(interaction.channelId)
|
||||
@@ -41,12 +40,11 @@ export const DeleteModel: SlashCommand = {
|
||||
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) {
|
||||
if (!modelExists) {
|
||||
interaction.editReply({
|
||||
content: `The Ollama service is not running. Please turn on/download the [service](https://ollama.com/).`
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, ChatInputCommandInteraction, ApplicationCommandOptionType, MessageFlags } from 'discord.js'
|
||||
import { Client, CommandInteraction, 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: ChatInputCommandInteraction) => {
|
||||
run: async (client: Client, interaction: CommandInteraction) => {
|
||||
// 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.getBoolean('enabled')
|
||||
interaction.options.get('enabled')?.value
|
||||
)
|
||||
|
||||
interaction.reply({
|
||||
content: `${client.user?.username} is now **${interaction.options.getBoolean('enabled') ? "enabled" : "disabled"}**.`,
|
||||
content: `${client.user?.username} is now **${interaction.options.get('enabled')?.value ? "enabled" : "disabled"}**.`,
|
||||
flags: MessageFlags.Ephemeral
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApplicationCommandOptionType, Client, ChatInputCommandInteraction, MessageFlags } from 'discord.js'
|
||||
import { ApplicationCommandOptionType, Client, CommandInteraction, 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: ChatInputCommandInteraction) => {
|
||||
run: async (client: Client, interaction: CommandInteraction) => {
|
||||
// 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.getBoolean('stream')
|
||||
interaction.options.get('stream')?.value
|
||||
)
|
||||
|
||||
interaction.reply({
|
||||
content: `Message streaming is now set to: \`${interaction.options.getBoolean('stream')}\``,
|
||||
content: `Message streaming is now set to: \`${interaction.options.get('stream')?.value}\``,
|
||||
flags: MessageFlags.Ephemeral
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApplicationCommandOptionType, Client, ChatInputCommandInteraction, MessageFlags } from "discord.js"
|
||||
import { ApplicationCommandOptionType, Client, CommandInteraction, MessageFlags } from "discord.js"
|
||||
import { ollama } from "../client.js"
|
||||
import { ModelResponse } from "ollama"
|
||||
import { UserCommand, SlashCommand } from "../utils/index.js"
|
||||
@@ -18,11 +18,10 @@ export const PullModel: SlashCommand = {
|
||||
],
|
||||
|
||||
// Pull for model from Ollama library
|
||||
run: async (client: Client, interaction: ChatInputCommandInteraction) => {
|
||||
run: async (client: Client, interaction: CommandInteraction) => {
|
||||
// defer reply to avoid timeout
|
||||
await interaction.deferReply()
|
||||
const modelInput: string = interaction.options.getString('model-to-pull') as string
|
||||
let ollamaOffline: boolean = false
|
||||
const modelInput: string = interaction.options.get('model-to-pull')!!.value as string
|
||||
|
||||
// fetch channel and message
|
||||
const channel = await client.channels.fetch(interaction.channelId)
|
||||
@@ -41,12 +40,11 @@ export const PullModel: SlashCommand = {
|
||||
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) {
|
||||
if (!modelExists) {
|
||||
interaction.editReply({
|
||||
content: `The Ollama service is not running. Please turn on/download the [service](https://ollama.com/).`
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApplicationCommandOptionType, Client, ChatInputCommandInteraction } from "discord.js"
|
||||
import { ApplicationCommandOptionType, Client, CommandInteraction } 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: ChatInputCommandInteraction) => {
|
||||
run: async (client: Client, interaction: CommandInteraction) => {
|
||||
await interaction.deferReply()
|
||||
|
||||
const modelInput: string = interaction.options.getString('model-to-use') 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)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TextChannel } from 'discord.js'
|
||||
import { event, Events, normalMessage, UserMessage, clean, addToChannelContext } from '../utils/index.js'
|
||||
import { event, Events, normalMessage, UserMessage, clean } 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, channelHistory, ollama, client, defaultModel }, message) => {
|
||||
export default event(Events.MessageCreate, async ({ log, msgHist, 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,61 +19,6 @@ export default event(Events.MessageCreate, async ({ log, msgHist, channelHistory
|
||||
// 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
|
||||
|
||||
@@ -194,6 +139,15 @@ export default event(Events.MessageCreate, async ({ log, msgHist, channelHistory
|
||||
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
|
||||
|
||||
@@ -4,7 +4,9 @@ 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')
|
||||
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
|
||||
} as const // readonly keys
|
||||
|
||||
export default Keys
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChatInputCommandInteraction, ChatInputApplicationCommandData, Client, ApplicationCommandOption } from 'discord.js'
|
||||
import { CommandInteraction, ChatInputApplicationCommandData, Client, ApplicationCommandOption } from 'discord.js'
|
||||
|
||||
/**
|
||||
* interface for how slash commands should be run
|
||||
@@ -6,7 +6,7 @@ import { ChatInputCommandInteraction, ChatInputApplicationCommandData, Client, A
|
||||
export interface SlashCommand extends ChatInputApplicationCommandData {
|
||||
run: (
|
||||
client: Client,
|
||||
interaction: ChatInputCommandInteraction,
|
||||
interaction: CommandInteraction,
|
||||
options?: ApplicationCommandOption[]
|
||||
) => void
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ export interface EventProps {
|
||||
client: Client,
|
||||
log: LogMethod,
|
||||
msgHist: Queue<UserMessage>,
|
||||
channelHistory: Queue<UserMessage>,
|
||||
ollama: Ollama,
|
||||
defaultModel: String
|
||||
}
|
||||
@@ -79,7 +78,6 @@ export function registerEvents(
|
||||
client: Client,
|
||||
events: Event[],
|
||||
msgHist: Queue<UserMessage>,
|
||||
channelHistory: Queue<UserMessage>,
|
||||
ollama: Ollama,
|
||||
defaultModel: String
|
||||
): void {
|
||||
@@ -90,7 +88,7 @@ export function registerEvents(
|
||||
|
||||
// Handle Errors, call callback, log errors as needed
|
||||
try {
|
||||
callback({ client, log, msgHist, channelHistory, ollama, defaultModel }, ...args)
|
||||
callback({ client, log, msgHist, ollama, defaultModel }, ...args)
|
||||
} catch (error) {
|
||||
log('[Uncaught Error]', error)
|
||||
}
|
||||
|
||||
@@ -56,39 +56,6 @@ 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
|
||||
*
|
||||
|
||||
@@ -56,10 +56,6 @@ 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))
|
||||
@@ -89,7 +85,3 @@ 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)
|
||||
}
|
||||
|
||||
@@ -5,9 +5,16 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
import commands from '../src/commands/index.js'
|
||||
|
||||
/**
|
||||
* Mocking client.ts because of the commands
|
||||
* Mocking redis found in client.ts because of the commands
|
||||
*/
|
||||
vi.mock('../src/client.js', () => ({}))
|
||||
vi.mock('../src/client.js', () => ({
|
||||
redis: {
|
||||
createClient: vi.fn(),
|
||||
connect: vi.fn(),
|
||||
get: vi.fn(),
|
||||
set: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
/**
|
||||
* Commands test suite, tests the commands object
|
||||
|
||||
Reference in New Issue
Block a user