9 Commits

Author SHA1 Message Date
snyk-bot
9ad36bb144 fix: upgrade redis from 4.7.0 to 4.7.1
Snyk has created this PR to upgrade redis from 4.7.0 to 4.7.1.

See this package in npm:
redis

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
2025-06-19 13:56:52 -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
Kevin Dang
a5faca87aa Fix: missing model env for docker (#172) 2025-04-18 19:42:18 -07:00
Jonathan Smoley
4c96b3863a Upgrade Dependencies (#164) 2025-03-28 10:00:50 -07:00
Kevin Dang
40783818b9 Upgrade Npm Packages (#159)
* Update: upgrade packages

* Update: add in all packages

* Update: fix whitespace in events

---------

Co-authored-by: JT2M0L3Y <jtsmoley@icloud.com>
2025-02-23 21:00:53 -07:00
Kevin Dang
ed0d8600df Deploy Badge (#163) 2025-02-22 15:23:01 -08:00
Kevin Dang
03939ef562 Server Deployment Scripts (#162) 2025-02-22 14:06:14 -08:00
19 changed files with 832 additions and 1085 deletions

View File

@@ -33,6 +33,7 @@ jobs:
echo CLIENT_TOKEN = ${{ secrets.BOT_TOKEN }} >> .env
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
@@ -61,6 +62,7 @@ jobs:
echo CLIENT_TOKEN = ${{ secrets.BOT_TOKEN }} >> .env
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

View File

@@ -30,12 +30,13 @@ jobs:
echo CLIENT_TOKEN = ${{ secrets.BOT_TOKEN }} >> .env
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: |
LINE_PCT=$(npm run test:coverage | tail -2 | head -1 | awk '{print $3}')
LINE_PCT=$(npm run coverage | tail -2 | head -1 | awk '{print $3}')
echo "COVERAGE=$LINE_PCT" >> $GITHUB_ENV
- name: Upload Code Coverage

133
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,133 @@
name: Deploy
run-name: Deploy Application Latest
on:
push:
tags:
- 'v*'
jobs:
Deploy-Application:
runs-on: self-hosted
environment: deploy
timeout-minutes: 5
steps:
- name: Checkout Repo
uses: actions/checkout@v4
# Generate Secret File for Compose case
- name: Create Environment Variables
run: |
touch .env
echo CLIENT_TOKEN = ${{ secrets.CLIENT }} >> .env
echo OLLAMA_IP = ${{ secrets.OLLAMA_IP }} >> .env
echo OLLAMA_PORT = ${{ secrets.OLLAMA_PORT }} >> .env
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: |
if [ -d "${{ secrets.PATH }}" ]; then
echo "Directory exists, deleting old version..."
rm -rf ${{ secrets.PATH }}
else
echo "Directory does not exist."
fi
- name: Clone Repo onto Server
run: |
git clone https://github.com/kevinthedang/discord-ollama.git ${{ secrets.PATH }}
cd ${{ secrets.PATH }}
- name: Install nvm and Node.js lts/jod
run: |
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
echo "NVM installed successfully."
nvm install lts/jod
nvm alias default lts/jod
node -v
npm -v
- name: Build Application
run: |
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
npm install
IMAGE="kevinthedang/discord-ollama"
REDIS="redis"
OLLAMA="ollama/ollama"
if docker images | grep -q $IMAGE; then
IMAGE_ID=$(docker images -q $IMAGE)
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 $IMAGE"
fi
docker rmi $IMAGE_ID
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")
if [ ! -z "$CONTAINER_IDS" ]; then
# Stop and remove the running containers
docker stop $CONTAINER_IDS
echo "Stopped and removed the containers using the image $OLLAMA"
fi
docker rmi $IMAGE_ID
echo "Old $OLLAMA Image Removed"
fi
docker network prune -f
docker system prune -a -f
npm run docker:build-latest
- name: Start Application
run: |
docker network create --subnet=${{ secrets.SUBNET_ADDRESS }}/16 ollama-net || true
docker run --rm -d \
-v ollama:/root/.ollama \
-p ${{ secrets.OLLAMA_PORT }}:${{ secrets.OLLAMA_PORT }} \
--name ollama \
--network ollama-net \
--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 \
--network ollama-net \
--ip ${{ secrets.DISCORD_IP }} \
kevinthedang/discord-ollama

View File

@@ -1,50 +0,0 @@
name: Deploy
run-name: Release Docker Image
on:
push:
tags:
- 'v*'
jobs:
Release-Docker-Image:
runs-on: ubuntu-latest
environment: release
timeout-minutes: 3
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Node Environment lts/jod
uses: actions/setup-node@v4
with:
node-version: lts/jod
cache: "npm"
- name: Create Environment Variables
run: |
touch .env
echo CLIENT_TOKEN = NOT_REAL_TOKEN >> .env
echo OLLAMA_IP = ${{ secrets.OLLAMA_IP }} >> .env
echo OLLAMA_PORT = ${{ secrets.OLLAMA_PORT }} >> .env
echo REDIS_IP = ${{ secrets.REDIS_IP }} >> .env
echo REDIS_PORT = ${{ secrets.REDIS_PORT }} >> .env
- name: Get Version from package.json
run: echo "VERSION=$(jq -r '.version' package.json)" >> $GITHUB_ENV
- name: Build Image
run: |
npm run docker:build
- name: Build Image as Latest
run: |
npm run docker:build-latest
- name: Log into Docker
run: |
docker login --username ${{ vars.DOCKER_USER }} --password ${{ secrets.DOCKER_PASS }}
- name: Release Docker Image
run: |
docker push ${{ vars.DOCKER_USER }}/discord-ollama:${{ env.VERSION }}
docker push ${{ vars.DOCKER_USER }}/discord-ollama:latest

View File

@@ -41,9 +41,10 @@ jobs:
echo CLIENT_TOKEN = ${{ secrets.BOT_TOKEN }} >> .env
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: |
npm run test:run
npm run tests

View File

@@ -5,7 +5,7 @@
<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/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/release.yml"><img alt="Release Status" src="https://github.com/kevinthedang/discord-ollama/actions/workflows/release.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/coverage.yml"><img alt="Code Coverage" src="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/kevinthedang/bc7b5dcfa16561ab02bb3df67a99b22d/raw/coverage.json"></a>
</div>

View File

@@ -7,11 +7,12 @@ services:
build: ./ # find docker file in designated path
container_name: discord
restart: always # rebuild container always
image: kevinthedang/discord-ollama:0.8.3
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:

1269
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,12 @@
{
"name": "discord-ollama",
"version": "0.8.3",
"version": "0.8.5",
"description": "Ollama Integration into discord",
"main": "build/index.js",
"exports": "./build/index.js",
"scripts": {
"test:run": "vitest run",
"test:coverage": "vitest run --coverage",
"tests": "vitest run",
"coverage": "vitest run --coverage",
"watch": "tsx watch src",
"build": "tsc",
"prod": "node .",
@@ -27,18 +27,18 @@
"author": "Kevin Dang",
"license": "ISC",
"dependencies": {
"discord.js": "^14.17.3",
"dotenv": "^16.4.7",
"ollama": "^0.5.11",
"redis": "^4.7.0"
"discord.js": "^14.18.0",
"dotenv": "^16.5.0",
"ollama": "^0.5.15",
"redis": "^4.7.1"
},
"devDependencies": {
"@types/node": "^22.10.2",
"@vitest/coverage-v8": "^2.1.8",
"@types/node": "^22.13.14",
"@vitest/coverage-v8": "^3.0.9",
"ts-node": "^10.9.2",
"tsx": "^4.19.2",
"typescript": "^5.7.2",
"vitest": "^2.1.4"
"tsx": "^4.19.3",
"typescript": "^5.8.2",
"vitest": "^3.0.4"
},
"type": "module",
"engines": {

View File

@@ -34,10 +34,14 @@ registerEvents(client, Events, messageHistory, ollama, Keys.defaultModel)
// Try to connect to redis
await redis.connect()
.then(() => console.log('[Redis] Connected'))
.catch((error) => {
console.error('[Redis] Connection Error', error)
process.exit(1)
.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

View File

@@ -37,8 +37,20 @@ export const DeleteModel: SlashCommand = {
}
// check if model exists
const modelExists: boolean = await ollama.list()
const modelExists = await ollama.list()
.then(response => response.models.some((model: ModelResponse) => model.name.startsWith(modelInput)))
.catch(error => {
console.error(`[Command: delete-model] Failed to connect with Ollama service. Error: ${error.message}`)
})
// Validate for any issue or if service is running
if (!modelExists) {
interaction.editReply({
content: `The Ollama service is not running. Please turn on/download the [service](https://ollama.com/).`
})
return
}
try {
// call ollama to delete model

View File

@@ -36,9 +36,21 @@ export const PullModel: SlashCommand = {
return
}
// check if model was already pulled
const modelExists: boolean = await ollama.list()
// check if model was already pulled, if the ollama service isn't running throw error
const modelExists = await ollama.list()
.then(response => response.models.some((model: ModelResponse) => model.name.startsWith(modelInput)))
.catch(error => {
console.error(`[Command: pull-model] Failed to connect with Ollama service. Error: ${error.message}`)
})
// Validate for any issue or if service is running
if (!modelExists) {
interaction.editReply({
content: `The Ollama service is not running. Please turn on/download the [service](https://ollama.com/).`
})
return
}
try {
// call ollama to pull desired model

View File

@@ -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
@@ -56,10 +59,13 @@ export const SwitchModel: SlashCommand = {
interaction.editReply({
content: `Could not find **${modelInput}** in local model library.\n\nPlease contact an server admin for access to this model.`
})
} catch (error) {
} catch (error: any) {
// could not resolve user model switch
if (error.message.includes("fetch failed") as string)
error.message = "The Ollama service is not running. Please turn on/download the [service](https://ollama.com/)."
interaction.editReply({
content: `Unable to switch user preferred model to **${modelInput}**.\n\n${error}\n\nPossible solution is to request an server admin run \`/pull-model ${modelInput}\` and try again.`
content: `Unable to switch user preferred model to **${modelInput}**.\n\n${error.message}`
})
return
}

View File

@@ -16,5 +16,4 @@ export default event(Events.InteractionCreate, async ({ log, client }, interacti
// the command exists, execute it
command.run(client, interaction)
}
)
})

View File

@@ -71,9 +71,8 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
userConfig = await new Promise((resolve, reject) => {
getUserConfig(`${message.author.username}-config.json`, (config) => {
if (config === undefined) {
openConfig(`${message.author.username}-config.json`, 'message-style', false)
openConfig(`${message.author.username}-config.json`, 'switch-model', defaultModel)
reject(new Error('No User Preferences is set up.\n\nCreating preferences file with \`message-style\` set as \`false\` for regular message style.\nPlease try chatting again.'))
reject(new Error(`No User Preferences is set up.\n\nCreating new preferences file for ${message.author.username}\nPlease try chatting again.`))
return
}
@@ -190,5 +189,4 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
msgHist.pop() // remove message because of failure
message.reply(`**Error Occurred:**\n\n**Reason:** *${error.message}*`)
}
}
)
})

View File

@@ -14,5 +14,4 @@ export default event(Events.ClientReady, ({ log }, client) => {
})
log(`Logged in as ${client.user.username}.`)
}
)
})

View File

@@ -37,5 +37,4 @@ export default event(Events.ThreadDelete, async ({ log }, thread: ThreadChannel)
} catch (error) {
log(`Issue deleting user history files from ${thread.id}`)
}
}
)
})

View File

@@ -17,7 +17,7 @@ export class Queue<T> implements IQueue<T> {
* Set up Queue
* @param capacity max length of queue
*/
constructor(public capacity: number = 5) {}
constructor(public capacity: number = 5) { }
/**
* Put item in front of queue

View File

@@ -74,9 +74,10 @@ export async function normalMessage(
}
} catch (error: any) {
console.log(`[Util: messageNormal] Error creating message: ${error.message}`)
if (error.message.includes('try pulling it first'))
sentMessage.edit(`**Response generation failed.**\n\nReason: You do not have the ${model} downloaded. Ask an admin to pull it using the \`pull-model\` command.`)
else
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.`
sentMessage.edit(`**Response generation failed.**\n\nReason: ${error.message}`)
}
})