mirror of
https://github.com/exo-explore/exo.git
synced 2025-10-23 02:57:14 +03:00
we have a lot of models so group them nicely
This commit is contained in:
@@ -778,4 +778,55 @@ main {
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: thinking-spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.model-group {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.model-group-header,
|
||||
.model-subgroup-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background-color: var(--primary-bg-color);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.model-group-header:hover,
|
||||
.model-subgroup-header:hover {
|
||||
background-color: var(--secondary-color-transparent);
|
||||
}
|
||||
|
||||
.model-group-content {
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.model-subgroup {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.model-subgroup-header {
|
||||
font-size: 0.9em;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.model-subgroup-content {
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.group-header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.model-count {
|
||||
font-size: 0.8em;
|
||||
color: var(--secondary-color-transparent);
|
||||
font-family: monospace;
|
||||
}
|
||||
@@ -50,50 +50,78 @@
|
||||
<span>Loading models...</span>
|
||||
</div>
|
||||
|
||||
<template x-for="(model, key) in models" :key="key">
|
||||
<div class="model-option"
|
||||
:class="{ 'selected': cstate.selectedModel === key }"
|
||||
@click="cstate.selectedModel = key">
|
||||
<div class="model-header">
|
||||
<div class="model-name" x-text="model.name"></div>
|
||||
<button
|
||||
@click.stop="deleteModel(key, model)"
|
||||
class="model-delete-button"
|
||||
x-show="model.download_percentage > 0">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="model-info">
|
||||
<div class="model-progress">
|
||||
<template x-if="model.loading">
|
||||
<span><i class="fas fa-spinner fa-spin"></i> Checking download status...</span>
|
||||
</template>
|
||||
<div class="model-progress-info">
|
||||
<template x-if="!model.loading && model.download_percentage != null">
|
||||
<span>
|
||||
<!-- Check if there's an active download for this model -->
|
||||
<template x-if="downloadProgress?.some(p =>
|
||||
p.repo_id && p.repo_id.toLowerCase().includes(key.toLowerCase()) && !p.isComplete
|
||||
)">
|
||||
<i class="fas fa-circle-notch fa-spin"></i>
|
||||
</template>
|
||||
<span x-text="model.downloaded ? 'Downloaded' : `${Math.round(model.download_percentage)}% downloaded`"></span>
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="!model.loading && (model.download_percentage === null || model.download_percentage < 100) && !downloadProgress?.some(p => !p.isComplete)">
|
||||
<button
|
||||
@click.stop="handleDownload(key)"
|
||||
class="model-download-button">
|
||||
<i class="fas fa-download"></i>
|
||||
<span x-text="(model.download_percentage > 0 && model.download_percentage < 100) ? 'Continue Downloading' : 'Download'"></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Group models by prefix -->
|
||||
<template x-for="[mainPrefix, subGroups] in Object.entries(groupModelsByPrefix(models))" :key="mainPrefix">
|
||||
<div class="model-group">
|
||||
<div class="model-group-header" @click="toggleGroup(mainPrefix)">
|
||||
<div class="group-header-content">
|
||||
<span x-text="mainPrefix"></span>
|
||||
<span class="model-count" x-text="getGroupCounts(Object.values(subGroups).flatMap(group => Object.values(group)))"></span>
|
||||
</div>
|
||||
<template x-if="model.total_size">
|
||||
<div class="model-size" x-text="model.total_downloaded ?
|
||||
`${formatBytes(model.total_downloaded)} / ${formatBytes(model.total_size)}` :
|
||||
formatBytes(model.total_size)">
|
||||
<i class="fas" :class="isGroupExpanded(mainPrefix) ? 'fa-chevron-down' : 'fa-chevron-right'"></i>
|
||||
</div>
|
||||
|
||||
<div class="model-group-content" x-show="isGroupExpanded(mainPrefix)" x-transition>
|
||||
<template x-for="[subPrefix, groupModels] in Object.entries(subGroups)" :key="subPrefix">
|
||||
<div class="model-subgroup">
|
||||
<div class="model-subgroup-header" @click.stop="toggleGroup(mainPrefix, subPrefix)">
|
||||
<div class="group-header-content">
|
||||
<span x-text="subPrefix"></span>
|
||||
<span class="model-count" x-text="getGroupCounts(groupModels)"></span>
|
||||
</div>
|
||||
<i class="fas" :class="isGroupExpanded(mainPrefix, subPrefix) ? 'fa-chevron-down' : 'fa-chevron-right'"></i>
|
||||
</div>
|
||||
|
||||
<div class="model-subgroup-content" x-show="isGroupExpanded(mainPrefix, subPrefix)" x-transition>
|
||||
<template x-for="(model, key) in groupModels" :key="key">
|
||||
<div class="model-option"
|
||||
:class="{ 'selected': cstate.selectedModel === key }"
|
||||
@click="cstate.selectedModel = key">
|
||||
<div class="model-header">
|
||||
<div class="model-name" x-text="model.name"></div>
|
||||
<button
|
||||
@click.stop="deleteModel(key, model)"
|
||||
class="model-delete-button"
|
||||
x-show="model.download_percentage > 0">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="model-info">
|
||||
<div class="model-progress">
|
||||
<template x-if="model.loading">
|
||||
<span><i class="fas fa-spinner fa-spin"></i> Checking download status...</span>
|
||||
</template>
|
||||
<div class="model-progress-info">
|
||||
<template x-if="!model.loading && model.download_percentage != null">
|
||||
<span>
|
||||
<template x-if="downloadProgress?.some(p =>
|
||||
p.repo_id && p.repo_id.toLowerCase().includes(key.toLowerCase()) && !p.isComplete
|
||||
)">
|
||||
<i class="fas fa-circle-notch fa-spin"></i>
|
||||
</template>
|
||||
<span x-text="model.downloaded ? 'Downloaded' : `${Math.round(model.download_percentage)}% downloaded`"></span>
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="!model.loading && (model.download_percentage === null || model.download_percentage < 100) && !downloadProgress?.some(p => !p.isComplete)">
|
||||
<button
|
||||
@click.stop="handleDownload(key)"
|
||||
class="model-download-button">
|
||||
<i class="fas fa-download"></i>
|
||||
<span x-text="(model.download_percentage > 0 && model.download_percentage < 100) ? 'Continue Downloading' : 'Download'"></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<template x-if="model.total_size">
|
||||
<div class="model-size" x-text="model.total_downloaded ?
|
||||
`${formatBytes(model.total_downloaded)} / ${formatBytes(model.total_size)}` :
|
||||
formatBytes(model.total_size)">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -178,6 +206,7 @@
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@click="
|
||||
home = 0;
|
||||
|
||||
@@ -42,6 +42,9 @@ document.addEventListener("alpine:init", () => {
|
||||
topology: null,
|
||||
topologyInterval: null,
|
||||
|
||||
// Add these new properties
|
||||
expandedGroups: {},
|
||||
|
||||
init() {
|
||||
// Clean up any pending messages
|
||||
localStorage.removeItem("pendingMessage");
|
||||
@@ -664,7 +667,55 @@ document.addEventListener("alpine:init", () => {
|
||||
`;
|
||||
vizElement.appendChild(nodeElement);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Add these helper methods
|
||||
countDownloadedModels(models) {
|
||||
return Object.values(models).filter(model => model.downloaded).length;
|
||||
},
|
||||
|
||||
getGroupCounts(groupModels) {
|
||||
const total = Object.keys(groupModels).length;
|
||||
const downloaded = this.countDownloadedModels(groupModels);
|
||||
return `[${downloaded}/${total}]`;
|
||||
},
|
||||
|
||||
// Update the existing groupModelsByPrefix method to include counts
|
||||
groupModelsByPrefix(models) {
|
||||
const groups = {};
|
||||
Object.entries(models).forEach(([key, model]) => {
|
||||
const parts = key.split('-');
|
||||
const mainPrefix = parts[0].toUpperCase();
|
||||
|
||||
let subPrefix;
|
||||
if (parts.length === 2) {
|
||||
subPrefix = parts[1].toUpperCase();
|
||||
} else if (parts.length > 2) {
|
||||
subPrefix = parts[1].toUpperCase();
|
||||
} else {
|
||||
subPrefix = 'OTHER';
|
||||
}
|
||||
|
||||
if (!groups[mainPrefix]) {
|
||||
groups[mainPrefix] = {};
|
||||
}
|
||||
if (!groups[mainPrefix][subPrefix]) {
|
||||
groups[mainPrefix][subPrefix] = {};
|
||||
}
|
||||
groups[mainPrefix][subPrefix][key] = model;
|
||||
});
|
||||
return groups;
|
||||
},
|
||||
|
||||
toggleGroup(prefix, subPrefix = null) {
|
||||
const key = subPrefix ? `${prefix}-${subPrefix}` : prefix;
|
||||
this.expandedGroups[key] = !this.expandedGroups[key];
|
||||
},
|
||||
|
||||
isGroupExpanded(prefix, subPrefix = null) {
|
||||
const key = subPrefix ? `${prefix}-${subPrefix}` : prefix;
|
||||
return this.expandedGroups[key] || false;
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user