@@ -146,6 +146,32 @@ def get_readme_first_line(log_dir: Path) -> str:
146146 return ""
147147
148148
149+ def get_creation_time_from_metadata (log_dir : Path ) -> str :
150+ """Extract creation time from metadata.json and format as MM/DD-HH:MM"""
151+ from datetime import datetime
152+
153+ metadata = load_metadata (log_dir )
154+ if not metadata :
155+ return ""
156+
157+ # Try to get creation timestamp from metadata
158+ timestamp = None
159+ if "created_timestamp" in metadata :
160+ timestamp = metadata ["created_timestamp" ]
161+ elif "game" in metadata and "created_timestamp" in metadata ["game" ]:
162+ timestamp = metadata ["game" ]["created_timestamp" ]
163+
164+ if timestamp :
165+ try :
166+ # Convert timestamp to datetime and format as MM/DD-HH:MM
167+ dt = datetime .fromtimestamp (timestamp )
168+ return dt .strftime ("%m/%d-%H:%M" )
169+ except (ValueError , OSError ):
170+ return ""
171+
172+ return ""
173+
174+
149175def get_agent_info_from_metadata (metadata : dict [str , Any ]) -> list [AgentInfo ]:
150176 """Extract detailed agent information from metadata"""
151177 agents = []
@@ -166,7 +192,7 @@ def get_agent_info_from_metadata(metadata: dict[str, Any]) -> list[AgentInfo]:
166192 return agents
167193
168194
169- def find_all_game_folders (base_dir : Path ) -> list [dict [str , Any ]]:
195+ def find_all_game_folders (base_dir : Path , sort_by : str = "name" , sort_order : str = "asc" ) -> list [dict [str , Any ]]:
170196 """Recursively find all folders and mark which ones contain metadata.json"""
171197 all_folders = []
172198 game_folders = set () # Track which folders are actual game folders
@@ -188,6 +214,7 @@ def scan_directory(directory: Path, relative_path: str = ""):
188214 models = get_models_from_metadata (item )
189215 readme_first_line = get_readme_first_line (item )
190216 game_name = get_game_name_from_metadata (item )
217+ creation_time = get_creation_time_from_metadata (item )
191218 game_folders .add (current_relative )
192219 all_folders .append (
193220 {
@@ -197,6 +224,7 @@ def scan_directory(directory: Path, relative_path: str = ""):
197224 "models" : models ,
198225 "readme_first_line" : readme_first_line ,
199226 "game_name" : game_name ,
227+ "creation_time" : creation_time ,
200228 "is_game" : True ,
201229 "depth" : depth ,
202230 "parent" : relative_path if relative_path else None ,
@@ -212,6 +240,7 @@ def scan_directory(directory: Path, relative_path: str = ""):
212240 "models" : [],
213241 "readme_first_line" : "" ,
214242 "game_name" : "" ,
243+ "creation_time" : "" ,
215244 "is_game" : False ,
216245 "depth" : depth ,
217246 "parent" : relative_path if relative_path else None ,
@@ -228,7 +257,7 @@ def scan_directory(directory: Path, relative_path: str = ""):
228257
229258 # Filter out intermediate folders that don't lead to any game folders
230259 filtered_folders = []
231- for folder in sorted ( all_folders , key = lambda x : x [ "name" ]) :
260+ for folder in all_folders :
232261 if folder ["is_game" ]:
233262 # Always include game folders
234263 filtered_folders .append (folder )
@@ -239,6 +268,32 @@ def scan_directory(directory: Path, relative_path: str = ""):
239268 if has_game_descendants :
240269 filtered_folders .append (folder )
241270
271+ # Sort the filtered folders
272+ def get_sort_key (folder ):
273+ if sort_by == "time" :
274+ # For time sorting, prioritize game folders and sort by creation time
275+ if folder ["is_game" ] and folder ["creation_time" ]:
276+ # Parse time back to comparable format for sorting
277+ try :
278+ from datetime import datetime
279+
280+ time_str = folder ["creation_time" ]
281+ # Convert MM/DD-HH:MM back to datetime for comparison
282+ # Assume current year for comparison
283+ current_year = datetime .now ().year
284+ dt = datetime .strptime (f"{ current_year } /{ time_str } " , "%Y/%m/%d-%H:%M" )
285+ return (0 , dt ) # 0 for game folders (higher priority)
286+ except (ValueError , TypeError ):
287+ return (1 , folder ["name" ]) # Fallback to name sorting
288+ else :
289+ return (1 , folder ["name" ]) # Non-game folders or no time, sort by name
290+ else : # sort_by == "name" or default
291+ # For name sorting, sort by folder depth first, then by name
292+ return (folder ["depth" ], folder ["name" ])
293+
294+ reverse_sort = sort_order == "desc"
295+ filtered_folders .sort (key = get_sort_key , reverse = reverse_sort )
296+
242297 return filtered_folders
243298
244299
@@ -813,9 +868,19 @@ def game_view(folder_path):
813868def game_picker ():
814869 """Game picker page with recursive folder support"""
815870 logs_dir = LOG_BASE_DIR
816- game_folders = find_all_game_folders (logs_dir )
871+ sort_by = request .args .get ("sort" , "name" ) # Default to name
872+ sort_order = request .args .get ("order" , "asc" ) # Default to ascending
873+
874+ game_folders = find_all_game_folders (logs_dir , sort_by = sort_by , sort_order = sort_order )
817875
818- return render_template ("picker.html" , game_folders = game_folders , base_dir = str (logs_dir ), is_static = STATIC_MODE )
876+ return render_template (
877+ "picker.html" ,
878+ game_folders = game_folders ,
879+ base_dir = str (logs_dir ),
880+ is_static = STATIC_MODE ,
881+ current_sort = sort_by ,
882+ current_order = sort_order ,
883+ )
819884
820885
821886@app .route ("/delete-experiment" , methods = ["POST" ])
0 commit comments