kiwi-code 0.0.23__tar.gz → 0.0.24__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.
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/PKG-INFO +1 -1
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/pyproject.toml +1 -1
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_tui/screens/dashboard.py +72 -19
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/uv.lock +1 -1
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/.github/workflows/publish.yml +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/.github/workflows/test.yml +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/.gitignore +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/.python-version +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/CLAUDE.md +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/Makefile +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/README.md +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_cli/__init__.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_cli/auth.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_cli/cli.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_cli/client.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_cli/commands.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_cli/logger.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_cli/models.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_cli/runtime_manager.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_cli/server.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_runtime/__init__.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_runtime/__main__.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_runtime/main.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_runtime/snake_game/.gitignore +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_runtime/snake_game/requirements.txt +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_tui/__init__.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_tui/inline_file_picker.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_tui/main.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_tui/runtime_agent.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_tui/screens/__init__.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_tui/screens/attach_content.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_tui/screens/command_result.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_tui/screens/file_browser.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_tui/screens/id_picker.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_tui/screens/login.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_tui/screens/runtime_cleanup.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_tui/screens/runtime_logs.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_tui/screens/slash_picker.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/src/kiwi_tui/widgets.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/test_hello.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/tests/__init__.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/tests/conftest.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/tests/test_cli_help.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/tests/test_imports.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/tests/test_reexec_kiwi.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/tests/test_tokens.py +0 -0
- {kiwi_code-0.0.23 → kiwi_code-0.0.24}/tests/test_tui_headless.py +0 -0
|
@@ -6,7 +6,7 @@ from textual.app import ComposeResult
|
|
|
6
6
|
from textual.binding import Binding
|
|
7
7
|
from textual.screen import Screen
|
|
8
8
|
from textual.widgets import Header, Footer, Input, Static, Button, Markdown, LoadingIndicator
|
|
9
|
-
from textual.containers import Vertical, VerticalScroll, Horizontal
|
|
9
|
+
from textual.containers import Vertical, VerticalScroll, Horizontal, Center
|
|
10
10
|
from kiwi_tui.screens.file_browser import FileBrowserScreen
|
|
11
11
|
from kiwi_tui.screens.attach_content import AttachContentScreen
|
|
12
12
|
from kiwi_tui.screens.slash_picker import SlashPickerScreen
|
|
@@ -38,6 +38,28 @@ class UserMessageRow(Horizontal):
|
|
|
38
38
|
yield Static(self._text, classes="user-body", markup=False)
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
|
|
42
|
+
class AssistantMessageRow(Horizontal):
|
|
43
|
+
"""A single assistant message rendered with a left dot + markdown body."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, markdown_text: str, *, classes: str = "") -> None:
|
|
46
|
+
super().__init__(classes=classes)
|
|
47
|
+
self._markdown_text = markdown_text
|
|
48
|
+
|
|
49
|
+
def compose(self) -> ComposeResult:
|
|
50
|
+
# Solid dot marker on the left side of the model response.
|
|
51
|
+
yield Static("●", classes="assistant-dot", markup=False)
|
|
52
|
+
yield Markdown(self._markdown_text, classes="assistant-body")
|
|
53
|
+
|
|
54
|
+
def update_markdown(self, markdown_text: str) -> None:
|
|
55
|
+
"""Update the markdown content in-place."""
|
|
56
|
+
self._markdown_text = markdown_text
|
|
57
|
+
try:
|
|
58
|
+
self.query_one(".assistant-body", Markdown).update(markdown_text)
|
|
59
|
+
except Exception:
|
|
60
|
+
# Defensive: if the widget tree isn't ready or the child was removed.
|
|
61
|
+
pass
|
|
62
|
+
|
|
41
63
|
# Footer hint for inserting a newline differs by OS:
|
|
42
64
|
# - macOS terminals often don't forward modified Enter combos to terminal apps reliably, so we advertise Ctrl+N.
|
|
43
65
|
# - Windows terminals tend to support Shift+Enter for multi-line input.
|
|
@@ -95,7 +117,7 @@ class DashboardScreen(Screen):
|
|
|
95
117
|
|
|
96
118
|
#run-status-bar {
|
|
97
119
|
width: 100%;
|
|
98
|
-
height:
|
|
120
|
+
height: 5;
|
|
99
121
|
background: $background;
|
|
100
122
|
}
|
|
101
123
|
|
|
@@ -129,12 +151,14 @@ class DashboardScreen(Screen):
|
|
|
129
151
|
}
|
|
130
152
|
#copy-run-id {
|
|
131
153
|
width: auto;
|
|
132
|
-
height
|
|
154
|
+
/* Explicit height so the full border (top+bottom) is visible. */
|
|
155
|
+
height: 3;
|
|
133
156
|
padding: 0 1;
|
|
134
|
-
margin: 0
|
|
157
|
+
margin: 0;
|
|
135
158
|
}
|
|
136
159
|
|
|
137
160
|
|
|
161
|
+
|
|
138
162
|
#activity-bar {
|
|
139
163
|
width: 100%;
|
|
140
164
|
height: 1;
|
|
@@ -168,7 +192,8 @@ class DashboardScreen(Screen):
|
|
|
168
192
|
width: 100%;
|
|
169
193
|
height: auto;
|
|
170
194
|
padding: 0 1;
|
|
171
|
-
|
|
195
|
+
/* Add breathing room between all messages (user/assistant/info/error/etc.). */
|
|
196
|
+
margin: 1 0 0 0;
|
|
172
197
|
}
|
|
173
198
|
|
|
174
199
|
.user-message {
|
|
@@ -190,13 +215,25 @@ class DashboardScreen(Screen):
|
|
|
190
215
|
|
|
191
216
|
.assistant-message {
|
|
192
217
|
color: $text;
|
|
193
|
-
margin: 0;
|
|
194
218
|
padding: 0 1;
|
|
195
219
|
}
|
|
196
220
|
|
|
221
|
+
.assistant-dot {
|
|
222
|
+
width: 2;
|
|
223
|
+
padding: 0 1 0 0;
|
|
224
|
+
color: $brand-cyan;
|
|
225
|
+
text-style: bold;
|
|
226
|
+
content-align: center top;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.assistant-body {
|
|
230
|
+
width: 1fr;
|
|
231
|
+
}
|
|
232
|
+
|
|
197
233
|
.assistant-message Markdown {
|
|
198
234
|
margin: 0;
|
|
199
235
|
padding: 0;
|
|
236
|
+
width: 1fr;
|
|
200
237
|
}
|
|
201
238
|
|
|
202
239
|
.error-message {
|
|
@@ -372,8 +409,10 @@ class DashboardScreen(Screen):
|
|
|
372
409
|
with Vertical(id="status-action-col"):
|
|
373
410
|
yield Static("", id="status-action", markup=False)
|
|
374
411
|
with Vertical(id="status-run-col"):
|
|
375
|
-
|
|
376
|
-
|
|
412
|
+
with Center():
|
|
413
|
+
yield Static("", id="status-run", markup=False)
|
|
414
|
+
with Center():
|
|
415
|
+
yield Button("Copy", id="copy-run-id", variant="default")
|
|
377
416
|
|
|
378
417
|
with Vertical(id="input-bar"):
|
|
379
418
|
with Horizontal(id="activity-bar"):
|
|
@@ -1520,8 +1559,8 @@ class DashboardScreen(Screen):
|
|
|
1520
1559
|
messages = self.query_one("#messages", VerticalScroll)
|
|
1521
1560
|
css_class = f"message {msg_type}-message"
|
|
1522
1561
|
if msg_type == "assistant":
|
|
1523
|
-
|
|
1524
|
-
messages.mount(
|
|
1562
|
+
prepared = self._prepare_markdown(text)
|
|
1563
|
+
messages.mount(AssistantMessageRow(prepared, classes=css_class))
|
|
1525
1564
|
elif msg_type == "user":
|
|
1526
1565
|
# Render a styled "YOU" label without enabling markup (keeps input safe).
|
|
1527
1566
|
messages.mount(UserMessageRow(str(text), classes=css_class))
|
|
@@ -1942,7 +1981,11 @@ class DashboardScreen(Screen):
|
|
|
1942
1981
|
|
|
1943
1982
|
self._set_streaming(False)
|
|
1944
1983
|
|
|
1945
|
-
def update_streaming_message(
|
|
1984
|
+
def update_streaming_message(
|
|
1985
|
+
self,
|
|
1986
|
+
output: any,
|
|
1987
|
+
widget_ref: Markdown | AssistantMessageRow | None = None,
|
|
1988
|
+
) -> Markdown | AssistantMessageRow | None:
|
|
1946
1989
|
"""Update or create a streaming message widget with new output.
|
|
1947
1990
|
|
|
1948
1991
|
Returns the widget being updated/created for future updates.
|
|
@@ -1953,21 +1996,24 @@ class DashboardScreen(Screen):
|
|
|
1953
1996
|
|
|
1954
1997
|
messages = self.query_one("#messages", VerticalScroll)
|
|
1955
1998
|
|
|
1956
|
-
|
|
1999
|
+
markdown_text = self._prepare_markdown(text_content)
|
|
1957
2000
|
if widget_ref is None:
|
|
1958
|
-
widget_ref =
|
|
2001
|
+
widget_ref = AssistantMessageRow(markdown_text, classes="message assistant-message")
|
|
1959
2002
|
messages.mount(widget_ref)
|
|
1960
2003
|
else:
|
|
1961
2004
|
try:
|
|
1962
|
-
widget_ref
|
|
2005
|
+
if isinstance(widget_ref, AssistantMessageRow):
|
|
2006
|
+
widget_ref.update_markdown(markdown_text)
|
|
2007
|
+
else:
|
|
2008
|
+
# Backward-compat: if an older ref is a Markdown widget.
|
|
2009
|
+
widget_ref.update(markdown_text)
|
|
1963
2010
|
except Exception as e:
|
|
1964
2011
|
logger.warning(f"Failed to update widget: {e}")
|
|
1965
|
-
widget_ref =
|
|
2012
|
+
widget_ref = AssistantMessageRow(markdown_text, classes="message assistant-message")
|
|
1966
2013
|
messages.mount(widget_ref)
|
|
1967
2014
|
|
|
1968
2015
|
messages.scroll_end(animate=False)
|
|
1969
2016
|
return widget_ref
|
|
1970
|
-
|
|
1971
2017
|
def extract_text_from_output(self, output: any) -> str:
|
|
1972
2018
|
"""Extract text content from output blocks structure and clean it for display."""
|
|
1973
2019
|
if not isinstance(output, dict):
|
|
@@ -2157,10 +2203,17 @@ class DashboardScreen(Screen):
|
|
|
2157
2203
|
messages = self.query_one("#messages", VerticalScroll)
|
|
2158
2204
|
assistant_messages = messages.query(".assistant-message")
|
|
2159
2205
|
|
|
2206
|
+
prepared = self._prepare_markdown(text)
|
|
2207
|
+
|
|
2160
2208
|
if assistant_messages:
|
|
2161
|
-
# Update the last assistant message
|
|
2162
2209
|
last_msg = assistant_messages[-1]
|
|
2163
|
-
last_msg
|
|
2210
|
+
if isinstance(last_msg, AssistantMessageRow):
|
|
2211
|
+
last_msg.update_markdown(prepared)
|
|
2212
|
+
else:
|
|
2213
|
+
# Backward-compat if we have an older Markdown widget in the tree.
|
|
2214
|
+
try:
|
|
2215
|
+
last_msg.update(prepared)
|
|
2216
|
+
except Exception:
|
|
2217
|
+
self.add_message(text, "assistant")
|
|
2164
2218
|
else:
|
|
2165
|
-
# No existing message, create new one
|
|
2166
2219
|
self.add_message(text, "assistant")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|