edsl 0.1.57__py3-none-any.whl → 0.1.59__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- edsl/__version__.py +1 -1
- edsl/agents/agent.py +23 -4
- edsl/agents/agent_list.py +36 -6
- edsl/coop/coop.py +274 -35
- edsl/coop/utils.py +63 -0
- edsl/dataset/dataset.py +74 -0
- edsl/dataset/dataset_operations_mixin.py +67 -62
- edsl/inference_services/services/test_service.py +1 -1
- edsl/interviews/exception_tracking.py +92 -20
- edsl/invigilators/invigilators.py +5 -1
- edsl/invigilators/prompt_constructor.py +299 -136
- edsl/jobs/html_table_job_logger.py +394 -48
- edsl/jobs/jobs_pricing_estimation.py +19 -114
- edsl/jobs/jobs_remote_inference_logger.py +29 -0
- edsl/jobs/jobs_runner_status.py +52 -21
- edsl/jobs/remote_inference.py +214 -30
- edsl/language_models/language_model.py +40 -3
- edsl/language_models/price_manager.py +91 -57
- edsl/prompts/prompt.py +1 -0
- edsl/questions/question_list.py +76 -20
- edsl/results/results.py +8 -1
- edsl/scenarios/file_store.py +8 -12
- edsl/scenarios/scenario.py +50 -2
- edsl/scenarios/scenario_list.py +34 -12
- edsl/surveys/survey.py +4 -0
- edsl/tasks/task_history.py +180 -6
- edsl/utilities/wikipedia.py +194 -0
- {edsl-0.1.57.dist-info → edsl-0.1.59.dist-info}/METADATA +4 -3
- {edsl-0.1.57.dist-info → edsl-0.1.59.dist-info}/RECORD +32 -32
- edsl/language_models/compute_cost.py +0 -78
- {edsl-0.1.57.dist-info → edsl-0.1.59.dist-info}/LICENSE +0 -0
- {edsl-0.1.57.dist-info → edsl-0.1.59.dist-info}/WHEEL +0 -0
- {edsl-0.1.57.dist-info → edsl-0.1.59.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,7 @@
|
|
1
1
|
import re
|
2
2
|
import uuid
|
3
3
|
from datetime import datetime
|
4
|
+
from typing import Union
|
4
5
|
|
5
6
|
from IPython.display import display, HTML
|
6
7
|
|
@@ -61,16 +62,28 @@ class HTMLTableJobLogger(JobLogger):
|
|
61
62
|
text,
|
62
63
|
)
|
63
64
|
|
64
|
-
def _create_uuid_copy_button(
|
65
|
+
def _create_uuid_copy_button(
|
66
|
+
self, uuid_value: str, helper_text: Union[str, None] = None
|
67
|
+
) -> str:
|
65
68
|
"""Create a UUID display with click-to-copy functionality"""
|
66
69
|
short_uuid = uuid_value
|
67
70
|
if len(uuid_value) > 12:
|
68
71
|
short_uuid = f"{uuid_value[:8]}...{uuid_value[-4:]}"
|
69
72
|
|
70
73
|
return f"""
|
71
|
-
<div class="uuid-container"
|
72
|
-
<
|
73
|
-
|
74
|
+
<div class="uuid-container-wrapper">
|
75
|
+
<div class="uuid-container" title="{uuid_value}">
|
76
|
+
<span class="uuid-code">{short_uuid}</span>
|
77
|
+
{self._create_copy_button(uuid_value)}
|
78
|
+
</div>
|
79
|
+
{f'<div class="helper-text">{helper_text}</div>' if helper_text else ''}
|
80
|
+
</div>
|
81
|
+
"""
|
82
|
+
|
83
|
+
def _create_copy_button(self, value: str) -> str:
|
84
|
+
"""Create a button with click-to-copy functionality"""
|
85
|
+
return f"""
|
86
|
+
<button class="copy-btn" onclick="navigator.clipboard.writeText('{value}').then(() => {{
|
74
87
|
const btn = this;
|
75
88
|
btn.querySelector('.copy-icon').style.display = 'none';
|
76
89
|
btn.querySelector('.check-icon').style.display = 'block';
|
@@ -87,7 +100,6 @@ class HTMLTableJobLogger(JobLogger):
|
|
87
100
|
<polyline points="20 6 9 17 4 12"></polyline>
|
88
101
|
</svg>
|
89
102
|
</button>
|
90
|
-
</div>
|
91
103
|
"""
|
92
104
|
|
93
105
|
def update(self, message: str, status: JobsStatus = JobsStatus.RUNNING):
|
@@ -107,9 +119,180 @@ class HTMLTableJobLogger(JobLogger):
|
|
107
119
|
else:
|
108
120
|
return None
|
109
121
|
|
122
|
+
def _collapse(self, content_id: str, arrow_id: str) -> str:
|
123
|
+
"""Generate the onclick JavaScript for collapsible sections"""
|
124
|
+
return f"""
|
125
|
+
const content = document.getElementById('{content_id}');
|
126
|
+
const arrow = document.getElementById('{arrow_id}');
|
127
|
+
if (content.style.display === 'none') {{
|
128
|
+
content.style.display = 'block';
|
129
|
+
arrow.innerHTML = '⌃';
|
130
|
+
}} else {{
|
131
|
+
content.style.display = 'none';
|
132
|
+
arrow.innerHTML = '⌄';
|
133
|
+
}}
|
134
|
+
"""
|
135
|
+
|
136
|
+
def _build_exceptions_table(self) -> str:
|
137
|
+
"""Generate HTML for the exceptions summary table section."""
|
138
|
+
if not self.jobs_info.exception_summary:
|
139
|
+
return ""
|
140
|
+
|
141
|
+
total_exceptions = sum(
|
142
|
+
exc.exception_count for exc in self.jobs_info.exception_summary
|
143
|
+
)
|
144
|
+
|
145
|
+
# Generate exception rows HTML before the return
|
146
|
+
exception_rows = "".join(
|
147
|
+
f"""
|
148
|
+
<tr>
|
149
|
+
<td>{exc.exception_type or '-'}</td>
|
150
|
+
<td>{exc.inference_service or '-'}</td>
|
151
|
+
<td>{exc.model or '-'}</td>
|
152
|
+
<td>{exc.question_name or '-'}</td>
|
153
|
+
<td class='exception-count'>{exc.exception_count:,}</td>
|
154
|
+
</tr>
|
155
|
+
"""
|
156
|
+
for exc in self.jobs_info.exception_summary
|
157
|
+
)
|
158
|
+
|
159
|
+
# Get the error report URL if it exists
|
160
|
+
error_report_url = getattr(self.jobs_info, "error_report_url", None)
|
161
|
+
error_report_link = (
|
162
|
+
f"""
|
163
|
+
<div style="margin-bottom: 12px; font-size: 0.85em;">
|
164
|
+
<a href="{error_report_url}" target="_blank" class="pill-link">
|
165
|
+
View full exceptions report{self.external_link_icon}
|
166
|
+
</a>
|
167
|
+
</div>
|
168
|
+
"""
|
169
|
+
if error_report_url
|
170
|
+
else ""
|
171
|
+
)
|
172
|
+
|
173
|
+
return f"""
|
174
|
+
<div class="exception-section">
|
175
|
+
<div class="exception-header" onclick="{self._collapse(f'exception-content-{self.log_id}', f'exception-arrow-{self.log_id}')}">
|
176
|
+
<span id="exception-arrow-{self.log_id}" class="expand-toggle">⌃</span>
|
177
|
+
<span>Exception Summary ({total_exceptions:,} total)</span>
|
178
|
+
<span style="flex-grow: 1;"></span>
|
179
|
+
</div>
|
180
|
+
<div id="exception-content-{self.log_id}" class="exception-content">
|
181
|
+
{error_report_link}
|
182
|
+
<table class='exception-table'>
|
183
|
+
<thead>
|
184
|
+
<tr>
|
185
|
+
<th>Exception Type</th>
|
186
|
+
<th>Service</th>
|
187
|
+
<th>Model</th>
|
188
|
+
<th>Question</th>
|
189
|
+
<th>Count</th>
|
190
|
+
</tr>
|
191
|
+
</thead>
|
192
|
+
<tbody>
|
193
|
+
{exception_rows}
|
194
|
+
</tbody>
|
195
|
+
</table>
|
196
|
+
</div>
|
197
|
+
</div>
|
198
|
+
"""
|
199
|
+
|
200
|
+
def _build_model_costs_table(self) -> str:
|
201
|
+
"""Generate HTML for the model costs summary table section."""
|
202
|
+
if not hasattr(self.jobs_info, "model_costs") or not self.jobs_info.model_costs:
|
203
|
+
return ""
|
204
|
+
|
205
|
+
# Calculate totals
|
206
|
+
total_input_tokens = sum(
|
207
|
+
cost.input_tokens or 0 for cost in self.jobs_info.model_costs
|
208
|
+
)
|
209
|
+
total_output_tokens = sum(
|
210
|
+
cost.output_tokens or 0 for cost in self.jobs_info.model_costs
|
211
|
+
)
|
212
|
+
total_input_cost = sum(
|
213
|
+
cost.input_cost_usd or 0 for cost in self.jobs_info.model_costs
|
214
|
+
)
|
215
|
+
total_output_cost = sum(
|
216
|
+
cost.output_cost_usd or 0 for cost in self.jobs_info.model_costs
|
217
|
+
)
|
218
|
+
total_cost = total_input_cost + total_output_cost
|
219
|
+
|
220
|
+
# Calculate credit totals
|
221
|
+
total_input_credits = sum(
|
222
|
+
cost.input_cost_credits_with_cache or 0
|
223
|
+
for cost in self.jobs_info.model_costs
|
224
|
+
)
|
225
|
+
total_output_credits = sum(
|
226
|
+
cost.output_cost_credits_with_cache or 0
|
227
|
+
for cost in self.jobs_info.model_costs
|
228
|
+
)
|
229
|
+
total_credits = total_input_credits + total_output_credits
|
230
|
+
|
231
|
+
# Generate cost rows HTML with class names for right alignment
|
232
|
+
cost_rows = "".join(
|
233
|
+
f"""
|
234
|
+
<tr>
|
235
|
+
<td>{cost.service or '-'}</td>
|
236
|
+
<td>{cost.model or '-'}</td>
|
237
|
+
<td class='token-count'>{cost.input_tokens:,}</td>
|
238
|
+
<td class='cost-value'>${cost.input_cost_usd:.4f}</td>
|
239
|
+
<td class='token-count'>{cost.output_tokens:,}</td>
|
240
|
+
<td class='cost-value'>${cost.output_cost_usd:.4f}</td>
|
241
|
+
<td class='cost-value'>${(cost.input_cost_usd or 0) + (cost.output_cost_usd or 0):.4f}</td>
|
242
|
+
<td class='cost-value'>{(cost.input_cost_credits_with_cache or 0) + (cost.output_cost_credits_with_cache or 0):,.2f}</td>
|
243
|
+
</tr>
|
244
|
+
"""
|
245
|
+
for cost in self.jobs_info.model_costs
|
246
|
+
)
|
247
|
+
|
248
|
+
# Add total row with the same alignment classes
|
249
|
+
total_row = f"""
|
250
|
+
<tr class='totals-row'>
|
251
|
+
<td colspan='2'><strong>Totals</strong></td>
|
252
|
+
<td class='token-count'>{total_input_tokens:,}</td>
|
253
|
+
<td class='cost-value'>${total_input_cost:.4f}</td>
|
254
|
+
<td class='token-count'>{total_output_tokens:,}</td>
|
255
|
+
<td class='cost-value'>${total_output_cost:.4f}</td>
|
256
|
+
<td class='cost-value'>${total_cost:.4f}</td>
|
257
|
+
<td class='cost-value'>{total_credits:,.2f}</td>
|
258
|
+
</tr>
|
259
|
+
"""
|
260
|
+
|
261
|
+
return f"""
|
262
|
+
<div class="model-costs-section">
|
263
|
+
<div class="model-costs-header" onclick="{self._collapse(f'model-costs-content-{self.log_id}', f'model-costs-arrow-{self.log_id}')}">
|
264
|
+
<span id="model-costs-arrow-{self.log_id}" class="expand-toggle">⌃</span>
|
265
|
+
<span>Model Costs (${total_cost:.4f} / {total_credits:,.2f} credits total)</span>
|
266
|
+
<span style="flex-grow: 1;"></span>
|
267
|
+
</div>
|
268
|
+
<div id="model-costs-content-{self.log_id}" class="model-costs-content">
|
269
|
+
<table class='model-costs-table'>
|
270
|
+
<thead>
|
271
|
+
<tr>
|
272
|
+
<th>Service</th>
|
273
|
+
<th>Model</th>
|
274
|
+
<th class="cost-header">Input Tokens</th>
|
275
|
+
<th class="cost-header">Input Cost</th>
|
276
|
+
<th class="cost-header">Output Tokens</th>
|
277
|
+
<th class="cost-header">Output Cost</th>
|
278
|
+
<th class="cost-header">Total Cost</th>
|
279
|
+
<th class="cost-header">Total Credits</th>
|
280
|
+
</tr>
|
281
|
+
</thead>
|
282
|
+
<tbody>
|
283
|
+
{cost_rows}
|
284
|
+
{total_row}
|
285
|
+
</tbody>
|
286
|
+
</table>
|
287
|
+
<p style="font-style: italic; margin-top: 8px; font-size: 0.85em; color: #4b5563;">
|
288
|
+
You can obtain the total credit cost by multiplying the total USD cost by 100. A lower credit cost indicates that you saved money by retrieving responses from the universal remote cache.
|
289
|
+
</p>
|
290
|
+
</div>
|
291
|
+
</div>
|
292
|
+
"""
|
293
|
+
|
110
294
|
def _get_html(self, current_status: JobsStatus = JobsStatus.RUNNING) -> str:
|
111
295
|
"""Generate the complete HTML display with modern design"""
|
112
|
-
# CSS for modern styling
|
113
296
|
css = """
|
114
297
|
<style>
|
115
298
|
.jobs-container {
|
@@ -131,6 +314,8 @@ class HTMLTableJobLogger(JobLogger):
|
|
131
314
|
justify-content: space-between;
|
132
315
|
font-weight: 500;
|
133
316
|
font-size: 0.9em;
|
317
|
+
flex-wrap: wrap;
|
318
|
+
gap: 8px;
|
134
319
|
}
|
135
320
|
.jobs-content {
|
136
321
|
background: white;
|
@@ -162,7 +347,7 @@ class HTMLTableJobLogger(JobLogger):
|
|
162
347
|
border-bottom: 1px solid #cbd5e1;
|
163
348
|
font-size: 0.85em;
|
164
349
|
}
|
165
|
-
.
|
350
|
+
.three-column-grid {
|
166
351
|
display: flex;
|
167
352
|
flex-wrap: wrap;
|
168
353
|
gap: 1px;
|
@@ -171,11 +356,15 @@ class HTMLTableJobLogger(JobLogger):
|
|
171
356
|
.column {
|
172
357
|
background-color: white;
|
173
358
|
}
|
174
|
-
.column:
|
359
|
+
.column:nth-child(1) { /* Job Links */
|
175
360
|
flex: 1;
|
176
361
|
min-width: 150px;
|
177
362
|
}
|
178
|
-
.column:
|
363
|
+
.column:nth-child(2) { /* Content */
|
364
|
+
flex: 1;
|
365
|
+
min-width: 150px;
|
366
|
+
}
|
367
|
+
.column:nth-child(3) { /* Identifiers */
|
179
368
|
flex: 2;
|
180
369
|
min-width: 300px;
|
181
370
|
}
|
@@ -185,6 +374,7 @@ class HTMLTableJobLogger(JobLogger):
|
|
185
374
|
.link-item {
|
186
375
|
padding: 3px 0;
|
187
376
|
border-bottom: 1px solid #f1f5f9;
|
377
|
+
font-size: 0.9em;
|
188
378
|
}
|
189
379
|
.link-item:last-child {
|
190
380
|
border-bottom: none;
|
@@ -203,11 +393,18 @@ class HTMLTableJobLogger(JobLogger):
|
|
203
393
|
.progress-link .pill-link:hover {
|
204
394
|
border-bottom-color: #3b82f6;
|
205
395
|
}
|
396
|
+
.remote-link .pill-link {
|
397
|
+
color: #4b5563;
|
398
|
+
font-weight: 500;
|
399
|
+
}
|
400
|
+
.remote-link .pill-link:hover {
|
401
|
+
border-bottom-color: #4b5563;
|
402
|
+
}
|
206
403
|
.uuid-item {
|
207
404
|
padding: 3px 0;
|
208
405
|
border-bottom: 1px solid #f1f5f9;
|
209
406
|
display: flex;
|
210
|
-
align-items:
|
407
|
+
align-items: flex-start;
|
211
408
|
}
|
212
409
|
.uuid-item:last-child {
|
213
410
|
border-bottom: none;
|
@@ -224,12 +421,10 @@ class HTMLTableJobLogger(JobLogger):
|
|
224
421
|
line-height: 1.5;
|
225
422
|
}
|
226
423
|
.pill-link {
|
227
|
-
color: #3b82f6;
|
228
424
|
font-weight: 500;
|
229
425
|
text-decoration: none;
|
230
426
|
border-bottom: 1px dotted #bfdbfe;
|
231
427
|
transition: border-color 0.2s;
|
232
|
-
font-size: 0.75em;
|
233
428
|
display: inline-flex;
|
234
429
|
align-items: center;
|
235
430
|
gap: 4px;
|
@@ -245,13 +440,16 @@ class HTMLTableJobLogger(JobLogger):
|
|
245
440
|
.status-banner {
|
246
441
|
display: flex;
|
247
442
|
align-items: center;
|
443
|
+
flex-wrap: wrap;
|
444
|
+
gap: 8px;
|
248
445
|
padding: 5px 12px;
|
249
446
|
background-color: #f7fafc;
|
250
447
|
border-top: 1px solid #edf2f7;
|
251
448
|
font-size: 0.85em;
|
449
|
+
cursor: pointer;
|
252
450
|
}
|
253
451
|
.status-running { color: #3b82f6; }
|
254
|
-
.status-completed { color: #
|
452
|
+
.status-completed { color: #059669; }
|
255
453
|
.status-partially-failed { color: #d97706; }
|
256
454
|
.status-failed { color: #ef4444; }
|
257
455
|
.status-unknown { color: #6b7280; }
|
@@ -273,15 +471,23 @@ class HTMLTableJobLogger(JobLogger):
|
|
273
471
|
.link:hover {
|
274
472
|
border-bottom: 1px solid #3b82f6;
|
275
473
|
}
|
474
|
+
.uuid-container-wrapper {
|
475
|
+
display: flex;
|
476
|
+
flex-direction: column;
|
477
|
+
align-items: stretch;
|
478
|
+
gap: 4px;
|
479
|
+
flex: 1;
|
480
|
+
padding-bottom: 4px;
|
481
|
+
}
|
276
482
|
.uuid-container {
|
277
483
|
display: flex;
|
278
484
|
align-items: center;
|
279
485
|
background-color: #f8fafc;
|
280
486
|
border-radius: 3px;
|
281
487
|
padding: 2px 6px;
|
282
|
-
font-family: monospace;
|
488
|
+
font-family: "SF Mono", "Cascadia Mono", monospace;
|
283
489
|
font-size: 0.75em;
|
284
|
-
|
490
|
+
width: 100%; /* Make sure it fills the width */
|
285
491
|
}
|
286
492
|
.uuid-code {
|
287
493
|
color: #4b5563;
|
@@ -353,6 +559,118 @@ class HTMLTableJobLogger(JobLogger):
|
|
353
559
|
.status-completed.badge { background-color: #d1fae5; }
|
354
560
|
.status-partially-failed.badge { background-color: #fef3c7; }
|
355
561
|
.status-failed.badge { background-color: #fee2e2; }
|
562
|
+
.helper-text {
|
563
|
+
color: #4b5563;
|
564
|
+
font-size: 0.75em;
|
565
|
+
text-align: left;
|
566
|
+
}
|
567
|
+
/* Exception table styles */
|
568
|
+
.exception-section {
|
569
|
+
border-top: 1px solid #edf2f7;
|
570
|
+
}
|
571
|
+
.exception-header {
|
572
|
+
padding: 8px 12px;
|
573
|
+
background-color: #f7fafc;
|
574
|
+
display: flex;
|
575
|
+
align-items: center;
|
576
|
+
cursor: pointer;
|
577
|
+
font-size: 0.85em;
|
578
|
+
font-weight: 500;
|
579
|
+
user-select: none; /* Prevent text selection */
|
580
|
+
}
|
581
|
+
.exception-content {
|
582
|
+
padding: 12px;
|
583
|
+
}
|
584
|
+
.exception-table {
|
585
|
+
width: 100%;
|
586
|
+
border-collapse: collapse;
|
587
|
+
margin: 0;
|
588
|
+
font-size: 0.85em;
|
589
|
+
}
|
590
|
+
.exception-table th {
|
591
|
+
background-color: #f1f5f9;
|
592
|
+
color: #475569;
|
593
|
+
font-weight: 500;
|
594
|
+
text-align: left;
|
595
|
+
padding: 8px 12px;
|
596
|
+
border-bottom: 2px solid #e2e8f0;
|
597
|
+
}
|
598
|
+
.exception-table td {
|
599
|
+
padding: 6px 12px;
|
600
|
+
border-bottom: 1px solid #e2e8f0;
|
601
|
+
color: #1f2937;
|
602
|
+
text-align: left; /* Ensure left alignment */
|
603
|
+
}
|
604
|
+
.exception-table tr:last-child td {
|
605
|
+
border-bottom: none;
|
606
|
+
}
|
607
|
+
.exception-count {
|
608
|
+
font-weight: 500;
|
609
|
+
color: #ef4444;
|
610
|
+
}
|
611
|
+
/* Model costs table styles */
|
612
|
+
.model-costs-section {
|
613
|
+
border-top: 1px solid #edf2f7;
|
614
|
+
}
|
615
|
+
.model-costs-header {
|
616
|
+
padding: 8px 12px;
|
617
|
+
background-color: #f7fafc;
|
618
|
+
display: flex;
|
619
|
+
align-items: center;
|
620
|
+
cursor: pointer;
|
621
|
+
font-size: 0.85em;
|
622
|
+
font-weight: 500;
|
623
|
+
user-select: none;
|
624
|
+
}
|
625
|
+
.model-costs-content {
|
626
|
+
padding: 12px;
|
627
|
+
}
|
628
|
+
.model-costs-table {
|
629
|
+
width: 100%;
|
630
|
+
border-collapse: collapse;
|
631
|
+
margin: 0;
|
632
|
+
font-size: 0.85em;
|
633
|
+
}
|
634
|
+
.model-costs-table th {
|
635
|
+
background-color: #f1f5f9;
|
636
|
+
color: #475569;
|
637
|
+
font-weight: 500;
|
638
|
+
text-align: left; /* Default left alignment */
|
639
|
+
padding: 8px 12px;
|
640
|
+
border-bottom: 2px solid #e2e8f0;
|
641
|
+
}
|
642
|
+
.model-costs-table th.cost-header { /* New class for cost headers */
|
643
|
+
text-align: right;
|
644
|
+
}
|
645
|
+
.model-costs-table td {
|
646
|
+
padding: 6px 12px;
|
647
|
+
border-bottom: 1px solid #e2e8f0;
|
648
|
+
color: #1f2937;
|
649
|
+
text-align: left; /* Ensure left alignment for all cells by default */
|
650
|
+
}
|
651
|
+
.model-costs-table tr:last-child td {
|
652
|
+
border-bottom: none;
|
653
|
+
}
|
654
|
+
.token-count td, .cost-value td { /* Override for specific columns that need right alignment */
|
655
|
+
text-align: right;
|
656
|
+
}
|
657
|
+
.totals-row {
|
658
|
+
background-color: #f8fafc;
|
659
|
+
}
|
660
|
+
.totals-row td {
|
661
|
+
border-top: 2px solid #e2e8f0;
|
662
|
+
}
|
663
|
+
/* Model costs table styles */
|
664
|
+
.model-costs-table td.token-count,
|
665
|
+
.model-costs-table td.cost-value {
|
666
|
+
text-align: right; /* Right align the token counts and cost values */
|
667
|
+
}
|
668
|
+
.code-text {
|
669
|
+
font-family: "SF Mono", "Cascadia Mono", monospace;
|
670
|
+
background-color: #f8fafc;
|
671
|
+
padding: 1px 4px;
|
672
|
+
border-radius: 3px;
|
673
|
+
}
|
356
674
|
</style>
|
357
675
|
"""
|
358
676
|
|
@@ -366,6 +684,8 @@ class HTMLTableJobLogger(JobLogger):
|
|
366
684
|
"pretty_names",
|
367
685
|
"completed_interviews",
|
368
686
|
"failed_interviews",
|
687
|
+
"exception_summary",
|
688
|
+
"model_costs",
|
369
689
|
]:
|
370
690
|
value = getattr(self.jobs_info, field)
|
371
691
|
if not value:
|
@@ -382,17 +702,18 @@ class HTMLTableJobLogger(JobLogger):
|
|
382
702
|
else:
|
383
703
|
other_fields.append((field, pretty_name, value))
|
384
704
|
|
385
|
-
# Build a
|
705
|
+
# Build a three-column layout
|
386
706
|
content_html = """
|
387
|
-
<div class="
|
707
|
+
<div class="three-column-grid">
|
388
708
|
<div class="column">
|
389
|
-
<div class="section-header">Links</div>
|
709
|
+
<div class="section-header">Job Links</div>
|
390
710
|
<div class="content-box">
|
391
711
|
"""
|
392
712
|
|
393
|
-
# Sort URLs to prioritize Results first, then Progress
|
713
|
+
# Sort URLs to prioritize Results first, then Progress
|
394
714
|
results_links = []
|
395
715
|
progress_links = []
|
716
|
+
remote_links = []
|
396
717
|
other_links = []
|
397
718
|
|
398
719
|
for field, pretty_name, value in url_fields:
|
@@ -404,32 +725,46 @@ class HTMLTableJobLogger(JobLogger):
|
|
404
725
|
|
405
726
|
if "result" in field.lower():
|
406
727
|
results_links.append((field, pretty_name, value, label))
|
407
|
-
elif "progress" in field.lower():
|
728
|
+
elif "progress" in field.lower() or "error_report" in field.lower():
|
408
729
|
progress_links.append((field, pretty_name, value, label))
|
730
|
+
elif "remote_cache" in field.lower() or "remote_inference" in field.lower():
|
731
|
+
remote_links.append((field, pretty_name, value, label))
|
409
732
|
else:
|
410
733
|
other_links.append((field, pretty_name, value, label))
|
411
734
|
|
412
|
-
# Add results links first
|
735
|
+
# Add results and progress links to first column
|
413
736
|
for field, pretty_name, value, label in results_links:
|
414
737
|
content_html += f"""
|
415
|
-
<div class="link-item results-link">
|
738
|
+
<div class="link-item results-link" style="display: flex; align-items: center; justify-content: space-between;">
|
416
739
|
<a href="{value}" target="_blank" class="pill-link">{label}{self.external_link_icon}</a>
|
740
|
+
{self._create_copy_button(value)}
|
417
741
|
</div>
|
418
742
|
"""
|
419
743
|
|
420
744
|
# Then add progress links with different special styling
|
421
745
|
for field, pretty_name, value, label in progress_links:
|
422
746
|
content_html += f"""
|
423
|
-
<div class="link-item progress-link">
|
747
|
+
<div class="link-item progress-link" style="display: flex; align-items: center; justify-content: space-between;">
|
424
748
|
<a href="{value}" target="_blank" class="pill-link">{label}{self.external_link_icon}</a>
|
749
|
+
{self._create_copy_button(value)}
|
425
750
|
</div>
|
426
751
|
"""
|
427
752
|
|
428
|
-
#
|
429
|
-
|
753
|
+
# Close first column and start second column
|
754
|
+
content_html += """
|
755
|
+
</div>
|
756
|
+
</div>
|
757
|
+
<div class="column">
|
758
|
+
<div class="section-header">Content</div>
|
759
|
+
<div class="content-box">
|
760
|
+
"""
|
761
|
+
|
762
|
+
# Add remote links to middle column
|
763
|
+
for field, pretty_name, value, label in remote_links + other_links:
|
430
764
|
content_html += f"""
|
431
|
-
<div class="link-item">
|
765
|
+
<div class="link-item remote-link" style="display: flex; align-items: center; justify-content: space-between;">
|
432
766
|
<a href="{value}" target="_blank" class="pill-link">{label}{self.external_link_icon}</a>
|
767
|
+
{self._create_copy_button(value)}
|
433
768
|
</div>
|
434
769
|
"""
|
435
770
|
|
@@ -444,10 +779,18 @@ class HTMLTableJobLogger(JobLogger):
|
|
444
779
|
# Sort UUIDs to prioritize Result UUID first
|
445
780
|
uuid_fields.sort(key=lambda x: 0 if "result" in x[0].lower() else 1)
|
446
781
|
for field, pretty_name, value in uuid_fields:
|
447
|
-
|
782
|
+
if "result" in field.lower():
|
783
|
+
helper_text = "Use <span class='code-text'>Results.pull(uuid)</span> to fetch results."
|
784
|
+
elif "job" in field.lower():
|
785
|
+
helper_text = (
|
786
|
+
"Use <span class='code-text'>Jobs.pull(uuid)</span> to fetch job."
|
787
|
+
)
|
788
|
+
else:
|
789
|
+
helper_text = ""
|
790
|
+
|
448
791
|
content_html += f"""
|
449
792
|
<div class="uuid-item">
|
450
|
-
<span class="uuid-label">{pretty_name}:</span>{self._create_uuid_copy_button(value)}
|
793
|
+
<span class="uuid-label">{pretty_name}:</span>{self._create_uuid_copy_button(value, helper_text)}
|
451
794
|
</div>
|
452
795
|
"""
|
453
796
|
|
@@ -470,7 +813,7 @@ class HTMLTableJobLogger(JobLogger):
|
|
470
813
|
"""
|
471
814
|
content_html += "</table>"
|
472
815
|
|
473
|
-
# Status banner
|
816
|
+
# Status banner and message log
|
474
817
|
status_class = {
|
475
818
|
JobsStatus.RUNNING: "status-running",
|
476
819
|
JobsStatus.COMPLETED: "status-completed",
|
@@ -488,9 +831,14 @@ class HTMLTableJobLogger(JobLogger):
|
|
488
831
|
status_text = str(current_status).capitalize()
|
489
832
|
|
490
833
|
status_banner = f"""
|
491
|
-
<div class="status-banner">
|
492
|
-
|
493
|
-
|
834
|
+
<div class="status-banner" onclick="{self._collapse(f'message-log-{self.log_id}', f'message-arrow-{self.log_id}')}">
|
835
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
836
|
+
<span id="message-arrow-{self.log_id}" class="expand-toggle">⌃</span>
|
837
|
+
<div style="display: flex; align-items: center;">
|
838
|
+
{status_icon}
|
839
|
+
<strong>Status:</strong> <span class="badge {status_class}">{status_text}</span>
|
840
|
+
</div>
|
841
|
+
</div>
|
494
842
|
<span style="flex-grow: 1;"></span>
|
495
843
|
<span>Last updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</span>
|
496
844
|
</div>
|
@@ -519,7 +867,7 @@ class HTMLTableJobLogger(JobLogger):
|
|
519
867
|
)
|
520
868
|
|
521
869
|
message_log = f"""
|
522
|
-
<div class="message-log">
|
870
|
+
<div id="message-log-{self.log_id}" class="message-log">
|
523
871
|
{''.join(reversed(message_items))}
|
524
872
|
</div>
|
525
873
|
"""
|
@@ -528,25 +876,25 @@ class HTMLTableJobLogger(JobLogger):
|
|
528
876
|
|
529
877
|
header_status_text = status_text
|
530
878
|
if (
|
531
|
-
|
532
|
-
and self.jobs_info.completed_interviews is not None
|
879
|
+
self.jobs_info.completed_interviews is not None
|
533
880
|
and self.jobs_info.failed_interviews is not None
|
534
881
|
):
|
535
882
|
header_status_text += f" ({self.jobs_info.completed_interviews:,} completed, {self.jobs_info.failed_interviews:,} failed)"
|
536
883
|
|
884
|
+
# Add model costs table before exceptions table
|
885
|
+
main_content = f"""
|
886
|
+
{content_html}
|
887
|
+
{status_banner}
|
888
|
+
{message_log}
|
889
|
+
{self._build_model_costs_table()}
|
890
|
+
{self._build_exceptions_table()}
|
891
|
+
"""
|
892
|
+
|
893
|
+
# Return the complete HTML
|
537
894
|
return f"""
|
538
895
|
{css}
|
539
896
|
<div class="jobs-container">
|
540
|
-
<div class="jobs-header" onclick="
|
541
|
-
const content = document.getElementById('content-{self.log_id}');
|
542
|
-
const arrow = document.getElementById('arrow-{self.log_id}');
|
543
|
-
if (content.style.display === 'none') {{
|
544
|
-
content.style.display = 'block';
|
545
|
-
arrow.innerHTML = '⌃';
|
546
|
-
}} else {{
|
547
|
-
content.style.display = 'none';
|
548
|
-
arrow.innerHTML = '⌄';
|
549
|
-
}}">
|
897
|
+
<div class="jobs-header" onclick="{self._collapse(f'content-{self.log_id}', f'arrow-{self.log_id}')}">
|
550
898
|
<div>
|
551
899
|
<span id="arrow-{self.log_id}" class="expand-toggle">{'⌃' if self.is_expanded else '⌄'}</span>
|
552
900
|
Job Status 🦜
|
@@ -554,9 +902,7 @@ class HTMLTableJobLogger(JobLogger):
|
|
554
902
|
<div class="{status_class}">{header_status_text}</div>
|
555
903
|
</div>
|
556
904
|
<div id="content-{self.log_id}" class="jobs-content" style="display: {display_style};">
|
557
|
-
{
|
558
|
-
{status_banner}
|
559
|
-
{message_log}
|
905
|
+
{main_content}
|
560
906
|
</div>
|
561
907
|
</div>
|
562
908
|
"""
|