diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aca29594..ae676584 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,14 +62,18 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '18' - cache: 'npm' + node-version: "18" + cache: "npm" cache-dependency-path: ui/package-lock.json - name: Install UI dependencies working-directory: ./ui run: npm ci + - name: Run UI format check + working-directory: ./ui + run: npm run format-check + - name: Run UI linting working-directory: ./ui run: npm run lint diff --git a/routstr/algorithm.py b/routstr/algorithm.py index a1b6babd..acd059eb 100644 --- a/routstr/algorithm.py +++ b/routstr/algorithm.py @@ -150,18 +150,6 @@ def should_prefer_model( # Prefer lower adjusted cost should_replace = candidate_adjusted < current_adjusted - # Log provider changes when candidate wins - if should_replace: - candidate_provider_name = getattr( - candidate_provider, "provider_type", "unknown" - ) - current_provider_name = getattr(current_provider, "provider_type", "unknown") - logger.debug( - f"Model selection for alias '{alias}': choosing {candidate_provider_name} " - f"(cost: ${candidate_adjusted:.6f}) over {current_provider_name} " - f"(cost: ${current_adjusted:.6f})" - ) - return should_replace diff --git a/routstr/core/db.py b/routstr/core/db.py index 96fe91d0..f40212b3 100644 --- a/routstr/core/db.py +++ b/routstr/core/db.py @@ -149,8 +149,6 @@ def run_migrations() -> None: import pathlib try: - logger.info("Starting database migrations") - # Get the path to the alembic.ini file project_root = pathlib.Path(__file__).resolve().parents[2] alembic_ini_path = project_root / "alembic.ini" @@ -167,7 +165,6 @@ def run_migrations() -> None: alembic_cfg.set_main_option("sqlalchemy.url", DATABASE_URL) # Run migrations to the latest revision - logger.info("Running migrations to latest revision") command.upgrade(alembic_cfg, "head") logger.info("Database migrations completed successfully") diff --git a/routstr/core/logging.py b/routstr/core/logging.py index 65b6a5c0..00474949 100644 --- a/routstr/core/logging.py +++ b/routstr/core/logging.py @@ -338,6 +338,11 @@ def setup_logging() -> None: "handlers": ["console"] if console_enabled else [], "propagate": False, }, + "openai": { + "level": "WARNING", + "handlers": ["console"] if console_enabled else [], + "propagate": False, + }, "httpcore": { "level": "WARNING", "handlers": ["console"] if console_enabled else [], @@ -360,6 +365,11 @@ def setup_logging() -> None: }, "watchfiles.main": {"level": "WARNING", "handlers": [], "propagate": False}, "aiosqlite": {"level": "ERROR", "handlers": [], "propagate": False}, + "alembic": { + "level": "WARNING", + "handlers": ["console"] if console_enabled else [], + "propagate": False, + }, }, "root": { "level": log_level, diff --git a/routstr/core/main.py b/routstr/core/main.py index 06b86d65..d2974d4a 100644 --- a/routstr/core/main.py +++ b/routstr/core/main.py @@ -54,9 +54,6 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]: try: # Run database migrations on startup - # This ensures the database schema is always up-to-date in production - # Migrations are idempotent - running them multiple times is safe - logger.info("Running database migrations") run_migrations() # Initialize database connection pools @@ -104,6 +101,9 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]: yield + except asyncio.CancelledError: + # Expected during shutdown + pass except Exception as e: logger.error( "Application startup failed", diff --git a/routstr/payment/models.py b/routstr/payment/models.py index 4e21f91e..a7bc5cf5 100644 --- a/routstr/payment/models.py +++ b/routstr/payment/models.py @@ -2,7 +2,6 @@ import asyncio import json import random from pathlib import Path -from typing import Final from urllib.request import urlopen import httpx @@ -20,15 +19,6 @@ logger = get_logger(__name__) models_router = APIRouter() -DEFAULT_EXCLUDED_MODEL_IDS: Final[set[str]] = { - "openrouter/auto", - "openrouter/bodybuilder", - "google/gemini-2.5-pro-exp-03-25", - "opengvlab/internvl3-78b", - "openrouter/sonoma-dusk-alpha", - "openrouter/sonoma-sky-alpha", -} - class Architecture(BaseModel): modality: str @@ -45,6 +35,8 @@ class Pricing(BaseModel): image: float = 0.0 web_search: float = 0.0 internal_reasoning: float = 0.0 + input_cache_read: float = 0.0 + input_cache_write: float = 0.0 max_prompt_cost: float = 0.0 # in sats not msats max_completion_cost: float = 0.0 # in sats not msats max_cost: float = 0.0 # in sats not msats @@ -76,6 +68,27 @@ class Model(BaseModel): return hash(self.id) +def _has_valid_pricing(model: dict) -> bool: + """Check if model has valid pricing (not free, no negative values).""" + pricing = model.get("pricing", {}) + if not pricing: + return False + + try: + prompt = float(pricing.get("prompt", 0)) + completion = float(pricing.get("completion", 0)) + except (ValueError, TypeError): + return False + + if prompt < 0 or completion < 0: + return False + + if prompt == 0 and completion == 0: + return False + + return True + + def fetch_openrouter_models(source_filter: str | None = None) -> list[dict]: """Fetches model information from OpenRouter API.""" base_url = "https://openrouter.ai/api/v1" @@ -97,10 +110,10 @@ def fetch_openrouter_models(source_filter: str | None = None) -> list[dict]: model["id"] = model_id[len(source_prefix) :] model_id = model["id"] - if ( - "(free)" in model.get("name", "") - or model_id in DEFAULT_EXCLUDED_MODEL_IDS - ): + if "(free)" in model.get("name", ""): + continue + + if not _has_valid_pricing(model): continue models_data.append(model) @@ -134,10 +147,10 @@ async def async_fetch_openrouter_models(source_filter: str | None = None) -> lis model["id"] = model_id[len(source_prefix) :] model_id = model["id"] - if ( - "(free)" in model.get("name", "") - or model_id in DEFAULT_EXCLUDED_MODEL_IDS - ): + if "(free)" in model.get("name", ""): + continue + + if not _has_valid_pricing(model): continue models_data.append(model) @@ -201,7 +214,22 @@ def load_models() -> list[Model]: return [] logger.info(f"Successfully fetched {len(models_data)} models from OpenRouter API") - return [Model(**model) for model in models_data] # type: ignore + + valid_models = [] + for model_data in models_data: + try: + model = Model(**model_data) # type: ignore + valid_models.append(model) + except Exception as e: + model_id = model_data.get("id", "unknown") + logger.warning(f"Skipping model {model_id} - validation failed: {e}") + + if len(valid_models) != len(models_data): + logger.warning( + f"Filtered out {len(models_data) - len(valid_models)} models with incomplete data" + ) + + return valid_models def _row_to_model( diff --git a/routstr/payment/price.py b/routstr/payment/price.py index c20ac34e..9011ce1d 100644 --- a/routstr/payment/price.py +++ b/routstr/payment/price.py @@ -110,10 +110,6 @@ async def _update_prices() -> None: return BTC_USD_PRICE = btc_price SATS_USD_PRICE = btc_price / 100_000_000 - logger.info( - "Updated BTC/USD price", - extra={"btc_usd": btc_price, "sats_usd": SATS_USD_PRICE}, - ) def btc_usd_price() -> float: diff --git a/routstr/upstream/base.py b/routstr/upstream/base.py index 64eb6a4c..7726b5f6 100644 --- a/routstr/upstream/base.py +++ b/routstr/upstream/base.py @@ -1726,7 +1726,6 @@ class BaseUpstreamProvider: Returns: List of Model objects with pricing """ - logger.debug(f"Fetching models for {self.provider_type or self.base_url}") try: or_models, provider_models_response = await asyncio.gather( @@ -1753,18 +1752,9 @@ class BaseUpstreamProvider: else: not_found_models.append(model_id) - logger.info( - "Fetched models for provider", - extra={ - "provider": self.provider_type or self.base_url, - "found_count": len(found_models), - "not_found_count": len(not_found_models), - }, - ) - if not_found_models: logger.debug( - "Models not found in OpenRouter", + f"({len(not_found_models)}/{len(provider_model_ids)}) unmatched models for {self.provider_type or self.base_url}", extra={"not_found_models": not_found_models}, ) @@ -1832,10 +1822,7 @@ class BaseUpstreamProvider: self._models_cache = models_with_fees self._models_by_id = {m.id: m for m in self._models_cache} - logger.info( - f"Refreshed models cache for {self.provider_type or self.base_url}", - extra={"model_count": len(models)}, - ) + except Exception as e: logger.error( f"Failed to refresh models cache for {self.provider_type or self.base_url}", diff --git a/routstr/upstream/helpers.py b/routstr/upstream/helpers.py index 3d91550f..b453af1f 100644 --- a/routstr/upstream/helpers.py +++ b/routstr/upstream/helpers.py @@ -193,7 +193,7 @@ async def init_upstreams() -> list[BaseUpstreamProvider]: if provider: await provider.refresh_models_cache() upstreams.append(provider) - logger.info( + logger.debug( f"Initialized {provider_row.provider_type} provider", extra={ "base_url": provider_row.base_url, diff --git a/routstr/upstream/ppqai.py b/routstr/upstream/ppqai.py index 24013edb..0b4dfb08 100644 --- a/routstr/upstream/ppqai.py +++ b/routstr/upstream/ppqai.py @@ -102,11 +102,6 @@ class PPQAIUpstreamProvider(BaseUpstreamProvider): url = f"{self.base_url}/models" headers = {"Authorization": f"Bearer {self.api_key}"} - logger.debug( - "Fetching models from PPQ.AI", - extra={"url": url, "has_api_key": bool(self.api_key)}, - ) - try: async with httpx.AsyncClient(timeout=30.0) as client: response = await client.get(url, headers=headers) @@ -114,10 +109,6 @@ class PPQAIUpstreamProvider(BaseUpstreamProvider): data = response.json() models_data = data.get("data", []) - logger.info( - "Fetched models from PPQ.AI", - extra={"model_count": len(models_data)}, - ) or_models = [ Model(**model) # type: ignore @@ -198,15 +189,6 @@ class PPQAIUpstreamProvider(BaseUpstreamProvider): return models - except httpx.HTTPStatusError as e: - logger.error( - "HTTP error fetching models from PPQ.AI", - extra={ - "status_code": e.response.status_code, - "error": str(e), - }, - ) - return [] except Exception as e: logger.error( "Error fetching models from PPQ.AI", diff --git a/tests/integration/test_performance_load.py b/tests/integration/test_performance_load.py index 581553b9..e5e5c3ce 100644 --- a/tests/integration/test_performance_load.py +++ b/tests/integration/test_performance_load.py @@ -142,46 +142,6 @@ class TestPerformanceBaseline: f" P99: {sorted(response_times)[int(len(response_times) * 0.99)]:.2f}ms" ) - @pytest.mark.asyncio - async def test_database_query_performance( - self, integration_session: Any, db_snapshot: Any - ) -> None: - """Test database operation performance""" - from sqlmodel import select - - from routstr.core.db import ApiKey - - # Create test data - for i in range(100): - key = ApiKey( - hashed_key=f"test_key_{i}", - balance=1000000, - total_spent=0, - total_requests=0, - ) - integration_session.add(key) - await integration_session.commit() - - # Test query performance - query_times = [] - - for _ in range(100): - start = time.time() - result = await integration_session.execute( - select(ApiKey).where(ApiKey.balance > 0) # type: ignore[arg-type] - ) - _ = result.all() - duration = (time.time() - start) * 1000 - query_times.append(duration) - - # All queries should complete < 100ms - assert max(query_times) < 100, ( - f"Max query time {max(query_times)}ms exceeds 100ms limit" - ) - print("\nDatabase query performance:") - print(f" Mean: {statistics.mean(query_times):.2f}ms") - print(f" Max: {max(query_times):.2f}ms") - @pytest.mark.integration @pytest.mark.slow diff --git a/ui/app/balances/page.tsx b/ui/app/balances/page.tsx index b5213287..89e44f3a 100644 --- a/ui/app/balances/page.tsx +++ b/ui/app/balances/page.tsx @@ -29,9 +29,7 @@ export default function BalancesPage() {
-

- Balances -

+

Balances

Monitor and manage wallet balances

diff --git a/ui/app/login/page.tsx b/ui/app/login/page.tsx index 7df50995..ba47a120 100644 --- a/ui/app/login/page.tsx +++ b/ui/app/login/page.tsx @@ -78,8 +78,8 @@ export default function AdminLoginPage(): ReactElement { }; return ( -
- +
+ Admin Login diff --git a/ui/app/logs/log-details-dialog.tsx b/ui/app/logs/log-details-dialog.tsx index 852aa4a8..5c0193ce 100644 --- a/ui/app/logs/log-details-dialog.tsx +++ b/ui/app/logs/log-details-dialog.tsx @@ -97,7 +97,7 @@ export function LogDetailsDialog({

Message

-
+                
                   {log.message}
                 
@@ -116,7 +116,12 @@ export function LogDetailsDialog({
{totalModels === 0 && ( - +

No models found for this provider

-
-

Common issues:

-
    +
    +

    + Common issues: +

    +
    • - API credentials: Check if the API key is correct and has the right permissions + API credentials:{' '} + Check if the API key is correct + and has the right permissions
    • - Base URL: Verify the base URL is correct for your provider + Base URL: Verify + the base URL is correct for your + provider
    • - Network access: Ensure the server can reach the provider's API endpoint + Network access:{' '} + Ensure the server can reach the + provider's API endpoint
    • - Provider status: The upstream provider might be temporarily unavailable + Provider status:{' '} + The upstream provider might be + temporarily unavailable
    {groupData?.group_url && ( -

    - Current endpoint: {groupData.group_url} +

    + Current endpoint:{' '} + + {groupData.group_url} +

    )}
    diff --git a/ui/app/page.tsx b/ui/app/page.tsx index 041512eb..ba5dff37 100644 --- a/ui/app/page.tsx +++ b/ui/app/page.tsx @@ -53,7 +53,7 @@ export default function DashboardPage() { window.removeEventListener('storage', syncAuthState); }; }, []); - + const { data: btcUsdPrice } = useQuery({ queryKey: ['btc-usd-price'], queryFn: fetchBtcUsdPrice, @@ -131,8 +131,13 @@ export default function DashboardPage() {
    -

    Dashboard

    - +

    + Dashboard +

    +
    @@ -140,8 +145,9 @@ export default function DashboardPage() {

    Usage Analytics

    -

    - Monitor requests, errors, and revenue over the last {timeRange} hours +

    + Monitor requests, errors, and revenue over the last {timeRange}{' '} + hours

    @@ -176,27 +182,31 @@ export default function DashboardPage() {
    {summaryLoading ? ( -
    Loading summary...
    +
    Loading summary...
    ) : summaryData ? ( ) : null}
    {metricsLoading ? ( -
    +
    Loading metrics...
    ) : metricsData && metricsData.metrics.length > 0 ? ( <> -
    +
    ({ - ...m, - revenue_sats: m.revenue_msats / 1000, - refunds_sats: m.refunds_msats / 1000, - net_revenue_sats: - (m.revenue_msats - m.refunds_msats) / 1000, - })) as Array & { timestamp: string }>} + data={ + metricsData.metrics.map((m) => ({ + ...m, + revenue_sats: m.revenue_msats / 1000, + refunds_sats: m.refunds_msats / 1000, + net_revenue_sats: + (m.revenue_msats - m.refunds_msats) / 1000, + })) as Array< + Record & { timestamp: string } + > + } title='Revenue Over Time (sats)' dataKeys={[ { @@ -218,7 +228,11 @@ export default function DashboardPage() { />
    & { timestamp: string }>} + data={ + metricsData.metrics as Array< + Record & { timestamp: string } + > + } title='Request Volume' dataKeys={[ { @@ -239,7 +253,11 @@ export default function DashboardPage() { ]} /> & { timestamp: string }>} + data={ + metricsData.metrics as Array< + Record & { timestamp: string } + > + } title='Error Tracking' dataKeys={[ { @@ -260,7 +278,11 @@ export default function DashboardPage() { ]} /> & { timestamp: string }>} + data={ + metricsData.metrics as Array< + Record & { timestamp: string } + > + } title='Payment Activity' dataKeys={[ { @@ -270,7 +292,7 @@ export default function DashboardPage() { }, ]} /> -
    +
    {summaryData && summaryData.unique_models.length > 0 && ( @@ -306,7 +328,9 @@ export default function DashboardPage() { key={type} className='flex items-center justify-between' > - {type} + + {type} + {count} @@ -335,16 +359,18 @@ export default function DashboardPage() {
    {revenueByModelLoading ? ( -
    Loading revenue by model...
    +
    + Loading revenue by model... +
    ) : revenueByModelData && revenueByModelData.models.length > 0 ? ( - ) : null} {errorLoading ? ( -
    Loading errors...
    +
    Loading errors...
    ) : errorData ? ( ) : null} diff --git a/ui/app/providers/page.tsx b/ui/app/providers/page.tsx index f431f101..4624f26a 100644 --- a/ui/app/providers/page.tsx +++ b/ui/app/providers/page.tsx @@ -76,7 +76,11 @@ function ProviderBalance({ ); const queryClient = useQueryClient(); - const { data: balanceData, isLoading, error } = useQuery({ + const { + data: balanceData, + isLoading, + error, + } = useQuery({ queryKey: ['provider-balance', providerId], queryFn: () => AdminService.getProviderBalance(providerId), refetchInterval: 30000, @@ -108,7 +112,10 @@ function ProviderBalance({ mutationFn: async (amount: number) => { console.log('Calling top-up API with:', { providerId, amount }); try { - const result = await AdminService.initiateProviderTopup(providerId, amount); + const result = await AdminService.initiateProviderTopup( + providerId, + amount + ); console.log('API returned:', result); return result; } catch (err) { @@ -120,11 +127,8 @@ function ProviderBalance({ console.log('Top-up response:', data); console.log('Type of data:', typeof data); console.log('Keys in data:', Object.keys(data || {})); - - if ( - data?.topup_data?.payment_request && - data?.topup_data?.invoice_id - ) { + + if (data?.topup_data?.payment_request && data?.topup_data?.invoice_id) { setInvoiceData({ payment_request: data.topup_data.payment_request as string, invoice_id: data.topup_data.invoice_id as string, @@ -172,19 +176,23 @@ function ProviderBalance({ // The backend throws 500/400 if not implemented. // A better approach is to check if we have a platform URL and maybe redirect there // if we know it's not supported. - + // BUT, we don't know for sure if it's supported without checking metadata or trying. // Let's rely on the "can_topup" metadata if available, but currently we only have "can_show_balance". - + // Simple heuristic: If platformUrl exists and we suspect no direct topup, redirect? // Actually, let's try to open the dialog, but if it's OpenRouter/OpenAI, maybe we just redirect? // The user specifically mentioned "like in openrouter". - - if (platformUrl && (platformUrl.includes('openrouter.ai') || platformUrl.includes('openai.com'))) { - window.open(platformUrl, '_blank'); - return; + + if ( + platformUrl && + (platformUrl.includes('openrouter.ai') || + platformUrl.includes('openai.com')) + ) { + window.open(platformUrl, '_blank'); + return; } - + setIsTopupDialogOpen(true); }; @@ -266,9 +274,7 @@ function ProviderBalance({
    -

    - Top-up successful! -

    +

    Top-up successful!

    ) : invoiceData ? (
    @@ -482,7 +488,9 @@ export default function ProvidersPage() { ...formData, api_key: String(response.account_data.api_key), }); - toast.success('Account created successfully! API key has been filled in.'); + toast.success( + 'Account created successfully! API key has been filled in.' + ); } else { toast.success('Account created, but no API key returned.'); } @@ -778,8 +786,7 @@ export default function ProvidersPage() { )} />

    - 1.01 means +1% e.g. currency exchange, card - fees, etc. + 1.01 means +1% e.g. currency exchange, card fees, etc.

    @@ -856,9 +863,11 @@ export default function ProvidersPage() {
    {canShowBalance(provider.provider_type) && provider.api_key && ( - )} @@ -970,7 +986,12 @@ export default function ProvidersPage() { variant='ghost' size='icon' className='h-8 w-8' - onClick={() => handleEditModel(provider.id, model)} + onClick={() => + handleEditModel( + provider.id, + model + ) + } > @@ -1027,10 +1048,17 @@ export default function ProvidersPage() {
    {providerModels.db_models.length > 0 && (
    - Custom models override or extend the provider's catalog. + Custom models override or extend the + provider's catalog.
    )} - @@ -1079,7 +1107,12 @@ export default function ProvidersPage() { variant='ghost' size='icon' className='h-8 w-8' - onClick={() => handleEditModel(provider.id, model)} + onClick={() => + handleEditModel( + provider.id, + model + ) + } > @@ -1126,7 +1159,12 @@ export default function ProvidersPage() { variant='outline' size='sm' className='h-7 text-xs' - onClick={() => handleOverrideModel(provider.id, model)} + onClick={() => + handleOverrideModel( + provider.id, + model + ) + } > Override @@ -1245,7 +1283,7 @@ export default function ProvidersPage() {
    )}
    - @@ -1277,8 +1315,7 @@ export default function ProvidersPage() { )} />

    - 1.01 means +1% e.g. currency exchange, card - fees, etc. + 1.01 means +1% e.g. currency exchange, card fees, etc.

    @@ -1303,7 +1340,9 @@ export default function ProvidersPage() { setModelDialogState((prev) => ({ ...prev, isOpen: false }))} + onClose={() => + setModelDialogState((prev) => ({ ...prev, isOpen: false })) + } onSuccess={() => { queryClient.invalidateQueries({ queryKey: ['provider-models', modelDialogState.providerId], diff --git a/ui/components/AddProviderModelDialog.tsx b/ui/components/AddProviderModelDialog.tsx index 1bc17154..301682f4 100644 --- a/ui/components/AddProviderModelDialog.tsx +++ b/ui/components/AddProviderModelDialog.tsx @@ -102,7 +102,8 @@ export function AddProviderModelDialog({ }: AddProviderModelDialogProps) { const [isSubmitting, setIsSubmitting] = useState(false); const [isPresetOpen, setIsPresetOpen] = useState(false); - const [selectedPresetLabel, setSelectedPresetLabel] = useState('Select a preset'); + const [selectedPresetLabel, setSelectedPresetLabel] = + useState('Select a preset'); const form = useForm({ resolver: zodResolver(FormSchema) as never, @@ -149,7 +150,10 @@ export function AddProviderModelDialog({ if (initialData) { const architecture = initialData.architecture as Record; const pricing = initialData.pricing as Record; - const topProvider = initialData.top_provider as Record | null; + const topProvider = initialData.top_provider as Record< + string, + unknown + > | null; form.reset({ id: initialData.id, @@ -250,7 +254,9 @@ export function AddProviderModelDialog({ form.setValue('context_length', model.context_length); form.setValue( 'modality', - typeof architecture?.modality === 'string' ? architecture.modality : 'text' + typeof architecture?.modality === 'string' + ? architecture.modality + : 'text' ); form.setValue( 'input_modalities_raw', @@ -318,7 +324,10 @@ export function AddProviderModelDialog({ setIsSubmitting(true); try { let perRequestLimits: Record | null = null; - if (data.per_request_limits_raw && data.per_request_limits_raw.trim().length) { + if ( + data.per_request_limits_raw && + data.per_request_limits_raw.trim().length + ) { try { perRequestLimits = JSON.parse(data.per_request_limits_raw); } catch { @@ -336,7 +345,9 @@ export function AddProviderModelDialog({ context_length: data.context_length, architecture: { modality: data.modality, - input_modalities: listFromString(data.input_modalities_raw || data.modality), + input_modalities: listFromString( + data.input_modalities_raw || data.modality + ), output_modalities: listFromString( data.output_modalities_raw || data.modality ), @@ -366,10 +377,9 @@ export function AddProviderModelDialog({ is_moderated: data.top_provider_is_moderated, } : null, - upstream_provider_id: - data.upstream_provider_id?.trim().length - ? data.upstream_provider_id.trim() - : providerId, + upstream_provider_id: data.upstream_provider_id?.trim().length + ? data.upstream_provider_id.trim() + : providerId, canonical_slug: data.canonical_slug?.trim() || null, alias_ids: listFromString(data.alias_ids_raw || ''), enabled: data.enabled, @@ -416,7 +426,7 @@ export function AddProviderModelDialog({ {description} {!isEdit && !isOverride && ( -
    +
    Presets
    @@ -425,23 +435,25 @@ export function AddProviderModelDialog({ variant='outline' role='combobox' aria-expanded={isPresetOpen} - className='w-full justify-between text-left text-sm overflow-hidden' + className='w-full justify-between overflow-hidden text-left text-sm' > - {isLoadingPresets ? 'Loading presets...' : selectedPresetLabel} + {isLoadingPresets + ? 'Loading presets...' + : selectedPresetLabel} - e.preventDefault()} > - e.stopPropagation()} > @@ -475,8 +487,9 @@ export function AddProviderModelDialog({
    -
    - Prefill fields from a preset model definition, then adjust as needed. +
    + Prefill fields from a preset model definition, then adjust as + needed.
    )} @@ -496,7 +509,9 @@ export function AddProviderModelDialog({ disabled={isOverride || isEdit} /> - Unique identifier for the model + + Unique identifier for the model + )} @@ -524,7 +539,11 @@ export function AddProviderModelDialog({ Description -