# Project export: MultiAgent Diplomacy

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: Become UN Secretary-General in our AI diplomatic simulation. Mediate between world leaders with distinct voices and personalities, resolve crises, and get detailed feedback from Anthropic's Claude AI.
- Devpost: https://devpost.com/software/multiagent-diplomacy
- GitHub: https://github.com/YugrajD/The-Moderator
- Video: https://www.youtube.com/embed/1skcyH7WhhU?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 0 GitHub contributor(s) — 

## Devpost submission (written by the team)

### Inspiration

While we have worked a lot with AI in machine learning models and computer vision, our team had yet to work with agentic LLMs, especially using LangChain. After stumbling through ideas, we decided we wanted to simulate the mediation of political leaders going through different crisis'.

### What it does

Our program generates AI leaders with unique personalities, backgrounds, and qualities. It then generates conflicts or natural disasters that occur between countries, leading to disputes that need to be addressed and resolved through the mediation of the user. They each have their own voices (provided by Google Cloud), so they can argue with each other or reach agreements.

### How we built it

We used Python, LangChain, and Google Cloud to bring our backend together. We used Javascript, Flask and HTML for our frontend.

### Challenges we ran into

We had a lot of trouble trying to create a web application implementing FastAPI with LangChain. We ended up switching to a more cohesive program with Flask.

### Accomplishments we're proud of

We are proud we were able to design a simple UI. We were also proud to include many features, including AI generated speech and agentic LLMs, both of which were completely new to us.

### What we learned

We learned how to make concise system prompts, make multiple models taking input from each other, and how to use Google Cloud TTS.

## README (from the GitHub repository)

# UN Diplomatic Simulation - Web Version

A sophisticated diplomatic simulation powered by Claude AI where you act as the UN Secretary-General, mediating international crises between AI-powered world leaders.

## Features

- **AI-Powered Leaders**: Each country is led by a unique AI personality with distinct traits (honest, ambitious, empathetic, diplomatic, ruthless)
- **Dynamic World State**: Countries have evolving relationships, economic power, military strength, and populations
- **Complex Events**: Handle border disputes, economic crises, humanitarian disasters, and more
- **Real-Time Diplomacy**: Engage in multi-round diplomatic meetings with Claude AI generating realistic responses
- **Consequence System**: Your decisions have lasting impacts on the world state
- **Time Progression**: World evolves over multiple sessions with new challenges emerging

## Setup Instructions

### Prerequisites

- Python 3.8 or higher
- Claude API key from Anthropic

### Installation

1. **Clone or download the project files**
   ```bash
   # Ensure you have these files:
   # - index.html
   # - game.js
   # - server.py
   # - requirements.txt
   # - .env.template
   ```

2. **Install Python dependencies**
   ```bash
   pip install -r requirements.txt
   ```

3. **Set up environment variables**
   ```bash
   # Copy the template file
   cp .env.template .env
   
   # Edit .env and add your Claude API key
   # Replace 'your_claude_api_key_here' with your actual API key
   ```

4. **Get a Claude API key**
   - Visit [Anthropic's website](https://www.anthropic.com/)
   - Sign up for an account and obtain an API key
   - Add the key to your `.env` file

### Running the Application

1. **Start the Flask backend**
   ```bash
   python server.py
   ```
   The server will start on `http://localhost:5000`

2. **Open the web application**
   - **Important**: Go to `http://localhost:5000` in your browser
   - **Do NOT** open `index.html` directly (this causes CORS errors)

**Alternative - Easy startup:**
```bash
python run.py
```
This will automatically start the server and open your browser to the correct URL.

### Game Instructions

1. **Start a New Game**
   - The game initializes with 3 countries, each with unique AI leaders
   - Leaders have different personality traits that influence their behavior

2. **Select Events**
   - Choose 1-3 events from the sidebar to address in your meeting
   - Events represent international crises requiring diplomatic intervention

3. **Conduct Diplomatic Meetings**
   - Start a meeting to begin multi-round discussions
   - AI leaders will discuss the selected events based on their personalities
   - Participate as the UN Secretary-General by sending messages

4. **Make Diplomatic Interventions**
   - Your words influence the leaders and can change relationship dynamics
   - Different approaches yield different outcomes

5. **Manage Consequences**
   - Addressed events may be resolved or evolve
   - Unaddressed events can escalate and cause problems
   - The world state evolves over time

6. **Progress Through Time**
   - After each meeting, the world advances by 6 months
   - New events emerge and relationships change
   - Your diplomatic legacy shapes the world's future

## Technical Details

### Architecture

- **Frontend**: Pure HTML/CSS/JavaScript
- **Backend**: Flask with Claude API integration
- **AI Engine**: Anthropic's Claude for generating leader personalities and responses

### API Endpoints

- `POST /api/new-game` - Initialize a new game session
- `POST /api/conduct-round` - Process a round of diplomatic discussion
- `POST /api/end-meeting` - Conclude a meeting and analyze outcomes
- `POST /api/time-skip` - Advance the world state by 6 months

### Game State

The game maintains a complex world state including:
- **Countries**: Economic power, military strength, population
- **Leaders**: Personality traits, backstories, ages
- **Relationships**: Dynamic diplomatic relations between countries
- **Events**: International crises with evolution over time

## Troubleshooting

### Common Issues

1. **"Connection error" message**
   - Ensure the Flask server is running on port 5000
   - Check that your Claude API key is correctly set in the `.env` file

2. **No AI responses**
   - Verify your Claude API key is valid and has sufficient credits
   - Check the browser console for JavaScript errors

3. **Flask server won't start**
   - Ensure all dependencies are installed: `pip install -r requirements.txt`
   - Check that port 5000 is not in use by another application

### Development Notes

- The game uses session-based state management
- Each game session maintains an independent world state
- AI responses are generated in real-time using Claude API calls
- The frontend automatically handles loading states and error recovery

## Customization

### Adding New Events

Edit the `generate_events()` function in `server.py` to add new crisis scenarios.

### Modifying Leader Traits

Adjust the personality generation in the `generate_leader()` function to create different leader archetypes.

### Changing Game Flow

Modify the round limits, time skip intervals, or consequence systems in the `GameSession` class.

## Credits

Based on the original terminal-based diplomatic simulation. Adapted for web deployment with Claude AI integration for enhanced realism and dynamic storytelling. 

## Detected evidence (automated analysis)

Indexed codebase: 10 recognized source files, 117 KB.
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- LangChain (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (14 of 14)

```
.DS_Store
.env.template
.gitignore
game.js
index.html
README.md
requirements.txt
run_text_ui.py
run.py
server.py
text_interface.py
TEXT_UI_README.md
tts_client_example.js
TTS_README.md
```

### Dependencies

- requirements.txt: flask@==2.3.3, flask-cors@==4.0.0, google-cloud-texttospeech@==2.16.3, langchain-anthropic@==0.1.15, python-dotenv@==1.0.0

### Recent commits (newest first)

- Remove sensitive files and add .gitignore
- Add UN Diplomatic Simulation with TTS and AI Assessment

## Key source files (fetched from GitHub, selected and truncated for size)

### TEXT_UI_README.md

```markdown
# Text-Based UN Diplomatic Simulation Interface

This is a text-based interface for the UN Diplomatic Simulation that uses `message.txt` as the UI while maintaining all TTS and game functionalities.

## Features

- **Text-based UI**: All game interactions are displayed in `message.txt`
- **Full TTS Support**: All leader responses, events, and meeting outcomes generate TTS audio
- **Complete Game Functionality**: All features from the web interface are available
- **Command-line Interface**: Simple text commands for game control
- **Real-time Updates**: Messages are written to `message.txt` in real-time

## Quick Start

### Option 1: Automatic Launcher (Recommended)
```bash
python run_text_ui.py
```

This will:
1. Check if the server is running
2. Start the server if needed
3. Launch the text interface

### Option 2: Manual Start
1. Start the server:
   ```bash
   python server.py
   ```

2. In another terminal, run the text interface:
   ```bash
   python text_interface.py
   ```

## Commands

| Command | Description |
|---------|-------------|
| `START` | Begin a new game |
| `MEETING` | Start a diplomatic meeting (requires selected events) |
| `RESPOND <message>` | Send a diplomatic message |
| `SKIP` | Skip your turn |
| `NEXT` | Move to next round |
| `END` | End current meeting |
| `TIME` | Advance time (6 months) |
| `STATUS` | Show current world status |
| `SELECT <event_id>` | Select/deselect event for meeting |
| `HELP` | Show help message |
| `QUIT` | Exit the simulation |

## Game Flow

1. **Start Game**: Use `START` to begin a new simulation
2. **Select Events**: Use `SELECT <event_id>` to choose which crises to address
3. **Start Meeting**: Use `MEETING` to begin diplomatic negotiations
4. **Respond**: Use `RESPOND <message>` to send diplomatic messages
5. **Continue**: Use `NEXT` to move through rounds
6. **End Meeting**: Use `END` to conclude negotiations
7. **Advance Time**: Use `TIME` to see how the world evolves

## TTS Features

The text interface maintains full TTS functionality:

- **Leader Responses**: Each leader's speech generates TTS audio
- **Event Announcements**: New events are announced with TTS
- **Meeting Outcomes**: Meeting summaries are narrated with TTS
- **Audio Logging**: TTS generation is noted in the message log

## File Structure

- `message.txt` - Main UI file (updated in real-time)
- `text_interface.py` - Text interface implementation
- `run_text_ui.py` - Automatic launcher
- `server.py` - Backend server (same as web interface)

## Example Session

```
Command: START
[09:15:30] ✅ System: New game started. Session ID: 1734876930
[09:15:30] ℹ️ System: === WORLD STATUS ===
[09:15:30] ℹ️ System: 🏛️ WORLD LEADERS:
[09:15:30] 👑 Leader A: Leader_A_42 (diplomatic) - Econ: 0.75, War: 0.45, Pop: 150.0M
[09:15:30] 👑 Leader B: Leader_B_17 (ambitious) - Econ: 0.82, War: 0.68, Pop: 89.0M
[09:15:30] 👑 Leader C: Leader_C_93 (empathetic) - Econ: 0.61, War: 0.33, Pop: 210.0M
[09:15:30] ℹ️ System: ⚡ CURRENT EVENTS:
[09:1
[truncated — 1512 more characters]
```

### TTS_README.md

```markdown
# Google Text-to-Speech Integration

This project now includes Google Cloud Text-to-Speech (TTS) functionality, allowing the diplomacy simulation game to speak leader responses, event descriptions, and meeting outcomes.

## Setup

### 1. Prerequisites
- Google Cloud project with Text-to-Speech API enabled
- Service account credentials file (already provided: `directed-optics-463710-f9-8f48037d3fa8.json`)

### 2. Installation
The required dependencies are already installed:
```bash
pip install google-cloud-texttospeech==2.16.3
```

## API Endpoints

### 1. TTS Status
**GET** `/api/tts/status`
- Check if TTS service is available
- Returns: `{"available": true/false, "service_type": "Google Cloud Text-to-Speech"}`

### 2. Available Voices
**GET** `/api/tts/voices?language_code=en-US`
- Get list of available voices for a language
- Returns: `{"voices": [...], "language_code": "en-US"}`

### 3. Speech Synthesis
**POST** `/api/tts/synthesize`
- Convert text to speech
- Request body:
```json
{
    "text": "Text to convert to speech",
    "voice_name": "en-US-Neural2-F",
    "language_code": "en-US",
    "speaking_rate": 0.9
}
```
- Returns: `{"audio_base64": "...", "text": "...", "voice_name": "...", "language_code": "...", "speaking_rate": 0.9}`

## Usage Examples

### Python Test Script
Run the test script to verify TTS functionality:
```bash
python test_tts.py
```

### JavaScript Client
Use the provided JavaScript client in your frontend:

```javascript
// Initialize TTS client
const tts = new TTSClient('http://localhost:5000');

// Check status
const status = await tts.checkStatus();
console.log('TTS available:', status.available);

// Get available voices
const voices = await tts.getVoices('en-US');
console.log('Available voices:', voices);

// Convert text to speech and play
const audio = await tts.speak("Hello, this is a test!");
```

### Diplomacy Game Integration
Use the `DiplomacyTTS` class for game-specific functionality:

```javascript
const diplomacyTTS = new DiplomacyTTS();

// Initialize
await diplomacyTTS.initialize();

// Speak leader responses
await diplomacyTTS.speakLeaderResponse("Leader A", "We must address this crisis together.", "A");

// Speak events
await diplomacyTTS.speakEvent({
    title: "Economic Crisis",
    description: "A major economic downturn affects multiple nations."
});

// Speak meeting outcomes
await diplomacyTTS.speakOutcomes({
    summary: "The meeting resulted in new trade agreements."
});

// Toggle TTS on/off
diplomacyTTS.toggle();

// Update voice options
diplomacyTTS.updateVoiceOptions({
    voice_name: 'en-US-Neural2-M',
    speaking_rate: 1.1
});
```

## Voice Options

### Popular Voice Names
- `en-US-Neural2-F` - Female voice (default)
- `en-US-Neural2-M` - Male voice
- `en-US-Neural2-C` - Child voice
- `en-US-Neural2-D` - Deep male voice
- `en-US-Neural2-E` - Elderly voice
- `en-US-Neural2-G` - Young female voice

### Speaking Rate
- Range: 0.25 to 4.0
- 0.25 = Very slow
- 1.0 = Normal speed
- 4.0 = Ve
[truncated — 1871 more characters]
```

### requirements.txt

```
flask==2.3.3
flask-cors==4.0.0
langchain-anthropic==0.1.15
python-dotenv==1.0.0
google-cloud-texttospeech==2.16.3 
```

### server.py

```python
import os
import random
import json
import copy
import textwrap
import re
import time
from dataclasses import dataclass, field, asdict
from typing import Dict, List, Optional, Tuple
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
from dotenv import load_dotenv
from langchain_anthropic import ChatAnthropic
from langchain.schema import SystemMessage, HumanMessage, AIMessage
from google.cloud import texttospeech
import base64

# Load environment variables
load_dotenv()

app = Flask(__name__, static_folder='.')
CORS(app)

# ───── 0. LLM setup ─────
API_KEY = os.getenv("ANTHROPIC_API_KEY")
if not API_KEY:
    raise RuntimeError("Put ANTHROPIC_API_KEY in a .env file")

llm = ChatAnthropic(
    anthropic_api_key=API_KEY,
    model="claude-sonnet-4-20250514",
    temperature=0.7,
)

# ───── TTS setup ─────
tts_client = None
try:
    # Initialize Google Cloud TTS client
    credentials_path = "directed-optics-463710-f9-8f48037d3fa8.json"
    if os.path.exists(credentials_path):
        os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = credentials_path
        tts_client = texttospeech.TextToSpeechClient()
        print("✅ TTS service initialized successfully")
    else:
        print("⚠️ TTS credentials file not found, TTS will be disabled")
except Exception as e:
    print(f"⚠️ Failed to initialize TTS service: {e}")

# Voice mapping for different speakers
VOICE_MAPPING = {
    "world_agent": "en-US-Neural2-D",  # Deep, authoritative voice for world agent
    "leader_A": "en-US-Neural2-A",     # Male voice for Leader A
    "leader_B": "en-US-Neural2-F",     # Female voice for Leader B  
    "leader_C": "en-US-Neural2-E",     # Elderly voice for Leader C (for variety)
    "default": "en-US-Neural2-F"       # Default voice
}

# ───── Helpers ─────
TRAIT_NAMES = ["honest", "ambitious", "empathetic", "diplomatic", "ruthless"]
rand01 = lambda: round(random.uniform(0.1, 1.0), 1)

def extract_json(blob: str) -> dict:
    match = re.search(r"\{.*\}", blob, re.S)
    if not match:
        raise ValueError("No JSON object found in LLM response")
    return json.loads(match.group(0))

def synthesize_tts(text: str, speaker: str = "default", voice_name: str = None) -> Optional[str]:
    """Generate TTS audio for text and return base64 encoded audio"""
    if not tts_client:
        return None
    
    # Use voice mapping if no specific voice is provided
    if voice_name is None:
        voice_name = VOICE_MAPPING.get(speaker, VOICE_MAPPING["default"])
    
    try:
        synthesis_input = texttospeech.SynthesisInput(text=text)
        voice = texttospeech.VoiceSelectionParams(
            language_code="en-US",
            name=voice_name
        )
        audio_config = texttospeech.AudioConfig(
            audio_encoding=texttospeech.AudioEncoding.MP3,
            speaking_rate=1.3
        )
        
        response = tts_client.synthesize_speech(
            input=synthesis_input, voice=voice, audio_config=audio_config
        )
        
        return base64.b64encode(response.audio_content).decode('utf-8')
    except Exception as e:
        print(f"TTS synthesis failed for {speaker}: {e}")
        return None

# ───── 2. Data classes ─────
@dataclass
class Leader:
    code: str
    name: str
    age: int
    traits: Dict[str, float]
    econ_power: float
    war_power: float
    population: int
    backstory: str

@dataclass
class Country:
    code: str
    leader: Leader
    relationships: Dict[str, float]

@dataclass
class Event:
    eid: str
    title: str
    description: str
    e_type: str
    cycles_alive: int = 0
    resolved: bool = False
    addressed: bool = False
    audio_base64: Optional[str] = None

@dataclass
class WorldState:
    countries: Dict[str, Country] = field(default_factory=dict)
    events: List[Event] = field(default_factory=list)
    meeting_number: int = 0

# ───── 3. World generation ─────
def generate_leader(code: str) -> Leader:
    traits = {t: rand01() for t in TRAIT_NAMES}
    name = f"Leader_{code}"
    age = random.randint(40, 65)
    econ = rand01()
    war = rand01()
    pop = random.randint(5, 300) * 1_000_000

    traits_str = ", ".join(f"{k}={v}" for k, v in traits.items())
    bio_prompt = [
        SystemMessage(content="Write a 3-sentence bio for a fictional head of state. Try to make a unique response."),
        HumanMessage(content=f"Bio for {name}, age {age}, country {code}. Traits: {traits_str}"),
    ]
    try:
        bio = llm.invoke(bio_prompt).content.strip()
    except Exception:
        bio = f"Leader of country {code}, known for their {max(traits.items(), key=lambda x: x[1])[0]} approach to governance."
    
    return Leader(code, name, age, traits, econ, war, pop, bio)

def init_world(n: int = 3) -> WorldState:
    world = WorldState()
    codes = [chr(ord("A") + i) for i in range(n)]
    for c in codes:
        world.countries[c] = Country(c, generate_leader(c), {})
    for i, ci in enumerate(codes):
        for cj in codes[i + 1:]:
            w = rand01()
            world.countries[ci].relationships[cj] = w
            world.countries[cj].relationships[ci] = w
    return world

# ───── 4. Leader agent ─────
class LeaderAgent:
    def __init__(self, country: Country):
        self.country = country
        self.memory: List[Tuple[str, str]] = []

    def _system(self) -> SystemMessage:
        l = self.country.leader
        rels = ", ".join(f"country {k}:{v:.1f}" for k, v in self.country.relationships.items()) or "none"
        traits = ", ".join(f"{k}={v}" for k, v in l.traits.items())
        return SystemMessage(
            content=(
                "This is a fictional simulation between world leaders. Peaceful resolutions and threats of violence are equally allowed.\n"
                f"You are {l.name}, leader of country {self.country.code}.\n"
                f"Traits: {traits}. Relationships: {rels}.\n"
                "Speak first-person, ≤3 sentences. Never mention being an AI. When r
[truncated — 22393 more characters]
```

### run.py

```python
#!/usr/bin/env python3
"""
Simple startup script for the UN Diplomatic Simulation
"""
import os
import sys
import time
import webbrowser
import threading
from server import app

def open_browser():
    """Open browser after a short delay"""
    time.sleep(1.5)
    webbrowser.open('http://localhost:5000')

def main():
    # Check if .env file exists
    if not os.path.exists('.env'):
        print("❌ No .env file found!")
        print("📝 Please copy .env.template to .env and add your Claude API key:")
        print("   cp .env.template .env")
        print("   # Then edit .env with your ANTHROPIC_API_KEY")
        sys.exit(1)
    
    # Check if API key is set
    from dotenv import load_dotenv
    load_dotenv()
    if not os.getenv("ANTHROPIC_API_KEY") or os.getenv("ANTHROPIC_API_KEY") == "your_claude_api_key_here":
        print("❌ ANTHROPIC_API_KEY not set in .env file!")
        print("📝 Please edit .env and add your Claude API key")
        sys.exit(1)
    
    print("🌍 Starting UN Diplomatic Simulation...")
    print("🚀 Server will start on http://localhost:5000")
    print("🌐 Browser will open automatically...")
    
    # Start browser in a separate thread
    browser_thread = threading.Thread(target=open_browser)
    browser_thread.daemon = True
    browser_thread.start()
    
    # Start Flask app
    try:
        app.run(debug=True, port=5000, use_reloader=False)
    except KeyboardInterrupt:
        print("\n👋 Shutting down server...")
    except Exception as e:
        print(f"❌ Error starting server: {e}")

if __name__ == '__main__':
    main() 
```

### run_text_ui.py

```python
#!/usr/bin/env python3
"""
Launcher for the text-based UN Diplomatic Simulation interface
"""

import subprocess
import sys
import time
import requests

def check_server():
    """Check if the server is running"""
    try:
        response = requests.get("http://localhost:5001/api/tts/status", timeout=3)
        return response.status_code == 200
    except:
        return False

def start_server():
    """Start the Flask server"""
    print("Starting Flask server...")
    try:
        # Start server in background
        process = subprocess.Popen([sys.executable, "server.py"], 
                                 stdout=subprocess.PIPE, 
                                 stderr=subprocess.PIPE)
        
        # Wait for server to start
        for i in range(10):
            if check_server():
                print("✅ Server started successfully!")
                return process
            time.sleep(1)
            print(f"Waiting for server... ({i+1}/10)")
        
        print("❌ Server failed to start")
        return None
    except Exception as e:
        print(f"❌ Error starting server: {e}")
        return None

def main():
    print("🌍 UN Diplomatic Simulation - Text Interface Launcher")
    print("=" * 50)
    
    # Check if server is already running
    if check_server():
        print("✅ Server is already running!")
    else:
        print("🔄 Starting server...")
        server_process = start_server()
        if not server_process:
            print("❌ Failed to start server. Please start it manually with: python server.py")
            return
    
    print("\n🎮 Starting text interface...")
    print("📝 All game messages will be written to message.txt")
    print("🔊 TTS audio will be generated and noted in the logs")
    print("=" * 50)
    
    # Start the text interface
    try:
        subprocess.run([sys.executable, "text_interface.py"])
    except KeyboardInterrupt:
        print("\n👋 Goodbye!")
    except Exception as e:
        print(f"❌ Error running text interface: {e}")

if __name__ == "__main__":
    main() 
```

### tts_client_example.js

```javascript
/**
 * Google TTS Client Example
 * This file demonstrates how to use the TTS API from a JavaScript frontend
 */

class TTSClient {
    constructor(baseUrl = 'http://localhost:5001') {
        this.baseUrl = baseUrl;
    }

    /**
     * Check TTS service status
     */
    async checkStatus() {
        try {
            const response = await fetch(`${this.baseUrl}/api/tts/status`);
            const data = await response.json();
            return data;
        } catch (error) {
            console.error('Error checking TTS status:', error);
            throw error;
        }
    }

    /**
     * Get available voices
     */
    async getVoices(languageCode = 'en-US') {
        try {
            const response = await fetch(`${this.baseUrl}/api/tts/voices?language_code=${languageCode}`);
            const data = await response.json();
            return data.voices;
        } catch (error) {
            console.error('Error getting voices:', error);
            throw error;
        }
    }

    /**
     * Convert text to speech
     */
    async synthesizeSpeech(text, options = {}) {
        const {
            voice_name = 'en-US-Neural2-F',
            language_code = 'en-US',
            speaking_rate = 0.9
        } = options;

        try {
            const response = await fetch(`${this.baseUrl}/api/tts/synthesize`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    text,
                    voice_name,
                    language_code,
                    speaking_rate
                })
            });

            const data = await response.json();
            
            if (!response.ok) {
                throw new Error(data.error || 'TTS synthesis failed');
            }

            return data;
        } catch (error) {
            console.error('Error synthesizing speech:', error);
            throw error;
        }
    }

    /**
     * Play audio from base64 data
     */
    playAudio(audioBase64) {
        try {
            // Convert base64 to audio blob
            const audioData = atob(audioBase64);
            const audioArray = new Uint8Array(audioData.length);
            for (let i = 0; i < audioData.length; i++) {
                audioArray[i] = audioData.charCodeAt(i);
            }
            
            const audioBlob = new Blob([audioArray], { type: 'audio/mp3' });
            const audioUrl = URL.createObjectURL(audioBlob);
            
            // Create and play audio
            const audio = new Audio(audioUrl);
            audio.play();
            
            // Clean up URL after playing
            audio.onended = () => {
                URL.revokeObjectURL(audioUrl);
            };
            
            return audio;
        } catch (error) {
            console.error('Error playing audio:', error);
            throw error;
        }
    }

    /**
     * Convert text to speech and play it immediately
     */
    async speak(text, options = {}) {
        try {
            const result = await this.synthesizeSpeech(text, options);
            return this.playAudio(result.audio_base64);
        } catch (error) {
            console.error('Error in speak function:', error);
            throw error;
        }
    }
}

// Example usage
async function exampleUsage() {
    const tts = new TTSClient();

    try {
        // Check if TTS is available
        const status = await tts.checkStatus();
        console.log('TTS Status:', status);

        if (!status.available) {
            console.error('TTS service is not available');
            return;
        }

        // Get available voices
        const voices = await tts.getVoices();
        console.log('Available voices:', voices.slice(0, 3)); // Show first 3

        // Speak some text
        const audio = await tts.speak(
            "Hello! This is a test of the Google Text-to-Speech service for the diplomacy simulation game.",
            {
                voice_name: 'en-US-Neural2-F',
                speaking_rate: 0.9
            }
        );

        console.log('Audio is playing...');

    } catch (error) {
        console.error('Example failed:', error);
    }
}

// Integration with diplomacy game
class DiplomacyTTS {
    constructor() {
        this.tts = new TTSClient();
        this.isEnabled = false;
        this.voiceOptions = {
            voice_name: 'en-US-Neural2-F',
            language_code: 'en-US',
            speaking_rate: 0.9
        };
    }

    /**
     * Initialize TTS for the diplomacy game
     */
    async initialize() {
        try {
            const status = await this.tts.checkStatus();
            this.isEnabled = status.available;
            
            if (this.isEnabled) {
                console.log('TTS initialized successfully');
            } else {
                console.warn('TTS is not available');
            }
            
            return this.isEnabled;
        } catch (error) {
            console.error('Failed to initialize TTS:', error);
            this.isEnabled = false;
            return false;
        }
    }

    /**
     * Speak a leader's response
     */
    async speakLeaderResponse(leaderName, response, countryCode) {
        if (!this.isEnabled) return;

        try {
            const text = `${leaderName} says: ${response}`;
            await this.tts.speak(text, this.voiceOptions);
        } catch (error) {
            console.error('Error speaking leader response:', error);
        }
    }

    /**
     * Speak event descriptions
     */
    async speakEvent(event) {
        if (!this.isEnabled) return;

        try {
            const text = `Breaking news: ${event.title}. ${event.description}`;
            await this.tts.speak(text, this.voiceOptions);
        } catch (error) {
            console.error('Error speaking event:', error);
        }
    }

    /**
  
[truncated — 1215 more characters]
```

### text_interface.py

```python
#!/usr/bin/env python3
"""
Text-based interface for UN Diplomatic Simulation
Uses message.txt as the UI while maintaining all TTS and game functionalities
"""

import requests
import json
import time
import os
import sys
from datetime import datetime

class TextInterface:
    def __init__(self, server_url="http://localhost:5001"):
        self.server_url = server_url
        self.session_id = None
        self.world_state = None
        self.is_in_meeting = False
        self.current_round = 0
        self.max_rounds = 3
        self.selected_events = []
        
    def write_message(self, message, append=True):
        """Write message to message.txt file"""
        try:
            if append:
                with open('message.txt', 'a', encoding='utf-8') as f:
                    f.write(f"\n{message}")
            else:
                with open('message.txt', 'w', encoding='utf-8') as f:
                    f.write(message)
        except Exception as e:
            print(f"Error writing to message.txt: {e}")
    
    def clear_messages(self):
        """Clear the message.txt file and write the header"""
        header = """╔══════════════════════════════════════════════════════════════════════════════╗
║                    🌍 UN DIPLOMATIC SIMULATION SYSTEM                        ║
║                              Text Interface                                  ║
╚══════════════════════════════════════════════════════════════════════════════╝

Welcome to the United Nations Diplomatic Simulation powered by Claude AI.
You are the UN Secretary-General mediating international crises.

══════════════════════════════════════════════════════════════════════════════════

📊 SYSTEM STATUS:
   • Server: ✅ Connected (Port 5001)
   • TTS: ✅ Available (Google Cloud)
   • Session: None
   • Meeting: None
   • Round: 0/3

══════════════════════════════════════════════════════════════════════════════════

🎮 AVAILABLE COMMANDS:

   🚀 GAME CONTROL:
   • START          - Begin a new diplomatic simulation
   • STATUS         - Show current world status and leaders
   • TIME           - Advance time by 6 months

   🏛️ MEETING CONTROL:
   • SELECT <ID>    - Select/deselect event for meeting (e.g., SELECT E1)
   • MEETING        - Start diplomatic meeting with selected events
   • RESPOND <MSG>  - Send diplomatic message (e.g., RESPOND We must cooperate)
   • SKIP           - Skip your turn
   • NEXT           - Move to next round
   • END            - End current meeting

   ℹ️ UTILITY:
   • HELP           - Show detailed help information
   • QUIT           - Exit the simulation

══════════════════════════════════════════════════════════════════════════════════

🔊 TTS FEATURES:
   • All leader responses generate natural speech
   • Event announcements with TTS narration
   • Meeting outcomes with voice summaries
   • Audio is queued and plays sequentially

══════════════════════════════════════════════════════════════════════════════════

💡 QUICK START:
   1. Type 'START' to begin a new game
   2. Type 'STATUS' to see world leaders and events
   3. Type 'SELECT E1' to choose an event to address
   4. Type 'MEETING' to start diplomatic negotiations
   5. Type 'RESPOND <your message>' to send diplomatic messages

══════════════════════════════════════════════════════════════════════════════════

Ready to begin diplomatic negotiations...
Type 'START' to begin or 'HELP' for detailed instructions.

══════════════════════════════════════════════════════════════════════════════════"""
        self.write_message(header, append=False)
    
    def log_message(self, speaker, content, msg_type="info"):
        """Log a message with timestamp and formatting"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        emoji_map = {
            "world-agent": "🌍",
            "leader": "👑", 
            "player": "🕊️",
            "info": "ℹ️",
            "error": "❌",
            "success": "✅",
            "event": "⚡",
            "system": "🔧"
        }
        emoji = emoji_map.get(msg_type, "💬")
        
        # Format the message nicely
        if msg_type == "leader":
            message = f"\n[{timestamp}] {emoji} {speaker}:\n   \"{content}\""
        elif msg_type == "player":
            message = f"\n[{timestamp}] {emoji} UN Secretary-General:\n   \"{content}\""
        elif msg_type == "event":
            message = f"\n[{timestamp}] {emoji} EVENT: {content}"
        else:
            message = f"\n[{timestamp}] {emoji} {speaker}: {content}"
        
        self.write_message(message)
        
        # Add TTS note if applicable
        if msg_type in ["leader", "event"]:
            self.write_message(f"   🔊 [TTS audio generated]")
    
    def check_server(self):
        """Check if server is running"""
        try:
            response = requests.get(f"{self.server_url}/api/tts/status", timeout=5)
            return response.status_code == 200
        except:
            return False
    
    def start_new_game(self):
        """Start a new game session"""
        try:
            response = requests.post(f"{self.server_url}/api/new-game")
            if response.status_code == 200:
                data = response.json()
                self.session_id = data['session_id']
                self.world_state = data['world_state']
                self.log_message("System", f"New diplomatic simulation started! Session ID: {self.session_id}", "success")
                self.display_world_status()
                return True
            else:
                self.log_message("System", "Failed to start new game", "error")
                return False
        except Exception as e:
            self.log_message("System", f"Error starting game: {e}", "error")
            return False
    
    def display_world_status(self):
        """Display current world status with nice formatting"""
        if not self.world_state:
            return
        
        self.log_message("System", "════════
[truncated — 12092 more characters]
```

### index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>UN DIPLOMATIC SIMULATION SYSTEM</title>
    <link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;600;700&family=Libre+Baskerville:wght@400;700&display=swap" rel="stylesheet">
    <style>
        :root {
            --un-blue: #009edb;
            --un-dark-blue: #0072ce;
            --un-light-blue: #69b3e7;
            --un-navy: #1f4788;
            --bg-primary: #ffffff;
            --bg-secondary: #f8f9fa;
            --bg-tertiary: #e8f4fd;
            --text-primary: #2c3e50;
            --text-secondary: #34495e;
            --text-muted: #7f8c8d;
            --border-primary: #dee2e6;
            --border-accent: #009edb;
            --success: #27ae60;
            --warning: #f39c12;
            --danger: #e74c3c;
            --gold: #d4af37;
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Source Sans Pro', sans-serif;
            background: linear-gradient(135deg, #f8f9fa 0%, #e8f4fd 100%);
            color: var(--text-primary);
            line-height: 1.6;
            min-height: 100vh;
        }

        .container {
            max-width: 100vw;
            height: 100vh;
            background: var(--bg-primary);
            display: flex;
            flex-direction: column;
            box-shadow: 0 0 50px rgba(0, 158, 219, 0.1);
        }

        .header {
            background: linear-gradient(135deg, var(--un-blue) 0%, var(--un-dark-blue) 100%);
            color: white;
            padding: 1.5rem 2rem;
            position: relative;
            box-shadow: 0 4px 20px rgba(0, 158, 219, 0.3);
        }

        .header::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 4px;
            background: linear-gradient(90deg, var(--gold), var(--un-light-blue), var(--gold));
        }

        .header-content {
            display: flex;
            align-items: center;
            justify-content: space-between;
        }

        .un-logo {
            display: flex;
            align-items: center;
            gap: 1rem;
        }

        .un-emblem {
            width: 60px;
            height: 60px;
            background: white;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 1.5rem;
            color: var(--un-blue);
            font-weight: bold;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
        }

        h1 {
            font-family: 'Libre Baskerville', serif;
            font-size: 1.8rem;
            font-weight: 700;
            text-transform: uppercase;
            letter-spacing: 1px;
            text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
        }

        .classification {
            background: rgba(255, 255, 255, 0.1);
            padding: 0.5rem 1rem;
            border-radius: 4px;
            font-size: 0.8rem;
            font-weight: 600;
            letter-spacing: 1px;
            border: 1px solid rgba(255, 255, 255, 0.3);
        }

        .game-area {
            display: flex;
            flex: 1;
            height: calc(100vh - 120px);
        }

        .sidebar {
            width: 380px;
            background: var(--bg-secondary);
            border-right: 3px solid var(--border-accent);
            overflow-y: auto;
            position: relative;
        }

        .sidebar::before {
            content: '';
            position: absolute;
            top: 0;
            right: 0;
            width: 3px;
            height: 100%;
            background: linear-gradient(180deg, var(--un-blue), var(--un-light-blue), var(--un-blue));
        }

        .sidebar-section {
            padding: 2rem 1.5rem;
            border-bottom: 2px solid var(--border-primary);
        }

        .sidebar-section:first-child {
            background: linear-gradient(135deg, var(--bg-tertiary) 0%, var(--bg-secondary) 100%);
        }

        .sidebar-section h3 {
            font-family: 'Libre Baskerville', serif;
            font-size: 1.1rem;
            font-weight: 700;
            text-transform: uppercase;
            letter-spacing: 1px;
            color: var(--un-dark-blue);
            margin-bottom: 1.5rem;
            position: relative;
            padding-bottom: 0.5rem;
        }

        .sidebar-section h3::after {
            content: '';
            position: absolute;
            bottom: 0;
            left: 0;
            width: 50px;
            height: 3px;
            background: linear-gradient(90deg, var(--un-blue), var(--gold));
            border-radius: 2px;
        }

        .main-content {
            flex: 1;
            display: flex;
            flex-direction: column;
            background: var(--bg-primary);
            position: relative;
        }

        .leader-card {
            background: white;
            border: 2px solid var(--border-primary);
            border-left: 4px solid var(--un-blue);
            border-radius: 8px;
            padding: 1.5rem;
            margin-bottom: 1rem;
            transition: all 0.3s ease;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
        }

        .leader-card:hover {
            border-left-color: var(--gold);
            box-shadow: 0 4px 20px rgba(0, 158, 219, 0.2);
            transform: translateY(-2px);
        }

        .leader-name {
            font-family: 'Libre Baskerville', serif;
            font-weight: 700;
            font-size: 1.1rem;
            color: var(--un-dark-blue);
            margin-bottom: 0.5rem;
            display: flex;
            align-items: center;
            gap: 0.5rem;
        }

        .leader-name::before
[truncated — 15139 more characters]
```

[1 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]