Skip to content

Commit 80341b7

Browse files
committed
Feat(viewer picker): Guess config files & aws cmds
1 parent 412ec7f commit 80341b7

3 files changed

Lines changed: 237 additions & 0 deletions

File tree

codeclash/viewer/app.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1409,4 +1409,84 @@ def compute():
14091409
return jsonify({"success": False, "error": str(e)}), 500
14101410

14111411

1412+
@app.route("/picker/api/guess-config-names", methods=["POST"])
1413+
@print_timing
1414+
def guess_config_names():
1415+
"""Guess config file names based on metadata.json files"""
1416+
try:
1417+
data = request.get_json()
1418+
folder_paths = data.get("folder_paths", [])
1419+
1420+
if not folder_paths:
1421+
return jsonify({"success": False, "error": "No folder paths provided"})
1422+
1423+
results = []
1424+
for folder_path in folder_paths:
1425+
folder_path_obj = LOG_BASE_DIR / folder_path
1426+
1427+
if not folder_path_obj.exists() or not is_game_folder(folder_path_obj):
1428+
results.append({"folder": folder_path, "config_name": None, "error": "Invalid folder"})
1429+
continue
1430+
1431+
metadata = load_metadata(folder_path_obj)
1432+
if not metadata.is_valid:
1433+
results.append({"folder": folder_path, "config_name": None, "error": "No metadata"})
1434+
continue
1435+
1436+
# Extract components for config name
1437+
game_name = metadata.game_name
1438+
rounds = metadata.get_path("config.tournament.rounds")
1439+
sims = metadata.get_path("config.game.sims_per_round")
1440+
players = metadata.get_path("config.players", [])
1441+
1442+
# Extract player names
1443+
player_names = []
1444+
for player in players:
1445+
if isinstance(player, dict):
1446+
name = player.get("name", "")
1447+
if name:
1448+
player_names.append(name)
1449+
1450+
# Build config file name
1451+
if game_name and rounds and sims and len(player_names) >= 2:
1452+
# Determine correct model ordering by checking folder name
1453+
folder_name = Path(folder_path).name
1454+
model1, model2 = player_names[0], player_names[1]
1455+
1456+
# Check which order appears in the folder name
1457+
if f"{model1}.{model2}" in folder_name:
1458+
# Order is correct
1459+
pass
1460+
elif f"{model2}.{model1}" in folder_name:
1461+
# Swap order
1462+
model1, model2 = model2, model1
1463+
# If neither appears, keep original order
1464+
1465+
config_name = f"{game_name}__{model1}__{model2}__r{rounds}__s{sims}.yaml"
1466+
results.append({"folder": folder_path, "config_name": config_name, "error": None})
1467+
else:
1468+
missing = []
1469+
if not game_name:
1470+
missing.append("game_name")
1471+
if not rounds:
1472+
missing.append("rounds")
1473+
if not sims:
1474+
missing.append("sims")
1475+
if len(player_names) < 2:
1476+
missing.append(f"players (found {len(player_names)})")
1477+
results.append(
1478+
{
1479+
"folder": folder_path,
1480+
"config_name": None,
1481+
"error": f"Missing: {', '.join(missing)}",
1482+
}
1483+
)
1484+
1485+
return jsonify({"success": True, "results": results})
1486+
1487+
except Exception as e:
1488+
logger.error(f"Error guessing config names: {e}", exc_info=True)
1489+
return jsonify({"success": False, "error": str(e)}), 500
1490+
1491+
14121492
# Use run_viewer.py to launch the application

codeclash/viewer/static/js/picker.js

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,157 @@ function fillTextareaWithAWSResubmitCommands() {
373373
}
374374
}
375375

376+
async function fillTextareaWithGuessedConfigNames() {
377+
const selectedCheckboxes = getVisibleCheckedCheckboxes();
378+
379+
if (selectedCheckboxes.length === 0) {
380+
showModalWarning(
381+
"No folders selected. Please select folders from the table first.",
382+
);
383+
document.getElementById("bulk-actions-textarea").value = "";
384+
return;
385+
}
386+
387+
// Show loading message
388+
const textarea = document.getElementById("bulk-actions-textarea");
389+
textarea.value = "Loading...";
390+
hideModalWarning();
391+
392+
// Extract folder paths
393+
const folderPaths = Array.from(selectedCheckboxes).map((checkbox) =>
394+
checkbox.getAttribute("data-path"),
395+
);
396+
397+
try {
398+
// Call backend API
399+
const response = await fetch("/picker/api/guess-config-names", {
400+
method: "POST",
401+
headers: {
402+
"Content-Type": "application/json",
403+
},
404+
body: JSON.stringify({ folder_paths: folderPaths }),
405+
});
406+
407+
const data = await response.json();
408+
409+
if (!data.success) {
410+
showModalWarning(`Error: ${data.error}`);
411+
textarea.value = "";
412+
return;
413+
}
414+
415+
// Process results
416+
const successfulResults = data.results.filter(
417+
(result) => result.config_name,
418+
);
419+
const failedResults = data.results.filter((result) => !result.config_name);
420+
421+
if (successfulResults.length === 0) {
422+
showModalWarning("No config names could be guessed from the metadata.");
423+
textarea.value = "";
424+
return;
425+
}
426+
427+
// Format output
428+
const configNames = successfulResults.map((result) => result.config_name);
429+
textarea.value = configNames.join("\n");
430+
431+
// Show warning if some folders failed
432+
if (failedResults.length > 0) {
433+
const errorMessages = failedResults.map((result) => {
434+
const folderName = result.folder.split("/").pop();
435+
return `${folderName}: ${result.error}`;
436+
});
437+
showModalWarning(
438+
`Warning: ${failedResults.length} folder(s) failed:\n${errorMessages.join("\n")}`,
439+
);
440+
} else {
441+
hideModalWarning();
442+
}
443+
} catch (error) {
444+
console.error("Error guessing config names:", error);
445+
showModalWarning(`Error: ${error.message}`);
446+
textarea.value = "";
447+
}
448+
}
449+
450+
async function fillTextareaWithAWSSubmitCommands() {
451+
const selectedCheckboxes = getVisibleCheckedCheckboxes();
452+
453+
if (selectedCheckboxes.length === 0) {
454+
showModalWarning(
455+
"No folders selected. Please select folders from the table first.",
456+
);
457+
document.getElementById("bulk-actions-textarea").value = "";
458+
return;
459+
}
460+
461+
// Show loading message
462+
const textarea = document.getElementById("bulk-actions-textarea");
463+
textarea.value = "Loading...";
464+
hideModalWarning();
465+
466+
// Extract folder paths
467+
const folderPaths = Array.from(selectedCheckboxes).map((checkbox) =>
468+
checkbox.getAttribute("data-path"),
469+
);
470+
471+
try {
472+
// Call backend API
473+
const response = await fetch("/picker/api/guess-config-names", {
474+
method: "POST",
475+
headers: {
476+
"Content-Type": "application/json",
477+
},
478+
body: JSON.stringify({ folder_paths: folderPaths }),
479+
});
480+
481+
const data = await response.json();
482+
483+
if (!data.success) {
484+
showModalWarning(`Error: ${data.error}`);
485+
textarea.value = "";
486+
return;
487+
}
488+
489+
// Process results
490+
const successfulResults = data.results.filter(
491+
(result) => result.config_name,
492+
);
493+
const failedResults = data.results.filter((result) => !result.config_name);
494+
495+
if (successfulResults.length === 0) {
496+
showModalWarning("No config names could be guessed from the metadata.");
497+
textarea.value = "";
498+
return;
499+
}
500+
501+
// Format output as AWS submit commands
502+
const commands = successfulResults.map(
503+
(result) =>
504+
`aws/run_job.py -- aws/docker_and_sync.sh python main.py configs/main/${result.config_name}`,
505+
);
506+
textarea.value = commands.join("\n");
507+
508+
// Show warning if some folders failed
509+
if (failedResults.length > 0) {
510+
const errorMessages = failedResults.map((result) => {
511+
const folderName = result.folder.split("/").pop();
512+
return `${folderName}: ${result.error}`;
513+
});
514+
showModalWarning(
515+
`Warning: ${failedResults.length} folder(s) skipped:\n${errorMessages.join("\n")}`,
516+
);
517+
} else {
518+
hideModalWarning();
519+
}
520+
} catch (error) {
521+
console.error("Error generating AWS submit commands:", error);
522+
showModalWarning(`Error: ${error.message}`);
523+
textarea.value = "";
524+
}
525+
}
526+
376527
function copyFromModal() {
377528
const textarea = document.getElementById("bulk-actions-textarea");
378529
const text = textarea.value;

codeclash/viewer/templates/picker.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ <h2>Bulk Actions</h2>
173173
<button class="modal-action-btn" onclick="fillTextareaWithAWSResubmitCommands()">
174174
<i class="bi bi-arrow-repeat"></i> AWS resubmit
175175
</button>
176+
<button class="modal-action-btn" onclick="fillTextareaWithGuessedConfigNames()">
177+
<i class="bi bi-file-earmark-text"></i> Guess config file name
178+
</button>
179+
<button class="modal-action-btn" onclick="fillTextareaWithAWSSubmitCommands()">
180+
<i class="bi bi-cloud-upload"></i> Guess AWS submit cmd
181+
</button>
176182
</div>
177183
<div id="modal-warning" class="modal-warning">
178184
<i class="bi bi-exclamation-triangle"></i>

0 commit comments

Comments
 (0)