From 33b19ba98bdda1ce44de470470faac135a47889e Mon Sep 17 00:00:00 2001 From: 9qeklajc <9qeklajc> Date: Tue, 4 Nov 2025 22:28:48 +0100 Subject: [PATCH 01/10] better model naming --- routstr/upstreams/ollama.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routstr/upstreams/ollama.py b/routstr/upstreams/ollama.py index e999e221..5414434e 100644 --- a/routstr/upstreams/ollama.py +++ b/routstr/upstreams/ollama.py @@ -121,7 +121,7 @@ class OllamaUpstreamProvider(UpstreamProvider): models_list.append( Model( id=model_name, - name=model_name, + name=model_name.replace(':', ' '), created=0, description=description, context_length=context_length, From b8a1d699246d1554fe452887bdd8cff3653ef846 Mon Sep 17 00:00:00 2001 From: 9qeklajc <9qeklajc> Date: Tue, 4 Nov 2025 22:45:07 +0100 Subject: [PATCH 02/10] fmt --- routstr/upstreams/ollama.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routstr/upstreams/ollama.py b/routstr/upstreams/ollama.py index 5414434e..117b95a7 100644 --- a/routstr/upstreams/ollama.py +++ b/routstr/upstreams/ollama.py @@ -121,7 +121,7 @@ class OllamaUpstreamProvider(UpstreamProvider): models_list.append( Model( id=model_name, - name=model_name.replace(':', ' '), + name=model_name.replace(":", " "), created=0, description=description, context_length=context_length, From 1e90b223cf7f29bbc358739117ddd4f82eee9b56 Mon Sep 17 00:00:00 2001 From: 9qeklajc <9qeklajc> Date: Tue, 4 Nov 2025 22:45:18 +0100 Subject: [PATCH 03/10] fix model filter --- routstr/proxy.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/routstr/proxy.py b/routstr/proxy.py index 7c63de49..8eece67b 100644 --- a/routstr/proxy.py +++ b/routstr/proxy.py @@ -83,9 +83,7 @@ async def refresh_model_maps() -> None: # Gather database overrides and disabled models async with create_session() as session: - result = await session.exec( - select(ModelRow).where(col(ModelRow.enabled).is_(True)) - ) + result = await session.exec(select(ModelRow).where(ModelRow.enabled == True)) override_rows = result.all() provider_result = await session.exec(select(UpstreamProviderRow)) @@ -103,7 +101,7 @@ async def refresh_model_maps() -> None: } disabled_result = await session.exec( - select(ModelRow.id).where(col(ModelRow.enabled).is_(False)) + select(ModelRow.id).where(ModelRow.enabled == False) ) disabled_model_ids = {row for row in disabled_result.all()} From ded82cd729ba9c01ee214f343dec8953bab89095 Mon Sep 17 00:00:00 2001 From: 9qeklajc <9qeklajc> Date: Tue, 4 Nov 2025 23:16:08 +0100 Subject: [PATCH 04/10] fix model endpoint & filter query --- routstr/proxy.py | 5 +++-- scripts/build-ui.sh | 1 + ui/components/ModelSelector.tsx | 12 ++++-------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/routstr/proxy.py b/routstr/proxy.py index 8eece67b..914d20bd 100644 --- a/routstr/proxy.py +++ b/routstr/proxy.py @@ -3,7 +3,7 @@ from typing import Any from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.responses import Response, StreamingResponse -from sqlmodel import col, select +from sqlmodel import select from .algorithm import create_model_mappings from .auth import pay_for_request, revert_pay_for_request, validate_bearer_key @@ -101,9 +101,10 @@ async def refresh_model_maps() -> None: } disabled_result = await session.exec( - select(ModelRow.id).where(ModelRow.enabled == False) + select(ModelRow.id).where(~ModelRow.enabled) ) disabled_model_ids = {row for row in disabled_result.all()} + print(disabled_model_ids) _model_instances, _provider_map, _unique_models = create_model_mappings( upstreams=_upstreams, diff --git a/scripts/build-ui.sh b/scripts/build-ui.sh index 557c26db..7a235f01 100755 --- a/scripts/build-ui.sh +++ b/scripts/build-ui.sh @@ -49,6 +49,7 @@ else npm run build fi +rm -rf ../ui_out mkdir -p ../ui_out mv out/* ../ui_out diff --git a/ui/components/ModelSelector.tsx b/ui/components/ModelSelector.tsx index 5b03e350..1a9a2814 100644 --- a/ui/components/ModelSelector.tsx +++ b/ui/components/ModelSelector.tsx @@ -387,14 +387,10 @@ export function ModelSelector({ providerIdNum, model.id ); - await AdminService.updateProviderModel( - providerIdNum, - model.full_name, - { - ...existingModel, - enabled: true, - } - ); + await AdminService.updateProviderModel(providerIdNum, model.id, { + ...existingModel, + enabled: true, + }); totalEnabled++; } catch (error) { console.error(`Failed to enable model ${model.full_name}:`, error); From 88fbefbd183257e52b58a33f393b4d058b7672b8 Mon Sep 17 00:00:00 2001 From: 9qeklajc <9qeklajc> Date: Tue, 4 Nov 2025 23:28:09 +0100 Subject: [PATCH 05/10] fmt --- routstr/payment/models.py | 2 -- routstr/proxy.py | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/routstr/payment/models.py b/routstr/payment/models.py index 61955035..c240b856 100644 --- a/routstr/payment/models.py +++ b/routstr/payment/models.py @@ -577,7 +577,6 @@ async def _cleanup_enabled_models_once() -> None: for db_model in db_models: # Find corresponding upstream model - print(db_model.id) upstream_model = None for upstream in upstreams: upstream_model = upstream.get_cached_model_by_id(db_model.id) @@ -628,7 +627,6 @@ def _pricing_matches( for key in keys_to_compare: db_val = float(db_pricing.get(key, 0.0)) * 1000000 upstream_val = float(upstream_pricing.get(key, 0.0)) * 1000000 - print(db_val - upstream_val) if abs(db_val - upstream_val) > tolerance: return False diff --git a/routstr/proxy.py b/routstr/proxy.py index 914d20bd..7da56b42 100644 --- a/routstr/proxy.py +++ b/routstr/proxy.py @@ -3,7 +3,7 @@ from typing import Any from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.responses import Response, StreamingResponse -from sqlmodel import select +from sqlmodel import select from .algorithm import create_model_mappings from .auth import pay_for_request, revert_pay_for_request, validate_bearer_key @@ -83,7 +83,7 @@ async def refresh_model_maps() -> None: # Gather database overrides and disabled models async with create_session() as session: - result = await session.exec(select(ModelRow).where(ModelRow.enabled == True)) + result = await session.exec(select(ModelRow).where(ModelRow.enabled)) override_rows = result.all() provider_result = await session.exec(select(UpstreamProviderRow)) @@ -104,7 +104,6 @@ async def refresh_model_maps() -> None: select(ModelRow.id).where(~ModelRow.enabled) ) disabled_model_ids = {row for row in disabled_result.all()} - print(disabled_model_ids) _model_instances, _provider_map, _unique_models = create_model_mappings( upstreams=_upstreams, From f5c9cde85282a679bd8af41a8bf3b175078a00c1 Mon Sep 17 00:00:00 2001 From: 9qeklajc <9qeklajc> Date: Tue, 4 Nov 2025 23:46:38 +0100 Subject: [PATCH 06/10] clean up --- ui/app/layout.tsx | 2 +- ui/app/model/page.tsx | 6 +- ui/app/providers/page.tsx | 96 ++++++++++++++--------- ui/components/ModelSelector.tsx | 18 +++-- ui/components/detailed-wallet-balance.tsx | 8 +- ui/components/temporary-balances.tsx | 13 ++- ui/lib/currency.ts | 1 - ui/lib/exchange-rate.ts | 1 - ui/lib/types/units.ts | 1 - 9 files changed, 85 insertions(+), 61 deletions(-) diff --git a/ui/app/layout.tsx b/ui/app/layout.tsx index 1eff9d1c..6c087695 100644 --- a/ui/app/layout.tsx +++ b/ui/app/layout.tsx @@ -34,7 +34,7 @@ export default function RootLayout({ return ( {children} diff --git a/ui/app/model/page.tsx b/ui/app/model/page.tsx index cc13cb7a..6382b53f 100644 --- a/ui/app/model/page.tsx +++ b/ui/app/model/page.tsx @@ -156,7 +156,10 @@ export default function ModelsPage() { models={models} onFilteredModelsChange={setFilteredModels} /> - + @@ -199,6 +202,7 @@ export default function ModelsPage() { filterProvider={provider} groupData={groupData} showProviderActions={true} + showDeleteAllButton={false} /> diff --git a/ui/app/providers/page.tsx b/ui/app/providers/page.tsx index 49411bb0..53ca72c4 100644 --- a/ui/app/providers/page.tsx +++ b/ui/app/providers/page.tsx @@ -277,7 +277,11 @@ export default function ProvidersPage() { } placeholder='https://api.example.com/v1' disabled={hasFixedBaseUrl(formData.provider_type)} - className={hasFixedBaseUrl(formData.provider_type) ? 'cursor-not-allowed opacity-60' : ''} + className={ + hasFixedBaseUrl(formData.provider_type) + ? 'cursor-not-allowed opacity-60' + : '' + } />
@@ -453,7 +457,8 @@ export default function ProvidersPage() {
{providerModels.db_models.length === 0 ? (
- No models configured. Add custom models to use this provider. + No models configured. Add custom models to + use this provider.
) : (
@@ -495,7 +500,10 @@ export default function ProvidersPage() {
) : ( // Has provided models - show tabs - + Provided Models - Provided + + Provided + {providerModels.db_models.length > 0 && (
- Custom models override or extend the provider's catalog. + Custom models override or extend the + provider's catalog.
)} {providerModels.db_models.length === 0 ? ( @@ -543,39 +554,42 @@ export default function ProvidersPage() {
) : (
- {providerModels.db_models.map((model) => ( -
-
-
- - {model.id} - - - {model.enabled - ? 'Enabled' - : 'Disabled'} - + {providerModels.db_models.map( + (model) => ( +
+
+
+ + {model.id} + + + {model.enabled + ? 'Enabled' + : 'Disabled'} + +
+
+ {model.description || + model.name} +
-
- {model.description || model.name} +
+ {model.context_length?.toLocaleString()}{' '} + tokens
-
- {model.context_length?.toLocaleString()}{' '} - tokens -
-
- ))} + ) + )}
)} @@ -583,9 +597,11 @@ export default function ProvidersPage() { value='provided' className='mt-4 space-y-2' > - {providerModels.remote_models.length > 0 && ( + {providerModels.remote_models.length > + 0 && (
- Models automatically discovered from the provider's catalog. + Models automatically discovered from the + provider's catalog.
)}
@@ -670,7 +686,11 @@ export default function ProvidersPage() { } placeholder='https://api.example.com/v1' disabled={hasFixedBaseUrl(formData.provider_type)} - className={hasFixedBaseUrl(formData.provider_type) ? 'cursor-not-allowed opacity-60' : ''} + className={ + hasFixedBaseUrl(formData.provider_type) + ? 'cursor-not-allowed opacity-60' + : '' + } />
diff --git a/ui/components/ModelSelector.tsx b/ui/components/ModelSelector.tsx index 1a9a2814..7f7e24a9 100644 --- a/ui/components/ModelSelector.tsx +++ b/ui/components/ModelSelector.tsx @@ -65,12 +65,14 @@ interface ModelSelectorProps { groupData?: ModelGroup; showProviderActions?: boolean; filteredModels?: Model[]; + showDeleteAllButton?: boolean; } export function ModelSelector({ filterProvider, groupData, filteredModels: propFilteredModels, + showDeleteAllButton = false, }: ModelSelectorProps) { const [selectedModelId, setSelectedModelId] = useState(''); const [, setHoveredModelId] = useState(null); @@ -824,13 +826,15 @@ export function ModelSelector({ Deselect All - + {showDeleteAllButton && ( + + )} {/* Model Management Actions @@ -1181,7 +1178,7 @@ export function ModelSelector({ {model.soft_deleted && ( - Deleted + Disabled )}
diff --git a/ui/lib/utils/modelSort.ts b/ui/lib/utils/modelSort.ts new file mode 100644 index 00000000..932cccc1 --- /dev/null +++ b/ui/lib/utils/modelSort.ts @@ -0,0 +1,36 @@ +import { type Model } from '@/lib/api/schemas/models'; + +export function sortModelsByStatus(a: Model, b: Model): number { + if (a.isEnabled && !b.isEnabled) return -1; + if (!a.isEnabled && b.isEnabled) return 1; + + if (a.isEnabled === b.isEnabled) { + if (!a.soft_deleted && b.soft_deleted) return -1; + if (a.soft_deleted && !b.soft_deleted) return 1; + } + + return 0; +} + +export function sortModels(models: Model[]): Model[] { + return [...models].sort(sortModelsByStatus); +} + +export function groupAndSortModelsByProvider( + models: Model[] +): Record { + const grouped = models.reduce>((acc, model) => { + const provider = model.provider; + if (!acc[provider]) { + acc[provider] = []; + } + acc[provider].push(model); + return acc; + }, {}); + + Object.keys(grouped).forEach((provider) => { + grouped[provider].sort(sortModelsByStatus); + }); + + return grouped; +}