mirror of
https://github.com/kevinthedang/discord-ollama.git
synced 2026-08-09 07:54:02 -04:00
Compare commits
12
Commits
v0.8.6
...
7a4879cdb3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a4879cdb3 | ||
|
|
2987aa386f | ||
|
|
c6af5765c8 | ||
|
|
e0888c4d7c | ||
|
|
0124da5027 | ||
|
|
effcc1fd15 | ||
|
|
b492d8f62f | ||
|
|
4aadea4611 | ||
|
|
c0e29b3bbe | ||
|
|
32137dacb0 | ||
|
|
c00ea5de98 | ||
|
|
b27cdfc162 |
@@ -8,10 +8,10 @@ assignees: ''
|
||||
---
|
||||
|
||||
## Issue
|
||||
A clear and concise description of what the problem/feature is.
|
||||
A clear and concise description of what the problem/feature is. PLease describe it as best as possible.
|
||||
|
||||
## Solution
|
||||
* Provide steps or ideals to how to implement or investigate this new feature.
|
||||
* Provide steps or ideas to how to implement or investigate this new feature.
|
||||
|
||||
## References
|
||||
* Provide additional context and external references here
|
||||
|
||||
+77
-75
@@ -1,75 +1,77 @@
|
||||
name: Builds
|
||||
run-name: Validate Node and Docker Builds
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master # runs after Pull Request is merged
|
||||
|
||||
jobs:
|
||||
Discord-Node-Build: # test if the node install and run
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
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: Install Project Dependencies
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Build Application
|
||||
run: |
|
||||
npm run build
|
||||
|
||||
- name: Create Environment Variables
|
||||
run: |
|
||||
touch .env
|
||||
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
|
||||
|
||||
# set -e ensures if nohup fails, this section fails
|
||||
- name: Startup Discord Bot Client
|
||||
run: |
|
||||
set -e
|
||||
nohup npm run prod &
|
||||
|
||||
Discord-Ollama-Container-Build: # test docker build and run
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
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 = ${{ secrets.BOT_TOKEN }} >> .env
|
||||
echo OLLAMA_IP = ${{ secrets.OLLAMA_IP }} >> .env
|
||||
echo OLLAMA_PORT = ${{ secrets.OLLAMA_PORT }} >> .env
|
||||
echo MODEL = ${{ secrets.MODEL }} >> .env
|
||||
|
||||
- name: Setup Docker Network and Images
|
||||
run: |
|
||||
npm run docker:start-cpu
|
||||
|
||||
- name: Check Images Exist
|
||||
run: |
|
||||
(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') || exit 1
|
||||
name: Builds
|
||||
run-name: Validate Node and Docker Builds
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master # runs after Pull Request is merged
|
||||
|
||||
jobs:
|
||||
Discord-Node-Build: # test if the node install and run
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node Environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/jod
|
||||
cache: npm
|
||||
|
||||
- name: Install Project Dependencies
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Build Application
|
||||
run: |
|
||||
npm run build
|
||||
|
||||
- name: Create Environment Variables
|
||||
run: |
|
||||
touch .env
|
||||
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
|
||||
|
||||
# set -e ensures if nohup fails, this section fails
|
||||
- name: Startup Discord Bot Client
|
||||
run: |
|
||||
set -e
|
||||
nohup npm run prod &
|
||||
|
||||
Discord-Ollama-Container-Build: # test docker build and run
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node Environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/jod
|
||||
cache: npm
|
||||
|
||||
- name: Create Environment Variables
|
||||
run: |
|
||||
touch .env
|
||||
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
|
||||
|
||||
- name: Setup Docker Network and Images
|
||||
run: |
|
||||
npm run docker:start-cpu
|
||||
|
||||
- name: Check Images Exist
|
||||
run: |
|
||||
(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') || exit 1
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
name: Coverage
|
||||
run-name: Code Coverage
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
types: [opened, synchronize, reopened, closed]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
Discord-Node-Coverage:
|
||||
runs-on: ubuntu-latest
|
||||
environment: coverage
|
||||
timeout-minutes: 2
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node Environment lts/jod
|
||||
- name: Set up Node Environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/jod
|
||||
cache: "npm"
|
||||
cache: npm
|
||||
|
||||
- name: Install Project Dependencies
|
||||
run: |
|
||||
npm install
|
||||
run: npm install
|
||||
|
||||
- name: Create Environment Variables
|
||||
run: |
|
||||
@@ -32,19 +36,21 @@ jobs:
|
||||
echo OLLAMA_PORT = ${{ secrets.OLLAMA_PORT }} >> .env
|
||||
echo MODEL = ${{ secrets.MODEL }} >> .env
|
||||
|
||||
- name: Collect Code Coverage
|
||||
run: |
|
||||
LINE_PCT=$(npm run coverage | tail -2 | head -1 | awk '{print $3}')
|
||||
echo "COVERAGE=$LINE_PCT" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload Code Coverage
|
||||
uses: schneegans/dynamic-badges-action@v1.7.0
|
||||
- name: Run tests with coverage
|
||||
run: npm run coverage
|
||||
|
||||
- name: Generate coverage badge
|
||||
uses: wjervis7/vitest-badge-action@v1.0.0
|
||||
if: success() || failure()
|
||||
with:
|
||||
auth: ${{ secrets.GIST_SECRET }}
|
||||
gistID: ${{ vars.GIST_ID }}
|
||||
filename: coverage.json
|
||||
label: Coverage
|
||||
message: ${{ env.COVERAGE }}
|
||||
valColorRange: ${{ env.COVERAGE }}
|
||||
maxColorRange: 100
|
||||
minColorRange: 0
|
||||
result-type: statements
|
||||
badge-text: 'Coverage (Statements)'
|
||||
badge-path: ./imgs/coverage.svg
|
||||
upload-badge: false
|
||||
|
||||
- name: Commit badge with merge
|
||||
if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true
|
||||
uses: stefanzweifel/git-auto-commit-action@v7
|
||||
with:
|
||||
file_pattern: ./imgs/coverage.svg
|
||||
commit_message: 'update with coverage badge'
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
name: Deploy
|
||||
run-name: Deploy Application Latest
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
Deploy-Application:
|
||||
runs-on: self-hosted
|
||||
environment: deploy
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
@@ -39,7 +38,7 @@ jobs:
|
||||
git clone https://github.com/kevinthedang/discord-ollama.git ${{ secrets.PATH }}
|
||||
cd ${{ secrets.PATH }}
|
||||
|
||||
- name: Install nvm and Node.js lts/jod
|
||||
- name: Install nvm and Node.js
|
||||
run: |
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
|
||||
+50
-48
@@ -1,48 +1,50 @@
|
||||
name: Tests
|
||||
run-name: Unit Tests
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '*'
|
||||
- 'package*.json'
|
||||
- 'src/**'
|
||||
- 'tests/**'
|
||||
- '!docs/**'
|
||||
- '!imgs/**'
|
||||
- '!.github/**'
|
||||
- '.github/workflows/**'
|
||||
- '!.gitignore'
|
||||
- '!LICENSE'
|
||||
- '!README'
|
||||
|
||||
jobs:
|
||||
Discord-Node-Test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
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: Install Project Dependencies
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Create Environment Variables
|
||||
run: |
|
||||
touch .env
|
||||
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
|
||||
|
||||
- name: Test Application
|
||||
run: |
|
||||
npm run tests
|
||||
name: Tests
|
||||
run-name: Unit Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '*'
|
||||
- 'package*.json'
|
||||
- 'src/**'
|
||||
- 'tests/**'
|
||||
- '!docs/**'
|
||||
- '!imgs/**'
|
||||
- '!.github/**'
|
||||
- '.github/workflows/**'
|
||||
- '!.gitignore'
|
||||
- '!LICENSE'
|
||||
- '!README'
|
||||
|
||||
jobs:
|
||||
Discord-Node-Test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node Environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/jod
|
||||
cache: npm
|
||||
|
||||
- name: Install Project Dependencies
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Create Environment Variables
|
||||
run: |
|
||||
touch .env
|
||||
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
|
||||
|
||||
- name: Test Application
|
||||
run: |
|
||||
npm run tests
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
# Technical/Business Code Ownership
|
||||
/src/ @kevinthedang @JT2M0L3Y
|
||||
/tests/ @JT2M0L3Y
|
||||
/.github/ @kevinthedang
|
||||
/.github/ @kevinthedang @JT2M0L3Y
|
||||
|
||||
# Docker Ownership
|
||||
Dockerfile @kevinthedang
|
||||
|
||||
@@ -4,28 +4,37 @@
|
||||
<h3><a href="#"></a>Ollama as your Discord AI Assistant</h3>
|
||||
<p><a href="#"></a><a href="https://creativecommons.org/licenses/by/4.0/"><img alt="License" src="https://img.shields.io/badge/License-CC_BY_4.0-darkgreen.svg" /></a>
|
||||
<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/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>
|
||||
<a href="#"></a><a href="https://github.com/kevinthedang/discord-ollama/actions/workflows/build.yml"><img alt="Builds" src="https://github.com/kevinthedang/discord-ollama/actions/workflows/build.yml/badge.svg" /></a>
|
||||
<a href="#"></a><a href="https://github.com/kevinthedang/discord-ollama/actions/workflows/test.yml"><img alt="Tests" src="https://github.com/kevinthedang/discord-ollama/actions/workflows/test.yml/badge.svg" /></a>
|
||||
<a href="#"></a><a href="https://github.com/kevinthedang/discord-ollama/actions/workflows/coverage.yml"><img alt="Coverage" src="https://github.com/kevinthedang/discord-ollama/blob/main/imgs/coverage.svg?raw=true" /></a>
|
||||
</div>
|
||||
|
||||
## About/Goals
|
||||
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
|
||||
* [x] Summary Command
|
||||
* [ ] Model Info Command
|
||||
* [ ] List Models Command
|
||||
* [x] Pull Model Command
|
||||
* [x] Switch Model Command
|
||||
* [x] Delete Model Command
|
||||
* [x] Create Thread Command
|
||||
* [x] Create Private Thread Command
|
||||
* [x] Message Stream Command
|
||||
* [x] Change Message History Size Command
|
||||
* [x] Clear Channel History Command (User Only)
|
||||
* [x] Administrator Role Compatible
|
||||
* [x] Generated Token Length Handling for >2000
|
||||
* [x] Token Length Handling of any message size
|
||||
* [x] User vs. Server Preferences
|
||||
* [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)
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ services:
|
||||
build: ./ # find docker file in designated path
|
||||
container_name: discord
|
||||
restart: always # rebuild container always
|
||||
image: kevinthedang/discord-ollama:0.8.6
|
||||
image: kevinthedang/discord-ollama:0.9.1
|
||||
environment:
|
||||
CLIENT_TOKEN: ${CLIENT_TOKEN}
|
||||
OLLAMA_IP: ${OLLAMA_IP}
|
||||
|
||||
Generated
+484
-384
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "discord-ollama",
|
||||
"version": "0.8.6",
|
||||
"version": "0.9.1",
|
||||
"description": "Ollama Integration into discord",
|
||||
"main": "build/index.js",
|
||||
"exports": "./build/index.js",
|
||||
|
||||
+43
-40
@@ -1,41 +1,44 @@
|
||||
import { Client, GatewayIntentBits } from 'discord.js'
|
||||
import { Ollama } from 'ollama'
|
||||
import { Queue } from './queues/queue.js'
|
||||
import { UserMessage, registerEvents } from './utils/index.js'
|
||||
import Events from './events/index.js'
|
||||
import Keys from './keys.js'
|
||||
|
||||
// initialize the client with the following permissions when logging in
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent
|
||||
]
|
||||
})
|
||||
|
||||
// initialize connection to ollama container
|
||||
export const ollama = new Ollama({
|
||||
host: `http://${Keys.ipAddress}:${Keys.portAddress}`,
|
||||
})
|
||||
|
||||
// Create Queue managed by Events
|
||||
const messageHistory: Queue<UserMessage> = new Queue<UserMessage>
|
||||
|
||||
// register all events
|
||||
registerEvents(client, Events, messageHistory, ollama, Keys.defaultModel)
|
||||
|
||||
// Try to log in the client
|
||||
await client.login(Keys.clientToken)
|
||||
.catch((error) => {
|
||||
console.error('[Login Error]', error)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
// queue up bots name
|
||||
messageHistory.enqueue({
|
||||
role: 'assistant',
|
||||
content: `My name is ${client.user?.username}`,
|
||||
images: []
|
||||
import { Client, GatewayIntentBits } from 'discord.js'
|
||||
import { Ollama } from 'ollama'
|
||||
import { Queue } from './queues/queue.js'
|
||||
import { UserMessage, registerEvents } from './utils/index.js'
|
||||
import Events from './events/index.js'
|
||||
import Keys from './keys.js'
|
||||
|
||||
// initialize the client with the following permissions when logging in
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent
|
||||
]
|
||||
})
|
||||
|
||||
// initialize connection to ollama container
|
||||
export const ollama = new Ollama({
|
||||
host: `http://${Keys.ipAddress}:${Keys.portAddress}`,
|
||||
})
|
||||
|
||||
// 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)
|
||||
|
||||
// Try to log in the client
|
||||
await client.login(Keys.clientToken)
|
||||
.catch((error) => {
|
||||
console.error('[Login Error]', error)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
// queue up bots name
|
||||
messageHistory.enqueue({
|
||||
role: 'assistant',
|
||||
content: `My name is ${client.user?.username}`,
|
||||
images: []
|
||||
})
|
||||
@@ -9,6 +9,7 @@ import { ClearUserChannelHistory } from './cleanUserChannelHistory.js'
|
||||
import { PullModel } from './pullModel.js'
|
||||
import { SwitchModel } from './switchModel.js'
|
||||
import { DeleteModel } from './deleteModel.js'
|
||||
import { Summary } from './summary.js'
|
||||
|
||||
export default [
|
||||
ThreadCreate,
|
||||
@@ -20,5 +21,6 @@ export default [
|
||||
ClearUserChannelHistory,
|
||||
PullModel,
|
||||
SwitchModel,
|
||||
DeleteModel
|
||||
DeleteModel,
|
||||
Summary
|
||||
] as SlashCommand[]
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Client, CommandInteraction, Message, MessageFlags, SendableChannels } from "discord.js";
|
||||
import { accessChannelContext, normalMessage, SlashCommand, summarizeContextHistory, UserCommand, UserMessage } from "../utils/index.js";
|
||||
import { ollama } from "../client.js"
|
||||
|
||||
export const Summary: SlashCommand = {
|
||||
name: 'summary',
|
||||
description: 'provides a summary of the chat history.',
|
||||
|
||||
// Generate Summary from additional context
|
||||
run: async (client: Client, interaction: CommandInteraction) => {
|
||||
// fetch channel
|
||||
const channel = await client.channels.fetch(interaction.channelId)
|
||||
if (!channel || !UserCommand.includes(channel.type)) return
|
||||
|
||||
// Defer
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral })
|
||||
|
||||
// create summary using context
|
||||
let channelContext: UserMessage[] = await new Promise((resolve) => {
|
||||
accessChannelContext(interaction.channelId, (additionalContext) => {
|
||||
if (additionalContext?.messages)
|
||||
resolve(additionalContext.messages)
|
||||
else
|
||||
resolve([])
|
||||
})
|
||||
})
|
||||
|
||||
if (channelContext.length === 0) {
|
||||
interaction.reply({
|
||||
content: `There are no recent chat messages in this channel.`,
|
||||
flags: MessageFlags.Ephemeral
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Push summarize prompt
|
||||
channelContext.push({
|
||||
role: "user",
|
||||
content: "Please Summarize everything from the prior messages, provide in bullet point fashion on what people have said prior. For example: \"Someone mentioned/talked about ...\"",
|
||||
images: []
|
||||
})
|
||||
|
||||
// todo: instead of a default of codellama, we can user default model somehow. Look into later.
|
||||
const response: string = await summarizeContextHistory(ollama, "codellama", channelContext)
|
||||
|
||||
console.log(response)
|
||||
|
||||
interaction.editReply({ content: response })
|
||||
}
|
||||
}
|
||||
+57
-11
@@ -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
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import('./client.js')
|
||||
import('./client.js')
|
||||
|
||||
+9
-9
@@ -1,10 +1,10 @@
|
||||
import { getEnvVar } from './utils/index.js'
|
||||
|
||||
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')
|
||||
} as const // readonly keys
|
||||
|
||||
import { getEnvVar } from './utils/index.js'
|
||||
|
||||
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')
|
||||
} as const // readonly keys
|
||||
|
||||
export default Keys
|
||||
+3
-1
@@ -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)
|
||||
}
|
||||
|
||||
@@ -56,6 +56,54 @@ 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`)
|
||||
}
|
||||
}
|
||||
|
||||
export async function accessChannelContext(filename: string, callback: (config: Channel | undefined) => void): Promise<void> {
|
||||
const fullFileName = `data/${filename}-context.json`
|
||||
if (fs.existsSync(fullFileName)) {
|
||||
fs.readFile(fullFileName, 'utf8', (error, data) => {
|
||||
if (error) {
|
||||
callback(undefined)
|
||||
return // something went wrong... stop
|
||||
}
|
||||
callback(JSON.parse(data))
|
||||
})
|
||||
} else {
|
||||
callback(undefined) // file not found
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to open the channel history
|
||||
*
|
||||
|
||||
@@ -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,39 @@ export async function normalMessage(
|
||||
// return the string representation of ollama query response
|
||||
return result
|
||||
}
|
||||
|
||||
export async function summarizeContextHistory(
|
||||
ollama: Ollama,
|
||||
model: string,
|
||||
msgHist: UserMessage[],
|
||||
): Promise<string> {
|
||||
// todo: this stuff to create a response in a simpler way!
|
||||
try {
|
||||
const params: ChatParams = {
|
||||
model: model,
|
||||
ollama: ollama,
|
||||
msgHist: msgHist
|
||||
}
|
||||
const response: ChatResponse = await blockResponse(params)
|
||||
let result = response.message.content
|
||||
|
||||
if (hasThinking(result))
|
||||
result = result.replace(/<think>[\s\S]*?<\/think>/g, '').trim()
|
||||
|
||||
return result
|
||||
} catch (error: any) {
|
||||
console.log(`[Util: messageNormal] Error creating message: ${error.message}`)
|
||||
if (error.message.includes('fetch failed'))
|
||||
error.message = 'Missing ollama service on machine'
|
||||
else if (error.message.includes('try pulling it first'))
|
||||
error.message = `You do not have the ${model} downloaded. Ask an admin to pull it using the \`pull-model\` command.`
|
||||
return `**Response generation failed.**\n\nReason: ${error.message}`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Region: Helpers
|
||||
function hasThinking(message: string): boolean {
|
||||
return /<think>[\s\S]*?<\/think>/i.test(message)
|
||||
}
|
||||
// End Region: Helpers
|
||||
|
||||
+77
-77
@@ -1,78 +1,78 @@
|
||||
// describe marks a test suite
|
||||
// expect takes a value from an expression
|
||||
// it marks a test case
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import commands from '../src/commands/index.js'
|
||||
|
||||
/**
|
||||
* Mocking client.ts because of the commands
|
||||
*/
|
||||
vi.mock('../src/client.js', () => ({}))
|
||||
|
||||
/**
|
||||
* Commands test suite, tests the commands object
|
||||
* Each command is to be tested elsewhere, this file
|
||||
* is to ensure that the commands object is defined.
|
||||
*
|
||||
* @param name name of the test suite
|
||||
* @param fn function holding tests to run
|
||||
*/
|
||||
describe('Commands Existence', () => {
|
||||
// test definition of commands object
|
||||
it('references defined object', () => {
|
||||
// toBe compares the value to the expected value
|
||||
expect(typeof commands).toBe('object')
|
||||
})
|
||||
|
||||
// test specific commands in the object
|
||||
it('references specific commands', () => {
|
||||
const commandsString = commands.map(e => e.name).join(', ')
|
||||
const expectedCommands = ['thread', 'private-thread', 'message-stream', 'toggle-chat', 'shutoff', 'modify-capacity', 'clear-user-channel-history', 'pull-model', 'switch-model', 'delete-model']
|
||||
expect(commandsString).toBe(expectedCommands.join(', '))
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* User Commands Test suite for testing out commands
|
||||
* that would be run by users when using the application.
|
||||
*/
|
||||
describe('User Command Tests', () => {
|
||||
// test capacity command
|
||||
it('run modify-capacity command', () => {
|
||||
|
||||
})
|
||||
|
||||
it('run clear-user-channel-history command', () => {
|
||||
|
||||
})
|
||||
|
||||
it('run message-stream command', () => {
|
||||
|
||||
})
|
||||
|
||||
it('run message-style command', () => {
|
||||
|
||||
})
|
||||
|
||||
it('run thread command', () => {
|
||||
|
||||
})
|
||||
|
||||
it('run private-thread command', () => {
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Admin Commands Test suite for running administrative
|
||||
* commands with the application.
|
||||
*/
|
||||
describe('Admin Command Tests', () => {
|
||||
it('run shutoff command', () => {
|
||||
|
||||
})
|
||||
|
||||
it('run toggle-chat command', () => {
|
||||
|
||||
})
|
||||
// describe marks a test suite
|
||||
// expect takes a value from an expression
|
||||
// it marks a test case
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import commands from '../src/commands/index.js'
|
||||
|
||||
/**
|
||||
* Mocking client.ts because of the commands
|
||||
*/
|
||||
vi.mock('../src/client.js', () => ({}))
|
||||
|
||||
/**
|
||||
* Commands test suite, tests the commands object
|
||||
* Each command is to be tested elsewhere, this file
|
||||
* is to ensure that the commands object is defined.
|
||||
*
|
||||
* @param name name of the test suite
|
||||
* @param fn function holding tests to run
|
||||
*/
|
||||
describe('Commands Existence', () => {
|
||||
// test definition of commands object
|
||||
it('references defined object', () => {
|
||||
// toBe compares the value to the expected value
|
||||
expect(typeof commands).toBe('object')
|
||||
})
|
||||
|
||||
// test specific commands in the object
|
||||
it('references specific commands', () => {
|
||||
const commandsString = commands.map(e => e.name).join(', ')
|
||||
const expectedCommands = ['thread', 'private-thread', 'message-stream', 'toggle-chat', 'shutoff', 'modify-capacity', 'clear-user-channel-history', 'pull-model', 'switch-model', 'delete-model', 'summary']
|
||||
expect(commandsString).toBe(expectedCommands.join(', '))
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* User Commands Test suite for testing out commands
|
||||
* that would be run by users when using the application.
|
||||
*/
|
||||
describe('User Command Tests', () => {
|
||||
// test capacity command
|
||||
it('run modify-capacity command', () => {
|
||||
|
||||
})
|
||||
|
||||
it('run clear-user-channel-history command', () => {
|
||||
|
||||
})
|
||||
|
||||
it('run message-stream command', () => {
|
||||
|
||||
})
|
||||
|
||||
it('run message-style command', () => {
|
||||
|
||||
})
|
||||
|
||||
it('run thread command', () => {
|
||||
|
||||
})
|
||||
|
||||
it('run private-thread command', () => {
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Admin Commands Test suite for running administrative
|
||||
* commands with the application.
|
||||
*/
|
||||
describe('Admin Command Tests', () => {
|
||||
it('run shutoff command', () => {
|
||||
|
||||
})
|
||||
|
||||
it('run toggle-chat command', () => {
|
||||
|
||||
})
|
||||
})
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
import { defineConfig, configDefaults } from 'vitest/config'
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
// config for vitest
|
||||
export default defineConfig({
|
||||
@@ -6,8 +6,8 @@ export default defineConfig({
|
||||
globals: true, // <-- reduces test file imports
|
||||
reporters: ['verbose'], // <-- verbose output
|
||||
coverage: {
|
||||
exclude: [...configDefaults.exclude, 'build/*', 'tests/*'], // <-- exclude JS build
|
||||
reporter: ['text-summary'] // <-- report in text-summary
|
||||
include: ['src/**/*.ts'],
|
||||
reporter: 'json-summary'
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user