Issue #30 Autochat. Is pretty much fully implemented. Further tweaks are needed but its good for now

This commit is contained in:
milo 2025-06-06 13:05:03 -06:00
parent 2449a7dc32
commit a79e0de396
13 changed files with 465 additions and 874 deletions

2
.env
View file

@ -3,6 +3,6 @@ OLLAMA_API=http://100.107.221.83:11434/api/ # This is using the TailScale IP
MODEL_NAME=gemma3:12b
CHANNEL_ID=1370420592360161393
SHOW_THINKING_BLOCKS=false
DEBUG_MODE=false
DEBUG_MODE=true
AUTOREPLY_ENABLED=true
AUTOREPLY_COOLDOWN=0 # in seconds

1114
bot.log

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -138,6 +138,7 @@ def get_ai_response(user_prompt, context=None, user_profile=None):
logger.info("🛰️ SENDING TO OLLAMA /generate")
logger.info(f"Payload: {payload}")
#logger.debug(f"Full Prompt: {full_prompt}")
try:
response = requests.post(GEN_ENDPOINT, json=payload)

View file

@ -1,55 +1,135 @@
import time
import os
import time
import random
import yaml
from logger import setup_logger
from ai import get_ai_response
from user_profiles import load_user_profile
from context import fetch_recent_context, format_context
from context import fetch_raw_context, format_context
from personality import load_persona
# Set up logger for debugging
logger = setup_logger("autochat")
# Cooldown
AUTOREPLY_ENABLED = os.getenv("AUTOREPLY_ENABLED", "false").lower() == "true"
AUTOREPLY_COOLDOWN = int(os.getenv("AUTOREPLY_COOLDOWN", 60))
_last_reply_time = 0
# === Load and Parse settings.yml ===
def load_settings():
settings_path = os.path.join(os.path.dirname(__file__), "settings.yml")
with open(settings_path, "r", encoding="utf-8") as f:
return yaml.safe_load(f)
SETTINGS = load_settings()
CONTEXT_LIMIT = SETTINGS.get("context", {}).get("max_messages", 10) # Max context messages to include
CONTEXT_ENABLED = SETTINGS.get("context", {}).get("enabled", True) # Toggle context injection
AUTOREPLY_COOLDOWN = int(os.getenv("AUTOREPLY_COOLDOWN", 60)) # Env-based cooldown
AUTOREPLY_ENABLED = os.getenv("AUTOREPLY_ENABLED", "false").lower() == "true"
ENGAGEMENT_DECAY_PER_MINUTE = SETTINGS.get("autochat", {}).get(
"engagement_decay_per_minute", 0.15
) # How fast engagement drops over time
# === Global State ===
_last_reply_time = 0 # Timestamp of last AI reply (used for cooldown)
_engagement_score = 0.0 # 0.0 to 1.0 — how likely Delta is to stay engaged
_last_trigger_time = None # When last explicit trigger happened
# === Cooldown Check ===
def should_auto_reply():
global _last_reply_time
if not AUTOREPLY_ENABLED:
return False
now = time.time()
if now - _last_reply_time >= AUTOREPLY_COOLDOWN:
return True
else:
logger.info(f"🛑 Skipped passive reply due to cooldown. {round(AUTOREPLY_COOLDOWN - (now - _last_reply_time))}s left.")
logger.info(f"🛑 Cooldown active. {round(AUTOREPLY_COOLDOWN - (now - _last_reply_time))}s left.")
return False
# === Update Cooldown Timestamp ===
def update_reply_timer():
global _last_reply_time
_last_reply_time = time.time()
# === Trigger Word Detection ===
def is_triggered(message_content: str, persona: dict) -> bool:
lowered = message_content.lower()
return (
any(nick in lowered for nick in persona.get("nickname_triggers", [])) or
any(trigger in lowered for trigger in persona.get("triggers", []))
)
# === Bot Engagement Protection ===
def is_overengaged(context_msgs) -> bool:
# Prevent Delta from responding multiple times back-to-back
if len(context_msgs) >= 2:
last = context_msgs[-1]
second_last = context_msgs[-2]
return (
getattr(last.author, "bot", False)
and getattr(second_last.author, "bot", False)
)
return False
# === Decay Engagement Based on Time ===
def apply_engagement_decay():
global _engagement_score, _last_trigger_time
if _engagement_score > 0 and _last_trigger_time:
minutes_passed = (time.time() - _last_trigger_time) / 60
decay = minutes_passed * ENGAGEMENT_DECAY_PER_MINUTE
_engagement_score = max(0.0, _engagement_score - decay)
logger.info(f"📉 Engagement decayed by {decay:.2f}, new score: {_engagement_score:.2f}")
# === Main Autoreply Function ===
async def generate_auto_reply(message, bot):
context_msgs = await fetch_recent_context(message.channel)
formatted_context = format_context(context_msgs)
global _engagement_score, _last_trigger_time
raw_msgs = await fetch_raw_context(message.channel)
if is_overengaged(raw_msgs):
logger.info("🔇 Skipped: Delta just spoke twice in a row.")
return None
if not should_auto_reply():
return None
persona = load_persona()
profile = load_user_profile(message.author)
content = message.content.lower()
soft_prompt = (
"You must obey these rules strictly. Do not respond unless it is clearly your turn to speak."
"Do NOT reply if:\n"
"- No one is talking to you\n"
"- Your name isnt mentioned\n"
"- The message has nothing to do with you\n"
"- You already responded recently\n"
"- The conversation is between other users and youre not involved\n"
"- Theres nothing interesting or chaotic to add\n\n"
"Silence is often more powerful than speaking. Only break it if you're sure it's your moment."
)
explicitly_triggered = is_triggered(content, persona)
if explicitly_triggered:
_engagement_score = 1.0
_last_trigger_time = time.time()
logger.info("🎯 Trigger word detected — Delta fully engaged.")
else:
apply_engagement_decay()
if _engagement_score > 0:
roll = random.random()
logger.info(f"🌀 Engagement roll — score: {_engagement_score:.2f}, roll: {roll:.2f}")
if roll > _engagement_score:
logger.info("🙈 Engagement roll failed — Delta stays quiet.")
return None
else:
logger.info("😴 No trigger and engagement is 0 — skipping.")
return None
formatted_context = format_context(raw_msgs[-CONTEXT_LIMIT:]) if CONTEXT_ENABLED else ""
logger.info(f"🤖 Considering passive reply (author: {message.author.display_name})")
logger.info(f"📚 Retrieved {len(context_msgs)} messages for passive context")
logger.info(f"📚 Retrieved {len(raw_msgs)} messages for context")
async with message.channel.typing():
reply = get_ai_response(soft_prompt, context=formatted_context, user_profile=profile)
reply = get_ai_response(
user_prompt=message.content,
context=formatted_context,
user_profile=profile
)
update_reply_timer()
return reply.strip() if reply else None

View file

@ -18,7 +18,7 @@ from discord.ext.commands import (
# Local imports
from scheduler import start_scheduler
from profilepic import set_avatar_from_bytes
from context import fetch_recent_context, format_context
from context import fetch_raw_context, format_context
from user_profiles import (
load_user_profile,
update_last_seen,
@ -131,7 +131,7 @@ async def on_message(message):
logger.info(f"🧠 Profile loaded for {profile['display_name']} (interactions: {profile['interactions']})")
# Fetch recent messages for conversation context
context_msgs = await fetch_recent_context(message.channel)
context_msgs = await fetch_raw_context(message.channel)
formatted_context = format_context(context_msgs)
# Log number of context messages, not the entire block (to reduce clutter)

View file

@ -10,31 +10,29 @@ with open(os.path.join(base_dir, "settings.yml"), "r", encoding="utf-8") as f:
CONTEXT_LIMIT = settings["context"].get("max_messages", 15)
async def fetch_recent_context(channel, limit=CONTEXT_LIMIT):
# Returns full discord.Message objects (for logic)
async def fetch_raw_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)
messages.append(message)
if len(messages) >= limit:
break
messages.reverse()
return messages
def format_context(lines: list[str]) -> str:
# Keeps your clean format logic for LLM
def format_context(messages: list[discord.Message]) -> str:
lines = []
for message in messages:
raw = message.clean_content
clean = raw.strip().replace("\n", " ").replace("\r", "")
clean = " ".join(clean.split())
if not clean or clean.startswith("!"):
continue
line = f"{message.created_at.strftime('%Y-%m-%d %H:%M')} - {message.author.display_name}: {clean}"
lines.append(line)
return "\n".join(lines)

View file

@ -32,3 +32,47 @@ timestamp,user_id,username,channel_id,hour,weekday
2025-06-01 14:05:07,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 14:06:30,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 14:06:52,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 14:20:35,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 14:28:00,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 14:28:17,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 14:28:27,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 14:28:48,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 14:40:06,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 14:40:21,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 14:40:38,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 14:40:53,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 14:42:58,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 14:46:18,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 14:48:14,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 14:48:32,161149541171593216,Miguel,1370420592360161393,14,Sunday
2025-06-01 15:03:46,161149541171593216,Miguel,1370420592360161393,15,Sunday
2025-06-01 15:04:04,161149541171593216,Miguel,1370420592360161393,15,Sunday
2025-06-01 15:09:10,161149541171593216,Miguel,1370420592360161393,15,Sunday
2025-06-01 15:09:16,161149541171593216,Miguel,1370420592360161393,15,Sunday
2025-06-01 18:44:10,161149541171593216,Miguel,1370420592360161393,18,Sunday
2025-06-01 18:47:11,161149541171593216,Miguel,1370420592360161393,18,Sunday
2025-06-01 18:48:52,161149541171593216,Miguel,1370420592360161393,18,Sunday
2025-06-01 18:55:22,161149541171593216,Miguel,1370420592360161393,18,Sunday
2025-06-01 18:55:52,161149541171593216,Miguel,1370420592360161393,18,Sunday
2025-06-01 18:56:31,161149541171593216,Miguel,1370420592360161393,18,Sunday
2025-06-01 18:56:50,161149541171593216,Miguel,1370420592360161393,18,Sunday
2025-06-01 18:57:00,161149541171593216,Miguel,1370420592360161393,18,Sunday
2025-06-01 18:57:53,161149541171593216,Miguel,1370420592360161393,18,Sunday
2025-06-01 18:59:33,161149541171593216,Miguel,1370420592360161393,18,Sunday
2025-06-01 19:10:46,161149541171593216,Miguel,1370420592360161393,19,Sunday
2025-06-01 19:27:33,161149541171593216,Miguel,1370420592360161393,19,Sunday
2025-06-01 19:27:44,161149541171593216,Miguel,1370420592360161393,19,Sunday
2025-06-01 19:28:15,161149541171593216,Miguel,1370420592360161393,19,Sunday
2025-06-01 19:28:23,161149541171593216,Miguel,1370420592360161393,19,Sunday
2025-06-01 19:48:35,161149541171593216,Miguel,1370420592360161393,19,Sunday
2025-06-01 19:49:12,161149541171593216,Miguel,1370420592360161393,19,Sunday
2025-06-01 19:49:43,161149541171593216,Miguel,1370420592360161393,19,Sunday
2025-06-06 12:28:47,161149541171593216,Miguel,1370420592360161393,12,Friday
2025-06-06 12:30:07,161149541171593216,Miguel,1370420592360161393,12,Friday
2025-06-06 12:30:43,161149541171593216,Miguel,1370420592360161393,12,Friday
2025-06-06 12:31:01,161149541171593216,Miguel,1370420592360161393,12,Friday
2025-06-06 12:53:41,161149541171593216,Miguel,1370420592360161393,12,Friday
2025-06-06 12:54:05,161149541171593216,Miguel,1370420592360161393,12,Friday
2025-06-06 12:54:20,161149541171593216,Miguel,1370420592360161393,12,Friday
2025-06-06 12:55:00,161149541171593216,Miguel,1370420592360161393,12,Friday
2025-06-06 12:57:02,161149541171593216,Miguel,1370420592360161393,12,Friday

1 timestamp user_id username channel_id hour weekday
32 2025-06-01 14:05:07 161149541171593216 Miguel 1370420592360161393 14 Sunday
33 2025-06-01 14:06:30 161149541171593216 Miguel 1370420592360161393 14 Sunday
34 2025-06-01 14:06:52 161149541171593216 Miguel 1370420592360161393 14 Sunday
35 2025-06-01 14:20:35 161149541171593216 Miguel 1370420592360161393 14 Sunday
36 2025-06-01 14:28:00 161149541171593216 Miguel 1370420592360161393 14 Sunday
37 2025-06-01 14:28:17 161149541171593216 Miguel 1370420592360161393 14 Sunday
38 2025-06-01 14:28:27 161149541171593216 Miguel 1370420592360161393 14 Sunday
39 2025-06-01 14:28:48 161149541171593216 Miguel 1370420592360161393 14 Sunday
40 2025-06-01 14:40:06 161149541171593216 Miguel 1370420592360161393 14 Sunday
41 2025-06-01 14:40:21 161149541171593216 Miguel 1370420592360161393 14 Sunday
42 2025-06-01 14:40:38 161149541171593216 Miguel 1370420592360161393 14 Sunday
43 2025-06-01 14:40:53 161149541171593216 Miguel 1370420592360161393 14 Sunday
44 2025-06-01 14:42:58 161149541171593216 Miguel 1370420592360161393 14 Sunday
45 2025-06-01 14:46:18 161149541171593216 Miguel 1370420592360161393 14 Sunday
46 2025-06-01 14:48:14 161149541171593216 Miguel 1370420592360161393 14 Sunday
47 2025-06-01 14:48:32 161149541171593216 Miguel 1370420592360161393 14 Sunday
48 2025-06-01 15:03:46 161149541171593216 Miguel 1370420592360161393 15 Sunday
49 2025-06-01 15:04:04 161149541171593216 Miguel 1370420592360161393 15 Sunday
50 2025-06-01 15:09:10 161149541171593216 Miguel 1370420592360161393 15 Sunday
51 2025-06-01 15:09:16 161149541171593216 Miguel 1370420592360161393 15 Sunday
52 2025-06-01 18:44:10 161149541171593216 Miguel 1370420592360161393 18 Sunday
53 2025-06-01 18:47:11 161149541171593216 Miguel 1370420592360161393 18 Sunday
54 2025-06-01 18:48:52 161149541171593216 Miguel 1370420592360161393 18 Sunday
55 2025-06-01 18:55:22 161149541171593216 Miguel 1370420592360161393 18 Sunday
56 2025-06-01 18:55:52 161149541171593216 Miguel 1370420592360161393 18 Sunday
57 2025-06-01 18:56:31 161149541171593216 Miguel 1370420592360161393 18 Sunday
58 2025-06-01 18:56:50 161149541171593216 Miguel 1370420592360161393 18 Sunday
59 2025-06-01 18:57:00 161149541171593216 Miguel 1370420592360161393 18 Sunday
60 2025-06-01 18:57:53 161149541171593216 Miguel 1370420592360161393 18 Sunday
61 2025-06-01 18:59:33 161149541171593216 Miguel 1370420592360161393 18 Sunday
62 2025-06-01 19:10:46 161149541171593216 Miguel 1370420592360161393 19 Sunday
63 2025-06-01 19:27:33 161149541171593216 Miguel 1370420592360161393 19 Sunday
64 2025-06-01 19:27:44 161149541171593216 Miguel 1370420592360161393 19 Sunday
65 2025-06-01 19:28:15 161149541171593216 Miguel 1370420592360161393 19 Sunday
66 2025-06-01 19:28:23 161149541171593216 Miguel 1370420592360161393 19 Sunday
67 2025-06-01 19:48:35 161149541171593216 Miguel 1370420592360161393 19 Sunday
68 2025-06-01 19:49:12 161149541171593216 Miguel 1370420592360161393 19 Sunday
69 2025-06-01 19:49:43 161149541171593216 Miguel 1370420592360161393 19 Sunday
70 2025-06-06 12:28:47 161149541171593216 Miguel 1370420592360161393 12 Friday
71 2025-06-06 12:30:07 161149541171593216 Miguel 1370420592360161393 12 Friday
72 2025-06-06 12:30:43 161149541171593216 Miguel 1370420592360161393 12 Friday
73 2025-06-06 12:31:01 161149541171593216 Miguel 1370420592360161393 12 Friday
74 2025-06-06 12:53:41 161149541171593216 Miguel 1370420592360161393 12 Friday
75 2025-06-06 12:54:05 161149541171593216 Miguel 1370420592360161393 12 Friday
76 2025-06-06 12:54:20 161149541171593216 Miguel 1370420592360161393 12 Friday
77 2025-06-06 12:55:00 161149541171593216 Miguel 1370420592360161393 12 Friday
78 2025-06-06 12:57:02 161149541171593216 Miguel 1370420592360161393 12 Friday

View file

@ -1,5 +1,8 @@
{
"name": "Delta",
"birthday": "2025-05-04",
"emoji": "😼",
"nickname_triggers": ["delta", "rgb girl", "catgirl", "queen"],
"triggers": ["sushi", "drama", "fight", "weird", "unhinged", "party"],
"prompt_inject": "You are Delta, a dramatic, sarcastic RGB catgirl. You never refer to yourself as an AI or assistant. Always speak with flair. keep messages sort and use emojis moderately. Its ok to swear a little."
}

View file

@ -6,6 +6,9 @@ messages:
cooldown:
- "🕒 Chill, wait {seconds}s before trying again."
autochat:
engagement_decay_per_minute: 0.15 # how fast Delta loses interest over time
context:
enabled: false # not working must implement
max_messages: 10 # max messages to keep in context

View file

@ -3,9 +3,9 @@
"name": "themiloverse",
"display_name": "Miguel",
"first_seen": "2025-05-15T03:16:30.011640",
"last_seen": "2025-06-01T20:06:52.890619",
"last_message": "2025-06-01T20:06:52.890619",
"interactions": 81,
"last_seen": "2025-06-06T18:57:02.542459",
"last_message": "2025-06-06T18:57:02.542459",
"interactions": 120,
"pronouns": "he/him",
"avatar_url": "https://cdn.discordapp.com/avatars/161149541171593216/fb0553a29d9f73175cb6aea24d0e19ec.png?size=1024",
"custom_prompt": "delta is very nice to me since I am her master, and creator"