@@ -367,7 +367,19 @@ def __init__(self, root: tk.Tk):
367367 self .ik_llama_dir .set (self .app_settings .get ("last_ik_llama_dir" , "" ))
368368 self .venv_dir .set (self .app_settings .get ("last_venv_dir" , "" ))
369369 # Ensure model_dirs is loaded as list of Paths
370- self .model_dirs = [Path (d ) for d in self .app_settings .get ("model_dirs" , []) if d ]
370+ raw_model_dirs = self .app_settings .get ("model_dirs" , [])
371+ self .model_dirs = []
372+ for d in raw_model_dirs :
373+ if d :
374+ try :
375+ path_obj = Path (d ).resolve ()
376+ if path_obj .exists () and path_obj .is_dir ():
377+ self .model_dirs .append (path_obj )
378+ print (f"DEBUG: Loaded valid model directory: { path_obj } " , file = sys .stderr )
379+ else :
380+ print (f"WARNING: Skipping invalid model directory from config: { d } (resolved to { path_obj } )" , file = sys .stderr )
381+ except Exception as e :
382+ print (f"ERROR: Failed to process model directory from config '{ d } ': { e } " , file = sys .stderr )
371383 # Load custom parameters list
372384 self .custom_parameters_list = self .app_settings .get ("custom_parameters" , [])
373385 # Load backend selection
@@ -1506,16 +1518,30 @@ def _add_model_dir(self):
15061518 initial_dir = self .model_dirs [- 1 ] if self .model_dirs and self .model_dirs [- 1 ].is_dir () else str (Path .home ())
15071519 directory = filedialog .askdirectory (title = "Select Model Directory" , initialdir = initial_dir )
15081520 if directory :
1509- p = Path (directory ).resolve () # Resolve path to handle symlinks etc.
1510- # Add the resolved path string to avoid issues with comparison later
1511- p_str = str (p )
1512- if p_str not in [str (x ) for x in self .model_dirs ]: # Compare resolved paths
1513- self .model_dirs .append (p ) # Store as Path object
1514- self ._update_model_dirs_listbox ()
1515- self ._save_configs ()
1516- self ._trigger_scan ()
1517- else :
1518- messagebox .showinfo ("Info" , "Directory already in list." )
1521+ try :
1522+ p = Path (directory ).resolve () # Resolve path to handle symlinks etc.
1523+ # Validate that the resolved path actually exists and is a directory
1524+ if not p .exists ():
1525+ messagebox .showerror ("Error" , f"Selected directory does not exist:\n { p } " )
1526+ return
1527+ if not p .is_dir ():
1528+ messagebox .showerror ("Error" , f"Selected path is not a directory:\n { p } " )
1529+ return
1530+
1531+ # Add the resolved path string to avoid issues with comparison later
1532+ p_str = str (p )
1533+ print (f"DEBUG: Adding model directory - Original: '{ directory } ', Resolved: '{ p_str } '" , file = sys .stderr )
1534+
1535+ if p_str not in [str (x ) for x in self .model_dirs ]: # Compare resolved paths
1536+ self .model_dirs .append (p ) # Store as Path object
1537+ self ._update_model_dirs_listbox ()
1538+ self ._save_configs ()
1539+ self ._trigger_scan ()
1540+ else :
1541+ messagebox .showinfo ("Info" , "Directory already in list." )
1542+ except Exception as e :
1543+ messagebox .showerror ("Error" , f"Error processing directory path:\n { directory } \n \n Error: { e } " )
1544+ print (f"ERROR: Failed to process directory '{ directory } ': { e } " , file = sys .stderr )
15191545
15201546 def _remove_model_dir (self ):
15211547 selection = self .model_dirs_listbox .curselection ()
@@ -1624,64 +1650,62 @@ def _scan_model_dirs(self):
16241650 multipart_pattern = re .compile (r"^(.*?)(?:-\d{5}-of-\d{5}|-F\d+)\.gguf$" , re .IGNORECASE )
16251651 # Pattern to match the FIRST part of a multi-part file (e.g., model-00001-of-00005.gguf or model-F1.gguf)
16261652 first_part_pattern = re .compile (r"^(.*?)-(?:00001-of-\d{5}|F1)\.gguf$" , re .IGNORECASE )
1627- processed_multipart_bases = set ()
1628-
1653+
1654+ # Two-pass approach to handle multi-part files correctly
1655+ all_gguf_files = []
16291656 for model_dir in self .model_dirs :
16301657 # Skip invalid or non-existent directories silently during scan
16311658 if not isinstance (model_dir , Path ) or not model_dir .is_dir (): continue
16321659 print (f"DEBUG: Scanning directory: { model_dir } " , file = sys .stderr )
16331660 try :
1634- # Use rglob for recursive search
1661+ # Collect all GGUF files first
16351662 for gguf_path in model_dir .rglob ('*.gguf' ):
16361663 if not gguf_path .is_file (): continue
16371664 filename = gguf_path .name
16381665 # Skip non-model GGUF files often found with models
16391666 if "mmproj" in filename .lower () or filename .lower ().endswith (".bin.gguf" ):
16401667 continue
1641-
1642- # Handle multi-part files: only list the base name, pointing to the first part
1643- first_part_match = first_part_pattern .match (filename )
1644- if first_part_match :
1645- base_name = first_part_match .group (1 )
1646- # Ensure the base name corresponds to the *first* part before adding
1647- # Check if the resolved path points to this specific file
1648- try : # <-- This try block starts a new indentation level
1649- resolved_gguf_path = gguf_path .resolve ()
1650- if base_name not in processed_multipart_bases :
1651- # Store the path to the first part
1652- found [base_name ] = resolved_gguf_path
1653- processed_multipart_bases .add (base_name )
1654- # The 'except' needs to match the 'try' it belongs to
1655- except Exception as resolve_exc : # <-- Corrected indentation
1656- print (f"Warning: Could not resolve path '{ gguf_path } ' during scan: { resolve_exc } " , file = sys .stderr ) # Log resolve errors
1657- # The 'continue' needs to align with the 'if first_part_match:' block
1658- continue # <-- Corrected indentation
1659-
1660- # Handle subsequent parts of multi-part files: mark base as processed but don't add
1661- multi_match = multipart_pattern .match (filename )
1662- if multi_match :
1663- base_name = multi_match .group (1 )
1664- processed_multipart_bases .add (base_name )
1665- # The 'continue' needs to align with the 'if multi_match:' block
1666- continue # <-- Corrected indentation
1667-
1668- # Handle single-part files (not matching the multi-part patterns)
1669- if filename .lower ().endswith (".gguf" ) and gguf_path .stem not in processed_multipart_bases :
1670- display_name = gguf_path .stem
1671- try : # <-- This try block starts a new indentation level
1672- resolved_gguf_path = gguf_path .resolve ()
1673- # Only add if we haven't already added a multi-part version with the same base name
1674- if display_name not in found :
1675- found [display_name ] = resolved_gguf_path
1676- # The 'except' needs to match the 'try' it belongs to
1677- except Exception as resolve_exc : # <-- Corrected indentation
1678- print (f"Warning: Could not resolve path '{ gguf_path } ' during scan: { resolve_exc } " , file = sys .stderr ) # Log resolve errors
1679-
1680-
1668+ all_gguf_files .append (gguf_path )
16811669 except Exception as e :
16821670 print (f"ERROR: Error scanning directory { model_dir } : { e } " , file = sys .stderr )
16831671 traceback .print_exc (file = sys .stderr )
1684-
1672+
1673+ # First pass: find all first parts of multi-part files
1674+ processed_multipart_bases = set ()
1675+ for gguf_path in all_gguf_files :
1676+ filename = gguf_path .name
1677+ first_part_match = first_part_pattern .match (filename )
1678+ if first_part_match :
1679+ base_name = first_part_match .group (1 )
1680+ if base_name not in processed_multipart_bases :
1681+ try :
1682+ resolved_gguf_path = gguf_path .resolve ()
1683+ found [base_name ] = resolved_gguf_path
1684+ processed_multipart_bases .add (base_name )
1685+ print (f"DEBUG: Found multi-part model: { base_name } " , file = sys .stderr )
1686+ except Exception as resolve_exc :
1687+ print (f"Warning: Could not resolve path '{ gguf_path } ' during scan: { resolve_exc } " , file = sys .stderr )
1688+
1689+ # Second pass: find single-part files that aren't part of multi-part sets
1690+ for gguf_path in all_gguf_files :
1691+ filename = gguf_path .name
1692+
1693+ # Skip if this is any part of a multi-part file
1694+ if multipart_pattern .match (filename ):
1695+ continue
1696+
1697+ # Handle single-part files
1698+ if filename .lower ().endswith (".gguf" ):
1699+ display_name = gguf_path .stem
1700+ if display_name not in processed_multipart_bases and display_name not in found :
1701+ try :
1702+ resolved_gguf_path = gguf_path .resolve ()
1703+ found [display_name ] = resolved_gguf_path
1704+ print (f"DEBUG: Found single-part model: { display_name } " , file = sys .stderr )
1705+ except Exception as resolve_exc :
1706+ print (f"Warning: Could not resolve path '{ gguf_path } ' during scan: { resolve_exc } " , file = sys .stderr )
1707+
1708+ print (f"DEBUG: Scan completed, found { len (found )} models" , file = sys .stderr )
16851709 self .root .after (0 , self ._update_model_listbox_after_scan , found )
16861710
16871711 # ═════════════════════════════════════════════════════════════════
0 commit comments