mirror of
https://github.com/GoodStartLabs/AI_Diplomacy.git
synced 2026-04-19 12:58:09 +00:00
webhooks
This commit is contained in:
parent
fa17592e75
commit
9b24abef53
6 changed files with 162 additions and 2 deletions
|
|
@ -1,2 +1,10 @@
|
||||||
# rename me to .env and change below keys
|
# rename me to .env and change below keys
|
||||||
STREAM_KEY="live_XXXXXXXXXX"
|
STREAM_KEY="live_XXXXXXXXXX"
|
||||||
|
DEEPSEEK_API_KEY=
|
||||||
|
OPENAI_API_KEY=
|
||||||
|
ANTHROPIC_API_KEY=
|
||||||
|
GEMINI_API_KEY=
|
||||||
|
VITE_ELEVENLABS_API_KEY =
|
||||||
|
ELEVENLABS_API_KEY =
|
||||||
|
OPENROUTER_API_KEY=
|
||||||
|
VITE_WEBHOOK_URL=
|
||||||
|
|
@ -3,3 +3,4 @@
|
||||||
| Problem | Attempted Solution | Real Outcome | Current $ Balance |
|
| Problem | Attempted Solution | Real Outcome | Current $ Balance |
|
||||||
| :----------------------------------------------------------- | :----------------- | :----------- | :---------------- |
|
| :----------------------------------------------------------- | :----------------- | :----------- | :---------------- |
|
||||||
| 1. Relationships chart is blank. <br> 2. Game stops after narrator summary in phase 2. | 1. Updated `PhaseSchema` in `types/gameState.ts` to include `agent_relationships` definition. <br> 2. Uncommented `updateChatWindows(currentPhase, true);` in `phase.ts`. | | $0 |
|
| 1. Relationships chart is blank. <br> 2. Game stops after narrator summary in phase 2. | 1. Updated `PhaseSchema` in `types/gameState.ts` to include `agent_relationships` definition. <br> 2. Uncommented `updateChatWindows(currentPhase, true);` in `phase.ts`. | | $0 |
|
||||||
|
| Add webhook notifications for phase changes | 1. Added `webhookUrl` config to `src/config.ts` <br> 2. Created `src/webhooks/phaseNotifier.ts` with fire-and-forget webhook notification <br> 3. Added `notifyPhaseChange()` call in `_setPhase()` function <br> 4. Updated `.env.example` with `VITE_WEBHOOK_URL` | | $0 |
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,8 @@ export const config = {
|
||||||
soundEffectFrequency: 3,
|
soundEffectFrequency: 3,
|
||||||
|
|
||||||
// Whether speech/TTS is enabled (can be toggled via debug menu)
|
// Whether speech/TTS is enabled (can be toggled via debug menu)
|
||||||
speechEnabled: import.meta.env.VITE_DEBUG_MODE ? false : true
|
speechEnabled: import.meta.env.VITE_DEBUG_MODE ? false : true,
|
||||||
|
|
||||||
|
// Webhook URL for phase change notifications (optional)
|
||||||
|
webhookUrl: import.meta.env.VITE_WEBHOOK_URL || ''
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { config } from "./config";
|
||||||
import { debugMenuInstance } from "./debug/debugMenu";
|
import { debugMenuInstance } from "./debug/debugMenu";
|
||||||
import { showTwoPowerConversation, closeTwoPowerConversation } from "./components/twoPowerConversation";
|
import { showTwoPowerConversation, closeTwoPowerConversation } from "./components/twoPowerConversation";
|
||||||
import { PowerENUM } from "./types/map";
|
import { PowerENUM } from "./types/map";
|
||||||
|
import { notifyPhaseChange } from "./webhooks/phaseNotifier";
|
||||||
|
|
||||||
const MOMENT_THRESHOLD = 8.0
|
const MOMENT_THRESHOLD = 8.0
|
||||||
// If we're in debug mode, show it quick, otherwise show it for 30 seconds
|
// If we're in debug mode, show it quick, otherwise show it for 30 seconds
|
||||||
|
|
@ -17,6 +18,11 @@ const MOMENT_DISPLAY_TIMEOUT_MS = config.isDebugMode ? 5000 : 30000
|
||||||
|
|
||||||
// FIXME: Going to previous phases is borked. Units do not animate properly, map doesn't update.
|
// FIXME: Going to previous phases is borked. Units do not animate properly, map doesn't update.
|
||||||
export function _setPhase(phaseIndex: number) {
|
export function _setPhase(phaseIndex: number) {
|
||||||
|
console.log(`[Phase] _setPhase called with index: ${phaseIndex}`);
|
||||||
|
|
||||||
|
// Store the old phase index at the very beginning
|
||||||
|
const oldPhaseIndex = gameState.phaseIndex;
|
||||||
|
|
||||||
if (config.isDebugMode) {
|
if (config.isDebugMode) {
|
||||||
debugMenuInstance.updateTools()
|
debugMenuInstance.updateTools()
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +64,9 @@ export function _setPhase(phaseIndex: number) {
|
||||||
|
|
||||||
// Finally, update the gameState with the current phaseIndex
|
// Finally, update the gameState with the current phaseIndex
|
||||||
gameState.phaseIndex = phaseIndex
|
gameState.phaseIndex = phaseIndex
|
||||||
|
|
||||||
|
// Send webhook notification for phase change
|
||||||
|
notifyPhaseChange(oldPhaseIndex, phaseIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
140
ai_animation/src/webhooks/phaseNotifier.ts
Normal file
140
ai_animation/src/webhooks/phaseNotifier.ts
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
import { config } from '../config';
|
||||||
|
import { gameState } from '../gameState';
|
||||||
|
|
||||||
|
// Test webhook URL validity on startup
|
||||||
|
testWebhookUrl().catch(err => console.error("Webhook test failed:", err));
|
||||||
|
|
||||||
|
async function testWebhookUrl() {
|
||||||
|
const webhookUrl = config.webhookUrl || import.meta.env.VITE_WEBHOOK_URL || '';
|
||||||
|
|
||||||
|
if (!webhookUrl) {
|
||||||
|
console.log("⚠️ No webhook URL configured (optional feature)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// For Discord webhooks, we can test with a GET request
|
||||||
|
if (webhookUrl.includes('discord.com/api/webhooks')) {
|
||||||
|
const response = await fetch(webhookUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const webhookInfo = await response.json();
|
||||||
|
console.log(`✅ Discord webhook is valid and ready (${webhookInfo.name || 'Unnamed webhook'})`);
|
||||||
|
} else if (response.status === 401) {
|
||||||
|
console.error(`❌ Discord webhook invalid: Unauthorized (check webhook URL)`);
|
||||||
|
} else {
|
||||||
|
console.error(`❌ Discord webhook error: ${response.status}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For non-Discord webhooks, just validate the URL format
|
||||||
|
try {
|
||||||
|
new URL(webhookUrl);
|
||||||
|
console.log(`✅ Webhook URL is valid: ${webhookUrl.substring(0, 50)}...`);
|
||||||
|
} catch {
|
||||||
|
console.error(`❌ Invalid webhook URL format`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Webhook connection error:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a webhook notification when a phase changes
|
||||||
|
* This is a fire-and-forget operation that won't block the UI
|
||||||
|
*/
|
||||||
|
export async function notifyPhaseChange(oldPhaseIndex: number, newPhaseIndex: number): Promise<void> {
|
||||||
|
console.log(`[Webhook] Phase change detected: ${oldPhaseIndex} -> ${newPhaseIndex}`);
|
||||||
|
|
||||||
|
// Skip if no webhook URL is configured
|
||||||
|
if (!config.webhookUrl) {
|
||||||
|
console.log('[Webhook] No webhook URL configured, skipping notification');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if game data is not loaded
|
||||||
|
if (!gameState.gameData || !gameState.gameData.phases) {
|
||||||
|
console.warn('[Webhook] Game data not loaded, cannot send notification');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPhase = gameState.gameData.phases[newPhaseIndex];
|
||||||
|
if (!currentPhase) {
|
||||||
|
console.warn(`[Webhook] Phase at index ${newPhaseIndex} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine direction of phase change
|
||||||
|
let direction: 'forward' | 'backward' | 'jump';
|
||||||
|
if (newPhaseIndex === oldPhaseIndex + 1) {
|
||||||
|
direction = 'forward';
|
||||||
|
} else if (newPhaseIndex === oldPhaseIndex - 1) {
|
||||||
|
direction = 'backward';
|
||||||
|
} else {
|
||||||
|
direction = 'jump';
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
event: 'phase_change',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
game_id: gameState.gameId || 0,
|
||||||
|
phase_index: newPhaseIndex,
|
||||||
|
phase_name: currentPhase.name,
|
||||||
|
phase_year: currentPhase.year || parseInt(currentPhase.name.substring(1, 5)) || null,
|
||||||
|
is_playing: gameState.isPlaying,
|
||||||
|
direction: direction,
|
||||||
|
total_phases: gameState.gameData.phases.length
|
||||||
|
};
|
||||||
|
|
||||||
|
// Discord webhooks need a different format
|
||||||
|
const isDiscordWebhook = config.webhookUrl.includes('discord.com/api/webhooks');
|
||||||
|
const webhookPayload = isDiscordWebhook ? {
|
||||||
|
content: `Phase Change: **${currentPhase.name}** (${direction})`,
|
||||||
|
embeds: [{
|
||||||
|
title: "AI Diplomacy Phase Update",
|
||||||
|
color: direction === 'forward' ? 0x00ff00 : direction === 'backward' ? 0xff0000 : 0x0000ff,
|
||||||
|
fields: [
|
||||||
|
{ name: "Phase", value: currentPhase.name, inline: true },
|
||||||
|
{ name: "Year", value: String(payload.phase_year || "Unknown"), inline: true },
|
||||||
|
{ name: "Direction", value: direction, inline: true },
|
||||||
|
{ name: "Game ID", value: String(payload.game_id), inline: true },
|
||||||
|
{ name: "Phase Index", value: `${newPhaseIndex}/${payload.total_phases}`, inline: true },
|
||||||
|
{ name: "Auto-playing", value: payload.is_playing ? "Yes" : "No", inline: true }
|
||||||
|
],
|
||||||
|
timestamp: payload.timestamp
|
||||||
|
}]
|
||||||
|
} : payload;
|
||||||
|
|
||||||
|
console.log(`[Webhook] Sending notification for phase ${currentPhase.name} to ${config.webhookUrl}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fire and forget - we don't await this
|
||||||
|
fetch(config.webhookUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(webhookPayload)
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
console.log(`[Webhook] ✅ Successfully sent notification for phase ${currentPhase.name}`);
|
||||||
|
} else {
|
||||||
|
console.warn(`[Webhook] ❌ Failed with status ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// Log errors but don't let them break the animation
|
||||||
|
console.error('[Webhook] ❌ Network error:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (config.isDebugMode) {
|
||||||
|
console.log('[Webhook] Debug - Full payload:', payload);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Catch any synchronous errors (shouldn't happen with fetch)
|
||||||
|
console.error('[Webhook] ❌ Unexpected error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -623,7 +623,6 @@ async def main():
|
||||||
try:
|
try:
|
||||||
current_year = int(current_year_str)
|
current_year = int(current_year_str)
|
||||||
consolidation_year = current_year - 2 # Two years ago
|
consolidation_year = current_year - 2 # Two years ago
|
||||||
|
|
||||||
logger.info(f"[DIARY CONSOLIDATION] Current year: {current_year}, Consolidation year: {consolidation_year}")
|
logger.info(f"[DIARY CONSOLIDATION] Current year: {current_year}, Consolidation year: {consolidation_year}")
|
||||||
logger.info(f"[DIARY CONSOLIDATION] Phase check - ends with 'M': {current_short_phase.endswith('M')}, starts with 'S': {current_short_phase.startswith('S')}")
|
logger.info(f"[DIARY CONSOLIDATION] Phase check - ends with 'M': {current_short_phase.endswith('M')}, starts with 'S': {current_short_phase.startswith('S')}")
|
||||||
logger.info(f"[DIARY CONSOLIDATION] Consolidation year check: {consolidation_year} >= 1901: {consolidation_year >= 1901}")
|
logger.info(f"[DIARY CONSOLIDATION] Consolidation year check: {consolidation_year} >= 1901: {consolidation_year >= 1901}")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue