pomera-ai-commander 0.1.0

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 (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +680 -0
  3. package/bin/pomera-ai-commander.js +62 -0
  4. package/core/__init__.py +66 -0
  5. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  6. package/core/__pycache__/app_context.cpython-313.pyc +0 -0
  7. package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
  8. package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
  9. package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
  10. package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
  11. package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
  12. package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
  13. package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
  14. package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
  15. package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
  16. package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
  17. package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
  18. package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
  19. package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
  20. package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
  21. package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
  22. package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
  23. package/core/__pycache__/error_service.cpython-313.pyc +0 -0
  24. package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
  25. package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
  26. package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
  27. package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
  28. package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
  29. package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
  30. package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
  31. package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
  32. package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
  33. package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
  34. package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
  35. package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
  36. package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
  37. package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
  38. package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
  39. package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
  40. package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
  41. package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
  42. package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
  43. package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
  44. package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
  45. package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
  46. package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
  47. package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
  48. package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
  49. package/core/app_context.py +482 -0
  50. package/core/async_text_processor.py +422 -0
  51. package/core/backup_manager.py +656 -0
  52. package/core/backup_recovery_manager.py +1034 -0
  53. package/core/content_hash_cache.py +509 -0
  54. package/core/context_menu.py +313 -0
  55. package/core/data_validator.py +1067 -0
  56. package/core/database_connection_manager.py +745 -0
  57. package/core/database_curl_settings_manager.py +609 -0
  58. package/core/database_promera_ai_settings_manager.py +447 -0
  59. package/core/database_schema.py +412 -0
  60. package/core/database_schema_manager.py +396 -0
  61. package/core/database_settings_manager.py +1508 -0
  62. package/core/database_settings_manager_interface.py +457 -0
  63. package/core/dialog_manager.py +735 -0
  64. package/core/efficient_line_numbers.py +511 -0
  65. package/core/error_handler.py +747 -0
  66. package/core/error_service.py +431 -0
  67. package/core/event_consolidator.py +512 -0
  68. package/core/mcp/__init__.py +43 -0
  69. package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  70. package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
  71. package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
  72. package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
  73. package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
  74. package/core/mcp/protocol.py +288 -0
  75. package/core/mcp/schema.py +251 -0
  76. package/core/mcp/server_stdio.py +299 -0
  77. package/core/mcp/tool_registry.py +2345 -0
  78. package/core/memory_efficient_text_widget.py +712 -0
  79. package/core/migration_manager.py +915 -0
  80. package/core/migration_test_suite.py +1086 -0
  81. package/core/migration_validator.py +1144 -0
  82. package/core/optimized_find_replace.py +715 -0
  83. package/core/optimized_pattern_engine.py +424 -0
  84. package/core/optimized_search_highlighter.py +553 -0
  85. package/core/performance_monitor.py +675 -0
  86. package/core/persistence_manager.py +713 -0
  87. package/core/progressive_stats_calculator.py +632 -0
  88. package/core/regex_pattern_cache.py +530 -0
  89. package/core/regex_pattern_library.py +351 -0
  90. package/core/search_operation_manager.py +435 -0
  91. package/core/settings_defaults_registry.py +1087 -0
  92. package/core/settings_integrity_validator.py +1112 -0
  93. package/core/settings_serializer.py +558 -0
  94. package/core/settings_validator.py +1824 -0
  95. package/core/smart_stats_calculator.py +710 -0
  96. package/core/statistics_update_manager.py +619 -0
  97. package/core/stats_config_manager.py +858 -0
  98. package/core/streaming_text_handler.py +723 -0
  99. package/core/task_scheduler.py +596 -0
  100. package/core/update_pattern_library.py +169 -0
  101. package/core/visibility_monitor.py +596 -0
  102. package/core/widget_cache.py +498 -0
  103. package/mcp.json +61 -0
  104. package/package.json +57 -0
  105. package/pomera.py +7483 -0
  106. package/pomera_mcp_server.py +144 -0
  107. package/tools/__init__.py +5 -0
  108. package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  109. package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
  110. package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
  111. package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
  112. package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
  113. package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
  114. package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
  115. package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
  116. package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
  117. package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
  118. package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
  119. package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
  120. package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
  121. package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
  122. package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
  123. package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
  124. package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
  125. package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
  126. package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
  127. package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
  128. package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
  129. package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
  130. package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
  131. package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
  132. package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
  133. package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
  134. package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
  135. package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
  136. package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
  137. package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
  138. package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
  139. package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
  140. package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
  141. package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
  142. package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
  143. package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
  144. package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
  145. package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
  146. package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
  147. package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
  148. package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
  149. package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
  150. package/tools/__pycache__/word_frequency_counter.cpython-313.pyc +0 -0
  151. package/tools/ai_tools.py +2892 -0
  152. package/tools/ascii_art_generator.py +353 -0
  153. package/tools/base64_tools.py +184 -0
  154. package/tools/base_tool.py +511 -0
  155. package/tools/case_tool.py +309 -0
  156. package/tools/column_tools.py +396 -0
  157. package/tools/cron_tool.py +885 -0
  158. package/tools/curl_history.py +601 -0
  159. package/tools/curl_processor.py +1208 -0
  160. package/tools/curl_settings.py +503 -0
  161. package/tools/curl_tool.py +5467 -0
  162. package/tools/diff_viewer.py +1072 -0
  163. package/tools/email_extraction_tool.py +249 -0
  164. package/tools/email_header_analyzer.py +426 -0
  165. package/tools/extraction_tools.py +250 -0
  166. package/tools/find_replace.py +1751 -0
  167. package/tools/folder_file_reporter.py +1463 -0
  168. package/tools/folder_file_reporter_adapter.py +480 -0
  169. package/tools/generator_tools.py +1217 -0
  170. package/tools/hash_generator.py +256 -0
  171. package/tools/html_tool.py +657 -0
  172. package/tools/huggingface_helper.py +449 -0
  173. package/tools/jsonxml_tool.py +730 -0
  174. package/tools/line_tools.py +419 -0
  175. package/tools/list_comparator.py +720 -0
  176. package/tools/markdown_tools.py +562 -0
  177. package/tools/mcp_widget.py +1417 -0
  178. package/tools/notes_widget.py +973 -0
  179. package/tools/number_base_converter.py +373 -0
  180. package/tools/regex_extractor.py +572 -0
  181. package/tools/slug_generator.py +311 -0
  182. package/tools/sorter_tools.py +459 -0
  183. package/tools/string_escape_tool.py +393 -0
  184. package/tools/text_statistics_tool.py +366 -0
  185. package/tools/text_wrapper.py +431 -0
  186. package/tools/timestamp_converter.py +422 -0
  187. package/tools/tool_loader.py +710 -0
  188. package/tools/translator_tools.py +523 -0
  189. package/tools/url_link_extractor.py +262 -0
  190. package/tools/url_parser.py +205 -0
  191. package/tools/whitespace_tools.py +356 -0
  192. package/tools/word_frequency_counter.py +147 -0
@@ -0,0 +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
+