tuieval 0.2.0.dev33__tar.gz → 0.2.0.dev35__tar.gz

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 (74) hide show
  1. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/PKG-INFO +1 -1
  2. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/_version.py +2 -2
  3. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/tui.py +24 -8
  4. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/tests/test_tuieval.py +58 -0
  5. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/.gitignore +0 -0
  6. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/CHANGELOG.md +0 -0
  7. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/LICENSE +0 -0
  8. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/README.md +0 -0
  9. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/RELEASING.md +0 -0
  10. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/brand/README.md +0 -0
  11. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/brand/favicon.ico +0 -0
  12. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/brand/readme-header.png +0 -0
  13. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/brand/social-preview.png +0 -0
  14. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-1024.png +0 -0
  15. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-128.png +0 -0
  16. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-16.png +0 -0
  17. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-16.svg +0 -0
  18. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-256.png +0 -0
  19. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-32.png +0 -0
  20. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-48.png +0 -0
  21. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-512.png +0 -0
  22. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-64.png +0 -0
  23. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-animated.svg +0 -0
  24. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon.svg +0 -0
  25. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/images/tui-setup.png +0 -0
  26. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/models.md +0 -0
  27. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/docs/writing-packs.md +0 -0
  28. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/pyproject.toml +0 -0
  29. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/__init__.py +0 -0
  30. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/__main__.py +0 -0
  31. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/builtin.toml +0 -0
  32. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/cli.py +0 -0
  33. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/client.py +0 -0
  34. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/compare.py +0 -0
  35. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/doctor.py +0 -0
  36. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/engine.py +0 -0
  37. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/export.py +0 -0
  38. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/graders/__init__.py +0 -0
  39. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/graders/answer.py +0 -0
  40. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/graders/code.py +0 -0
  41. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/graders/rag.py +0 -0
  42. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/graders/reply.py +0 -0
  43. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/graders/tool_call.py +0 -0
  44. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/machines.py +0 -0
  45. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/onboard.py +0 -0
  46. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/packs.py +0 -0
  47. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/profiles.py +0 -0
  48. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/remove.py +0 -0
  49. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/run_evals.py +0 -0
  50. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/scaffold.py +0 -0
  51. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/selftest.py +0 -0
  52. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/models.toml +0 -0
  53. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/answer/pack.toml +0 -0
  54. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/answer/system.txt +0 -0
  55. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/answer/tests.yaml +0 -0
  56. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/code/pack.toml +0 -0
  57. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/code/system.txt +0 -0
  58. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/code/tests.yaml +0 -0
  59. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/rag/pack.toml +0 -0
  60. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/rag/system.txt +0 -0
  61. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/rag/tests.yaml +0 -0
  62. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/reply/pack.toml +0 -0
  63. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/reply/system.txt +0 -0
  64. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/reply/tests.yaml +0 -0
  65. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/tool_call/pack.toml +0 -0
  66. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/tool_call/system.txt +0 -0
  67. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/tool_call/tests.yaml +0 -0
  68. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/tool_call/tools.yaml +0 -0
  69. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/tune.py +0 -0
  70. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/verdict.py +0 -0
  71. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/watch_proxy.py +0 -0
  72. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/workspace.py +0 -0
  73. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/src/tuieval/yamlout.py +0 -0
  74. {tuieval-0.2.0.dev33 → tuieval-0.2.0.dev35}/tests/mock_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tuieval
3
- Version: 0.2.0.dev33
3
+ Version: 0.2.0.dev35
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
@@ -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.dev33'
22
- __version_tuple__ = version_tuple = (0, 2, 0, 'dev33')
21
+ __version__ = version = '0.2.0.dev35'
22
+ __version_tuple__ = version_tuple = (0, 2, 0, 'dev35')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -10,9 +10,10 @@ Run screen: live reasoning and answer, per-test ✓/✗, TTFT and tokens/s, p
10
10
  k skips the current model, c cancels everything, f pauses auto-scroll.
11
11
  r shows results at any time (also mid-run); n starts a new run when it ends.
12
12
  Enter on a Recent results row shows that answer in full.
13
- Results: ctrl+r reloads (it also reloads when a run's pack finishes). Per question compares
14
- each model's tokens and seconds on the same question. Enter on a Failures row or a
15
- Per question cell shows the answer: grading and checks, reasoning, answer, question.
13
+ Results: opens on Per question, which compares each model's tokens and seconds on the same
14
+ question. ctrl+r reloads (it also reloads when a run's pack finishes). Enter on a
15
+ Failures row or a Per question cell shows the answer: grading and checks, reasoning,
16
+ answer, question.
16
17
  Answer detail: [ and ] step to the previous/next answer; esc/q close.
17
18
  Anywhere: esc/q go back (q quits only on Setup), ctrl+q quits, w lists this session's runs and
18
19
  tunes. Leaving a live run or tune keeps it going; the header shows its progress.
@@ -180,7 +181,8 @@ def fmt_secs(secs):
180
181
 
181
182
 
182
183
  class StreamView(TextArea):
183
- """Read-only, word-wrapped text that grows as tokens stream in. Scroll it with the mouse."""
184
+ """Read-only, word-wrapped text that grows as tokens stream in. Scroll it with the mouse; it follows
185
+ new text only while scrolled to the bottom."""
184
186
 
185
187
  can_focus = False
186
188
 
@@ -188,14 +190,28 @@ class StreamView(TextArea):
188
190
 
189
191
  def __init__(self, **kw):
190
192
  super().__init__(read_only=True, soft_wrap=True, show_cursor=False, max_checkpoints=1, **kw)
193
+ self.trim_pending = False
191
194
 
192
195
  def append(self, text, follow=True):
196
+ # Follow only when already at the bottom: scrolled up to read, the view stays put.
197
+ follow = follow and self.scroll_y >= self.max_scroll_y - 1
193
198
  self.insert(text, self.document.end)
194
199
  if len(self.text) > self.MAX_CHARS:
195
- self.load_text("…" + self.text[-self.MAX_CHARS // 2:])
200
+ if follow:
201
+ self.trim()
202
+ elif not self.trim_pending: # once the new text is laid out, so the place is measured right
203
+ self.trim_pending = True
204
+ self.call_after_refresh(self.trim)
196
205
  if follow:
197
206
  self.scroll_end(animate=False)
198
207
 
208
+ def trim(self):
209
+ """Drop the oldest text, keeping the view the same distance from the end (the text that's left)."""
210
+ self.trim_pending = False
211
+ from_end = self.max_scroll_y - self.scroll_y
212
+ self.load_text("…" + self.text[-self.MAX_CHARS // 2:])
213
+ self.call_after_refresh(lambda: self.scroll_to(y=max(0, self.max_scroll_y - from_end), animate=False))
214
+
199
215
 
200
216
  # ------------------------------------------------------------------ answer detail
201
217
  def answer_entry(row):
@@ -315,8 +331,8 @@ with the model's reasoning and answer as they stream, then recent results with t
315
331
  v with several models at a time: stream the next one (k skips the one streaming)""",
316
332
  "ResultsScreen": """[b]Results[/b]
317
333
 
318
- Start with [b]Production readiness[/b]: one verdict per model and use case, and what's missing when
319
- it's INCONCLUSIVE. The other tabs are the evidence:
334
+ Opens on [b]Per question[/b]. [b]Production readiness[/b] gives one verdict per model and use case,
335
+ and what's missing when it's INCONCLUSIVE. The other tabs are the evidence:
320
336
  Scorecard accuracy, critical failures and truncation per model and pack
321
337
  Speed & tokens time and tokens per answer (★ = nothing beats it on both accuracy and time)
322
338
  Per question every question, model by model
@@ -2324,7 +2340,7 @@ class ResultsScreen(Screen):
2324
2340
  yield Static(id="results-info")
2325
2341
  if self.smoke_dir:
2326
2342
  yield Button("Show this Smoke run's results", id="toggle-smoke")
2327
- with TabbedContent():
2343
+ with TabbedContent(initial="tab-per-question"):
2328
2344
  with TabPane("Production readiness"):
2329
2345
  yield Static(id="readiness-short")
2330
2346
  yield Static(self.READINESS_HELP, id="readiness-help")
@@ -733,6 +733,64 @@ class FirstRun(unittest.TestCase):
733
733
  env=dict(os.environ, TUIEVAL_HOME=self.ws, TUIEVAL_DETECT_PORTS=""), timeout=60).stdout.strip()
734
734
  self.assertEqual(out, "['first'] ['my-model'] smoke False") # tuning shows once something has run
735
735
 
736
+ def test_results_open_on_per_question(self):
737
+ tuieval(self.ws, "add", "my-model", "--server", "local")
738
+ code = textwrap.dedent("""
739
+ import asyncio
740
+ from tuieval.tui import EvalsApp
741
+ from textual.widgets import TabbedContent
742
+ async def go():
743
+ app = EvalsApp({})
744
+ async with app.run_test(size=(140, 40)) as pilot:
745
+ await pilot.pause()
746
+ await pilot.press("r")
747
+ await pilot.pause()
748
+ print(type(app.screen).__name__, app.screen.query_one(TabbedContent).active)
749
+ asyncio.run(go())
750
+ """)
751
+ out = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True,
752
+ env=dict(os.environ, TUIEVAL_HOME=self.ws, TUIEVAL_DETECT_PORTS=""), timeout=60).stdout.strip()
753
+ self.assertEqual(out, "ResultsScreen tab-per-question")
754
+
755
+ def test_stream_follows_only_at_the_bottom(self):
756
+ code = textwrap.dedent("""
757
+ import asyncio
758
+ from textual.app import App
759
+ from tuieval.tui import StreamView
760
+ class A(App):
761
+ def compose(self):
762
+ yield StreamView()
763
+ async def go():
764
+ app = A()
765
+ async with app.run_test(size=(60, 12)) as pilot:
766
+ s = app.query_one(StreamView)
767
+ for i in range(100):
768
+ s.append(f"line {i}\\n")
769
+ await pilot.pause()
770
+ s.scroll_to(y=20, animate=False)
771
+ await pilot.pause()
772
+ for i in range(100):
773
+ s.append("more\\n")
774
+ await pilot.pause()
775
+ held = s.scroll_y
776
+ s.scroll_end(animate=False)
777
+ await pilot.pause()
778
+ s.append("last\\n")
779
+ await pilot.pause()
780
+ following = s.scroll_y == s.max_scroll_y
781
+ s.MAX_CHARS = len(s.text) + 100 # the next text trims the oldest
782
+ s.scroll_to(y=s.max_scroll_y - 30, animate=False)
783
+ await pilot.pause()
784
+ top = s.document.get_line(s.scroll_y)
785
+ s.append("x\\n" * 60)
786
+ await pilot.pause()
787
+ await pilot.pause()
788
+ print(held, following, s.text.startswith("…"), s.document.get_line(s.scroll_y) == top)
789
+ asyncio.run(go())
790
+ """)
791
+ out = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True, timeout=60).stdout.strip()
792
+ self.assertEqual(out, "20 True True True") # scrolled up stays put, also through a trim; bottom follows
793
+
736
794
  def test_doctor(self):
737
795
  tuieval(self.ws, "add", "mock", "--server", "local", "--label", "wanted")
738
796
  p = tuieval(self.ws, "doctor", check=False, ports=self.port)
File without changes
File without changes
File without changes