Files
ComfyUI-Lora-Manager/static/js/api/apiConfig.js
T
Will Miao ad2402724b feat(update): track buzz prices and alert below a threshold
CivitAI's public API deliberately omits prices — paidAccess is trimmed to
{permanent, endsAt} because "pricing belongs to the purchase flow" — but the
public model page embeds the site's own model.getById result, including
paidAccess.terms, in its server-rendered payload. That is read anonymously
(no API key, no internal endpoint, no forged Origin), one request per gated
model, so only the ~2% of models that actually carry a gate pay for it.

- optional capture, off by default: price_tracking_enabled,
  price_alert_threshold_buzz (0 = alert on "became free" only) and
  price_check_ttl_hours; prices refresh on their own TTL and immediately when a
  gate changes, and a failed fetch keeps the stored price instead of blanking it
- versions that stop carrying a gate are marked free (persisted gate_lapsed_at)
  and gate transitions are reported as events on the refresh response, so a
  version already in the library can announce that it became free
- price_alert_state plus a price_drop edge event; new
  GET /api/lm/{type}/updates/price-alerts lists what is under the threshold
- versions tab shows the price (effective, with the list price struck through
  and a Blue Buzz note) and a Free Now badge; an update check toasts the
  transitions in one message
- the parser and the alerts query are unit-tested against a trimmed page
  fixture, and every route definition is now asserted to resolve to a handler

Plan, verification notes and the deviations from it are in
docs/plans/paid-model-price-tracking.md.
2026-10-04 08:53:24 +08:00

235 lines
8.4 KiB
JavaScript

import { state } from '../state/index.js';
/**
* API Configuration
* Centralized configuration for all model types and their endpoints
*/
// Model type definitions
export const MODEL_TYPES = {
LORA: 'loras',
CHECKPOINT: 'checkpoints',
EMBEDDING: 'embeddings',
OTHER: 'other'
};
// Base API configuration for each model type
export const MODEL_CONFIG = {
[MODEL_TYPES.LORA]: {
displayName: 'LoRA',
singularName: 'lora',
defaultPageSize: 100,
supportsLetterFilter: true,
supportsBulkOperations: true,
supportsMove: true,
supportsFolderManagement: true,
templateName: 'loras.html'
},
[MODEL_TYPES.CHECKPOINT]: {
displayName: 'Checkpoint',
singularName: 'checkpoint',
defaultPageSize: 100,
supportsLetterFilter: false,
supportsBulkOperations: true,
supportsMove: true,
supportsFolderManagement: true,
templateName: 'checkpoints.html'
},
[MODEL_TYPES.EMBEDDING]: {
displayName: 'Embedding',
singularName: 'embedding',
defaultPageSize: 100,
supportsLetterFilter: true,
supportsBulkOperations: true,
supportsMove: true,
supportsFolderManagement: true,
templateName: 'embeddings.html'
},
[MODEL_TYPES.OTHER]: {
displayName: 'Other Model',
singularName: 'other',
defaultPageSize: 100,
supportsLetterFilter: false,
supportsBulkOperations: true,
supportsMove: true,
supportsFolderManagement: true,
templateName: 'other.html'
}
};
/**
* Generate API endpoints for a given model type
* @param {string} modelType - The model type (e.g., 'loras', 'checkpoints')
* @returns {Object} Object containing all API endpoints for the model type
*/
export function getApiEndpoints(modelType) {
return {
// Base CRUD operations
list: `/api/lm/${modelType}/list`,
excluded: `/api/lm/${modelType}/excluded`,
delete: `/api/lm/${modelType}/delete`,
exclude: `/api/lm/${modelType}/exclude`,
unexclude: `/api/lm/${modelType}/unexclude`,
rename: `/api/lm/${modelType}/rename`,
save: `/api/lm/${modelType}/save-metadata`,
cancelTask: `/api/lm/${modelType}/cancel-task`,
// Bulk operations
bulkDelete: `/api/lm/${modelType}/bulk-delete`,
// Tag operations
addTags: `/api/lm/${modelType}/add-tags`,
// Move operations (now common for all model types that support move)
moveModel: `/api/lm/${modelType}/move_model`,
moveBulk: `/api/lm/${modelType}/move_models_bulk`,
createFolder: `/api/lm/${modelType}/create-folder`,
deleteFolder: `/api/lm/${modelType}/delete-folder`,
renameFolder: `/api/lm/${modelType}/rename-folder`,
// CivitAI integration
fetchCivitai: `/api/lm/${modelType}/fetch-civitai`,
fetchAllCivitai: `/api/lm/${modelType}/fetch-all-civitai`,
relinkCivitai: `/api/lm/${modelType}/relink-civitai`,
civitaiVersions: `/api/lm/${modelType}/civitai/versions`,
refreshUpdates: `/api/lm/${modelType}/updates/refresh`,
fetchMissingLicenses: `/api/lm/${modelType}/updates/fetch-missing-license`,
modelUpdateStatus: `/api/lm/${modelType}/updates/status`,
modelUpdateVersions: `/api/lm/${modelType}/updates/versions`,
ignoreModelUpdate: `/api/lm/${modelType}/updates/ignore`,
ignoreVersionUpdate: `/api/lm/${modelType}/updates/ignore-version`,
priceAlerts: `/api/lm/${modelType}/updates/price-alerts`,
// Preview management
replacePreview: `/api/lm/${modelType}/replace-preview`,
setPreviewFromUrl: `/api/lm/${modelType}/set-preview-from-url`,
// Query operations
scan: `/api/lm/${modelType}/scan`,
topTags: `/api/lm/${modelType}/top-tags`,
searchTags: `/api/lm/${modelType}/search-tags`,
baseModels: `/api/lm/${modelType}/base-models`,
roots: `/api/lm/${modelType}/roots`,
folders: `/api/lm/${modelType}/folders`,
folderTree: `/api/lm/${modelType}/folder-tree`,
unifiedFolderTree: `/api/lm/${modelType}/unified-folder-tree`,
duplicates: `/api/lm/${modelType}/find-duplicates`,
conflicts: `/api/lm/${modelType}/find-filename-conflicts`,
verify: `/api/lm/${modelType}/verify-duplicates`,
metadata: `/api/lm/${modelType}/metadata`,
modelDescription: `/api/lm/${modelType}/model-description`,
// Auto-organize operations
autoOrganize: `/api/lm/${modelType}/auto-organize`,
autoOrganizeProgress: `/api/lm/${modelType}/auto-organize-progress`,
// Filename template operations
applyFilenameTemplate: `/api/lm/${modelType}/apply-filename-template`,
// Model-specific endpoints (will be merged with specific configs)
specific: {}
};
}
/**
* Model-specific endpoint configurations
*/
export const MODEL_SPECIFIC_ENDPOINTS = {
[MODEL_TYPES.LORA]: {
letterCounts: `/api/lm/${MODEL_TYPES.LORA}/letter-counts`,
notes: `/api/lm/${MODEL_TYPES.LORA}/get-notes`,
triggerWords: `/api/lm/${MODEL_TYPES.LORA}/get-trigger-words`,
previewUrl: `/api/lm/${MODEL_TYPES.LORA}/preview-url`,
civitaiUrl: `/api/lm/${MODEL_TYPES.LORA}/civitai-url`,
metadata: `/api/lm/${MODEL_TYPES.LORA}/metadata`,
getTriggerWordsPost: `/api/lm/${MODEL_TYPES.LORA}/get_trigger_words`,
civitaiModelByVersion: `/api/lm/${MODEL_TYPES.LORA}/civitai/model/version`,
civitaiModelByHash: `/api/lm/${MODEL_TYPES.LORA}/civitai/model/hash`,
},
[MODEL_TYPES.CHECKPOINT]: {
info: `/api/lm/${MODEL_TYPES.CHECKPOINT}/info`,
checkpoints_roots: `/api/lm/${MODEL_TYPES.CHECKPOINT}/checkpoints_roots`,
unet_roots: `/api/lm/${MODEL_TYPES.CHECKPOINT}/unet_roots`,
metadata: `/api/lm/${MODEL_TYPES.CHECKPOINT}/metadata`,
},
[MODEL_TYPES.EMBEDDING]: {
metadata: `/api/lm/${MODEL_TYPES.EMBEDDING}/metadata`,
},
[MODEL_TYPES.OTHER]: {
metadata: `/api/lm/${MODEL_TYPES.OTHER}/metadata`,
roots_by_subtype: `/api/lm/${MODEL_TYPES.OTHER}/roots_by_subtype`,
}
};
/**
* Get complete API configuration for a model type
* @param {string} modelType - The model type
* @returns {Object} Complete API configuration
*/
export function getCompleteApiConfig(modelType) {
const baseEndpoints = getApiEndpoints(modelType);
const specificEndpoints = MODEL_SPECIFIC_ENDPOINTS[modelType] || {};
const config = MODEL_CONFIG[modelType];
return {
modelType,
config,
endpoints: {
...baseEndpoints,
specific: specificEndpoints
}
};
}
/**
* Validate if a model type is supported
* @param {string} modelType - The model type to validate
* @returns {boolean} True if valid, false otherwise
*/
export function isValidModelType(modelType) {
return Object.values(MODEL_TYPES).includes(modelType);
}
/**
* Get model type from current page or explicit parameter
* @param {string} [explicitType] - Explicitly provided model type
* @returns {string} The model type
*/
export function getCurrentModelType(explicitType = null) {
if (explicitType && isValidModelType(explicitType)) {
return explicitType;
}
return state.currentPageType || MODEL_TYPES.LORA;
}
// Download API endpoints (shared across all model types)
export const DOWNLOAD_ENDPOINTS = {
download: '/api/lm/download-model',
downloadGet: '/api/lm/download-model-get',
cancelGet: '/api/lm/cancel-download-get',
progress: '/api/lm/download-progress',
routing: '/api/lm/download/routing',
exampleImages: '/api/lm/force-download-example-images', // Re-process example images ignoring previous status
exampleImagesMissing: '/api/lm/download-example-images' // Download only missing example images
};
// External model source endpoints (Hugging Face / ModelScope).
// The hf-* paths are the historical names, kept as server-side aliases.
export const MODEL_SOURCE_ENDPOINTS = {
repoFiles: '/api/lm/model-source-files',
download: '/api/lm/download-model-source',
sources: '/api/lm/model-sources',
};
/** @deprecated use MODEL_SOURCE_ENDPOINTS */
export const HF_ENDPOINTS = {
repoFiles: MODEL_SOURCE_ENDPOINTS.repoFiles,
download: MODEL_SOURCE_ENDPOINTS.download,
};
// WebSocket endpoints
export const WS_ENDPOINTS = {
fetchProgress: '/ws/fetch-progress'
};