pomera-ai-commander 0.1.0 → 1.2.1

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.
Files changed (191) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +105 -680
  3. package/bin/pomera-ai-commander.js +62 -62
  4. package/core/__init__.py +65 -65
  5. package/core/app_context.py +482 -482
  6. package/core/async_text_processor.py +421 -421
  7. package/core/backup_manager.py +655 -655
  8. package/core/backup_recovery_manager.py +1033 -1033
  9. package/core/content_hash_cache.py +508 -508
  10. package/core/context_menu.py +313 -313
  11. package/core/data_validator.py +1066 -1066
  12. package/core/database_connection_manager.py +744 -744
  13. package/core/database_curl_settings_manager.py +608 -608
  14. package/core/database_promera_ai_settings_manager.py +446 -446
  15. package/core/database_schema.py +411 -411
  16. package/core/database_schema_manager.py +395 -395
  17. package/core/database_settings_manager.py +1507 -1507
  18. package/core/database_settings_manager_interface.py +456 -456
  19. package/core/dialog_manager.py +734 -734
  20. package/core/efficient_line_numbers.py +510 -510
  21. package/core/error_handler.py +746 -746
  22. package/core/error_service.py +431 -431
  23. package/core/event_consolidator.py +511 -511
  24. package/core/mcp/__init__.py +43 -43
  25. package/core/mcp/protocol.py +288 -288
  26. package/core/mcp/schema.py +251 -251
  27. package/core/mcp/server_stdio.py +299 -299
  28. package/core/mcp/tool_registry.py +2372 -2345
  29. package/core/memory_efficient_text_widget.py +711 -711
  30. package/core/migration_manager.py +914 -914
  31. package/core/migration_test_suite.py +1085 -1085
  32. package/core/migration_validator.py +1143 -1143
  33. package/core/optimized_find_replace.py +714 -714
  34. package/core/optimized_pattern_engine.py +424 -424
  35. package/core/optimized_search_highlighter.py +552 -552
  36. package/core/performance_monitor.py +674 -674
  37. package/core/persistence_manager.py +712 -712
  38. package/core/progressive_stats_calculator.py +632 -632
  39. package/core/regex_pattern_cache.py +529 -529
  40. package/core/regex_pattern_library.py +350 -350
  41. package/core/search_operation_manager.py +434 -434
  42. package/core/settings_defaults_registry.py +1087 -1087
  43. package/core/settings_integrity_validator.py +1111 -1111
  44. package/core/settings_serializer.py +557 -557
  45. package/core/settings_validator.py +1823 -1823
  46. package/core/smart_stats_calculator.py +709 -709
  47. package/core/statistics_update_manager.py +619 -619
  48. package/core/stats_config_manager.py +858 -858
  49. package/core/streaming_text_handler.py +723 -723
  50. package/core/task_scheduler.py +596 -596
  51. package/core/update_pattern_library.py +168 -168
  52. package/core/visibility_monitor.py +596 -596
  53. package/core/widget_cache.py +498 -498
  54. package/mcp.json +51 -61
  55. package/package.json +61 -57
  56. package/pomera.py +7482 -7482
  57. package/pomera_mcp_server.py +183 -144
  58. package/requirements.txt +32 -0
  59. package/tools/__init__.py +4 -4
  60. package/tools/ai_tools.py +2891 -2891
  61. package/tools/ascii_art_generator.py +352 -352
  62. package/tools/base64_tools.py +183 -183
  63. package/tools/base_tool.py +511 -511
  64. package/tools/case_tool.py +308 -308
  65. package/tools/column_tools.py +395 -395
  66. package/tools/cron_tool.py +884 -884
  67. package/tools/curl_history.py +600 -600
  68. package/tools/curl_processor.py +1207 -1207
  69. package/tools/curl_settings.py +502 -502
  70. package/tools/curl_tool.py +5467 -5467
  71. package/tools/diff_viewer.py +1071 -1071
  72. package/tools/email_extraction_tool.py +248 -248
  73. package/tools/email_header_analyzer.py +425 -425
  74. package/tools/extraction_tools.py +250 -250
  75. package/tools/find_replace.py +1750 -1750
  76. package/tools/folder_file_reporter.py +1463 -1463
  77. package/tools/folder_file_reporter_adapter.py +480 -480
  78. package/tools/generator_tools.py +1216 -1216
  79. package/tools/hash_generator.py +255 -255
  80. package/tools/html_tool.py +656 -656
  81. package/tools/jsonxml_tool.py +729 -729
  82. package/tools/line_tools.py +419 -419
  83. package/tools/markdown_tools.py +561 -561
  84. package/tools/mcp_widget.py +1417 -1417
  85. package/tools/notes_widget.py +973 -973
  86. package/tools/number_base_converter.py +372 -372
  87. package/tools/regex_extractor.py +571 -571
  88. package/tools/slug_generator.py +310 -310
  89. package/tools/sorter_tools.py +458 -458
  90. package/tools/string_escape_tool.py +392 -392
  91. package/tools/text_statistics_tool.py +365 -365
  92. package/tools/text_wrapper.py +430 -430
  93. package/tools/timestamp_converter.py +421 -421
  94. package/tools/tool_loader.py +710 -710
  95. package/tools/translator_tools.py +522 -522
  96. package/tools/url_link_extractor.py +261 -261
  97. package/tools/url_parser.py +204 -204
  98. package/tools/whitespace_tools.py +355 -355
  99. package/tools/word_frequency_counter.py +146 -146
  100. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  101. package/core/__pycache__/app_context.cpython-313.pyc +0 -0
  102. package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
  103. package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
  104. package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
  105. package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
  106. package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
  107. package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
  108. package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
  109. package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
  110. package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
  111. package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
  112. package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
  113. package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
  114. package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
  115. package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
  116. package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
  117. package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
  118. package/core/__pycache__/error_service.cpython-313.pyc +0 -0
  119. package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
  120. package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
  121. package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
  122. package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
  123. package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
  124. package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
  125. package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
  126. package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
  127. package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
  128. package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
  129. package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
  130. package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
  131. package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
  132. package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
  133. package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
  134. package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
  135. package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
  136. package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
  137. package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
  138. package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
  139. package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
  140. package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
  141. package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
  142. package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
  143. package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
  144. package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  145. package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
  146. package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
  147. package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
  148. package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
  149. package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  150. package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
  151. package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
  152. package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
  153. package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
  154. package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
  155. package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
  156. package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
  157. package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
  158. package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
  159. package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
  160. package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
  161. package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
  162. package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
  163. package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
  164. package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
  165. package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
  166. package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
  167. package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
  168. package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
  169. package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
  170. package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
  171. package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
  172. package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
  173. package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
  174. package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
  175. package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
  176. package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
  177. package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
  178. package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
  179. package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
  180. package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
  181. package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
  182. package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
  183. package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
  184. package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
  185. package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
  186. package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
  187. package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
  188. package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
  189. package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
  190. package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
  191. package/tools/__pycache__/word_frequency_counter.cpython-313.pyc +0 -0
@@ -1,511 +1,511 @@
1
- """
2
- Base Tool - Abstract base class for all tools.
3
-
4
- This module provides a standardized interface that all tools should implement
5
- for consistent behavior across the application.
6
-
7
- Author: Pomera AI Commander Team
8
- """
9
-
10
- from abc import ABC, abstractmethod
11
- from typing import Dict, Any, Optional, Callable, List, Tuple, Type
12
- import tkinter as tk
13
- from tkinter import ttk
14
- import logging
15
-
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
-
20
- class BaseTool(ABC):
21
- """
22
- Abstract base class that all tools should inherit from.
23
-
24
- This ensures a consistent interface across all tools for:
25
- - UI creation
26
- - Text processing
27
- - Settings management
28
- - Font application
29
-
30
- Subclasses must implement:
31
- - process_text(): The main text processing logic
32
- - create_ui(): Create the tool's settings UI
33
- - get_default_settings(): Return default settings
34
-
35
- Example:
36
- class MyTool(BaseTool):
37
- TOOL_NAME = "My Tool"
38
- TOOL_DESCRIPTION = "Does something useful"
39
-
40
- def process_text(self, input_text: str, settings: Dict[str, Any]) -> str:
41
- return input_text.upper()
42
-
43
- def create_ui(self, parent, settings, on_change=None, apply=None):
44
- # Create UI widgets
45
- return self._ui_frame
46
-
47
- @classmethod
48
- def get_default_settings(cls) -> Dict[str, Any]:
49
- return {"option": "default"}
50
- """
51
-
52
- # Tool metadata - override in subclasses
53
- TOOL_NAME: str = "Base Tool"
54
- TOOL_DESCRIPTION: str = ""
55
- TOOL_VERSION: str = "1.0.0"
56
-
57
- # Tool capabilities
58
- REQUIRES_INPUT: bool = True # Whether tool needs input text
59
- SUPPORTS_STREAMING: bool = False # Whether tool supports streaming output
60
- SUPPORTS_ASYNC: bool = False # Whether tool supports async processing
61
-
62
- def __init__(self):
63
- """Initialize the tool."""
64
- self._settings: Dict[str, Any] = {}
65
- self._ui_frame: Optional[tk.Frame] = None
66
- self._on_setting_change: Optional[Callable] = None
67
- self._apply_callback: Optional[Callable] = None
68
- self._logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
69
- self._initializing: bool = False
70
-
71
- @abstractmethod
72
- def process_text(self, input_text: str, settings: Dict[str, Any]) -> str:
73
- """
74
- Process input text and return result.
75
-
76
- Args:
77
- input_text: The text to process
78
- settings: Current tool settings
79
-
80
- Returns:
81
- Processed text result
82
- """
83
- pass
84
-
85
- @abstractmethod
86
- def create_ui(self,
87
- parent: tk.Frame,
88
- settings: Dict[str, Any],
89
- on_setting_change_callback: Optional[Callable] = None,
90
- apply_tool_callback: Optional[Callable] = None) -> Any:
91
- """
92
- Create the tool's settings UI.
93
-
94
- Args:
95
- parent: Parent frame for the UI
96
- settings: Current tool settings
97
- on_setting_change_callback: Called when settings change
98
- apply_tool_callback: Called to apply the tool
99
-
100
- Returns:
101
- The created UI component (frame or widget)
102
- """
103
- pass
104
-
105
- @classmethod
106
- @abstractmethod
107
- def get_default_settings(cls) -> Dict[str, Any]:
108
- """
109
- Get default settings for this tool.
110
-
111
- Returns:
112
- Dictionary of default settings
113
- """
114
- pass
115
-
116
- def validate_settings(self, settings: Dict[str, Any]) -> Tuple[bool, str]:
117
- """
118
- Validate settings for this tool.
119
-
120
- Override this method to add custom validation.
121
-
122
- Args:
123
- settings: Settings to validate
124
-
125
- Returns:
126
- Tuple of (is_valid, error_message)
127
- """
128
- return True, ""
129
-
130
- def get_current_settings(self) -> Dict[str, Any]:
131
- """
132
- Get current settings from UI state.
133
-
134
- Override this to extract settings from UI widgets.
135
- """
136
- return self._settings.copy()
137
-
138
- def update_settings(self, settings: Dict[str, Any]) -> None:
139
- """
140
- Update tool settings.
141
-
142
- Override this to update UI widgets when settings change.
143
-
144
- Args:
145
- settings: New settings to apply
146
- """
147
- self._settings.update(settings)
148
-
149
- def apply_font_to_widgets(self, font_tuple: Tuple[str, int]) -> None:
150
- """
151
- Apply font settings to tool widgets.
152
-
153
- Override this if your tool has text widgets that need font updates.
154
-
155
- Args:
156
- font_tuple: Tuple of (font_family, font_size)
157
- """
158
- pass
159
-
160
- def cleanup(self) -> None:
161
- """
162
- Clean up resources when tool is destroyed.
163
-
164
- Override this to clean up any resources (threads, connections, etc.)
165
- """
166
- pass
167
-
168
- def _notify_setting_change(self, key: Optional[str] = None, value: Any = None) -> None:
169
- """
170
- Notify that a setting has changed.
171
-
172
- Args:
173
- key: Setting key that changed (optional)
174
- value: New value (optional)
175
- """
176
- if key is not None:
177
- self._settings[key] = value
178
-
179
- if not self._initializing and self._on_setting_change:
180
- self._on_setting_change()
181
-
182
- # UI Helper methods
183
- def _create_labeled_entry(self,
184
- parent: tk.Frame,
185
- label: str,
186
- var: tk.Variable,
187
- width: int = 30,
188
- label_width: int = 15) -> ttk.Entry:
189
- """
190
- Helper to create a labeled entry field.
191
-
192
- Args:
193
- parent: Parent widget
194
- label: Label text
195
- var: Tkinter variable for the entry
196
- width: Entry width
197
- label_width: Label width
198
-
199
- Returns:
200
- The created Entry widget
201
- """
202
- frame = ttk.Frame(parent)
203
- frame.pack(fill=tk.X, pady=2)
204
- ttk.Label(frame, text=label, width=label_width).pack(side=tk.LEFT)
205
- entry = ttk.Entry(frame, textvariable=var, width=width)
206
- entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
207
- return entry
208
-
209
- def _create_labeled_combo(self,
210
- parent: tk.Frame,
211
- label: str,
212
- var: tk.Variable,
213
- values: List[str],
214
- width: int = 27,
215
- label_width: int = 15,
216
- on_change: Optional[Callable] = None) -> ttk.Combobox:
217
- """
218
- Helper to create a labeled combobox.
219
-
220
- Args:
221
- parent: Parent widget
222
- label: Label text
223
- var: Tkinter variable for the combobox
224
- values: List of values
225
- width: Combobox width
226
- label_width: Label width
227
- on_change: Callback for selection change
228
-
229
- Returns:
230
- The created Combobox widget
231
- """
232
- frame = ttk.Frame(parent)
233
- frame.pack(fill=tk.X, pady=2)
234
- ttk.Label(frame, text=label, width=label_width).pack(side=tk.LEFT)
235
- combo = ttk.Combobox(frame, textvariable=var, values=values, width=width, state="readonly")
236
- combo.pack(side=tk.LEFT)
237
- if on_change:
238
- combo.bind("<<ComboboxSelected>>", lambda e: on_change())
239
- return combo
240
-
241
- def _create_labeled_spinbox(self,
242
- parent: tk.Frame,
243
- label: str,
244
- var: tk.Variable,
245
- from_: int = 0,
246
- to: int = 100,
247
- width: int = 10,
248
- label_width: int = 15) -> ttk.Spinbox:
249
- """
250
- Helper to create a labeled spinbox.
251
-
252
- Args:
253
- parent: Parent widget
254
- label: Label text
255
- var: Tkinter variable
256
- from_: Minimum value
257
- to: Maximum value
258
- width: Spinbox width
259
- label_width: Label width
260
-
261
- Returns:
262
- The created Spinbox widget
263
- """
264
- frame = ttk.Frame(parent)
265
- frame.pack(fill=tk.X, pady=2)
266
- ttk.Label(frame, text=label, width=label_width).pack(side=tk.LEFT)
267
- spinbox = ttk.Spinbox(frame, textvariable=var, from_=from_, to=to, width=width)
268
- spinbox.pack(side=tk.LEFT)
269
- return spinbox
270
-
271
- def _create_checkbox(self,
272
- parent: tk.Frame,
273
- label: str,
274
- var: tk.BooleanVar,
275
- on_change: Optional[Callable] = None) -> ttk.Checkbutton:
276
- """
277
- Helper to create a checkbox.
278
-
279
- Args:
280
- parent: Parent widget
281
- label: Checkbox label
282
- var: BooleanVar for the checkbox
283
- on_change: Callback for state change
284
-
285
- Returns:
286
- The created Checkbutton widget
287
- """
288
- cb = ttk.Checkbutton(parent, text=label, variable=var)
289
- cb.pack(anchor="w", pady=2)
290
- if on_change:
291
- cb.configure(command=on_change)
292
- return cb
293
-
294
- def _create_radio_group(self,
295
- parent: tk.Frame,
296
- label: str,
297
- var: tk.Variable,
298
- options: List[Tuple[str, str]],
299
- on_change: Optional[Callable] = None) -> ttk.Frame:
300
- """
301
- Helper to create a radio button group.
302
-
303
- Args:
304
- parent: Parent widget
305
- label: Group label
306
- var: Variable for the selection
307
- options: List of (text, value) tuples
308
- on_change: Callback for selection change
309
-
310
- Returns:
311
- Frame containing the radio buttons
312
- """
313
- frame = ttk.LabelFrame(parent, text=label)
314
- frame.pack(fill=tk.X, pady=5)
315
-
316
- for text, value in options:
317
- rb = ttk.Radiobutton(
318
- frame,
319
- text=text,
320
- variable=var,
321
- value=value,
322
- command=on_change if on_change else None
323
- )
324
- rb.pack(anchor="w")
325
-
326
- return frame
327
-
328
- def _create_apply_button(self,
329
- parent: tk.Frame,
330
- text: str = "Apply",
331
- callback: Optional[Callable] = None) -> ttk.Button:
332
- """
333
- Helper to create an apply/process button.
334
-
335
- Args:
336
- parent: Parent widget
337
- text: Button text
338
- callback: Button callback (uses apply_tool_callback if None)
339
-
340
- Returns:
341
- The created Button widget
342
- """
343
- cmd = callback or self._apply_callback
344
- if cmd:
345
- btn = ttk.Button(parent, text=text, command=cmd)
346
- btn.pack(side=tk.LEFT, padx=10, pady=10)
347
- return btn
348
- return None
349
-
350
-
351
- class SimpleProcessingTool(BaseTool):
352
- """
353
- Base class for simple text processing tools that don't need complex UI.
354
-
355
- Subclasses only need to implement:
356
- - process_text(): The text processing logic
357
- - get_default_settings(): Default settings
358
-
359
- The UI will show a simple description and apply button.
360
- """
361
-
362
- def create_ui(self,
363
- parent: tk.Frame,
364
- settings: Dict[str, Any],
365
- on_setting_change_callback: Optional[Callable] = None,
366
- apply_tool_callback: Optional[Callable] = None) -> tk.Frame:
367
- """Create a simple UI showing tool description and apply button."""
368
- self._settings = settings.copy()
369
- self._on_setting_change = on_setting_change_callback
370
- self._apply_callback = apply_tool_callback
371
-
372
- frame = ttk.Frame(parent)
373
- frame.pack(fill=tk.BOTH, expand=True)
374
-
375
- # Show description if available
376
- if self.TOOL_DESCRIPTION:
377
- desc_label = ttk.Label(frame, text=self.TOOL_DESCRIPTION, wraplength=400)
378
- desc_label.pack(pady=10)
379
-
380
- # Add apply button
381
- self._create_apply_button(frame)
382
-
383
- self._ui_frame = frame
384
- return frame
385
-
386
- @classmethod
387
- def get_default_settings(cls) -> Dict[str, Any]:
388
- """Simple tools have no settings by default."""
389
- return {}
390
-
391
-
392
- class ToolWithOptions(BaseTool):
393
- """
394
- Base class for tools with selectable options/modes.
395
-
396
- Provides automatic UI generation for tools that have:
397
- - A mode/option selector (radio buttons or dropdown)
398
- - Optional additional settings per mode
399
-
400
- Subclasses should define:
401
- - OPTIONS: List of (display_name, value) tuples
402
- - OPTIONS_LABEL: Label for the options (default: "Mode")
403
- - USE_DROPDOWN: True for dropdown, False for radio buttons
404
- """
405
-
406
- OPTIONS: List[Tuple[str, str]] = []
407
- OPTIONS_LABEL: str = "Mode"
408
- USE_DROPDOWN: bool = False
409
- DEFAULT_OPTION: str = ""
410
-
411
- def __init__(self):
412
- super().__init__()
413
- self._option_var: Optional[tk.StringVar] = None
414
-
415
- def create_ui(self,
416
- parent: tk.Frame,
417
- settings: Dict[str, Any],
418
- on_setting_change_callback: Optional[Callable] = None,
419
- apply_tool_callback: Optional[Callable] = None) -> tk.Frame:
420
- """Create UI with option selector."""
421
- self._settings = settings.copy()
422
- self._on_setting_change = on_setting_change_callback
423
- self._apply_callback = apply_tool_callback
424
- self._initializing = True
425
-
426
- frame = ttk.Frame(parent)
427
- frame.pack(fill=tk.BOTH, expand=True)
428
-
429
- # Option selector
430
- current_value = settings.get("mode", self.DEFAULT_OPTION or
431
- (self.OPTIONS[0][1] if self.OPTIONS else ""))
432
- self._option_var = tk.StringVar(value=current_value)
433
-
434
- if self.USE_DROPDOWN:
435
- values = [opt[0] for opt in self.OPTIONS]
436
- self._create_labeled_combo(
437
- frame,
438
- self.OPTIONS_LABEL + ":",
439
- self._option_var,
440
- values,
441
- on_change=self._on_option_change
442
- )
443
- else:
444
- self._create_radio_group(
445
- frame,
446
- self.OPTIONS_LABEL,
447
- self._option_var,
448
- self.OPTIONS,
449
- on_change=self._on_option_change
450
- )
451
-
452
- # Create additional UI (override in subclass)
453
- self._create_additional_ui(frame, settings)
454
-
455
- # Apply button
456
- self._create_apply_button(frame)
457
-
458
- self._ui_frame = frame
459
- self._initializing = False
460
- return frame
461
-
462
- def _create_additional_ui(self, parent: tk.Frame, settings: Dict[str, Any]) -> None:
463
- """
464
- Override to add additional UI elements.
465
-
466
- Args:
467
- parent: Parent frame
468
- settings: Current settings
469
- """
470
- pass
471
-
472
- def _on_option_change(self) -> None:
473
- """Handle option change."""
474
- self._notify_setting_change("mode", self._option_var.get())
475
-
476
- def get_current_settings(self) -> Dict[str, Any]:
477
- """Get current settings including selected option."""
478
- settings = self._settings.copy()
479
- if self._option_var:
480
- settings["mode"] = self._option_var.get()
481
- return settings
482
-
483
- @classmethod
484
- def get_default_settings(cls) -> Dict[str, Any]:
485
- """Default settings with first option selected."""
486
- default_mode = cls.DEFAULT_OPTION or (cls.OPTIONS[0][1] if cls.OPTIONS else "")
487
- return {"mode": default_mode}
488
-
489
-
490
- def get_tool_registry_defaults(tool_name: str, fallback: Dict[str, Any]) -> Dict[str, Any]:
491
- """
492
- Helper to get defaults from registry with fallback.
493
-
494
- Args:
495
- tool_name: Name of the tool
496
- fallback: Fallback defaults if registry unavailable
497
-
498
- Returns:
499
- Tool default settings
500
- """
501
- try:
502
- from core.settings_defaults_registry import get_registry
503
- registry = get_registry()
504
- return registry.get_tool_defaults(tool_name)
505
- except ImportError:
506
- pass
507
- except Exception as e:
508
- logger.debug(f"Could not get registry defaults for {tool_name}: {e}")
509
-
510
- return fallback
511
-
1
+ """
2
+ Base Tool - Abstract base class for all tools.
3
+
4
+ This module provides a standardized interface that all tools should implement
5
+ for consistent behavior across the application.
6
+
7
+ Author: Pomera AI Commander Team
8
+ """
9
+
10
+ from abc import ABC, abstractmethod
11
+ from typing import Dict, Any, Optional, Callable, List, Tuple, Type
12
+ import tkinter as tk
13
+ from tkinter import ttk
14
+ import logging
15
+
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class BaseTool(ABC):
21
+ """
22
+ Abstract base class that all tools should inherit from.
23
+
24
+ This ensures a consistent interface across all tools for:
25
+ - UI creation
26
+ - Text processing
27
+ - Settings management
28
+ - Font application
29
+
30
+ Subclasses must implement:
31
+ - process_text(): The main text processing logic
32
+ - create_ui(): Create the tool's settings UI
33
+ - get_default_settings(): Return default settings
34
+
35
+ Example:
36
+ class MyTool(BaseTool):
37
+ TOOL_NAME = "My Tool"
38
+ TOOL_DESCRIPTION = "Does something useful"
39
+
40
+ def process_text(self, input_text: str, settings: Dict[str, Any]) -> str:
41
+ return input_text.upper()
42
+
43
+ def create_ui(self, parent, settings, on_change=None, apply=None):
44
+ # Create UI widgets
45
+ return self._ui_frame
46
+
47
+ @classmethod
48
+ def get_default_settings(cls) -> Dict[str, Any]:
49
+ return {"option": "default"}
50
+ """
51
+
52
+ # Tool metadata - override in subclasses
53
+ TOOL_NAME: str = "Base Tool"
54
+ TOOL_DESCRIPTION: str = ""
55
+ TOOL_VERSION: str = "1.0.0"
56
+
57
+ # Tool capabilities
58
+ REQUIRES_INPUT: bool = True # Whether tool needs input text
59
+ SUPPORTS_STREAMING: bool = False # Whether tool supports streaming output
60
+ SUPPORTS_ASYNC: bool = False # Whether tool supports async processing
61
+
62
+ def __init__(self):
63
+ """Initialize the tool."""
64
+ self._settings: Dict[str, Any] = {}
65
+ self._ui_frame: Optional[tk.Frame] = None
66
+ self._on_setting_change: Optional[Callable] = None
67
+ self._apply_callback: Optional[Callable] = None
68
+ self._logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
69
+ self._initializing: bool = False
70
+
71
+ @abstractmethod
72
+ def process_text(self, input_text: str, settings: Dict[str, Any]) -> str:
73
+ """
74
+ Process input text and return result.
75
+
76
+ Args:
77
+ input_text: The text to process
78
+ settings: Current tool settings
79
+
80
+ Returns:
81
+ Processed text result
82
+ """
83
+ pass
84
+
85
+ @abstractmethod
86
+ def create_ui(self,
87
+ parent: tk.Frame,
88
+ settings: Dict[str, Any],
89
+ on_setting_change_callback: Optional[Callable] = None,
90
+ apply_tool_callback: Optional[Callable] = None) -> Any:
91
+ """
92
+ Create the tool's settings UI.
93
+
94
+ Args:
95
+ parent: Parent frame for the UI
96
+ settings: Current tool settings
97
+ on_setting_change_callback: Called when settings change
98
+ apply_tool_callback: Called to apply the tool
99
+
100
+ Returns:
101
+ The created UI component (frame or widget)
102
+ """
103
+ pass
104
+
105
+ @classmethod
106
+ @abstractmethod
107
+ def get_default_settings(cls) -> Dict[str, Any]:
108
+ """
109
+ Get default settings for this tool.
110
+
111
+ Returns:
112
+ Dictionary of default settings
113
+ """
114
+ pass
115
+
116
+ def validate_settings(self, settings: Dict[str, Any]) -> Tuple[bool, str]:
117
+ """
118
+ Validate settings for this tool.
119
+
120
+ Override this method to add custom validation.
121
+
122
+ Args:
123
+ settings: Settings to validate
124
+
125
+ Returns:
126
+ Tuple of (is_valid, error_message)
127
+ """
128
+ return True, ""
129
+
130
+ def get_current_settings(self) -> Dict[str, Any]:
131
+ """
132
+ Get current settings from UI state.
133
+
134
+ Override this to extract settings from UI widgets.
135
+ """
136
+ return self._settings.copy()
137
+
138
+ def update_settings(self, settings: Dict[str, Any]) -> None:
139
+ """
140
+ Update tool settings.
141
+
142
+ Override this to update UI widgets when settings change.
143
+
144
+ Args:
145
+ settings: New settings to apply
146
+ """
147
+ self._settings.update(settings)
148
+
149
+ def apply_font_to_widgets(self, font_tuple: Tuple[str, int]) -> None:
150
+ """
151
+ Apply font settings to tool widgets.
152
+
153
+ Override this if your tool has text widgets that need font updates.
154
+
155
+ Args:
156
+ font_tuple: Tuple of (font_family, font_size)
157
+ """
158
+ pass
159
+
160
+ def cleanup(self) -> None:
161
+ """
162
+ Clean up resources when tool is destroyed.
163
+
164
+ Override this to clean up any resources (threads, connections, etc.)
165
+ """
166
+ pass
167
+
168
+ def _notify_setting_change(self, key: Optional[str] = None, value: Any = None) -> None:
169
+ """
170
+ Notify that a setting has changed.
171
+
172
+ Args:
173
+ key: Setting key that changed (optional)
174
+ value: New value (optional)
175
+ """
176
+ if key is not None:
177
+ self._settings[key] = value
178
+
179
+ if not self._initializing and self._on_setting_change:
180
+ self._on_setting_change()
181
+
182
+ # UI Helper methods
183
+ def _create_labeled_entry(self,
184
+ parent: tk.Frame,
185
+ label: str,
186
+ var: tk.Variable,
187
+ width: int = 30,
188
+ label_width: int = 15) -> ttk.Entry:
189
+ """
190
+ Helper to create a labeled entry field.
191
+
192
+ Args:
193
+ parent: Parent widget
194
+ label: Label text
195
+ var: Tkinter variable for the entry
196
+ width: Entry width
197
+ label_width: Label width
198
+
199
+ Returns:
200
+ The created Entry widget
201
+ """
202
+ frame = ttk.Frame(parent)
203
+ frame.pack(fill=tk.X, pady=2)
204
+ ttk.Label(frame, text=label, width=label_width).pack(side=tk.LEFT)
205
+ entry = ttk.Entry(frame, textvariable=var, width=width)
206
+ entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
207
+ return entry
208
+
209
+ def _create_labeled_combo(self,
210
+ parent: tk.Frame,
211
+ label: str,
212
+ var: tk.Variable,
213
+ values: List[str],
214
+ width: int = 27,
215
+ label_width: int = 15,
216
+ on_change: Optional[Callable] = None) -> ttk.Combobox:
217
+ """
218
+ Helper to create a labeled combobox.
219
+
220
+ Args:
221
+ parent: Parent widget
222
+ label: Label text
223
+ var: Tkinter variable for the combobox
224
+ values: List of values
225
+ width: Combobox width
226
+ label_width: Label width
227
+ on_change: Callback for selection change
228
+
229
+ Returns:
230
+ The created Combobox widget
231
+ """
232
+ frame = ttk.Frame(parent)
233
+ frame.pack(fill=tk.X, pady=2)
234
+ ttk.Label(frame, text=label, width=label_width).pack(side=tk.LEFT)
235
+ combo = ttk.Combobox(frame, textvariable=var, values=values, width=width, state="readonly")
236
+ combo.pack(side=tk.LEFT)
237
+ if on_change:
238
+ combo.bind("<<ComboboxSelected>>", lambda e: on_change())
239
+ return combo
240
+
241
+ def _create_labeled_spinbox(self,
242
+ parent: tk.Frame,
243
+ label: str,
244
+ var: tk.Variable,
245
+ from_: int = 0,
246
+ to: int = 100,
247
+ width: int = 10,
248
+ label_width: int = 15) -> ttk.Spinbox:
249
+ """
250
+ Helper to create a labeled spinbox.
251
+
252
+ Args:
253
+ parent: Parent widget
254
+ label: Label text
255
+ var: Tkinter variable
256
+ from_: Minimum value
257
+ to: Maximum value
258
+ width: Spinbox width
259
+ label_width: Label width
260
+
261
+ Returns:
262
+ The created Spinbox widget
263
+ """
264
+ frame = ttk.Frame(parent)
265
+ frame.pack(fill=tk.X, pady=2)
266
+ ttk.Label(frame, text=label, width=label_width).pack(side=tk.LEFT)
267
+ spinbox = ttk.Spinbox(frame, textvariable=var, from_=from_, to=to, width=width)
268
+ spinbox.pack(side=tk.LEFT)
269
+ return spinbox
270
+
271
+ def _create_checkbox(self,
272
+ parent: tk.Frame,
273
+ label: str,
274
+ var: tk.BooleanVar,
275
+ on_change: Optional[Callable] = None) -> ttk.Checkbutton:
276
+ """
277
+ Helper to create a checkbox.
278
+
279
+ Args:
280
+ parent: Parent widget
281
+ label: Checkbox label
282
+ var: BooleanVar for the checkbox
283
+ on_change: Callback for state change
284
+
285
+ Returns:
286
+ The created Checkbutton widget
287
+ """
288
+ cb = ttk.Checkbutton(parent, text=label, variable=var)
289
+ cb.pack(anchor="w", pady=2)
290
+ if on_change:
291
+ cb.configure(command=on_change)
292
+ return cb
293
+
294
+ def _create_radio_group(self,
295
+ parent: tk.Frame,
296
+ label: str,
297
+ var: tk.Variable,
298
+ options: List[Tuple[str, str]],
299
+ on_change: Optional[Callable] = None) -> ttk.Frame:
300
+ """
301
+ Helper to create a radio button group.
302
+
303
+ Args:
304
+ parent: Parent widget
305
+ label: Group label
306
+ var: Variable for the selection
307
+ options: List of (text, value) tuples
308
+ on_change: Callback for selection change
309
+
310
+ Returns:
311
+ Frame containing the radio buttons
312
+ """
313
+ frame = ttk.LabelFrame(parent, text=label)
314
+ frame.pack(fill=tk.X, pady=5)
315
+
316
+ for text, value in options:
317
+ rb = ttk.Radiobutton(
318
+ frame,
319
+ text=text,
320
+ variable=var,
321
+ value=value,
322
+ command=on_change if on_change else None
323
+ )
324
+ rb.pack(anchor="w")
325
+
326
+ return frame
327
+
328
+ def _create_apply_button(self,
329
+ parent: tk.Frame,
330
+ text: str = "Apply",
331
+ callback: Optional[Callable] = None) -> ttk.Button:
332
+ """
333
+ Helper to create an apply/process button.
334
+
335
+ Args:
336
+ parent: Parent widget
337
+ text: Button text
338
+ callback: Button callback (uses apply_tool_callback if None)
339
+
340
+ Returns:
341
+ The created Button widget
342
+ """
343
+ cmd = callback or self._apply_callback
344
+ if cmd:
345
+ btn = ttk.Button(parent, text=text, command=cmd)
346
+ btn.pack(side=tk.LEFT, padx=10, pady=10)
347
+ return btn
348
+ return None
349
+
350
+
351
+ class SimpleProcessingTool(BaseTool):
352
+ """
353
+ Base class for simple text processing tools that don't need complex UI.
354
+
355
+ Subclasses only need to implement:
356
+ - process_text(): The text processing logic
357
+ - get_default_settings(): Default settings
358
+
359
+ The UI will show a simple description and apply button.
360
+ """
361
+
362
+ def create_ui(self,
363
+ parent: tk.Frame,
364
+ settings: Dict[str, Any],
365
+ on_setting_change_callback: Optional[Callable] = None,
366
+ apply_tool_callback: Optional[Callable] = None) -> tk.Frame:
367
+ """Create a simple UI showing tool description and apply button."""
368
+ self._settings = settings.copy()
369
+ self._on_setting_change = on_setting_change_callback
370
+ self._apply_callback = apply_tool_callback
371
+
372
+ frame = ttk.Frame(parent)
373
+ frame.pack(fill=tk.BOTH, expand=True)
374
+
375
+ # Show description if available
376
+ if self.TOOL_DESCRIPTION:
377
+ desc_label = ttk.Label(frame, text=self.TOOL_DESCRIPTION, wraplength=400)
378
+ desc_label.pack(pady=10)
379
+
380
+ # Add apply button
381
+ self._create_apply_button(frame)
382
+
383
+ self._ui_frame = frame
384
+ return frame
385
+
386
+ @classmethod
387
+ def get_default_settings(cls) -> Dict[str, Any]:
388
+ """Simple tools have no settings by default."""
389
+ return {}
390
+
391
+
392
+ class ToolWithOptions(BaseTool):
393
+ """
394
+ Base class for tools with selectable options/modes.
395
+
396
+ Provides automatic UI generation for tools that have:
397
+ - A mode/option selector (radio buttons or dropdown)
398
+ - Optional additional settings per mode
399
+
400
+ Subclasses should define:
401
+ - OPTIONS: List of (display_name, value) tuples
402
+ - OPTIONS_LABEL: Label for the options (default: "Mode")
403
+ - USE_DROPDOWN: True for dropdown, False for radio buttons
404
+ """
405
+
406
+ OPTIONS: List[Tuple[str, str]] = []
407
+ OPTIONS_LABEL: str = "Mode"
408
+ USE_DROPDOWN: bool = False
409
+ DEFAULT_OPTION: str = ""
410
+
411
+ def __init__(self):
412
+ super().__init__()
413
+ self._option_var: Optional[tk.StringVar] = None
414
+
415
+ def create_ui(self,
416
+ parent: tk.Frame,
417
+ settings: Dict[str, Any],
418
+ on_setting_change_callback: Optional[Callable] = None,
419
+ apply_tool_callback: Optional[Callable] = None) -> tk.Frame:
420
+ """Create UI with option selector."""
421
+ self._settings = settings.copy()
422
+ self._on_setting_change = on_setting_change_callback
423
+ self._apply_callback = apply_tool_callback
424
+ self._initializing = True
425
+
426
+ frame = ttk.Frame(parent)
427
+ frame.pack(fill=tk.BOTH, expand=True)
428
+
429
+ # Option selector
430
+ current_value = settings.get("mode", self.DEFAULT_OPTION or
431
+ (self.OPTIONS[0][1] if self.OPTIONS else ""))
432
+ self._option_var = tk.StringVar(value=current_value)
433
+
434
+ if self.USE_DROPDOWN:
435
+ values = [opt[0] for opt in self.OPTIONS]
436
+ self._create_labeled_combo(
437
+ frame,
438
+ self.OPTIONS_LABEL + ":",
439
+ self._option_var,
440
+ values,
441
+ on_change=self._on_option_change
442
+ )
443
+ else:
444
+ self._create_radio_group(
445
+ frame,
446
+ self.OPTIONS_LABEL,
447
+ self._option_var,
448
+ self.OPTIONS,
449
+ on_change=self._on_option_change
450
+ )
451
+
452
+ # Create additional UI (override in subclass)
453
+ self._create_additional_ui(frame, settings)
454
+
455
+ # Apply button
456
+ self._create_apply_button(frame)
457
+
458
+ self._ui_frame = frame
459
+ self._initializing = False
460
+ return frame
461
+
462
+ def _create_additional_ui(self, parent: tk.Frame, settings: Dict[str, Any]) -> None:
463
+ """
464
+ Override to add additional UI elements.
465
+
466
+ Args:
467
+ parent: Parent frame
468
+ settings: Current settings
469
+ """
470
+ pass
471
+
472
+ def _on_option_change(self) -> None:
473
+ """Handle option change."""
474
+ self._notify_setting_change("mode", self._option_var.get())
475
+
476
+ def get_current_settings(self) -> Dict[str, Any]:
477
+ """Get current settings including selected option."""
478
+ settings = self._settings.copy()
479
+ if self._option_var:
480
+ settings["mode"] = self._option_var.get()
481
+ return settings
482
+
483
+ @classmethod
484
+ def get_default_settings(cls) -> Dict[str, Any]:
485
+ """Default settings with first option selected."""
486
+ default_mode = cls.DEFAULT_OPTION or (cls.OPTIONS[0][1] if cls.OPTIONS else "")
487
+ return {"mode": default_mode}
488
+
489
+
490
+ def get_tool_registry_defaults(tool_name: str, fallback: Dict[str, Any]) -> Dict[str, Any]:
491
+ """
492
+ Helper to get defaults from registry with fallback.
493
+
494
+ Args:
495
+ tool_name: Name of the tool
496
+ fallback: Fallback defaults if registry unavailable
497
+
498
+ Returns:
499
+ Tool default settings
500
+ """
501
+ try:
502
+ from core.settings_defaults_registry import get_registry
503
+ registry = get_registry()
504
+ return registry.get_tool_defaults(tool_name)
505
+ except ImportError:
506
+ pass
507
+ except Exception as e:
508
+ logger.debug(f"Could not get registry defaults for {tool_name}: {e}")
509
+
510
+ return fallback
511
+