Files
DemonGatanjieu-Anomalous_Mo…/api/__init__.py
T
DemonGatanjieuandClaude Opus 5.5 e466e9dfcf feat(scan): list unmatched and unscanned models, show each scan's result, explain why
The scan page now says which models are unmatched or not scanned yet (type, folder,
why Civitai had nothing) and what each scan did, model by model; rows open the model,
scan it, look it up again or search Civitai. The progress box shows the counts and,
when floating, a way to the result. The activity log records a scan when it ends with
its result, and model edits (from -> to) and deletions (files moved) in detail.

- scraper.py reports one JSON line per changed model (--report-file); a failing model
  no longer ends the scan. Unmatched info keeps why (not_found / network / offline);
  online scans look network/offline ones up again. After two models in a row without
  an answer the rest of the scan infers from the files (--civitai-down for the next
  folders) instead of waiting out every timeout.
- civitai_client.py: the network side moved out of scraper.py; tells a 404 from
  Civitai being unreachable (CivitaiUnreachable).
- api/scan_report.py: ScanJob turns a scan's reports into one result, kept as the last
  scan (/anomalous/last_scan) and logged; /anomalous/scan_model opens a listed model.
- /anomalous/scan_all takes picked or listed models as `targets`, so one scan has one
  result; scan_summary lists unmatched and new models with their reason.
- Activity middleware takes details a handler attaches (request[ACTIVITY_DETAIL]).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 02:52:17 +08:00

120 lines
7.9 KiB
Python

from . import (
activity_log, audio_catalog, folder_types, gallery_routes, materials, media_routes, model_catalog,
model_media, model_metadata, model_resolution, model_type_listing, recipe_packages, recipes, scan_report, scan_summary,
translation_routes, version_manager,
)
from .scanner import *
from .config import *
from .notebooks import *
from .parameters import *
from aiohttp import web
EXTENSION_STATIC_PREFIX = '/extensions/Anomalous_Model_Browser/'
@web.middleware
async def no_cache_extension_middleware(request, handler):
"""Make browsers revalidate this plugin's JS/CSS so a normal refresh picks up updates.
`no-cache` still allows 304 responses via ETag/Last-Modified. This replaces
per-import `?v=` query strings, which load a second module instance whenever
two files import the same module with different URLs.
"""
response = await handler(request)
if request.path.startswith(EXTENSION_STATIC_PREFIX):
response.headers['Cache-Control'] = 'no-cache'
return response
def setup_routes(app):
if hasattr(app, 'middlewares') and no_cache_extension_middleware not in app.middlewares:
app.middlewares.append(no_cache_extension_middleware)
app.router.add_get('/anomalous/folders', model_catalog.api_get_folders)
app.router.add_get('/anomalous/all_folder_types', folder_types.api_get_all_folder_types)
app.router.add_get('/anomalous/models', model_catalog.api_get_models)
app.router.add_get('/anomalous/type_models', model_type_listing.api_get_type_models)
app.router.add_get('/anomalous/all_scan_models', model_catalog.api_get_all_scan_models)
app.router.add_get('/anomalous/batch_select', model_catalog.api_batch_select)
app.router.add_get('/anomalous/image', media_routes.api_serve_image)
app.router.add_post('/anomalous/scan', api_scan_folder)
app.router.add_get('/anomalous/scan_status', api_scan_status)
app.router.add_get('/anomalous/find_model', model_catalog.api_find_model)
app.router.add_get('/anomalous/config', api_get_config)
app.router.add_post('/anomalous/save_config', api_save_config)
app.router.add_post('/anomalous/delete_model', model_metadata.api_delete_model)
app.router.add_get('/anomalous/compatible_models', model_catalog.api_compatible_models)
app.router.add_get('/anomalous/notebooks', api_get_notebooks)
app.router.add_post('/anomalous/save_notebook', api_save_notebook)
app.router.add_post('/anomalous/delete_notebook', api_delete_notebook)
# Parameter Routes
app.router.add_get('/anomalous/parameters', api_get_parameters)
app.router.add_get('/anomalous/parameters/by_node_type', api_get_parameters_by_type)
app.router.add_post('/anomalous/save_parameter', api_save_parameter)
app.router.add_post('/anomalous/rename_parameter', api_rename_parameter)
app.router.add_post('/anomalous/delete_parameter', api_delete_parameter)
app.router.add_get('/anomalous/parameter_gallery', api_get_parameter_gallery)
# Recipe Routes
app.router.add_get('/anomalous/recipes', recipes.api_get_recipes)
app.router.add_get('/anomalous/recipe_full', recipes.api_get_recipe_full)
app.router.add_get('/anomalous/recipe_asset', recipes.api_get_recipe_asset)
app.router.add_get('/anomalous/recipe_gallery', recipes.api_get_recipe_gallery)
app.router.add_get('/anomalous/recipe_parameter_gallery', recipes.api_get_recipe_parameter_gallery)
app.router.add_get('/anomalous/recipe_gallery_compare', recipes.api_get_recipe_gallery_compare)
app.router.add_post('/anomalous/save_recipe', recipes.api_save_recipe)
app.router.add_post('/anomalous/update_recipe', recipes.api_update_recipe)
app.router.add_post('/anomalous/set_recipe_gallery_cover', recipes.api_set_recipe_gallery_cover)
app.router.add_post('/anomalous/delete_recipe', recipes.api_delete_recipe)
app.router.add_get('/anomalous/recipe_history', recipes.api_get_recipe_history)
app.router.add_get('/anomalous/recipe_version', recipes.api_get_recipe_version)
app.router.add_post('/anomalous/restore_recipe_version', recipes.api_restore_recipe_version)
app.router.add_post('/anomalous/refresh_recipe_identity', recipes.api_refresh_recipe_identity)
app.router.add_post('/anomalous/export_recipe_package', recipe_packages.api_export_recipe_package)
app.router.add_post('/anomalous/import_recipe_package_inspect', recipe_packages.api_import_recipe_package_inspect)
app.router.add_post('/anomalous/import_recipe_package_commit', recipe_packages.api_import_recipe_package_commit)
# Curated Material Library Routes
app.router.add_get('/anomalous/materials', materials.api_get_materials)
app.router.add_get('/anomalous/material_full', materials.api_get_material_full)
app.router.add_get('/anomalous/material_asset', materials.api_get_material_asset)
app.router.add_get('/anomalous/materials/by_node_type', materials.api_get_materials_by_node_type)
app.router.add_post('/anomalous/inspect_image_material', materials.api_inspect_image_material)
app.router.add_post('/anomalous/save_image_material', materials.api_save_image_material)
app.router.add_post('/anomalous/save_parameter_material', materials.api_save_parameter_material)
app.router.add_post('/anomalous/save_prompt_note_material', materials.api_save_prompt_note_material)
app.router.add_post('/anomalous/save_prompt_plan', materials.api_save_prompt_plan)
app.router.add_post('/anomalous/delete_material', materials.api_delete_material)
app.router.add_post('/anomalous/update_material', materials.api_update_material)
app.router.add_post('/anomalous/translate', translation_routes.api_translate)
app.router.add_get('/anomalous/base_models', model_catalog.api_base_models)
app.router.add_get('/anomalous/gallery_images', gallery_routes.api_get_gallery_images)
app.router.add_post('/anomalous/delete_gallery_image', gallery_routes.api_delete_gallery_image)
app.router.add_get('/anomalous/output_thumbnail', gallery_routes.api_output_thumbnail)
app.router.add_get('/anomalous/resolve_hash', model_resolution.api_resolve_hash)
app.router.add_post('/anomalous/resolve_hash_batch', model_resolution.api_resolve_hash_batch)
app.router.add_get('/anomalous/all_hashes', model_resolution.api_get_all_hashes)
app.router.add_post('/anomalous/scan_all', api_scan_all)
app.router.add_get('/anomalous/global_scan_status', api_global_scan_status)
app.router.add_get('/anomalous/scan_missing_models_status', api_scan_missing_models_status)
app.router.add_post('/anomalous/clear_cache', media_routes.api_clear_cache)
app.router.add_post('/anomalous/update_metadata', model_metadata.api_update_metadata)
app.router.add_post('/anomalous/set_custom_cover', model_media.api_set_custom_cover)
app.router.add_post('/anomalous/upload_custom_cover', model_media.api_upload_custom_cover)
app.router.add_get('/anomalous/model_images', media_routes.api_get_model_images)
app.router.add_post('/anomalous/resolve_paths_to_previews', model_catalog.api_resolve_paths_to_previews)
app.router.add_post('/anomalous/scan_missing_models', api_scan_missing_models)
app.router.add_get('/anomalous/scan_summary', scan_summary.api_scan_summary)
app.router.add_get('/anomalous/last_scan', scan_report.api_last_scan)
app.router.add_get('/anomalous/scan_model', scan_report.api_scan_model)
# Audio & Voice Studio Routes
app.router.add_get('/anomalous/audio_stream', audio_catalog.api_serve_audio)
app.router.add_get('/anomalous/audio_gallery', audio_catalog.api_get_audio_gallery)
app.router.add_post('/anomalous/delete_audio_gallery', audio_catalog.api_delete_audio_gallery)
app.router.add_get('/anomalous/audio_previews', audio_catalog.api_get_audio_previews)
app.router.add_post('/anomalous/save_audio_preview', audio_catalog.api_save_audio_preview)
version_manager.register_routes(app)
activity_log.register_routes(app)