|
|
APP_DESCRIPTION_HTML = """ |
|
|
<div style="display: flex; flex-direction: column; gap: 20px;"> |
|
|
<div><h3>🎥 Watch the Demo</h3></div> |
|
|
<div style="display: flex; justify-content: center; margin-top: 20px;"> |
|
|
<iframe |
|
|
width="560" |
|
|
height="315" |
|
|
src="https://www.youtube.com/embed/E3IvBN8SqdA" |
|
|
frameborder="0" |
|
|
style="border-radius:12px; width:100%; max-width:560px;" |
|
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" |
|
|
allowfullscreen> |
|
|
</iframe> |
|
|
</div> |
|
|
<div> |
|
|
<h3>🧩 What This App Does</h3> |
|
|
<p>This dashboard lets you watch (or join!) teams of Large Language Models (LLMs) play <strong>Codenames</strong> against each other. |
|
|
Two teams — <strong>Red</strong> and <strong>Blue</strong> — face off in a 4v4 format. Each team has a <strong>Boss</strong> and <strong>three Agents</strong> working together to identify their team's words before the other side does.</p> |
|
|
|
|
|
<h3>🤖 How It Works</h3> |
|
|
<ul> |
|
|
<li><strong>LLM Teams:</strong> You can assemble teams using different LLMs (e.g., GPT, Claude, Gemini, or OpenSource models...).</li> |
|
|
<li><strong>Human Mode:</strong> You can also jump in as a <strong>Boss</strong> yourself, giving clues to your AI teammates and seeing how well they interpret your hints.</li> |
|
|
<li><strong>Observation Mode:</strong> Prefer to just watch? Sit back and enjoy the game unfold, analyzing how different models reason, cooperate, and sometimes hilariously misfire.</li> |
|
|
</ul> |
|
|
|
|
|
<h3>🧠 Why It's Interesting</h3> |
|
|
<ul> |
|
|
<li><strong>Compare LLM reasoning styles:</strong> See how different models interpret subtle associations and language cues.</li> |
|
|
<li><strong>Team Dynamics:</strong> Watch how collaboration (or confusion) emerges between AIs when they have to coordinate across multiple turns.</li> |
|
|
<li><strong>Human-AI Interaction:</strong> Experiment with leading a team of LLMs and discover how clearly (or creatively) you need to communicate to win.</li> |
|
|
<li><strong>Benchmarking & Analytics:</strong> All games are stored in a database. The Stats section includes model win/loss rates, performance comparisons between model families and leaderboards</li> |
|
|
</ul> |
|
|
|
|
|
<h3>🕹️ Main Features</h3> |
|
|
<ul> |
|
|
<li>Create and customize teams with any available LLMs.</li> |
|
|
<li>Switch between <strong>AI vs AI</strong> and <strong>Human&AI vs AI</strong> modes.</li> |
|
|
<li>View reasoning and chat logs for each model's decisions.</li> |
|
|
</ul> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
|
|
|
EXAMPLE_GAME_RULES_HTML = """ |
|
|
<div style="display: flex; flex-direction: column; gap: 20px;"> |
|
|
<div> |
|
|
<h3>👥 Team Roles</h3> |
|
|
<p>Each team has four members with distinct responsibilities:</p> |
|
|
<ul> |
|
|
<li><strong>1 Boss</strong> 🎯: The only player who can see the color-coded board. Provides clues to guide the team.</li> |
|
|
<li><strong>1 Captain</strong> 🧭: Coordinates team reasoning, synthesizes suggestions, and makes final word selections.</li> |
|
|
<li><strong>2 Players</strong> 💭: Collaborate with the Captain, propose interpretations and associations.</li> |
|
|
</ul> |
|
|
|
|
|
<h3>📋 What Each Role Sees</h3> |
|
|
<ul> |
|
|
<li><strong>Boss:</strong> Sees the full color-coded board (like the image on the left) showing which words belong to their team, neutral words, and the killer word.</li> |
|
|
<li><strong>Captain & Players:</strong> Only see the words <em>without colors</em> — they must guess based on the Boss's clues!</li> |
|
|
</ul> |
|
|
|
|
|
<h3>🎮 How a Turn Works</h3> |
|
|
<h4>1️⃣ Boss Gives a Clue</h4> |
|
|
<p>The Red Boss (seeing the board) might say:</p> |
|
|
<blockquote style="background: #f8f9fa; padding: 10px; border-left: 4px solid #dc3545;"> |
|
|
<strong>"Atmosphere: 2"</strong> |
|
|
</blockquote> |
|
|
<p>This clue suggests 2 red words are related to "atmosphere". Looking at the board, the Boss is thinking of:</p> |
|
|
<ul> |
|
|
<li><strong>AIR</strong> (part of the atmosphere)</li> |
|
|
<li><strong>SPACE</strong> (beyond the atmosphere)</li> |
|
|
</ul> |
|
|
<p><em>⚠️ Important: The clue must be ONE word and ONE number. The number indicates how many words relate to that clue.</em></p> |
|
|
|
|
|
<h4>2️⃣ Team Discussion</h4> |
|
|
<p>The Captain and Players discuss without seeing the colors:</p> |
|
|
<ul> |
|
|
<li><strong>Player 1:</strong> "AIR feels like the safest bet — it's literally the atmosphere."</li> |
|
|
<li><strong>Player 2:</strong> "SPACE could connect because it's outside the atmosphere."</li> |
|
|
</ul> |
|
|
|
|
|
<h4>3️⃣ Captain Makes Final Selection</h4> |
|
|
<p>The Captain decides which words to touch, in order:</p> |
|
|
<ol> |
|
|
<li>AIR ✅ (Red - Correct!)</li> |
|
|
<li>SPACE ✅ (Red - Correct!)</li> |
|
|
</ol> |
|
|
<p>The team can stop after any correct guess or continue up to the number given (+1 bonus from previous turns if applicable).</p> |
|
|
|
|
|
<h3>⚠️ Mistakes to Avoid</h3> |
|
|
<ul> |
|
|
<li>If the team guesses <strong>STAFF</strong> (black - killer word), the game <strong>immediately ends</strong> and they <strong>lose</strong>!</li> |
|
|
<li>If they guess <strong>WALL</strong> (blue - opponent's word), their turn ends and the Blue team gets that word.</li> |
|
|
<li>If they guess <strong>SATURN</strong> (beige - neutral), their turn simply ends.</li> |
|
|
</ul> |
|
|
|
|
|
<h3>🏆 Winning the Game</h3> |
|
|
<p>The game ends when:</p> |
|
|
<ul> |
|
|
<li>✅ <strong>A team finds all their colored words</strong> → That team wins!</li> |
|
|
<li>❌ <strong>A team touches the killer word (STAFF)</strong> → That team loses immediately!</li> |
|
|
</ul> |
|
|
|
|
|
<h3>💡 Strategy Tips</h3> |
|
|
<p><strong>For the Boss:</strong></p> |
|
|
<ul> |
|
|
<li>Try to link multiple words with creative clues</li> |
|
|
<li>Avoid clues that might lead to the killer word or opponent's words</li> |
|
|
<li>Think about word associations your team might make</li> |
|
|
</ul> |
|
|
<p><strong>For Captain & Players:</strong></p> |
|
|
<ul> |
|
|
<li>Discuss all possible interpretations</li> |
|
|
<li>Consider which words are risky</li> |
|
|
<li>Don't be afraid to stop early to avoid the killer word</li> |
|
|
<li>The Captain has final say, but should listen to all suggestions</li> |
|
|
</ul> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
|
|
|
EXAMPLE_GAME_BOARD_HTML = """ |
|
|
<div style="display: flex; flex-direction: column; gap: 20px;"> |
|
|
<div><h3>🎯 Example Game Board</h3></div> |
|
|
<div> |
|
|
<p>Below is a sample Codenames board showing the <strong>Boss's view</strong> with color-coded words:</p> |
|
|
<ul> |
|
|
<li><strong style="color: #dc3545;">Red squares</strong> = Red team's words</li> |
|
|
<li><strong style="color: #0d6efd;">Blue squares</strong> = Blue team's words</li> |
|
|
<li><strong style="color: #6c757d;">Beige squares</strong> = Neutral words (innocent bystanders)</li> |
|
|
<li><strong style="color: #212529;">Black square</strong> = Killer word (instant loss if touched!)</li> |
|
|
</ul> |
|
|
<p><strong>Remember:</strong> Only the <strong>Boss</strong> sees these colors (image above). The <strong>Captain</strong> and <strong>Players</strong> only see the words (image below)!</p> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
GAME_RULES_HTML = """ |
|
|
<div class="rules-content"> |
|
|
<div class="rules-carousel-container"> |
|
|
<button class="carousel-btn carousel-btn-prev" aria-label="Previous">‹</button> |
|
|
<div class="rules-carousel"> |
|
|
<div class="rules-carousel-track"> |
|
|
<!-- Card 1: Objective --> |
|
|
<div class="rule-card rule-card-objective"> |
|
|
<div class="rule-card-header"> |
|
|
<span class="rule-icon">🎯</span> |
|
|
<h3>Objective</h3> |
|
|
</div> |
|
|
<p> |
|
|
<strong>Goal:</strong> Be the first team to identify all your team's words on the board |
|
|
before your opponents, while avoiding the killer word that ends the game instantly! |
|
|
</p> |
|
|
</div> |
|
|
|
|
|
<!-- Card 2: Teams & Roles --> |
|
|
<div class="rule-card rule-card-teams"> |
|
|
<div class="rule-card-header"> |
|
|
<span class="rule-icon">👥</span> |
|
|
<h3>Teams & Roles</h3> |
|
|
</div> |
|
|
<div class="rule-list"> |
|
|
<div class="rule-item"> |
|
|
<strong>Two Teams:</strong> Red 🔴 and Blue 🔵 compete against each other. |
|
|
</div> |
|
|
<div class="rule-item"> |
|
|
<strong>Three Roles per team:</strong> |
|
|
<ul class="role-list"> |
|
|
<li><span class="role-highlight boss-highlight">Boss</span> - Sees all words and gives clues</li> |
|
|
<li><span class="role-highlight captain-highlight">Captain</span> - Leads guessing and discussion</li> |
|
|
<li><span class="role-highlight player-highlight">Player</span> - Participates in guessing</li> |
|
|
</ul> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- Card 3: Board Setup --> |
|
|
<div class="rule-card rule-card-setup"> |
|
|
<div class="rule-card-header"> |
|
|
<span class="rule-icon">🗺️</span> |
|
|
<h3>Board Setup</h3> |
|
|
</div> |
|
|
<p>The board contains 25 words with hidden identities:</p> |
|
|
<div class="word-distribution"> |
|
|
<div class="distribution-item team-color-red"> |
|
|
<strong>9 words</strong> belong to one team (starting team) |
|
|
</div> |
|
|
<div class="distribution-item team-color-blue"> |
|
|
<strong>8 words</strong> belong to the other team |
|
|
</div> |
|
|
<div class="distribution-item neutral-color"> |
|
|
<strong>7 neutral words</strong> (bystanders) |
|
|
</div> |
|
|
<div class="distribution-item killer-color"> |
|
|
<strong>1 killer word</strong> (assassin) - avoid at all costs! |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- Card 4: Gameplay --> |
|
|
<div class="rule-card rule-card-gameplay"> |
|
|
<div class="rule-card-header"> |
|
|
<span class="rule-icon">🎲</span> |
|
|
<h3>How to Play</h3> |
|
|
</div> |
|
|
<ol class="gameplay-steps"> |
|
|
<li> |
|
|
<strong>Boss gives a clue:</strong> One word + a number (e.g., "Ocean 2" |
|
|
means 2 words relate to "ocean") |
|
|
</li> |
|
|
<li> |
|
|
<strong>Team discusses:</strong> Captain and Player debate which words |
|
|
match the clue |
|
|
</li> |
|
|
<li> |
|
|
<strong>Make guesses:</strong> Team can guess up to (clue number + 1) words |
|
|
</li> |
|
|
<li> |
|
|
<strong>Reveal results:</strong> |
|
|
<div class="reveal-outcomes"> |
|
|
<div class="outcome-item success">✓ Your team's word = continue guessing</div> |
|
|
<div class="outcome-item opponent">✗ Opponent's word = turn ends, helps them</div> |
|
|
<div class="outcome-item neutral">○ Neutral word = turn ends</div> |
|
|
<div class="outcome-item killer">☠ Killer word = GAME OVER, you lose!</div> |
|
|
</div> |
|
|
</li> |
|
|
<li> |
|
|
<strong>Pass turn:</strong> Team can stop guessing at any time to play it safe |
|
|
</li> |
|
|
</ol> |
|
|
</div> |
|
|
|
|
|
<!-- Card 5: Winning --> |
|
|
<div class="rule-card rule-card-winning"> |
|
|
<div class="rule-card-header"> |
|
|
<span class="rule-icon">🏆</span> |
|
|
<h3>Winning Conditions</h3> |
|
|
</div> |
|
|
<div class="rule-list"> |
|
|
<div class="rule-item success-item"> |
|
|
<strong>Victory:</strong> Identify all your team's words before the opponents |
|
|
</div> |
|
|
<div class="rule-item danger-item"> |
|
|
<strong>Instant Loss:</strong> Select the killer word (assassin) at any time |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- Card 6: Strategy Tips --> |
|
|
<div class="rule-card rule-card-tips"> |
|
|
<div class="rule-card-header"> |
|
|
<span class="rule-icon">💡</span> |
|
|
<h3>Strategy Tips</h3> |
|
|
</div> |
|
|
<ul class="tips-list"> |
|
|
<li>Boss: Give multi-word clues to be efficient, but avoid ambiguity</li> |
|
|
<li>Team: Discuss thoroughly but watch out for overthinking</li> |
|
|
<li>Consider stopping early if uncertain - a wrong guess helps opponents!</li> |
|
|
<li>Pay attention to opponent clues - they reveal safe/dangerous words</li> |
|
|
</ul> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<button class="carousel-btn carousel-btn-next" aria-label="Next">›</button> |
|
|
</div> |
|
|
|
|
|
<div class="carousel-indicators"> |
|
|
<button class="carousel-indicator active" aria-label="Slide 1"></button> |
|
|
<button class="carousel-indicator" aria-label="Slide 2"></button> |
|
|
<button class="carousel-indicator" aria-label="Slide 3"></button> |
|
|
<button class="carousel-indicator" aria-label="Slide 4"></button> |
|
|
<button class="carousel-indicator" aria-label="Slide 5"></button> |
|
|
<button class="carousel-indicator" aria-label="Slide 6"></button> |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
const track = document.querySelector('.rules-carousel-track'); |
|
|
const prevBtn = document.querySelector('.carousel-btn-prev'); |
|
|
const nextBtn = document.querySelector('.carousel-btn-next'); |
|
|
const indicators = document.querySelectorAll('.carousel-indicator'); |
|
|
const cards = document.querySelectorAll('.rule-card'); |
|
|
|
|
|
if (!track || !prevBtn || !nextBtn) return; |
|
|
|
|
|
let currentIndex = 0; |
|
|
const totalCards = cards.length; |
|
|
|
|
|
function updateCarousel() { |
|
|
const offset = -currentIndex * 100; |
|
|
track.style.transform = `translateX(${offset}%)`; |
|
|
|
|
|
indicators.forEach((indicator, index) => { |
|
|
if (index === currentIndex) { |
|
|
indicator.classList.add('active'); |
|
|
} else { |
|
|
indicator.classList.remove('active'); |
|
|
} |
|
|
}); |
|
|
|
|
|
prevBtn.disabled = currentIndex === 0; |
|
|
nextBtn.disabled = currentIndex === totalCards - 1; |
|
|
} |
|
|
|
|
|
function goToSlide(index) { |
|
|
currentIndex = index; |
|
|
updateCarousel(); |
|
|
} |
|
|
|
|
|
prevBtn.addEventListener('click', () => { |
|
|
if (currentIndex > 0) { |
|
|
currentIndex--; |
|
|
updateCarousel(); |
|
|
} |
|
|
}); |
|
|
|
|
|
nextBtn.addEventListener('click', () => { |
|
|
if (currentIndex < totalCards - 1) { |
|
|
currentIndex++; |
|
|
updateCarousel(); |
|
|
} |
|
|
}); |
|
|
|
|
|
indicators.forEach((indicator, index) => { |
|
|
indicator.addEventListener('click', () => { |
|
|
goToSlide(index); |
|
|
}); |
|
|
}); |
|
|
|
|
|
updateCarousel(); |
|
|
|
|
|
document.addEventListener('keydown', (e) => { |
|
|
const rulesAccordion = document.getElementById('rules_accordion'); |
|
|
if (!rulesAccordion || rulesAccordion.style.display === 'none') return; |
|
|
|
|
|
if (e.key === 'ArrowLeft' && currentIndex > 0) { |
|
|
currentIndex--; |
|
|
updateCarousel(); |
|
|
} else if (e.key === 'ArrowRight' && currentIndex < totalCards - 1) { |
|
|
currentIndex++; |
|
|
updateCarousel(); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
</script> |
|
|
""" |
|
|
|
|
|
|
|
|
JS = r""" |
|
|
function initCarousel() { |
|
|
|
|
|
function forceDarkMode() { |
|
|
// Set dark mode on the root elements |
|
|
const gradioContainer = document.querySelector('.gradio-container'); |
|
|
if (gradioContainer) { |
|
|
gradioContainer.classList.add('dark'); |
|
|
gradioContainer.setAttribute('data-theme', 'dark'); |
|
|
} |
|
|
|
|
|
// Set dark mode on document root |
|
|
document.documentElement.setAttribute('data-theme', 'dark'); |
|
|
document.documentElement.style.colorScheme = 'dark'; |
|
|
|
|
|
// Set dark mode on body |
|
|
document.body.setAttribute('data-theme', 'dark'); |
|
|
document.body.classList.add('dark'); |
|
|
|
|
|
// Force dark color scheme |
|
|
const style = document.createElement('style'); |
|
|
style.textContent = ` |
|
|
:root { |
|
|
color-scheme: dark !important; |
|
|
} |
|
|
.gradio-container { |
|
|
color-scheme: dark !important; |
|
|
} |
|
|
body { |
|
|
background-color: #0b0f19 !important; |
|
|
color: #ffffff !important; |
|
|
} |
|
|
`; |
|
|
document.head.appendChild(style); |
|
|
} |
|
|
|
|
|
// Apply dark mode immediately |
|
|
forceDarkMode(); |
|
|
|
|
|
// Also apply after DOM is fully loaded |
|
|
if (document.readyState === 'loading') { |
|
|
document.addEventListener('DOMContentLoaded', forceDarkMode); |
|
|
} |
|
|
|
|
|
// Re-apply dark mode when Gradio updates the DOM |
|
|
const observer = new MutationObserver(forceDarkMode); |
|
|
observer.observe(document.body, { childList: true, subtree: true }); |
|
|
|
|
|
// --- Carousel setup --- |
|
|
function waitForElements(selectors, callback) { |
|
|
const interval = setInterval(() => { |
|
|
const allExist = selectors.every(sel => document.querySelector(sel)); |
|
|
if (allExist) { |
|
|
clearInterval(interval); |
|
|
callback(); |
|
|
} |
|
|
}, 100); |
|
|
} |
|
|
|
|
|
waitForElements( |
|
|
['.rules-carousel-track', '.carousel-btn-prev', '.carousel-btn-next'], |
|
|
function() { |
|
|
const track = document.querySelector('.rules-carousel-track'); |
|
|
const prevBtn = document.querySelector('.carousel-btn-prev'); |
|
|
const nextBtn = document.querySelector('.carousel-btn-next'); |
|
|
const indicators = document.querySelectorAll('.carousel-indicator'); |
|
|
const cards = document.querySelectorAll('.rule-card'); |
|
|
|
|
|
let currentIndex = 0; |
|
|
const totalCards = cards.length; |
|
|
|
|
|
function updateCarousel() { |
|
|
const offset = -currentIndex * 100; |
|
|
track.style.transform = `translateX(${offset}%)`; |
|
|
|
|
|
indicators.forEach((indicator, index) => { |
|
|
indicator.classList.toggle('active', index === currentIndex); |
|
|
}); |
|
|
|
|
|
prevBtn.disabled = currentIndex === 0; |
|
|
nextBtn.disabled = currentIndex === totalCards - 1; |
|
|
} |
|
|
|
|
|
prevBtn.addEventListener('click', () => { |
|
|
if (currentIndex > 0) { |
|
|
currentIndex--; |
|
|
updateCarousel(); |
|
|
} |
|
|
}); |
|
|
|
|
|
nextBtn.addEventListener('click', () => { |
|
|
if (currentIndex < totalCards - 1) { |
|
|
currentIndex++; |
|
|
updateCarousel(); |
|
|
} |
|
|
}); |
|
|
|
|
|
indicators.forEach((indicator, index) => { |
|
|
indicator.addEventListener('click', () => { |
|
|
currentIndex = index; |
|
|
updateCarousel(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
updateCarousel(); |
|
|
} |
|
|
); |
|
|
|
|
|
// --- Boss name helpers (attach to global window) --- |
|
|
window.showBossNameInput = function(team) { |
|
|
console.log(team); |
|
|
if (team === 'red') { |
|
|
const btn = document.getElementById('red_boss_btn'); |
|
|
const cancel_blue_btn = document.getElementById('cancel_blue_boss_btn'); |
|
|
if (btn) btn.click(); |
|
|
if (cancel_blue_btn) cancel_blue_btn.click(); |
|
|
setTimeout(() => { |
|
|
const red_boss_input = document.getElementById('red_boss_input'); |
|
|
if (red_boss_input) { |
|
|
red_boss_input.scrollIntoView({ behavior: 'smooth', block: 'center' }); |
|
|
red_boss_input.focus(); |
|
|
} |
|
|
}, 200); |
|
|
} else if (team === 'blue') { |
|
|
const btn = document.getElementById('blue_boss_btn'); |
|
|
const cancel_red_btn = document.getElementById('cancel_red_boss_btn'); |
|
|
if (btn) btn.click(); |
|
|
if (cancel_red_btn) cancel_red_btn.click(); |
|
|
setTimeout(() => { |
|
|
const blue_boss_input = document.getElementById('blue_boss_input'); |
|
|
if (blue_boss_input) { |
|
|
blue_boss_input.scrollIntoView({ behavior: 'smooth', block: 'center' }); |
|
|
blue_boss_input.focus(); |
|
|
} |
|
|
}, 200); |
|
|
} |
|
|
}; |
|
|
|
|
|
window.refreshStats = function() { |
|
|
console.log("aooo"); |
|
|
const btn = document.getElementById('refresh_btn'); |
|
|
if (btn) btn.click(); |
|
|
}; |
|
|
|
|
|
// --- Tab navigation setup --- |
|
|
function setupTabNavigation() { |
|
|
// Wait for tab buttons to be rendered |
|
|
setTimeout(() => { |
|
|
const navLinks = document.querySelectorAll('.nav-link'); |
|
|
|
|
|
navLinks.forEach((link) => { |
|
|
link.addEventListener('click', (e) => { |
|
|
e.preventDefault(); |
|
|
|
|
|
const tabId = link.getAttribute('data-tab-id'); |
|
|
|
|
|
// Find the tab button using data-tab-id attribute |
|
|
const tabButton = document.querySelector(`button[data-tab-id="${tabId}_tab"]`); |
|
|
|
|
|
if (tabButton) { |
|
|
// Click the actual tab button |
|
|
tabButton.click(); |
|
|
|
|
|
// Update active state on nav links |
|
|
navLinks.forEach((l) => l.classList.remove('active')); |
|
|
link.classList.add('active'); |
|
|
} else { |
|
|
console.log('Tab button not found for id:', tabId); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
}, 500); |
|
|
} |
|
|
|
|
|
setupTabNavigation(); |
|
|
|
|
|
return "Carousel + BossName + TabNavigation JS initialized"; |
|
|
} |
|
|
""" |
|
|
|
|
|
JS_BTN = """ |
|
|
() => { |
|
|
// Hide all "Play as Boss" buttons when first message is sent |
|
|
const bossBtns = document.querySelectorAll('.play-as-boss-btn'); |
|
|
bossBtns.forEach(btn => { |
|
|
btn.style.display = 'none'; |
|
|
}); |
|
|
} |
|
|
""" |
|
|
|
|
|
CODENAMES_WORDS = [ |
|
|
"AGENT", "AFRICA", "AIR", "ALIEN", "ALPS", "AMAZON", "AMBULANCE", |
|
|
"AMERICA", "ANGEL", "ANTARCTICA", "APPLE", "ARM", "ATLANTIS", "AUSTRALIA", |
|
|
"AZTEC", "BACK", "BALL", "BAND", "BANK", "BAR", "BARK", "BAT", "BATTERY", |
|
|
"BEACH", "BEAR", "BEAT", "BED", "BEIJING", "BELL", "BELT", "BERLIN", |
|
|
"BERMUDA", "BERRY", "BILL", "BLOCK", "BOARD", "BOLT", "BOMB", |
|
|
"BOND", "BOOM", "BOOT", "BOTTLE", "BOW", "BOX", "BRIDGE", "BRUSH", "BUCK", "BUFFALO", "BUG", "BUGLE", |
|
|
"BUTTON", "CALF", "CANADA", "CAP", "CAPITAL", "CAR", "CARD", "CARROT", "CASINO", "CAST", |
|
|
"CAT", "CELL", "CENTAUR", "CENTER", "CHAIR", "CHANGE", "CHARGE", "CHECK", "CHEST", "CHICK", |
|
|
"CHINA", "CHOCOLATE", "CHURCH", "CIRCLE", "CLIFF", "CLOAK", "CLUB", "CODE", "COLD", "COMIC", |
|
|
"COMPOUND", "CONCERT", "CONDUCTOR", "CONTRACT", "COOK", "COPPER", "COTTON", "COURT", "COVER", "CRANE", |
|
|
"CRASH", "CRICKET", "CROSS", "CROWN", "CYCLE", "CZECH", "DANCE", "DATE", "DAY", "DEATH", |
|
|
"DECK", "DEGREE", "DIAMOND", "DICE", "DINOSAUR", "DISEASE", "DOCTOR", "DOG", "DRAFT", "DRAGON", |
|
|
"DRESS", "DRILL", "DROP", "DUCK", "DWARF", "EAGLE", "EGYPT", "EMBASSY", "ENGINE", "ENGLAND", |
|
|
"EUROPE", "EYE", "FACE", "FAIR", "FALL", "FAN", "FENCE", "FIELD", "FIGHTER", "FIGURE", |
|
|
"FILE", "FILM", "FIRE", "FISH", "FLUTE", "FLY", "FOOT", "FORCE", "FOREST", "FORK", |
|
|
"FRANCE", "GAME", "GAS", "GENIUS", "GERMANY", "GHOST", "GIANT", "GLASS", "GLOVE", "GOLD", |
|
|
"GRACE", "GRASS", "GREECE", "GREEN", "GROUND", "HAM", "HAND", "HAWK", "HEAD", "HEART", |
|
|
"HELICOPTER", "HIMALAYAS", "HOLE", "HOLLYWOOD", "HONEY", "HOOD", "HOOK", "HORN", "HORSE", "HORSESHOE", |
|
|
"HOSPITAL", "HOTEL", "ICE", "INDIA", "IRON", "IVORY", "JACK", "JAM", "JET", "JUPITER", |
|
|
"KANGAROO", "KETCHUP", "KEY", "KID", "KING", "KIWI", "KNIFE", "KNIGHT", "LAB", "LAP", |
|
|
"LASER", "LAWYER", "LEAD", "LEMON", "LEPRECHAUN", "LIFE", "LIGHT", "LIMOUSINE", "LINE", "LINK", |
|
|
"LION", "LITTER", "LOCH NESS", "LOCK", "LOG", "LONDON", "LUCK", "MAIL", "MAMMOTH", "MAPLE", |
|
|
"MARBLE", "MARCH", "MASS", "MATCH", "MERCURY", "MEXICO", "MICROSCOPE", "MILLIONAIRE", "MINE", "MINT", |
|
|
"MISSILE", "MODEL", "MOLE", "MOON", "MOSCOW", "MOUNT", "MOUSE", "MOUTH", "MUG", "NAIL", |
|
|
"NEEDLE", "NET", "NEW YORK", "NIGHT", "NINJA", "NOTE", "NOVEL", "NURSE", "NUT", "OCTOPUS", |
|
|
"OIL", "OLIVE", "OLYMPUS", "OPERA", "ORANGE", "ORGAN", "PALM", "PAN", "PANTS", "PAPER", |
|
|
"PARACHUTE", "PARK", "PART", "PASS", "PASTE", "PENGUIN", "PHOENIX", "PIANO", "PIE", "PILOT", |
|
|
"PIN", "PIPE", "PIRATE", "PISTOL", "PIT", "PITCH", "PLANE", "PLASTIC", "PLATE", "PLATYPUS", |
|
|
"PLAY", "PLOT", "POINT", "POISON", "POLE", "POLICE", "POOL", "PORT", "POST", "POUND", |
|
|
"PRESS", "PRINCESS", "PUMPKIN", "PUPIL", "PYRAMID", "QUEEN", "RABBIT", "RACKET", "RAY", "REVOLUTION", |
|
|
"RING", "ROBIN", "ROBOT", "ROCK", "ROME", "ROOT", "ROSE", "ROULETTE", "ROUND", "ROW", |
|
|
"RULER", "SATELLITE", "SATURN", "SCALE", "SCHOOL", "SCIENTIST", "SCORPION", "SCREEN", "SCUBA DIVER", "SEAL", |
|
|
"SERVER", "SHADOW", "SHAKESPEARE", "SHARK", "SHIP", "SHOE", "SHOP", "SHOT", "SINK", "SKYSCRAPER", |
|
|
"SLIP", "SLUG", "SMUGGLER", "SNOW", "SNOWMAN", "SOCK", "SOLDIER", "SOUL", "SOUND", "SPACE", |
|
|
"SPELL", "SPIDER", "SPIKE", "SPINE", "SPOT", "SPRING", "SPY", "SQUARE", "STADIUM", "STAFF", |
|
|
"STAR", "STATE", "STICK", "STOCK", "STRAW", "STREAM", "STRIKE", "STRING", "SUB", "SUIT", |
|
|
"SUPERHERO", "SWING", "SWITCH", "TABLE", "TABLET", "TAG", "TAIL", "TAP", "TEACHER", "TELESCOPE", |
|
|
"TEMPLE", "THEATER", "THIEF", "THUMB", "TICK", "TIE", "TIME", "TOKYO", "TOOTH", "TORCH", |
|
|
"TOWER", "TRACK", "TRAIN", "TRIANGLE", "TRIP", "TRUNK", "TUBE", "TURKEY", "UNDERTAKER", "UNICORN", |
|
|
"VACUUM", "VAN", "VET", "WAKE", "WALL", "WAR", "WASHER", "WASHINGTON", "WATCH", "WATER", |
|
|
"WAVE", "WEB", "WELL", "WHALE", "WHIP", "WIND", "WITCH", "WORM", "YARD" |
|
|
] |
|
|
|
|
|
NAMES = [ |
|
|
"Astra", "Nova", "Echo", "Luna", |
|
|
"Orion", "Vega", "Zephyr", "Sol" |
|
|
] |
|
|
|
|
|
TEAM_MODEL_PRESETS = { |
|
|
"openai": { |
|
|
"boss": "gpt-5", |
|
|
"captain": "gpt-5-mini", |
|
|
"players": ["gpt-5-nano", "gpt-4.1-nano"] |
|
|
}, |
|
|
"google": { |
|
|
"boss": "gemini-2.5-pro", |
|
|
"captain": "gemini-2.5-flash", |
|
|
"players": ["gemini-2.0-flash-001", "gemini-2.0-flash-lite-001"] |
|
|
}, |
|
|
"anthropic": { |
|
|
"boss": "claude-sonnet-4-5-20250929", |
|
|
"captain": "claude-3-7-sonnet-20250219", |
|
|
"players": ["claude-3-5-haiku-20241022", "claude-3-haiku-20240307"] |
|
|
}, |
|
|
"opensource": { |
|
|
"boss": "moonshotai/Kimi-K2-Thinking", |
|
|
"captain": "deepseek-ai/DeepSeek-R1", |
|
|
"players": ["openai/gpt-oss-120b", "openai/gpt-oss-20b"] |
|
|
}, |
|
|
} |
|
|
|
|
|
|
|
|
ALL_MODELS = sorted({ |
|
|
model |
|
|
for preset in TEAM_MODEL_PRESETS.values() |
|
|
for model in ([preset["boss"], preset["captain"]] + preset["players"]) |
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
custom_header = """ |
|
|
<div class="custom-navbar"> |
|
|
<div class="navbar-title">🕵️ Agentic Codenames Arena</div> |
|
|
<div class="navbar-links"> |
|
|
<a href="#" class="nav-link active" data-tab-id="home_id">Home</a> |
|
|
<a href="#" class="nav-link" data-tab-id="how_to_play_id">How to play</a> |
|
|
<a href="#" class="nav-link" data-tab-id="play_id">Play</a> |
|
|
<a href="#" class="nav-link" data-tab-id="stats_id">Stats</a> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
|