edsl 0.1.56__py3-none-any.whl → 0.1.58__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/coop/coop.py +174 -37
- edsl/coop/utils.py +63 -0
- edsl/interviews/exception_tracking.py +26 -0
- edsl/jobs/html_table_job_logger.py +377 -48
- edsl/jobs/jobs_pricing_estimation.py +19 -96
- edsl/jobs/jobs_remote_inference_logger.py +27 -0
- edsl/jobs/jobs_runner_status.py +52 -21
- edsl/jobs/remote_inference.py +187 -30
- edsl/language_models/language_model.py +1 -1
- edsl/language_models/price_manager.py +91 -57
- {edsl-0.1.56.dist-info → edsl-0.1.58.dist-info}/METADATA +2 -2
- {edsl-0.1.56.dist-info → edsl-0.1.58.dist-info}/RECORD +16 -17
- edsl/language_models/compute_cost.py +0 -78
- {edsl-0.1.56.dist-info → edsl-0.1.58.dist-info}/LICENSE +0 -0
- {edsl-0.1.56.dist-info → edsl-0.1.58.dist-info}/WHEEL +0 -0
- {edsl-0.1.56.dist-info → edsl-0.1.58.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,163 @@ 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
|
+
# Generate cost rows HTML with class names for right alignment
|
221
|
+
cost_rows = "".join(
|
222
|
+
f"""
|
223
|
+
<tr>
|
224
|
+
<td>{cost.service or '-'}</td>
|
225
|
+
<td>{cost.model or '-'}</td>
|
226
|
+
<td class='token-count'>{cost.input_tokens:,}</td>
|
227
|
+
<td class='cost-value'>${cost.input_cost_usd:.4f}</td>
|
228
|
+
<td class='token-count'>{cost.output_tokens:,}</td>
|
229
|
+
<td class='cost-value'>${cost.output_cost_usd:.4f}</td>
|
230
|
+
<td class='cost-value'>${(cost.input_cost_usd or 0) + (cost.output_cost_usd or 0):.4f}</td>
|
231
|
+
</tr>
|
232
|
+
"""
|
233
|
+
for cost in self.jobs_info.model_costs
|
234
|
+
)
|
235
|
+
|
236
|
+
# Add total row with the same alignment classes
|
237
|
+
total_row = f"""
|
238
|
+
<tr class='totals-row'>
|
239
|
+
<td colspan='2'><strong>Totals</strong></td>
|
240
|
+
<td class='token-count'>{total_input_tokens:,}</td>
|
241
|
+
<td class='cost-value'>${total_input_cost:.4f}</td>
|
242
|
+
<td class='token-count'>{total_output_tokens:,}</td>
|
243
|
+
<td class='cost-value'>${total_output_cost:.4f}</td>
|
244
|
+
<td class='cost-value'>${total_cost:.4f}</td>
|
245
|
+
</tr>
|
246
|
+
"""
|
247
|
+
|
248
|
+
return f"""
|
249
|
+
<div class="model-costs-section">
|
250
|
+
<div class="model-costs-header" onclick="{self._collapse(f'model-costs-content-{self.log_id}', f'model-costs-arrow-{self.log_id}')}">
|
251
|
+
<span id="model-costs-arrow-{self.log_id}" class="expand-toggle">⌃</span>
|
252
|
+
<span>Model Costs (${total_cost:.4f} total)</span>
|
253
|
+
<span style="flex-grow: 1;"></span>
|
254
|
+
</div>
|
255
|
+
<div id="model-costs-content-{self.log_id}" class="model-costs-content">
|
256
|
+
<table class='model-costs-table'>
|
257
|
+
<thead>
|
258
|
+
<tr>
|
259
|
+
<th>Service</th>
|
260
|
+
<th>Model</th>
|
261
|
+
<th class="cost-header">Input Tokens</th>
|
262
|
+
<th class="cost-header">Input Cost</th>
|
263
|
+
<th class="cost-header">Output Tokens</th>
|
264
|
+
<th class="cost-header">Output Cost</th>
|
265
|
+
<th class="cost-header">Total Cost</th>
|
266
|
+
</tr>
|
267
|
+
</thead>
|
268
|
+
<tbody>
|
269
|
+
{cost_rows}
|
270
|
+
{total_row}
|
271
|
+
</tbody>
|
272
|
+
</table>
|
273
|
+
</div>
|
274
|
+
</div>
|
275
|
+
"""
|
276
|
+
|
110
277
|
def _get_html(self, current_status: JobsStatus = JobsStatus.RUNNING) -> str:
|
111
278
|
"""Generate the complete HTML display with modern design"""
|
112
|
-
# CSS for modern styling
|
113
279
|
css = """
|
114
280
|
<style>
|
115
281
|
.jobs-container {
|
@@ -131,6 +297,8 @@ class HTMLTableJobLogger(JobLogger):
|
|
131
297
|
justify-content: space-between;
|
132
298
|
font-weight: 500;
|
133
299
|
font-size: 0.9em;
|
300
|
+
flex-wrap: wrap;
|
301
|
+
gap: 8px;
|
134
302
|
}
|
135
303
|
.jobs-content {
|
136
304
|
background: white;
|
@@ -162,7 +330,7 @@ class HTMLTableJobLogger(JobLogger):
|
|
162
330
|
border-bottom: 1px solid #cbd5e1;
|
163
331
|
font-size: 0.85em;
|
164
332
|
}
|
165
|
-
.
|
333
|
+
.three-column-grid {
|
166
334
|
display: flex;
|
167
335
|
flex-wrap: wrap;
|
168
336
|
gap: 1px;
|
@@ -171,11 +339,15 @@ class HTMLTableJobLogger(JobLogger):
|
|
171
339
|
.column {
|
172
340
|
background-color: white;
|
173
341
|
}
|
174
|
-
.column:
|
342
|
+
.column:nth-child(1) { /* Job Links */
|
175
343
|
flex: 1;
|
176
344
|
min-width: 150px;
|
177
345
|
}
|
178
|
-
.column:
|
346
|
+
.column:nth-child(2) { /* Content */
|
347
|
+
flex: 1;
|
348
|
+
min-width: 150px;
|
349
|
+
}
|
350
|
+
.column:nth-child(3) { /* Identifiers */
|
179
351
|
flex: 2;
|
180
352
|
min-width: 300px;
|
181
353
|
}
|
@@ -185,6 +357,7 @@ class HTMLTableJobLogger(JobLogger):
|
|
185
357
|
.link-item {
|
186
358
|
padding: 3px 0;
|
187
359
|
border-bottom: 1px solid #f1f5f9;
|
360
|
+
font-size: 0.9em;
|
188
361
|
}
|
189
362
|
.link-item:last-child {
|
190
363
|
border-bottom: none;
|
@@ -203,11 +376,18 @@ class HTMLTableJobLogger(JobLogger):
|
|
203
376
|
.progress-link .pill-link:hover {
|
204
377
|
border-bottom-color: #3b82f6;
|
205
378
|
}
|
379
|
+
.remote-link .pill-link {
|
380
|
+
color: #4b5563;
|
381
|
+
font-weight: 500;
|
382
|
+
}
|
383
|
+
.remote-link .pill-link:hover {
|
384
|
+
border-bottom-color: #4b5563;
|
385
|
+
}
|
206
386
|
.uuid-item {
|
207
387
|
padding: 3px 0;
|
208
388
|
border-bottom: 1px solid #f1f5f9;
|
209
389
|
display: flex;
|
210
|
-
align-items:
|
390
|
+
align-items: flex-start;
|
211
391
|
}
|
212
392
|
.uuid-item:last-child {
|
213
393
|
border-bottom: none;
|
@@ -224,12 +404,10 @@ class HTMLTableJobLogger(JobLogger):
|
|
224
404
|
line-height: 1.5;
|
225
405
|
}
|
226
406
|
.pill-link {
|
227
|
-
color: #3b82f6;
|
228
407
|
font-weight: 500;
|
229
408
|
text-decoration: none;
|
230
409
|
border-bottom: 1px dotted #bfdbfe;
|
231
410
|
transition: border-color 0.2s;
|
232
|
-
font-size: 0.75em;
|
233
411
|
display: inline-flex;
|
234
412
|
align-items: center;
|
235
413
|
gap: 4px;
|
@@ -245,13 +423,16 @@ class HTMLTableJobLogger(JobLogger):
|
|
245
423
|
.status-banner {
|
246
424
|
display: flex;
|
247
425
|
align-items: center;
|
426
|
+
flex-wrap: wrap;
|
427
|
+
gap: 8px;
|
248
428
|
padding: 5px 12px;
|
249
429
|
background-color: #f7fafc;
|
250
430
|
border-top: 1px solid #edf2f7;
|
251
431
|
font-size: 0.85em;
|
432
|
+
cursor: pointer;
|
252
433
|
}
|
253
434
|
.status-running { color: #3b82f6; }
|
254
|
-
.status-completed { color: #
|
435
|
+
.status-completed { color: #059669; }
|
255
436
|
.status-partially-failed { color: #d97706; }
|
256
437
|
.status-failed { color: #ef4444; }
|
257
438
|
.status-unknown { color: #6b7280; }
|
@@ -273,15 +454,23 @@ class HTMLTableJobLogger(JobLogger):
|
|
273
454
|
.link:hover {
|
274
455
|
border-bottom: 1px solid #3b82f6;
|
275
456
|
}
|
457
|
+
.uuid-container-wrapper {
|
458
|
+
display: flex;
|
459
|
+
flex-direction: column;
|
460
|
+
align-items: stretch;
|
461
|
+
gap: 4px;
|
462
|
+
flex: 1;
|
463
|
+
padding-bottom: 4px;
|
464
|
+
}
|
276
465
|
.uuid-container {
|
277
466
|
display: flex;
|
278
467
|
align-items: center;
|
279
468
|
background-color: #f8fafc;
|
280
469
|
border-radius: 3px;
|
281
470
|
padding: 2px 6px;
|
282
|
-
font-family: monospace;
|
471
|
+
font-family: "SF Mono", "Cascadia Mono", monospace;
|
283
472
|
font-size: 0.75em;
|
284
|
-
|
473
|
+
width: 100%; /* Make sure it fills the width */
|
285
474
|
}
|
286
475
|
.uuid-code {
|
287
476
|
color: #4b5563;
|
@@ -353,6 +542,118 @@ class HTMLTableJobLogger(JobLogger):
|
|
353
542
|
.status-completed.badge { background-color: #d1fae5; }
|
354
543
|
.status-partially-failed.badge { background-color: #fef3c7; }
|
355
544
|
.status-failed.badge { background-color: #fee2e2; }
|
545
|
+
.helper-text {
|
546
|
+
color: #4b5563;
|
547
|
+
font-size: 0.75em;
|
548
|
+
text-align: left;
|
549
|
+
}
|
550
|
+
/* Exception table styles */
|
551
|
+
.exception-section {
|
552
|
+
border-top: 1px solid #edf2f7;
|
553
|
+
}
|
554
|
+
.exception-header {
|
555
|
+
padding: 8px 12px;
|
556
|
+
background-color: #f7fafc;
|
557
|
+
display: flex;
|
558
|
+
align-items: center;
|
559
|
+
cursor: pointer;
|
560
|
+
font-size: 0.85em;
|
561
|
+
font-weight: 500;
|
562
|
+
user-select: none; /* Prevent text selection */
|
563
|
+
}
|
564
|
+
.exception-content {
|
565
|
+
padding: 12px;
|
566
|
+
}
|
567
|
+
.exception-table {
|
568
|
+
width: 100%;
|
569
|
+
border-collapse: collapse;
|
570
|
+
margin: 0;
|
571
|
+
font-size: 0.85em;
|
572
|
+
}
|
573
|
+
.exception-table th {
|
574
|
+
background-color: #f1f5f9;
|
575
|
+
color: #475569;
|
576
|
+
font-weight: 500;
|
577
|
+
text-align: left;
|
578
|
+
padding: 8px 12px;
|
579
|
+
border-bottom: 2px solid #e2e8f0;
|
580
|
+
}
|
581
|
+
.exception-table td {
|
582
|
+
padding: 6px 12px;
|
583
|
+
border-bottom: 1px solid #e2e8f0;
|
584
|
+
color: #1f2937;
|
585
|
+
text-align: left; /* Ensure left alignment */
|
586
|
+
}
|
587
|
+
.exception-table tr:last-child td {
|
588
|
+
border-bottom: none;
|
589
|
+
}
|
590
|
+
.exception-count {
|
591
|
+
font-weight: 500;
|
592
|
+
color: #ef4444;
|
593
|
+
}
|
594
|
+
/* Model costs table styles */
|
595
|
+
.model-costs-section {
|
596
|
+
border-top: 1px solid #edf2f7;
|
597
|
+
}
|
598
|
+
.model-costs-header {
|
599
|
+
padding: 8px 12px;
|
600
|
+
background-color: #f7fafc;
|
601
|
+
display: flex;
|
602
|
+
align-items: center;
|
603
|
+
cursor: pointer;
|
604
|
+
font-size: 0.85em;
|
605
|
+
font-weight: 500;
|
606
|
+
user-select: none;
|
607
|
+
}
|
608
|
+
.model-costs-content {
|
609
|
+
padding: 12px;
|
610
|
+
}
|
611
|
+
.model-costs-table {
|
612
|
+
width: 100%;
|
613
|
+
border-collapse: collapse;
|
614
|
+
margin: 0;
|
615
|
+
font-size: 0.85em;
|
616
|
+
}
|
617
|
+
.model-costs-table th {
|
618
|
+
background-color: #f1f5f9;
|
619
|
+
color: #475569;
|
620
|
+
font-weight: 500;
|
621
|
+
text-align: left; /* Default left alignment */
|
622
|
+
padding: 8px 12px;
|
623
|
+
border-bottom: 2px solid #e2e8f0;
|
624
|
+
}
|
625
|
+
.model-costs-table th.cost-header { /* New class for cost headers */
|
626
|
+
text-align: right;
|
627
|
+
}
|
628
|
+
.model-costs-table td {
|
629
|
+
padding: 6px 12px;
|
630
|
+
border-bottom: 1px solid #e2e8f0;
|
631
|
+
color: #1f2937;
|
632
|
+
text-align: left; /* Ensure left alignment for all cells by default */
|
633
|
+
}
|
634
|
+
.model-costs-table tr:last-child td {
|
635
|
+
border-bottom: none;
|
636
|
+
}
|
637
|
+
.token-count td, .cost-value td { /* Override for specific columns that need right alignment */
|
638
|
+
text-align: right;
|
639
|
+
}
|
640
|
+
.totals-row {
|
641
|
+
background-color: #f8fafc;
|
642
|
+
}
|
643
|
+
.totals-row td {
|
644
|
+
border-top: 2px solid #e2e8f0;
|
645
|
+
}
|
646
|
+
/* Model costs table styles */
|
647
|
+
.model-costs-table td.token-count,
|
648
|
+
.model-costs-table td.cost-value {
|
649
|
+
text-align: right; /* Right align the token counts and cost values */
|
650
|
+
}
|
651
|
+
.code-text {
|
652
|
+
font-family: "SF Mono", "Cascadia Mono", monospace;
|
653
|
+
background-color: #f8fafc;
|
654
|
+
padding: 1px 4px;
|
655
|
+
border-radius: 3px;
|
656
|
+
}
|
356
657
|
</style>
|
357
658
|
"""
|
358
659
|
|
@@ -366,6 +667,8 @@ class HTMLTableJobLogger(JobLogger):
|
|
366
667
|
"pretty_names",
|
367
668
|
"completed_interviews",
|
368
669
|
"failed_interviews",
|
670
|
+
"exception_summary",
|
671
|
+
"model_costs",
|
369
672
|
]:
|
370
673
|
value = getattr(self.jobs_info, field)
|
371
674
|
if not value:
|
@@ -382,17 +685,18 @@ class HTMLTableJobLogger(JobLogger):
|
|
382
685
|
else:
|
383
686
|
other_fields.append((field, pretty_name, value))
|
384
687
|
|
385
|
-
# Build a
|
688
|
+
# Build a three-column layout
|
386
689
|
content_html = """
|
387
|
-
<div class="
|
690
|
+
<div class="three-column-grid">
|
388
691
|
<div class="column">
|
389
|
-
<div class="section-header">Links</div>
|
692
|
+
<div class="section-header">Job Links</div>
|
390
693
|
<div class="content-box">
|
391
694
|
"""
|
392
695
|
|
393
|
-
# Sort URLs to prioritize Results first, then Progress
|
696
|
+
# Sort URLs to prioritize Results first, then Progress
|
394
697
|
results_links = []
|
395
698
|
progress_links = []
|
699
|
+
remote_links = []
|
396
700
|
other_links = []
|
397
701
|
|
398
702
|
for field, pretty_name, value in url_fields:
|
@@ -404,32 +708,46 @@ class HTMLTableJobLogger(JobLogger):
|
|
404
708
|
|
405
709
|
if "result" in field.lower():
|
406
710
|
results_links.append((field, pretty_name, value, label))
|
407
|
-
elif "progress" in field.lower():
|
711
|
+
elif "progress" in field.lower() or "error_report" in field.lower():
|
408
712
|
progress_links.append((field, pretty_name, value, label))
|
713
|
+
elif "remote_cache" in field.lower() or "remote_inference" in field.lower():
|
714
|
+
remote_links.append((field, pretty_name, value, label))
|
409
715
|
else:
|
410
716
|
other_links.append((field, pretty_name, value, label))
|
411
717
|
|
412
|
-
# Add results links first
|
718
|
+
# Add results and progress links to first column
|
413
719
|
for field, pretty_name, value, label in results_links:
|
414
720
|
content_html += f"""
|
415
|
-
<div class="link-item results-link">
|
721
|
+
<div class="link-item results-link" style="display: flex; align-items: center; justify-content: space-between;">
|
416
722
|
<a href="{value}" target="_blank" class="pill-link">{label}{self.external_link_icon}</a>
|
723
|
+
{self._create_copy_button(value)}
|
417
724
|
</div>
|
418
725
|
"""
|
419
726
|
|
420
727
|
# Then add progress links with different special styling
|
421
728
|
for field, pretty_name, value, label in progress_links:
|
422
729
|
content_html += f"""
|
423
|
-
<div class="link-item progress-link">
|
730
|
+
<div class="link-item progress-link" style="display: flex; align-items: center; justify-content: space-between;">
|
424
731
|
<a href="{value}" target="_blank" class="pill-link">{label}{self.external_link_icon}</a>
|
732
|
+
{self._create_copy_button(value)}
|
425
733
|
</div>
|
426
734
|
"""
|
427
735
|
|
428
|
-
#
|
429
|
-
|
736
|
+
# Close first column and start second column
|
737
|
+
content_html += """
|
738
|
+
</div>
|
739
|
+
</div>
|
740
|
+
<div class="column">
|
741
|
+
<div class="section-header">Content</div>
|
742
|
+
<div class="content-box">
|
743
|
+
"""
|
744
|
+
|
745
|
+
# Add remote links to middle column
|
746
|
+
for field, pretty_name, value, label in remote_links + other_links:
|
430
747
|
content_html += f"""
|
431
|
-
<div class="link-item">
|
748
|
+
<div class="link-item remote-link" style="display: flex; align-items: center; justify-content: space-between;">
|
432
749
|
<a href="{value}" target="_blank" class="pill-link">{label}{self.external_link_icon}</a>
|
750
|
+
{self._create_copy_button(value)}
|
433
751
|
</div>
|
434
752
|
"""
|
435
753
|
|
@@ -444,10 +762,18 @@ class HTMLTableJobLogger(JobLogger):
|
|
444
762
|
# Sort UUIDs to prioritize Result UUID first
|
445
763
|
uuid_fields.sort(key=lambda x: 0 if "result" in x[0].lower() else 1)
|
446
764
|
for field, pretty_name, value in uuid_fields:
|
447
|
-
|
765
|
+
if "result" in field.lower():
|
766
|
+
helper_text = "Use <span class='code-text'>Results.pull(uuid)</span> to fetch results."
|
767
|
+
elif "job" in field.lower():
|
768
|
+
helper_text = (
|
769
|
+
"Use <span class='code-text'>Jobs.pull(uuid)</span> to fetch job."
|
770
|
+
)
|
771
|
+
else:
|
772
|
+
helper_text = ""
|
773
|
+
|
448
774
|
content_html += f"""
|
449
775
|
<div class="uuid-item">
|
450
|
-
<span class="uuid-label">{pretty_name}:</span>{self._create_uuid_copy_button(value)}
|
776
|
+
<span class="uuid-label">{pretty_name}:</span>{self._create_uuid_copy_button(value, helper_text)}
|
451
777
|
</div>
|
452
778
|
"""
|
453
779
|
|
@@ -470,7 +796,7 @@ class HTMLTableJobLogger(JobLogger):
|
|
470
796
|
"""
|
471
797
|
content_html += "</table>"
|
472
798
|
|
473
|
-
# Status banner
|
799
|
+
# Status banner and message log
|
474
800
|
status_class = {
|
475
801
|
JobsStatus.RUNNING: "status-running",
|
476
802
|
JobsStatus.COMPLETED: "status-completed",
|
@@ -488,9 +814,14 @@ class HTMLTableJobLogger(JobLogger):
|
|
488
814
|
status_text = str(current_status).capitalize()
|
489
815
|
|
490
816
|
status_banner = f"""
|
491
|
-
<div class="status-banner">
|
492
|
-
|
493
|
-
|
817
|
+
<div class="status-banner" onclick="{self._collapse(f'message-log-{self.log_id}', f'message-arrow-{self.log_id}')}">
|
818
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
819
|
+
<span id="message-arrow-{self.log_id}" class="expand-toggle">⌃</span>
|
820
|
+
<div style="display: flex; align-items: center;">
|
821
|
+
{status_icon}
|
822
|
+
<strong>Status:</strong> <span class="badge {status_class}">{status_text}</span>
|
823
|
+
</div>
|
824
|
+
</div>
|
494
825
|
<span style="flex-grow: 1;"></span>
|
495
826
|
<span>Last updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</span>
|
496
827
|
</div>
|
@@ -519,7 +850,7 @@ class HTMLTableJobLogger(JobLogger):
|
|
519
850
|
)
|
520
851
|
|
521
852
|
message_log = f"""
|
522
|
-
<div class="message-log">
|
853
|
+
<div id="message-log-{self.log_id}" class="message-log">
|
523
854
|
{''.join(reversed(message_items))}
|
524
855
|
</div>
|
525
856
|
"""
|
@@ -528,25 +859,25 @@ class HTMLTableJobLogger(JobLogger):
|
|
528
859
|
|
529
860
|
header_status_text = status_text
|
530
861
|
if (
|
531
|
-
|
532
|
-
and self.jobs_info.completed_interviews is not None
|
862
|
+
self.jobs_info.completed_interviews is not None
|
533
863
|
and self.jobs_info.failed_interviews is not None
|
534
864
|
):
|
535
865
|
header_status_text += f" ({self.jobs_info.completed_interviews:,} completed, {self.jobs_info.failed_interviews:,} failed)"
|
536
866
|
|
867
|
+
# Add model costs table before exceptions table
|
868
|
+
main_content = f"""
|
869
|
+
{content_html}
|
870
|
+
{status_banner}
|
871
|
+
{message_log}
|
872
|
+
{self._build_model_costs_table()}
|
873
|
+
{self._build_exceptions_table()}
|
874
|
+
"""
|
875
|
+
|
876
|
+
# Return the complete HTML
|
537
877
|
return f"""
|
538
878
|
{css}
|
539
879
|
<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
|
-
}}">
|
880
|
+
<div class="jobs-header" onclick="{self._collapse(f'content-{self.log_id}', f'arrow-{self.log_id}')}">
|
550
881
|
<div>
|
551
882
|
<span id="arrow-{self.log_id}" class="expand-toggle">{'⌃' if self.is_expanded else '⌄'}</span>
|
552
883
|
Job Status 🦜
|
@@ -554,9 +885,7 @@ class HTMLTableJobLogger(JobLogger):
|
|
554
885
|
<div class="{status_class}">{header_status_text}</div>
|
555
886
|
</div>
|
556
887
|
<div id="content-{self.log_id}" class="jobs-content" style="display: {display_style};">
|
557
|
-
{
|
558
|
-
{status_banner}
|
559
|
-
{message_log}
|
888
|
+
{main_content}
|
560
889
|
</div>
|
561
890
|
</div>
|
562
891
|
"""
|