Compare commits
2 commits
69354229d6
...
863d3e3c88
| Author | SHA1 | Date | |
|---|---|---|---|
| 863d3e3c88 | |||
| 864201214d |
9 changed files with 1189 additions and 18 deletions
1042
bot.log
1042
bot.log
File diff suppressed because one or more lines are too long
Binary file not shown.
BIN
src/__pycache__/context.cpython-310.pyc
Normal file
BIN
src/__pycache__/context.cpython-310.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/profilepic.cpython-310.pyc
Normal file
BIN
src/__pycache__/profilepic.cpython-310.pyc
Normal file
Binary file not shown.
24
src/ai.py
24
src/ai.py
|
|
@ -82,23 +82,33 @@ def get_current_model():
|
|||
return get_model_name()
|
||||
|
||||
# Main LLM interaction — injects personality and sends prompt to Ollama
|
||||
def get_ai_response(user_prompt):
|
||||
def get_ai_response(user_prompt, context=None):
|
||||
model_name = get_model_name()
|
||||
load_model(model_name) # Ensures the model is pulled and ready
|
||||
load_model(model_name)
|
||||
|
||||
persona = load_persona()
|
||||
full_prompt = ""
|
||||
|
||||
# Inject persona first if available
|
||||
if persona:
|
||||
# Clean fancy quotes and build final prompt with character injection
|
||||
safe_inject = persona["prompt_inject"].replace("“", "\"").replace("”", "\"").replace("’", "'")
|
||||
full_prompt = f"{safe_inject}\nUser: {user_prompt}\n{persona['name']}:"
|
||||
full_prompt += f"{safe_inject}\n"
|
||||
|
||||
# Add recent conversation context, if available
|
||||
if context:
|
||||
logger.info("🧠 Injected context block (pre-prompt):\n" + context)
|
||||
full_prompt += f"[Recent Conversation]\n{context}\n\n"
|
||||
|
||||
# Add user prompt + character or plain ending
|
||||
if persona:
|
||||
full_prompt += f"User: {user_prompt}\n{persona['name']}:"
|
||||
else:
|
||||
full_prompt = user_prompt # fallback to raw prompt if no persona loaded
|
||||
full_prompt += user_prompt
|
||||
|
||||
payload = {
|
||||
"model": model_name, # 🔧 Suggested fix: previously hardcoded to MODEL_NAME
|
||||
"model": model_name,
|
||||
"prompt": full_prompt,
|
||||
"stream": False
|
||||
# optional: add "keep_alive": 300 to keep model warm
|
||||
}
|
||||
|
||||
logger.info("🛰️ SENDING TO OLLAMA /generate")
|
||||
|
|
|
|||
68
src/bot.py
68
src/bot.py
|
|
@ -9,10 +9,12 @@ from dotenv import load_dotenv
|
|||
import random
|
||||
import yaml
|
||||
from scheduler import start_scheduler
|
||||
from profilepic import set_avatar_from_bytes
|
||||
from context import fetch_recent_context, format_context
|
||||
from logger import setup_logger
|
||||
logger = setup_logger("bot")
|
||||
|
||||
from ai import unload_model, load_model, get_current_model
|
||||
from ai import unload_model, load_model, get_current_model, get_ai_response
|
||||
|
||||
dotenv_path = os.path.join(os.path.dirname(__file__), '..', '.env')
|
||||
load_dotenv(dotenv_path)
|
||||
|
|
@ -33,16 +35,6 @@ else:
|
|||
|
||||
logger.info(f"✅ Final model in use: {MODEL_NAME}")
|
||||
|
||||
from ai import get_ai_response, load_model
|
||||
MODEL_NAME = os.getenv("MODEL_NAME", "llama3:latest")
|
||||
|
||||
if load_model(MODEL_NAME):
|
||||
logger.info(f"🚀 Model `{MODEL_NAME}` preloaded on startup.")
|
||||
else:
|
||||
logger.warning(f"⚠️ Failed to preload model `{MODEL_NAME}`.")
|
||||
|
||||
logger.info(f"✅ Final model in use: {MODEL_NAME}")
|
||||
|
||||
from personality import apply_personality, set_persona
|
||||
from discord.ext.commands import (
|
||||
cooldown,
|
||||
|
|
@ -95,6 +87,41 @@ async def global_command_cooldown(ctx):
|
|||
raise CommandOnCooldown(bucket, retry_after, BucketType.user)
|
||||
return True
|
||||
|
||||
@bot.event
|
||||
async def on_message(message):
|
||||
if message.author == bot.user:
|
||||
return
|
||||
|
||||
if bot.user.mentioned_in(message):
|
||||
prompt = message.content.replace(f"<@{bot.user.id}>", "").strip()
|
||||
context_msgs = await fetch_recent_context(message.channel)
|
||||
formatted_context = format_context(context_msgs)
|
||||
|
||||
logger.info("🧠 Injected context block:\n" + formatted_context)
|
||||
|
||||
async with message.channel.typing():
|
||||
reply = get_ai_response(prompt, context=formatted_context)
|
||||
await message.channel.send(reply)
|
||||
|
||||
await bot.process_commands(message)
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
print(f"✅ Logged in as {bot.user.name}")
|
||||
logger.info(f"Logged in as {bot.user.name}")
|
||||
|
||||
# Optional: rename itself in servers (if it has permission)
|
||||
for guild in bot.guilds:
|
||||
me = guild.me
|
||||
if me.nick != "Delta":
|
||||
try:
|
||||
await me.edit(nick="Delta")
|
||||
logger.info(f"🔄 Renamed self to Delta in {guild.name}")
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Failed to rename in {guild.name}: {e}")
|
||||
|
||||
bot.loop.create_task(start_scheduler(bot))
|
||||
|
||||
@bot.command()
|
||||
async def ping(ctx):
|
||||
await ctx.send("🏓 Pong!")
|
||||
|
|
@ -200,6 +227,25 @@ async def list_models(ctx):
|
|||
except Exception as e:
|
||||
await ctx.send(f"❌ Failed to fetch models: {e}")
|
||||
|
||||
@bot.command(name="setavatar")
|
||||
@commands.is_owner() # Only the bot owner can run this
|
||||
async def set_avatar(ctx):
|
||||
if not ctx.message.attachments:
|
||||
return await ctx.send("❌ Please attach an image (PNG) to use as the new avatar.")
|
||||
|
||||
image = ctx.message.attachments[0]
|
||||
image_bytes = await image.read()
|
||||
|
||||
token = os.getenv("DISCORD_TOKEN")
|
||||
if not token:
|
||||
return await ctx.send("❌ Bot token not found in environment.")
|
||||
|
||||
success = set_avatar_from_bytes(image_bytes, token)
|
||||
if success:
|
||||
await ctx.send("✅ Avatar updated successfully!")
|
||||
else:
|
||||
await ctx.send("❌ Failed to update avatar.")
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
print(f"✅ Logged in as {bot.user.name}")
|
||||
|
|
|
|||
40
src/context.py
Normal file
40
src/context.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# context.py
|
||||
|
||||
import os
|
||||
import yaml
|
||||
import discord
|
||||
|
||||
base_dir = os.path.dirname(__file__)
|
||||
with open(os.path.join(base_dir, "settings.yml"), "r", encoding="utf-8") as f:
|
||||
settings = yaml.safe_load(f)
|
||||
|
||||
CONTEXT_LIMIT = settings["context"].get("max_messages", 15)
|
||||
|
||||
async def fetch_recent_context(channel, limit=CONTEXT_LIMIT):
|
||||
messages = []
|
||||
async for message in channel.history(limit=100):
|
||||
# Skip other bots (but not Delta herself)
|
||||
if message.author.bot and message.author.id != channel.guild.me.id:
|
||||
continue
|
||||
|
||||
raw = message.clean_content
|
||||
clean = raw.strip().replace("\n", " ").replace("\r", "")
|
||||
clean = " ".join(clean.split()) # Collapse all extra whitespace
|
||||
|
||||
if not clean:
|
||||
continue
|
||||
|
||||
if clean.startswith("!"):
|
||||
continue
|
||||
|
||||
line = f"{message.created_at.strftime('%Y-%m-%d %H:%M')} - {message.author.display_name}: {clean}"
|
||||
messages.append(line)
|
||||
|
||||
if len(messages) >= limit:
|
||||
break
|
||||
|
||||
messages.reverse()
|
||||
return messages
|
||||
|
||||
def format_context(lines: list[str]) -> str:
|
||||
return "\n".join(lines)
|
||||
29
src/profilepic.py
Normal file
29
src/profilepic.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# profilepic.py
|
||||
|
||||
import base64
|
||||
import requests
|
||||
import logging
|
||||
import os
|
||||
|
||||
logger = logging.getLogger("bot")
|
||||
|
||||
DISCORD_API = "https://discord.com/api/v10"
|
||||
|
||||
def set_avatar_from_bytes(image_bytes: bytes, token: str) -> bool:
|
||||
try:
|
||||
b64_avatar = base64.b64encode(image_bytes).decode("utf-8")
|
||||
payload = {
|
||||
"avatar": f"data:image/png;base64,{b64_avatar}"
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bot {token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
response = requests.patch(f"{DISCORD_API}/users/@me", json=payload, headers=headers)
|
||||
logger.info(f"🖼️ Avatar update status: {response.status_code} - {response.text}")
|
||||
return response.status_code == 200
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to update avatar: {str(e)}")
|
||||
return False
|
||||
|
|
@ -6,6 +6,10 @@ messages:
|
|||
cooldown:
|
||||
- "🕒 Chill, wait {seconds}s before trying again."
|
||||
|
||||
context:
|
||||
enabled: true
|
||||
max_messages: 30
|
||||
|
||||
scheduler:
|
||||
enabled: false
|
||||
mode: simple # <- this activates simple mode
|
||||
|
|
|
|||
Loading…
Reference in a new issue