pomera-ai-commander 1.2.5 → 1.2.8

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.
package/pomera.py CHANGED
@@ -494,6 +494,22 @@ except ImportError as e:
494
494
  WIDGET_CACHE_AVAILABLE = False
495
495
  print(f"Widget Cache not available: {e}")
496
496
 
497
+ # Collapsible Panel import (UI redesign)
498
+ try:
499
+ from core.collapsible_panel import CollapsiblePanel
500
+ COLLAPSIBLE_PANEL_AVAILABLE = True
501
+ except ImportError as e:
502
+ COLLAPSIBLE_PANEL_AVAILABLE = False
503
+ print(f"Collapsible Panel not available: {e}")
504
+
505
+ # Tool Search Widget import (UI redesign)
506
+ try:
507
+ from core.tool_search_widget import ToolSearchPalette
508
+ TOOL_SEARCH_WIDGET_AVAILABLE = True
509
+ except ImportError as e:
510
+ TOOL_SEARCH_WIDGET_AVAILABLE = False
511
+ print(f"Tool Search Widget not available: {e}")
512
+
497
513
  class AppConfig:
498
514
  """Configuration constants for the application."""
499
515
  DEFAULT_WINDOW_SIZE = "1200x900"
@@ -610,14 +626,16 @@ class PromeraAISettingsManager:
610
626
 
611
627
  def get_pattern_library(self) -> List[Dict[str, str]]:
612
628
  """Get the regex pattern library."""
613
- # Initialize pattern library if it doesn't exist
614
- if "pattern_library" not in self.app.settings:
629
+ # Initialize pattern library if it doesn't exist OR is empty
630
+ existing = self.app.settings.get("pattern_library", None)
631
+ if not existing or len(existing) == 0:
615
632
  # Try to import and use the comprehensive pattern library
616
633
  try:
617
634
  from core.regex_pattern_library import RegexPatternLibrary
618
635
  library = RegexPatternLibrary()
619
636
  self.app.settings["pattern_library"] = library._convert_to_settings_format()
620
- self.app.logger.info(f"Loaded comprehensive pattern library with {len(self.app.settings['pattern_library'])} patterns")
637
+ pattern_count = len(self.app.settings.get("pattern_library", []))
638
+ self.app.logger.info(f"Loaded comprehensive pattern library with {pattern_count} patterns")
621
639
  except ImportError:
622
640
  # Fallback to basic patterns if comprehensive library is not available
623
641
  self.app.settings["pattern_library"] = [
@@ -938,7 +956,8 @@ class PromeraAIApp(tk.Tk):
938
956
  self.logger.info("Existing content found - skipping apply_tool to preserve loaded content")
939
957
  elif self.tool_var.get() == "Diff Viewer":
940
958
  self.central_frame.grid_remove()
941
- self.diff_frame.grid(row=0, column=0, sticky="nsew", pady=5)
959
+ # Use row=1 (same as central_frame) to not cover search bar in row=0
960
+ self.diff_frame.grid(row=1, column=0, sticky="nsew", pady=5)
942
961
  self.update_tool_settings_ui()
943
962
  self.load_diff_viewer_content()
944
963
  self.run_diff_viewer()
@@ -961,6 +980,15 @@ class PromeraAIApp(tk.Tk):
961
980
  if MCP_MANAGER_MODULE_AVAILABLE:
962
981
  self.bind_all("<Control-m>", lambda e: self.open_mcp_manager())
963
982
 
983
+ # Set up Tool Search keyboard shortcut (Ctrl+K)
984
+ if TOOL_SEARCH_WIDGET_AVAILABLE:
985
+ self.bind_all("<Control-k>", self.focus_tool_search)
986
+
987
+ # Set up Options Panel toggle shortcut (Ctrl+Shift+H)
988
+ if COLLAPSIBLE_PANEL_AVAILABLE:
989
+ self.bind_all("<Control-Shift-h>", self.toggle_options_panel)
990
+ self.bind_all("<Control-Shift-H>", self.toggle_options_panel) # Windows needs uppercase
991
+
964
992
  # Set up window focus and minimize event handlers for visibility-aware updates
965
993
  if hasattr(self, 'statistics_update_manager') and self.statistics_update_manager:
966
994
  self.bind("<FocusIn>", self._on_window_focus_in)
@@ -2193,11 +2221,17 @@ class PromeraAIApp(tk.Tk):
2193
2221
 
2194
2222
  main_frame = ttk.Frame(self, padding="10")
2195
2223
  main_frame.pack(fill=tk.BOTH, expand=True)
2196
- main_frame.rowconfigure(0, weight=1)
2224
+ main_frame.rowconfigure(1, weight=1) # Central frame gets the weight
2197
2225
  main_frame.columnconfigure(0, weight=1)
2226
+
2227
+ # Row 0: Search bar (below menu, above Input/Output)
2228
+ self.top_search_frame = ttk.Frame(main_frame)
2229
+ self.top_search_frame.grid(row=0, column=0, sticky="ew", pady=(0, 5))
2230
+ self._create_top_search_bar(self.top_search_frame)
2198
2231
 
2232
+ # Row 1: Central frame with Input/Output panels
2199
2233
  self.central_frame = ttk.Frame(main_frame, padding="10")
2200
- self.central_frame.grid(row=0, column=0, sticky="nsew", pady=5)
2234
+ self.central_frame.grid(row=1, column=0, sticky="nsew", pady=5)
2201
2235
  self.central_frame.grid_columnconfigure(0, weight=1)
2202
2236
  self.central_frame.grid_columnconfigure(1, weight=1)
2203
2237
  self.central_frame.grid_rowconfigure(1, weight=1)
@@ -2210,12 +2244,13 @@ class PromeraAIApp(tk.Tk):
2210
2244
 
2211
2245
  self.create_diff_viewer(main_frame)
2212
2246
 
2213
- # Add separator
2247
+ # Row 2: Separator
2214
2248
  separator = ttk.Separator(main_frame, orient='horizontal')
2215
- separator.grid(row=1, column=0, sticky="ew", pady=10)
2249
+ separator.grid(row=2, column=0, sticky="ew", pady=10)
2216
2250
 
2251
+ # Row 3: Tool options frame (collapsible)
2217
2252
  tool_frame = ttk.Frame(main_frame, padding="10")
2218
- tool_frame.grid(row=2, column=0, sticky="ew", pady=5)
2253
+ tool_frame.grid(row=3, column=0, sticky="ew", pady=5)
2219
2254
  self.create_tool_widgets(tool_frame)
2220
2255
 
2221
2256
  # Setup context menus for all text widgets
@@ -3688,35 +3723,78 @@ class PromeraAIApp(tk.Tk):
3688
3723
  print(f"Filter text: {repr(self.output_filter_var.get())}")
3689
3724
  except Exception as e:
3690
3725
  print(f"Debug error: {e}")
3691
-
3692
- def create_tool_widgets(self, parent):
3693
- """Creates widgets for the tool selection and settings section."""
3726
+
3727
+ def _create_top_search_bar(self, parent):
3728
+ """Create the top search bar (below menu, above Input/Output panels)."""
3694
3729
  self.tool_var = tk.StringVar(value=self.settings.get("selected_tool", "Case Tool"))
3695
3730
 
3696
- self.tool_options = [
3697
- "AI Tools", "Base64 Encoder/Decoder", "Case Tool", "Column Tools",
3698
- "Cron Tool", "Diff Viewer", "Email Header Analyzer", "Extraction Tools",
3699
- "Find & Replace Text", "Folder File Reporter", "Generator Tools",
3700
- "JSON/XML Tool", "Line Tools", "Markdown Tools",
3701
- "Number Base Converter", "Sorter Tools",
3702
- "String Escape Tool", "Text Statistics", "Text Wrapper", "Timestamp Converter",
3703
- "Translator Tools", "URL Parser", "Whitespace Tools"
3704
- ]
3705
- self.filtered_tool_options = self.tool_options.copy()
3706
-
3707
- # Create custom dropdown that opens upwards
3708
- self.tool_menu = ttk.Menubutton(parent, textvariable=self.tool_var, direction="above", width=25)
3709
- self.tool_menu.pack(side=tk.LEFT, padx=5)
3710
-
3711
- # Create the dropdown menu
3712
- self.tool_dropdown_menu = tk.Menu(self.tool_menu, tearoff=0)
3713
- self.tool_menu.config(menu=self.tool_dropdown_menu)
3714
-
3715
- # Populate the dropdown menu with all tools
3716
- self.update_tool_dropdown_menu()
3731
+ # Use ToolSearchPalette if available
3732
+ if TOOL_SEARCH_WIDGET_AVAILABLE and TOOL_LOADER_AVAILABLE and self.tool_loader:
3733
+ self.tool_search_palette = ToolSearchPalette(
3734
+ parent,
3735
+ tool_loader=self.tool_loader,
3736
+ on_tool_selected=self._on_palette_tool_selected,
3737
+ settings=self.settings,
3738
+ on_settings_change=self._on_ui_settings_change
3739
+ )
3740
+ self.tool_search_palette.pack(fill=tk.X, expand=True, padx=5)
3741
+ self.tool_menu = self.tool_search_palette
3742
+ self.tool_dropdown_menu = None
3743
+ self.logger.info("Top search bar created with ToolSearchPalette")
3744
+ else:
3745
+ # Fallback: simple label
3746
+ self.tool_search_palette = None
3747
+ self.tool_menu = None
3748
+ self.tool_dropdown_menu = None
3749
+ ttk.Label(parent, text="Tool search not available").pack()
3750
+
3717
3751
 
3718
- self.tool_settings_frame = ttk.Frame(parent)
3719
- self.tool_settings_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)
3752
+ def create_tool_widgets(self, parent):
3753
+ """Creates widgets for the tool options/settings section (bottom panel).
3754
+
3755
+ Note: The search bar is now created separately by _create_top_search_bar()
3756
+ at the top of the main layout.
3757
+ """
3758
+ # Get tool list for fallback
3759
+ if TOOL_LOADER_AVAILABLE and self.tool_loader:
3760
+ self.tool_options = self.tool_loader.get_available_tools()
3761
+ else:
3762
+ self.tool_options = [
3763
+ "AI Tools", "Base64 Encoder/Decoder", "Case Tool", "Column Tools",
3764
+ "Cron Tool", "Diff Viewer", "Email Header Analyzer", "Extraction Tools",
3765
+ "Find & Replace Text", "Folder File Reporter", "Generator Tools",
3766
+ "JSON/XML Tool", "Line Tools", "Markdown Tools",
3767
+ "Number Base Converter", "Sorter Tools",
3768
+ "String Escape Tool", "Text Statistics", "Text Wrapper", "Timestamp Converter",
3769
+ "Translator Tools", "URL Parser", "Whitespace Tools"
3770
+ ]
3771
+ self.filtered_tool_options = list(self.tool_options)
3772
+
3773
+ # Tool settings panel (with collapsible wrapper if available)
3774
+ if COLLAPSIBLE_PANEL_AVAILABLE:
3775
+ # Get collapsed state from settings
3776
+ ui_layout = self.settings.get("ui_layout", {})
3777
+ initial_collapsed = ui_layout.get("options_panel_collapsed", False)
3778
+
3779
+ # Create CollapsiblePanel to wrap tool settings
3780
+ self.options_collapsible = CollapsiblePanel(
3781
+ parent,
3782
+ title="Tool Options",
3783
+ collapsed=initial_collapsed,
3784
+ on_state_change=self._on_options_panel_toggle
3785
+ )
3786
+ self.options_collapsible.pack(fill=tk.BOTH, expand=True, padx=5)
3787
+
3788
+ # Tool settings go inside the collapsible panel's content
3789
+ self.tool_settings_frame = ttk.Frame(self.options_collapsible.content_frame)
3790
+ self.tool_settings_frame.pack(fill=tk.BOTH, expand=True)
3791
+
3792
+ self.logger.info(f"Options panel wrapped in CollapsiblePanel (collapsed={initial_collapsed})")
3793
+ else:
3794
+ # Fallback: Tool settings frame without collapsible wrapper
3795
+ self.tool_settings_frame = ttk.Frame(parent)
3796
+ self.tool_settings_frame.pack(fill=tk.BOTH, expand=True, padx=5)
3797
+ self.options_collapsible = None
3720
3798
 
3721
3799
  # Initialize Widget Cache for tool settings UI
3722
3800
  if WIDGET_CACHE_AVAILABLE:
@@ -3731,9 +3809,262 @@ class PromeraAIApp(tk.Tk):
3731
3809
  self.widget_cache = None
3732
3810
 
3733
3811
  self.update_tool_settings_ui()
3812
+
3813
+ def _create_tool_search_palette(self, parent):
3814
+ """Create the new ToolSearchPalette for tool selection."""
3815
+ # Get UI layout settings
3816
+ ui_layout = self.settings.get("ui_layout", {})
3817
+
3818
+ self.tool_search_palette = ToolSearchPalette(
3819
+ parent,
3820
+ tool_loader=self.tool_loader,
3821
+ on_tool_selected=self._on_palette_tool_selected,
3822
+ settings=self.settings,
3823
+ on_settings_change=self._on_ui_settings_change
3824
+ )
3825
+ # Pack to fill horizontally (top-center layout)
3826
+ self.tool_search_palette.pack(fill=tk.BOTH, expand=True, padx=5)
3827
+
3828
+ # For backwards compatibility, also keep a reference as tool_menu
3829
+ self.tool_menu = self.tool_search_palette
3830
+ self.tool_dropdown_menu = None # Not used with palette
3831
+
3832
+ self.logger.info("Tool Search Palette initialized with fuzzy search")
3833
+
3834
+ def _create_legacy_tool_dropdown(self, parent):
3835
+ """Create the legacy dropdown menu for tool selection (fallback)."""
3836
+ # Create custom dropdown that opens upwards
3837
+ self.tool_menu = ttk.Menubutton(parent, textvariable=self.tool_var, direction="above", width=25)
3838
+ self.tool_menu.pack(side=tk.LEFT, padx=5)
3839
+
3840
+ # Create the dropdown menu
3841
+ self.tool_dropdown_menu = tk.Menu(self.tool_menu, tearoff=0)
3842
+ self.tool_menu.config(menu=self.tool_dropdown_menu)
3843
+
3844
+ # Populate the dropdown menu with all tools
3845
+ self.update_tool_dropdown_menu()
3846
+
3847
+ self.tool_search_palette = None # Not available in legacy mode
3848
+ self.logger.info("Using legacy tool dropdown (ToolSearchPalette not available)")
3849
+
3850
+ # Sub-tools that should redirect to their parent category
3851
+ SUB_TOOL_TO_PARENT = {
3852
+ # AI Tools (11 sub-tools)
3853
+ "Google AI": "AI Tools",
3854
+ "Vertex AI": "AI Tools",
3855
+ "Azure AI": "AI Tools",
3856
+ "Anthropic AI": "AI Tools",
3857
+ "OpenAI": "AI Tools",
3858
+ "Cohere AI": "AI Tools",
3859
+ "HuggingFace AI": "AI Tools",
3860
+ "Groq AI": "AI Tools",
3861
+ "OpenRouterAI": "AI Tools",
3862
+ "LM Studio": "AI Tools",
3863
+ "AWS Bedrock": "AI Tools",
3864
+ # Generator Tools (8 sub-tools)
3865
+ "Strong Password Generator": "Generator Tools",
3866
+ "Repeating Text Generator": "Generator Tools",
3867
+ "Lorem Ipsum Generator": "Generator Tools",
3868
+ "UUID/GUID Generator": "Generator Tools",
3869
+ "Random Email Generator": "Generator Tools",
3870
+ "ASCII Art Generator": "Generator Tools",
3871
+ "Hash Generator": "Generator Tools",
3872
+ "Slug Generator": "Generator Tools",
3873
+ # Extraction Tools (4 sub-tools)
3874
+ "Email Extraction": "Extraction Tools",
3875
+ "HTML Tool": "Extraction Tools",
3876
+ "Regex Extractor": "Extraction Tools",
3877
+ "URL Link Extractor": "Extraction Tools",
3878
+ # Line Tools (6 sub-tools)
3879
+ "Remove Duplicates": "Line Tools",
3880
+ "Remove Empty Lines": "Line Tools",
3881
+ "Add Line Numbers": "Line Tools",
3882
+ "Remove Line Numbers": "Line Tools",
3883
+ "Reverse Lines": "Line Tools",
3884
+ "Shuffle Lines": "Line Tools",
3885
+ # Markdown Tools (5 sub-tools)
3886
+ "Strip Markdown": "Markdown Tools",
3887
+ "Extract Links": "Markdown Tools",
3888
+ "Extract Headers": "Markdown Tools",
3889
+ "Table to CSV": "Markdown Tools",
3890
+ "Format Table": "Markdown Tools",
3891
+ # Sorter Tools (2 sub-tools)
3892
+ "Number Sorter": "Sorter Tools",
3893
+ "Alphabetical Sorter": "Sorter Tools",
3894
+ # Text Wrapper (5 sub-tools)
3895
+ "Word Wrap": "Text Wrapper",
3896
+ "Justify Text": "Text Wrapper",
3897
+ "Prefix/Suffix": "Text Wrapper",
3898
+ "Indent Text": "Text Wrapper",
3899
+ "Quote Text": "Text Wrapper",
3900
+ # Translator Tools (2 sub-tools)
3901
+ "Morse Code Translator": "Translator Tools",
3902
+ "Binary Code Translator": "Translator Tools",
3903
+ # Whitespace Tools (4 sub-tools)
3904
+ "Trim Lines": "Whitespace Tools",
3905
+ "Remove Extra Spaces": "Whitespace Tools",
3906
+ "Tabs/Spaces Converter": "Whitespace Tools",
3907
+ "Normalize Line Endings": "Whitespace Tools",
3908
+ # Other sub-tools
3909
+ "Word Frequency Counter": "Text Statistics",
3910
+ }
3911
+
3912
+ # Tab index within parent category for sub-tools (0-indexed)
3913
+ # Based on actual tab order in UI from screenshot
3914
+ SUB_TOOL_TAB_INDEX = {
3915
+ # AI Tools tabs (11 tabs):
3916
+ "Google AI": 0,
3917
+ "Vertex AI": 1,
3918
+ "Azure AI": 2,
3919
+ "Anthropic AI": 3,
3920
+ "OpenAI": 4,
3921
+ "Cohere AI": 5,
3922
+ "HuggingFace AI": 6,
3923
+ "Groq AI": 7,
3924
+ "OpenRouterAI": 8,
3925
+ "LM Studio": 9,
3926
+ "AWS Bedrock": 10,
3927
+ # Generator Tools tabs (8 tabs):
3928
+ "Strong Password Generator": 0,
3929
+ "Repeating Text Generator": 1,
3930
+ "Lorem Ipsum Generator": 2,
3931
+ "UUID/GUID Generator": 3,
3932
+ "Random Email Generator": 4,
3933
+ "ASCII Art Generator": 5,
3934
+ "Hash Generator": 6,
3935
+ "Slug Generator": 7,
3936
+ # Extraction Tools tabs (4 tabs):
3937
+ "Email Extraction": 0,
3938
+ "HTML Tool": 1,
3939
+ "Regex Extractor": 2,
3940
+ "URL Link Extractor": 3,
3941
+ # Line Tools tabs (6 tabs):
3942
+ "Remove Duplicates": 0,
3943
+ "Remove Empty Lines": 1,
3944
+ "Add Line Numbers": 2,
3945
+ "Remove Line Numbers": 3,
3946
+ "Reverse Lines": 4,
3947
+ "Shuffle Lines": 5,
3948
+ # Markdown Tools tabs (5 tabs):
3949
+ "Strip Markdown": 0,
3950
+ "Extract Links": 1,
3951
+ "Extract Headers": 2,
3952
+ "Table to CSV": 3,
3953
+ "Format Table": 4,
3954
+ # Sorter Tools tabs (2 tabs):
3955
+ "Number Sorter": 0,
3956
+ "Alphabetical Sorter": 1,
3957
+ # Text Wrapper tabs (5 tabs):
3958
+ "Word Wrap": 0,
3959
+ "Justify Text": 1,
3960
+ "Prefix/Suffix": 2,
3961
+ "Indent Text": 3,
3962
+ "Quote Text": 4,
3963
+ # Translator Tools tabs (2 tabs):
3964
+ "Morse Code Translator": 0,
3965
+ "Binary Code Translator": 1,
3966
+ # Whitespace Tools tabs (4 tabs):
3967
+ "Trim Lines": 0,
3968
+ "Remove Extra Spaces": 1,
3969
+ "Tabs/Spaces Converter": 2,
3970
+ "Normalize Line Endings": 3,
3971
+ }
3972
+
3973
+ def _on_palette_tool_selected(self, tool_name):
3974
+ """Handle tool selection from the ToolSearchPalette."""
3975
+ # Map sub-tools to their parent category
3976
+ actual_tool = self.SUB_TOOL_TO_PARENT.get(tool_name, tool_name)
3977
+ sub_tool_tab = self.SUB_TOOL_TAB_INDEX.get(tool_name)
3978
+
3979
+ if actual_tool != tool_name:
3980
+ self.logger.debug(f"Mapped sub-tool '{tool_name}' to parent '{actual_tool}' (tab {sub_tool_tab})")
3981
+
3982
+ self.tool_var.set(actual_tool)
3983
+
3984
+ # Auto-expand options panel if collapsed
3985
+ if hasattr(self, 'options_collapsible') and self.options_collapsible:
3986
+ if self.options_collapsible.collapsed:
3987
+ self.options_collapsible.expand()
3988
+ self.logger.debug("Auto-expanded options panel on tool select")
3989
+
3990
+ self.on_tool_selected()
3991
+
3992
+ # Schedule tab selection after UI updates (if sub-tool with tab index)
3993
+ if sub_tool_tab is not None:
3994
+ self.after(100, lambda: self._select_tool_tab(actual_tool, sub_tool_tab))
3995
+
3996
+ def _select_tool_tab(self, tool_name: str, tab_index: int):
3997
+ """Select a specific tab within a tool's tabbed interface."""
3998
+ try:
3999
+ # Recursively find notebooks in tool_settings_frame
4000
+ def find_notebook(widget):
4001
+ if isinstance(widget, ttk.Notebook):
4002
+ return widget
4003
+ for child in widget.winfo_children():
4004
+ result = find_notebook(child)
4005
+ if result:
4006
+ return result
4007
+ return None
4008
+
4009
+ notebook = find_notebook(self.tool_settings_frame)
4010
+ if notebook:
4011
+ if 0 <= tab_index < notebook.index('end'):
4012
+ notebook.select(tab_index)
4013
+ self.logger.debug(f"Selected tab {tab_index} for {tool_name}")
4014
+ else:
4015
+ self.logger.warning(f"Tab index {tab_index} out of range for {tool_name}")
4016
+ except Exception as e:
4017
+ self.logger.warning(f"Could not select tab {tab_index} for {tool_name}: {e}")
4018
+
4019
+ def _on_ui_settings_change(self, settings_update):
4020
+ """Handle UI settings change from ToolSearchPalette."""
4021
+ try:
4022
+ # Merge with existing settings
4023
+ if "ui_layout" in settings_update:
4024
+ current_layout = self.settings.get("ui_layout", {})
4025
+ current_layout.update(settings_update["ui_layout"])
4026
+ self.settings["ui_layout"] = current_layout
4027
+
4028
+ # Save settings
4029
+ if hasattr(self, 'db_settings_manager'):
4030
+ self.db_settings_manager.save_settings(self.settings)
4031
+ except Exception as e:
4032
+ self.logger.warning(f"Error saving UI settings: {e}")
4033
+
4034
+ def _on_options_panel_toggle(self, collapsed: bool):
4035
+ """Handle options panel toggle (save collapsed state to settings)."""
4036
+ try:
4037
+ current_layout = self.settings.get("ui_layout", {})
4038
+ current_layout["options_panel_collapsed"] = collapsed
4039
+ self.settings["ui_layout"] = current_layout
4040
+
4041
+ # Save settings
4042
+ if hasattr(self, 'db_settings_manager'):
4043
+ self.db_settings_manager.save_settings(self.settings)
4044
+
4045
+ self.logger.debug(f"Options panel collapsed: {collapsed}")
4046
+ except Exception as e:
4047
+ self.logger.warning(f"Error saving options panel state: {e}")
4048
+
4049
+ def toggle_options_panel(self, event=None):
4050
+ """Toggle the options panel visibility (Ctrl+Shift+H shortcut)."""
4051
+ if hasattr(self, 'options_collapsible') and self.options_collapsible:
4052
+ self.options_collapsible.toggle()
4053
+ return "break"
4054
+ return None
4055
+
4056
+ def focus_tool_search(self, event=None):
4057
+ """Focus the tool search entry (Ctrl+K shortcut)."""
4058
+ if self.tool_search_palette:
4059
+ self.tool_search_palette.focus_search()
4060
+ return "break"
4061
+ return None
3734
4062
 
3735
4063
  def update_tool_dropdown_menu(self):
3736
4064
  """Updates the tool dropdown menu with all available tools."""
4065
+ if self.tool_dropdown_menu is None:
4066
+ return # Using ToolSearchPalette instead
4067
+
3737
4068
  # Clear existing menu items
3738
4069
  self.tool_dropdown_menu.delete(0, tk.END)
3739
4070
 
@@ -3827,7 +4158,8 @@ class PromeraAIApp(tk.Tk):
3827
4158
  self.curl_tool_window,
3828
4159
  logger=self.logger,
3829
4160
  send_to_input_callback=self.send_content_to_input_tab,
3830
- dialog_manager=self.dialog_manager
4161
+ dialog_manager=self.dialog_manager,
4162
+ db_settings_manager=getattr(self, 'db_settings_manager', None)
3831
4163
  )
3832
4164
 
3833
4165
  # Add context menus to cURL tool widgets
@@ -4455,8 +4787,8 @@ class PromeraAIApp(tk.Tk):
4455
4787
  self.after(10, self.diff_viewer_widget.update_tab_labels)
4456
4788
  self.diff_viewer_widget.run_comparison()
4457
4789
  else:
4458
- # Show placeholder when module not available
4459
- self.diff_frame.grid(row=0, column=0, sticky="nsew", pady=5)
4790
+ # Show placeholder when module not available (row=1 same as central_frame)
4791
+ self.diff_frame.grid(row=1, column=0, sticky="nsew", pady=5)
4460
4792
  self.update_tool_settings_ui()
4461
4793
  else:
4462
4794
  # Sync diff viewer content before switching away
@@ -4732,33 +5064,37 @@ class PromeraAIApp(tk.Tk):
4732
5064
 
4733
5065
  self._current_tool_ui = tool_name
4734
5066
  tool_settings = self.settings["tool_settings"].get(tool_name, {})
4735
-
5067
+
5068
+ # Use tool_settings_frame directly as parent
5069
+ # Note: Centering was causing layout issues (e.g. Folder File Reporter cut off)
5070
+ parent_frame = self.tool_settings_frame
5071
+
4736
5072
  if tool_name in ["Google AI", "Anthropic AI", "OpenAI", "Cohere AI", "HuggingFace AI", "Groq AI", "OpenRouterAI"]:
4737
- self.create_ai_provider_widgets(self.tool_settings_frame, tool_name)
5073
+ self.create_ai_provider_widgets(parent_frame, tool_name)
4738
5074
  elif tool_name == "AI Tools":
4739
- self.create_ai_tools_widget(self.tool_settings_frame)
5075
+ self.create_ai_tools_widget(parent_frame)
4740
5076
  elif tool_name == "Case Tool":
4741
5077
  if CASE_TOOL_MODULE_AVAILABLE and self.case_tool:
4742
5078
  self.case_tool_ui = self.case_tool.create_ui(
4743
- self.tool_settings_frame,
5079
+ parent_frame,
4744
5080
  tool_settings,
4745
5081
  on_setting_change_callback=self.on_tool_setting_change,
4746
5082
  apply_tool_callback=self.apply_tool
4747
5083
  )
4748
5084
  else:
4749
- ttk.Label(self.tool_settings_frame, text="Case Tool module not available").pack()
5085
+ ttk.Label(parent_frame, text="Case Tool module not available").pack()
4750
5086
  elif tool_name == "Translator Tools":
4751
- self.create_translator_tools_widget(self.tool_settings_frame)
5087
+ self.create_translator_tools_widget(parent_frame)
4752
5088
  elif tool_name == "Base64 Encoder/Decoder":
4753
- self.create_base64_tools_widget(self.tool_settings_frame)
5089
+ self.create_base64_tools_widget(parent_frame)
4754
5090
  elif tool_name == "JSON/XML Tool":
4755
- self.create_jsonxml_tool_widget(self.tool_settings_frame)
5091
+ self.create_jsonxml_tool_widget(parent_frame)
4756
5092
  elif tool_name == "Cron Tool":
4757
- self.create_cron_tool_widget(self.tool_settings_frame)
5093
+ self.create_cron_tool_widget(parent_frame)
4758
5094
  elif tool_name == "Extraction Tools":
4759
- self.create_extraction_tools_widget(self.tool_settings_frame)
5095
+ self.create_extraction_tools_widget(parent_frame)
4760
5096
  elif tool_name == "Sorter Tools":
4761
- self.create_sorter_tools_widget(self.tool_settings_frame)
5097
+ self.create_sorter_tools_widget(parent_frame)
4762
5098
  elif tool_name == "Find & Replace Text":
4763
5099
  if FIND_REPLACE_MODULE_AVAILABLE and self.find_replace_widget:
4764
5100
  # Set up callback for settings changes
@@ -4771,36 +5107,36 @@ class PromeraAIApp(tk.Tk):
4771
5107
  self.input_notebook, self.output_notebook
4772
5108
  )
4773
5109
  # Create the widgets
4774
- self.find_replace_widget.create_widgets(self.tool_settings_frame, tool_settings)
5110
+ self.find_replace_widget.create_widgets(parent_frame, tool_settings)
4775
5111
  else:
4776
5112
  # Find & Replace module not available
4777
- ttk.Label(self.tool_settings_frame, text="Find & Replace module not available").pack()
5113
+ ttk.Label(parent_frame, text="Find & Replace module not available").pack()
4778
5114
  elif tool_name == "Generator Tools":
4779
- self.create_generator_tools_widget(self.tool_settings_frame)
5115
+ self.create_generator_tools_widget(parent_frame)
4780
5116
  elif tool_name == "URL Parser":
4781
5117
  if URL_PARSER_MODULE_AVAILABLE and self.url_parser:
4782
5118
  tool_settings = self.settings["tool_settings"].get("URL Parser", {
4783
5119
  "ascii_decode": True
4784
5120
  })
4785
5121
  self.url_parser_ui = self.url_parser.create_ui(
4786
- self.tool_settings_frame,
5122
+ parent_frame,
4787
5123
  tool_settings,
4788
5124
  on_setting_change_callback=self.on_tool_setting_change,
4789
5125
  apply_tool_callback=self.apply_tool
4790
5126
  )
4791
5127
  else:
4792
- ttk.Label(self.tool_settings_frame, text="URL Parser module not available").pack()
5128
+ ttk.Label(parent_frame, text="URL Parser module not available").pack()
4793
5129
  elif tool_name == "Diff Viewer":
4794
5130
  if DIFF_VIEWER_MODULE_AVAILABLE:
4795
5131
  # Use the new modular settings widget
4796
5132
  self.diff_settings_widget = DiffViewerSettingsWidget(
4797
- self.tool_settings_frame,
5133
+ parent_frame,
4798
5134
  self.diff_viewer_widget,
4799
5135
  self.on_tool_setting_change
4800
5136
  )
4801
5137
  else:
4802
5138
  # Module not available - show message
4803
- ttk.Label(self.tool_settings_frame, text="Diff Viewer module not available").pack(side=tk.LEFT, padx=10)
5139
+ ttk.Label(parent_frame, text="Diff Viewer module not available").pack(side=tk.LEFT, padx=10)
4804
5140
  elif tool_name == "Email Extraction Tool":
4805
5141
  if EMAIL_EXTRACTION_MODULE_AVAILABLE and self.email_extraction_tool:
4806
5142
  tool_settings = self.settings["tool_settings"].get("Email Extraction Tool", {
@@ -4810,13 +5146,13 @@ class PromeraAIApp(tk.Tk):
4810
5146
  "only_domain": False
4811
5147
  })
4812
5148
  self.email_extraction_ui = self.email_extraction_tool.create_ui(
4813
- self.tool_settings_frame,
5149
+ parent_frame,
4814
5150
  tool_settings,
4815
5151
  on_setting_change_callback=self.on_tool_setting_change,
4816
5152
  apply_tool_callback=self.apply_tool
4817
5153
  )
4818
5154
  else:
4819
- ttk.Label(self.tool_settings_frame, text="Email Extraction Tool module not available").pack()
5155
+ ttk.Label(parent_frame, text="Email Extraction Tool module not available").pack()
4820
5156
  elif tool_name == "Email Header Analyzer":
4821
5157
  if EMAIL_HEADER_ANALYZER_MODULE_AVAILABLE and self.email_header_analyzer:
4822
5158
  tool_settings = self.settings["tool_settings"].get("Email Header Analyzer", {
@@ -4826,13 +5162,13 @@ class PromeraAIApp(tk.Tk):
4826
5162
  "show_spam_score": True
4827
5163
  })
4828
5164
  self.email_header_analyzer_ui = self.email_header_analyzer.create_ui(
4829
- self.tool_settings_frame,
5165
+ parent_frame,
4830
5166
  tool_settings,
4831
5167
  on_setting_change_callback=self.on_tool_setting_change,
4832
5168
  apply_tool_callback=self.apply_tool
4833
5169
  )
4834
5170
  else:
4835
- ttk.Label(self.tool_settings_frame, text="Email Header Analyzer module not available").pack()
5171
+ ttk.Label(parent_frame, text="Email Header Analyzer module not available").pack()
4836
5172
  elif tool_name == "Folder File Reporter":
4837
5173
  if FOLDER_FILE_REPORTER_MODULE_AVAILABLE and self.folder_file_reporter:
4838
5174
  tool_settings = self.settings["tool_settings"].get("Folder File Reporter", {
@@ -4840,29 +5176,29 @@ class PromeraAIApp(tk.Tk):
4840
5176
  "last_output_folder": ""
4841
5177
  })
4842
5178
  self.folder_file_reporter.load_settings(tool_settings)
4843
- self.folder_file_reporter_ui = self.folder_file_reporter.create_ui(self.tool_settings_frame)
5179
+ self.folder_file_reporter_ui = self.folder_file_reporter.create_ui(parent_frame)
4844
5180
  else:
4845
- ttk.Label(self.tool_settings_frame, text="Folder File Reporter module not available").pack()
5181
+ ttk.Label(parent_frame, text="Folder File Reporter module not available").pack()
4846
5182
  elif tool_name == "Line Tools":
4847
- self.create_line_tools_widget(self.tool_settings_frame)
5183
+ self.create_line_tools_widget(parent_frame)
4848
5184
  elif tool_name == "Whitespace Tools":
4849
- self.create_whitespace_tools_widget(self.tool_settings_frame)
5185
+ self.create_whitespace_tools_widget(parent_frame)
4850
5186
  elif tool_name == "Text Statistics":
4851
- self.create_text_statistics_widget(self.tool_settings_frame)
5187
+ self.create_text_statistics_widget(parent_frame)
4852
5188
  elif tool_name == "Markdown Tools":
4853
- self.create_markdown_tools_widget(self.tool_settings_frame)
5189
+ self.create_markdown_tools_widget(parent_frame)
4854
5190
  elif tool_name == "String Escape Tool":
4855
- self.create_string_escape_tool_widget(self.tool_settings_frame)
5191
+ self.create_string_escape_tool_widget(parent_frame)
4856
5192
  elif tool_name == "Number Base Converter":
4857
- self.create_number_base_converter_widget(self.tool_settings_frame)
5193
+ self.create_number_base_converter_widget(parent_frame)
4858
5194
  elif tool_name == "Text Wrapper":
4859
- self.create_text_wrapper_widget(self.tool_settings_frame)
5195
+ self.create_text_wrapper_widget(parent_frame)
4860
5196
  elif tool_name == "Slug Generator":
4861
- self.create_slug_generator_widget(self.tool_settings_frame)
5197
+ self.create_slug_generator_widget(parent_frame)
4862
5198
  elif tool_name == "Column Tools":
4863
- self.create_column_tools_widget(self.tool_settings_frame)
5199
+ self.create_column_tools_widget(parent_frame)
4864
5200
  elif tool_name == "Timestamp Converter":
4865
- self.create_timestamp_converter_widget(self.tool_settings_frame)
5201
+ self.create_timestamp_converter_widget(parent_frame)
4866
5202
 
4867
5203
 
4868
5204