Add webhook option to manual message override for persona selection

Users can now send manual messages as either Hatsune Miku or Evil Miku
via webhooks without needing to toggle Evil Mode. This provides more
flexibility for controlling which persona sends messages.

Features:
- Checkbox option to "Send as Webhook" in manual message section
- Radio buttons to select between Hatsune Miku and Evil Miku
- Both personas use their respective profile pictures and mood emojis
- Webhooks only available for channel messages (not DMs)
- DM option automatically disabled when webhook mode is enabled
- New API endpoint: POST /manual/send-webhook

Frontend Changes:
- Added webhook checkbox and persona selection UI
- toggleWebhookOptions() function to show/hide persona options
- Updated sendManualMessage() to handle webhook mode
- Automatic channel selection when webhook is enabled

Backend Changes:
- New /manual/send-webhook endpoint in api.py
- Integrates with bipolar_mode.py webhook management
- Uses get_or_create_webhooks_for_channel() for webhook creation
- Applies correct display name with mood emoji based on persona
- Supports file attachments via webhook

This allows manual control over which Miku persona sends messages,
useful for testing, demonstrations, or creative scenarios without
needing to switch the entire bot mode.
This commit is contained in:
2026-01-07 10:21:46 +02:00
parent 86a54dd0ba
commit caab444c08
2 changed files with 131 additions and 1 deletions

View File

@@ -997,6 +997,28 @@
<div class="section" id="manual-message-section">
<h3>🎭 Send Message as Miku (Manual Override)</h3>
<!-- Webhook Option -->
<div style="margin-bottom: 1rem; padding: 0.5rem; background: #2a2a2a; border-radius: 4px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="manual-use-webhook" onchange="toggleWebhookOptions()" style="margin-right: 0.5rem;" />
<span>Send as Webhook (allows choosing persona)</span>
</label>
<div id="webhook-persona-options" style="display: none; margin-top: 0.5rem; padding-left: 1.5rem;">
<label style="display: block; margin-bottom: 0.3rem;">
<input type="radio" name="webhook-persona" value="miku" checked style="margin-right: 0.5rem;" />
Hatsune Miku 💙 (with mood emoji)
</label>
<label style="display: block;">
<input type="radio" name="webhook-persona" value="evil" style="margin-right: 0.5rem;" />
Evil Miku 😈 (with mood emoji)
</label>
<p style="font-size: 0.8rem; color: #888; margin: 0.3rem 0 0 0;">
Note: Webhooks only work in channels, not DMs. Profile picture and mood emoji will be used.
</p>
</div>
</div>
<!-- Target Selection -->
<div style="margin-bottom: 1rem;">
<label for="manual-target-type">Target Type:</label>
@@ -2736,6 +2758,27 @@ function toggleCustomPromptTarget() {
}
}
function toggleWebhookOptions() {
const useWebhook = document.getElementById('manual-use-webhook').checked;
const webhookOptions = document.getElementById('webhook-persona-options');
const targetType = document.getElementById('manual-target-type');
if (useWebhook) {
webhookOptions.style.display = 'block';
// Webhooks only work in channels, so switch to channel if DM is selected
if (targetType.value === 'dm') {
targetType.value = 'channel';
toggleManualMessageTarget();
}
// Disable DM option when webhook is enabled
targetType.options[1].disabled = true;
} else {
webhookOptions.style.display = 'none';
// Re-enable DM option
targetType.options[1].disabled = false;
}
}
function toggleManualMessageTarget() {
const targetType = document.getElementById('manual-target-type').value;
const channelSection = document.getElementById('manual-channel-section');
@@ -2899,12 +2942,20 @@ async function sendManualMessage() {
const targetType = document.getElementById('manual-target-type').value;
const replyMessageId = document.getElementById('manualReplyMessageId').value.trim();
const replyMention = document.querySelector('input[name="manualReplyMention"]:checked').value === 'true';
const useWebhook = document.getElementById('manual-use-webhook').checked;
const webhookPersona = document.querySelector('input[name="webhook-persona"]:checked')?.value || 'miku';
if (!message) {
showNotification('Please enter a message', 'error');
return;
}
// Webhooks only work in channels
if (useWebhook && targetType === 'dm') {
showNotification('Webhooks only work in channels, not DMs', 'error');
return;
}
let targetId, endpoint;
if (targetType === 'dm') {
@@ -2920,13 +2971,19 @@ async function sendManualMessage() {
showNotification('Please enter a channel ID', 'error');
return;
}
endpoint = '/manual/send';
// Use webhook endpoint if webhook is enabled
endpoint = useWebhook ? '/manual/send-webhook' : '/manual/send';
}
try {
const formData = new FormData();
formData.append('message', message);
// Add webhook persona if using webhook
if (useWebhook) {
formData.append('persona', webhookPersona);
}
// Add reply parameters if message ID is provided
if (replyMessageId) {
formData.append('reply_to_message_id', replyMessageId);