← back to Exo
we have a lot of models so group them nicely
59174bdc62010cf0664bf370cbeeff496901f893 · 2025-01-24 18:02:00 +0000 · Alex Cheema
Files touched
M exo/tinychat/index.cssM exo/tinychat/index.htmlM exo/tinychat/index.js
Diff
commit 59174bdc62010cf0664bf370cbeeff496901f893
Author: Alex Cheema <alexcheema123@gmail.com>
Date: Fri Jan 24 18:02:00 2025 +0000
we have a lot of models so group them nicely
---
exo/tinychat/index.css | 51 +++++++++++++++++++++
exo/tinychat/index.html | 115 ++++++++++++++++++++++++++++++------------------
exo/tinychat/index.js | 53 +++++++++++++++++++++-
3 files changed, 175 insertions(+), 44 deletions(-)
diff --git a/exo/tinychat/index.css b/exo/tinychat/index.css
index 2dd3d087..47c5e4be 100644
--- a/exo/tinychat/index.css
+++ b/exo/tinychat/index.css
@@ -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;
}
\ No newline at end of file
diff --git a/exo/tinychat/index.html b/exo/tinychat/index.html
index 7ef552e9..ecc7a973 100644
--- a/exo/tinychat/index.html
+++ b/exo/tinychat/index.html
@@ -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;
diff --git a/exo/tinychat/index.js b/exo/tinychat/index.js
index 5b0a76d0..dff5fc97 100644
--- a/exo/tinychat/index.js
+++ b/exo/tinychat/index.js
@@ -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;
+ },
}));
});
← cfdaaef8 handle thinking outputs nicely, format latex beautifully
·
back to Exo
·
fix tokenizer tests fdd05bad →