feat: add embedding support in statistics page, including data handling and UI updates

This commit is contained in:
Will Miao
2025-07-27 16:36:14 +08:00
parent e2df5fcf27
commit b77105795a
4 changed files with 190 additions and 25 deletions

View File

@@ -56,6 +56,24 @@
color: var(--lora-error);
}
/* Update color scheme to include embeddings */
:root {
--embedding-color: oklch(68% 0.28 120); /* Green for embeddings */
}
/* Update metric cards and chart colors to support embeddings */
.metric-card.embedding .metric-icon {
color: var(--embedding-color);
}
.model-item.embedding {
border-left: 3px solid var(--embedding-color);
}
.model-item.embedding:hover {
border-color: var(--embedding-color);
}
/* Dashboard Content */
.dashboard-content {
background: var(--card-bg);

View File

@@ -150,6 +150,12 @@ class StatisticsManager {
value: this.data.collection.checkpoint_count,
label: 'Checkpoints',
format: 'number'
},
{
icon: 'fas fa-code',
value: this.data.collection.embedding_count,
label: 'Embeddings',
format: 'number'
}
];
@@ -195,7 +201,9 @@ class StatisticsManager {
if (!this.data.collection) return 0;
const totalModels = this.data.collection.total_models;
const unusedModels = this.data.collection.unused_loras + this.data.collection.unused_checkpoints;
const unusedModels = this.data.collection.unused_loras +
this.data.collection.unused_checkpoints +
this.data.collection.unused_embeddings;
const usedModels = totalModels - unusedModels;
return totalModels > 0 ? (usedModels / totalModels) * 100 : 0;
@@ -233,12 +241,17 @@ class StatisticsManager {
if (!ctx || !this.data.collection) return;
const data = {
labels: ['LoRAs', 'Checkpoints'],
labels: ['LoRAs', 'Checkpoints', 'Embeddings'],
datasets: [{
data: [this.data.collection.lora_count, this.data.collection.checkpoint_count],
data: [
this.data.collection.lora_count,
this.data.collection.checkpoint_count,
this.data.collection.embedding_count
],
backgroundColor: [
'oklch(68% 0.28 256)',
'oklch(68% 0.28 200)'
'oklch(68% 0.28 200)',
'oklch(68% 0.28 120)'
],
borderWidth: 2,
borderColor: getComputedStyle(document.documentElement).getPropertyValue('--border-color')
@@ -266,8 +279,13 @@ class StatisticsManager {
const loraData = this.data.baseModels.loras;
const checkpointData = this.data.baseModels.checkpoints;
const embeddingData = this.data.baseModels.embeddings;
const allModels = new Set([...Object.keys(loraData), ...Object.keys(checkpointData)]);
const allModels = new Set([
...Object.keys(loraData),
...Object.keys(checkpointData),
...Object.keys(embeddingData)
]);
const data = {
labels: Array.from(allModels),
@@ -281,6 +299,11 @@ class StatisticsManager {
label: 'Checkpoints',
data: Array.from(allModels).map(model => checkpointData[model] || 0),
backgroundColor: 'oklch(68% 0.28 200 / 0.7)'
},
{
label: 'Embeddings',
data: Array.from(allModels).map(model => embeddingData[model] || 0),
backgroundColor: 'oklch(68% 0.28 120 / 0.7)'
}
]
};
@@ -325,6 +348,13 @@ class StatisticsManager {
borderColor: 'oklch(68% 0.28 200)',
backgroundColor: 'oklch(68% 0.28 200 / 0.1)',
fill: true
},
{
label: 'Embedding Usage',
data: timeline.map(item => item.embedding_usage),
borderColor: 'oklch(68% 0.28 120)',
backgroundColor: 'oklch(68% 0.28 120 / 0.1)',
fill: true
}
]
};
@@ -365,11 +395,13 @@ class StatisticsManager {
const topLoras = this.data.usage.top_loras || [];
const topCheckpoints = this.data.usage.top_checkpoints || [];
const topEmbeddings = this.data.usage.top_embeddings || [];
// Combine and sort all models by usage
const allModels = [
...topLoras.map(m => ({ ...m, type: 'LoRA' })),
...topCheckpoints.map(m => ({ ...m, type: 'Checkpoint' }))
...topCheckpoints.map(m => ({ ...m, type: 'Checkpoint' })),
...topEmbeddings.map(m => ({ ...m, type: 'Embedding' }))
].sort((a, b) => b.usage_count - a.usage_count).slice(0, 10);
const data = {
@@ -377,9 +409,14 @@ class StatisticsManager {
datasets: [{
label: 'Usage Count',
data: allModels.map(model => model.usage_count),
backgroundColor: allModels.map(model =>
model.type === 'LoRA' ? 'oklch(68% 0.28 256)' : 'oklch(68% 0.28 200)'
)
backgroundColor: allModels.map(model => {
switch(model.type) {
case 'LoRA': return 'oklch(68% 0.28 256)';
case 'Checkpoint': return 'oklch(68% 0.28 200)';
case 'Embedding': return 'oklch(68% 0.28 120)';
default: return 'oklch(68% 0.28 256)';
}
})
}]
};
@@ -404,12 +441,17 @@ class StatisticsManager {
if (!ctx || !this.data.collection) return;
const data = {
labels: ['LoRAs', 'Checkpoints'],
labels: ['LoRAs', 'Checkpoints', 'Embeddings'],
datasets: [{
data: [this.data.collection.lora_size, this.data.collection.checkpoint_size],
data: [
this.data.collection.lora_size,
this.data.collection.checkpoint_size,
this.data.collection.embedding_size
],
backgroundColor: [
'oklch(68% 0.28 256)',
'oklch(68% 0.28 200)'
'oklch(68% 0.28 200)',
'oklch(68% 0.28 120)'
]
}]
};
@@ -443,10 +485,12 @@ class StatisticsManager {
const loraData = this.data.storage.loras || [];
const checkpointData = this.data.storage.checkpoints || [];
const embeddingData = this.data.storage.embeddings || [];
const allData = [
...loraData.map(item => ({ ...item, type: 'LoRA' })),
...checkpointData.map(item => ({ ...item, type: 'Checkpoint' }))
...checkpointData.map(item => ({ ...item, type: 'Checkpoint' })),
...embeddingData.map(item => ({ ...item, type: 'Embedding' }))
];
const data = {
@@ -458,9 +502,14 @@ class StatisticsManager {
name: item.name,
type: item.type
})),
backgroundColor: allData.map(item =>
item.type === 'LoRA' ? 'oklch(68% 0.28 256 / 0.6)' : 'oklch(68% 0.28 200 / 0.6)'
)
backgroundColor: allData.map(item => {
switch(item.type) {
case 'LoRA': return 'oklch(68% 0.28 256 / 0.6)';
case 'Checkpoint': return 'oklch(68% 0.28 200 / 0.6)';
case 'Embedding': return 'oklch(68% 0.28 120 / 0.6)';
default: return 'oklch(68% 0.28 256 / 0.6)';
}
})
}]
};
@@ -502,6 +551,7 @@ class StatisticsManager {
renderTopModelsLists() {
this.renderTopLorasList();
this.renderTopCheckpointsList();
this.renderTopEmbeddingsList();
this.renderLargestModelsList();
}
@@ -555,17 +605,44 @@ class StatisticsManager {
`).join('');
}
renderTopEmbeddingsList() {
const container = document.getElementById('topEmbeddingsList');
if (!container || !this.data.usage?.top_embeddings) return;
const topEmbeddings = this.data.usage.top_embeddings;
if (topEmbeddings.length === 0) {
container.innerHTML = '<div class="loading-placeholder">No usage data available</div>';
return;
}
container.innerHTML = topEmbeddings.map(embedding => `
<div class="model-item">
<img src="${embedding.preview_url || '/loras_static/images/no-preview.png'}"
alt="${embedding.name}" class="model-preview"
onerror="this.src='/loras_static/images/no-preview.png'">
<div class="model-info">
<div class="model-name" title="${embedding.name}">${embedding.name}</div>
<div class="model-meta">${embedding.base_model}${embedding.folder}</div>
</div>
<div class="model-usage">${embedding.usage_count}</div>
</div>
`).join('');
}
renderLargestModelsList() {
const container = document.getElementById('largestModelsList');
if (!container || !this.data.storage) return;
const loraModels = this.data.storage.loras || [];
const checkpointModels = this.data.storage.checkpoints || [];
const embeddingModels = this.data.storage.embeddings || [];
// Combine and sort by size
const allModels = [
...loraModels.map(m => ({ ...m, type: 'LoRA' })),
...checkpointModels.map(m => ({ ...m, type: 'Checkpoint' }))
...checkpointModels.map(m => ({ ...m, type: 'Checkpoint' })),
...embeddingModels.map(m => ({ ...m, type: 'Embedding' }))
].sort((a, b) => b.size - a.size).slice(0, 10);
if (allModels.length === 0) {