agentcrew-ai 0.8.12__py3-none-any.whl → 0.9.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.
- AgentCrew/__init__.py +1 -1
- AgentCrew/app.py +34 -633
- AgentCrew/main.py +55 -3
- AgentCrew/main_docker.py +1 -30
- AgentCrew/modules/agents/local_agent.py +26 -1
- AgentCrew/modules/chat/message/command_processor.py +33 -8
- AgentCrew/modules/chat/message/handler.py +5 -1
- AgentCrew/modules/code_analysis/__init__.py +8 -0
- AgentCrew/modules/code_analysis/parsers/__init__.py +67 -0
- AgentCrew/modules/code_analysis/parsers/base.py +93 -0
- AgentCrew/modules/code_analysis/parsers/cpp_parser.py +127 -0
- AgentCrew/modules/code_analysis/parsers/csharp_parser.py +162 -0
- AgentCrew/modules/code_analysis/parsers/generic_parser.py +63 -0
- AgentCrew/modules/code_analysis/parsers/go_parser.py +154 -0
- AgentCrew/modules/code_analysis/parsers/java_parser.py +103 -0
- AgentCrew/modules/code_analysis/parsers/javascript_parser.py +268 -0
- AgentCrew/modules/code_analysis/parsers/kotlin_parser.py +84 -0
- AgentCrew/modules/code_analysis/parsers/php_parser.py +107 -0
- AgentCrew/modules/code_analysis/parsers/python_parser.py +60 -0
- AgentCrew/modules/code_analysis/parsers/ruby_parser.py +46 -0
- AgentCrew/modules/code_analysis/parsers/rust_parser.py +72 -0
- AgentCrew/modules/code_analysis/service.py +231 -897
- AgentCrew/modules/command_execution/constants.py +2 -2
- AgentCrew/modules/console/completers.py +1 -1
- AgentCrew/modules/console/confirmation_handler.py +4 -4
- AgentCrew/modules/console/console_ui.py +17 -3
- AgentCrew/modules/console/conversation_browser/__init__.py +9 -0
- AgentCrew/modules/console/conversation_browser/browser.py +84 -0
- AgentCrew/modules/console/conversation_browser/browser_input_handler.py +279 -0
- AgentCrew/modules/console/conversation_browser/browser_ui.py +643 -0
- AgentCrew/modules/console/conversation_handler.py +34 -1
- AgentCrew/modules/console/diff_display.py +22 -51
- AgentCrew/modules/console/display_handlers.py +142 -26
- AgentCrew/modules/console/tool_display.py +4 -6
- AgentCrew/modules/file_editing/service.py +8 -8
- AgentCrew/modules/file_editing/tool.py +65 -67
- AgentCrew/modules/gui/components/command_handler.py +137 -29
- AgentCrew/modules/gui/components/tool_handlers.py +0 -2
- AgentCrew/modules/gui/themes/README.md +30 -14
- AgentCrew/modules/gui/themes/__init__.py +2 -1
- AgentCrew/modules/gui/themes/atom_light.yaml +1287 -0
- AgentCrew/modules/gui/themes/catppuccin.yaml +1276 -0
- AgentCrew/modules/gui/themes/dracula.yaml +1262 -0
- AgentCrew/modules/gui/themes/nord.yaml +1267 -0
- AgentCrew/modules/gui/themes/saigontech.yaml +1268 -0
- AgentCrew/modules/gui/themes/style_provider.py +76 -264
- AgentCrew/modules/gui/themes/theme_loader.py +379 -0
- AgentCrew/modules/gui/themes/unicorn.yaml +1276 -0
- AgentCrew/modules/gui/widgets/configs/global_settings.py +3 -4
- AgentCrew/modules/gui/widgets/diff_widget.py +30 -61
- AgentCrew/modules/llm/constants.py +18 -9
- AgentCrew/modules/memory/context_persistent.py +1 -0
- AgentCrew/modules/memory/tool.py +1 -1
- AgentCrew/setup.py +470 -0
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/METADATA +1 -1
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/RECORD +60 -41
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/WHEEL +1 -1
- AgentCrew/modules/gui/themes/atom_light.py +0 -1365
- AgentCrew/modules/gui/themes/catppuccin.py +0 -1404
- AgentCrew/modules/gui/themes/dracula.py +0 -1372
- AgentCrew/modules/gui/themes/nord.py +0 -1365
- AgentCrew/modules/gui/themes/saigontech.py +0 -1359
- AgentCrew/modules/gui/themes/unicorn.py +0 -1372
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/entry_points.txt +0 -0
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/top_level.txt +0 -0
|
@@ -10,7 +10,7 @@ definitions for the CommandExecutionService.
|
|
|
10
10
|
# ==============================================================================
|
|
11
11
|
|
|
12
12
|
# Maximum number of commands that can run concurrently (application-wide)
|
|
13
|
-
MAX_CONCURRENT_COMMANDS =
|
|
13
|
+
MAX_CONCURRENT_COMMANDS = 10
|
|
14
14
|
|
|
15
15
|
# Maximum lifetime for a single command execution (seconds)
|
|
16
16
|
MAX_COMMAND_LIFETIME = 600
|
|
@@ -19,7 +19,7 @@ MAX_COMMAND_LIFETIME = 600
|
|
|
19
19
|
MAX_OUTPUT_LINES = 300
|
|
20
20
|
|
|
21
21
|
# Maximum number of commands allowed per minute (application-wide rate limit)
|
|
22
|
-
MAX_COMMANDS_PER_MINUTE =
|
|
22
|
+
MAX_COMMANDS_PER_MINUTE = 50
|
|
23
23
|
|
|
24
24
|
# Default timeout for command execution (seconds)
|
|
25
25
|
DEFAULT_TIMEOUT = 5
|
|
@@ -174,7 +174,7 @@ class ChatCompleter(Completer):
|
|
|
174
174
|
),
|
|
175
175
|
(
|
|
176
176
|
"/debug",
|
|
177
|
-
"Show debug
|
|
177
|
+
"Show debug info (usage: /debug [agent|chat])",
|
|
178
178
|
),
|
|
179
179
|
("/think", "Set thinking budget (usage: /think <budget>)"),
|
|
180
180
|
(
|
|
@@ -125,7 +125,7 @@ class ConfirmationHandler:
|
|
|
125
125
|
|
|
126
126
|
self.input_handler._start_input_thread()
|
|
127
127
|
|
|
128
|
-
def _display_write_or_edit_file_diff(self, tool_use, file_path,
|
|
128
|
+
def _display_write_or_edit_file_diff(self, tool_use, file_path, blocks):
|
|
129
129
|
"""Display split diff view for write_or_edit_file tool."""
|
|
130
130
|
header = Text("📝 File Edit ", style=RICH_STYLE_YELLOW)
|
|
131
131
|
header.append(file_path, style=RICH_STYLE_BLUE_BOLD)
|
|
@@ -135,15 +135,15 @@ class ConfirmationHandler:
|
|
|
135
135
|
Panel(header, box=HORIZONTALS, border_style=RICH_STYLE_YELLOW)
|
|
136
136
|
)
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
parsed_blocks = DiffDisplay.parse_search_replace_blocks(blocks)
|
|
139
139
|
|
|
140
|
-
if not
|
|
140
|
+
if not parsed_blocks:
|
|
141
141
|
self.console.print(
|
|
142
142
|
Text("No valid search/replace blocks found", style=RICH_STYLE_RED)
|
|
143
143
|
)
|
|
144
144
|
return
|
|
145
145
|
|
|
146
|
-
for block in
|
|
146
|
+
for block in parsed_blocks:
|
|
147
147
|
diff_table = DiffDisplay.create_split_diff_table(
|
|
148
148
|
block["search"], block["replace"], max_width=self.console.width - 4
|
|
149
149
|
)
|
|
@@ -215,8 +215,10 @@ class ConsoleUI(Observer):
|
|
|
215
215
|
)
|
|
216
216
|
elif event == "conversations_listed":
|
|
217
217
|
self.display_handlers.display_conversations(
|
|
218
|
-
data
|
|
219
|
-
|
|
218
|
+
data,
|
|
219
|
+
get_history_callback=self.conversation_handler.get_conversation_history,
|
|
220
|
+
delete_callback=self.conversation_handler.delete_conversations,
|
|
221
|
+
)
|
|
220
222
|
self.conversation_handler.update_cached_conversations(data)
|
|
221
223
|
elif event == "conversation_loaded":
|
|
222
224
|
loaded_text = Text("Loaded conversation: ", style=RICH_STYLE_YELLOW)
|
|
@@ -454,7 +456,19 @@ class ConsoleUI(Observer):
|
|
|
454
456
|
self.conversation_handler.update_cached_conversations(
|
|
455
457
|
conversations
|
|
456
458
|
)
|
|
457
|
-
self.
|
|
459
|
+
self.input_handler._stop_input_thread()
|
|
460
|
+
try:
|
|
461
|
+
selected_id = self.display_handlers.display_conversations(
|
|
462
|
+
conversations,
|
|
463
|
+
get_history_callback=self.conversation_handler.get_conversation_history,
|
|
464
|
+
delete_callback=self.conversation_handler.delete_conversations,
|
|
465
|
+
)
|
|
466
|
+
if selected_id:
|
|
467
|
+
self.conversation_handler.handle_load_conversation(
|
|
468
|
+
selected_id, self.message_handler
|
|
469
|
+
)
|
|
470
|
+
finally:
|
|
471
|
+
self.input_handler._start_input_thread()
|
|
458
472
|
continue
|
|
459
473
|
|
|
460
474
|
# Handle load command directly
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from .browser import ConversationBrowser
|
|
2
|
+
from .browser_ui import ConversationBrowserUI
|
|
3
|
+
from .browser_input_handler import ConversationBrowserInputHandler
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"ConversationBrowser",
|
|
7
|
+
"ConversationBrowserUI",
|
|
8
|
+
"ConversationBrowserInputHandler",
|
|
9
|
+
]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Conversation browser with split-panel interface.
|
|
2
|
+
|
|
3
|
+
Provides Rich-based UI for listing and loading conversations with preview.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import List, Dict, Any, Optional, Callable
|
|
9
|
+
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.text import Text
|
|
12
|
+
|
|
13
|
+
from ..constants import RICH_STYLE_YELLOW
|
|
14
|
+
from .browser_ui import ConversationBrowserUI
|
|
15
|
+
from .browser_input_handler import ConversationBrowserInputHandler
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConversationBrowser:
|
|
19
|
+
"""Interactive conversation browser with split-panel layout.
|
|
20
|
+
|
|
21
|
+
This class orchestrates the UI rendering and input handling components
|
|
22
|
+
to provide a complete interactive conversation browsing experience.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
console: Console,
|
|
28
|
+
get_conversation_history: Optional[
|
|
29
|
+
Callable[[str], List[Dict[str, Any]]]
|
|
30
|
+
] = None,
|
|
31
|
+
on_delete: Optional[Callable[[List[str]], bool]] = None,
|
|
32
|
+
):
|
|
33
|
+
"""Initialize the conversation browser.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
console: Rich console for rendering
|
|
37
|
+
get_conversation_history: Optional callback to fetch full conversation history
|
|
38
|
+
on_delete: Optional callback to delete conversations by IDs. Returns True if successful.
|
|
39
|
+
"""
|
|
40
|
+
self._console = console
|
|
41
|
+
self._ui = ConversationBrowserUI(
|
|
42
|
+
console=console,
|
|
43
|
+
get_conversation_history=get_conversation_history,
|
|
44
|
+
)
|
|
45
|
+
self._input_handler = ConversationBrowserInputHandler(
|
|
46
|
+
ui=self._ui,
|
|
47
|
+
on_delete=on_delete,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def set_conversations(self, conversations: List[Dict[str, Any]]):
|
|
51
|
+
"""Set the conversations list to browse."""
|
|
52
|
+
self._ui.set_conversations(conversations)
|
|
53
|
+
|
|
54
|
+
def get_selected_conversation_id(self) -> Optional[str]:
|
|
55
|
+
"""Get the ID of the currently selected conversation."""
|
|
56
|
+
return self._ui.get_selected_conversation_id()
|
|
57
|
+
|
|
58
|
+
def get_selected_conversation_index(self) -> int:
|
|
59
|
+
"""Get the 1-based index of the currently selected conversation."""
|
|
60
|
+
return self._ui.get_selected_conversation_index()
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def ui(self) -> ConversationBrowserUI:
|
|
64
|
+
"""Access the UI component directly."""
|
|
65
|
+
return self._ui
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def input_handler(self) -> ConversationBrowserInputHandler:
|
|
69
|
+
"""Access the input handler component directly."""
|
|
70
|
+
return self._input_handler
|
|
71
|
+
|
|
72
|
+
def show(self) -> Optional[str]:
|
|
73
|
+
"""Show the interactive conversation browser.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
The ID of the selected conversation, or None if cancelled.
|
|
77
|
+
"""
|
|
78
|
+
if not self._ui.conversations:
|
|
79
|
+
self._console.print(
|
|
80
|
+
Text("No conversations available.", style=RICH_STYLE_YELLOW)
|
|
81
|
+
)
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
return self._input_handler.run()
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""Conversation browser input handling."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Optional, Callable, List
|
|
6
|
+
|
|
7
|
+
from prompt_toolkit import PromptSession
|
|
8
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
9
|
+
from prompt_toolkit.keys import Keys
|
|
10
|
+
|
|
11
|
+
from loguru import logger
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .browser_ui import ConversationBrowserUI
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConversationBrowserInputHandler:
|
|
18
|
+
"""Handles keyboard input for the conversation browser."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
ui: ConversationBrowserUI,
|
|
23
|
+
on_select: Optional[Callable[[str], None]] = None,
|
|
24
|
+
on_cancel: Optional[Callable[[], None]] = None,
|
|
25
|
+
on_delete: Optional[Callable[[List[str]], bool]] = None,
|
|
26
|
+
):
|
|
27
|
+
self._ui = ui
|
|
28
|
+
self._running = False
|
|
29
|
+
self._g_pressed = False
|
|
30
|
+
self._d_pressed = False
|
|
31
|
+
self._selected_id: Optional[str] = None
|
|
32
|
+
self._on_select = on_select
|
|
33
|
+
self._on_cancel = on_cancel
|
|
34
|
+
self._on_delete = on_delete
|
|
35
|
+
|
|
36
|
+
def _create_key_bindings(self) -> KeyBindings:
|
|
37
|
+
"""Create and configure key bindings for the browser."""
|
|
38
|
+
kb = KeyBindings()
|
|
39
|
+
|
|
40
|
+
@kb.add(Keys.Up)
|
|
41
|
+
@kb.add("k")
|
|
42
|
+
def _(event):
|
|
43
|
+
if self._ui.search_mode:
|
|
44
|
+
return
|
|
45
|
+
self._g_pressed = False
|
|
46
|
+
self._d_pressed = False
|
|
47
|
+
if self._ui.handle_navigation("up"):
|
|
48
|
+
self._ui.render()
|
|
49
|
+
|
|
50
|
+
@kb.add(Keys.Down)
|
|
51
|
+
@kb.add("j")
|
|
52
|
+
def _(event):
|
|
53
|
+
if self._ui.search_mode:
|
|
54
|
+
return
|
|
55
|
+
self._g_pressed = False
|
|
56
|
+
self._d_pressed = False
|
|
57
|
+
if self._ui.handle_navigation("down"):
|
|
58
|
+
self._ui.render()
|
|
59
|
+
|
|
60
|
+
@kb.add(Keys.ControlP)
|
|
61
|
+
def _(event):
|
|
62
|
+
if self._ui.search_mode:
|
|
63
|
+
return
|
|
64
|
+
self._g_pressed = False
|
|
65
|
+
self._d_pressed = False
|
|
66
|
+
if self._ui.handle_navigation("up"):
|
|
67
|
+
self._ui.render()
|
|
68
|
+
|
|
69
|
+
@kb.add(Keys.ControlN)
|
|
70
|
+
def _(event):
|
|
71
|
+
if self._ui.search_mode:
|
|
72
|
+
return
|
|
73
|
+
self._g_pressed = False
|
|
74
|
+
self._d_pressed = False
|
|
75
|
+
if self._ui.handle_navigation("down"):
|
|
76
|
+
self._ui.render()
|
|
77
|
+
|
|
78
|
+
@kb.add("g")
|
|
79
|
+
def _(event):
|
|
80
|
+
if self._ui.search_mode:
|
|
81
|
+
self._ui.append_search_char("g")
|
|
82
|
+
self._ui.render()
|
|
83
|
+
return
|
|
84
|
+
self._d_pressed = False
|
|
85
|
+
if self._g_pressed:
|
|
86
|
+
self._g_pressed = False
|
|
87
|
+
if self._ui.handle_navigation("top"):
|
|
88
|
+
self._ui.render()
|
|
89
|
+
else:
|
|
90
|
+
self._g_pressed = True
|
|
91
|
+
|
|
92
|
+
@kb.add("G")
|
|
93
|
+
def _(event):
|
|
94
|
+
if self._ui.search_mode:
|
|
95
|
+
self._ui.append_search_char("G")
|
|
96
|
+
self._ui.render()
|
|
97
|
+
return
|
|
98
|
+
self._g_pressed = False
|
|
99
|
+
self._d_pressed = False
|
|
100
|
+
if self._ui.handle_navigation("bottom"):
|
|
101
|
+
self._ui.render()
|
|
102
|
+
|
|
103
|
+
@kb.add(Keys.ControlU)
|
|
104
|
+
@kb.add(Keys.PageUp)
|
|
105
|
+
def _(event):
|
|
106
|
+
if self._ui.search_mode:
|
|
107
|
+
return
|
|
108
|
+
self._g_pressed = False
|
|
109
|
+
self._d_pressed = False
|
|
110
|
+
if self._ui.handle_navigation("page_up"):
|
|
111
|
+
self._ui.render()
|
|
112
|
+
|
|
113
|
+
@kb.add(Keys.ControlD)
|
|
114
|
+
@kb.add(Keys.PageDown)
|
|
115
|
+
def _(event):
|
|
116
|
+
if self._ui.search_mode:
|
|
117
|
+
return
|
|
118
|
+
self._g_pressed = False
|
|
119
|
+
self._d_pressed = False
|
|
120
|
+
if self._ui.handle_navigation("page_down"):
|
|
121
|
+
self._ui.render()
|
|
122
|
+
|
|
123
|
+
@kb.add("v")
|
|
124
|
+
def _(event):
|
|
125
|
+
if self._ui.search_mode:
|
|
126
|
+
self._ui.append_search_char("v")
|
|
127
|
+
self._ui.render()
|
|
128
|
+
return
|
|
129
|
+
self._g_pressed = False
|
|
130
|
+
self._d_pressed = False
|
|
131
|
+
if self._ui.toggle_selection():
|
|
132
|
+
self._ui.render()
|
|
133
|
+
|
|
134
|
+
@kb.add("d")
|
|
135
|
+
def _(event):
|
|
136
|
+
if self._ui.search_mode:
|
|
137
|
+
self._ui.append_search_char("d")
|
|
138
|
+
self._ui.render()
|
|
139
|
+
return
|
|
140
|
+
self._g_pressed = False
|
|
141
|
+
if self._d_pressed:
|
|
142
|
+
self._d_pressed = False
|
|
143
|
+
self._handle_delete()
|
|
144
|
+
else:
|
|
145
|
+
self._d_pressed = True
|
|
146
|
+
|
|
147
|
+
@kb.add("/")
|
|
148
|
+
def _(event):
|
|
149
|
+
if self._ui.search_mode:
|
|
150
|
+
self._ui.append_search_char("/")
|
|
151
|
+
self._ui.render()
|
|
152
|
+
return
|
|
153
|
+
self._g_pressed = False
|
|
154
|
+
self._d_pressed = False
|
|
155
|
+
self._ui.start_search_mode()
|
|
156
|
+
self._ui.render()
|
|
157
|
+
|
|
158
|
+
@kb.add(Keys.Backspace)
|
|
159
|
+
def _(event):
|
|
160
|
+
if self._ui.search_mode:
|
|
161
|
+
self._ui.backspace_search()
|
|
162
|
+
self._ui.render()
|
|
163
|
+
|
|
164
|
+
@kb.add(Keys.Enter)
|
|
165
|
+
@kb.add("l")
|
|
166
|
+
def _(event):
|
|
167
|
+
self._g_pressed = False
|
|
168
|
+
self._d_pressed = False
|
|
169
|
+
if self._ui.search_mode:
|
|
170
|
+
self._ui.exit_search_mode(clear_filter=False)
|
|
171
|
+
self._ui.render()
|
|
172
|
+
return
|
|
173
|
+
self._selected_id = self._ui.get_selected_conversation_id()
|
|
174
|
+
event.app.exit()
|
|
175
|
+
|
|
176
|
+
@kb.add(Keys.Escape)
|
|
177
|
+
def _(event):
|
|
178
|
+
self._g_pressed = False
|
|
179
|
+
self._d_pressed = False
|
|
180
|
+
if self._ui.search_mode:
|
|
181
|
+
self._ui.exit_search_mode(clear_filter=True)
|
|
182
|
+
self._ui.render()
|
|
183
|
+
return
|
|
184
|
+
event.app.exit()
|
|
185
|
+
|
|
186
|
+
@kb.add("q")
|
|
187
|
+
def _(event):
|
|
188
|
+
if self._ui.search_mode:
|
|
189
|
+
self._ui.append_search_char("q")
|
|
190
|
+
self._ui.render()
|
|
191
|
+
return
|
|
192
|
+
self._g_pressed = False
|
|
193
|
+
self._d_pressed = False
|
|
194
|
+
event.app.exit()
|
|
195
|
+
|
|
196
|
+
@kb.add(Keys.ControlC)
|
|
197
|
+
def _(event):
|
|
198
|
+
self._g_pressed = False
|
|
199
|
+
self._d_pressed = False
|
|
200
|
+
if self._ui.search_mode:
|
|
201
|
+
self._ui.exit_search_mode(clear_filter=True)
|
|
202
|
+
self._ui.render()
|
|
203
|
+
return
|
|
204
|
+
event.app.exit()
|
|
205
|
+
|
|
206
|
+
@kb.add(Keys.Any)
|
|
207
|
+
def _(event):
|
|
208
|
+
if self._ui.search_mode:
|
|
209
|
+
char = event.data
|
|
210
|
+
if char and char.isprintable():
|
|
211
|
+
self._ui.append_search_char(char)
|
|
212
|
+
self._ui.render()
|
|
213
|
+
return
|
|
214
|
+
self._g_pressed = False
|
|
215
|
+
self._d_pressed = False
|
|
216
|
+
|
|
217
|
+
return kb
|
|
218
|
+
|
|
219
|
+
def _handle_delete(self):
|
|
220
|
+
"""Handle delete action for selected or current conversation."""
|
|
221
|
+
if not self._ui.conversations:
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
if self._ui.selected_items:
|
|
225
|
+
indices_to_delete = list(self._ui.selected_items)
|
|
226
|
+
ids_to_delete = self._ui.get_selected_conversation_ids()
|
|
227
|
+
else:
|
|
228
|
+
indices_to_delete = [self._ui.selected_index]
|
|
229
|
+
current_id = self._ui.get_selected_conversation_id()
|
|
230
|
+
ids_to_delete = [current_id] if current_id else []
|
|
231
|
+
|
|
232
|
+
if not ids_to_delete:
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
if self._on_delete:
|
|
236
|
+
success = self._on_delete(ids_to_delete)
|
|
237
|
+
if success:
|
|
238
|
+
self._ui.remove_conversations(indices_to_delete)
|
|
239
|
+
self._ui.render()
|
|
240
|
+
else:
|
|
241
|
+
self._ui.remove_conversations(indices_to_delete)
|
|
242
|
+
self._ui.render()
|
|
243
|
+
|
|
244
|
+
def run(self) -> Optional[str]:
|
|
245
|
+
"""Run the input handler loop.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
The ID of the selected conversation, or None if cancelled.
|
|
249
|
+
"""
|
|
250
|
+
self._running = True
|
|
251
|
+
self._g_pressed = False
|
|
252
|
+
self._d_pressed = False
|
|
253
|
+
self._selected_id = None
|
|
254
|
+
|
|
255
|
+
self._ui.start_live()
|
|
256
|
+
|
|
257
|
+
kb = self._create_key_bindings()
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
session = PromptSession(key_bindings=kb)
|
|
261
|
+
session.prompt("")
|
|
262
|
+
except (KeyboardInterrupt, EOFError):
|
|
263
|
+
pass
|
|
264
|
+
except Exception as e:
|
|
265
|
+
logger.error(f"Error in conversation browser input handler: {e}")
|
|
266
|
+
finally:
|
|
267
|
+
self._ui.stop_live()
|
|
268
|
+
|
|
269
|
+
self._running = False
|
|
270
|
+
return self._selected_id
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def is_running(self) -> bool:
|
|
274
|
+
"""Check if the input handler is currently running."""
|
|
275
|
+
return self._running
|
|
276
|
+
|
|
277
|
+
def stop(self):
|
|
278
|
+
"""Stop the input handler."""
|
|
279
|
+
self._running = False
|