Skip to content

Commit 842df15

Browse files
committed
cp
1 parent fbb582e commit 842df15

File tree

5 files changed

+160
-66
lines changed

5 files changed

+160
-66
lines changed

projects/skydeck/skydeck/desired_state.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ async def create_experiment(self, request: CreateExperimentRequest) -> Experimen
4343
name=request.name,
4444
flags=request.flags,
4545
base_command=request.base_command,
46-
run_name=request.id, # Use experiment ID as run name
4746
nodes=request.nodes,
4847
gpus=request.gpus,
4948
instance_type=request.instance_type,

projects/skydeck/skydeck/models.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ class Experiment(BaseModel):
9292
flags: dict[str, Union[str, int, float, bool]] = Field(default_factory=dict, description="Configuration flags")
9393
base_command: str = Field("lt", description="Base command (before flags)")
9494
tool_path: Optional[str] = Field(None, description="Tool path (e.g., recipes.experiment.cog_arena.train)")
95-
run_name: Optional[str] = Field(None, description="Run name for run=daveey.exp_name")
9695
git_branch: Optional[str] = Field(None, description="Git branch name")
9796

9897
# Job management
@@ -128,18 +127,25 @@ def build_command(self) -> str:
128127
"""Construct full command from base_command, gpus, nodes, tool_path, and flags.
129128
130129
Returns:
131-
Command string like: "lt --gpus=4 --nodes=4 recipes.experiment.cog_arena.train key1=val1 key2=val2"
130+
Command string like: "lt --gpus=4 --nodes=4 --git-ref=branch recipe key=val"
132131
"""
133132
parts = [self.base_command]
134133

135134
# Always add GPUs and nodes as CLI flags
136135
parts.append(f"--gpus={self.gpus}")
137136
parts.append(f"--nodes={self.nodes}")
138137

138+
# Add git branch if specified (skip invalid values like "-")
139+
if self.git_branch and self.git_branch != "-":
140+
parts.append(f"--git-ref={self.git_branch}")
141+
139142
# Add tool path
140143
if self.tool_path:
141144
parts.append(self.tool_path)
142145

146+
# Always add run name using experiment ID
147+
parts.append(f"run={self.id}")
148+
143149
# Add all flags
144150
for key, value in sorted(self.flags.items()):
145151
if isinstance(value, bool):
@@ -193,7 +199,6 @@ class CreateExperimentRequest(BaseModel):
193199
flags: dict[str, Union[str, int, float, bool]] = Field(default_factory=dict)
194200
base_command: str = "lt"
195201
tool_path: Optional[str] = None
196-
run_name: Optional[str] = None
197202
git_branch: Optional[str] = None
198203
nodes: int = 1
199204
gpus: int = 0

projects/skydeck/skydeck/static/app.js

Lines changed: 84 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ let showStoppedJobs = false;
1515
let showOrphanedOnly = false;
1616
let jobsLimit = 20;
1717
let jobsFilterText = '';
18+
let lastJobsHash = ''; // Cache hash to prevent unnecessary table rebuilds
1819

1920
// Initialize on page load
2021
document.addEventListener('DOMContentLoaded', async () => {
@@ -800,16 +801,22 @@ function createExpandedRow(exp, numFlagColumns) {
800801
<div style="display: flex; gap: 20px; align-items: flex-start; margin-bottom: 20px;">
801802
<div class="detail-section" style="width: auto; min-width: 400px;">
802803
<div style="display: flex; justify-content: space-between; align-items: center;">
803-
<h3>Configuration</h3>
804-
<div id="config-buttons-${exp.id}">
805-
<button class="btn btn-small" onclick="toggleConfigEdit('${exp.id}')" id="config-edit-btn-${exp.id}">Edit</button>
806-
</div>
804+
<h3 style="display: flex; align-items: center; gap: 8px;">
805+
Configuration
806+
<span id="config-buttons-${exp.id}">
807+
${editingRows.has(exp.id) ? `
808+
<a onclick="toggleConfigEdit('${exp.id}')" id="config-edit-btn-${exp.id}" class="wandb-link" style="cursor: pointer; margin-left: 0; margin-right: 6px; font-size: 12px; color: #4caf50;" title="Save changes">✓</a>
809+
<a onclick="cancelConfigEdit('${exp.id}')" class="wandb-link" style="cursor: pointer; margin-left: 0; font-size: 12px; color: #f44336;" title="Cancel">✗</a>
810+
` : `
811+
<a onclick="toggleConfigEdit('${exp.id}')" id="config-edit-btn-${exp.id}" class="wandb-link" style="cursor: pointer; margin-left: 0; font-size: 11px;" title="Edit configuration">✎</a>
812+
`}
813+
</span>
814+
</h3>
807815
</div>
808816
<div class="detail-grid" id="config-grid-${exp.id}">
809817
<span class="detail-label">Experiment ID:</span>
810818
<span class="detail-value">
811819
<span class="config-field" data-field="id">${exp.id}</span>
812-
<button class="copy-btn" onclick="event.stopPropagation(); copyToClipboard('${exp.id.replace(/'/g, "\\'")}', event); return false;" title="Copy experiment ID">⎘</button>
813820
<a href="https://wandb.ai/metta-research/metta/runs/${exp.id}" target="_blank" class="wandb-link" onclick="event.stopPropagation();" title="Open in W&B">W&B</a>
814821
<a href="https://app.datadoghq.com/logs?query=metta_run_id%3A%22${exp.id}%22" target="_blank" class="wandb-link" onclick="event.stopPropagation();" title="Open in Datadog">log</a>
815822
</span>
@@ -838,7 +845,7 @@ function createExpandedRow(exp, numFlagColumns) {
838845
</div>
839846
</div>
840847
841-
<div class="detail-section" style="width: 200px; flex-shrink: 0;">
848+
<div class="detail-section" style="width: auto; flex-shrink: 0;">
842849
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
843850
<h3 style="margin: 0;">Jobs</h3>
844851
<div style="display: flex; gap: 5px;">
@@ -863,10 +870,10 @@ function createExpandedRow(exp, numFlagColumns) {
863870
<!-- Second Row: Command Panel -->
864871
<div style="display: flex;">
865872
<div class="detail-section" style="flex: 1;">
866-
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
867-
<h3 style="margin: 0;">Command</h3>
868-
<button class="copy-btn" onclick="event.stopPropagation(); copyToClipboard('${escapedCommand}', 'Command copied!'); return false;" title="Copy command" style="padding: 4px 10px; font-size: 12px;">⎘ Copy</button>
869-
</div>
873+
<h3 style="margin: 0 0 8px 0;">
874+
Command
875+
<button class="copy-btn copy-btn-left" onclick="event.stopPropagation(); copyToClipboard('${escapedCommand}', 'Command copied!'); return false;" title="Copy command">⎘</button>
876+
</h3>
870877
<div style="background-color: #f5f5f5; padding: 10px; border-radius: 4px; font-family: 'Monaco', 'Menlo', 'Courier New', monospace; font-size: 12px; overflow-x: auto; text-align: left; white-space: pre-wrap; word-break: break-word;">${fullCommand}</div>
871878
</div>
872879
</div>
@@ -890,11 +897,19 @@ function buildCommand(exp) {
890897
parts.push(`--gpus=${exp.gpus}`);
891898
parts.push(`--nodes=${exp.nodes}`);
892899

900+
// Add git branch if specified (skip invalid values like "-")
901+
if (exp.git_branch && exp.git_branch !== '-') {
902+
parts.push(`--git-ref=${exp.git_branch}`);
903+
}
904+
893905
// Add tool path
894906
if (exp.tool_path) {
895907
parts.push(exp.tool_path);
896908
}
897909

910+
// Always add run name using experiment ID
911+
parts.push(`run=${exp.id}`);
912+
898913
// Add all flags in sorted order
899914
const sortedFlags = Object.entries(exp.flags || {}).sort((a, b) => a[0].localeCompare(b[0]));
900915
for (const [key, value] of sortedFlags) {
@@ -1007,24 +1022,34 @@ async function loadJobHistory(experimentId) {
10071022
if (startBtn) startBtn.disabled = hasRunningJob;
10081023
if (stopBtn) stopBtn.disabled = !hasRunningJob;
10091024

1010-
if (jobs.length === 0) {
1011-
container.innerHTML = '<p style="color: #999; font-size: 12px;">No jobs yet</p>';
1012-
return;
1013-
}
1014-
1015-
// Show all jobs in order
1016-
const html = jobs.map(job => {
1025+
// Show all jobs in table format
1026+
const rows = jobs.map(job => {
1027+
// Extract numeric ID from full ID (e.g., "daveey.ca4.4x4.mcl_1.0-9482" -> "9482")
1028+
const numericId = job.id.split('-').pop();
10171029
return `
1018-
<div class="job-item" style="display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 12px;">
1019-
<span style="min-width: 45px; font-weight: 500;">#${job.id}</span>
1020-
<a href="https://skypilot-api.softmax-research.net/dashboard/jobs/${job.id}" target="_blank" class="wandb-link" onclick="event.stopPropagation();" title="Open in SkyPilot Dashboard" style="font-size: 10px;">sky</a>
1021-
<a href="https://app.datadoghq.com/logs?query=metta_run_id%3A%22${job.experiment_id}%22" target="_blank" class="wandb-link" onclick="event.stopPropagation();" title="Open in Datadog" style="font-size: 10px;">log</a>
1022-
<span class="status-badge ${job.status.toLowerCase()}" title="${job.status}">${abbreviateStatus(job.status)}</span>
1023-
<span style="color: #666; font-size: 11px;">${formatTime(job.created_at)}</span>
1024-
</div>
1025-
`;
1030+
<tr>
1031+
<td style="padding: 4px 6px; font-size: 11px; border-bottom: 1px solid #eee; white-space: nowrap; cursor: pointer;" onclick="event.stopPropagation(); copyToClipboard('${job.id}', event); return false;" title="Click to copy full ID">${numericId}</td>
1032+
<td style="padding: 4px 6px; font-size: 11px; border-bottom: 1px solid #eee; white-space: nowrap;"><span class="status-badge ${job.status.toLowerCase()}" title="${job.status}">${abbreviateStatus(job.status)}</span></td>
1033+
<td style="padding: 4px 6px; font-size: 11px; border-bottom: 1px solid #eee; white-space: nowrap;"><a href="https://app.datadoghq.com/logs?query=metta_run_id%3A%22${job.experiment_id}%22" target="_blank" class="wandb-link" onclick="event.stopPropagation();" title="Open in Datadog">log</a></td>
1034+
</tr>
1035+
`;
10261036
}).join('');
10271037

1038+
const html = `
1039+
<table style="width: 100%; border-collapse: collapse; font-size: 11px;">
1040+
<thead>
1041+
<tr style="background: #f5f5f5;">
1042+
<th style="padding: 4px 6px; text-align: left; font-weight: 600; border-bottom: 2px solid #e0e0e0; font-size: 10px; white-space: nowrap;">Job ID</th>
1043+
<th style="padding: 4px 6px; text-align: left; font-weight: 600; border-bottom: 2px solid #e0e0e0; font-size: 10px; white-space: nowrap;">Status</th>
1044+
<th style="padding: 4px 6px; text-align: left; font-weight: 600; border-bottom: 2px solid #e0e0e0; font-size: 10px; white-space: nowrap;">Links</th>
1045+
</tr>
1046+
</thead>
1047+
<tbody>
1048+
${rows}
1049+
</tbody>
1050+
</table>
1051+
`;
1052+
10281053
container.innerHTML = html;
10291054
} catch (error) {
10301055
container.innerHTML = '<p style="color: #f44336; font-size: 12px;">Error loading jobs</p>';
@@ -1071,7 +1096,7 @@ async function loadCheckpoints(experimentId) {
10711096
return `
10721097
<tr>
10731098
<td style="padding: 4px 6px; font-size: 11px; border-bottom: 1px solid #eee; white-space: nowrap;">${cp.epoch}</td>
1074-
<td style="padding: 4px 6px; font-size: 11px; border-bottom: 1px solid #eee; color: #666; white-space: nowrap;">${formatTime(cp.created_at)}</td>
1099+
<td style="padding: 4px 6px; font-size: 11px; border-bottom: 1px solid #eee; color: #666; white-space: nowrap;">${formatDuration(cp.created_at)}</td>
10751100
<td style="padding: 4px 6px; font-size: 11px; border-bottom: 1px solid #eee; white-space: nowrap;">${s3Pill}${mettaPill}${obsPill}</td>
10761101
<td style="padding: 4px 6px; font-size: 11px; border-bottom: 1px solid #eee; text-align: center; white-space: nowrap;">${replayCount}</td>
10771102
</tr>
@@ -1173,20 +1198,20 @@ async function toggleConfigEdit(experimentId) {
11731198
const flagsTable = document.getElementById(`flags-table-${experimentId}`);
11741199
const addFlagBtn = document.getElementById(`add-flag-btn-${experimentId}`);
11751200

1176-
if (btn.textContent === 'Edit') {
1201+
if (btn.textContent === '') {
11771202
// Enter edit mode
1178-
btn.textContent = 'Save';
1179-
// Add Cancel button
1203+
// Add Save and Cancel icons
11801204
btnContainer.innerHTML = `
1181-
<button class="btn btn-small" onclick="toggleConfigEdit('${experimentId}')" id="config-edit-btn-${experimentId}">Save</button>
1182-
<button class="btn btn-small" onclick="cancelConfigEdit('${experimentId}')">Cancel</button>
1205+
<a onclick="toggleConfigEdit('${experimentId}')" id="config-edit-btn-${experimentId}" class="wandb-link" style="cursor: pointer; margin-left: 0; margin-right: 6px; font-size: 12px; color: #4caf50;" title="Save changes">✓</a>
1206+
<a onclick="cancelConfigEdit('${experimentId}')" class="wandb-link" style="cursor: pointer; margin-left: 0; font-size: 12px; color: #f44336;" title="Cancel">✗</a>
11831207
`;
11841208
editingRows.add(experimentId); // Track that this row is being edited
11851209

11861210
// Make config fields editable
11871211
fields.forEach(field => {
11881212
const currentValue = field.textContent === '-' ? '' : field.textContent;
11891213
field.dataset.originalValue = currentValue;
1214+
field.textContent = currentValue; // Clear '-' from display
11901215

11911216
field.contentEditable = 'true';
11921217
field.style.backgroundColor = '#fff8e1';
@@ -1225,7 +1250,7 @@ async function toggleConfigEdit(experimentId) {
12251250

12261251
} else {
12271252
// Save mode
1228-
btnContainer.innerHTML = `<button class="btn btn-small" onclick="toggleConfigEdit('${experimentId}')" id="config-edit-btn-${experimentId}" disabled>Saving...</button>`;
1253+
btnContainer.innerHTML = `<a class="wandb-link" style="cursor: not-allowed; margin-left: 0; font-size: 12px; color: #999;" title="Saving...">✓</a>`;
12291254
editingRows.delete(experimentId); // Remove from editing tracking
12301255

12311256
try {
@@ -1376,6 +1401,22 @@ function updateJobsTable(jobs) {
13761401
);
13771402
}
13781403

1404+
// Compute hash to detect changes
1405+
const jobsHash = JSON.stringify(filteredJobs.map(j => ({
1406+
id: j.id,
1407+
status: j.status,
1408+
experiment_id: j.experiment_id,
1409+
nodes: j.nodes,
1410+
gpus: j.gpus,
1411+
started_at: j.started_at
1412+
}))) + selectedJobs.size;
1413+
1414+
// Only update if data has changed
1415+
if (jobsHash === lastJobsHash && tbody.children.length > 0) {
1416+
return;
1417+
}
1418+
lastJobsHash = jobsHash;
1419+
13791420
if (!filteredJobs || filteredJobs.length === 0) {
13801421
tbody.innerHTML = '<tr><td colspan="7" class="empty-state">No jobs match the current filters</td></tr>';
13811422
updateJobsBulkActions();
@@ -1399,19 +1440,22 @@ function updateJobsTable(jobs) {
13991440

14001441
return `
14011442
<tr>
1402-
<td class="col-checkbox">
1443+
<td class="col-checkbox" style="padding: 4px 2px;">
14031444
<input type="checkbox" ${canStop ? '' : 'disabled'} ${isSelected ? 'checked' : ''} onchange="toggleJobSelection('${job.id}', this.checked)">
14041445
</td>
1405-
<td style="font-family: monospace; font-size: 12px;">
1406-
${job.id}
1446+
<td style="font-family: monospace; font-size: 12px; padding: 4px 6px;">
1447+
<span onclick="copyToClipboard('${job.id}'); return false;" style="cursor: pointer;" title="Click to copy">${job.id}</span>
1448+
<a href="https://wandb.ai/metta-research/metta/runs/${job.experiment_id}" target="_blank" class="wandb-link" onclick="event.stopPropagation();" title="Open in W&B" style="margin-left: 6px;">w&b</a>
14071449
<a href="https://skypilot-api.softmax-research.net/dashboard/jobs/${job.id}" target="_blank" class="wandb-link" onclick="event.stopPropagation();" title="Open in SkyPilot Dashboard" style="margin-left: 6px;">sky</a>
14081450
<a href="https://app.datadoghq.com/logs?query=metta_run_id%3A%22${job.experiment_id}%22" target="_blank" class="wandb-link" onclick="event.stopPropagation();" title="Open in Datadog" style="margin-left: 6px;">log</a>
14091451
</td>
1410-
<td style="max-width: 300px; overflow: hidden; text-overflow: ellipsis;" title="${job.experiment_id}">${job.experiment_id}</td>
1411-
<td><span class="status-badge ${job.status.toLowerCase()}" title="${job.status}">${abbreviateStatus(job.status)}</span></td>
1412-
<td style="font-family: monospace; font-size: 11px;">${resources}</td>
1413-
<td style="font-size: 11px;">${formatDuration(job.started_at)}</td>
1414-
<td style="font-size: 11px;">${job.started_at ? formatTime(job.started_at) : 'Not started'}</td>
1452+
<td style="max-width: 300px; overflow: hidden; text-overflow: ellipsis; padding: 4px 6px;">
1453+
<span onclick="copyToClipboard('${job.experiment_id.replace(/'/g, "\\'")}'); return false;" style="cursor: pointer;" title="Click to copy">${job.experiment_id}</span>
1454+
</td>
1455+
<td style="padding: 4px 6px;"><span class="status-badge ${job.status.toLowerCase()}" title="${job.status}">${abbreviateStatus(job.status)}</span></td>
1456+
<td style="font-family: monospace; font-size: 11px; padding: 4px 6px;">${resources}</td>
1457+
<td style="font-size: 11px; padding: 4px 6px;">${formatDuration(job.started_at)}</td>
1458+
<td style="font-size: 11px; padding: 4px 6px;">${job.started_at ? formatTime(job.started_at) : 'Not started'}</td>
14151459
</tr>
14161460
`;
14171461
}).join('');
@@ -1541,7 +1585,6 @@ async function duplicateExperiment(experimentId) {
15411585
name: newId,
15421586
base_command: exp.base_command,
15431587
tool_path: exp.tool_path,
1544-
run_name: exp.run_name,
15451588
git_branch: exp.git_branch,
15461589
nodes: exp.nodes,
15471590
gpus: exp.gpus,
@@ -1830,13 +1873,3 @@ async function saveExperimentOrder(order) {
18301873
console.error('Error saving experiment order:', error);
18311874
}
18321875
}
1833-
1834-
// Copy to clipboard function
1835-
function copyToClipboard(text, message) {
1836-
navigator.clipboard.writeText(text).then(() => {
1837-
showNotification(message || 'Copied to clipboard!', 'success');
1838-
}).catch (err => {
1839-
console.error('Failed to copy:', err);
1840-
showNotification('Failed to copy to clipboard', 'error');
1841-
});
1842-
}

0 commit comments

Comments
 (0)