From 23f90d8dba934139dfd7b2e618d96a6b73512742 Mon Sep 17 00:00:00 2001 From: deucebucket Date: Tue, 7 Apr 2026 01:14:43 -0500 Subject: [PATCH] Feat #110: Add folder triage UI - dashboard stats, library badges, settings toggle Backend folder triage (clean/messy/garbage classification) was already implemented but invisible in the UI. Now surfaces triage data: - Dashboard info banner showing messy/garbage folder counts - Library view badges on affected books (Messy/Garbage indicators) - Settings toggle to enable/disable folder triage - Triage data in "all" library API responses Split push corrections to #205 (blocked on Skaldleita backend). --- CHANGELOG.md | 11 +++++++++++ README.md | 2 +- app.py | 21 +++++++++++++++------ library_manager/hints.py | 3 +++ templates/dashboard.html | 21 +++++++++++++++++++++ templates/library.html | 8 ++++++++ templates/settings.html | 11 +++++++++++ 7 files changed, 70 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 823ec7c..fee571d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to Library Manager will be documented in this file. +## [0.9.0-beta.146] - 2026-04-07 + +### Added + +- **Issue #110: Folder triage UI** - Dashboard now shows messy/garbage folder counts in an + info banner. Library view displays triage badges (Messy/Garbage) on affected books. Added + Settings toggle to enable/disable folder triage. Triage data now included in "all" library + view API responses. Split push corrections feature to #205 (blocked on Skaldleita). + +--- + ## [0.9.0-beta.145] - 2026-04-07 ### Added diff --git a/README.md b/README.md index ff83754..95ecbcb 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ **Smart Audiobook Library Organizer with Multi-Source Metadata & AI Verification** -[![Version](https://img.shields.io/badge/version-0.9.0--beta.145-blue.svg)](CHANGELOG.md) +[![Version](https://img.shields.io/badge/version-0.9.0--beta.146-blue.svg)](CHANGELOG.md) [![Docker](https://img.shields.io/badge/docker-ghcr.io-blue.svg)](https://ghcr.io/deucebucket/library-manager) [![License](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](LICENSE) diff --git a/app.py b/app.py index bd4b705..76dd44c 100644 --- a/app.py +++ b/app.py @@ -11,7 +11,7 @@ - Multi-provider AI (Gemini, OpenRouter, Ollama) """ -APP_VERSION = "0.9.0-beta.145" +APP_VERSION = "0.9.0-beta.146" GITHUB_REPO = "deucebucket/library-manager" # Your GitHub repo # Versioning Guide: @@ -4924,6 +4924,7 @@ def deep_scan_library(config): validation_counts = {'valid': 0, 'invalid': 0, 'skipped': 0} # Issue #110: File validation stats issues_found = {} # path -> list of issues triage_counts = {'clean': 0, 'messy': 0, 'garbage': 0} # Issue #110: Folder triage stats + triage_enabled = config.get('enable_folder_triage', True) # Issue #110: Folder triage toggle # Issue #110: Check ffmpeg availability once at scan start ffmpeg_available, ffmpeg_msg = check_ffmpeg_available() @@ -5111,7 +5112,7 @@ def deep_scan_library(config): # Issue #132: Resolve path to prevent duplicates flat_path = str(author_dir.resolve()) # Issue #110: Triage folder name quality - flat_triage = triage_folder(author) + flat_triage = triage_folder(author) if triage_enabled else 'clean' checked += 1 @@ -5293,7 +5294,7 @@ def deep_scan_library(config): checked += 1 # Issue #110: Triage folder name quality - series_book_triage = triage_folder(book_title) + series_book_triage = triage_folder(book_title) if triage_enabled else 'clean' # Check if already tracked c.execute('''SELECT id, status, profile, user_locked, attempt_count, @@ -5370,7 +5371,7 @@ def deep_scan_library(config): checked += 1 # Issue #110: Triage folder name quality - folder_triage_result = triage_folder(title) + folder_triage_result = triage_folder(title) if triage_enabled else 'clean' triage_counts[folder_triage_result] = triage_counts.get(folder_triage_result, 0) + 1 if folder_triage_result != 'clean': logger.info(f"Folder triage: {folder_triage_result} - {title[:60]}") @@ -7032,6 +7033,11 @@ def dashboard(): c.execute("SELECT COUNT(*) as count FROM books WHERE validation_status = 'invalid'") validation_failed_count = c.fetchone()['count'] + # Issue #110: Count folder triage categories + c.execute("SELECT folder_triage, COUNT(*) as count FROM books WHERE folder_triage != 'clean' GROUP BY folder_triage") + triage_rows = c.fetchall() + triage_counts = {row['folder_triage']: row['count'] for row in triage_rows} + # Get recent history (use LEFT JOIN in case book was deleted) c.execute('''SELECT h.*, b.path FROM history h LEFT JOIN books b ON h.book_id = b.id @@ -7054,6 +7060,7 @@ def dashboard(): verified_count=verified_count, pending_fixes=pending_fixes, validation_failed_count=validation_failed_count, + triage_counts=triage_counts, recent_history=recent_history, daily_stats=daily_stats, config=config, @@ -7314,6 +7321,7 @@ def settings_page(): config['enable_file_validation'] = 'enable_file_validation' in request.form config['min_audio_duration_seconds'] = int(request.form.get('min_audio_duration_seconds', 600)) config['min_audio_file_size_mb'] = int(request.form.get('min_audio_file_size_mb', 1)) + config['enable_folder_triage'] = 'enable_folder_triage' in request.form # Provider chain settings - parse comma-separated values into lists audio_chain_str = request.form.get('audio_provider_chain', 'bookdb,gemini').strip() @@ -9817,7 +9825,7 @@ def build_order_by(sort_cols, default_order): else: # 'all' - show everything from books table order = build_order_by(BOOK_SORT_COLS, 'current_author ASC, current_title ASC') c.execute('''SELECT b.id, b.path, b.current_author, b.current_title, b.status, - b.user_locked, b.confidence, b.media_type, + b.user_locked, b.confidence, b.media_type, b.folder_triage, h.old_author, h.old_title, h.new_author, h.new_title, h.old_path, h.new_path, h.status as history_status, h.fixed_at, h.error_message @@ -9856,7 +9864,8 @@ def build_order_by(sort_cols, default_order): 'status': history_status or book_status, 'confidence': row['confidence'] or 0, 'user_locked': row['user_locked'] == 1, - 'media_type': row['media_type'] or 'audiobook' + 'media_type': row['media_type'] or 'audiobook', + 'folder_triage': row['folder_triage'] or 'clean' } # Overlay history data when present if history_status: diff --git a/library_manager/hints.py b/library_manager/hints.py index 8f72c9b..3d26d6d 100644 --- a/library_manager/hints.py +++ b/library_manager/hints.py @@ -109,6 +109,9 @@ # === Post-Processing Hooks === 'post_processing': 'Run external scripts or webhooks after a book is successfully renamed. Use for M4B conversion, Audiobookshelf library scans, Discord notifications, backup scripts, etc. Hook failures never undo a successful rename.', + # === Folder Triage === + 'folder_triage': 'Classifies folder names as clean, messy (scene tags, torrent markers), or garbage (hashes, generic names). Messy and garbage folders skip path-based hints and rely on audio/metadata identification only.', + # === Plugins === 'custom_api_sources': 'Add your own book metadata APIs as processing layers. Each source queries an HTTP endpoint and maps the response into the book profile system.', 'python_plugins': 'Drop-in Python plugins for advanced users. Place a plugin folder in /data/plugins/ with a manifest.json and a Python file extending BasePlugin. Plugins are auto-discovered on startup.', diff --git a/templates/dashboard.html b/templates/dashboard.html index 500bcec..2d1d497 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -66,6 +66,27 @@ {% endif %} +{% if triage_counts is defined and (triage_counts.get('messy', 0) > 0 or triage_counts.get('garbage', 0) > 0) %} +
+
+ +
+
+{% endif %} +
+ +
+ + +
+