mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-19 08:52:05 -03:00
fix(stats): implement Model Types chart in Collection tab with correct type distribution
This commit is contained in:
@@ -11,6 +11,8 @@ from ..config import config
|
|||||||
from ..services.settings_manager import get_settings_manager
|
from ..services.settings_manager import get_settings_manager
|
||||||
from ..services.server_i18n import server_i18n
|
from ..services.server_i18n import server_i18n
|
||||||
from ..services.service_registry import ServiceRegistry
|
from ..services.service_registry import ServiceRegistry
|
||||||
|
from ..services.model_query import normalize_sub_type, resolve_sub_type
|
||||||
|
from ..utils.constants import VALID_LORA_SUB_TYPES, VALID_CHECKPOINT_SUB_TYPES
|
||||||
from ..utils.usage_stats import UsageStats
|
from ..utils.usage_stats import UsageStats
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -140,6 +142,21 @@ class StatsRoutes:
|
|||||||
# Get usage statistics
|
# Get usage statistics
|
||||||
usage_data = await self.usage_stats.get_stats()
|
usage_data = await self.usage_stats.get_stats()
|
||||||
|
|
||||||
|
# CivitAI model type distribution across all model types
|
||||||
|
# Use the same logic as the filter panel: normalize_sub_type(resolve_sub_type(entry))
|
||||||
|
# with sub-type validation per model type
|
||||||
|
model_types_counter: Counter[str] = Counter()
|
||||||
|
for entry in lora_cache.raw_data:
|
||||||
|
ntype = normalize_sub_type(resolve_sub_type(entry))
|
||||||
|
if ntype and ntype in VALID_LORA_SUB_TYPES:
|
||||||
|
model_types_counter[ntype] += 1
|
||||||
|
for entry in checkpoint_cache.raw_data:
|
||||||
|
ntype = normalize_sub_type(resolve_sub_type(entry))
|
||||||
|
if ntype and ntype in VALID_CHECKPOINT_SUB_TYPES:
|
||||||
|
model_types_counter[ntype] += 1
|
||||||
|
# Embeddings: always count as "embedding" regardless of CivitAI sub-type
|
||||||
|
model_types_counter['embedding'] = len(embedding_cache.raw_data)
|
||||||
|
|
||||||
return web.json_response({
|
return web.json_response({
|
||||||
'success': True,
|
'success': True,
|
||||||
'data': {
|
'data': {
|
||||||
@@ -154,7 +171,8 @@ class StatsRoutes:
|
|||||||
'total_generations': usage_data.get('total_executions', 0),
|
'total_generations': usage_data.get('total_executions', 0),
|
||||||
'unused_loras': self._count_unused_models(lora_cache.raw_data, usage_data.get('loras', {})),
|
'unused_loras': self._count_unused_models(lora_cache.raw_data, usage_data.get('loras', {})),
|
||||||
'unused_checkpoints': self._count_unused_models(checkpoint_cache.raw_data, usage_data.get('checkpoints', {})),
|
'unused_checkpoints': self._count_unused_models(checkpoint_cache.raw_data, usage_data.get('checkpoints', {})),
|
||||||
'unused_embeddings': self._count_unused_models(embedding_cache.raw_data, usage_data.get('embeddings', {}))
|
'unused_embeddings': self._count_unused_models(embedding_cache.raw_data, usage_data.get('embeddings', {})),
|
||||||
|
'model_types_distribution': dict(model_types_counter.most_common())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -240,6 +240,9 @@ export class StatisticsManager {
|
|||||||
|
|
||||||
// Storage efficiency chart
|
// Storage efficiency chart
|
||||||
this.createStorageEfficiencyChart();
|
this.createStorageEfficiencyChart();
|
||||||
|
|
||||||
|
// Model types chart (Collection tab)
|
||||||
|
this.createModelTypesChart();
|
||||||
}
|
}
|
||||||
|
|
||||||
createCollectionPieChart() {
|
createCollectionPieChart() {
|
||||||
@@ -554,6 +557,68 @@ export class StatisticsManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createModelTypesChart() {
|
||||||
|
const ctx = document.getElementById('modelTypesChart');
|
||||||
|
if (!ctx || !this.data.collection || !this.data.collection.model_types_distribution) return;
|
||||||
|
|
||||||
|
const distribution = this.data.collection.model_types_distribution;
|
||||||
|
const typeDisplayNames = {
|
||||||
|
lora: 'LoRA',
|
||||||
|
locon: 'LyCORIS',
|
||||||
|
dora: 'DoRA',
|
||||||
|
checkpoint: 'Checkpoint',
|
||||||
|
diffusion_model: 'Diffusion Model',
|
||||||
|
embedding: 'Embeddings'
|
||||||
|
};
|
||||||
|
|
||||||
|
const colorPalette = {
|
||||||
|
lora: 'oklch(68% 0.28 256)',
|
||||||
|
locon: 'oklch(68% 0.25 190)',
|
||||||
|
dora: 'oklch(68% 0.25 330)',
|
||||||
|
checkpoint: 'oklch(68% 0.28 45)',
|
||||||
|
diffusion_model: 'oklch(68% 0.25 280)',
|
||||||
|
embedding: 'oklch(68% 0.25 120)'
|
||||||
|
};
|
||||||
|
|
||||||
|
const labels = Object.keys(distribution).map(k => typeDisplayNames[k] || k);
|
||||||
|
const values = Object.values(distribution);
|
||||||
|
const colors = Object.keys(distribution).map(k => colorPalette[k] || 'oklch(68% 0.15 0)');
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [{
|
||||||
|
data: values,
|
||||||
|
backgroundColor: colors,
|
||||||
|
borderColor: getComputedStyle(document.documentElement).getPropertyValue('--border-color'),
|
||||||
|
borderWidth: 2
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.charts.modelTypes = new Chart(ctx, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: data,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'bottom'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: (context) => {
|
||||||
|
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
||||||
|
const value = context.parsed;
|
||||||
|
const pct = ((value / total) * 100).toFixed(1);
|
||||||
|
return ` ${context.label}: ${value} (${pct}%)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async initializeLists() {
|
async initializeLists() {
|
||||||
const listTypes = [
|
const listTypes = [
|
||||||
{ type: 'lora', containerId: 'topLorasList' },
|
{ type: 'lora', containerId: 'topLorasList' },
|
||||||
|
|||||||
Reference in New Issue
Block a user