minion-code 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl
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.
- examples/cli_entrypoint.py +60 -0
- examples/{agent_with_todos.py → components/agent_with_todos.py} +58 -47
- examples/{message_response_children_demo.py → components/message_response_children_demo.py} +61 -55
- examples/components/messages_component.py +199 -0
- examples/file_freshness_example.py +22 -22
- examples/file_watching_example.py +32 -26
- examples/interruptible_tui.py +921 -3
- examples/repl_tui.py +129 -0
- examples/skills/example_usage.py +57 -0
- examples/start.py +173 -0
- minion_code/__init__.py +1 -1
- minion_code/acp_server/__init__.py +34 -0
- minion_code/acp_server/agent.py +539 -0
- minion_code/acp_server/hooks.py +354 -0
- minion_code/acp_server/main.py +194 -0
- minion_code/acp_server/permissions.py +142 -0
- minion_code/acp_server/test_client.py +104 -0
- minion_code/adapters/__init__.py +22 -0
- minion_code/adapters/output_adapter.py +207 -0
- minion_code/adapters/rich_adapter.py +169 -0
- minion_code/adapters/textual_adapter.py +254 -0
- minion_code/agents/__init__.py +2 -2
- minion_code/agents/code_agent.py +517 -104
- minion_code/agents/hooks.py +378 -0
- minion_code/cli.py +538 -429
- minion_code/cli_simple.py +665 -0
- minion_code/commands/__init__.py +136 -29
- minion_code/commands/clear_command.py +19 -46
- minion_code/commands/help_command.py +33 -49
- minion_code/commands/history_command.py +37 -55
- minion_code/commands/model_command.py +194 -0
- minion_code/commands/quit_command.py +9 -12
- minion_code/commands/resume_command.py +181 -0
- minion_code/commands/skill_command.py +89 -0
- minion_code/commands/status_command.py +48 -73
- minion_code/commands/tools_command.py +54 -52
- minion_code/commands/version_command.py +34 -69
- minion_code/components/ConfirmDialog.py +430 -0
- minion_code/components/Message.py +318 -97
- minion_code/components/MessageResponse.py +30 -29
- minion_code/components/Messages.py +351 -0
- minion_code/components/PromptInput.py +499 -245
- minion_code/components/__init__.py +24 -17
- minion_code/const.py +7 -0
- minion_code/screens/REPL.py +1453 -469
- minion_code/screens/__init__.py +1 -1
- minion_code/services/__init__.py +20 -20
- minion_code/services/event_system.py +19 -14
- minion_code/services/file_freshness_service.py +223 -170
- minion_code/skills/__init__.py +25 -0
- minion_code/skills/skill.py +128 -0
- minion_code/skills/skill_loader.py +198 -0
- minion_code/skills/skill_registry.py +177 -0
- minion_code/subagents/__init__.py +31 -0
- minion_code/subagents/builtin/__init__.py +30 -0
- minion_code/subagents/builtin/claude_code_guide.py +32 -0
- minion_code/subagents/builtin/explore.py +36 -0
- minion_code/subagents/builtin/general_purpose.py +19 -0
- minion_code/subagents/builtin/plan.py +61 -0
- minion_code/subagents/subagent.py +116 -0
- minion_code/subagents/subagent_loader.py +147 -0
- minion_code/subagents/subagent_registry.py +151 -0
- minion_code/tools/__init__.py +8 -2
- minion_code/tools/bash_tool.py +16 -3
- minion_code/tools/file_edit_tool.py +201 -104
- minion_code/tools/file_read_tool.py +183 -26
- minion_code/tools/file_write_tool.py +17 -3
- minion_code/tools/glob_tool.py +23 -2
- minion_code/tools/grep_tool.py +229 -21
- minion_code/tools/ls_tool.py +28 -3
- minion_code/tools/multi_edit_tool.py +89 -84
- minion_code/tools/python_interpreter_tool.py +9 -1
- minion_code/tools/skill_tool.py +210 -0
- minion_code/tools/task_tool.py +287 -0
- minion_code/tools/todo_read_tool.py +28 -24
- minion_code/tools/todo_write_tool.py +82 -65
- minion_code/{types.py → type_defs.py} +15 -2
- minion_code/utils/__init__.py +45 -17
- minion_code/utils/config.py +610 -0
- minion_code/utils/history.py +114 -0
- minion_code/utils/logs.py +53 -0
- minion_code/utils/mcp_loader.py +153 -55
- minion_code/utils/output_truncator.py +233 -0
- minion_code/utils/session_storage.py +369 -0
- minion_code/utils/todo_file_utils.py +26 -22
- minion_code/utils/todo_storage.py +43 -33
- minion_code/web/__init__.py +9 -0
- minion_code/web/adapters/__init__.py +5 -0
- minion_code/web/adapters/web_adapter.py +524 -0
- minion_code/web/api/__init__.py +7 -0
- minion_code/web/api/chat.py +277 -0
- minion_code/web/api/interactions.py +136 -0
- minion_code/web/api/sessions.py +135 -0
- minion_code/web/server.py +149 -0
- minion_code/web/services/__init__.py +5 -0
- minion_code/web/services/session_manager.py +420 -0
- minion_code-0.1.2.dist-info/METADATA +476 -0
- minion_code-0.1.2.dist-info/RECORD +111 -0
- {minion_code-0.1.0.dist-info → minion_code-0.1.2.dist-info}/WHEEL +1 -1
- minion_code-0.1.2.dist-info/entry_points.txt +6 -0
- tests/test_adapter.py +67 -0
- tests/test_adapter_simple.py +79 -0
- tests/test_file_read_tool.py +144 -0
- tests/test_readonly_tools.py +0 -2
- tests/test_skills.py +441 -0
- examples/advance_tui.py +0 -508
- examples/rich_example.py +0 -4
- examples/simple_file_watching.py +0 -57
- examples/simple_tui.py +0 -267
- examples/simple_usage.py +0 -69
- minion_code-0.1.0.dist-info/METADATA +0 -350
- minion_code-0.1.0.dist-info/RECORD +0 -59
- minion_code-0.1.0.dist-info/entry_points.txt +0 -4
- {minion_code-0.1.0.dist-info → minion_code-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {minion_code-0.1.0.dist-info → minion_code-0.1.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Dialog Components for Textual TUI
|
|
5
|
+
|
|
6
|
+
These components provide user interaction dialogs for confirmation,
|
|
7
|
+
choice selection, and text input in the Textual TUI.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from textual.containers import Container, Vertical, Horizontal, VerticalScroll
|
|
11
|
+
from textual.widgets import Button, Static, Label
|
|
12
|
+
from textual.app import ComposeResult
|
|
13
|
+
from textual import on
|
|
14
|
+
from typing import Callable, Optional, List
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConfirmDialog(Container):
|
|
18
|
+
"""
|
|
19
|
+
Confirmation dialog component.
|
|
20
|
+
|
|
21
|
+
Displays a message with Yes/No buttons and calls a callback
|
|
22
|
+
with the user's choice.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
DEFAULT_CSS = """
|
|
26
|
+
ConfirmDialog {
|
|
27
|
+
width: 60;
|
|
28
|
+
height: auto;
|
|
29
|
+
background: $panel;
|
|
30
|
+
border: thick $primary;
|
|
31
|
+
padding: 1 2;
|
|
32
|
+
layer: overlay;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
ConfirmDialog .dialog-title {
|
|
36
|
+
text-style: bold;
|
|
37
|
+
text-align: center;
|
|
38
|
+
color: $warning;
|
|
39
|
+
margin-bottom: 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
ConfirmDialog .dialog-message {
|
|
43
|
+
text-align: left;
|
|
44
|
+
color: $text;
|
|
45
|
+
margin-bottom: 2;
|
|
46
|
+
padding: 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
ConfirmDialog .dialog-buttons {
|
|
50
|
+
height: 3;
|
|
51
|
+
align: center middle;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
ConfirmDialog Button {
|
|
55
|
+
margin: 0 1;
|
|
56
|
+
}
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
interaction_id: str,
|
|
62
|
+
message: str,
|
|
63
|
+
title: str = "Confirm",
|
|
64
|
+
ok_text: str = "Yes",
|
|
65
|
+
cancel_text: str = "No",
|
|
66
|
+
on_result: Optional[Callable[[str, bool], None]] = None,
|
|
67
|
+
**kwargs,
|
|
68
|
+
):
|
|
69
|
+
"""
|
|
70
|
+
Initialize confirmation dialog.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
interaction_id: Unique ID for this interaction
|
|
74
|
+
message: Message to display
|
|
75
|
+
title: Dialog title
|
|
76
|
+
ok_text: Text for confirmation button
|
|
77
|
+
cancel_text: Text for cancel button
|
|
78
|
+
on_result: Callback function (interaction_id, result)
|
|
79
|
+
"""
|
|
80
|
+
super().__init__(**kwargs)
|
|
81
|
+
self.interaction_id = interaction_id
|
|
82
|
+
self.message = message
|
|
83
|
+
self.title = title
|
|
84
|
+
self.ok_text = ok_text
|
|
85
|
+
self.cancel_text = cancel_text
|
|
86
|
+
self.on_result = on_result
|
|
87
|
+
|
|
88
|
+
def compose(self) -> ComposeResult:
|
|
89
|
+
"""Compose the dialog UI."""
|
|
90
|
+
with Vertical():
|
|
91
|
+
yield Static(self.title, classes="dialog-title")
|
|
92
|
+
yield Static(self.message, classes="dialog-message")
|
|
93
|
+
with Horizontal(classes="dialog-buttons"):
|
|
94
|
+
yield Button(self.ok_text, id="confirm_ok", variant="success")
|
|
95
|
+
yield Button(self.cancel_text, id="confirm_cancel", variant="error")
|
|
96
|
+
|
|
97
|
+
@on(Button.Pressed, "#confirm_ok")
|
|
98
|
+
def on_ok(self):
|
|
99
|
+
"""Handle confirmation button click."""
|
|
100
|
+
if self.on_result:
|
|
101
|
+
self.on_result(self.interaction_id, True)
|
|
102
|
+
self.remove()
|
|
103
|
+
|
|
104
|
+
@on(Button.Pressed, "#confirm_cancel")
|
|
105
|
+
def on_cancel(self):
|
|
106
|
+
"""Handle cancel button click."""
|
|
107
|
+
if self.on_result:
|
|
108
|
+
self.on_result(self.interaction_id, False)
|
|
109
|
+
self.remove()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ChoiceDialog(Container):
|
|
113
|
+
"""
|
|
114
|
+
Choice selection dialog component.
|
|
115
|
+
|
|
116
|
+
Displays a list of choices as buttons and calls a callback
|
|
117
|
+
with the selected index. Supports keyboard navigation with
|
|
118
|
+
arrow keys and number keys.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
BINDINGS = [
|
|
122
|
+
("up", "move_up", "Previous"),
|
|
123
|
+
("down", "move_down", "Next"),
|
|
124
|
+
("k", "move_up", "Previous"),
|
|
125
|
+
("j", "move_down", "Next"),
|
|
126
|
+
("escape", "cancel", "Cancel"),
|
|
127
|
+
("enter", "select", "Select"),
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
DEFAULT_CSS = """
|
|
131
|
+
ChoiceDialog {
|
|
132
|
+
width: 60;
|
|
133
|
+
max-height: 80%;
|
|
134
|
+
background: $panel;
|
|
135
|
+
border: thick $primary;
|
|
136
|
+
padding: 1 2;
|
|
137
|
+
layer: overlay;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
ChoiceDialog .dialog-title {
|
|
141
|
+
text-style: bold;
|
|
142
|
+
text-align: center;
|
|
143
|
+
color: $accent;
|
|
144
|
+
margin-bottom: 1;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
ChoiceDialog .dialog-message {
|
|
148
|
+
text-align: left;
|
|
149
|
+
color: $text;
|
|
150
|
+
margin-bottom: 1;
|
|
151
|
+
padding: 1;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
ChoiceDialog .dialog-hint {
|
|
155
|
+
text-align: center;
|
|
156
|
+
color: $text-muted;
|
|
157
|
+
margin-bottom: 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
ChoiceDialog .choice-scroll {
|
|
161
|
+
max-height: 50vh;
|
|
162
|
+
min-height: 10;
|
|
163
|
+
padding: 1;
|
|
164
|
+
scrollbar-gutter: stable;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
ChoiceDialog Button {
|
|
168
|
+
width: 100%;
|
|
169
|
+
margin: 0 0 1 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
ChoiceDialog Button:focus {
|
|
173
|
+
background: $accent;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
ChoiceDialog .cancel-button {
|
|
177
|
+
margin-top: 1;
|
|
178
|
+
}
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
def __init__(
|
|
182
|
+
self,
|
|
183
|
+
interaction_id: str,
|
|
184
|
+
message: str,
|
|
185
|
+
choices: List[str],
|
|
186
|
+
title: str = "Select",
|
|
187
|
+
on_result: Optional[Callable[[str, int], None]] = None,
|
|
188
|
+
**kwargs,
|
|
189
|
+
):
|
|
190
|
+
"""
|
|
191
|
+
Initialize choice dialog.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
interaction_id: Unique ID for this interaction
|
|
195
|
+
message: Message to display
|
|
196
|
+
choices: List of choice strings
|
|
197
|
+
title: Dialog title
|
|
198
|
+
on_result: Callback function (interaction_id, selected_index)
|
|
199
|
+
"""
|
|
200
|
+
super().__init__(**kwargs)
|
|
201
|
+
self.interaction_id = interaction_id
|
|
202
|
+
self.message = message
|
|
203
|
+
self.choices = choices
|
|
204
|
+
self.title = title
|
|
205
|
+
self.on_result = on_result
|
|
206
|
+
self.can_focus = True
|
|
207
|
+
|
|
208
|
+
def compose(self) -> ComposeResult:
|
|
209
|
+
"""Compose the dialog UI."""
|
|
210
|
+
with Vertical():
|
|
211
|
+
yield Static(self.title, classes="dialog-title")
|
|
212
|
+
if self.message:
|
|
213
|
+
yield Static(self.message, classes="dialog-message")
|
|
214
|
+
yield Static(
|
|
215
|
+
"Use ↑↓/jk to navigate, Enter to select, Esc to cancel",
|
|
216
|
+
classes="dialog-hint",
|
|
217
|
+
)
|
|
218
|
+
with VerticalScroll(classes="choice-scroll"):
|
|
219
|
+
for i, choice in enumerate(self.choices):
|
|
220
|
+
yield Button(
|
|
221
|
+
f"{i+1}. {choice}", id=f"choice_{i}", variant="primary"
|
|
222
|
+
)
|
|
223
|
+
yield Button(
|
|
224
|
+
"Cancel", id="choice_cancel", variant="error", classes="cancel-button"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def on_mount(self):
|
|
228
|
+
"""Focus first button when dialog appears."""
|
|
229
|
+
try:
|
|
230
|
+
first_button = self.query_one("#choice_0", Button)
|
|
231
|
+
first_button.focus()
|
|
232
|
+
except Exception:
|
|
233
|
+
pass
|
|
234
|
+
|
|
235
|
+
def action_move_up(self):
|
|
236
|
+
"""Move focus to previous choice."""
|
|
237
|
+
try:
|
|
238
|
+
focused = self.app.focused
|
|
239
|
+
if focused and focused.id and focused.id.startswith("choice_"):
|
|
240
|
+
if focused.id == "choice_cancel":
|
|
241
|
+
# Move from cancel to last choice
|
|
242
|
+
last_btn = self.query_one(f"#choice_{len(self.choices)-1}", Button)
|
|
243
|
+
last_btn.focus()
|
|
244
|
+
self._scroll_to_button(last_btn)
|
|
245
|
+
else:
|
|
246
|
+
idx = int(focused.id.split("_")[1])
|
|
247
|
+
if idx > 0:
|
|
248
|
+
prev_btn = self.query_one(f"#choice_{idx-1}", Button)
|
|
249
|
+
prev_btn.focus()
|
|
250
|
+
self._scroll_to_button(prev_btn)
|
|
251
|
+
except Exception:
|
|
252
|
+
pass
|
|
253
|
+
|
|
254
|
+
def action_move_down(self):
|
|
255
|
+
"""Move focus to next choice."""
|
|
256
|
+
try:
|
|
257
|
+
focused = self.app.focused
|
|
258
|
+
if focused and focused.id and focused.id.startswith("choice_"):
|
|
259
|
+
idx = int(focused.id.split("_")[1])
|
|
260
|
+
if idx < len(self.choices) - 1:
|
|
261
|
+
next_btn = self.query_one(f"#choice_{idx+1}", Button)
|
|
262
|
+
next_btn.focus()
|
|
263
|
+
self._scroll_to_button(next_btn)
|
|
264
|
+
else:
|
|
265
|
+
# Move to cancel button
|
|
266
|
+
cancel_btn = self.query_one("#choice_cancel", Button)
|
|
267
|
+
cancel_btn.focus()
|
|
268
|
+
except Exception:
|
|
269
|
+
pass
|
|
270
|
+
|
|
271
|
+
def _scroll_to_button(self, button: Button):
|
|
272
|
+
"""Scroll to make the button visible."""
|
|
273
|
+
try:
|
|
274
|
+
scroll_container = self.query_one(".choice-scroll", VerticalScroll)
|
|
275
|
+
scroll_container.scroll_to_widget(button)
|
|
276
|
+
except Exception:
|
|
277
|
+
pass
|
|
278
|
+
|
|
279
|
+
def action_select(self):
|
|
280
|
+
"""Select the currently focused choice."""
|
|
281
|
+
try:
|
|
282
|
+
focused = self.app.focused
|
|
283
|
+
if focused and focused.id:
|
|
284
|
+
focused.press()
|
|
285
|
+
except Exception:
|
|
286
|
+
pass
|
|
287
|
+
|
|
288
|
+
def action_cancel(self):
|
|
289
|
+
"""Cancel the dialog."""
|
|
290
|
+
if self.on_result:
|
|
291
|
+
self.on_result(self.interaction_id, -1)
|
|
292
|
+
self.remove()
|
|
293
|
+
|
|
294
|
+
@on(Button.Pressed)
|
|
295
|
+
def on_button_pressed(self, event: Button.Pressed):
|
|
296
|
+
"""Handle button click."""
|
|
297
|
+
button_id = event.button.id
|
|
298
|
+
|
|
299
|
+
if button_id == "choice_cancel":
|
|
300
|
+
if self.on_result:
|
|
301
|
+
self.on_result(self.interaction_id, -1)
|
|
302
|
+
elif button_id and button_id.startswith("choice_"):
|
|
303
|
+
try:
|
|
304
|
+
choice_index = int(button_id.split("_")[1])
|
|
305
|
+
if self.on_result:
|
|
306
|
+
self.on_result(self.interaction_id, choice_index)
|
|
307
|
+
except (ValueError, IndexError):
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
self.remove()
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class InputDialog(Container):
|
|
314
|
+
"""
|
|
315
|
+
Text input dialog component.
|
|
316
|
+
|
|
317
|
+
Displays an input field and calls a callback with the entered text.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
DEFAULT_CSS = """
|
|
321
|
+
InputDialog {
|
|
322
|
+
width: 60;
|
|
323
|
+
height: auto;
|
|
324
|
+
background: $panel;
|
|
325
|
+
border: thick $primary;
|
|
326
|
+
padding: 1 2;
|
|
327
|
+
layer: overlay;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
InputDialog .dialog-title {
|
|
331
|
+
text-style: bold;
|
|
332
|
+
text-align: center;
|
|
333
|
+
color: $accent;
|
|
334
|
+
margin-bottom: 1;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
InputDialog .dialog-message {
|
|
338
|
+
text-align: left;
|
|
339
|
+
color: $text;
|
|
340
|
+
margin-bottom: 1;
|
|
341
|
+
padding: 1;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
InputDialog Input {
|
|
345
|
+
width: 100%;
|
|
346
|
+
margin: 1 0;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
InputDialog .dialog-buttons {
|
|
350
|
+
height: 3;
|
|
351
|
+
align: center middle;
|
|
352
|
+
margin-top: 1;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
InputDialog Button {
|
|
356
|
+
margin: 0 1;
|
|
357
|
+
}
|
|
358
|
+
"""
|
|
359
|
+
|
|
360
|
+
def __init__(
|
|
361
|
+
self,
|
|
362
|
+
interaction_id: str,
|
|
363
|
+
message: str,
|
|
364
|
+
title: str = "Input",
|
|
365
|
+
default: str = "",
|
|
366
|
+
placeholder: str = "",
|
|
367
|
+
on_result: Optional[Callable[[str, Optional[str]], None]] = None,
|
|
368
|
+
**kwargs,
|
|
369
|
+
):
|
|
370
|
+
"""
|
|
371
|
+
Initialize input dialog.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
interaction_id: Unique ID for this interaction
|
|
375
|
+
message: Message to display
|
|
376
|
+
title: Dialog title
|
|
377
|
+
default: Default input value
|
|
378
|
+
placeholder: Placeholder text
|
|
379
|
+
on_result: Callback function (interaction_id, input_text or None)
|
|
380
|
+
"""
|
|
381
|
+
super().__init__(**kwargs)
|
|
382
|
+
self.interaction_id = interaction_id
|
|
383
|
+
self.message = message
|
|
384
|
+
self.title = title
|
|
385
|
+
self.default = default
|
|
386
|
+
self.placeholder = placeholder
|
|
387
|
+
self.on_result = on_result
|
|
388
|
+
|
|
389
|
+
def compose(self) -> ComposeResult:
|
|
390
|
+
"""Compose the dialog UI."""
|
|
391
|
+
from textual.widgets import Input
|
|
392
|
+
|
|
393
|
+
with Vertical():
|
|
394
|
+
yield Static(self.title, classes="dialog-title")
|
|
395
|
+
if self.message:
|
|
396
|
+
yield Static(self.message, classes="dialog-message")
|
|
397
|
+
yield Input(
|
|
398
|
+
value=self.default, placeholder=self.placeholder, id="input_field"
|
|
399
|
+
)
|
|
400
|
+
with Horizontal(classes="dialog-buttons"):
|
|
401
|
+
yield Button("OK", id="input_ok", variant="success")
|
|
402
|
+
yield Button("Cancel", id="input_cancel", variant="error")
|
|
403
|
+
|
|
404
|
+
def on_mount(self):
|
|
405
|
+
"""Focus the input field when dialog appears."""
|
|
406
|
+
try:
|
|
407
|
+
input_field = self.query_one("#input_field")
|
|
408
|
+
input_field.focus()
|
|
409
|
+
except:
|
|
410
|
+
pass
|
|
411
|
+
|
|
412
|
+
@on(Button.Pressed, "#input_ok")
|
|
413
|
+
def on_ok(self):
|
|
414
|
+
"""Handle OK button click."""
|
|
415
|
+
try:
|
|
416
|
+
input_field = self.query_one("#input_field")
|
|
417
|
+
value = input_field.value
|
|
418
|
+
if self.on_result:
|
|
419
|
+
self.on_result(self.interaction_id, value)
|
|
420
|
+
except:
|
|
421
|
+
if self.on_result:
|
|
422
|
+
self.on_result(self.interaction_id, None)
|
|
423
|
+
self.remove()
|
|
424
|
+
|
|
425
|
+
@on(Button.Pressed, "#input_cancel")
|
|
426
|
+
def on_cancel(self):
|
|
427
|
+
"""Handle cancel button click."""
|
|
428
|
+
if self.on_result:
|
|
429
|
+
self.on_result(self.interaction_id, None)
|
|
430
|
+
self.remove()
|