benchkit-cli 0.1.0__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.
Files changed (149) hide show
  1. benchkit/__init__.py +1 -0
  2. benchkit/_pi_proxy.py +219 -0
  3. benchkit/answer_key_guard.ts +49 -0
  4. benchkit/artifacts.py +87 -0
  5. benchkit/benchmarks/__init__.py +105 -0
  6. benchkit/benchmarks/aider_polyglot.py +405 -0
  7. benchkit/benchmarks/arc.py +52 -0
  8. benchkit/benchmarks/banking77.py +149 -0
  9. benchkit/benchmarks/base.py +19 -0
  10. benchkit/benchmarks/boolq.py +49 -0
  11. benchkit/benchmarks/chartqa.py +133 -0
  12. benchkit/benchmarks/code_verifier.py +77 -0
  13. benchkit/benchmarks/downloads.py +166 -0
  14. benchkit/benchmarks/evalplus.py +288 -0
  15. benchkit/benchmarks/git_surgery.py +485 -0
  16. benchkit/benchmarks/gpqa.py +56 -0
  17. benchkit/benchmarks/gsm8k.py +97 -0
  18. benchkit/benchmarks/hellaswag.py +49 -0
  19. benchkit/benchmarks/humaneval.py +103 -0
  20. benchkit/benchmarks/ifeval.py +56 -0
  21. benchkit/benchmarks/ifeval_instructions.py +477 -0
  22. benchkit/benchmarks/mbpp.py +60 -0
  23. benchkit/benchmarks/mc_arena.py +479 -0
  24. benchkit/benchmarks/mcq.py +94 -0
  25. benchkit/benchmarks/medfailbench.py +537 -0
  26. benchkit/benchmarks/mmlu.py +54 -0
  27. benchkit/benchmarks/mmlu_pro.py +62 -0
  28. benchkit/benchmarks/mmvp.py +115 -0
  29. benchkit/benchmarks/ocrbench.py +96 -0
  30. benchkit/benchmarks/openbookqa.py +52 -0
  31. benchkit/benchmarks/patcheval.py +929 -0
  32. benchkit/benchmarks/piqa.py +52 -0
  33. benchkit/benchmarks/ruler.py +172 -0
  34. benchkit/benchmarks/ruler_generator.py +535 -0
  35. benchkit/benchmarks/sanity.py +174 -0
  36. benchkit/benchmarks/treejs_arena.py +268 -0
  37. benchkit/benchmarks/truthfulqa.py +52 -0
  38. benchkit/benchmarks/utils.py +25 -0
  39. benchkit/benchmarks/vision.py +230 -0
  40. benchkit/benchmarks/vstar.py +108 -0
  41. benchkit/benchmarks/winogrande.py +52 -0
  42. benchkit/benchmarks/xstest.py +116 -0
  43. benchkit/browser.py +429 -0
  44. benchkit/catalog.py +121 -0
  45. benchkit/cli.py +1000 -0
  46. benchkit/client.py +1413 -0
  47. benchkit/controls.py +87 -0
  48. benchkit/datasets/MMLU_LICENSE.txt +21 -0
  49. benchkit/datasets/README.md +142 -0
  50. benchkit/datasets/arc_challenge.jsonl +1172 -0
  51. benchkit/datasets/banking77.jsonl +3080 -0
  52. benchkit/datasets/banking77_intents.json +79 -0
  53. benchkit/datasets/boolq.jsonl +3270 -0
  54. benchkit/datasets/code_verifier.jsonl +500 -0
  55. benchkit/datasets/gpqa_diamond.jsonl +198 -0
  56. benchkit/datasets/gsm8k.jsonl +1319 -0
  57. benchkit/datasets/hellaswag.jsonl +1000 -0
  58. benchkit/datasets/humaneval.jsonl +164 -0
  59. benchkit/datasets/ifeval.jsonl +541 -0
  60. benchkit/datasets/mbpp.jsonl +500 -0
  61. benchkit/datasets/mc_blocks_1_20_1.jsonl +1003 -0
  62. benchkit/datasets/medfailbench_hard30.jsonl +30 -0
  63. benchkit/datasets/medfailbench_hard30.md +13 -0
  64. benchkit/datasets/openbookqa.jsonl +500 -0
  65. benchkit/datasets/piqa.jsonl +1838 -0
  66. benchkit/datasets/sanity_code.jsonl +6 -0
  67. benchkit/datasets/truthfulqa.jsonl +817 -0
  68. benchkit/datasets/winogrande.jsonl +1267 -0
  69. benchkit/datasets/xstest.csv +451 -0
  70. benchkit/decision.py +194 -0
  71. benchkit/demo.py +451 -0
  72. benchkit/dockerfiles.py +247 -0
  73. benchkit/engine.py +1270 -0
  74. benchkit/evaluation.py +195 -0
  75. benchkit/events.py +141 -0
  76. benchkit/executor.py +96 -0
  77. benchkit/git_surgery/backport-release-stack/setup.sh +328 -0
  78. benchkit/git_surgery/backport-release-stack/verify.sh +119 -0
  79. benchkit/git_surgery/bisect-the-regression/setup.sh +49 -0
  80. benchkit/git_surgery/bisect-the-regression/verify.sh +34 -0
  81. benchkit/git_surgery/rebase-conflict-chain/setup.sh +65 -0
  82. benchkit/git_surgery/rebase-conflict-chain/verify.sh +43 -0
  83. benchkit/git_surgery/recover-complex-stash/setup.sh +152 -0
  84. benchkit/git_surgery/recover-complex-stash/verify.sh +95 -0
  85. benchkit/git_surgery/recover-lost-work/setup.sh +40 -0
  86. benchkit/git_surgery/recover-lost-work/verify.sh +36 -0
  87. benchkit/git_surgery/repair-force-pushed-remote/setup.sh +171 -0
  88. benchkit/git_surgery/repair-force-pushed-remote/verify.sh +83 -0
  89. benchkit/git_surgery/revert-merge-with-followups/setup.sh +198 -0
  90. benchkit/git_surgery/revert-merge-with-followups/verify.sh +98 -0
  91. benchkit/git_surgery/secret-in-history/README.md +27 -0
  92. benchkit/git_surgery/secret-in-history/setup.sh +107 -0
  93. benchkit/git_surgery/secret-in-history/verify.sh +106 -0
  94. benchkit/git_surgery/split-the-mega-commit/setup.sh +50 -0
  95. benchkit/git_surgery/split-the-mega-commit/verify.sh +48 -0
  96. benchkit/git_surgery/untangle-nested-submodules/setup.sh +193 -0
  97. benchkit/git_surgery/untangle-nested-submodules/verify.sh +90 -0
  98. benchkit/history.py +474 -0
  99. benchkit/jobs.py +332 -0
  100. benchkit/leakage.py +51 -0
  101. benchkit/looping.py +873 -0
  102. benchkit/mc_render.py +274 -0
  103. benchkit/mc_viewer/dist/atlas.png +0 -0
  104. benchkit/mc_viewer/dist/blockStates.json.gz +0 -0
  105. benchkit/mc_viewer/dist/version.json +4 -0
  106. benchkit/mc_viewer/dist/viewer.html +23 -0
  107. benchkit/mc_viewer/dist/viewer.js +2 -0
  108. benchkit/mc_viewer/dist/viewer.js.LICENSE.txt +25 -0
  109. benchkit/mc_viewer/dist/worker.js +2 -0
  110. benchkit/mc_viewer/dist/worker.js.LICENSE.txt +19 -0
  111. benchkit/metrics.py +100 -0
  112. benchkit/ollama.py +60 -0
  113. benchkit/perf.py +353 -0
  114. benchkit/perf_report.py +207 -0
  115. benchkit/perturbations.py +208 -0
  116. benchkit/pi_agent.py +476 -0
  117. benchkit/pi_package/package-lock.json +1827 -0
  118. benchkit/pi_package/package.json +8 -0
  119. benchkit/report.py +142 -0
  120. benchkit/report_markdown.py +645 -0
  121. benchkit/results.py +688 -0
  122. benchkit/retry.py +163 -0
  123. benchkit/runner.py +769 -0
  124. benchkit/sandbox.py +1167 -0
  125. benchkit/template_check.py +291 -0
  126. benchkit/templates/arena.html +504 -0
  127. benchkit/templates/history.html +58 -0
  128. benchkit/templates/perf.html +509 -0
  129. benchkit/templates/report.html +769 -0
  130. benchkit/tui/__init__.py +5 -0
  131. benchkit/tui/app.py +87 -0
  132. benchkit/tui/app.tcss +512 -0
  133. benchkit/tui/formatting.py +84 -0
  134. benchkit/tui/screens/__init__.py +1 -0
  135. benchkit/tui/screens/connect.py +191 -0
  136. benchkit/tui/screens/detail.py +288 -0
  137. benchkit/tui/screens/help.py +84 -0
  138. benchkit/tui/screens/modals.py +544 -0
  139. benchkit/tui/screens/results.py +361 -0
  140. benchkit/tui/screens/run.py +988 -0
  141. benchkit/tui/screens/setup.py +733 -0
  142. benchkit/tui/theme.py +135 -0
  143. benchkit/tui/widgets.py +120 -0
  144. benchkit_cli-0.1.0.dist-info/METADATA +294 -0
  145. benchkit_cli-0.1.0.dist-info/RECORD +149 -0
  146. benchkit_cli-0.1.0.dist-info/WHEEL +5 -0
  147. benchkit_cli-0.1.0.dist-info/entry_points.txt +3 -0
  148. benchkit_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
  149. benchkit_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,5 @@
1
+ """BenchKit terminal user interface."""
2
+
3
+ from benchkit.tui.app import BenchKitApp, run_tui
4
+
5
+ __all__ = ["BenchKitApp", "run_tui"]
benchkit/tui/app.py ADDED
@@ -0,0 +1,87 @@
1
+ """The BenchKit terminal application."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from textual.app import App
8
+ from textual.binding import Binding
9
+
10
+ from benchkit.engine import JobSpec
11
+ from benchkit.tui.screens.connect import ConnectScreen
12
+ from benchkit.tui.screens.help import HelpScreen
13
+ from benchkit.tui.screens.results import ResultsScreen
14
+ from benchkit.tui.screens.run import RunScreen
15
+ from benchkit.tui.screens.setup import SetupScreen
16
+ from benchkit.tui.theme import (
17
+ ALTERNATE_THEME,
18
+ DEFAULT_THEME,
19
+ THEMES,
20
+ VARIABLE_DEFAULTS,
21
+ )
22
+
23
+
24
+ class BenchKitApp(App[None]):
25
+ """Full-screen benchmark cockpit: connect, configure, run, inspect."""
26
+
27
+ CSS_PATH = "app.tcss"
28
+ TITLE = "BenchKit"
29
+ SUB_TITLE = "benchmark your local LLMs"
30
+ ENABLE_COMMAND_PALETTE = True
31
+
32
+ BINDINGS = [
33
+ Binding("question_mark", "help", "Help"),
34
+ Binding("f1", "help", "Help", show=False),
35
+ Binding("ctrl+q", "quit", "Quit", priority=True, show=True),
36
+ Binding("f2", "toggle_theme", "Theme", show=False),
37
+ ]
38
+
39
+ def __init__(self, *, demo: bool = False, host: str | None = None) -> None:
40
+ super().__init__()
41
+ for theme in THEMES:
42
+ self.register_theme(theme)
43
+ self.theme = DEFAULT_THEME
44
+ self.demo = demo
45
+ self.host_override = host
46
+ self.client = None
47
+ self.models: list[dict] = []
48
+ self.last_output: Path | None = None
49
+
50
+ def get_theme_variable_defaults(self) -> dict[str, str]:
51
+ return dict(VARIABLE_DEFAULTS)
52
+
53
+ def on_mount(self) -> None:
54
+ self.push_screen(ConnectScreen())
55
+
56
+ # Navigation -------------------------------------------------------
57
+
58
+ def show_setup(self, client, models: list[dict]) -> None:
59
+ self.client = client
60
+ self.models = models
61
+ self.push_screen(SetupScreen())
62
+
63
+ def start_run(self, jobs: list[JobSpec], *, force_unload: bool = False) -> None:
64
+ self.push_screen(RunScreen(jobs, force_unload=force_unload))
65
+
66
+ def show_results(self, results: list[dict], output: Path | None) -> None:
67
+ self.last_output = output
68
+ self.switch_screen(ResultsScreen(results, output))
69
+
70
+ def back_to_setup(self) -> None:
71
+ """Return to the configuration screen from anywhere downstream."""
72
+ while len(self.screen_stack) > 2 and not isinstance(self.screen, SetupScreen):
73
+ self.pop_screen()
74
+
75
+ # Actions ----------------------------------------------------------
76
+
77
+ def action_help(self) -> None:
78
+ if not isinstance(self.screen, HelpScreen):
79
+ self.push_screen(HelpScreen())
80
+
81
+ def action_toggle_theme(self) -> None:
82
+ self.theme = ALTERNATE_THEME if self.theme == DEFAULT_THEME else DEFAULT_THEME
83
+ self.notify(f"Theme: {self.theme}", timeout=2)
84
+
85
+
86
+ def run_tui(*, demo: bool = False, host: str | None = None) -> None:
87
+ BenchKitApp(demo=demo, host=host).run()
benchkit/tui/app.tcss ADDED
@@ -0,0 +1,512 @@
1
+ /* BenchKit TUI stylesheet */
2
+
3
+ Screen {
4
+ background: $background;
5
+ }
6
+
7
+ Header {
8
+ background: $bk-raise;
9
+ }
10
+
11
+ Footer {
12
+ background: $bk-raise;
13
+ }
14
+
15
+ .pane {
16
+ background: $surface;
17
+ border: round $bk-border;
18
+ padding: 0 1;
19
+ height: 1fr;
20
+ }
21
+
22
+ .pane:focus-within {
23
+ border: round $primary;
24
+ }
25
+
26
+ .card-title {
27
+ text-style: bold;
28
+ padding: 0 0 1 0;
29
+ }
30
+
31
+ .hint {
32
+ color: $text-muted;
33
+ }
34
+
35
+ .field {
36
+ height: 1;
37
+ margin: 0 0 1 0;
38
+ }
39
+
40
+ .field-label {
41
+ width: 12;
42
+ padding: 0 1 0 0;
43
+ color: $text-muted;
44
+ }
45
+
46
+ .field-label.wide {
47
+ width: 22;
48
+ }
49
+
50
+ .filter {
51
+ border: none;
52
+ background: $bk-raise;
53
+ height: 1;
54
+ margin: 0 0 1 0;
55
+ padding: 0 1;
56
+ }
57
+
58
+ .filter:focus {
59
+ background: $primary 15%;
60
+ }
61
+
62
+ SelectionList {
63
+ background: $surface;
64
+ height: 1fr;
65
+ padding: 0;
66
+ }
67
+
68
+ DataTable {
69
+ height: 1fr;
70
+ background: $surface;
71
+ }
72
+
73
+ DataTable > .datatable--header {
74
+ background: $bk-raise;
75
+ color: $text-muted;
76
+ text-style: bold;
77
+ }
78
+
79
+ DataTable > .datatable--cursor {
80
+ background: $bk-selection;
81
+ color: $foreground;
82
+ text-style: bold;
83
+ }
84
+
85
+ /* ---------------------------------------------------------------- Connect */
86
+
87
+ #connect-root {
88
+ align: center middle;
89
+ height: 1fr;
90
+ padding: 1 2;
91
+ }
92
+
93
+ /* Every child is pinned to the card width so `align: center middle` has a
94
+ group narrower than the screen to centre. A full-width child (Static
95
+ defaults to one) would silently pin the group to the left edge. */
96
+ #banner {
97
+ width: 72;
98
+ max-width: 100%;
99
+ color: $primary;
100
+ text-style: bold;
101
+ margin: 0 0 1 0;
102
+ }
103
+
104
+ #tagline {
105
+ width: 72;
106
+ max-width: 100%;
107
+ color: $text-muted;
108
+ margin: 0 0 1 0;
109
+ }
110
+
111
+ #connect-card {
112
+ width: 72;
113
+ max-width: 100%;
114
+ height: auto;
115
+ background: $surface;
116
+ border: round $bk-border;
117
+ padding: 1 2;
118
+ }
119
+
120
+ #connect-card.busy {
121
+ border: round $primary;
122
+ }
123
+
124
+ #connect-form {
125
+ height: auto;
126
+ }
127
+
128
+ #connect-form Input,
129
+ #connect-form Select {
130
+ width: 1fr;
131
+ background: $bk-raise;
132
+ }
133
+
134
+ #connect-actions {
135
+ height: auto;
136
+ padding: 1 0 0 0;
137
+ }
138
+
139
+ #connect-actions Button {
140
+ margin: 0 2 0 0;
141
+ }
142
+
143
+ #connect-status {
144
+ height: auto;
145
+ padding: 1 0 0 0;
146
+ color: $text-muted;
147
+ }
148
+
149
+ #connect-status.working {
150
+ color: $accent;
151
+ }
152
+
153
+ #connect-status.ok {
154
+ color: $success;
155
+ }
156
+
157
+ #connect-status.error {
158
+ color: $error;
159
+ }
160
+
161
+ /* ------------------------------------------------------------------ Setup */
162
+
163
+ #setup-body {
164
+ height: 1fr;
165
+ padding: 1 1 0 1;
166
+ }
167
+
168
+ #model-pane {
169
+ width: 5fr;
170
+ margin: 0 1 0 0;
171
+ }
172
+
173
+ #bench-pane {
174
+ width: 7fr;
175
+ }
176
+
177
+ /* Sized to the rows it holds, so a filtered list does not leave a tall gap. */
178
+ #bench-list {
179
+ height: auto;
180
+ max-height: 1fr;
181
+ }
182
+
183
+ #setup-options {
184
+ height: 1;
185
+ padding: 0 2;
186
+ margin: 1 0 0 0;
187
+ }
188
+
189
+ #setup-options .field-label,
190
+ #setup-harness .field-label,
191
+ #setup-perturbation .field-label,
192
+ #setup-unload .field-label {
193
+ padding: 0 1 0 0;
194
+ }
195
+
196
+ .limit-input {
197
+ width: 14;
198
+ border: none;
199
+ background: $bk-raise;
200
+ padding: 0 1;
201
+ height: 1;
202
+ margin: 0 2 0 0;
203
+ }
204
+
205
+ #setup-options .hint {
206
+ text-wrap: nowrap;
207
+ text-overflow: ellipsis;
208
+ }
209
+
210
+ #setup-harness {
211
+ height: 3;
212
+ padding: 0 2;
213
+ }
214
+
215
+ #setup-harness Select {
216
+ width: 22;
217
+ height: 3;
218
+ margin: -1 2 0 0;
219
+ }
220
+
221
+ .repair-input {
222
+ width: 8;
223
+ height: 3;
224
+ margin: -1 2 0 0;
225
+ }
226
+
227
+ #setup-harness .hint {
228
+ padding: 0 0 0 0;
229
+ text-wrap: nowrap;
230
+ text-overflow: ellipsis;
231
+ }
232
+
233
+ #setup-perturbation {
234
+ height: 1;
235
+ padding: 0 2;
236
+ }
237
+
238
+ #choice-order {
239
+ width: 29;
240
+ height: 1;
241
+ }
242
+
243
+ #perturbation-seed-label {
244
+ width: 6;
245
+ padding: 0 1 0 0;
246
+ }
247
+
248
+ .seed-input {
249
+ width: 9;
250
+ height: 1;
251
+ border: none;
252
+ background: $bk-raise;
253
+ padding: 0 1;
254
+ margin: 0 2 0 0;
255
+ }
256
+
257
+ #setup-perturbation .hint {
258
+ text-wrap: nowrap;
259
+ text-overflow: ellipsis;
260
+ }
261
+
262
+ #setup-unload {
263
+ height: 1;
264
+ padding: 0 2;
265
+ }
266
+
267
+ #force-unload {
268
+ width: 32;
269
+ height: 1;
270
+ }
271
+
272
+ #setup-unload .hint {
273
+ text-wrap: nowrap;
274
+ text-overflow: ellipsis;
275
+ }
276
+
277
+ #setup-footer {
278
+ height: 3;
279
+ padding: 0 2;
280
+ align-vertical: middle;
281
+ background: $bk-raise;
282
+ }
283
+
284
+ #plan-summary {
285
+ width: 1fr;
286
+ padding: 1 0 0 0;
287
+ }
288
+
289
+ #template-check-dialog {
290
+ width: 92%;
291
+ height: 70%;
292
+ }
293
+
294
+ #template-check-table {
295
+ height: 1fr;
296
+ margin: 1 0;
297
+ }
298
+
299
+ #datasets-dialog {
300
+ width: 92%;
301
+ height: 80%;
302
+ }
303
+
304
+ #datasets-table {
305
+ height: 1fr;
306
+ margin: 1 0 0 0;
307
+ }
308
+
309
+ /* -------------------------------------------------------------------- Run */
310
+
311
+ #run-root {
312
+ height: 1fr;
313
+ padding: 1 1 0 1;
314
+ }
315
+
316
+ #run-head {
317
+ height: 1;
318
+ padding: 0 1;
319
+ }
320
+
321
+ #run-title {
322
+ width: 1fr;
323
+ }
324
+
325
+ .state {
326
+ width: auto;
327
+ padding: 0 1;
328
+ text-style: bold;
329
+ }
330
+
331
+ .state.running {
332
+ background: $success 20%;
333
+ color: $success;
334
+ }
335
+
336
+ .state.paused {
337
+ background: $warning 20%;
338
+ color: $warning;
339
+ }
340
+
341
+ .state.evaluating {
342
+ background: $warning 20%;
343
+ color: $warning;
344
+ }
345
+
346
+ .state.looping {
347
+ background: $error 20%;
348
+ color: $error;
349
+ }
350
+
351
+ .state.done {
352
+ background: $accent 20%;
353
+ color: $accent;
354
+ }
355
+
356
+ #progress-block {
357
+ height: auto;
358
+ padding: 1 1 0 1;
359
+ }
360
+
361
+ #progress-block Static {
362
+ color: $text-muted;
363
+ }
364
+
365
+ #progress-block ProgressBar {
366
+ width: 100%;
367
+ margin: 0 0 1 0;
368
+ }
369
+
370
+ #progress-block Bar {
371
+ width: 1fr;
372
+ }
373
+
374
+ ProgressBar > Bar > .bar--bar {
375
+ color: $primary;
376
+ }
377
+
378
+ ProgressBar > Bar > .bar--complete {
379
+ color: $success;
380
+ }
381
+
382
+ #stat-row {
383
+ height: 5;
384
+ padding: 0 1;
385
+ }
386
+
387
+ #run-body {
388
+ height: 1fr;
389
+ padding: 1 0 0 0;
390
+ }
391
+
392
+ #queue-pane {
393
+ width: 1fr;
394
+ margin: 0 1 0 0;
395
+ }
396
+
397
+ #task-pane {
398
+ width: 1fr;
399
+ }
400
+
401
+ /* --------------------------------------------------------------- Results */
402
+
403
+ #results-root, #detail-root {
404
+ height: 1fr;
405
+ padding: 1 1 0 1;
406
+ }
407
+
408
+ #results-pane, #detail-pane {
409
+ height: 1fr;
410
+ }
411
+
412
+ #output-path {
413
+ height: 1;
414
+ padding: 0 2;
415
+ }
416
+
417
+ #task-search {
418
+ border: none;
419
+ background: $bk-raise;
420
+ height: 1;
421
+ margin: 0 0 1 0;
422
+ padding: 0 1;
423
+ }
424
+
425
+ /* --------------------------------------------------------------- Modals */
426
+
427
+ LimitScreen, TaskDetailScreen, ConfirmScreen, HelpScreen {
428
+ align: center middle;
429
+ background: $background 70%;
430
+ }
431
+
432
+ .dialog {
433
+ background: $surface;
434
+ border: round $primary;
435
+ padding: 1 2;
436
+ }
437
+
438
+ #limit-dialog {
439
+ width: 64;
440
+ height: auto;
441
+ }
442
+
443
+ #limit-dialog Input {
444
+ margin: 1 0;
445
+ }
446
+
447
+ #confirm-dialog {
448
+ width: 56;
449
+ height: auto;
450
+ }
451
+
452
+ #task-dialog {
453
+ width: 90%;
454
+ height: 85%;
455
+ }
456
+
457
+ #task-dialog TabbedContent {
458
+ height: 1fr;
459
+ }
460
+
461
+ #task-dialog TextArea {
462
+ height: 1fr;
463
+ border: none;
464
+ }
465
+
466
+ #help-dialog {
467
+ width: 84;
468
+ height: 80%;
469
+ }
470
+
471
+ .dialog-actions {
472
+ height: auto;
473
+ padding: 1 0 0 0;
474
+ }
475
+
476
+ .dialog-actions Button {
477
+ margin: 0 2 0 0;
478
+ }
479
+
480
+ /* ------------------------------------------------ Short-terminal fallback */
481
+
482
+ .compact #banner {
483
+ display: none;
484
+ }
485
+
486
+ .compact #tagline {
487
+ display: none;
488
+ }
489
+
490
+ .compact #stat-row {
491
+ height: 3;
492
+ }
493
+
494
+ .compact StatCard {
495
+ height: 2;
496
+ }
497
+
498
+ .compact .stat-hint {
499
+ display: none;
500
+ }
501
+
502
+ .compact #progress-block {
503
+ padding: 0 1;
504
+ }
505
+
506
+ .compact #progress-block ProgressBar {
507
+ margin: 0;
508
+ }
509
+
510
+ .compact #run-body {
511
+ padding: 0;
512
+ }
@@ -0,0 +1,84 @@
1
+ """Small formatting helpers shared across TUI screens."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from benchkit.tui.theme import score_palette
6
+
7
+
8
+ def fmt_duration(seconds: float | None) -> str:
9
+ """Compact duration: ``1h 04m``, ``4m 12s``, ``42s`` or ``0.8s``."""
10
+ if seconds is None:
11
+ return "--"
12
+ if seconds < 0:
13
+ return "--"
14
+ if seconds < 10:
15
+ return f"{seconds:.1f}s"
16
+
17
+ total = round(seconds)
18
+ if total < 60:
19
+ return f"{total}s"
20
+ if total < 3600:
21
+ return f"{total // 60}m {total % 60:02d}s"
22
+ return f"{total // 3600}h {(total % 3600) // 60:02d}m"
23
+
24
+
25
+ def fmt_size(size: object) -> str:
26
+ """Render a model size in GB when the server reports one."""
27
+ if isinstance(size, (int, float)) and size > 0:
28
+ return f"{size / (1024**3):.1f} GB"
29
+ return ""
30
+
31
+
32
+ def fmt_count(value: int) -> str:
33
+ return f"{value:,}"
34
+
35
+
36
+ def throughput_stat(
37
+ *,
38
+ concurrency: int,
39
+ aggregate: float,
40
+ stream: float,
41
+ effective: float,
42
+ precision: int = 1,
43
+ ) -> tuple[str, str]:
44
+ """Format throughput, reserving aggregate metrics for parallel runs."""
45
+ if concurrency > 1:
46
+ return (
47
+ f"{aggregate:.{precision}f} tok/s",
48
+ f"aggregate · {stream:.{precision}f} stream · {effective:.2f}x effective",
49
+ )
50
+ return f"{stream:.{precision}f} tok/s", "single stream"
51
+
52
+
53
+ def score_class(score: float) -> str:
54
+ """CSS class name matching a score band."""
55
+ if score >= 80:
56
+ return "score-high"
57
+ if score >= 50:
58
+ return "score-mid"
59
+ return "score-low"
60
+
61
+
62
+ def score_color(score: float, dark: bool = True) -> str:
63
+ """Score colour from the active Dogi palette."""
64
+ palette = score_palette(dark)
65
+ if score >= 80:
66
+ return palette["high"]
67
+ if score >= 50:
68
+ return palette["mid"]
69
+ return palette["low"]
70
+
71
+
72
+ def result_color(passed: bool, error: bool = False, dark: bool = True) -> str:
73
+ """Colour for a single task outcome."""
74
+ palette = score_palette(dark)
75
+ if error:
76
+ return palette["mid"]
77
+ return palette["high"] if passed else palette["low"]
78
+
79
+
80
+ def bar(fraction: float, width: int = 12) -> str:
81
+ """A tiny inline bar for table cells."""
82
+ fraction = max(0.0, min(1.0, fraction))
83
+ filled = round(fraction * width)
84
+ return "█" * filled + "░" * (width - filled)
@@ -0,0 +1 @@
1
+ """Screens that make up the BenchKit TUI."""