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,304 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Message Component - Python equivalent of React Message component
|
|
3
|
+
Handles rendering of user and assistant messages with different content types
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from textual.containers import Container, Vertical, Horizontal
|
|
7
|
+
from textual.widgets import Static, RichLog
|
|
8
|
+
from textual.reactive import reactive
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.markdown import Markdown
|
|
12
|
+
from rich.syntax import Syntax
|
|
13
|
+
from typing import List, Dict, Any, Optional, Set
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
# Import shared types
|
|
18
|
+
from ..types import Message as MessageType, MessageContent, InputMode
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Message(Container):
|
|
24
|
+
"""
|
|
25
|
+
Main message component equivalent to React Message
|
|
26
|
+
Handles rendering of both user and assistant messages
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
DEFAULT_CSS = """
|
|
30
|
+
Message {
|
|
31
|
+
width: 80%;
|
|
32
|
+
height: auto;
|
|
33
|
+
margin-bottom: 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.user-message {
|
|
37
|
+
border-left: thick blue;
|
|
38
|
+
padding-left: 1;
|
|
39
|
+
height: auto;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.assistant-message {
|
|
43
|
+
border-left: thick green;
|
|
44
|
+
padding-left: 1;
|
|
45
|
+
height: auto;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.tool-use-message {
|
|
49
|
+
border-left: thick yellow;
|
|
50
|
+
padding-left: 1;
|
|
51
|
+
background: $surface-lighten-1;
|
|
52
|
+
height: auto;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.error-message {
|
|
56
|
+
border-left: thick red;
|
|
57
|
+
padding-left: 1;
|
|
58
|
+
background: $error 10%;
|
|
59
|
+
height: auto;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.message-content {
|
|
63
|
+
width: 100%;
|
|
64
|
+
height: auto;
|
|
65
|
+
padding: 1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.message-meta {
|
|
69
|
+
color: $text-muted;
|
|
70
|
+
text-style: dim;
|
|
71
|
+
height: 1;
|
|
72
|
+
}
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self,
|
|
76
|
+
message: MessageType,
|
|
77
|
+
messages: List[MessageType] = None,
|
|
78
|
+
add_margin: bool = True,
|
|
79
|
+
tools: List[Any] = None,
|
|
80
|
+
verbose: bool = False,
|
|
81
|
+
debug: bool = False,
|
|
82
|
+
errored_tool_use_ids: Set[str] = None,
|
|
83
|
+
in_progress_tool_use_ids: Set[str] = None,
|
|
84
|
+
unresolved_tool_use_ids: Set[str] = None,
|
|
85
|
+
should_animate: bool = False,
|
|
86
|
+
should_show_dot: bool = False,
|
|
87
|
+
width: Optional[int] = None,
|
|
88
|
+
**kwargs):
|
|
89
|
+
super().__init__(**kwargs)
|
|
90
|
+
|
|
91
|
+
self.message = message
|
|
92
|
+
self.messages = messages or []
|
|
93
|
+
self.add_margin = add_margin
|
|
94
|
+
self.tools = tools or []
|
|
95
|
+
self.verbose = verbose
|
|
96
|
+
self.debug = debug
|
|
97
|
+
self.errored_tool_use_ids = errored_tool_use_ids or set()
|
|
98
|
+
self.in_progress_tool_use_ids = in_progress_tool_use_ids or set()
|
|
99
|
+
self.unresolved_tool_use_ids = unresolved_tool_use_ids or set()
|
|
100
|
+
self.should_animate = should_animate
|
|
101
|
+
self.should_show_dot = should_show_dot
|
|
102
|
+
self.width = width
|
|
103
|
+
|
|
104
|
+
def compose(self):
|
|
105
|
+
"""Compose the message interface"""
|
|
106
|
+
if self.message.type.value == "assistant":
|
|
107
|
+
yield from self._render_assistant_message()
|
|
108
|
+
else:
|
|
109
|
+
yield from self._render_user_message()
|
|
110
|
+
|
|
111
|
+
def _render_user_message(self):
|
|
112
|
+
"""Render user message - equivalent to UserMessage component"""
|
|
113
|
+
with Vertical(classes="user-message"):
|
|
114
|
+
# Message metadata
|
|
115
|
+
if self.verbose or self.debug:
|
|
116
|
+
yield Static(
|
|
117
|
+
f"User • {self._format_timestamp()}",
|
|
118
|
+
classes="message-meta"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Message content
|
|
122
|
+
content = self.message.message.content
|
|
123
|
+
if isinstance(content, str):
|
|
124
|
+
yield from self._render_text_content(content)
|
|
125
|
+
elif isinstance(content, list):
|
|
126
|
+
for item in content:
|
|
127
|
+
yield from self._render_content_block(item)
|
|
128
|
+
|
|
129
|
+
def _render_assistant_message(self):
|
|
130
|
+
"""Render assistant message - equivalent to AssistantMessage component"""
|
|
131
|
+
with Vertical(classes="assistant-message"):
|
|
132
|
+
# Message metadata
|
|
133
|
+
if self.verbose or self.debug:
|
|
134
|
+
meta_text = f"Assistant • {self._format_timestamp()}"
|
|
135
|
+
if hasattr(self.message, 'cost_usd') and self.message.cost_usd:
|
|
136
|
+
meta_text += f" • ${self.message.cost_usd:.4f}"
|
|
137
|
+
if hasattr(self.message, 'duration_ms') and self.message.duration_ms:
|
|
138
|
+
meta_text += f" • {self.message.duration_ms}ms"
|
|
139
|
+
|
|
140
|
+
yield Static(meta_text, classes="message-meta")
|
|
141
|
+
|
|
142
|
+
# Message content
|
|
143
|
+
content = self.message.message.content
|
|
144
|
+
if isinstance(content, str):
|
|
145
|
+
yield from self._render_text_content(content)
|
|
146
|
+
elif isinstance(content, list):
|
|
147
|
+
for item in content:
|
|
148
|
+
yield from self._render_content_block(item)
|
|
149
|
+
|
|
150
|
+
def _render_content_block(self, block: Dict[str, Any]):
|
|
151
|
+
"""Render individual content blocks based on type"""
|
|
152
|
+
block_type = block.get('type', 'text')
|
|
153
|
+
|
|
154
|
+
if block_type == 'text':
|
|
155
|
+
yield from self._render_text_content(block.get('text', ''))
|
|
156
|
+
elif block_type == 'tool_use':
|
|
157
|
+
yield from self._render_tool_use_block(block)
|
|
158
|
+
elif block_type == 'tool_result':
|
|
159
|
+
yield from self._render_tool_result_block(block)
|
|
160
|
+
elif block_type == 'thinking':
|
|
161
|
+
yield from self._render_thinking_block(block)
|
|
162
|
+
elif block_type == 'redacted_thinking':
|
|
163
|
+
yield from self._render_redacted_thinking_block()
|
|
164
|
+
else:
|
|
165
|
+
# Unknown block type
|
|
166
|
+
yield Static(f"[Unknown content type: {block_type}]", classes="error-message")
|
|
167
|
+
|
|
168
|
+
def _render_text_content(self, text: str):
|
|
169
|
+
"""Render text content with markdown support"""
|
|
170
|
+
if not text.strip():
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
# For now, just render as plain text to avoid compose-time issues
|
|
175
|
+
# TODO: Implement proper markdown rendering with RichLog after mount
|
|
176
|
+
yield Static(text, classes="message-content")
|
|
177
|
+
except Exception as e:
|
|
178
|
+
logger.warning(f"Error rendering text: {e}")
|
|
179
|
+
yield Static(text, classes="message-content")
|
|
180
|
+
|
|
181
|
+
def _render_tool_use_block(self, block: Dict[str, Any]):
|
|
182
|
+
"""Render tool use block - equivalent to AssistantToolUseMessage"""
|
|
183
|
+
tool_name = block.get('name', 'unknown')
|
|
184
|
+
tool_id = block.get('id', '')
|
|
185
|
+
parameters = block.get('input', {})
|
|
186
|
+
|
|
187
|
+
# Determine status
|
|
188
|
+
status = "completed"
|
|
189
|
+
if tool_id in self.in_progress_tool_use_ids:
|
|
190
|
+
status = "in_progress"
|
|
191
|
+
elif tool_id in self.errored_tool_use_ids:
|
|
192
|
+
status = "error"
|
|
193
|
+
elif tool_id in self.unresolved_tool_use_ids:
|
|
194
|
+
status = "unresolved"
|
|
195
|
+
|
|
196
|
+
with Vertical(classes="tool-use-message"):
|
|
197
|
+
# Tool header
|
|
198
|
+
status_icon = {
|
|
199
|
+
"completed": "✅",
|
|
200
|
+
"in_progress": "⏳",
|
|
201
|
+
"error": "❌",
|
|
202
|
+
"unresolved": "⏸️"
|
|
203
|
+
}.get(status, "🔧")
|
|
204
|
+
|
|
205
|
+
yield Static(
|
|
206
|
+
f"{status_icon} Tool: {tool_name}",
|
|
207
|
+
classes="message-meta"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Tool parameters (if verbose)
|
|
211
|
+
if self.verbose and parameters:
|
|
212
|
+
try:
|
|
213
|
+
params_json = json.dumps(parameters, indent=2)
|
|
214
|
+
# For now, render as plain text to avoid compose-time issues
|
|
215
|
+
yield Static(params_json, classes="message-content")
|
|
216
|
+
except Exception:
|
|
217
|
+
yield Static(str(parameters), classes="message-content")
|
|
218
|
+
|
|
219
|
+
def _render_tool_result_block(self, block: Dict[str, Any]):
|
|
220
|
+
"""Render tool result block - equivalent to UserToolResultMessage"""
|
|
221
|
+
tool_use_id = block.get('tool_use_id', '')
|
|
222
|
+
content = block.get('content', '')
|
|
223
|
+
is_error = block.get('is_error', False)
|
|
224
|
+
|
|
225
|
+
classes = "error-message" if is_error else "message-content"
|
|
226
|
+
|
|
227
|
+
with Vertical():
|
|
228
|
+
# Result header
|
|
229
|
+
icon = "❌" if is_error else "📤"
|
|
230
|
+
yield Static(
|
|
231
|
+
f"{icon} Tool Result",
|
|
232
|
+
classes="message-meta"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Result content
|
|
236
|
+
if isinstance(content, str):
|
|
237
|
+
yield Static(content, classes=classes)
|
|
238
|
+
elif isinstance(content, list):
|
|
239
|
+
for item in content:
|
|
240
|
+
if isinstance(item, dict) and item.get('type') == 'text':
|
|
241
|
+
yield Static(item.get('text', ''), classes=classes)
|
|
242
|
+
else:
|
|
243
|
+
yield Static(str(item), classes=classes)
|
|
244
|
+
|
|
245
|
+
def _render_thinking_block(self, block: Dict[str, Any]):
|
|
246
|
+
"""Render thinking block - equivalent to AssistantThinkingMessage"""
|
|
247
|
+
if not self.debug:
|
|
248
|
+
return # Only show in debug mode
|
|
249
|
+
|
|
250
|
+
thinking_content = block.get('content', '')
|
|
251
|
+
|
|
252
|
+
with Vertical():
|
|
253
|
+
yield Static("🤔 Thinking...", classes="message-meta")
|
|
254
|
+
yield Static(thinking_content, classes="message-content")
|
|
255
|
+
|
|
256
|
+
def _render_redacted_thinking_block(self):
|
|
257
|
+
"""Render redacted thinking block"""
|
|
258
|
+
if not self.debug:
|
|
259
|
+
return
|
|
260
|
+
|
|
261
|
+
yield Static("🤔 [Thinking content redacted]", classes="message-meta")
|
|
262
|
+
|
|
263
|
+
def _format_timestamp(self) -> str:
|
|
264
|
+
"""Format message timestamp"""
|
|
265
|
+
import datetime
|
|
266
|
+
dt = datetime.datetime.fromtimestamp(self.message.timestamp)
|
|
267
|
+
return dt.strftime("%H:%M:%S")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class UserMessage(Message):
|
|
271
|
+
"""Specialized component for user messages"""
|
|
272
|
+
|
|
273
|
+
def __init__(self, message: MessageType, **kwargs):
|
|
274
|
+
super().__init__(message, **kwargs)
|
|
275
|
+
|
|
276
|
+
def compose(self):
|
|
277
|
+
"""Compose user message with specific styling"""
|
|
278
|
+
yield from self._render_user_message()
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class AssistantMessage(Message):
|
|
282
|
+
"""Specialized component for assistant messages"""
|
|
283
|
+
|
|
284
|
+
def __init__(self, message: MessageType, **kwargs):
|
|
285
|
+
super().__init__(message, **kwargs)
|
|
286
|
+
|
|
287
|
+
def compose(self):
|
|
288
|
+
"""Compose assistant message with specific styling"""
|
|
289
|
+
yield from self._render_assistant_message()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class ToolUseMessage(Message):
|
|
293
|
+
"""Specialized component for tool use messages"""
|
|
294
|
+
|
|
295
|
+
def __init__(self, message: MessageType, **kwargs):
|
|
296
|
+
super().__init__(message, **kwargs)
|
|
297
|
+
|
|
298
|
+
def compose(self):
|
|
299
|
+
"""Compose tool use message with specific styling"""
|
|
300
|
+
content = self.message.message.content
|
|
301
|
+
if isinstance(content, list):
|
|
302
|
+
for item in content:
|
|
303
|
+
if item.get('type') == 'tool_use':
|
|
304
|
+
yield from self._render_tool_use_block(item)
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MessageResponse Component - Python equivalent of React MessageResponse component
|
|
3
|
+
Provides visual indentation for tool execution progress messages
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from textual.containers import Container, Horizontal
|
|
7
|
+
from textual.widgets import Static
|
|
8
|
+
from textual.widget import Widget
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
from typing import Any, Optional, List, Union
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MessageResponse(Container):
|
|
14
|
+
"""
|
|
15
|
+
MessageResponse component equivalent to React MessageResponse
|
|
16
|
+
Provides visual indentation with "⎿" indicator for children widgets
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
# With direct content
|
|
20
|
+
response = MessageResponse(content="Operation completed")
|
|
21
|
+
|
|
22
|
+
# With child widgets (like React children)
|
|
23
|
+
response = MessageResponse()
|
|
24
|
+
response.mount(Message(...)) # Mount child widgets
|
|
25
|
+
|
|
26
|
+
# Or pass children during initialization
|
|
27
|
+
response = MessageResponse(children=[message_widget, status_widget])
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
DEFAULT_CSS = """
|
|
31
|
+
MessageResponse {
|
|
32
|
+
height: auto;
|
|
33
|
+
width: 100%;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.message-response-container {
|
|
37
|
+
height: auto;
|
|
38
|
+
width: 100%;
|
|
39
|
+
margin: 0;
|
|
40
|
+
padding: 0;
|
|
41
|
+
layers: first second;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.response-indicator {
|
|
45
|
+
height: 1;
|
|
46
|
+
width: auto;
|
|
47
|
+
color: $text-muted;
|
|
48
|
+
text-style: dim;
|
|
49
|
+
margin: 0;
|
|
50
|
+
padding: 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.response-content {
|
|
54
|
+
width: 100%;
|
|
55
|
+
height: auto;
|
|
56
|
+
margin-left: 4;
|
|
57
|
+
margin-top: 0;
|
|
58
|
+
margin-bottom: 0;
|
|
59
|
+
padding: 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.response-content > * {
|
|
63
|
+
margin: 0;
|
|
64
|
+
padding: 0;
|
|
65
|
+
height: auto;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* 移除Vertical容器的默认间距 */
|
|
69
|
+
.response-content Vertical {
|
|
70
|
+
margin: 0;
|
|
71
|
+
padding: 0;
|
|
72
|
+
}
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self,
|
|
76
|
+
children: Optional[Union[Widget, List[Widget]]] = None,
|
|
77
|
+
content: Optional[str] = None,
|
|
78
|
+
**kwargs):
|
|
79
|
+
super().__init__(**kwargs)
|
|
80
|
+
self.children_widgets = []
|
|
81
|
+
self.content = content
|
|
82
|
+
|
|
83
|
+
# Handle children parameter
|
|
84
|
+
if children:
|
|
85
|
+
if isinstance(children, list):
|
|
86
|
+
self.children_widgets = children
|
|
87
|
+
else:
|
|
88
|
+
self.children_widgets = [children]
|
|
89
|
+
|
|
90
|
+
def compose(self):
|
|
91
|
+
"""Compose the MessageResponse interface"""
|
|
92
|
+
from textual.containers import Vertical
|
|
93
|
+
|
|
94
|
+
# 使用Vertical多层布局
|
|
95
|
+
with Horizontal(classes="message-response-container"):
|
|
96
|
+
#yield (Static(" ⎿", classes="response-indicator"))
|
|
97
|
+
if self.children_widgets:
|
|
98
|
+
|
|
99
|
+
for child in self.children_widgets:
|
|
100
|
+
yield child
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# def on_mount(self):
|
|
104
|
+
# print("mounting")
|
|
105
|
+
# pass
|
|
106
|
+
def mount_child(self, widget: Widget):
|
|
107
|
+
"""
|
|
108
|
+
Mount a child widget to the content area
|
|
109
|
+
Equivalent to React's children mounting
|
|
110
|
+
"""
|
|
111
|
+
content_area = self.query_one("#content-area", Container)
|
|
112
|
+
content_area.mount(widget)
|
|
113
|
+
|
|
114
|
+
def mount_children(self, widgets: List[Widget]):
|
|
115
|
+
"""Mount multiple child widgets"""
|
|
116
|
+
for widget in widgets:
|
|
117
|
+
self.mount_child(widget)
|
|
118
|
+
|
|
119
|
+
def clear_children(self):
|
|
120
|
+
"""Clear all child widgets from content area"""
|
|
121
|
+
content_area = self.query_one("#content-area", Container)
|
|
122
|
+
for child in list(content_area.children):
|
|
123
|
+
child.remove()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class MessageResponseText(MessageResponse):
|
|
127
|
+
"""Specialized MessageResponse for text content"""
|
|
128
|
+
|
|
129
|
+
def __init__(self, text: str, **kwargs):
|
|
130
|
+
super().__init__(content=text, **kwargs)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class MessageResponseStatus(MessageResponse):
|
|
134
|
+
"""Specialized MessageResponse for status messages"""
|
|
135
|
+
|
|
136
|
+
def __init__(self, status: str, message: str = "", **kwargs):
|
|
137
|
+
status_icons = {
|
|
138
|
+
"loading": "⏳",
|
|
139
|
+
"success": "✅",
|
|
140
|
+
"error": "❌",
|
|
141
|
+
"warning": "⚠️",
|
|
142
|
+
"info": "ℹ️",
|
|
143
|
+
"thinking": "🤔"
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
icon = status_icons.get(status, "•")
|
|
147
|
+
content = f"{icon} {message}" if message else icon
|
|
148
|
+
|
|
149
|
+
super().__init__(content=content, **kwargs)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class MessageResponseProgress(MessageResponse):
|
|
153
|
+
"""Specialized MessageResponse for progress indicators"""
|
|
154
|
+
|
|
155
|
+
def __init__(self, current: int, total: int, message: str = "", **kwargs):
|
|
156
|
+
progress_text = f"[{current}/{total}]"
|
|
157
|
+
if message:
|
|
158
|
+
progress_text += f" {message}"
|
|
159
|
+
|
|
160
|
+
super().__init__(content=progress_text, **kwargs)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class MessageResponseTyping(MessageResponse):
|
|
164
|
+
"""Specialized MessageResponse for typing indicators"""
|
|
165
|
+
|
|
166
|
+
def __init__(self, **kwargs):
|
|
167
|
+
super().__init__(content="typing...", **kwargs)
|
|
168
|
+
|
|
169
|
+
def on_mount(self):
|
|
170
|
+
"""Start typing animation when mounted"""
|
|
171
|
+
self._animate_typing()
|
|
172
|
+
|
|
173
|
+
def _animate_typing(self):
|
|
174
|
+
"""Simple typing animation"""
|
|
175
|
+
# This could be enhanced with actual animation
|
|
176
|
+
# For now, just show static typing indicator
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class MessageResponseWithChildren(MessageResponse):
|
|
181
|
+
"""
|
|
182
|
+
Specialized MessageResponse that demonstrates children usage
|
|
183
|
+
Equivalent to React's <MessageResponse><Message /></MessageResponse>
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
def __init__(self, message_widget: Widget, **kwargs):
|
|
187
|
+
# Pass the message widget as a child
|
|
188
|
+
super().__init__(children=[message_widget], **kwargs)
|