tuieval 0.2.0.dev34__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.dev34 → tuieval-0.2.0.dev35}/PKG-INFO +1 -1
  2. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/_version.py +2 -2
  3. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/tui.py +17 -2
  4. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/tests/test_tuieval.py +39 -0
  5. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/.gitignore +0 -0
  6. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/CHANGELOG.md +0 -0
  7. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/LICENSE +0 -0
  8. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/README.md +0 -0
  9. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/RELEASING.md +0 -0
  10. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/brand/README.md +0 -0
  11. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/brand/favicon.ico +0 -0
  12. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/brand/readme-header.png +0 -0
  13. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/brand/social-preview.png +0 -0
  14. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-1024.png +0 -0
  15. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-128.png +0 -0
  16. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-16.png +0 -0
  17. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-16.svg +0 -0
  18. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-256.png +0 -0
  19. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-32.png +0 -0
  20. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-48.png +0 -0
  21. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-512.png +0 -0
  22. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-64.png +0 -0
  23. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon-animated.svg +0 -0
  24. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/brand/tuieval-icon.svg +0 -0
  25. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/images/tui-setup.png +0 -0
  26. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/models.md +0 -0
  27. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/docs/writing-packs.md +0 -0
  28. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/pyproject.toml +0 -0
  29. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/__init__.py +0 -0
  30. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/__main__.py +0 -0
  31. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/builtin.toml +0 -0
  32. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/cli.py +0 -0
  33. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/client.py +0 -0
  34. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/compare.py +0 -0
  35. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/doctor.py +0 -0
  36. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/engine.py +0 -0
  37. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/export.py +0 -0
  38. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/graders/__init__.py +0 -0
  39. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/graders/answer.py +0 -0
  40. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/graders/code.py +0 -0
  41. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/graders/rag.py +0 -0
  42. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/graders/reply.py +0 -0
  43. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/graders/tool_call.py +0 -0
  44. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/machines.py +0 -0
  45. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/onboard.py +0 -0
  46. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/packs.py +0 -0
  47. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/profiles.py +0 -0
  48. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/remove.py +0 -0
  49. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/run_evals.py +0 -0
  50. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/scaffold.py +0 -0
  51. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/selftest.py +0 -0
  52. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/models.toml +0 -0
  53. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/answer/pack.toml +0 -0
  54. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/answer/system.txt +0 -0
  55. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/answer/tests.yaml +0 -0
  56. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/code/pack.toml +0 -0
  57. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/code/system.txt +0 -0
  58. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/code/tests.yaml +0 -0
  59. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/rag/pack.toml +0 -0
  60. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/rag/system.txt +0 -0
  61. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/rag/tests.yaml +0 -0
  62. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/reply/pack.toml +0 -0
  63. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/reply/system.txt +0 -0
  64. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/reply/tests.yaml +0 -0
  65. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/tool_call/pack.toml +0 -0
  66. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/tool_call/system.txt +0 -0
  67. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/tool_call/tests.yaml +0 -0
  68. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/templates/packs/tool_call/tools.yaml +0 -0
  69. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/tune.py +0 -0
  70. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/verdict.py +0 -0
  71. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/watch_proxy.py +0 -0
  72. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/workspace.py +0 -0
  73. {tuieval-0.2.0.dev34 → tuieval-0.2.0.dev35}/src/tuieval/yamlout.py +0 -0
  74. {tuieval-0.2.0.dev34 → 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.dev34
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.dev34'
22
- __version_tuple__ = version_tuple = (0, 2, 0, 'dev34')
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
@@ -181,7 +181,8 @@ def fmt_secs(secs):
181
181
 
182
182
 
183
183
  class StreamView(TextArea):
184
- """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."""
185
186
 
186
187
  can_focus = False
187
188
 
@@ -189,14 +190,28 @@ class StreamView(TextArea):
189
190
 
190
191
  def __init__(self, **kw):
191
192
  super().__init__(read_only=True, soft_wrap=True, show_cursor=False, max_checkpoints=1, **kw)
193
+ self.trim_pending = False
192
194
 
193
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
194
198
  self.insert(text, self.document.end)
195
199
  if len(self.text) > self.MAX_CHARS:
196
- 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)
197
205
  if follow:
198
206
  self.scroll_end(animate=False)
199
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
+
200
215
 
201
216
  # ------------------------------------------------------------------ answer detail
202
217
  def answer_entry(row):
@@ -752,6 +752,45 @@ class FirstRun(unittest.TestCase):
752
752
  env=dict(os.environ, TUIEVAL_HOME=self.ws, TUIEVAL_DETECT_PORTS=""), timeout=60).stdout.strip()
753
753
  self.assertEqual(out, "ResultsScreen tab-per-question")
754
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
+
755
794
  def test_doctor(self):
756
795
  tuieval(self.ws, "add", "mock", "--server", "local", "--label", "wanted")
757
796
  p = tuieval(self.ws, "doctor", check=False, ports=self.port)
File without changes
File without changes
File without changes