Skip to content

Commit 6b3ad7e

Browse files
committed
Feat(viewer): Create static version of viewer
1 parent 25d5db6 commit 6b3ad7e

9 files changed

Lines changed: 278 additions & 13 deletions

File tree

codeclash/viewer/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
A Flask-based web application to visualize AI agent game trajectories
55
"""
66

7-
from .app import app, set_log_base_directory
7+
from .app import app, is_static_mode, set_log_base_directory, set_static_mode
88

9-
__all__ = ["app", "set_log_base_directory"]
9+
__all__ = ["app", "set_log_base_directory", "set_static_mode", "is_static_mode"]

codeclash/viewer/app.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
# Global variable to store the directory to search for logs
2020
LOG_BASE_DIR = Path.cwd() / "logs"
2121

22+
# Global flag to indicate if we're running in static mode
23+
STATIC_MODE = False
24+
2225

2326
@dataclass
2427
class AgentInfo:
@@ -35,6 +38,17 @@ def set_log_base_directory(directory: str | Path):
3538
LOG_BASE_DIR = Path(directory).resolve()
3639

3740

41+
def set_static_mode(enabled: bool = True):
42+
"""Enable or disable static mode"""
43+
global STATIC_MODE
44+
STATIC_MODE = enabled
45+
46+
47+
def is_static_mode() -> bool:
48+
"""Check if we're running in static mode"""
49+
return STATIC_MODE
50+
51+
3852
def is_game_folder(log_dir: Path) -> bool:
3953
"""Check if a directory contains metadata.json and is therefore a game folder"""
4054
metadata_file = log_dir / "metadata.json"
@@ -705,6 +719,48 @@ def index():
705719
metadata=metadata,
706720
trajectories_by_round=trajectories_by_round,
707721
analysis_data=analysis_data,
722+
is_static=STATIC_MODE,
723+
)
724+
725+
726+
@app.route("/game/<path:folder_path>")
727+
def game_view(folder_path):
728+
"""Static-friendly game viewer route using path parameters"""
729+
# Validate the selected folder exists and is a game folder
730+
logs_dir = LOG_BASE_DIR
731+
folder_path_obj = logs_dir / folder_path
732+
733+
if not folder_path_obj.exists() or not is_game_folder(folder_path_obj):
734+
return redirect(url_for("game_picker"))
735+
736+
# Parse the selected game
737+
parser = LogParser(folder_path_obj)
738+
metadata = parser.parse_game_metadata()
739+
available_trajectories = parser.get_available_trajectories()
740+
741+
# Group trajectories by round
742+
trajectories_by_round = {}
743+
for player_name, round_num in available_trajectories:
744+
if round_num not in trajectories_by_round:
745+
trajectories_by_round[round_num] = []
746+
trajectory = parser.parse_trajectory(player_name, round_num)
747+
if trajectory:
748+
trajectories_by_round[round_num].append(trajectory)
749+
750+
# Get analysis data
751+
analysis_data = parser.analyze_line_counts()
752+
753+
# Get the full path of the selected folder
754+
selected_folder_path = str(folder_path_obj)
755+
756+
return render_template(
757+
"index.html",
758+
selected_folder=folder_path,
759+
selected_folder_path=selected_folder_path,
760+
metadata=metadata,
761+
trajectories_by_round=trajectories_by_round,
762+
analysis_data=analysis_data,
763+
is_static=STATIC_MODE,
708764
)
709765

710766

@@ -714,7 +770,7 @@ def game_picker():
714770
logs_dir = LOG_BASE_DIR
715771
game_folders = find_all_game_folders(logs_dir)
716772

717-
return render_template("picker.html", game_folders=game_folders, base_dir=str(logs_dir))
773+
return render_template("picker.html", game_folders=game_folders, base_dir=str(logs_dir), is_static=STATIC_MODE)
718774

719775

720776
@app.route("/delete-experiment", methods=["POST"])

codeclash/viewer/static/js/app.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22

33
// Game picker
44
function openGamePicker() {
5-
window.location.href = "/picker";
5+
const isStatic = document.body.hasAttribute("data-static-mode");
6+
const url = isStatic ? "/picker.html" : "/picker";
7+
window.location.href = url;
68
}
79

810
function openGamePickerInNewTab() {
9-
window.open("/picker", "_blank");
11+
const isStatic = document.body.hasAttribute("data-static-mode");
12+
const url = isStatic ? "/picker.html" : "/picker";
13+
window.open(url, "_blank");
1014
}
1115

1216
function handlePickerClick(event) {

codeclash/viewer/static/js/picker.js

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,36 @@
22

33
function openGame(gameName) {
44
// Navigate to the viewer with the selected game
5-
const url = `/?folder=${encodeURIComponent(gameName)}`;
6-
window.location.href = url;
5+
// In static mode, use path-based URLs; in dynamic mode, use query parameters
6+
const isStatic = document.body.hasAttribute("data-static-mode");
7+
if (isStatic) {
8+
// For static mode, encode each path segment separately to preserve slashes
9+
const pathSegments = gameName
10+
.split("/")
11+
.map((segment) => encodeURIComponent(segment));
12+
const url = `/game/${pathSegments.join("/")}.html`;
13+
window.location.href = url;
14+
} else {
15+
const url = `/?folder=${encodeURIComponent(gameName)}`;
16+
window.location.href = url;
17+
}
718
}
819

920
function openGameInNewTab(gameName) {
1021
// Open the viewer in a new tab with the selected game
11-
const url = `/?folder=${encodeURIComponent(gameName)}`;
12-
window.open(url, "_blank");
22+
// In static mode, use path-based URLs; in dynamic mode, use query parameters
23+
const isStatic = document.body.hasAttribute("data-static-mode");
24+
if (isStatic) {
25+
// For static mode, encode each path segment separately to preserve slashes
26+
const pathSegments = gameName
27+
.split("/")
28+
.map((segment) => encodeURIComponent(segment));
29+
const url = `/game/${pathSegments.join("/")}.html`;
30+
window.open(url, "_blank");
31+
} else {
32+
const url = `/?folder=${encodeURIComponent(gameName)}`;
33+
window.open(url, "_blank");
34+
}
1335
}
1436

1537
function handleGameClick(event, gameName) {

codeclash/viewer/templates/includes/folder_path.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,21 @@ <h3><i class="bi bi-folder"></i> Current Folder:</h3>
77
<button class="btn btn-outline-primary btn-sm copy-path-btn" data-path="{{ selected_folder_path }}" title="Copy folder path">
88
<i class="bi bi-clipboard"></i> Copy path
99
</button>
10+
{% if not is_static %}
1011
<button class="btn btn-outline-warning btn-sm move-rename-btn" onclick="showMoveDialog('{{ selected_folder_path }}')" title="Move/rename this experiment">
1112
<i class="bi bi-folder-symlink"></i> Move/Rename
1213
</button>
1314
<button class="btn btn-outline-danger btn-sm delete-experiment-btn" data-folder-path="{{ selected_folder_path }}" title="Delete this experiment">
1415
<i class="bi bi-trash"></i> Delete
1516
</button>
17+
{% else %}
18+
<button class="btn btn-outline-secondary btn-sm" disabled title="Not available in static mode">
19+
<i class="bi bi-folder-symlink"></i> Move/Rename
20+
</button>
21+
<button class="btn btn-outline-secondary btn-sm" disabled title="Not available in static mode">
22+
<i class="bi bi-trash"></i> Delete
23+
</button>
24+
{% endif %}
1625
</div>
1726
</div>
1827
</section>

codeclash/viewer/templates/includes/readme.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
<section class="readme-section">
33
<h2><i class="bi bi-journal-text"></i> Readme</h2>
44
<div class="readme-container">
5+
{% if not is_static %}
56
<textarea id="readme-textarea" class="form-control readme-textarea" placeholder="Add notes about this experiment..."></textarea>
67
<div class="readme-status" id="readme-status">Loading...</div>
8+
{% else %}
9+
<textarea id="readme-textarea" class="form-control readme-textarea" placeholder="Readme editing not available in static mode" disabled></textarea>
10+
<div class="readme-status" id="readme-status">Static Mode - Editing Disabled</div>
11+
{% endif %}
712
</div>
813
</section>

codeclash/viewer/templates/index.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<!-- Custom CSS -->
1414
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
1515
</head>
16-
<body>
16+
<body{% if is_static %} data-static-mode{% endif %}>
1717
{% include 'includes/header.html' %}
1818

1919
<main class="container-fluid main-content">
@@ -83,8 +83,10 @@ <h3>Move/Rename Folder</h3>
8383
document.addEventListener('DOMContentLoaded', function() {
8484
// Setup copy buttons
8585
setupCopyButtons();
86-
// Setup readme autosave
86+
// Setup readme autosave (only if not in static mode)
87+
{% if not is_static %}
8788
setupReadmeAutosave();
89+
{% endif %}
8890
// Initialize JSON editors with metadata
8991
initializeJSONEditors({{ metadata | tojson | safe }});
9092
});

codeclash/viewer/templates/picker.html

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
1313
<link rel="stylesheet" href="{{ url_for('static', filename='css/picker.css') }}">
1414
</head>
15-
<body>
15+
<body{% if is_static %} data-static-mode{% endif %}>
1616

1717
<div class="picker-container">
1818
<div class="picker-header">
@@ -28,6 +28,7 @@ <h3><i class="bi bi-folder"></i> Base Directory:</h3>
2828
{% if game_folders %}
2929
<div class="games-table">
3030
<div class="table-controls">
31+
{% if not is_static %}
3132
<div class="selection-controls">
3233
<input type="checkbox" id="select-all" onchange="toggleSelectAll(this)">
3334
<label for="select-all">Select All</label>
@@ -41,6 +42,17 @@ <h3><i class="bi bi-folder"></i> Base Directory:</h3>
4142
<option value="move-to-subfolder">Move to subfolder</option>
4243
</select>
4344
</div>
45+
{% else %}
46+
<div class="selection-controls">
47+
<input type="checkbox" id="select-all" disabled>
48+
<label for="select-all" style="color: #666;">Select All (Disabled in static mode)</label>
49+
</div>
50+
<div class="action-controls">
51+
<select id="action-dropdown" disabled>
52+
<option value="">Actions disabled in static mode</option>
53+
</select>
54+
</div>
55+
{% endif %}
4456
</div>
4557
<div class="table-header">
4658
<div><i class="bi bi-check-square"></i> Select</div>
@@ -61,7 +73,7 @@ <h3><i class="bi bi-folder"></i> Base Directory:</h3>
6173
<div class="checkbox-cell">
6274
{% if game.is_game %}
6375
<input type="checkbox" class="game-checkbox" data-path="{{ game.name }}"
64-
onclick="handleCheckboxClick(event, '{{ game.name }}')">
76+
onclick="{% if not is_static %}handleCheckboxClick(event, '{{ game.name }}'){% endif %}" {% if is_static %}disabled{% endif %}>
6577
{% else %}
6678
<span class="checkbox-placeholder"></span>
6779
{% endif %}
@@ -122,10 +134,16 @@ <h3><i class="bi bi-folder"></i> Base Directory:</h3>
122134
<button class="btn btn-outline-primary btn-sm open-new-tab-button" onclick="event.stopPropagation(); openGameInNewTab('{{ game.name }}')" title="Open in new tab">
123135
<i class="bi bi-box-arrow-up-right"></i>
124136
</button>
137+
{% if not is_static %}
125138
<button class="btn btn-outline-warning btn-sm move-button" onclick="event.stopPropagation(); showMoveDialog('{{ game.name }}')" title="Move/rename">
126139
<i class="bi bi-folder-symlink"></i>
127140
</button>
128141
{% else %}
142+
<button class="btn btn-outline-secondary btn-sm move-button" disabled title="Not available in static mode">
143+
<i class="bi bi-folder-symlink"></i>
144+
</button>
145+
{% endif %}
146+
{% else %}
129147
<span class="folder-indicator">Folder</span>
130148
{% endif %}
131149
</div>

0 commit comments

Comments
 (0)