Implement Evil Miku mode with persistence, fix API event loop issues, and improve formatting

- Added Evil Miku mode with 4 evil moods (aggressive, cunning, sarcastic, evil_neutral)
- Created evil mode content files (evil_miku_lore.txt, evil_miku_prompt.txt, evil_miku_lyrics.txt)
- Implemented persistent evil mode state across restarts (saves to memory/evil_mode_state.json)
- Fixed API endpoints to use client.loop.create_task() to prevent timeout errors
- Added evil mode toggle in web UI with red theme styling
- Modified mood rotation to handle evil mode
- Configured DarkIdol uncensored model for evil mode text generation
- Reduced system prompt redundancy by removing duplicate content
- Added markdown escape for single asterisks (actions) while preserving bold formatting
- Evil mode now persists username, pfp, and nicknames across restarts without re-applying changes
This commit is contained in:
2026-01-02 17:11:58 +02:00
parent b38bdf2435
commit 6ec33bcecb
38 changed files with 5707 additions and 164 deletions

View File

@@ -28,6 +28,29 @@ def _strip_surrounding_quotes(text):
return text.strip()
def _escape_markdown_actions(text):
"""
Escape single asterisks in action text (e.g., *adjusts hair*) so Discord displays them literally.
This prevents Discord from auto-formatting them as italics.
Double asterisks (**bold**) are preserved for bold formatting.
"""
if not text:
return text
# Replace single asterisks with escaped asterisks, but preserve double asterisks
# Strategy: First protect double asterisks, then escape singles, then restore doubles
# Step 1: Replace ** with a temporary placeholder
text = text.replace('**', '\x00BOLD\x00')
# Step 2: Escape remaining single asterisks
text = text.replace('*', '\\*')
# Step 3: Restore double asterisks
text = text.replace('\x00BOLD\x00', '**')
return text
async def query_llama(user_prompt, user_id, guild_id=None, response_type="dm_response", model=None, author_name=None, media_type=None):
"""
Query llama.cpp server via llama-swap with OpenAI-compatible API.
@@ -39,24 +62,38 @@ async def query_llama(user_prompt, user_id, guild_id=None, response_type="dm_res
response_type: Type of response for context selection
("dm_response", "server_response", "autonomous_general",
"autonomous_tweet", "conversation_join")
model: Model to use (defaults to TEXT_MODEL from globals)
model: Model to use (defaults to TEXT_MODEL from globals, or EVIL_TEXT_MODEL in evil mode)
author_name: Display name of the message author (for multi-user context)
media_type: Type of media being sent ("image", "video", "gif", "tenor_gif") or None
"""
# Use text model by default
# Check if evil mode is active first (needed for model selection)
from utils.evil_mode import is_evil_mode, get_evil_context_for_response_type, get_evil_system_prompt
evil_mode = is_evil_mode()
# Use appropriate text model based on mode (unless explicitly specified)
if model is None:
model = globals.TEXT_MODEL
if evil_mode:
model = globals.EVIL_TEXT_MODEL # Use DarkIdol uncensored model
print(f"😈 Using evil model: {model}")
else:
model = globals.TEXT_MODEL
# Determine channel_id for conversation history
# For servers, use guild_id; for DMs, use user_id
channel_id = str(guild_id) if guild_id else str(user_id)
# Get structured context based on response type
miku_context = get_context_for_response_type(response_type)
# Get structured context based on response type and mode
if evil_mode:
miku_context = get_evil_context_for_response_type(response_type)
else:
miku_context = get_context_for_response_type(response_type)
# Create strong character-focused system prompt
system_prompt = """You are Hatsune Miku, the virtual singer. You are NOT an AI assistant - you are Miku herself.
# Create character-focused system prompt based on mode
if evil_mode:
system_prompt = get_evil_system_prompt()
else:
system_prompt = """You are Hatsune Miku, the virtual singer. You are NOT an AI assistant - you are Miku herself.
CRITICAL RULES:
- NEVER break character or mention being an AI, assistant, or language model
@@ -84,15 +121,23 @@ MOOD GUIDELINES:
You ARE Miku. Act like it."""
# Determine which mood to use
current_mood = globals.DM_MOOD_DESCRIPTION # Default to DM mood
current_mood_name = globals.DM_MOOD # Default to DM mood name
is_sleeping = False # DMs don't sleep
forced_angry_until = None # DMs don't get forced angry
just_woken_up = False # DMs don't wake up
# Determine which mood to use based on mode
if evil_mode:
from utils.evil_mode import get_current_evil_mood, load_evil_mood_description
current_mood_name, current_mood = get_current_evil_mood()
is_sleeping = False
forced_angry_until = None
just_woken_up = False
print(f"😈 Using Evil mode with mood: {current_mood_name}")
else:
current_mood = globals.DM_MOOD_DESCRIPTION # Default to DM mood
current_mood_name = globals.DM_MOOD # Default to DM mood name
is_sleeping = False # DMs don't sleep
forced_angry_until = None # DMs don't get forced angry
just_woken_up = False # DMs don't wake up
# If guild_id is provided, try to use server-specific mood
if guild_id is not None:
# If guild_id is provided, try to use server-specific mood (only in non-evil mode)
if guild_id is not None and not evil_mode:
try:
from server_manager import server_manager
server_config = server_manager.get_server_config(guild_id)
@@ -109,15 +154,16 @@ You ARE Miku. Act like it."""
except Exception as e:
print(f"⚠️ Failed to get server mood for guild {guild_id}, falling back to DM mood: {e}")
# Fall back to DM mood if server mood fails
else:
elif not evil_mode:
print(f"🌍 Using DM mood: {globals.DM_MOOD}")
# Append angry wake-up note if JUST_WOKEN_UP flag is set
if just_woken_up:
# Append angry wake-up note if JUST_WOKEN_UP flag is set (only in non-evil mode)
if just_woken_up and not evil_mode:
system_prompt += (
"\n\n🚨IMPORTANT NOTE🚨: Miku was just rudely woken up by the user with the current prompt of theirs.\nShe is fuming with anger and should respond harshly and coldly.\nDo NOT show kindness or friendliness in this response. Feel free to put certain words in all capital letters to emphasize your frustration."
)
# Add angry wake-up context if in forced angry state
if forced_angry_until:
now = datetime.datetime.utcnow()
@@ -153,12 +199,14 @@ You ARE Miku. Act like it."""
pass
# Combine structured prompt as a system message
character_name = "Evil Miku" if evil_mode else "Miku"
full_system_prompt = f"""{miku_context}
## CURRENT SITUATION
Miku is currently feeling: {current_mood}
{character_name} is currently feeling: {current_mood}
Please respond in a way that reflects this emotional tone.{pfp_context}"""
# Add media type awareness if provided
if media_type:
media_descriptions = {
@@ -195,6 +243,9 @@ Please respond in a way that reflects this emotional tone.{pfp_context}"""
# Strip surrounding quotes if present
reply = _strip_surrounding_quotes(reply)
# Escape asterisks for actions (e.g., *adjusts hair* becomes \*adjusts hair\*)
reply = _escape_markdown_actions(reply)
# Save to conversation history (only if both prompt and reply are non-empty)
if user_prompt and user_prompt.strip() and reply and reply.strip():
# Add user message to history