tuieval 0.2.0.dev81__py3-none-any.whl → 0.2.0.dev87__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.
tuieval/_version.py CHANGED
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.0.dev81'
22
- __version_tuple__ = version_tuple = (0, 2, 0, 'dev81')
21
+ __version__ = version = '0.2.0.dev87'
22
+ __version_tuple__ = version_tuple = (0, 2, 0, 'dev87')
23
23
 
24
24
  __commit_id__ = commit_id = None
tuieval/tui.py CHANGED
@@ -1550,6 +1550,13 @@ def run_title(tier, models, packs):
1550
1550
  f"{', '.join(packs) if len(packs) <= 2 else f'{len(packs)} packs'}")
1551
1551
 
1552
1552
 
1553
+ def short_keys(keys, n=4):
1554
+ """A few "model/pack" pairs as "model · pack", the rest counted."""
1555
+ keys = sorted(keys)
1556
+ shown = ", ".join(k.replace("/", " · ", 1) for k in keys[:n])
1557
+ return rich_escape(shown + (f" and {len(keys) - n} more" if len(keys) > n else ""))
1558
+
1559
+
1553
1560
  class QueuedRun:
1554
1561
  """A run waiting its turn. It's planned again when it starts, so it picks up what the runs before
1555
1562
  it finished; it never sets aside answers that weren't confirmed when it was queued. retest: the
@@ -1557,13 +1564,29 @@ class QueuedRun:
1557
1564
  kind = "Run"
1558
1565
 
1559
1566
  def __init__(self, sel, retest, leave, accepted, eng):
1560
- self.sel, self.retest, self.leave, self.accepted = sel, retest, leave, accepted
1567
+ self.sel, self.retest, self.leave, self.accepted = sel, retest, set(leave), accepted
1561
1568
  self.queued = time.time()
1569
+ self.replan(eng)
1570
+
1571
+ def replan(self, eng):
1562
1572
  self.jobs, self.problem = self.plan(eng)
1563
1573
  jobs = self.jobs or []
1564
- self.title = run_title(sel["tier"], {j.label for j in jobs}, {j.pack.label for j in jobs})
1574
+ self.title = run_title(self.sel["tier"], {j.label for j in jobs}, {j.pack.label for j in jobs})
1565
1575
  self.est = eng.estimate_seconds(jobs)[0]
1566
1576
 
1577
+ def keys(self):
1578
+ """The "model/pack" pairs it covers."""
1579
+ return {f"{m}/{p}" for m in self.sel["models"] for p in self.sel["packs"]} - self.leave
1580
+
1581
+ def deliberate(self):
1582
+ """Retest or picked questions: asked again on purpose, never treated as a duplicate."""
1583
+ return bool(self.sel.get("force") or self.sel.get("tests"))
1584
+
1585
+ def drop(self, keys, eng):
1586
+ """Leave these pairs out (they're already running or queued)."""
1587
+ self.leave |= set(keys)
1588
+ self.replan(eng)
1589
+
1567
1590
  def tests(self, eng):
1568
1591
  """The picked tests that are still in their packs (a pack edited while queued drops the rest)."""
1569
1592
  out = {}
@@ -2183,6 +2206,7 @@ class RunScreen(Screen):
2183
2206
  yield Footer()
2184
2207
 
2185
2208
  def on_mount(self):
2209
+ self._queue_follow, self._queue_y = True, None # follow_queue
2186
2210
  q = self.query_one("#queue", DataTable)
2187
2211
  for label, key in (("Model", "model"), ("Eval", "suite"), ("Status", "status"), ("Progress", "progress"),
2188
2212
  ("✓", "pass"), ("✗", "fail"), ("Time", "time"), ("Note", "note")):
@@ -2500,6 +2524,25 @@ class RunScreen(Screen):
2500
2524
  self.update_job(j)
2501
2525
  self.update_overall()
2502
2526
  self.refresh_current()
2527
+ self.follow_queue()
2528
+
2529
+ def follow_queue(self):
2530
+ """Keep the running model's row in view in the queue, unless you scrolled the queue yourself;
2531
+ scrolling back to where the running row shows picks the following up again."""
2532
+ q = self.query_one("#queue", DataTable)
2533
+ keys = [r.key.value for r in q.ordered_rows]
2534
+ now = next((i for i, k in enumerate(keys)
2535
+ if any(j.key == k and j.status in ("loading", "running") for j in self.jobs)), None)
2536
+ if now is None:
2537
+ return
2538
+ if self._queue_y is not None and q.scroll_y != self._queue_y: # you scrolled since we last did
2539
+ seen = q.scroll_y <= now < q.scroll_y + max(1, q.scrollable_content_region.height - 1)
2540
+ self._queue_follow = seen
2541
+ if self._queue_follow:
2542
+ q.scroll_to(y=max(0, now - 1), animate=False)
2543
+ self._queue_y = max(0, min(now - 1, q.max_scroll_y))
2544
+ else:
2545
+ self._queue_y = q.scroll_y
2503
2546
 
2504
2547
  # -- actions
2505
2548
  def action_skip_model(self):
@@ -3236,8 +3279,32 @@ class EvalsApp(App):
3236
3279
  what = s.live_text() if s else "The last run is still stopping its server"
3237
3280
  return what + (f" (+{len(self.queue)} queued)" if self.queue else "")
3238
3281
 
3282
+ def taken(self, tier):
3283
+ """The "model/pack" pairs of this tier already in the run going (not finished) or in the queue."""
3284
+ out = set()
3285
+ run = self.active_session
3286
+ if isinstance(run, RunScreen) and run.running and run.jobs and run.jobs[0].tier == tier:
3287
+ out |= {j.key for j in run.jobs if j.status in ("waiting", "loading", "running")}
3288
+ for q in self.queue:
3289
+ if q.kind == "Run" and q.sel["tier"] == tier:
3290
+ out |= q.keys()
3291
+ return out
3292
+
3239
3293
  # -- the queue: Start or t while something runs adds to it; each item starts when the one before ends
3240
3294
  def start_or_queue(self, item):
3295
+ skipped = set()
3296
+ if self.is_busy() and item.kind == "Run" and not item.deliberate():
3297
+ mine = item.keys()
3298
+ skipped = mine & self.taken(item.sel["tier"])
3299
+ if skipped == mine:
3300
+ self.notify(f"Already running or queued, so nothing was added: {short_keys(skipped)}.", timeout=10)
3301
+ return
3302
+ if skipped:
3303
+ item.drop(skipped, self.engine)
3304
+ if not item.jobs:
3305
+ self.notify(f"Nothing new to run: {item.problem}. Already running or queued: "
3306
+ f"{short_keys(skipped)}.", timeout=10)
3307
+ return
3241
3308
  if not self.is_busy():
3242
3309
  screen, problem = item.make(self.engine)
3243
3310
  if problem:
@@ -3256,11 +3323,11 @@ class EvalsApp(App):
3256
3323
  self.add_to_run(run, item)
3257
3324
  elif choice == "queue":
3258
3325
  self.enqueue(item)
3259
- what = f"{', '.join(item.sel['models'][:4])}{' …' if len(item.sel['models']) > 4 else ''} · " \
3260
- f"{', '.join(item.sel['packs'][:4])}{' …' if len(item.sel['packs']) > 4 else ''}"
3326
+ what = short_keys(item.keys(), 6)
3327
+ skip = (f"\n\nLeft out, already running or queued: {short_keys(skipped)}." if skipped else "")
3261
3328
  self.push_screen(ChoiceScreen(
3262
3329
  f"A run is going: {run.live_text()}.\n\nAdd {what} to it? Its models run after the ones "
3263
- "already in it.\nOr queue it as a separate run that starts when this one ends.",
3330
+ f"already in it.\nOr queue it as a separate run that starts when this one ends.{skip}",
3264
3331
  [("add", "Add to this run", "success"), ("queue", f"Queue it as #{len(self.queue) + 1}", "primary"),
3265
3332
  ("keep", "Cancel", "default")]), picked)
3266
3333
  return
@@ -3270,6 +3337,9 @@ class EvalsApp(App):
3270
3337
  jobs, problem = item.plan(self.engine)
3271
3338
  added, why = self.engine.add_jobs(jobs) if jobs else ([], problem)
3272
3339
  if not added:
3340
+ if why and why.startswith("everything picked is already in the run"):
3341
+ self.notify(f"Not added: {why}.", timeout=10) # queueing it again would only duplicate it
3342
+ return
3273
3343
  self.notify(f"Not added: {why}. Queued it instead.", severity="warning", timeout=10)
3274
3344
  self.enqueue(item)
3275
3345
  return
@@ -3281,6 +3351,8 @@ class EvalsApp(App):
3281
3351
  def enqueue(self, item):
3282
3352
  ahead = self.busy_text()
3283
3353
  self.queue.append(item)
3354
+ if not self.active_session: # the last run already ended (its server may still be stopping):
3355
+ self.set_timer(0.3, self.start_next) # nothing else would start the queue
3284
3356
  self.notify(f"Queued #{len(self.queue)}: {item.title}. It starts by itself after: {ahead}. "
3285
3357
  "w shows the queue.", timeout=10)
3286
3358
  self.refresh_live()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tuieval
3
- Version: 0.2.0.dev81
3
+ Version: 0.2.0.dev87
4
4
  Summary: Evaluate local and frontier LLMs on your own questions: accuracy, speed, tokens and PASS/FAIL verdicts, in the terminal.
5
5
  Project-URL: Homepage, https://github.com/ashe-wb/tuieval
6
6
  Project-URL: Issues, https://github.com/ashe-wb/tuieval/issues
@@ -1,6 +1,6 @@
1
1
  tuieval/__init__.py,sha256=myVXVX7htbwezW6k3x_FuKgn1RSZ2WcTaDEMByGn6Wg,312
2
2
  tuieval/__main__.py,sha256=E6Gls0DNz8GQK2K-kOUIx8cYhgANW_CH54VKrfCfs14,52
3
- tuieval/_version.py,sha256=wvLQcBRh9Ke_k0afcu8VifHptYL1UWBFOCSFcnpeLi0,535
3
+ tuieval/_version.py,sha256=MbDAskHZqL7Ly_g4fmQSqzeTJS3q2IGRhFiHFeJp5zA,535
4
4
  tuieval/builtin.toml,sha256=Kxw22RZCJhURUyu5Wz8tj5Y1nM85A8hFVRbA4ocoPUg,4293
5
5
  tuieval/cli.py,sha256=VwiaU1F9bZtU8C-gEowJq58kPeYQY4t6SY_HwrV-qgc,4702
6
6
  tuieval/client.py,sha256=kpsyLEZKfxCsvIGBHBbBy83TpI97CvnGCDq-4AbJS4Y,9986
@@ -18,7 +18,7 @@ tuieval/remove.py,sha256=Vwh43nm9lVb9U9aN-h6CMYbkTwdR3UWfZObwweF72n0,5157
18
18
  tuieval/run_evals.py,sha256=FU3bvu3jt9ZWg3YLsXNINbdeClXMioyc4zQ4yEhmBQs,47855
19
19
  tuieval/scaffold.py,sha256=YBOqniJpf_GtKKAFF3_OHxxMGbmTWdFjcClGcOnrrKE,12130
20
20
  tuieval/selftest.py,sha256=nExkrOidgymlmvkxImEBfrQG5iTUN5WuRMIKtqusCCs,9835
21
- tuieval/tui.py,sha256=bGAy0cP55rWkg9Cj3iAsSqR3tNM67Cj3G3nfvCitN8A,173351
21
+ tuieval/tui.py,sha256=ZE9FEOr84Vx2YULSiBpaZM4DyOo0iIkuKlH870K88I8,176810
22
22
  tuieval/tune.py,sha256=azxxthgJeag8JyoQUgaRxq6oI1XvEgzb9vkszjJZH7w,39645
23
23
  tuieval/verdict.py,sha256=3SM8qrVdWNIFDENagaR5ZPBYEnjsBtkYB9WS5TDVssU,20517
24
24
  tuieval/watch_proxy.py,sha256=bnDO1i6n6yUs1hODoUWcMppJMGPavoQqs6aNYrIxpT8,11416
@@ -47,8 +47,8 @@ tuieval/templates/packs/tool_call/pack.toml,sha256=n4bx0lGJHSmW41uVqhfIXJ2BWZpmT
47
47
  tuieval/templates/packs/tool_call/system.txt,sha256=mKxu-lB-0YjLHXdgeFJa8x2C180OvcNz5cHv2N5Bg3U,254
48
48
  tuieval/templates/packs/tool_call/tests.yaml,sha256=Nv7c4syrzJ9cU8EBKSK_1ZSFx9V02aCjW4-2TBCPT7s,1859
49
49
  tuieval/templates/packs/tool_call/tools.yaml,sha256=HgxCkh0On2VwbHSsjAGM8tNCK7ZtKLGrs7lMCn3Fprs,1048
50
- tuieval-0.2.0.dev81.dist-info/METADATA,sha256=362fng3M9sOKj7AGpBf-ebNRTQgTjW84h1nfrn3YeKg,14580
51
- tuieval-0.2.0.dev81.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
52
- tuieval-0.2.0.dev81.dist-info/entry_points.txt,sha256=7eIhi1GpFxBP2OLJV02Nn8LwsNuO512eKOGt1IBbj04,45
53
- tuieval-0.2.0.dev81.dist-info/licenses/LICENSE,sha256=7wfRgGtBGH1aXhZkIn47oUznqKC18kEPMvUVgol7Ffc,1077
54
- tuieval-0.2.0.dev81.dist-info/RECORD,,
50
+ tuieval-0.2.0.dev87.dist-info/METADATA,sha256=XHHaef6pqjG5DaNsjztrZJ9K_ZPhGi63ONwA_J8XFqg,14580
51
+ tuieval-0.2.0.dev87.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
52
+ tuieval-0.2.0.dev87.dist-info/entry_points.txt,sha256=7eIhi1GpFxBP2OLJV02Nn8LwsNuO512eKOGt1IBbj04,45
53
+ tuieval-0.2.0.dev87.dist-info/licenses/LICENSE,sha256=7wfRgGtBGH1aXhZkIn47oUznqKC18kEPMvUVgol7Ffc,1077
54
+ tuieval-0.2.0.dev87.dist-info/RECORD,,