chore(priority-tags): add newline terminator

This commit is contained in:
pixelpaws
2025-10-11 17:38:20 +08:00
parent 47da9949d9
commit 6120922204
26 changed files with 1079 additions and 99 deletions

View File

@@ -194,10 +194,16 @@ export const BASE_MODEL_CATEGORIES = {
]
};
// Preset tag suggestions
export const PRESET_TAGS = [
// Default priority tag entries for fallback suggestions and initial settings
export const DEFAULT_PRIORITY_TAG_ENTRIES = [
'character', 'concept', 'clothing',
'realistic', 'anime', 'toon', 'furry', 'style',
'poses', 'background', 'vehicle', 'buildings',
'objects', 'animal'
'poses', 'background', 'tool', 'vehicle', 'buildings',
'objects', 'assets', 'animal', 'action'
];
export const DEFAULT_PRIORITY_TAG_CONFIG = {
lora: DEFAULT_PRIORITY_TAG_ENTRIES.join(', '),
checkpoint: DEFAULT_PRIORITY_TAG_ENTRIES.join(', '),
embedding: DEFAULT_PRIORITY_TAG_ENTRIES.join(', ')
};

View File

@@ -0,0 +1,223 @@
import { DEFAULT_PRIORITY_TAG_CONFIG } from './constants.js';
function splitPriorityEntries(raw = '') {
const segments = [];
raw.split('\n').forEach(line => {
line.split(',').forEach(part => {
const trimmed = part.trim();
if (trimmed) {
segments.push(trimmed);
}
});
});
return segments;
}
export function parsePriorityTagString(raw = '') {
const entries = [];
const rawEntries = splitPriorityEntries(raw);
rawEntries.forEach((entry) => {
const { canonical, aliases } = parsePriorityEntry(entry);
if (!canonical) {
return;
}
entries.push({ canonical, aliases });
});
return entries;
}
function parsePriorityEntry(entry) {
let canonical = entry;
let aliasSection = '';
const openIndex = entry.indexOf('(');
if (openIndex !== -1) {
if (!entry.endsWith(')')) {
canonical = entry.replace('(', '').replace(')', '');
} else {
canonical = entry.slice(0, openIndex).trim();
aliasSection = entry.slice(openIndex + 1, -1);
}
}
canonical = canonical.trim();
if (!canonical) {
return { canonical: '', aliases: [] };
}
const aliasList = aliasSection ? aliasSection.split('|').map((alias) => alias.trim()).filter(Boolean) : [];
const seen = new Set();
const normalizedCanonical = canonical.toLowerCase();
const uniqueAliases = [];
aliasList.forEach((alias) => {
const normalized = alias.toLowerCase();
if (normalized === normalizedCanonical) {
return;
}
if (!seen.has(normalized)) {
seen.add(normalized);
uniqueAliases.push(alias);
}
});
return { canonical, aliases: uniqueAliases };
}
export function formatPriorityTagEntries(entries, useNewlines = false) {
if (!entries.length) {
return '';
}
const separator = useNewlines ? ',\n' : ', ';
return entries.map(({ canonical, aliases }) => {
if (aliases && aliases.length) {
return `${canonical}(${aliases.join('|')})`;
}
return canonical;
}).join(separator);
}
export function validatePriorityTagString(raw = '') {
const trimmed = raw.trim();
if (!trimmed) {
return { valid: true, errors: [], entries: [], formatted: '' };
}
const errors = [];
const entries = [];
const rawEntries = splitPriorityEntries(raw);
const seenCanonicals = new Set();
rawEntries.forEach((entry, index) => {
const hasOpening = entry.includes('(');
const hasClosing = entry.endsWith(')');
if (hasOpening && !hasClosing) {
errors.push({ type: 'missingClosingParen', index: index + 1 });
}
const { canonical, aliases } = parsePriorityEntry(entry);
if (!canonical) {
errors.push({ type: 'missingCanonical', index: index + 1 });
return;
}
const normalizedCanonical = canonical.toLowerCase();
if (seenCanonicals.has(normalizedCanonical)) {
errors.push({ type: 'duplicateCanonical', canonical });
} else {
seenCanonicals.add(normalizedCanonical);
}
entries.push({ canonical, aliases });
});
const formatted = errors.length === 0
? formatPriorityTagEntries(entries, raw.includes('\n'))
: raw.trim();
return {
valid: errors.length === 0,
errors,
entries,
formatted,
};
}
let cachedPriorityTagMap = null;
let fetchPromise = null;
export async function getPriorityTagSuggestionsMap() {
if (cachedPriorityTagMap) {
return cachedPriorityTagMap;
}
if (!fetchPromise) {
fetchPromise = fetch('/api/lm/priority-tags')
.then(async (response) => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (!data || data.success === false || typeof data.tags !== 'object') {
throw new Error(data?.error || 'Invalid response payload');
}
const normalized = {};
Object.entries(data.tags).forEach(([modelType, tags]) => {
if (!Array.isArray(tags)) {
return;
}
normalized[modelType] = tags.filter(tag => typeof tag === 'string' && tag.trim());
});
const withDefaults = applyDefaultPriorityTagFallback(normalized);
cachedPriorityTagMap = withDefaults;
return withDefaults;
})
.catch(() => {
const fallback = buildDefaultPriorityTagMap();
cachedPriorityTagMap = fallback;
return fallback;
})
.finally(() => {
fetchPromise = null;
});
}
return fetchPromise;
}
export async function getPriorityTagSuggestions() {
const map = await getPriorityTagSuggestionsMap();
const unique = new Set();
Object.values(map).forEach((tags) => {
tags.forEach((tag) => {
unique.add(tag);
});
});
return Array.from(unique);
}
function applyDefaultPriorityTagFallback(map) {
const result = { ...buildDefaultPriorityTagMap(), ...map };
Object.entries(result).forEach(([key, tags]) => {
result[key] = dedupeTags(Array.isArray(tags) ? tags : []);
});
return result;
}
function buildDefaultPriorityTagMap() {
const map = {};
Object.entries(DEFAULT_PRIORITY_TAG_CONFIG).forEach(([modelType, configString]) => {
const entries = parsePriorityTagString(configString);
map[modelType] = entries.map((entry) => entry.canonical);
});
return map;
}
function dedupeTags(tags) {
const seen = new Set();
const ordered = [];
tags.forEach((tag) => {
const normalized = tag.toLowerCase();
if (!seen.has(normalized)) {
seen.add(normalized);
ordered.push(tag);
}
});
return ordered;
}
export function getDefaultPriorityTagConfig() {
return { ...DEFAULT_PRIORITY_TAG_CONFIG };
}
export function invalidatePriorityTagSuggestionsCache() {
cachedPriorityTagMap = null;
fetchPromise = null;
}