pomera-ai-commander 1.2.5 → 1.2.7

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