question styling, fix Kimi

This commit is contained in:
Will McGugan 2026-01-14 11:39:51 +00:00
parent ad6e47fbcd
commit b993e686ef
8 changed files with 55 additions and 26 deletions

5
.gitignore vendored
View file

@ -14,9 +14,8 @@ __pycache__
.zed
.git
.mypy_cache
.github
toad.log
log.txt
*.github
*.log
uv.lock
agent.jsonl

View file

@ -1,5 +1,6 @@
import asyncio
from contextlib import suppress
from datetime import datetime
import json
import os
@ -103,6 +104,8 @@ class Agent(AgentBase):
log_filename: str = generate_datetime_filename(f"{agent['name']}", ".txt")
if log_path := os.environ.get("TOAD_LOG"):
self._log_file_path = Path(log_path).resolve().absolute()
with suppress(OSError):
self._log_file_path.unlink(missing_ok=True)
else:
self._log_file_path = paths.get_log() / log_filename
@ -142,7 +145,7 @@ class Agent(AgentBase):
"""Write log in a thread."""
try:
with log_file_path.open("at") as log_file:
log_file.write(line)
log_file.write(f"{line.rstrip()}\n")
except OSError:
pass
@ -556,8 +559,12 @@ class Agent(AgentBase):
await self.acp_new_session()
except jsonrpc.APIError as error:
if isinstance(error.data, dict):
reason = str(error.data.get("reason") or "")
details = str(error.data.get("details") or "")
reason = str(
error.data.get("reason") or "Failed to initialize agent"
)
details = str(
error.data.get("details") or error.data.get("error") or ""
)
else:
reason = "Failed to initialize agent"
details = ""

View file

@ -13,7 +13,7 @@ publisher_url = "https://willmcgugan.github.io/"
type = "coding"
description = "Kimi CLI is a new CLI agent that can help you with your software development tasks and terminal operations."
tags = []
run_command."*" = "kimi --acp"
run_command."*" = "kimi acp"
help = '''
# Kimi CLI

View file

@ -90,7 +90,7 @@ Checkbox {
}
MarkdownNote {
Note,MarkdownNote {
padding: 0 1 0 0;
&.about MarkdownTable {
# Make the tables compact in /about
@ -399,6 +399,17 @@ Welcome {
}
ACPToolCallContent {
Markdown {
margin: 0;
padding-left: 0;
MarkdownBlock:last-of-type {
margin-bottom: 0;
}
}
}
Prompt {
padding: 0 0 0 0;
height: auto;
@ -481,10 +492,12 @@ Prompt {
}
}
& > * {
# height: 0;
margin: 1 0 0 0;
}
}
&:empty {
display: none;
height: 1;
}
}
#option-container {

View file

@ -21,8 +21,14 @@ class ACPToolCallContent(containers.VerticalGroup):
def compose(self) -> ComposeResult:
for content in self._content:
match content:
case {"type": "content", "content": {"text": text}}:
yield widgets.Label(text)
case {
"type": "content",
"content": {
"type": "text",
"text": text,
},
}:
yield widgets.Markdown(text)
case {
"type": "diff",
"oldText": old_text,

View file

@ -723,7 +723,7 @@ class Conversation(containers.Vertical):
if message.message:
error = Content.assemble(
Content.from_markup(message.message).stylize("$text-error"),
" - ",
" ",
Content.from_markup(message.details.strip()).stylize("dim"),
)
else:
@ -1138,10 +1138,13 @@ class Conversation(containers.Vertical):
kind = tool_call_update.get("kind", None)
title = tool_call_update.get("title", "") or ""
print("request_permission")
from textual import log
print(tool_call_update)
contents = tool_call_update.get("content", []) or []
# If all the content is diffs, we will set kind to "edit" to show the permisisons screen
for content in contents:
if content.get("type") != "diff":
break
else:
kind = "edit"
if kind == "edit":
diffs: list[tuple[str, str, str | None, str]] = []

View file

@ -285,11 +285,10 @@ class DiffView(containers.VerticalGroup):
text_lines_a = self.code_before.splitlines()
text_lines_b = self.code_after.splitlines()
sequence_matcher = difflib.SequenceMatcher(
# lambda character: character in " \t",
None,
lambda character: character in " \t",
text_lines_a,
text_lines_b,
# autojunk=True,
autojunk=True,
)
self._grouped_opcodes = list(sequence_matcher.get_grouped_opcodes())
@ -339,6 +338,7 @@ class DiffView(containers.VerticalGroup):
)
code_a_spans: list[Span] = []
code_b_spans: list[Span] = []
for tag, i1, i2, j1, j2 in sequence_matcher.get_opcodes():
if (
tag

View file

@ -1,7 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Callable, Iterable
from typing import Any, Callable
from textual.app import ComposeResult
from textual import events, on
@ -11,7 +11,8 @@ from textual.content import Content
from textual.reactive import var, reactive
from textual.message import Message
from textual.widget import Widget
from textual.widgets import Label
from textual import widgets
from toad.answer import Answer
@ -28,7 +29,7 @@ class Ask:
callback: Callable[[Answer], Any] | None = None
class NonSelectableLabel(Label):
class NonSelectableLabel(widgets.Label):
ALLOW_SELECT = False
@ -244,10 +245,10 @@ class Question(containers.VerticalGroup, can_focus=True):
def compose(self) -> ComposeResult:
if self.title:
yield Label(self.title, id="title", markup=False)
yield widgets.Label(self.title, id="title", markup=False)
if self._get_content is not None:
with containers.VerticalGroup(id="contents"):
with containers.VerticalGroup(id="contents"):
if self._get_content is not None:
yield self._get_content()
with containers.VerticalGroup(id="option-container"):