minion-code 0.1.0__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/advance_tui.py +508 -0
- examples/agent_with_todos.py +165 -0
- examples/file_freshness_example.py +97 -0
- examples/file_watching_example.py +110 -0
- examples/interruptible_tui.py +5 -0
- examples/message_response_children_demo.py +226 -0
- examples/rich_example.py +4 -0
- examples/simple_file_watching.py +57 -0
- examples/simple_tui.py +267 -0
- examples/simple_usage.py +69 -0
- minion_code/__init__.py +16 -0
- minion_code/agents/__init__.py +11 -0
- minion_code/agents/code_agent.py +320 -0
- minion_code/cli.py +502 -0
- minion_code/commands/__init__.py +90 -0
- minion_code/commands/clear_command.py +70 -0
- minion_code/commands/help_command.py +90 -0
- minion_code/commands/history_command.py +104 -0
- minion_code/commands/quit_command.py +32 -0
- minion_code/commands/status_command.py +115 -0
- minion_code/commands/tools_command.py +86 -0
- minion_code/commands/version_command.py +104 -0
- minion_code/components/Message.py +304 -0
- minion_code/components/MessageResponse.py +188 -0
- minion_code/components/PromptInput.py +534 -0
- minion_code/components/__init__.py +29 -0
- minion_code/screens/REPL.py +925 -0
- minion_code/screens/__init__.py +4 -0
- minion_code/services/__init__.py +50 -0
- minion_code/services/event_system.py +108 -0
- minion_code/services/file_freshness_service.py +582 -0
- minion_code/tools/__init__.py +69 -0
- minion_code/tools/bash_tool.py +58 -0
- minion_code/tools/file_edit_tool.py +238 -0
- minion_code/tools/file_read_tool.py +73 -0
- minion_code/tools/file_write_tool.py +36 -0
- minion_code/tools/glob_tool.py +58 -0
- minion_code/tools/grep_tool.py +105 -0
- minion_code/tools/ls_tool.py +65 -0
- minion_code/tools/multi_edit_tool.py +271 -0
- minion_code/tools/python_interpreter_tool.py +105 -0
- minion_code/tools/todo_read_tool.py +100 -0
- minion_code/tools/todo_write_tool.py +234 -0
- minion_code/tools/user_input_tool.py +53 -0
- minion_code/types.py +88 -0
- minion_code/utils/__init__.py +44 -0
- minion_code/utils/mcp_loader.py +211 -0
- minion_code/utils/todo_file_utils.py +110 -0
- minion_code/utils/todo_storage.py +149 -0
- minion_code-0.1.0.dist-info/METADATA +350 -0
- minion_code-0.1.0.dist-info/RECORD +59 -0
- minion_code-0.1.0.dist-info/WHEEL +5 -0
- minion_code-0.1.0.dist-info/entry_points.txt +4 -0
- minion_code-0.1.0.dist-info/licenses/LICENSE +661 -0
- minion_code-0.1.0.dist-info/top_level.txt +3 -0
- tests/__init__.py +1 -0
- tests/test_basic.py +20 -0
- tests/test_readonly_tools.py +102 -0
- tests/test_tools.py +83 -0
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PromptInput Component - Python equivalent of React PromptInput
|
|
3
|
+
Handles user input with multiple modes (prompt, bash, koding)
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from textual.containers import Container, Horizontal, Vertical
|
|
7
|
+
from textual.widgets import Input, Static, Button
|
|
8
|
+
from textual.reactive import reactive, var
|
|
9
|
+
from textual import on, work
|
|
10
|
+
from textual.events import Key
|
|
11
|
+
from rich.text import Text
|
|
12
|
+
from typing import List, Dict, Any, Optional, Callable, Union
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from enum import Enum
|
|
15
|
+
import asyncio
|
|
16
|
+
import time
|
|
17
|
+
|
|
18
|
+
# Simple logging setup for TUI - disable to prevent screen interference
|
|
19
|
+
import logging
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
logger.disabled = True
|
|
22
|
+
|
|
23
|
+
# Import shared types
|
|
24
|
+
from ..types import (
|
|
25
|
+
InputMode, Message, MessageType, MessageContent, ModelInfo
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PromptInput(Container):
|
|
30
|
+
"""
|
|
31
|
+
Main input component equivalent to React PromptInput
|
|
32
|
+
Handles user input with mode switching and command processing
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# Working CSS
|
|
36
|
+
CSS = """
|
|
37
|
+
PromptInput {
|
|
38
|
+
dock: bottom;
|
|
39
|
+
height: 6;
|
|
40
|
+
margin: 1;
|
|
41
|
+
border: solid white;
|
|
42
|
+
padding: 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.mode-bash PromptInput {
|
|
46
|
+
border: solid yellow;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.mode-koding PromptInput {
|
|
50
|
+
border: solid cyan;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#mode_prefix {
|
|
54
|
+
width: 3;
|
|
55
|
+
content-align: center middle;
|
|
56
|
+
text-style: bold;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#main_input {
|
|
60
|
+
width: 1fr;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.help-text {
|
|
64
|
+
color: gray;
|
|
65
|
+
text-style: dim;
|
|
66
|
+
margin-bottom: 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.model-info {
|
|
70
|
+
height: 1;
|
|
71
|
+
content-align: right middle;
|
|
72
|
+
background: gray 10%;
|
|
73
|
+
color: white;
|
|
74
|
+
}
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
# Reactive properties equivalent to React useState
|
|
78
|
+
mode = reactive(InputMode.PROMPT)
|
|
79
|
+
input_value = reactive("")
|
|
80
|
+
is_disabled = reactive(False)
|
|
81
|
+
is_loading = reactive(False)
|
|
82
|
+
submit_count = reactive(0)
|
|
83
|
+
cursor_offset = reactive(0)
|
|
84
|
+
|
|
85
|
+
# State for messages and UI feedback
|
|
86
|
+
exit_message = var(dict) # {"show": bool, "key": str}
|
|
87
|
+
message = var(dict) # {"show": bool, "text": str}
|
|
88
|
+
model_switch_message = var(dict) # {"show": bool, "text": str}
|
|
89
|
+
pasted_image = var(None) # Optional[str]
|
|
90
|
+
pasted_text = var(None) # Optional[str]
|
|
91
|
+
placeholder = reactive("")
|
|
92
|
+
|
|
93
|
+
def __init__(self,
|
|
94
|
+
commands=None,
|
|
95
|
+
fork_number=0,
|
|
96
|
+
message_log_name="default",
|
|
97
|
+
is_disabled=False,
|
|
98
|
+
is_loading=False,
|
|
99
|
+
debug=False,
|
|
100
|
+
verbose=False,
|
|
101
|
+
messages=None,
|
|
102
|
+
tools=None,
|
|
103
|
+
input_value="",
|
|
104
|
+
mode=InputMode.PROMPT,
|
|
105
|
+
submit_count=0,
|
|
106
|
+
read_file_timestamps=None,
|
|
107
|
+
abort_controller=None,
|
|
108
|
+
**kwargs):
|
|
109
|
+
super().__init__(**kwargs)
|
|
110
|
+
|
|
111
|
+
# Props equivalent to TypeScript Props interface
|
|
112
|
+
self.commands = commands or []
|
|
113
|
+
self.fork_number = fork_number
|
|
114
|
+
self.message_log_name = message_log_name
|
|
115
|
+
self.debug = debug
|
|
116
|
+
self.verbose = verbose
|
|
117
|
+
self.messages = messages or []
|
|
118
|
+
self.tools = tools or []
|
|
119
|
+
self.read_file_timestamps = read_file_timestamps or {}
|
|
120
|
+
self.abort_controller = abort_controller
|
|
121
|
+
|
|
122
|
+
# Initialize reactive state
|
|
123
|
+
self.mode = mode
|
|
124
|
+
self.input_value = input_value
|
|
125
|
+
self.is_disabled = is_disabled
|
|
126
|
+
self.is_loading = is_loading
|
|
127
|
+
self.submit_count = submit_count
|
|
128
|
+
self.cursor_offset = len(input_value)
|
|
129
|
+
|
|
130
|
+
# Initialize state variables
|
|
131
|
+
self.exit_message = {"show": False, "key": ""}
|
|
132
|
+
self.message = {"show": False, "text": ""}
|
|
133
|
+
self.model_switch_message = {"show": False, "text": ""}
|
|
134
|
+
self.pasted_image = None
|
|
135
|
+
self.pasted_text = None
|
|
136
|
+
self.placeholder = ""
|
|
137
|
+
|
|
138
|
+
# Callbacks (would be passed as props in React)
|
|
139
|
+
self.on_query: Optional[Callable] = None
|
|
140
|
+
self.on_input_change: Optional[Callable] = None
|
|
141
|
+
self.on_mode_change: Optional[Callable] = None
|
|
142
|
+
self.on_submit_count_change: Optional[Callable] = None
|
|
143
|
+
self.set_is_loading: Optional[Callable] = None
|
|
144
|
+
self.set_abort_controller: Optional[Callable] = None
|
|
145
|
+
self.on_show_message_selector: Optional[Callable] = None
|
|
146
|
+
self.set_fork_convo_with_messages: Optional[Callable] = None
|
|
147
|
+
self.on_model_change: Optional[Callable] = None
|
|
148
|
+
self.set_tool_jsx: Optional[Callable] = None
|
|
149
|
+
|
|
150
|
+
logger.info(f"PromptInput initialized in {mode.value} mode")
|
|
151
|
+
|
|
152
|
+
def on_mount(self):
|
|
153
|
+
"""Set focus to input when component mounts"""
|
|
154
|
+
try:
|
|
155
|
+
input_widget = self.query_one("#main_input", expect_type=Input)
|
|
156
|
+
input_widget.focus()
|
|
157
|
+
logger.info("Focus set to main input")
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.warning(f"Could not set focus to input: {e}")
|
|
160
|
+
|
|
161
|
+
def compose(self):
|
|
162
|
+
"""Compose the PromptInput interface - working version"""
|
|
163
|
+
# Model info at top
|
|
164
|
+
yield self._render_model_info()
|
|
165
|
+
|
|
166
|
+
# Help text
|
|
167
|
+
yield Static("! for bash mode · # for AGENTS.md · / for commands", classes="help-text")
|
|
168
|
+
|
|
169
|
+
# Input area with mode prefix
|
|
170
|
+
with Horizontal():
|
|
171
|
+
yield Static(self._get_mode_prefix(), id="mode_prefix")
|
|
172
|
+
yield Input(
|
|
173
|
+
placeholder=self._get_placeholder(),
|
|
174
|
+
value=self.input_value,
|
|
175
|
+
id="main_input",
|
|
176
|
+
disabled=self.is_disabled or self.is_loading
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def _render_model_info(self) -> Static:
|
|
180
|
+
"""Render model information - equivalent to model info display"""
|
|
181
|
+
model_info = self._get_model_info()
|
|
182
|
+
if model_info:
|
|
183
|
+
info_text = f"[{model_info.provider}] {model_info.name}: {model_info.current_tokens//1000}k / {model_info.context_length//1000}k"
|
|
184
|
+
return Static(info_text, id="model_info", classes="model-info")
|
|
185
|
+
return Static("", id="model_info")
|
|
186
|
+
|
|
187
|
+
# _render_status_area method removed - content moved to compose method
|
|
188
|
+
|
|
189
|
+
def _get_mode_prefix(self) -> str:
|
|
190
|
+
"""Get the mode prefix character"""
|
|
191
|
+
if self.mode == InputMode.BASH:
|
|
192
|
+
return " ! "
|
|
193
|
+
elif self.mode == InputMode.KODING:
|
|
194
|
+
return " # "
|
|
195
|
+
else:
|
|
196
|
+
return " > "
|
|
197
|
+
|
|
198
|
+
def _get_placeholder(self) -> str:
|
|
199
|
+
"""Get placeholder text based on current mode"""
|
|
200
|
+
if self.placeholder:
|
|
201
|
+
return self.placeholder
|
|
202
|
+
|
|
203
|
+
if self.mode == InputMode.BASH:
|
|
204
|
+
return "Enter bash command..."
|
|
205
|
+
elif self.mode == InputMode.KODING:
|
|
206
|
+
return "Enter note for AGENTS.md..."
|
|
207
|
+
else:
|
|
208
|
+
return "Enter your message..."
|
|
209
|
+
|
|
210
|
+
def _get_model_info(self) -> Optional[ModelInfo]:
|
|
211
|
+
"""Get current model information - equivalent to modelInfo useMemo"""
|
|
212
|
+
# This would integrate with the actual model manager
|
|
213
|
+
# For now, return mock data
|
|
214
|
+
return ModelInfo(
|
|
215
|
+
name="claude-3-5-sonnet-20241022",
|
|
216
|
+
provider="anthropic",
|
|
217
|
+
context_length=200000,
|
|
218
|
+
current_tokens=len(str(self.messages)) * 4, # Rough token estimate
|
|
219
|
+
id="claude-3-5-sonnet"
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Event handlers
|
|
223
|
+
@on(Input.Changed, "#main_input")
|
|
224
|
+
def on_input_changed(self, event):
|
|
225
|
+
"""Handle input value changes - equivalent to onChange callback"""
|
|
226
|
+
value = event.value
|
|
227
|
+
|
|
228
|
+
# Handle mode switching based on input prefix
|
|
229
|
+
if value.startswith('!'):
|
|
230
|
+
if self.mode != InputMode.BASH:
|
|
231
|
+
self.mode = InputMode.BASH
|
|
232
|
+
if self.on_mode_change:
|
|
233
|
+
self.on_mode_change(InputMode.BASH)
|
|
234
|
+
elif value.startswith('#'):
|
|
235
|
+
if self.mode != InputMode.KODING:
|
|
236
|
+
self.mode = InputMode.KODING
|
|
237
|
+
if self.on_mode_change:
|
|
238
|
+
self.on_mode_change(InputMode.KODING)
|
|
239
|
+
|
|
240
|
+
self.input_value = value
|
|
241
|
+
if self.on_input_change:
|
|
242
|
+
self.on_input_change(value)
|
|
243
|
+
|
|
244
|
+
@on(Input.Submitted, "#main_input")
|
|
245
|
+
async def on_submit(self, event):
|
|
246
|
+
"""Handle input submission - equivalent to onSubmit function"""
|
|
247
|
+
input_text = event.value.strip()
|
|
248
|
+
|
|
249
|
+
if not input_text:
|
|
250
|
+
return
|
|
251
|
+
|
|
252
|
+
if self.is_disabled or self.is_loading:
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
logger.info(f"Submitting input: {input_text[:50]}... (mode: {self.mode.value})")
|
|
256
|
+
|
|
257
|
+
# Handle exit commands
|
|
258
|
+
if input_text.lower() in ['exit', 'quit', ':q', ':q!', ':wq', ':wq!']:
|
|
259
|
+
self._handle_exit()
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
# Handle different modes
|
|
263
|
+
if self.mode == InputMode.KODING or input_text.startswith('#'):
|
|
264
|
+
await self._handle_koding_input(input_text)
|
|
265
|
+
elif self.mode == InputMode.BASH or input_text.startswith('!'):
|
|
266
|
+
await self._handle_bash_input(input_text)
|
|
267
|
+
else:
|
|
268
|
+
await self._handle_prompt_input(input_text)
|
|
269
|
+
|
|
270
|
+
# Clear input and reset mode
|
|
271
|
+
self.input_value = ""
|
|
272
|
+
self.mode = InputMode.PROMPT
|
|
273
|
+
if self.on_mode_change:
|
|
274
|
+
self.on_mode_change(InputMode.PROMPT)
|
|
275
|
+
|
|
276
|
+
# Update submit count
|
|
277
|
+
self.submit_count += 1
|
|
278
|
+
if self.on_submit_count_change:
|
|
279
|
+
self.on_submit_count_change(lambda x: x + 1)
|
|
280
|
+
|
|
281
|
+
async def _handle_koding_input(self, input_text: str):
|
|
282
|
+
"""Handle koding mode input - equivalent to koding mode handling"""
|
|
283
|
+
logger.info(f"Processing koding input: {input_text}")
|
|
284
|
+
|
|
285
|
+
# Strip # prefix if present
|
|
286
|
+
content = input_text[1:].strip() if input_text.startswith('#') else input_text
|
|
287
|
+
|
|
288
|
+
# Check if this is an action prompt (put, create, generate, etc.)
|
|
289
|
+
if any(word in content.lower() for word in ['put', 'create', 'generate', 'write', 'give', 'provide']):
|
|
290
|
+
# Handle as AI request for AGENTS.md content
|
|
291
|
+
await self._handle_koding_ai_request(content)
|
|
292
|
+
else:
|
|
293
|
+
# Handle as direct note to AGENTS.md
|
|
294
|
+
await self._handle_koding_note(content)
|
|
295
|
+
|
|
296
|
+
# Add to history
|
|
297
|
+
self._add_to_history(f"#{input_text}" if not input_text.startswith('#') else input_text)
|
|
298
|
+
|
|
299
|
+
async def _handle_bash_input(self, input_text: str):
|
|
300
|
+
"""Handle bash mode input - equivalent to bash command processing"""
|
|
301
|
+
logger.info(f"Processing bash input: {input_text}")
|
|
302
|
+
|
|
303
|
+
# Strip ! prefix if present
|
|
304
|
+
command = input_text[1:].strip() if input_text.startswith('!') else input_text
|
|
305
|
+
|
|
306
|
+
try:
|
|
307
|
+
# Execute bash command (simplified version)
|
|
308
|
+
import subprocess
|
|
309
|
+
result = subprocess.run(
|
|
310
|
+
command,
|
|
311
|
+
shell=True,
|
|
312
|
+
capture_output=True,
|
|
313
|
+
text=True,
|
|
314
|
+
timeout=30
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# Create assistant message with result
|
|
318
|
+
if result.returncode == 0:
|
|
319
|
+
response = result.stdout or "Command executed successfully"
|
|
320
|
+
else:
|
|
321
|
+
response = f"Error: {result.stderr}"
|
|
322
|
+
|
|
323
|
+
# This would typically be handled by the parent REPL component
|
|
324
|
+
logger.info(f"Bash command result: {response[:100]}...")
|
|
325
|
+
|
|
326
|
+
except Exception as e:
|
|
327
|
+
logger.error(f"Error executing bash command: {e}")
|
|
328
|
+
|
|
329
|
+
# Add to history
|
|
330
|
+
self._add_to_history(f"!{input_text}" if not input_text.startswith('!') else input_text)
|
|
331
|
+
|
|
332
|
+
async def _handle_prompt_input(self, input_text: str):
|
|
333
|
+
"""Handle regular prompt input - equivalent to normal message processing"""
|
|
334
|
+
logger.info(f"Processing prompt input: {input_text}")
|
|
335
|
+
|
|
336
|
+
if self.set_is_loading:
|
|
337
|
+
self.set_is_loading(True)
|
|
338
|
+
|
|
339
|
+
# Create new abort controller
|
|
340
|
+
if self.set_abort_controller:
|
|
341
|
+
new_controller = asyncio.create_task(asyncio.sleep(0)) # Mock controller
|
|
342
|
+
self.set_abort_controller(new_controller)
|
|
343
|
+
|
|
344
|
+
# Process user input (this would integrate with the actual message processing)
|
|
345
|
+
messages = await self._process_user_input(input_text, self.mode)
|
|
346
|
+
|
|
347
|
+
if messages and self.on_query:
|
|
348
|
+
await self.on_query(messages)
|
|
349
|
+
|
|
350
|
+
# Add to history
|
|
351
|
+
self._add_to_history(input_text)
|
|
352
|
+
|
|
353
|
+
async def _handle_koding_ai_request(self, content: str):
|
|
354
|
+
"""Handle AI request for koding mode"""
|
|
355
|
+
logger.info(f"Processing koding AI request: {content}")
|
|
356
|
+
|
|
357
|
+
# This would integrate with the AI system to generate content for AGENTS.md
|
|
358
|
+
# For now, just log the request
|
|
359
|
+
koding_context = (
|
|
360
|
+
"The user is using Koding mode. Format your response as a comprehensive, "
|
|
361
|
+
"well-structured document suitable for adding to AGENTS.md. Use proper "
|
|
362
|
+
"markdown formatting with headings, lists, code blocks, etc."
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# This would be processed by the main query system
|
|
366
|
+
if self.on_query:
|
|
367
|
+
user_message = Message(
|
|
368
|
+
type=MessageType.USER,
|
|
369
|
+
message=MessageContent(content),
|
|
370
|
+
options={"isKodingRequest": True, "kodingContext": koding_context}
|
|
371
|
+
)
|
|
372
|
+
await self.on_query([user_message])
|
|
373
|
+
|
|
374
|
+
async def _handle_koding_note(self, content: str):
|
|
375
|
+
"""Handle direct note to AGENTS.md"""
|
|
376
|
+
logger.info(f"Adding note to AGENTS.md: {content}")
|
|
377
|
+
|
|
378
|
+
# Interpret and format the note using AI (simplified version)
|
|
379
|
+
try:
|
|
380
|
+
interpreted_content = await self._interpret_hash_command(content)
|
|
381
|
+
self._handle_hash_command(interpreted_content)
|
|
382
|
+
except Exception as e:
|
|
383
|
+
logger.error(f"Error interpreting hash command: {e}")
|
|
384
|
+
# Fallback to simple formatting
|
|
385
|
+
formatted_content = f"# {content}\n\n_Added on {time.strftime('%Y-%m-%d %H:%M:%S')}_"
|
|
386
|
+
self._handle_hash_command(formatted_content)
|
|
387
|
+
|
|
388
|
+
async def _interpret_hash_command(self, content: str) -> str:
|
|
389
|
+
"""Interpret hash command using AI - equivalent to interpretHashCommand"""
|
|
390
|
+
# This would integrate with the AI system
|
|
391
|
+
# For now, return simple formatting
|
|
392
|
+
return f"# {content}\n\n_Added on {time.strftime('%Y-%m-%d %H:%M:%S')}_"
|
|
393
|
+
|
|
394
|
+
def _handle_hash_command(self, content: str):
|
|
395
|
+
"""Handle hash command - equivalent to handleHashCommand"""
|
|
396
|
+
try:
|
|
397
|
+
from pathlib import Path
|
|
398
|
+
agents_md = Path("AGENTS.md")
|
|
399
|
+
|
|
400
|
+
if agents_md.exists():
|
|
401
|
+
with open(agents_md, "a", encoding="utf-8") as f:
|
|
402
|
+
f.write(f"\n\n{content}\n")
|
|
403
|
+
logger.info("Added content to AGENTS.md")
|
|
404
|
+
else:
|
|
405
|
+
logger.warning("AGENTS.md not found")
|
|
406
|
+
except Exception as e:
|
|
407
|
+
logger.error(f"Error writing to AGENTS.md: {e}")
|
|
408
|
+
|
|
409
|
+
async def _process_user_input(self, input_text: str, mode: InputMode) -> List[Message]:
|
|
410
|
+
"""Process user input - equivalent to processUserInput"""
|
|
411
|
+
user_message = Message(
|
|
412
|
+
type=MessageType.USER,
|
|
413
|
+
message=MessageContent(input_text),
|
|
414
|
+
options={"mode": mode.value}
|
|
415
|
+
)
|
|
416
|
+
return [user_message]
|
|
417
|
+
|
|
418
|
+
def _add_to_history(self, input_text: str):
|
|
419
|
+
"""Add input to history - equivalent to addToHistory"""
|
|
420
|
+
logger.info(f"Added to history: {input_text[:50]}...")
|
|
421
|
+
|
|
422
|
+
def _handle_exit(self):
|
|
423
|
+
"""Handle exit command"""
|
|
424
|
+
logger.info("Exit command received")
|
|
425
|
+
# This would typically exit the application
|
|
426
|
+
# For now, just show exit message
|
|
427
|
+
self.exit_message = {"show": True, "key": "Ctrl+C"}
|
|
428
|
+
self.set_timer(3.0, lambda: setattr(self, 'exit_message', {"show": False, "key": ""}))
|
|
429
|
+
|
|
430
|
+
# Key event handling
|
|
431
|
+
def on_key(self, event: Key) -> bool:
|
|
432
|
+
"""Handle special key combinations - equivalent to useInput hook"""
|
|
433
|
+
# Handle mode switching with backspace/delete
|
|
434
|
+
if event.key in ["backspace", "delete"]:
|
|
435
|
+
if self.mode == InputMode.BASH and not self.input_value:
|
|
436
|
+
self.mode = InputMode.PROMPT
|
|
437
|
+
if self.on_mode_change:
|
|
438
|
+
self.on_mode_change(InputMode.PROMPT)
|
|
439
|
+
return True
|
|
440
|
+
elif self.mode == InputMode.KODING and not self.input_value:
|
|
441
|
+
self.mode = InputMode.PROMPT
|
|
442
|
+
if self.on_mode_change:
|
|
443
|
+
self.on_mode_change(InputMode.PROMPT)
|
|
444
|
+
return True
|
|
445
|
+
|
|
446
|
+
# Handle escape key
|
|
447
|
+
if event.key == "escape":
|
|
448
|
+
if not self.input_value and not self.is_loading and len(self.messages) > 0:
|
|
449
|
+
if self.on_show_message_selector:
|
|
450
|
+
self.on_show_message_selector()
|
|
451
|
+
return True
|
|
452
|
+
else:
|
|
453
|
+
self.mode = InputMode.PROMPT
|
|
454
|
+
if self.on_mode_change:
|
|
455
|
+
self.on_mode_change(InputMode.PROMPT)
|
|
456
|
+
return True
|
|
457
|
+
|
|
458
|
+
# Handle Shift+M for model switching
|
|
459
|
+
if event.key == "shift+m":
|
|
460
|
+
self._handle_quick_model_switch()
|
|
461
|
+
return True
|
|
462
|
+
|
|
463
|
+
# Handle Shift+Tab for mode cycling
|
|
464
|
+
if event.key == "shift+tab":
|
|
465
|
+
self._cycle_mode()
|
|
466
|
+
return True
|
|
467
|
+
|
|
468
|
+
return False
|
|
469
|
+
|
|
470
|
+
def _handle_quick_model_switch(self):
|
|
471
|
+
"""Handle quick model switching - equivalent to handleQuickModelSwitch"""
|
|
472
|
+
logger.info("Model switch requested")
|
|
473
|
+
|
|
474
|
+
# This would integrate with the model manager
|
|
475
|
+
# For now, show a mock message
|
|
476
|
+
self.model_switch_message = {
|
|
477
|
+
"show": True,
|
|
478
|
+
"text": "✅ Model switching would happen here"
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
# Clear message after 3 seconds
|
|
482
|
+
self.set_timer(3.0, lambda: setattr(self, 'model_switch_message', {"show": False, "text": ""}))
|
|
483
|
+
|
|
484
|
+
if self.on_model_change:
|
|
485
|
+
self.on_model_change()
|
|
486
|
+
|
|
487
|
+
def _cycle_mode(self):
|
|
488
|
+
"""Cycle through input modes"""
|
|
489
|
+
modes = list(InputMode)
|
|
490
|
+
current_index = modes.index(self.mode)
|
|
491
|
+
new_mode = modes[(current_index + 1) % len(modes)]
|
|
492
|
+
self.mode = new_mode
|
|
493
|
+
|
|
494
|
+
if self.on_mode_change:
|
|
495
|
+
self.on_mode_change(new_mode)
|
|
496
|
+
|
|
497
|
+
logger.info(f"Cycled to mode: {new_mode.value}")
|
|
498
|
+
|
|
499
|
+
# Reactive property watchers
|
|
500
|
+
def watch_mode(self, mode: InputMode):
|
|
501
|
+
"""Watch mode changes and update UI"""
|
|
502
|
+
logger.info(f"Mode changed to: {mode.value}")
|
|
503
|
+
try:
|
|
504
|
+
# Update mode prefix
|
|
505
|
+
prefix_widget = self.query_one("#mode_prefix", expect_type=Static)
|
|
506
|
+
prefix_widget.update(self._get_mode_prefix())
|
|
507
|
+
|
|
508
|
+
# Update input placeholder
|
|
509
|
+
input_widget = self.query_one("#main_input", expect_type=Input)
|
|
510
|
+
input_widget.placeholder = self._get_placeholder()
|
|
511
|
+
|
|
512
|
+
# Update container classes
|
|
513
|
+
container = self.query_one("#input_container")
|
|
514
|
+
container.remove_class("mode-prompt", "mode-bash", "mode-koding")
|
|
515
|
+
container.add_class(f"mode-{mode.value}")
|
|
516
|
+
except:
|
|
517
|
+
pass # Widgets might not be mounted yet
|
|
518
|
+
|
|
519
|
+
def watch_is_loading(self, is_loading: bool):
|
|
520
|
+
"""Watch loading state changes"""
|
|
521
|
+
try:
|
|
522
|
+
input_widget = self.query_one("#main_input", expect_type=Input)
|
|
523
|
+
input_widget.disabled = self.is_disabled or is_loading
|
|
524
|
+
except:
|
|
525
|
+
pass
|
|
526
|
+
|
|
527
|
+
def watch_input_value(self, value: str):
|
|
528
|
+
"""Watch input value changes"""
|
|
529
|
+
try:
|
|
530
|
+
input_widget = self.query_one("#main_input", expect_type=Input)
|
|
531
|
+
if input_widget.value != value:
|
|
532
|
+
input_widget.value = value
|
|
533
|
+
except:
|
|
534
|
+
pass
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Components module for minion_code
|
|
3
|
+
Contains reusable UI components using Textual
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .PromptInput import PromptInput
|
|
7
|
+
from .Message import Message, UserMessage, AssistantMessage, ToolUseMessage
|
|
8
|
+
from .MessageResponse import (
|
|
9
|
+
MessageResponse,
|
|
10
|
+
MessageResponseText,
|
|
11
|
+
MessageResponseStatus,
|
|
12
|
+
MessageResponseProgress,
|
|
13
|
+
MessageResponseTyping,
|
|
14
|
+
MessageResponseWithChildren
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
'PromptInput',
|
|
19
|
+
'Message',
|
|
20
|
+
'UserMessage',
|
|
21
|
+
'AssistantMessage',
|
|
22
|
+
'ToolUseMessage',
|
|
23
|
+
'MessageResponse',
|
|
24
|
+
'MessageResponseText',
|
|
25
|
+
'MessageResponseStatus',
|
|
26
|
+
'MessageResponseProgress',
|
|
27
|
+
'MessageResponseTyping',
|
|
28
|
+
'MessageResponseWithChildren'
|
|
29
|
+
]
|