From 3710a5b93b703f038e936268345493e3385228a2 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 24 Jul 2025 10:30:16 -0500 Subject: [PATCH 1/2] Use new textual markdown streaming --- requirements.txt | 2 +- src/chap/commands/tui.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index 052d816..42e058a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,6 @@ lorem-text platformdirs pyperclip simple_parsing -textual[syntax] +textual[syntax] >= 4 tiktoken websockets diff --git a/src/chap/commands/tui.py b/src/chap/commands/tui.py index c094667..acd2400 100644 --- a/src/chap/commands/tui.py +++ b/src/chap/commands/tui.py @@ -145,7 +145,6 @@ class Tui(App[None]): await self.container.mount_all( [markdown_for_step(User(query)), output], before="#pad" ) - tokens: list[str] = [] update: asyncio.Queue[bool] = asyncio.Queue(1) for markdown in self.container.children: @@ -166,15 +165,22 @@ class Tui(App[None]): ) async def render_fun() -> None: + old_len = 0 while await update.get(): - if tokens: - output.update("".join(tokens).strip()) - self.container.scroll_end() - await asyncio.sleep(0.1) + content = message.content + new_len = len(content) + new_content = content[old_len:new_len] + if new_content: + if old_len: + await output.append(new_content) + else: + output.update(content) + self.container.scroll_end() + old_len = new_len + await asyncio.sleep(0.01) async def get_token_fun() -> None: async for token in self.api.aask(session, query): - tokens.append(token) message.content += token try: update.put_nowait(True) From 077113632bf25d731bc657d98f380b4ad75d13dd Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 24 Jul 2025 10:30:56 -0500 Subject: [PATCH 2/2] core: List available presets in --help --- src/chap/core.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/chap/core.py b/src/chap/core.py index 7a4ee2b..ae3e539 100644 --- a/src/chap/core.py +++ b/src/chap/core.py @@ -44,6 +44,7 @@ else: conversations_path = platformdirs.user_state_path("chap") / "conversations" conversations_path.mkdir(parents=True, exist_ok=True) configuration_path = platformdirs.user_config_path("chap") +preset_path = configuration_path / "preset" class ABackend(Protocol): @@ -363,7 +364,7 @@ def expand_splats(args: list[str]) -> list[str]: result.append(a) continue if a.startswith("@:"): - fn: pathlib.Path = configuration_path / "preset" / a[2:] + fn: pathlib.Path = preset_path / a[2:] else: fn = pathlib.Path(a[1:]) fn = maybe_add_txt_extension(fn) @@ -403,6 +404,19 @@ class MyCLI(click.Group): except ModuleNotFoundError as exc: raise click.UsageError(f"Invalid subcommand {cmd_name!r}", ctx) from exc + def gather_preset_info(self) -> list[tuple[str, str]]: + result = [] + for p in preset_path.glob("*"): + if p.is_file(): + with p.open() as f: + first_line = f.readline() + if first_line.startswith("#"): + help_str = first_line[1:].strip() + else: + help_str = "(A comment on the first line would be shown here)" + result.append((f"@:{p.name}", help_str)) + return result + def format_splat_options( self, ctx: click.Context, formatter: click.HelpFormatter ) -> None: @@ -436,6 +450,13 @@ class MyCLI(click.Group): the extension may be omitted.""" ) ) + formatter.write_paragraph() + if preset_info := self.gather_preset_info(): + formatter.write_text("Presets found:") + formatter.write_paragraph() + formatter.indent() + formatter.write_dl(preset_info) + formatter.dedent() def format_options( self, ctx: click.Context, formatter: click.HelpFormatter