Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions patch.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--- public/views/models.html
+++ public/views/models.html
@@ -113,13 +113,13 @@
</button>

<!-- Add Custom Model UI -->
- <div class="flex items-center gap-2 mt-4 lg:mt-0 w-full lg:w-auto ml-auto">
+ <div class="flex items-center gap-2 ml-0 md:ml-2">
<input type="text"
placeholder="Add custom Model ID"
- class="input-search-sm w-full md:w-48 bg-space-800 border border-space-border/50 text-gray-300 placeholder-gray-500 focus:border-neon-purple focus:ring-1 focus:ring-neon-purple rounded-lg px-3 py-1.5 text-xs transition-all h-9"
+ class="w-48 bg-space-800 border border-space-border/50 text-gray-300 placeholder-gray-500 focus:border-neon-purple focus:outline-none focus:ring-1 focus:ring-neon-purple rounded-lg px-3 text-xs transition-all h-9"
x-model="newModelId"
@keydown.enter="addCustomModel()">
- <button class="btn btn-xs h-9 px-4 bg-neon-purple/20 text-neon-purple border border-neon-purple/50 hover:bg-neon-purple/30 transition-colors whitespace-nowrap min-h-0"
+ <button class="btn h-9 px-4 bg-neon-purple/20 text-neon-purple border border-neon-purple/50 hover:bg-neon-purple/30 transition-colors whitespace-nowrap min-h-0 text-[10px] font-medium uppercase tracking-wide"
@click="addCustomModel()"
:disabled="!newModelId.trim()">
Add
2 changes: 1 addition & 1 deletion public/css/style.css

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions public/js/components/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,70 @@ window.Components.models = () => ({
this.expandedModels = new Set(this.expandedModels);
},

newModelId: '',

async addCustomModel() {
const modelId = this.newModelId.trim();
if (!modelId) return;

const store = Alpine.store('global');
const dataStore = Alpine.store('data');

try {
const { response, newPassword } = await window.utils.request(
'/api/models',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ modelId })
},
store.webuiPassword
);
if (newPassword) store.webuiPassword = newPassword;

const data = await response.json();
if (data.status === 'ok') {
store.showToast(`Custom model added: ${modelId}`, 'success');
this.newModelId = '';
// Refresh data to show the new model
await dataStore.fetchData();
} else {
throw new Error(data.error || 'Failed to add model');
}
} catch (e) {
store.showToast('Error adding model: ' + e.message, 'error');
}
},

async deleteCustomModel(modelId) {
if (!confirm(`Are you sure you want to remove the custom model "${modelId}"?`)) return;

const store = Alpine.store('global');
const dataStore = Alpine.store('data');

try {
const { response, newPassword } = await window.utils.request(
`/api/models/${encodeURIComponent(modelId)}`,
{
method: 'DELETE'
},
store.webuiPassword
);
if (newPassword) store.webuiPassword = newPassword;

const data = await response.json();
if (data.status === 'ok') {
store.showToast(`Custom model removed: ${modelId}`, 'success');
// Refresh data
await dataStore.fetchData();
} else {
throw new Error(data.error || 'Failed to remove model');
}
} catch (e) {
store.showToast('Error removing model: ' + e.message, 'error');
}
},

/**
* Get visible account rows for a model's breakdown, respecting the cap
*/
Expand Down
4 changes: 4 additions & 0 deletions public/js/data-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ document.addEventListener('alpine:init', () => {
Alpine.store('data', {
accounts: [],
models: [], // Source of truth
customModels: [], // User-added custom models
modelConfig: {}, // Model metadata (hidden, pinned, alias)
quotaRows: [], // Filtered view
usageHistory: {}, // Usage statistics history (from /account-limits?includeHistory=true)
Expand Down Expand Up @@ -79,6 +80,7 @@ document.addEventListener('alpine:init', () => {
if (data.accounts && data.models) {
this.accounts = data.accounts;
this.models = data.models;
this.customModels = data.customModels || [];
this.modelConfig = data.modelConfig || {};
this.usageHistory = data.usageHistory || {};

Expand All @@ -98,6 +100,7 @@ document.addEventListener('alpine:init', () => {
const cacheData = {
accounts: this.accounts,
models: this.models,
customModels: this.customModels,
modelConfig: this.modelConfig,
usageHistory: this.usageHistory,
timestamp: Date.now()
Expand Down Expand Up @@ -130,6 +133,7 @@ document.addEventListener('alpine:init', () => {
if (data.models && data.models.length > 0) {
this.models = data.models;
}
this.customModels = data.customModels || [];
this.modelConfig = data.modelConfig || {};
this.globalQuotaThreshold = data.globalQuotaThreshold || 0;

Expand Down
24 changes: 24 additions & 0 deletions public/views/models.html
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,20 @@ <h1 class="text-2xl font-bold text-white tracking-tight" x-text="$store.global.t
</svg>
<span class="text-[10px] font-medium uppercase tracking-wide" x-text="$store.settings.showAllAccounts ? 'All' : 'Capped'"></span>
</button>

<!-- Add Custom Model UI -->
<div class="flex items-center gap-2 ml-0 md:ml-2">
<input type="text"
placeholder="Add custom Model ID"
class="w-48 bg-space-800 border border-space-border/50 text-gray-300 placeholder-gray-500 focus:border-neon-purple focus:outline-none focus:ring-1 focus:ring-neon-purple rounded-lg px-3 text-xs transition-all h-9"
x-model="newModelId"
@keydown.enter="addCustomModel()">
<button class="btn h-9 px-4 bg-neon-purple/20 text-neon-purple border border-neon-purple/50 hover:bg-neon-purple/30 transition-colors whitespace-nowrap min-h-0 text-[10px] font-medium uppercase tracking-wide"
@click="addCustomModel()"
:disabled="!newModelId.trim()">
Add
</button>
</div>
</div>
</div>

Expand Down Expand Up @@ -388,6 +402,16 @@ <h1 class="text-2xl font-bold text-white tracking-tight" x-text="$store.global.t
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg>
</button>
<!-- Delete Custom Model Toggle -->
<button x-show="$store.data.customModels.includes(row.modelId)"
class="btn btn-xs btn-circle transition-colors btn-ghost text-red-400 hover:bg-red-500/20 hover:text-red-300"
@click="deleteCustomModel(row.modelId)"
title="Delete Custom Model"
tabindex="0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
</td>
</tr>
Expand Down
Loading