This commit is contained in:
AlxAI 2025-06-02 20:33:36 -04:00
parent fa17592e75
commit 9b24abef53
6 changed files with 162 additions and 2 deletions

View file

@ -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=

View file

@ -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 |

View file

@ -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 || ''
} }

View file

@ -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);
} }

View 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);
}
}

View file

@ -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}")