2025-01-02 17:53:16 -08:00
import os
2024-12-19 17:19:06 -08:00
import discord
from discord . ext import commands
from openai import OpenAI
2025-01-02 19:41:08 -08:00
import base64
import requests
from io import BytesIO
2025-01-02 17:53:16 -08:00
from collections import deque
2024-12-19 17:19:06 -08:00
from dotenv import load_dotenv
2025-01-06 19:44:47 -08:00
import json
import datetime
import aiohttp
from typing import Dict , Any , List
2024-12-19 17:19:06 -08:00
# Load environment variables
load_dotenv ( )
2025-01-02 17:53:16 -08:00
# Get environment variables
DISCORD_TOKEN = os . getenv ( ' DISCORD_TOKEN ' )
OPENAI_API_KEY = os . getenv ( ' OPENAI_API_KEY ' )
OPENWEBUI_API_BASE = os . getenv ( ' OPENWEBUI_API_BASE ' )
MODEL_NAME = os . getenv ( ' MODEL_NAME ' )
2024-12-19 17:19:06 -08:00
# Configure OpenAI client to point to OpenWebUI
client = OpenAI (
api_key = os . getenv ( ' OPENAI_API_KEY ' ) ,
base_url = os . getenv ( ' OPENWEBUI_API_BASE ' ) # e.g., "http://localhost:8080/v1"
)
2025-01-02 17:53:16 -08:00
# Configure OpenAI
# TODO: The 'openai.api_base' option isn't read in the client API. You will need to pass it when you instantiate the client, e.g. 'OpenAI(base_url=OPENWEBUI_API_BASE)'
# openai.api_base = OPENWEBUI_API_BASE
2024-12-19 17:19:06 -08:00
# Initialize Discord bot
intents = discord . Intents . default ( )
intents . message_content = True
2025-01-02 17:53:16 -08:00
intents . messages = True
bot = commands . Bot ( command_prefix = ' ! ' , intents = intents )
# Message history cache
channel_history = { }
2024-12-19 17:19:06 -08:00
2025-01-02 19:41:08 -08:00
async def download_image ( url ) :
response = requests . get ( url )
if response . status_code == 200 :
image_data = BytesIO ( response . content )
base64_image = base64 . b64encode ( image_data . read ( ) ) . decode ( ' utf-8 ' )
return base64_image
return None
2025-01-02 17:53:16 -08:00
async def get_chat_history ( channel , limit = 100 ) :
messages = [ ]
async for message in channel . history ( limit = limit ) :
2025-01-02 19:41:08 -08:00
content = f " { message . author . name } : { message . content } "
# Handle attachments (images)
for attachment in message . attachments :
if any ( attachment . filename . lower ( ) . endswith ( ext ) for ext in [ ' .png ' , ' .jpg ' , ' .jpeg ' , ' .gif ' , ' .webp ' ] ) :
content + = f " [Image: { attachment . url } ] "
messages . append ( content )
2025-01-02 17:53:16 -08:00
return " \n " . join ( reversed ( messages ) )
2025-01-06 19:44:47 -08:00
async def get_available_tools ( ) - > List [ Dict [ str , Any ] ] :
""" Fetch available tools from OpenWebUI API. """
try :
headers = {
" Content-Type " : " application/json " ,
" Authorization " : f " Bearer { OPENAI_API_KEY } "
}
async with aiohttp . ClientSession ( ) as session :
async with session . get (
f " { OPENWEBUI_API_BASE } /v1/tools/list " ,
headers = headers
) as response :
if response . status == 200 :
tools = await response . json ( )
return tools
else :
print ( f " Error fetching tools: { await response . text ( ) } " )
return [ ]
except Exception as e :
print ( f " Error fetching tools: { str ( e ) } " )
return [ ]
2025-01-02 19:41:08 -08:00
async def get_ai_response ( context , user_message , image_urls = None ) :
2025-01-06 19:44:47 -08:00
# Fetch available tools
tools = await get_available_tools ( )
tools_json = json . dumps ( tools , indent = 2 )
2025-01-02 19:41:08 -08:00
2025-01-06 19:44:47 -08:00
system_message = f " \" \" \" Previous conversation context: { context } \n Available Tools: { tools_json } \n Return an empty string if no tools match the query. " + """ If a function tool matches, construct and return a JSON object in the format { " name " : " functionName " , " parameters " : { " requiredFunctionParamKey " : " requiredFunctionParamValue \" }} using the appropriate tool and its parameters. Only return the object and limit the response to the JSON object without additional text. """
messages = [
{ " role " : " system " , " content " : system_message } ,
{ " role " : " user " , " content " : [ ] if image_urls else user_message }
]
2025-01-02 19:41:08 -08:00
2025-01-06 19:44:47 -08:00
# Handle messages with images differently
2025-01-02 19:41:08 -08:00
if image_urls :
2025-01-06 19:44:47 -08:00
content_parts = [ { " type " : " text " , " text " : user_message } ]
2025-01-02 19:41:08 -08:00
for url in image_urls :
base64_image = await download_image ( url )
if base64_image :
2025-01-06 19:44:47 -08:00
content_parts . append ( {
2025-01-02 19:41:08 -08:00
" type " : " image_url " ,
" image_url " : {
" url " : f " data:image/jpeg;base64, { base64_image } "
}
} )
2025-01-06 19:44:47 -08:00
messages [ 1 ] [ " content " ] = content_parts
2024-12-30 21:37:29 -08:00
2024-12-19 17:19:06 -08:00
try :
2025-01-02 19:41:08 -08:00
response = client . chat . completions . create (
model = MODEL_NAME ,
messages = messages
)
2024-12-19 17:19:06 -08:00
return response . choices [ 0 ] . message . content
except Exception as e :
2025-01-02 17:53:16 -08:00
return f " Error: { str ( e ) } "
2024-12-19 17:19:06 -08:00
@bot.event
async def on_message ( message ) :
2025-01-02 17:53:16 -08:00
# Ignore messages from the bot itself
2024-12-19 17:19:06 -08:00
if message . author == bot . user :
return
2025-01-02 17:53:16 -08:00
should_respond = False
# Check if bot was mentioned
if bot . user in message . mentions :
should_respond = True
2024-12-19 17:19:06 -08:00
2025-01-02 17:53:16 -08:00
# Check if message is a DM
if isinstance ( message . channel , discord . DMChannel ) :
should_respond = True
if should_respond :
2024-12-19 17:19:06 -08:00
async with message . channel . typing ( ) :
2025-01-02 17:53:16 -08:00
# Get chat history
history = await get_chat_history ( message . channel )
# Remove bot mention from the message
user_message = message . content . replace ( f ' <@ { bot . user . id } > ' , ' ' ) . strip ( )
2025-01-02 19:41:08 -08:00
# Collect image URLs from the message
image_urls = [ ]
for attachment in message . attachments :
if any ( attachment . filename . lower ( ) . endswith ( ext ) for ext in [ ' .png ' , ' .jpg ' , ' .jpeg ' , ' .gif ' , ' .webp ' ] ) :
image_urls . append ( attachment . url )
2025-01-02 17:53:16 -08:00
# Get AI response
2025-01-02 19:41:08 -08:00
response = await get_ai_response ( history , user_message , image_urls )
2025-01-02 17:53:16 -08:00
# Send response
await message . reply ( response )
2024-12-19 17:19:06 -08:00
await bot . process_commands ( message )
2025-01-02 19:41:08 -08:00
@bot.event
async def on_ready ( ) :
print ( f ' { bot . user } has connected to Discord! ' )
2024-12-19 17:19:06 -08:00
def main ( ) :
2025-01-02 17:53:16 -08:00
if not all ( [ DISCORD_TOKEN , OPENAI_API_KEY , OPENWEBUI_API_BASE , MODEL_NAME ] ) :
print ( " Error: Missing required environment variables " )
return
2024-12-19 17:19:06 -08:00
2025-01-02 17:53:16 -08:00
bot . run ( DISCORD_TOKEN )
2024-12-19 17:19:06 -08:00
if __name__ == " __main__ " :
2025-01-02 17:53:16 -08:00
main ( )