# Change History ### Version 0.4.6 (23.07.2026) * **Remote-aware metadata handling:** * Metadata-based search (`semantic-metadata` mode) no longer triggers silent downloads of remote files. Cache keys for auto-descriptions now use only file stats (path + size + mtime + model hash) instead of a content hash, so the first lookup after a restart — or the first time a remote file is ever seen — only performs a cheap stat, never reads file content. * For remote files, `MetadataSearch.generate_full_description` (used both by the search hot path and the rating scheduler) now includes only `filename + path + .meta sidecar`. Auto-descriptions, internal file metadata (EXIF / ID3 / moviepy), and embedding proxies are intentionally skipped — they would each require downloading the original file. * **Faster search hot path on first contact:** * `get_file_hash` is no longer called anywhere on the search hot path. Cache lookups for both `auto_desc::` and `meta::` use stat-only keys, so an empty `.meta` cache directory no longer forces a download of every file just to look up the description. * **Safe background schedulers:** * `make_scheduled_description_check` no longer crashes: it now iterates only `osfs:///mnt/media/` (local-only) instead of the previously-missing `file_manager.list_all_files()`. Remote files continue to be skipped entirely from the description scheduler. * Both the description scheduler and the embedding scheduler remain restricted to local files. Only the rating scheduler walks remote files, and only because ratings are derived from the (now-safe) thin description path. * **`.meta` reading is now hard-capped at 32 KB:** * Previously the cap was `30 000 chars OR 300 lines` — small `.meta` files with very long lines could exceed the byte budget. The new `_MAX_META_BYTES = 128 * 1024` makes the cap unambiguous and matches the documented "downloaded up to N kb" behaviour for remote files. ### Version 0.4.5 (21.07.2026) * **Search Performance & Reliability:** * "Content-based" semantic search was iterating the full list of matched files on every progress update to count cache hits inside the status callback, making the lookup O(N²) instead of O(N). Replaced the per-iteration list scan with a running counter; subsequent searches over cached embeddings now complete almost immediately. * "Content-based" semantic search now skips remote (WebDAV/SFTP/FTP) files instead of force-downloading their content to embed it. Only local files are processed for content-based matching; remote files remain searchable via filename and metadata modes. Previously, browsing a folder that contained remote files with this mode enabled could hang or time out. * **Bug Fixes:** * Fixed semantic search in the music module. It was failing for some files silently falling back to plain text search or returning nothing. * Fixed semantic search in the text module. File metadata extraction was hanging on large files because `get_text_metadata` read the entire file every time; it is now a stat-only O(1) operation. File previews are now capped at the first 4 KiB so even multi-gigabyte logs load instantly. * Files with no computed embeddings yet were appearing in the result list as if they had a real score, making sort-by-rating unreliable. Such files are now hidden from the list and counted separately so the user can see what is still being processed in the background. * Fixed metadata extraction process for text files. * **Background Embedding Computation:** * New automatic background scheduler for the Music module that periodically scans the cache and computes embeddings for any files that don't have one yet. Configurable via `embedding_update_interval_minutes` (default 10) and `embedding_update_batch_size` (default 1000) in `config.yaml`. New files added to your library now get their embeddings in the background so they become searchable almost immediately, without requiring a manual batch run. ### Version 0.4.4 (18.07.2026) * **Search Performance & Reliability:** * Search queries are now embedded on CPU directly in the main process, so searching no longer competes with background tasks (rating, description, embedding) for GPU time. The app stays responsive even while heavy background work is running. (This feature is not yet fully complete, though) * "Content-based" semantic search was silently returning text-search results after the recent refactor and now correctly produces real semantic matches again. * "Find similar" search (asking for files similar to a given file) was failing for files on remote or VFS-mounted drives and silently falling back to a plain text search. Fixed. * **Background Embedding Computation:** * Images module now has a new automatic background scheduler that computes missing embeddings every 10 minutes (configurable via `embedding_update_interval_minutes` and `embedding_update_batch_size` in `config.yaml`). Files added to your library are indexed in the background so they become searchable almost immediately, instead of waiting for the next manual batch run. * **Bug Fixes:** * "Show full search description" was returning an empty modal. Now displays the full metadata payload used to compute the file's search embeddings. * Files with no computed embeddings yet (e.g. newly added files) were appearing in the result list as if they had a real score, making sort-by-rating unreliable. Such files are now hidden from the list and counted separately so the user can see what is still being processed in the background. * TwoLevelCache was reloading the same disk shard on every read, causing unnecessary repeated I/O. Added an in-memory cache so after the first read of a shard, subsequent reads are served from RAM. * **UI & Themes:** * Added a new "Matrix" theme with falling katakana rain, green-on-black phosphor colors and occasional glitch bursts on images. Available alongside the existing Light, Dark and Solarized options. * Status bar now reports `"Showed X of Y files in Zs. N file(s) still unindexed."` when some files in a folder are still being indexed in the background, making it clearer what is happening. ### Version 0.4.3 (15.07.2026) * **Remote Severs Support:** * Now the project could also fetch and play data from the remote servers. For now the remote servers could only be added manually by editing `project_data/config.yaml` file. To support remote servers all four major modules were completely refactored with use of [pyfilesystem2](https://github.com/pyfilesystem/pyfilesystem2) library in replace of typical `os` based file manipulation commands. Because of this major change external modules (WebSearch, YouTube) will not be able to work properly until the new way of working with files is adapted there. * **Removed hash dependency:** * To avoid expensive hash computations, especially for files that are stored on the remote server, the application now uses a new `file_path`-based key to find the score or (for example) play count history instead of the previous `file_hash`-based key approach. Unfortunately, this means that if you have moved or renamed files in your library, the application will not be able to find the score you have previously gave to that file. To mitigate this loss of highly important data, now each time you score a file (or when we migrate the score from the old database to the new one), the application will create new file in `project_data/memory` that contains all the metadata we were able to obtain about the file at the time of scoring. This includes but not limited to actual file path at the moment of scoring, the tags and fingerprint of the file generated by the appropriate embedding model, textual description of the file generated by the omni model, information from the associated `.meta` if it exists, and the score itself. Only the data from that `memory/` folder will be used for training the evaluator model from now on, while the scores in the DB used only for fast search and filtering routines. This way, even though the file_path->score association might be lost, the application will still be able to remember what kind of data was scored how and appropriately reevaluate the same kind of data to the same score after the evaluator model is retrained. * **Text Embedder change:** * Transformers library had a major security issue and have to be upgraded to version `transformers>=5.5.0`, unfortunately that's means that old `jina-embeddings-v3` text embedding model is no longer supported because of compatibility issues. So for now `Qwen/Qwen3-Embedding-0.6B` text embedding model is used instead. **HIGHLY IMPORTANT!** Before moving to this version create a backup of your project database file in `project_data/project.db` to avoid losing any of important data. Then remove 'project_data/migrations' folder completely and run the application. The application will automatically create a new migrations folder and migrate your database to the new version. ### Version 0.4.2 (12.06.2026) * **Files Handling Refactor:** * Files are now shared with single route `files/` instead of having separate routes for each module. This simplifies the codebase and makes it easier to maintain. * All modules are now using full paths of files instead of module's relative paths. This unifies the file handling across modules and allows for simpler remote server integration. All other relative references to files in the codebase have been updated as well. * **App Factory Refactor:** * App initialization code inside overgrown `app.py` file has been refactored into a multiple files in `src/app_factory` with factory pattern for easier initialization management in the future. * **Music and Images Autotags:** * Music and Images autotags expanded inside `config.yaml` for more variety and better coverage. * **Remote Server Support:** * Some initial experiment with remote server support has been done as well. ### Version 0.4.1 (29.05.2026) * **Search & Filtering Performance:** * Searching in `metadata-based` mode no longer triggers automatic descriptions from the omni model, making searches much faster when there are undescribed files in the library. All omni-model description generation now happens exclusively in background tasks. * Removed the inline `update_model_ratings` call from the `rating` filter in `CommonFilters`. Previously, every time a user sorted files by rating, the application would synchronously compute AI model ratings for any unrated files before returning results, blocking the entire response until the model had processed every file in the folder. Now the `rating` filter simply reads whatever scores are already in the database and returns immediately. Background schedulers running in the task manager keep ratings up to date, so by the time the user browses a folder the files are typically already rated. * Removed `update_model_ratings` and `evaluator_hash` parameters from `FileManager.get_files()` and from all `get_files()` across all modules. The net effect is that all search and filtering operations, including sort-by-rating, are now free of any model inference or database writes on the hot path, making every browse and filter request noticeably faster regardless of library size. * **Stale Rating Indicators:** * Each file card now shows a subtle visual hint next to the model rating when the score is out of date or missing. A faint outlined question-mark icon appears when the rating was produced by an older version of the model and is waiting to be refreshed, and the same icon appears when a file has not been rated yet at all. Hovering the icon shows a short explanation. This applies to all modules - images, music, videos, text, YouTube, and WebSearch. * **Background Rating Priority:** * When the background scheduler runs its rating pass, it now always processes files that have never been rated before processing files whose rating is simply out of date. Previously both groups were mixed together, which could leave newly added files waiting behind a large backlog of re-rating work. Now a freshly added file is guaranteed to get its first rating as soon as possible, while stale re-ratings are handled afterwards. ### Version 0.4.0 (25.05.2026) * **Automated Testing:** * Added a full automated test suite covering the core logic of the application. Tests run inside Docker with no GPU or model downloads required. * The suite covers configuration loading, caching (both in-memory and on-disk), embedding quantization, file management, text search filters, task management, database import/export, and path traversal security. * A dedicated security test set verifies that path traversal attacks — including URL-encoded and double-encoded variants — are correctly blocked across query parameters, JSON bodies, and form data. * Stale inline tests in the music recommendation engine were updated to match the current API. * `tests/commands.sh` and `tests/testing_strategy.md` have been updated to document the new test tiers and how to run them. * **Bug Fixes:** * Fixed a crash in the database import function where updating an existing row would fail silently or raise an error. Rows are now updated correctly when a matching entry is found by hash or file path. * Fixed a stale test configuration in the text embedder that used an outdated setting name, causing the manual model test to fail on startup. * **Config style** * Migrated `music -> embedding_model` and `images -> embedding_model` out of their module sections into dedicated top-level embedder sections, consistent with the existing `text_embedder` pattern. * **Release Tests** * All automatic and manual testing are performed to make sure that all of projects' features works correctly with the clean-state installation. ### Version 0.3.18 (21.05.2026) * **Subprocess Deadlock Fix:** * All five subprocess proxy classes (`TextEmbedder`, `ImageEmbedder`, `AudioEmbedder`, `OmniDescriptor`, `UniversalEvaluator`) were using a single `queue.get(timeout=48h)` call while holding their internal lock. If a worker process died unexpectedly (OOM kill, CUDA context invalidated after GPU suspend/resume), the queue never received a response and the main thread held the lock indefinitely, freezing the entire application silently with no error logged. * Replaced the single long timeout with a 5-second polling loop that checks `process.is_alive()` on each iteration. When a dead subprocess is detected, the lock is released within ≤5 s, a descriptive `RuntimeError` (including the exit code) is raised and logged, and the next request transparently restarts the worker. * **Subprocess Watchdog:** * Added a daemon thread in `app.py` that logs subprocess health and main-process RAM every 60 seconds to the app log. Each line shows the state of all five proxy subprocesses (`unused` / `idle` / `alive(pid=…)` / `DEAD(exit=…)`) alongside current RAM usage, providing a clear timestamp trail for diagnosing crashes. * **Search Status Leaking:** * `show_search_status` had a broadcast fallback that emitted `emit_show_search_status` to every connected browser tab whenever it was called without a socket request context (i.e. from a background thread). Scheduled tasks such as background rating and description generation run in the task-manager worker thread, which has no request context, so every progress message they emitted flooded all open tabs. Fixed by silently dropping the emit when there is no target SID; background task progress is already visible in the Task Manager UI. * **Background Task:** * When the scheduler automatically rated unrated files in the background, the Task Manager modal showed no progress bar movement and the Stop button had no effect. This was because the rating functions had no way to report progress or check for cancellation. The task context is now passed through so that the progress bar fills in real time as files are processed, and pressing Stop cleanly cancels the task at the next file boundary. This applies to all four modules (images, music, videos, text). ### Version 0.3.17 (19.05.2026) * **Subprocess Embeddings:** * SigLIP (image embeddings) and CLAP (audio embeddings) no longer live inside the main application process. Each is now loaded on demand in its own dedicated subprocess and automatically shut down after 120 seconds of inactivity. VRAM is only occupied while a search or embedding pass is actively running. * The main process no longer initialises a CUDA context at all during idle time, eliminating the ~200 MB of baseline VRAM usage that was previously always present. * All tests for both embedders include a lifecycle check: the subprocess is confirmed to start, become idle, shut down, and transparently restart on the next request — without any change visible to callers. * **UniversalEvaluator:** * `UniversalEvaluator` (the `TransformerEvaluator` used for scoring files across all modules) has been moved from `modules/train/universal_train.py` to `src/universal_evaluator.py` and converted to a subprocess proxy following the same pattern as `TextEmbedder`, `ImageEmbedder`, and `AudioEmbedder`. All torch/CUDA operations now execute exclusively in a dedicated worker process. * The worker subprocess is started lazily on first use and terminated automatically after 120 seconds of inactivity, releasing GPU memory between scoring passes. * Training (`train_universal_evaluator`) now runs the full epoch loop inside the subprocess. Progress is streamed back to the main process epoch-by-epoch via a shared queue, so the UI chart and task-manager progress bar continue to update in real time without any data being exchanged per-epoch. * All six serve modules (`images`, `music`, `videos`, `text`, `WebSearch`, `YouTube`) now import `UniversalEvaluator` from `src.universal_evaluator` directly. * **Full CUDA Isolation from main process:** * Removed all `torch.cuda.is_available()` calls from the proxy `__init__` methods of `TextEmbedder`, `ImageEmbedder`, `AudioEmbedder`, and `OmniDescriptor`. Proxies now default to `device='cpu'` and update the mirrored attribute after the subprocess reports back. * Fixed `modules/videos/engine.py` stub: hardcoded `device='cpu'` (was computing it via `torch.cuda.is_available()` and returning `.to(self.device)` zero tensors that could hit CUDA). * Removed `configure_device()` calls from `app.py` — scoring models and embedders now detect their target device independently inside their respective subprocesses. * Removed stale `import src.scoring_models` from `modules/images/serve.py` and `modules/text/engine.py`, and `from src.scoring_models import Evaluator` from `modules/train/serve.py` (these were unused dead imports). * **Docker Build:** * Added a `.dockerignore` file so that large runtime directories (`cache/`, `models/`, `logs/`, `project_data/`) are excluded from the Docker build context. This prevents permission errors on cached files and speeds up all image builds. ### Version 0.3.16 (17.05.2026) * **Embedding Proxy:** * Both the Images and Music modules now use the shared `UniversalEvaluator` for scoring files, replacing their previous module-specific evaluators. The separate "Train music evaluator" and "Train image evaluator" buttons have been removed from the Training page; only the "Train universal evaluator" button remains. * Added an embedding proxy system for Images and Music. Files that do not yet have an automatic OmniDescriptor description are now assigned a temporary text proxy derived from their SigLIP (images) or CLAP (music) embedding. The proxy includes zero-shot semantic tags matched from a configurable vocabulary and a compact quantized fingerprint, giving the universal evaluator meaningful content to score from immediately, even for files that have never been described. * Files that fail to generate an embedding (e.g. corrupt or unreadable files) now show a clear error message in the proxy section instead of silently producing no output or an identical misleading result shared across all failed files. * Fixed a CLAP model compatibility issue with newer `transformers` versions where audio files were silently failing to process, causing zero embeddings to be stored and all music files to show the same proxy. Affected files are automatically re-processed on the next rating pass. * **Scheduler:** * Created single `Scheduler` class living entirely in `scheduler.py`. Starting a scheduler is now just creating an instance: `Scheduler(app, interval_minutes=..., fn=..., name=..., check_fn=...)`. No more function-with-a-handle split; the class owns both the state and the thread. * **UI:** * Added a **Schedulers** section to the Background Tasks modal. Each registered scheduler shows its name, how often it runs, and a live countdown to the next fire. Schedulers can be paused and resumed directly from the modal; pausing freezes the countdown and the task will not fire until resumed. * Schedulers are grouped by module (e.g. all "Music:" entries appear under a "Music" heading), and the module prefix is stripped from the individual row labels to avoid repetition. * The Schedulers section is placed at the bottom of the modal, below Running, Queued, and Recent, so active task information remains prominent. * **Docker Deployment:** * Reduced the Docker image size by switching to a lighter base image and using a two-stage build process. Build tools needed only during installation (compilers, git, etc.) are no longer included in the final image, cutting several hundred megabytes of unnecessary overhead. The application itself and all its Python dependencies are unaffected. * Added `gcc` and `libc6-dev` to the runtime image, which are required by the quantization libraries (`bitsandbytes`/`triton`) that compile small GPU helper utilities on first use. ### Version 0.3.15 (13.05.2026) * **Scheduler:** * Removed manual dedup guards (`startswith(base_name)` active/queued checks) from `src/module_helpers.py` (`make_scheduled_rating_check` and `make_scheduled_description_check`) and from `modules/_module_template/serve.py`. The scheduler's cooldown-after-completion model is the correct mechanism to prevent accumulation; the in-function guards were redundant and masked the real issue. * Added a `start_immediately` option to `schedule_task`. When enabled, the scheduled task fires once right away on startup instead of waiting for the first interval to pass. Defaults to the previous behavior (wait first). * **Docker Deployment:** * Fixed a bug where module-specific Python packages (e.g. `beautifulsoup4` from `WebSearch`) were silently not installed. When Docker copied all module `requirements.txt` files into a flat temporary directory they all had the same filename, so each one overwrote the previous and only the last one (alphabetically) survived. The Dockerfile now copies the entire `modules/` folder structure so each module keeps its own path, and all requirements are correctly installed. * **UI:** * Added a list view mode to `FileGridComponent`. A small toggle in the top-right corner of any file grid lets you switch between the existing grid layout and a new horizontal list layout. In list mode each row shows the file preview on the left, its metadata in the middle, and a short text excerpt on the right. The chosen view is saved per-module in the browser so it is remembered across page refreshes. * Folder lists no longer re-sort themselves alphabetically in the browser. The order returned by the server is now used as-is, making it possible to control folder ordering from the backend. * Fixed text overflow in the task manager panel. Long task names and progress messages now truncate with an ellipsis instead of pushing other elements out of place. * **File Manager:** * Hidden files and folders (names starting with `.`) are now skipped when scanning media directories, preventing dot-files like `.DS_Store` or `.Trash` from being treated as media. * **Background Description Generation:** * Temporarily disabled the automatic background description scheduling for Images, Music, and Videos modules while the feature is being stabilized. ### Version 0.3.14 (26.04.2026) * **TransformerEvaluator:** * Replaced the fixed sinusoidal positional encoding (`register_buffer`) with a trainable `nn.Embedding(max_seq_len + 1, d_model)` table (position 0 = CLS, 1–512 = chunk positions), initialized with `std=0.02`. The model now learns what positional information is useful for rating prediction rather than relying on a fixed mathematical formula. Batch size when training increased to 32, learning rate is also adjusted. All of this significantly improved the text evaluator's training behavior make it improving for the test accuracy on the span of the whole training run, rather than plateauing after a few hundred epochs. * **Solarized Light Theme:** * Fixed hover and active states rendering as achromatic gray. Added `--bulma-scheme-h/s` (44°/60%) so all Bulma-computed interactive states stay in the warm solarized palette. Pinned `--bulma-background-hover/active` and `--bulma-border-hover/active` to explicit warm solarized hex values for clear state feedback. * Improved text contrast: body text darkened from 45% to 38% lightness, secondary text from 60% to 52%, so all text is more legible on the cream Base3 background. * Fixed muddy same-hue text on colored backgrounds (`is-info`, `is-primary`, etc.). Bulma's default invert for solarized accent colors produced dark-tinted text on a same-hue background. All accent inverts now use white, giving proper contrast on buttons, tags, panels, and progress bars. * Images now render with a warm sepia filter (`sepia(30%) saturate(85%)`) to blend with the cream palette; hovering restores full color. * **Database Migration:** * Fixed issue with migrations for staled database. Introduced naming conventions for columns that should help to avoid migration issues in the future. * **Docker Deployment:** * Dockerfile updated to better handle issues with external `Youtube` module. Particularly `Node.js 20` now installed to support the latest `yt-dlp` versions. * **Scheduler:** * Changed `schedule_task` from a fixed-interval timer to a *cooldown-after-completion* model. The new cycle is: sleep N minutes → fire → wait for the submitted task to finish → sleep N minutes → repeat. Previously the interval was wall-clock based, so a long-running description or rating batch would accumulate duplicate submissions in the queue by the time it finished. Now the N-minute gap always starts *after* the previous task is done. * Added `TaskManager.wait_for_task(task_id)` — polls at 2-second intervals until the task leaves the active/queued state. Used internally by the scheduler; safe to call from any thread. * Both factory functions in `src/module_helpers.py` (`make_scheduled_rating_check`, `make_scheduled_description_check`) and the `_module_template` equivalents now `return` the task id from `app.task_manager.submit()` so the scheduler can track completion. ### Version 0.3.13 (12.04.2026) * **File Manager:** * Fixed bug displaying error when trying to show files in the empty folder. Now the message "No files found in this folder" is displayed instead of an error. * **Docker Deployment:** * If error occurs during the installation of the dependencies of the container (for example, due to incorrect media folder path), the error message is now displayed in the console. ### Version 0.3.12 (10.04.2026) * **Module Template:** * Updated `modules/_module_template/` to match the actual module architecture. Updated `serve.py`, `engine.py`, `train.py`, `db_models.py`, `page.html`, `js/main.js`, `config.defaults.yaml`, `ARCHITECTURE.md`, `BEST_PRACTICES.md`, and `README.md` with accurate examples, conventions, and documentation reflecting the current codebase. * **Code Deduplication:** * Extracted shared Python helpers (`src/module_helpers.py`) for `.meta` file handlers and scheduled background tasks (rating and description checks). All four modules now use these shared helpers instead of maintaining their own near-identical copies. * Extracted shared JavaScript factory (`modules/ModuleMetaEditors.js`) for creating MetaEditor instances. Images, Music, and Videos modules now use a single `createModuleMetaEditors(socket, moduleName)` call instead of duplicating the same MetaEditor setup code. * **Dead Code Removal:** * Removed unused `parse_terminal_command()` function from `src/file_manager.py`. * Removed unused imports across all module `serve.py` files. * Removed commented-out code blocks from Python and JavaScript files across all modules. ### Version 0.3.11 (08.04.2026) * **Background Ratings:** * Replaced the DB-first unrated file query (which was counting stale rows pointing to deleted/moved files as "unrated") with a disk-first approach. `FileManager.get_unrated_files(evaluator_hash)` now walks the media directory, hashes every file via the engine's path+mtime cache, and queries the DB to find which hashes lack a current rating — naturally ignoring files that no longer exist on disk. * `get_unrated_files()` calls `sync_file_paths()` first, so moved or renamed files have their DB `file_path` corrected before the rating lookup. Both operations share the same disk walk and hash pass, so there is no extra I/O. * All four module `_check_and_submit_rating()` functions simplified: `candidates = file_manager.get_unrated_files(evaluator_hash)` replaces the `unrated_q()` factory, `sync_file_paths()` call, and `os.path.exists()` filter that were needed before. * **Background Description Generation:** * Added proactive background description generation via `OmniDescriptor`, analogous to the existing rating scheduler. Every 10 minutes each module checks for files whose auto-description is not yet in the cache and submits a batch to the task manager. * `MetadataSearch.get_undescribed_files(file_paths)` probes the shared `TwoLevelCache` for each file's `auto_desc::` key using the current model hash. Returns `None` if the model hash is not yet known (model was never loaded since install), in which case the scheduler falls back to treating all files as candidates. * `MetadataSearch._get_omni_model_hash()` resolves the model hash from the proxy's in-memory `model_hash` first, then falls back to `omni_model_hash::{model_name}` in the shared cache. The hash is persisted to cache immediately after `OmniDescriptor.initiate()` completes, so it survives process restarts without reloading the model. * `FileManager.list_all_files()` added — returns all media file paths on disk via the existing mtime-cached walker, without hashing. Used by the description scheduler (no DB involved). * All four modules (`images`, `music`, `text`, `videos`) have a new `_check_and_submit_description()` function and a corresponding `schedule_task` registration at module startup. * `config.yaml` gains `description_update_interval_minutes` (default: 10) and `description_update_batch_size` (default: 100; 50 for videos) per module. Set to `null` to disable. ### Version 0.3.10 (05.04.2026) * **OmniDescriptor:** * Now new, optimized version of the `MiniCPM-o-4.5` model is used for generating descriptions. New model is provided by [Dystrio](https://huggingface.co/dystrio/MiniCPM-o-4_5-Sculpt-Throughput) and is pruned specifically for fast description generation and lower memory footprint, providing faster token generation speed and bigger context window. * **Task Manager:** * Added a centralised background task queue (`src/task_manager.py`) with a single worker thread. Tasks run sequentially; each task receives a `TaskContext` that supports cooperative pause, resume, and cancel via `threading.Event`, and throttled progress updates (`ctx.update(progress, message)`) at 250 ms intervals. * Added `TaskManagerComponent.js` — an IIFE frontend component that renders a navbar badge showing the number of active/queued tasks, and a Bulma modal with running, queued, and history sections. Individual tasks can be paused, resumed, cancelled, or removed from history directly in the UI. * `TaskManager` is instantiated in `app.py` immediately after `SocketIO` and exposed as `app.task_manager` so all modules can reach it. * **Training — Background Processing:** * Migrated music evaluator, image evaluator, and universal evaluator training handlers to `task_manager.submit()`. The `TRAINING_ACTIVE` global flag is replaced by inspecting the task queue for active/queued `Train:` tasks. Training progress is reported via `TaskContext` in addition to the existing Train-page chart socket events. * Fixed a pre-existing bug in the training callback where `socketio.emit("emit_train_page_display_train_data", data)` was called unconditionally even when the throttle condition was false, potentially referencing an undefined `data` variable. * **Proactive Background Rating:** * Each media module now periodically scans the DB for files with a missing or stale model rating and submits them to the Task Manager in the background. By the time a user browses to a folder its files are typically already rated, making the inline rating step faster. * The check interval is configured per-module in `config.yaml` via `rating_update_interval_minutes` (set to `null` to disable). * Added `src/scheduler.py` with a `schedule_task(app, interval_minutes, name, fn)` utility used by all four modules. * Added `FileManager.sync_file_paths()` which walks the media directory on disk, compares file hashes against the DB, and corrects any stale `file_path` values (e.g. from moved files). Returns the number of rows updated. Each module's `_task_preemptive_rating` calls this first to ensure paths are current, then runs its own DB query to find files with a missing or outdated model rating. ### Version 0.3.9 (23.03.2026) * **Color Themes:** * Added three color themes: **Light** (default), **Dark**, and **Solarized Light**. The active theme is persisted in `localStorage` and applied before first paint to avoid flash of unstyled content. * Theme switcher dropdown added to the navbar, accessible on all pages. * All hardcoded colors across HTML templates and JavaScript components replaced with Bulma CSS variables so they respond correctly to theme changes. ### Version 0.3.8 (21.03.2026) * **Universal Evaluator Training** * Added modal window that controls how much time you want to spend on training the model. You can either set a number minutes to train the model or number of optimization steps to take. * Migrated all built-in modules (music, images, videos, text) to the same self-contained training data interface. Each module now owns a `train.py` with a `get_training_pairs(cfg, text_embedder, status_callback)` generator that handles its own DB queries, file reading, and embedding — no central registry needed. * Added `get_training_pairs()` to `modules/music/train.py` and `modules/images/train.py` (metadata strategy: generate a text description via `MetadataSearch` and embed with `text_embedder`). * Created `modules/videos/train.py` with the same metadata strategy. * Created `modules/text/train.py` supporting two strategies switchable via `cfg.evaluator.text_embedding_method`: `"full_text"` (default) embeds the raw file content directly; `"metadata"` generates a short description via `MetadataSearch` instead, useful if you want the evaluator to reason about summaries rather than raw content. Switching strategies requires no code changes, just a config update. * Removed the legacy `_MODULE_DEFS` registry, `_LEGACY_MODULE_NAMES` exclusion list, `_import_attr` helper, `_gather_rated_files()`, and the two-phase embedding pipeline (Phase A full-text pre-computation + Phase B per-file metadata loop) from `universal_train.py`. All of that complexity now lives inside each module's own `train.py`. * `_gather_from_module_train_files()` now covers all modules without any exclusions. Adding a new module to universal evaluator training is as simple as dropping a `train.py` with `get_training_pairs()` into its folder — no changes to core training code ever required. * Updated `modules/_module_template/train.py` documentation to reflect the removal of the legacy exclusion list. ### Version 0.3.7 (15.03.2026) * **Universal Evaluator - Training Improvements:** * Added three toggleable training augmentation strategies at the top of `universal_train.py`: **nonsensical negatives** (`ENABLE_NONSENSICAL_NEGATIVES`) injects random-noise embeddings mapped to score 0 to teach the model that meaningless content should rank lowest; **oversampling** (`ENABLE_OVERSAMPLING`) duplicates underrepresented score bins up to the median bin count; **loss weighting** (`ENABLE_LOSS_WEIGHTING`) applies per-sample inverse-frequency weights so rare scores contribute proportionally more to the gradient. * Fixed `TransformerEvaluator.reinitialize()` to fully rebuild the model from scratch via a new `_build_transformer_net()` helper. The previous implementation iterated `nn.Linear` / `nn.LayerNorm` submodules and called `reset_parameters()`, but `nn.MultiheadAttention` stores its Q/K/V projections as raw `nn.Parameter` tensors that are not `nn.Linear` instances, so they survived every reinitialize call and carried weights from any previously loaded checkpoint into the new training run. * Fixed `ModelManager` idle-eviction firing mid-epoch. The cleanup thread checks a `_busy` flag, but that flag was only held during each individual forward pass. Between batches (`loss.backward()` → next forward) `_busy` was `False`, so the thread could move the model to CPU mid-epoch, corrupting the computation graph and causing the periodic sharp accuracy drops visible in the training graph. The flag is now set for the entire epoch loop and cleared in a `finally` block. * **OmniDescriptor - Inference Benchmarks (`src/omni_descriptor.py`):** * Added GPU inference speed benchmark (`benchmark_inference_speed()`) with a shared `_run_speed_probes()` core that runs two passes per probe - a prefill-only pass (`max_new_tokens=1`) and a full generation pass - to separately measure prefill throughput and generation throughput. * Added CPU inference speed benchmark (`benchmark_inference_speed_cpu()`). The model is temporarily loaded in `bfloat16` (≈16 GB RAM) via a new `_with_cpu_model(fn)` helper that swaps the GPU model out, loads a CPU copy with `init_vision=True` and `init_audio=True`, calls the benchmark, then restores the GPU model. `torch.set_num_threads(os.cpu_count())` is set to utilise all available CPU cores. * Added CPU context window benchmark (`benchmark_context_window_cpu()`) that probes increasing input sizes (128 → 4096 tokens) with RAM tracking via `psutil` and `resource.getrusage`, measuring peak RSS and free RAM at each step. * Fixed overly conservative context window recommendations: the input-token suggestion was clamped to the last successful probe size even when no probe had failed; added a `hit_limit` flag so clamping only applies when a probe actually errors or OOMs. Also removed a hardcoded `min(..., 1024)` cap on suggested output tokens, letting the 80/20 input/output split determine the value naturally. * **Project Structure - Folder Rename (`pages/` to `modules/`):** * Renamed the root `pages/` folder to `modules/` to better reflect its purpose as a container for self-contained feature modules rather than web pages. * Updated all references across the codebase: Python package imports (`from pages.X` → `from modules.X`), filesystem path strings in `app.py`, the Flask `template_folder` and `send_from_directory` calls, the `@app.route('/modules/...')` static-file route, JavaScript ES module imports (`/pages/ComponentName.js` → `/modules/ComponentName.js`), HTML `