Files
mudknight-comfyui-mudknight…/web/modules/api.js
T
mudknight 7bec9656a5 Switched to danbooru.csv and improved tag parsing (closes #11)
This commit does a few things:
- Replaces autocomplete.txt with danbooru.csv, which contains aliases
  and categories
- Categries have been added to the autocompletion, changing the color of
  the tag and adding `(category)` after the tag.
- Aliases have been added to the autcompletion, automatically
  redirecting to their corresponding tag.
2026-01-01 23:07:32 -08:00

314 lines
7.0 KiB
JavaScript

import { state } from './state.js';
import { encodeName } from './utils.js';
export async function loadCharacters() {
const response = await fetch('/character_editor');
if (response.ok) {
return await response.json();
}
throw new Error('Failed to load characters');
}
export async function loadModels() {
const response = await fetch('/model_editor');
if (response.ok) {
return await response.json();
}
throw new Error('Failed to load models');
}
export async function loadStyles() {
const response = await fetch('/style_editor');
if (response.ok) {
return await response.json();
}
throw new Error('Failed to load styles');
}
export async function loadTags() {
const response = await fetch('/tag_editor');
if (response.ok) {
return await response.json();
}
throw new Error('Failed to load tags');
}
export async function loadAutocompleteTags() {
try {
const response = await fetch('danbooru.csv');
if (!response.ok) {
console.log('Danbooru CSV not found');
return [];
}
const text = await response.text();
const lines = text.split('\n');
const tags = [];
for (const line of lines) {
if (!line.trim()) continue;
// Parse CSV line handling quotes
const parts = parseCsvLine(line);
if (parts.length < 3) continue;
const tag = parts[0].trim();
const category = parseInt(parts[1]) || 0;
const count = parseInt(parts[2]) || 0;
const aliasField = parts[3] || '';
// Skip category 2 (unused)
if (category === 2) continue;
// Add main tag
tags.push({
tag: tag,
category: category,
count: count,
isAlias: false
});
// Parse and add aliases
if (aliasField) {
const aliases = parseAliases(aliasField);
for (const alias of aliases) {
tags.push({
tag: alias,
category: category,
count: count,
isAlias: true,
aliasFor: tag
});
}
}
}
// Sort by count (descending)
tags.sort((a, b) => b.count - a.count);
console.log(`Loaded ${tags.length} tags from Danbooru CSV`);
return tags;
} catch (error) {
console.error('Error loading Danbooru tags:', error);
return [];
}
}
function parseCsvLine(line) {
const parts = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (char === '"') {
inQuotes = !inQuotes;
} else if (char === ',' && !inQuotes) {
parts.push(current);
current = '';
} else {
current += char;
}
}
if (current) {
parts.push(current);
}
return parts;
}
function parseAliases(aliasField) {
if (!aliasField) return [];
// Remove quotes and leading/trailing slashes
let cleaned = aliasField.replace(/^["\/]+|["\/]+$/g, '');
// Split by comma
const aliases = cleaned.split(',')
.map(a => a.trim())
.filter(a => a.length > 0);
return aliases;
}
export async function saveCharacters(characters) {
const response = await fetch('/character_editor', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(characters)
});
if (!response.ok) {
throw new Error('Failed to save characters');
}
}
export async function saveModels(models) {
const response = await fetch('/model_editor', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(models)
});
if (!response.ok) {
throw new Error('Failed to save models');
}
}
export async function saveStyles(styles) {
const response = await fetch('/style_editor', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(styles)
});
if (!response.ok) {
throw new Error('Failed to save styles');
}
}
export async function saveTags(tags) {
const response = await fetch('/tag_editor', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(tags)
});
if (!response.ok) {
throw new Error('Failed to save tags');
}
}
export async function renameCharacter(oldName, newName, data) {
const response = await fetch('/character_editor/rename', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
oldName: oldName,
newName: newName,
data: data
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to rename');
}
}
export async function checkImages(type) {
const dataMap = {
character: state.characters,
style: state.styles
};
const imageMap = {
character: state.characterImages,
style: state.styleImages
};
const endpoint = type === 'character' ?
'/character_editor/image/' : '/style_editor/image/';
for (const name of Object.keys(dataMap[type])) {
const response = await fetch(
`${endpoint}${encodeName(name)}`
);
imageMap[type][name] = response.ok;
}
}
export function getImageUrl(name, type = 'character') {
const endpoint = type === 'character' ?
'/character_editor/image/' : '/style_editor/image/';
return `${endpoint}${encodeName(name)}?t=${Date.now()}`;
}
export async function uploadImage(file, name, type = 'character') {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = async (e) => {
try {
const endpoint = type === 'character' ?
'/character_editor/image/' : '/style_editor/image/';
const response = await fetch(
`${endpoint}${encodeName(name)}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
image: e.target.result
})
}
);
if (response.ok) {
if (type === 'character') {
state.characterImages[name] = true;
} else {
state.styleImages[name] = true;
}
resolve(true);
} else {
throw new Error('Failed to upload image');
}
} catch (error) {
console.error('Error uploading image:', error);
resolve(false);
}
};
reader.readAsDataURL(file);
});
}
export async function deleteImage(name, type = 'character') {
const endpoint = type === 'character' ?
'/character_editor/image/' : '/style_editor/image/';
const response = await fetch(
`${endpoint}${encodeName(name)}`,
{
method: 'DELETE'
}
);
if (response.ok || response.status === 404) {
if (type === 'character') {
state.characterImages[name] = false;
} else {
state.styleImages[name] = false;
}
return true;
}
throw new Error('Failed to delete image');
}
export async function loadLoras() {
try {
const response = await fetch('/lora_list');
if (!response.ok) {
console.log('Failed to load LoRA list');
return [];
}
const loras = await response.json();
console.log(`Loaded ${loras.length} LoRAs`);
return loras;
} catch (error) {
console.error('Error loading LoRAs:', error);
return [];
}
}
export async function loadEmbeddings() {
try {
const response = await fetch('/embedding_list');
if (!response.ok) {
console.log('Failed to load embedding list');
return [];
}
const embeddings = await response.json();
console.log(`Loaded ${embeddings.length} embeddings`);
return embeddings;
} catch (error) {
console.error('Error loading embeddings:', error);
return [];
}
}