kiwi-code 0.0.6__tar.gz → 0.0.7__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.6 → kiwi_code-0.0.7}/PKG-INFO +1 -1
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/pyproject.toml +1 -1
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_tui/screens/dashboard.py +11 -7
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_tui/widgets.py +91 -1
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/uv.lock +1 -1
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/.github/workflows/publish.yml +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/.gitignore +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/.python-version +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/CLAUDE.md +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/Makefile +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/README.md +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_cli/__init__.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_cli/auth.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_cli/cli.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_cli/client.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_cli/commands.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_cli/config.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_cli/logger.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_cli/models.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_cli/runtime_manager.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_runtime/__init__.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_runtime/__main__.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_runtime/main.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_tui/__init__.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_tui/main.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_tui/screens/__init__.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_tui/screens/actions.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_tui/screens/autobots.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_tui/screens/file_browser.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_tui/screens/login.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/src/kiwi_tui/screens/runtime_logs.py +0 -0
- {kiwi_code-0.0.6 → kiwi_code-0.0.7}/test_hello.py +0 -0
|
@@ -5,6 +5,7 @@ from textual.screen import Screen
|
|
|
5
5
|
from textual.widgets import Header, Footer, Input, Static, Button
|
|
6
6
|
from textual.containers import Vertical, VerticalScroll, Horizontal
|
|
7
7
|
from kiwi_tui.screens.file_browser import FileBrowserScreen
|
|
8
|
+
from kiwi_tui.widgets import ChatInput
|
|
8
9
|
from textual.worker import Worker, WorkerState
|
|
9
10
|
from loguru import logger
|
|
10
11
|
import json
|
|
@@ -166,14 +167,14 @@ class DashboardScreen(Screen):
|
|
|
166
167
|
yield Static("", id="pending-files-bar")
|
|
167
168
|
with Horizontal(id="input-row"):
|
|
168
169
|
yield Button("+", id="upload-btn", variant="default")
|
|
169
|
-
yield
|
|
170
|
+
yield ChatInput(placeholder="Message...", id="chat-input")
|
|
170
171
|
yield Button("Send", id="send-btn", variant="default")
|
|
171
172
|
|
|
172
173
|
yield Footer()
|
|
173
174
|
|
|
174
175
|
def on_mount(self) -> None:
|
|
175
176
|
"""Called when screen is mounted."""
|
|
176
|
-
self.query_one("#chat-input",
|
|
177
|
+
self.query_one("#chat-input", ChatInput).focus()
|
|
177
178
|
|
|
178
179
|
def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
179
180
|
"""Handle message submission."""
|
|
@@ -181,7 +182,9 @@ class DashboardScreen(Screen):
|
|
|
181
182
|
if not message:
|
|
182
183
|
return
|
|
183
184
|
|
|
184
|
-
#
|
|
185
|
+
# Record in history and clear input
|
|
186
|
+
chat_input = self.query_one("#chat-input", ChatInput)
|
|
187
|
+
chat_input.record(message)
|
|
185
188
|
event.input.value = ""
|
|
186
189
|
|
|
187
190
|
# Handle "/" commands
|
|
@@ -483,7 +486,7 @@ class DashboardScreen(Screen):
|
|
|
483
486
|
for btn_id, label in rows:
|
|
484
487
|
messages.mount(Button(label, id=btn_id, classes="action-btn"))
|
|
485
488
|
messages.scroll_end(animate=False)
|
|
486
|
-
self.query_one("#chat-input",
|
|
489
|
+
self.query_one("#chat-input", ChatInput).focus()
|
|
487
490
|
|
|
488
491
|
def _render_actions_list(self, command: str, api_client) -> None:
|
|
489
492
|
"""Render /actions list with clickable rows."""
|
|
@@ -646,10 +649,11 @@ class DashboardScreen(Screen):
|
|
|
646
649
|
return
|
|
647
650
|
|
|
648
651
|
if btn_id == "send-btn":
|
|
649
|
-
chat_input = self.query_one("#chat-input",
|
|
652
|
+
chat_input = self.query_one("#chat-input", ChatInput)
|
|
650
653
|
message = chat_input.value.strip()
|
|
651
654
|
if not message:
|
|
652
655
|
return
|
|
656
|
+
chat_input.record(message)
|
|
653
657
|
chat_input.value = ""
|
|
654
658
|
if message.startswith("/"):
|
|
655
659
|
self.handle_slash_command(message)
|
|
@@ -709,7 +713,7 @@ class DashboardScreen(Screen):
|
|
|
709
713
|
else:
|
|
710
714
|
return
|
|
711
715
|
|
|
712
|
-
self.query_one("#chat-input",
|
|
716
|
+
self.query_one("#chat-input", ChatInput).focus()
|
|
713
717
|
|
|
714
718
|
def add_message(self, text: str, msg_type: str = "assistant") -> None:
|
|
715
719
|
"""Add a message to the chat."""
|
|
@@ -733,7 +737,7 @@ class DashboardScreen(Screen):
|
|
|
733
737
|
self._update_pending_files_bar()
|
|
734
738
|
else:
|
|
735
739
|
self.add_message(f"Upload failed: {message}", "error")
|
|
736
|
-
self.query_one("#chat-input",
|
|
740
|
+
self.query_one("#chat-input", ChatInput).focus()
|
|
737
741
|
|
|
738
742
|
def _update_pending_files_bar(self) -> None:
|
|
739
743
|
"""Update the pending-files bar to show queued file URLs or hide when empty."""
|
|
@@ -2,11 +2,101 @@
|
|
|
2
2
|
|
|
3
3
|
from textual.app import ComposeResult
|
|
4
4
|
from textual.containers import Container, Vertical, Horizontal
|
|
5
|
-
from textual.widgets import Static, Button, Label, DataTable
|
|
5
|
+
from textual.widgets import Static, Button, Label, DataTable, Input
|
|
6
6
|
from textual.reactive import reactive
|
|
7
|
+
from textual.suggester import SuggestFromList
|
|
7
8
|
from rich.text import Text
|
|
8
9
|
|
|
9
10
|
|
|
11
|
+
# Slash commands for autocomplete
|
|
12
|
+
_SLASH_COMMANDS = [
|
|
13
|
+
"/help",
|
|
14
|
+
"/actions list",
|
|
15
|
+
"/actions get ",
|
|
16
|
+
"/runs list",
|
|
17
|
+
"/runs get ",
|
|
18
|
+
"/graphs list",
|
|
19
|
+
"/graphs get ",
|
|
20
|
+
"/graph-runs list",
|
|
21
|
+
"/graph-runs get ",
|
|
22
|
+
"/use ",
|
|
23
|
+
"/continue ",
|
|
24
|
+
"/new",
|
|
25
|
+
"/status",
|
|
26
|
+
"/cancel",
|
|
27
|
+
"/upload ",
|
|
28
|
+
"/files",
|
|
29
|
+
"/clear-files",
|
|
30
|
+
"/metadata",
|
|
31
|
+
"/metadata set ",
|
|
32
|
+
"/metadata remove ",
|
|
33
|
+
"/metadata clear",
|
|
34
|
+
"/runtime status",
|
|
35
|
+
"/runtime start",
|
|
36
|
+
"/runtime stop",
|
|
37
|
+
"/runtime restart",
|
|
38
|
+
"/runtime list",
|
|
39
|
+
"/runtime logs",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ChatInput(Input):
|
|
44
|
+
"""Input with command history (up/down arrows) and slash command autocomplete."""
|
|
45
|
+
|
|
46
|
+
_MAX_HISTORY = 200
|
|
47
|
+
|
|
48
|
+
def __init__(self, *args, **kwargs):
|
|
49
|
+
kwargs.setdefault("suggester", SuggestFromList(_SLASH_COMMANDS, case_sensitive=False))
|
|
50
|
+
super().__init__(*args, **kwargs)
|
|
51
|
+
self._history: list[str] = []
|
|
52
|
+
self._history_index: int = -1
|
|
53
|
+
self._draft: str = "" # saves current text when browsing history
|
|
54
|
+
|
|
55
|
+
def record(self, value: str) -> None:
|
|
56
|
+
"""Add a submitted value to history."""
|
|
57
|
+
value = value.strip()
|
|
58
|
+
if not value:
|
|
59
|
+
return
|
|
60
|
+
# Deduplicate consecutive
|
|
61
|
+
if self._history and self._history[-1] == value:
|
|
62
|
+
return
|
|
63
|
+
self._history.append(value)
|
|
64
|
+
if len(self._history) > self._MAX_HISTORY:
|
|
65
|
+
self._history.pop(0)
|
|
66
|
+
self._history_index = -1
|
|
67
|
+
self._draft = ""
|
|
68
|
+
|
|
69
|
+
async def _on_key(self, event) -> None:
|
|
70
|
+
if event.key == "up":
|
|
71
|
+
if not self._history:
|
|
72
|
+
return
|
|
73
|
+
event.prevent_default()
|
|
74
|
+
event.stop()
|
|
75
|
+
if self._history_index == -1:
|
|
76
|
+
# Entering history — save current draft
|
|
77
|
+
self._draft = self.value
|
|
78
|
+
self._history_index = len(self._history) - 1
|
|
79
|
+
elif self._history_index > 0:
|
|
80
|
+
self._history_index -= 1
|
|
81
|
+
self.value = self._history[self._history_index]
|
|
82
|
+
self.cursor_position = len(self.value)
|
|
83
|
+
elif event.key == "down":
|
|
84
|
+
if self._history_index == -1:
|
|
85
|
+
return
|
|
86
|
+
event.prevent_default()
|
|
87
|
+
event.stop()
|
|
88
|
+
if self._history_index < len(self._history) - 1:
|
|
89
|
+
self._history_index += 1
|
|
90
|
+
self.value = self._history[self._history_index]
|
|
91
|
+
else:
|
|
92
|
+
# Past end — restore draft
|
|
93
|
+
self._history_index = -1
|
|
94
|
+
self.value = self._draft
|
|
95
|
+
self.cursor_position = len(self.value)
|
|
96
|
+
else:
|
|
97
|
+
await super()._on_key(event)
|
|
98
|
+
|
|
99
|
+
|
|
10
100
|
class StatusBadge(Static):
|
|
11
101
|
"""A colored status badge widget."""
|
|
12
102
|
|
|
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
|