unify category token normalization
This commit is contained in:
+17
-12
@@ -358,6 +358,22 @@ CIVARCHIVE_API_TYPE_MAP = {
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
|
||||
def normalize_category_token(category: Any) -> str:
|
||||
"""Normalize a raw category into the shared alias lookup token format."""
|
||||
token = (
|
||||
str(category or "")
|
||||
.strip()
|
||||
.lower()
|
||||
.replace("\\", "_")
|
||||
.replace("/", "_")
|
||||
.replace("-", "_")
|
||||
.replace(" ", "_")
|
||||
)
|
||||
while "__" in token:
|
||||
token = token.replace("__", "_")
|
||||
return token.strip("_")
|
||||
|
||||
|
||||
def resolve_model_category(
|
||||
category: Any,
|
||||
target_format: str = "folder",
|
||||
@@ -370,18 +386,7 @@ def resolve_model_category(
|
||||
- 'civarchive': CivArchive API type string (e.g. 'Checkpoint', 'LoCon')
|
||||
- 'urn': URN category string
|
||||
"""
|
||||
token = (
|
||||
str(category or "")
|
||||
.strip()
|
||||
.lower()
|
||||
.replace("\\", "_")
|
||||
.replace("/", "_")
|
||||
.replace("-", "_")
|
||||
.replace(" ", "_")
|
||||
)
|
||||
while "__" in token:
|
||||
token = token.replace("__", "_")
|
||||
token = token.strip("_")
|
||||
token = normalize_category_token(category)
|
||||
|
||||
if target_format == "civitai":
|
||||
canonical_folder = CATEGORY_MAP.get(token, token)
|
||||
|
||||
@@ -2,7 +2,12 @@ import json
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from core.type_utils import CATEGORY_MAP, normalize_download_category, resolve_model_category
|
||||
from core.type_utils import (
|
||||
CATEGORY_MAP,
|
||||
normalize_category_token,
|
||||
normalize_download_category,
|
||||
resolve_model_category,
|
||||
)
|
||||
|
||||
|
||||
class CategoryMappingTests(unittest.TestCase):
|
||||
@@ -38,12 +43,32 @@ class CategoryMappingTests(unittest.TestCase):
|
||||
self.assertEqual("checkpoints", normalize_download_category(None))
|
||||
self.assertEqual("new_category", normalize_download_category("new category"))
|
||||
|
||||
def test_category_token_contract_normalizes_common_separators(self):
|
||||
expected = {
|
||||
" TEXTUAL\\INVERSION ": ("textual_inversion", "embeddings"),
|
||||
"unet / gguf": ("unet_gguf", "diffusion_models"),
|
||||
"select---safetensors": ("select_safetensors", "diffusion_models"),
|
||||
"clip__vision": ("clip_vision", "clip_vision"),
|
||||
}
|
||||
|
||||
for raw_category, (token, canonical_category) in expected.items():
|
||||
self.assertEqual(
|
||||
token,
|
||||
normalize_category_token(raw_category),
|
||||
)
|
||||
self.assertEqual(
|
||||
canonical_category,
|
||||
normalize_download_category(raw_category),
|
||||
)
|
||||
|
||||
def test_frontend_category_alias_artifact_matches_backend_map(self):
|
||||
project_root = Path(__file__).resolve().parents[1]
|
||||
artifact = (
|
||||
project_root / "web" / "resolver" / "utils" / "category_aliases.generated.js"
|
||||
).read_text(encoding="utf-8")
|
||||
payload = artifact.split("Object.freeze(", 1)[1].rsplit(");", 1)[0].strip()
|
||||
payload = artifact.split(
|
||||
"export const CATEGORY_ALIASES = Object.freeze(\n", 1
|
||||
)[1].split("\n);\n", 1)[0]
|
||||
|
||||
self.assertEqual(CATEGORY_MAP, json.loads(payload))
|
||||
|
||||
|
||||
@@ -20,7 +20,10 @@ import {
|
||||
} from '../web/resolver/node_context_menu.js';
|
||||
import { startSplitterDrag } from '../web/resolver/utils/splitter_drag.js';
|
||||
import { normalizeDownloadCategoryValue } from '../web/resolver/utils/category_utils.js';
|
||||
import { CATEGORY_ALIASES } from '../web/resolver/utils/category_aliases.generated.js';
|
||||
import {
|
||||
CATEGORY_ALIASES,
|
||||
normalizeCategoryToken,
|
||||
} from '../web/resolver/utils/category_aliases.generated.js';
|
||||
import { getSha256Field, normalizeSha256 } from '../web/resolver/utils/hash_utils.js';
|
||||
import { getSourceDisplayLabel, normalizeSourceKey } from '../web/resolver/utils/source_labels.js';
|
||||
import { baseModelAliasMethods } from '../web/resolver/search/base_model_alias_methods.js';
|
||||
@@ -39,6 +42,7 @@ import {
|
||||
|
||||
void normalizeDownloadCategoryValue;
|
||||
void CATEGORY_ALIASES;
|
||||
void normalizeCategoryToken;
|
||||
void normalizeSha256;
|
||||
|
||||
const projectRoot = path.resolve(import.meta.dirname, '..');
|
||||
@@ -5993,6 +5997,25 @@ test('download category normalization uses the generated backend alias fallback'
|
||||
assert.equal(normalizeDownloadCategoryValue('control-net'), 'controlnet');
|
||||
});
|
||||
|
||||
test('download category normalization follows the shared separator contract', () => {
|
||||
const expected = [
|
||||
[' TEXTUAL\\INVERSION ', 'embeddings'],
|
||||
['unet / gguf', 'diffusion_models'],
|
||||
['select---safetensors', 'diffusion_models'],
|
||||
['clip__vision', 'clip_vision'],
|
||||
];
|
||||
|
||||
for (const [rawCategory, canonicalCategory] of expected) {
|
||||
assert.equal(normalizeCategoryToken(rawCategory), rawCategory
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[/\\\s-]+/g, '_')
|
||||
.replace(/_+/g, '_')
|
||||
.replace(/^_|_$/g, ''));
|
||||
assert.equal(normalizeDownloadCategoryValue(rawCategory), canonicalCategory);
|
||||
}
|
||||
});
|
||||
|
||||
test('missing model categories preserve node priority and fallback order', () => {
|
||||
const getMissingSupportedDownloadCategories = eval(
|
||||
`(${extractMethod(downloadTargetMethodsSource, 'getMissingSupportedDownloadCategories')})`
|
||||
|
||||
@@ -16,6 +16,14 @@ def main() -> None:
|
||||
payload = json.dumps(CATEGORY_MAP, ensure_ascii=False, indent=4, sort_keys=True)
|
||||
content = (
|
||||
"// Generated by tools/generate_category_aliases.py. Do not edit manually.\n"
|
||||
"export function normalizeCategoryToken(category) {\n"
|
||||
" return String(category || '')\n"
|
||||
" .trim()\n"
|
||||
" .toLowerCase()\n"
|
||||
" .replace(/[/\\\\\\s-]+/g, '_')\n"
|
||||
" .replace(/_+/g, '_')\n"
|
||||
" .replace(/^_|_$/g, '');\n"
|
||||
"}\n\n"
|
||||
"export const CATEGORY_ALIASES = Object.freeze(\n"
|
||||
f"{payload}\n"
|
||||
");\n"
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
// Generated by tools/generate_category_aliases.py. Do not edit manually.
|
||||
export function normalizeCategoryToken(category) {
|
||||
return String(category || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[/\\\s-]+/g, '_')
|
||||
.replace(/_+/g, '_')
|
||||
.replace(/^_|_$/g, '');
|
||||
}
|
||||
|
||||
export const CATEGORY_ALIASES = Object.freeze(
|
||||
{
|
||||
"audio_encoder": "audio_encoders",
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
import { CATEGORY_ALIASES } from './category_aliases.generated.js';
|
||||
|
||||
function normalizeCategoryToken(category) {
|
||||
return String(category || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[/\\\s-]+/g, '_')
|
||||
.replace(/_+/g, '_')
|
||||
.replace(/^_|_$/g, '');
|
||||
}
|
||||
import { CATEGORY_ALIASES, normalizeCategoryToken } from './category_aliases.generated.js';
|
||||
|
||||
export function normalizeDownloadCategoryValue(
|
||||
category = '',
|
||||
|
||||
Reference in New Issue
Block a user