Skip to content

Commit f13b796

Browse files
committed
Feat(viewer): Show overview table on top
1 parent eeca3f4 commit f13b796

3 files changed

Lines changed: 323 additions & 24 deletions

File tree

codeclash/viewer/app.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@
1616
LOG_BASE_DIR = Path.cwd() / "logs"
1717

1818

19+
@dataclass
20+
class AgentInfo:
21+
"""Information about a single agent"""
22+
23+
name: str
24+
model_name: str | None = None
25+
agent_class: str | None = None
26+
27+
1928
def set_log_base_directory(directory: str | Path):
2029
"""Set the logs directory directly"""
2130
global LOG_BASE_DIR
@@ -73,6 +82,33 @@ def get_models_from_metadata(log_dir: Path) -> list[str]:
7382
return []
7483

7584

85+
def get_agent_info_from_metadata(metadata: dict[str, Any]) -> list[AgentInfo]:
86+
"""Extract detailed agent information from metadata"""
87+
agents = []
88+
players_config = metadata.get("config", {}).get("players", {})
89+
90+
# Handle both list and dict formats
91+
if isinstance(players_config, list):
92+
# If players is a list, iterate through each player
93+
for i, player_config in enumerate(players_config):
94+
if isinstance(player_config, dict):
95+
name = f"p{i + 1}" # Default naming p1, p2, etc.
96+
config = player_config.get("config", {})
97+
model_name = config.get("model", {}).get("model_name")
98+
agent_class = config.get("agent_class")
99+
agents.append(AgentInfo(name=name, model_name=model_name, agent_class=agent_class))
100+
elif isinstance(players_config, dict):
101+
# If players is a dict, iterate through player keys (p1, p2, etc.)
102+
for player_key, player_config in sorted(players_config.items()):
103+
if isinstance(player_config, dict):
104+
config = player_config.get("config", {})
105+
model_name = config.get("model", {}).get("model_name")
106+
agent_class = config.get("agent_class")
107+
agents.append(AgentInfo(name=player_key, model_name=model_name, agent_class=agent_class))
108+
109+
return agents
110+
111+
76112
def find_all_game_folders(base_dir: Path) -> list[dict[str, Any]]:
77113
"""Recursively find all folders and mark which ones contain metadata.json"""
78114
all_folders = []
@@ -152,6 +188,40 @@ class GameMetadata:
152188
main_log_path: str
153189
metadata_file_path: str
154190
rounds: list[dict[str, Any]]
191+
agent_info: list[AgentInfo] | None = None
192+
193+
194+
def process_round_results(round_results: dict[str, Any] | None) -> dict[str, Any] | None:
195+
"""Process round results to add computed fields and sort scores"""
196+
if not round_results or not round_results.get("scores"):
197+
return round_results
198+
199+
# Create a copy to avoid modifying original data
200+
processed = round_results.copy()
201+
202+
# Sort scores alphabetically by key
203+
scores = dict(sorted(round_results["scores"].items()))
204+
processed["scores"] = scores
205+
processed["sorted_scores"] = list(scores.items())
206+
207+
# Calculate winner percentage
208+
winner = round_results.get("winner")
209+
if winner and scores:
210+
total_games = sum(scores.values())
211+
if total_games > 0:
212+
if winner != "Tie":
213+
winner_wins = scores.get(winner, 0)
214+
ties = scores.get("Tie", 0)
215+
win_percentage = round(((winner_wins + 0.5 * ties) / total_games) * 100, 1)
216+
processed["winner_percentage"] = win_percentage
217+
else:
218+
processed["winner_percentage"] = None # No percentage for ties
219+
else:
220+
processed["winner_percentage"] = None
221+
else:
222+
processed["winner_percentage"] = None
223+
224+
return processed
155225

156226

157227
@dataclass
@@ -225,15 +295,20 @@ def parse_game_metadata(self) -> GameMetadata:
225295
round_results = None
226296
if results_file.exists():
227297
round_results = json.loads(results_file.read_text())
298+
round_results = process_round_results(round_results)
228299

229300
rounds.append({"round_num": round_num, "sim_logs": sim_logs, "results": round_results})
230301

302+
# Extract agent information
303+
agent_info = get_agent_info_from_metadata(results) if results else []
304+
231305
return GameMetadata(
232306
results=results,
233307
main_log=main_log,
234308
main_log_path=main_log_path,
235309
metadata_file_path=metadata_file_path,
236310
rounds=rounds,
311+
agent_info=agent_info,
237312
)
238313

239314
def parse_trajectory(self, player_id: int, round_num: int) -> TrajectoryInfo | None:

codeclash/viewer/static/css/style.css

Lines changed: 155 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,19 +176,154 @@ body {
176176
}
177177

178178
/* Sections */
179-
.results-section,
179+
.setup-section,
180+
.overview-section,
180181
.rounds-section {
181182
margin-bottom: 3rem;
182183
}
183184

184-
.results-section h2,
185+
.setup-section h2,
186+
.overview-section h2,
185187
.rounds-section h2 {
186188
color: var(--accent-color);
187189
font-size: 1.5rem;
188190
margin-bottom: 1.5rem;
189191
font-weight: 600;
190192
}
191193

194+
/* Agent Information */
195+
.agent-info {
196+
margin-bottom: 2rem;
197+
}
198+
199+
.agent-info h3 {
200+
color: var(--text-primary);
201+
font-size: 1.25rem;
202+
margin-bottom: 1rem;
203+
font-weight: 600;
204+
}
205+
206+
.agents-grid {
207+
display: grid;
208+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
209+
gap: 1rem;
210+
margin-bottom: 1.5rem;
211+
}
212+
213+
.agent-card {
214+
background-color: var(--bg-secondary);
215+
border: 1px solid var(--border-color);
216+
border-radius: 0.5rem;
217+
padding: 1rem;
218+
transition: all 0.2s ease;
219+
}
220+
221+
.agent-card:hover {
222+
box-shadow: var(--shadow);
223+
transform: translateY(-2px);
224+
}
225+
226+
.agent-name {
227+
font-size: 1.1rem;
228+
font-weight: 600;
229+
color: var(--accent-color);
230+
margin-bottom: 0.5rem;
231+
}
232+
233+
.agent-model,
234+
.agent-class {
235+
font-size: 0.9rem;
236+
color: var(--text-secondary);
237+
margin-bottom: 0.25rem;
238+
}
239+
240+
.agent-model {
241+
font-weight: 500;
242+
}
243+
244+
/* Overview Table */
245+
.overview-summary {
246+
margin-bottom: 2rem;
247+
}
248+
249+
.results-table {
250+
width: 100%;
251+
border-collapse: collapse;
252+
background-color: var(--bg-secondary);
253+
border-radius: 0.5rem;
254+
overflow: hidden;
255+
border: 1px solid var(--border-color);
256+
box-shadow: var(--shadow);
257+
}
258+
259+
.results-table th,
260+
.results-table td {
261+
padding: 0.75rem 1rem;
262+
text-align: left;
263+
border-bottom: 1px solid var(--border-color);
264+
}
265+
266+
.results-table th {
267+
background-color: var(--bg-tertiary);
268+
font-weight: 600;
269+
color: var(--text-primary);
270+
font-size: 0.875rem;
271+
text-transform: uppercase;
272+
letter-spacing: 0.05em;
273+
}
274+
275+
.results-table tr:last-child td {
276+
border-bottom: none;
277+
}
278+
279+
.results-table tr:hover {
280+
background-color: var(--bg-primary);
281+
}
282+
283+
.winner-badge {
284+
display: inline-block;
285+
background-color: var(--success-color);
286+
color: white;
287+
padding: 0.25rem 0.5rem;
288+
border-radius: 0.25rem;
289+
font-size: 0.8rem;
290+
font-weight: 600;
291+
text-transform: uppercase;
292+
}
293+
294+
.winner-percentage {
295+
display: inline-block;
296+
margin-left: 0.5rem;
297+
font-size: 0.8rem;
298+
color: var(--text-secondary);
299+
font-weight: 500;
300+
}
301+
302+
.score-breakdown {
303+
display: flex;
304+
flex-wrap: wrap;
305+
gap: 0.5rem;
306+
}
307+
308+
.score-item {
309+
background-color: var(--bg-primary);
310+
border: 1px solid var(--border-color);
311+
padding: 0.25rem 0.5rem;
312+
border-radius: 0.25rem;
313+
font-size: 0.8rem;
314+
color: var(--text-primary);
315+
}
316+
317+
.steps-count {
318+
font-weight: 500;
319+
color: var(--accent-color);
320+
}
321+
322+
.no-result {
323+
color: var(--text-muted);
324+
font-style: italic;
325+
}
326+
192327
/* Metadata display */
193328
.metadata-display {
194329
background-color: var(--code-bg);
@@ -585,6 +720,24 @@ details summary {
585720
.main-content {
586721
padding: 1rem 0.5rem;
587722
}
723+
724+
.agents-grid {
725+
grid-template-columns: 1fr;
726+
}
727+
728+
.results-table {
729+
font-size: 0.8rem;
730+
}
731+
732+
.results-table th,
733+
.results-table td {
734+
padding: 0.5rem;
735+
}
736+
737+
.score-breakdown {
738+
flex-direction: column;
739+
gap: 0.25rem;
740+
}
588741
}
589742

590743
/* Smooth transitions */

0 commit comments

Comments
 (0)