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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user