Compare commits

...

9 Commits

Author SHA1 Message Date
Kevin Dang
a2c954136d Fix: redis workaround for local non docker 2025-05-08 22:02:26 -07:00
Kevin Dang
5820583609 Update: simplify npm command to run tests 2025-05-08 21:16:32 -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
Jonathan Smoley
456f70b9e1 Deprecated ephemeral field (#158)
* Update: ephemeral flag added in place of field

* Update: remove unused import

* Update: version increment

---------

Co-authored-by: Kevin Dang <kevinthedang_1@outlook.com>
2025-02-02 15:10:58 -08:00
Jonathan Smoley
5b542aca1a [Snyk] Upgrade discord.js from 14.16.3 to 14.17.3 (#155) 2025-01-31 16:23:31 -08:00
32 changed files with 752 additions and 1029 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.2
image: kevinthedang/discord-ollama:0.8.4
environment:
CLIENT_TOKEN: ${CLIENT_TOKEN}
OLLAMA_IP: ${OLLAMA_IP}
OLLAMA_PORT: ${OLLAMA_PORT}
MODEL: ${MODEL}
REDIS_IP: ${REDIS_IP}
REDIS_PORT: ${REDIS_PORT}
networks:
@@ -28,7 +29,6 @@ services:
networks:
ollama-net:
ipv4_address: ${OLLAMA_IP}
runtime: nvidia # use Nvidia Container Toolkit for GPU support
devices:
- /dev/nvidia0

1287
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.2",
"version": "0.8.4",
"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.16.3",
"discord.js": "^14.18.0",
"dotenv": "^16.4.7",
"ollama": "^0.5.11",
"ollama": "^0.5.14",
"redis": "^4.7.0"
},
"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

@@ -33,12 +33,15 @@ const messageHistory: Queue<UserMessage> = new Queue<UserMessage>
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)
})
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
}
// Try to log in the client
await client.login(Keys.clientToken)

View File

@@ -1,4 +1,4 @@
import { Client, CommandInteraction, ApplicationCommandOptionType } from 'discord.js'
import { Client, CommandInteraction, ApplicationCommandOptionType, MessageFlags } from 'discord.js'
import { openConfig, SlashCommand, UserCommand } from '../utils/index.js'
export const Capacity: SlashCommand = {
@@ -22,11 +22,13 @@ export const Capacity: SlashCommand = {
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)
openConfig(`${interaction.user.username}-config.json`, interaction.commandName,
interaction.options.get('context-capacity')?.value
)
interaction.reply({
content: `Max message history is now set to \`${interaction.options.get('context-capacity')?.value}\``,
ephemeral: true
flags: MessageFlags.Ephemeral
})
}
}

View File

@@ -1,4 +1,4 @@
import { Channel, Client, CommandInteraction, TextChannel } from 'discord.js'
import { Channel, Client, CommandInteraction, MessageFlags, TextChannel } from 'discord.js'
import { clearChannelInfo, SlashCommand, UserCommand } from '../utils/index.js'
export const ClearUserChannelHistory: SlashCommand = {
@@ -14,20 +14,22 @@ export const ClearUserChannelHistory: SlashCommand = {
if (!channel || !UserCommand.includes(channel.type)) return
// clear channel info for user
const successfulWipe = await clearChannelInfo(interaction.channelId,
const successfulWipe = await clearChannelInfo(
interaction.channelId,
interaction.channel as TextChannel,
interaction.user.username)
interaction.user.username
)
// check result of clearing history
if (successfulWipe)
interaction.reply({
content: `History cleared in **this channel** cleared for **${interaction.user.username}**.`,
ephemeral: true
flags: MessageFlags.Ephemeral
})
else
interaction.reply({
content: `History was not be found for **${interaction.user.username}** in **this channel**.\n\nPlease chat with **${client.user?.username}** to start a chat history.`,
ephemeral: true
flags: MessageFlags.Ephemeral
})
}
}

View File

@@ -1,4 +1,4 @@
import { ApplicationCommandOptionType, Client, CommandInteraction } 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'
@@ -31,7 +31,7 @@ export const DeleteModel: SlashCommand = {
if (!interaction.memberPermissions?.has('Administrator')) {
interaction.reply({
content: `${interaction.commandName} is an admin command.\n\nPlease contact a server admin to pull the model you want.`,
ephemeral: true
flags: MessageFlags.Ephemeral
})
return
}
@@ -53,7 +53,7 @@ export const DeleteModel: SlashCommand = {
// could not delete the model
interaction.reply({
content: `Could not delete the **${modelInput}** model. It probably doesn't exist or you spelled it incorrectly.\n\nPlease try again if this is a mistake.`,
ephemeral: true
flags: MessageFlags.Ephemeral
})
}
}

View File

@@ -1,4 +1,4 @@
import { Client, CommandInteraction, ApplicationCommandOptionType } from 'discord.js'
import { Client, CommandInteraction, ApplicationCommandOptionType, MessageFlags } from 'discord.js'
import { AdminCommand, openConfig, SlashCommand } from '../utils/index.js'
export const Disable: SlashCommand = {
@@ -25,17 +25,19 @@ export const Disable: SlashCommand = {
if (!interaction.memberPermissions?.has('Administrator')) {
interaction.reply({
content: `${interaction.commandName} is an admin command.\n\nPlease contact an admin to use this command for you.`,
ephemeral: true
flags: MessageFlags.Ephemeral
})
return
}
// set state of bot chat features
openConfig(`${interaction.guildId}-config.json`, interaction.commandName, interaction.options.get('enabled')?.value)
openConfig(`${interaction.guildId}-config.json`, interaction.commandName,
interaction.options.get('enabled')?.value
)
interaction.reply({
content: `${client.user?.username} is now **${interaction.options.get('enabled')?.value ? "enabled" : "disabled" }**.`,
ephemeral: true
content: `${client.user?.username} is now **${interaction.options.get('enabled')?.value ? "enabled" : "disabled"}**.`,
flags: MessageFlags.Ephemeral
})
}
}

View File

@@ -1,4 +1,4 @@
import { ApplicationCommandOptionType, Client, CommandInteraction } from 'discord.js'
import { ApplicationCommandOptionType, Client, CommandInteraction, MessageFlags } from 'discord.js'
import { openConfig, SlashCommand, UserCommand } from '../utils/index.js'
export const MessageStream: SlashCommand = {
@@ -22,11 +22,13 @@ export const MessageStream: SlashCommand = {
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)
openConfig(`${interaction.user.username}-config.json`, interaction.commandName,
interaction.options.get('stream')?.value
)
interaction.reply({
content: `Message streaming is now set to: \`${interaction.options.get('stream')?.value}\``,
ephemeral: true
flags: MessageFlags.Ephemeral
})
}
}

View File

@@ -1,4 +1,4 @@
import { ApplicationCommandOptionType, Client, CommandInteraction } 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"
@@ -31,7 +31,7 @@ export const PullModel: SlashCommand = {
if (!interaction.memberPermissions?.has('Administrator')) {
interaction.reply({
content: `${interaction.commandName} is an admin command.\n\nPlease contact a server admin to pull the model you want.`,
ephemeral: true
flags: MessageFlags.Ephemeral
})
return
}

View File

@@ -1,4 +1,4 @@
import { Client, CommandInteraction } from 'discord.js'
import { Client, CommandInteraction, MessageFlags } from 'discord.js'
import { AdminCommand, SlashCommand } from '../utils/index.js'
export const Shutoff: SlashCommand = {
@@ -18,7 +18,7 @@ export const Shutoff: SlashCommand = {
if (!interaction.memberPermissions?.has('Administrator')) {
interaction.reply({
content: `**Shutdown Aborted:**\n\n${interaction.user.tag}, You do not have permission to shutoff **${client.user?.tag}**.`,
ephemeral: true
flags: MessageFlags.Ephemeral
})
return // stop from shutting down
}
@@ -26,8 +26,9 @@ export const Shutoff: SlashCommand = {
// Shutoff cleared, do it
interaction.reply({
content: `${client.user?.tag} is shutting down.`,
ephemeral: true
flags: MessageFlags.Ephemeral
})
console.log(`[Command: shutoff] ${client.user?.tag} is shutting down.`)
// clean up client instance and stop

View File

@@ -1,4 +1,4 @@
import { ChannelType, Client, CommandInteraction, TextChannel, ThreadChannel } from 'discord.js'
import { ChannelType, Client, CommandInteraction, MessageFlags, TextChannel, ThreadChannel } from 'discord.js'
import { AdminCommand, openChannelInfo, SlashCommand } from '../utils/index.js'
export const ThreadCreate: SlashCommand = {
@@ -21,14 +21,12 @@ export const ThreadCreate: SlashCommand = {
thread.send(`Hello ${interaction.user} and others! \n\nIt's nice to meet you. Please talk to me by typing **@${client.user?.username}** with your message.`)
// handle storing this chat channel
openChannelInfo(thread.id,
thread as ThreadChannel,
interaction.user.tag)
openChannelInfo(thread.id, thread as ThreadChannel, interaction.user.tag)
// user only reply
return interaction.reply({
content: `I can help you in <#${thread.id}> below.`,
ephemeral: true
flags: MessageFlags.Ephemeral
})
}
}

View File

@@ -1,4 +1,4 @@
import { ChannelType, Client, CommandInteraction, TextChannel, ThreadChannel } from 'discord.js'
import { ChannelType, Client, CommandInteraction, MessageFlags, TextChannel, ThreadChannel } from 'discord.js'
import { AdminCommand, openChannelInfo, SlashCommand } from '../utils/index.js'
export const PrivateThreadCreate: SlashCommand = {
@@ -22,15 +22,12 @@ export const PrivateThreadCreate: SlashCommand = {
// handle storing this chat channel
// store: thread.id, thread.name
openChannelInfo(thread.id,
thread as ThreadChannel,
interaction.user.tag
)
openChannelInfo(thread.id, thread as ThreadChannel, interaction.user.tag)
// user only reply
return interaction.reply({
content: `I can help you in <#${thread.id}>.`,
ephemeral: true
flags: MessageFlags.Ephemeral
})
}
}

View File

@@ -1,6 +1,9 @@
import { TextChannel } from 'discord.js'
import { event, Events, normalMessage, UserMessage, clean, getTextFileAttachmentData } from '../utils/index.js'
import { getChannelInfo, getServerConfig, getUserConfig, openChannelInfo, openConfig, UserConfig, getAttachmentData } from '../utils/index.js'
import { event, Events, normalMessage, UserMessage, clean } from '../utils/index.js'
import {
getChannelInfo, getServerConfig, getUserConfig, openChannelInfo,
openConfig, UserConfig, getAttachmentData, getTextFileAttachmentData
} from '../utils/index.js'
/**
* Max Message length for free users is 2000 characters (bot or not).

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

@@ -21,13 +21,12 @@ export function getEnvVar(name: string, fallback?: string): string {
throw new Error(`Environment variable ${name} is not set.`)
// validate User-Generated Discord Application Tokens
if (name === "CLIENT_TOKEN")
if (value.length < 72) throw new Error(`The "CLIENT_TOKEN" provided is not of at least length 72.
if (name === "CLIENT_TOKEN" && value.length > 72)
throw new Error(`The "CLIENT_TOKEN" provided is not of at least length 72.
This is probably an invalid token unless Discord updated their token policy. Please provide a valid token.`)
// validate IPv4 address found in environment variables
if (name.endsWith("_IP") || name.endsWith("_ADDRESS"))
if (!ipValidate.test(value))
if ((name.endsWith("_IP") || name.endsWith("_ADDRESS")) && !ipValidate.test(value))
throw new Error(`Environment variable ${name} does not follow IPv4 formatting.`)
// return env variable

View File

@@ -24,6 +24,7 @@ export type ChatParams = {
* Format for the messages to be stored when communicating when the bot
* @param role either assistant, user, or system
* @param content string of the message the user or assistant provided
* @param images array of images that the user or assistant provided
*/
export type UserMessage = {
role: string,
@@ -33,12 +34,18 @@ export type UserMessage = {
// Event properties
export interface EventProps {
client: Client
log: LogMethod
msgHist: Queue<UserMessage>
client: Client,
log: LogMethod,
msgHist: Queue<UserMessage>,
ollama: Ollama,
defaultModel: String
}
/**
* Format for the callback function tied to an event
* @param props the properties of the event
* @param args the arguments of the event
*/
export type EventCallback<T extends EventKeys> = (
props: EventProps,
...args: ClientEvents[T]
@@ -50,6 +57,12 @@ export interface Event<T extends EventKeys = EventKeys> {
callback: EventCallback<T>
}
/**
* Method to create an event object
* @param key type of event
* @param callback function to run when event is triggered
* @returns event object
*/
export function event<T extends EventKeys>(key: T, callback: EventCallback<T>): Event<T> {
return { key, callback }
}

View File

@@ -80,7 +80,14 @@ export async function openChannelInfo(filename: string, channel: TextChannel | T
}
})
} else { // file doesn't exist, create it
const object: Configuration = JSON.parse(`{ \"id\": \"${channel?.id}\", \"name\": \"${channel?.name}\", \"user\": \"${user}\", \"messages\": []}`)
const object: Configuration = JSON.parse(
`{
\"id\": \"${channel?.id}\",
\"name\": \"${channel?.name}\",
\"user\": \"${user}\",
\"messages\": []
}`
)
const directory = path.dirname(fullFileName)
if (!fs.existsSync(directory))

View File

@@ -5,7 +5,7 @@ import { AbortableAsyncIterator } from "ollama/src/utils.js"
/**
* Method to query the Ollama client for async generation
* @param params
* @returns Asyn
* @returns AsyncIterator<ChatResponse> generated by the Ollama client
*/
export async function streamResponse(params: ChatParams): Promise<AbortableAsyncIterator<ChatResponse>> {
return await params.ollama.chat({

View File

@@ -40,7 +40,8 @@ export async function normalMessage(
result = portion.message.content
// new message block, wait for it to send and assign new block to respond.
await channel.send("Creating new stream block...").then(sentMessage => { messageBlock = sentMessage })
await channel.send("Creating new stream block...")
.then(sentMessage => { messageBlock = sentMessage })
} else {
result += portion.message.content
@@ -71,7 +72,7 @@ export async function normalMessage(
} else // edit the 'generic' response to new message since <2000
sentMessage.edit(result)
}
} catch(error: any) {
} 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.`)

View File

@@ -34,7 +34,8 @@ describe('Commands Existence', () => {
// test specific commands in the object
it('references specific commands', () => {
const commandsString = commands.map(e => e.name).join(', ')
expect(commandsString).toBe('thread, private-thread, message-stream, toggle-chat, shutoff, modify-capacity, clear-user-channel-history, pull-model, switch-model, delete-model')
const expectedCommands = ['thread', 'private-thread', 'message-stream', 'toggle-chat', 'shutoff', 'modify-capacity', 'clear-user-channel-history', 'pull-model', 'switch-model', 'delete-model']
expect(commandsString).toBe(expectedCommands.join(', '))
})
})

View File

@@ -8,7 +8,7 @@ import { Queue } from '../src/queues/queue.js'
* @param fn function holding tests to run
*/
describe('Queue Structure', () => {
let queue= new Queue<string>()
let queue = new Queue<string>()
// test for queue creation
it('creates a new queue', () => {