feat: Add advanced engagement submenu with user targeting and engagement types

- Replace simple 'Engage Random User' button with expandable submenu
- Add user ID input field for targeting specific users
- Add engagement type selection: random, activity-based, general, status-based
- Update API endpoints to accept user_id and engagement_type parameters
- Modify autonomous functions to support targeted engagement
- Maintain backward compatibility with random user/type selection as default
This commit is contained in:
2025-12-16 23:13:19 +02:00
parent dfcda72cc8
commit b38bdf2435
3 changed files with 217 additions and 40 deletions

View File

@@ -268,20 +268,37 @@ async def trigger_autonomous_general(guild_id: int = None):
return {"status": "error", "message": "Bot not ready"} return {"status": "error", "message": "Bot not ready"}
@app.post("/autonomous/engage") @app.post("/autonomous/engage")
async def trigger_autonomous_engage_user(guild_id: int = None): async def trigger_autonomous_engage_user(guild_id: int = None, user_id: str = None, engagement_type: str = None):
# If guild_id is provided, send autonomous engagement only to that server # If guild_id is provided, send autonomous engagement only to that server
# If no guild_id, send to all servers (legacy behavior) # If no guild_id, send to all servers (legacy behavior)
# user_id: Optional specific user to engage (Discord user ID as string)
# engagement_type: Optional type - 'activity', 'general', 'status', or None for random
if globals.client and globals.client.loop and globals.client.loop.is_running(): if globals.client and globals.client.loop and globals.client.loop.is_running():
if guild_id is not None: if guild_id is not None:
# Send to specific server only # Send to specific server only
from utils.autonomous import miku_engage_random_user_for_server from utils.autonomous import miku_engage_random_user_for_server
globals.client.loop.create_task(miku_engage_random_user_for_server(guild_id)) globals.client.loop.create_task(miku_engage_random_user_for_server(guild_id, user_id=user_id, engagement_type=engagement_type))
return {"status": "ok", "message": f"Autonomous user engagement queued for server {guild_id}"}
# Build detailed message
msg_parts = [f"Autonomous user engagement queued for server {guild_id}"]
if user_id:
msg_parts.append(f"targeting user {user_id}")
if engagement_type:
msg_parts.append(f"with {engagement_type} engagement")
return {"status": "ok", "message": " ".join(msg_parts)}
else: else:
# Send to all servers (legacy behavior) # Send to all servers (legacy behavior)
from utils.autonomous import miku_engage_random_user from utils.autonomous import miku_engage_random_user
globals.client.loop.create_task(miku_engage_random_user()) globals.client.loop.create_task(miku_engage_random_user(user_id=user_id, engagement_type=engagement_type))
return {"status": "ok", "message": "Autonomous user engagement queued for all servers"}
msg_parts = ["Autonomous user engagement queued for all servers"]
if user_id:
msg_parts.append(f"targeting user {user_id}")
if engagement_type:
msg_parts.append(f"with {engagement_type} engagement")
return {"status": "ok", "message": " ".join(msg_parts)}
else: else:
return {"status": "error", "message": "Bot not ready"} return {"status": "error", "message": "Bot not ready"}
@@ -841,12 +858,20 @@ async def trigger_autonomous_general_for_server(guild_id: int):
return {"status": "error", "message": f"Failed to trigger autonomous message: {e}"} return {"status": "error", "message": f"Failed to trigger autonomous message: {e}"}
@app.post("/servers/{guild_id}/autonomous/engage") @app.post("/servers/{guild_id}/autonomous/engage")
async def trigger_autonomous_engage_for_server(guild_id: int): async def trigger_autonomous_engage_for_server(guild_id: int, user_id: str = None, engagement_type: str = None):
"""Trigger autonomous user engagement for a specific server""" """Trigger autonomous user engagement for a specific server"""
from utils.autonomous import miku_engage_random_user_for_server from utils.autonomous import miku_engage_random_user_for_server
try: try:
await miku_engage_random_user_for_server(guild_id) await miku_engage_random_user_for_server(guild_id, user_id=user_id, engagement_type=engagement_type)
return {"status": "ok", "message": f"Autonomous user engagement triggered for server {guild_id}"}
# Build detailed message
msg_parts = [f"Autonomous user engagement triggered for server {guild_id}"]
if user_id:
msg_parts.append(f"targeting user {user_id}")
if engagement_type:
msg_parts.append(f"with {engagement_type} engagement")
return {"status": "ok", "message": " ".join(msg_parts)}
except Exception as e: except Exception as e:
return {"status": "error", "message": f"Failed to trigger user engagement: {e}"} return {"status": "error", "message": f"Failed to trigger user engagement: {e}"}

View File

@@ -702,7 +702,38 @@
</select> </select>
</div> </div>
<button onclick="triggerAutonomous('general')">Say Something General</button> <button onclick="triggerAutonomous('general')">Say Something General</button>
<button onclick="triggerAutonomous('engage')">Engage Random User</button>
<!-- Engage User Submenu -->
<div style="margin-bottom: 1rem;">
<button onclick="toggleEngageSubmenu()">Engage User ▼</button>
<div id="engage-submenu" style="display: none; margin-left: 1rem; margin-top: 0.5rem; padding: 1rem; background: #1e1e1e; border: 1px solid #444; border-radius: 4px;">
<div style="margin-bottom: 0.5rem;">
<label for="engage-user-id" style="display: block; margin-bottom: 0.3rem;">User ID (leave empty for random):</label>
<input type="text" id="engage-user-id" placeholder="User ID" style="width: 200px;">
</div>
<div style="margin-bottom: 0.5rem;">
<label style="display: block; margin-bottom: 0.3rem;">Engagement Type:</label>
<div style="margin-left: 0.5rem;">
<label style="display: block; margin-bottom: 0.2rem;">
<input type="radio" name="engage-type" value="random" checked> Random (auto-detect)
</label>
<label style="display: block; margin-bottom: 0.2rem;">
<input type="radio" name="engage-type" value="activity"> Activity-based (comment on what they're doing)
</label>
<label style="display: block; margin-bottom: 0.2rem;">
<input type="radio" name="engage-type" value="general"> General conversation
</label>
<label style="display: block; margin-bottom: 0.2rem;">
<input type="radio" name="engage-type" value="status"> Status-based (online/idle/invisible)
</label>
</div>
</div>
<button onclick="triggerEngageUser()">🚀 Engage User</button>
</div>
</div>
<button onclick="triggerAutonomous('tweet')">Share Tweet</button> <button onclick="triggerAutonomous('tweet')">Share Tweet</button>
<button onclick="triggerAutonomous('reaction')">React to Message</button> <button onclick="triggerAutonomous('reaction')">React to Message</button>
<button onclick="triggerAutonomous('join-conversation')">Detect and Join Conversation</button> <button onclick="triggerAutonomous('join-conversation')">Detect and Join Conversation</button>
@@ -1967,6 +1998,65 @@ async function triggerAutonomous(actionType) {
} }
} }
// Toggle Engage User Submenu
function toggleEngageSubmenu() {
const submenu = document.getElementById('engage-submenu');
if (submenu.style.display === 'none') {
submenu.style.display = 'block';
} else {
submenu.style.display = 'none';
}
}
// Trigger Engage User with parameters
async function triggerEngageUser() {
const selectedServer = document.getElementById('server-select').value;
const userId = document.getElementById('engage-user-id').value.trim();
const engageType = document.querySelector('input[name="engage-type"]:checked').value;
try {
let endpoint = '/autonomous/engage';
const params = new URLSearchParams();
// Add guild_id if a specific server is selected
if (selectedServer !== 'all') {
params.append('guild_id', selectedServer);
}
// Add user_id if specified
if (userId) {
params.append('user_id', userId);
}
// Add engagement_type if not random
if (engageType !== 'random') {
params.append('engagement_type', engageType);
}
if (params.toString()) {
endpoint += `?${params.toString()}`;
}
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const result = await response.json();
if (response.ok) {
showNotification(result.message || 'Engagement triggered successfully');
// Optionally collapse the submenu after successful trigger
// toggleEngageSubmenu();
} else {
throw new Error(result.message || 'Failed to trigger engagement');
}
} catch (error) {
console.error('Failed to trigger user engagement:', error);
showNotification(error.message || 'Failed to trigger engagement', 'error');
}
}
// Profile Picture Management // Profile Picture Management
async function changeProfilePicture() { async function changeProfilePicture() {
const selectedServer = document.getElementById('server-select').value; const selectedServer = document.getElementById('server-select').value;

View File

@@ -121,8 +121,14 @@ async def miku_say_something_general_for_server(guild_id: int):
except Exception as e: except Exception as e:
print(f"⚠️ Failed to send autonomous message: {e}") print(f"⚠️ Failed to send autonomous message: {e}")
async def miku_engage_random_user_for_server(guild_id: int): async def miku_engage_random_user_for_server(guild_id: int, user_id: str = None, engagement_type: str = None):
"""Miku engages a random user in a specific server""" """Miku engages a random user in a specific server
Args:
guild_id: The server ID
user_id: Optional specific user ID to engage (as string). If None, picks random user
engagement_type: Optional engagement style - 'activity', 'general', 'status', or None for auto-detect
"""
server_config = server_manager.get_server_config(guild_id) server_config = server_manager.get_server_config(guild_id)
if not server_config: if not server_config:
print(f"⚠️ No config found for server {guild_id}") print(f"⚠️ No config found for server {guild_id}")
@@ -138,19 +144,37 @@ async def miku_engage_random_user_for_server(guild_id: int):
print(f"⚠️ Autonomous channel not found for server {guild_id}") print(f"⚠️ Autonomous channel not found for server {guild_id}")
return return
members = [ # Get target user
m for m in guild.members if user_id:
if m.status in {Status.online, Status.idle, Status.dnd} and not m.bot # Target specific user
] try:
target = guild.get_member(int(user_id))
if not target:
print(f"⚠️ User {user_id} not found in server {guild_id}")
return
if target.bot:
print(f"⚠️ Cannot engage bot user {user_id}")
return
print(f"🎯 Targeting specific user: {target.display_name} (ID: {user_id})")
except ValueError:
print(f"⚠️ Invalid user ID: {user_id}")
return
else:
# Pick random user
members = [
m for m in guild.members
if m.status in {Status.online, Status.idle, Status.dnd} and not m.bot
]
if not members:
print(f"😴 No available members to talk to in server {guild_id}.")
return
target = random.choice(members)
print(f"🎲 Randomly selected user: {target.display_name}")
time_of_day = get_time_of_day() time_of_day = get_time_of_day()
if not members:
print(f"😴 No available members to talk to in server {guild_id}.")
return
target = random.choice(members)
# Initialize server-specific user engagements # Initialize server-specific user engagements
if guild_id not in _server_user_engagements: if guild_id not in _server_user_engagements:
_server_user_engagements[guild_id] = {} _server_user_engagements[guild_id] = {}
@@ -176,30 +200,63 @@ async def miku_engage_random_user_for_server(guild_id: int):
is_invisible = target.status == Status.offline is_invisible = target.status == Status.offline
display_name = target.display_name display_name = target.display_name
prompt = ( # Build prompt based on engagement_type
f"Miku is feeling {mood} {emoji} during the {time_of_day}. " prompt = f"Miku is feeling {mood} {emoji} during the {time_of_day}. "
f"She notices {display_name}'s current status is {target.status.name}. "
)
if is_invisible: if engagement_type == 'activity':
# Force activity-based engagement
if activity_name:
prompt += (
f"She notices {display_name} is currently playing or doing: {activity_name}. "
f"Miku wants to comment on this activity and start a friendly conversation about it."
)
else:
prompt += (
f"She wants to ask {display_name} what they're up to or what they like to do for fun."
)
elif engagement_type == 'status':
# Force status-based engagement
prompt += f"She notices {display_name}'s current status is {target.status.name}. "
if is_invisible:
prompt += (
f"Miku suspects that {display_name} is being sneaky and invisible 👻. "
f"She wants to playfully call them out in a fun, teasing, but still affectionate way."
)
else:
prompt += (
f"Miku wants to comment on their current status and start a conversation."
)
elif engagement_type == 'general':
# Force general conversation
prompt += ( prompt += (
f"Miku suspects that {display_name} is being sneaky and invisible 👻. " f"Miku wants to casually start a conversation with {display_name}, "
f"She wants to playfully call them out in a fun, teasing, but still affectionate way. " f"maybe ask how they're doing, what they're up to, or talk about something random."
)
elif activity_name:
prompt += (
f"They appear to be playing or doing: {activity_name}. "
f"Miku wants to comment on this and start a friendly conversation."
) )
else: else:
prompt += ( # Auto-detect (original behavior)
f"Miku wants to casually start a conversation with them, maybe ask how they're doing, what they're up to, or even talk about something random with them." prompt += f"She notices {display_name}'s current status is {target.status.name}. "
) if is_invisible:
prompt += (
f"Miku suspects that {display_name} is being sneaky and invisible 👻. "
f"She wants to playfully call them out in a fun, teasing, but still affectionate way. "
)
elif activity_name:
prompt += (
f"They appear to be playing or doing: {activity_name}. "
f"Miku wants to comment on this and start a friendly conversation."
)
else:
prompt += (
f"Miku wants to casually start a conversation with them, maybe ask how they're doing, what they're up to, or even talk about something random with them."
)
prompt += ( prompt += (
f"\nThe message should be short and reflect Miku's current mood." f"\nThe message should be short and reflect Miku's current mood."
) )
if engagement_type:
print(f"💬 Engagement type: {engagement_type}")
try: try:
# Use consistent user_id for engaging users to enable conversation history # Use consistent user_id for engaging users to enable conversation history
message = await query_llama(prompt, user_id=f"miku-engage-{guild_id}", guild_id=guild_id) message = await query_llama(prompt, user_id=f"miku-engage-{guild_id}", guild_id=guild_id)
@@ -391,10 +448,15 @@ async def miku_say_something_general():
for guild_id in server_manager.servers: for guild_id in server_manager.servers:
await miku_say_something_general_for_server(guild_id) await miku_say_something_general_for_server(guild_id)
async def miku_engage_random_user(): async def miku_engage_random_user(user_id: str = None, engagement_type: str = None):
"""Legacy function - now runs for all servers""" """Legacy function - now runs for all servers
Args:
user_id: Optional specific user ID to engage
engagement_type: Optional engagement style
"""
for guild_id in server_manager.servers: for guild_id in server_manager.servers:
await miku_engage_random_user_for_server(guild_id) await miku_engage_random_user_for_server(guild_id, user_id=user_id, engagement_type=engagement_type)
async def miku_detect_and_join_conversation(force: bool = False): async def miku_detect_and_join_conversation(force: bool = False):
"""Legacy function - now runs for all servers """Legacy function - now runs for all servers