import { ArrowsRightLeftIcon } from "@heroicons/react/20/solid"; import { type MetaFunction } from "@remix-run/react"; import { type LoaderFunctionArgs } from "@remix-run/server-runtime"; import { useState } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; import { InlineCode } from "~/components/code/InlineCode"; import { PageBody, PageContainer } from "~/components/layout/AppLayout"; import type { QueryWidgetConfig } from "~/components/metrics/QueryWidget"; import { Badge } from "~/components/primitives/Badge"; import { LinkButton } from "~/components/primitives/Buttons"; import { Callout } from "~/components/primitives/Callout"; import { Header2 } from "~/components/primitives/Headers"; import { Input } from "~/components/primitives/Input"; import { Label } from "~/components/primitives/Label"; import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader"; import * as Property from "~/components/primitives/PropertyTable"; import { TabButton, TabContainer } from "~/components/primitives/Tabs"; import { useEnvironment } from "~/hooks/useEnvironment"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; import { findProjectBySlug } from "~/models/project.server"; import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server"; import { ModelRegistryPresenter } from "~/presenters/v3/ModelRegistryPresenter.server"; import { MetricWidget } from "~/routes/resources.metric"; import { clickhouseFactory } from "~/services/clickhouse/clickhouseFactoryInstance.server"; import { requireUserId } from "~/services/session.server"; import { formatFeature, formatModelCost, formatModelPrice, formatProviderName, formatTokenCount, } from "~/utils/modelFormatters"; import { EnvironmentParamSchema, v3ModelComparePath, v3ModelsPath } from "~/utils/pathBuilder"; const ParamSchema = EnvironmentParamSchema.extend({ modelId: z.string(), }); export const meta: MetaFunction = () => { return [{ title: "Model Detail | Trigger.dev" }]; }; export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { organizationSlug, projectParam, envParam, modelId } = ParamSchema.parse(params); const project = await findProjectBySlug(organizationSlug, projectParam, userId); if (!project) { throw new Response("Project not found", { status: 404 }); } const environment = await findEnvironmentBySlug(project.id, envParam, userId); if (!environment) { throw new Response("Environment not found", { status: 404 }); } const clickhouse = await clickhouseFactory.getClickhouseForOrganization( project.organizationId, "standard" ); const presenter = new ModelRegistryPresenter(clickhouse); const model = await presenter.getModelDetail(modelId); if (!model) { throw new Response("Model not found", { status: 404 }); } const now = new Date(); const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const userMetrics = await presenter.getUserMetrics( model.modelName, project.id, environment.id, sevenDaysAgo, now ); return typedjson({ model, userMetrics, organizationId: project.organizationId, projectId: project.id, environmentId: environment.id, }); }; /** Escape a value for safe interpolation into a TSQL single-quoted string. */ function escapeTSQL(value: string): string { return value.replace(/'/g, "''"); } function bignumberConfig( column: string, opts?: { aggregation?: "sum" | "avg" | "first"; suffix?: string; abbreviate?: boolean } ): QueryWidgetConfig { return { type: "bignumber", column, aggregation: opts?.aggregation ?? "sum", abbreviate: opts?.abbreviate ?? false, suffix: opts?.suffix, }; } function chartConfig(opts: { chartType: "bar" | "line"; xAxisColumn: string; yAxisColumns: string[]; aggregation?: "sum" | "avg"; }): QueryWidgetConfig { return { type: "chart", chartType: opts.chartType, xAxisColumn: opts.xAxisColumn, yAxisColumns: opts.yAxisColumns, groupByColumn: null, stacked: false, sortByColumn: null, sortDirection: "asc", aggregation: opts.aggregation ?? "sum", }; } type Tab = "overview" | "global" | "usage"; const TAB_CONFIG: { id: Tab; label: string }[] = [ { id: "overview", label: "Overview" }, { id: "usage", label: "Metrics" }, { id: "global", label: "Global metrics" }, ]; export default function ModelDetailPage() { const { model, userMetrics, organizationId, projectId, environmentId } = useTypedLoaderData(); const organization = useOrganization(); const project = useProject(); const environment = useEnvironment(); const [activeTab, setActiveTab] = useState("overview"); return ( Compare with... {TAB_CONFIG.map((tab) => ( setActiveTab(tab.id)} > {tab.label} ))}
{activeTab === "overview" && } {activeTab === "global" && ( )} {activeTab === "usage" && ( )}
); } // --- Cost Estimator --- function CostEstimator({ inputPrice, outputPrice, defaultInputTokens, defaultOutputTokens, }: { inputPrice: number | null; outputPrice: number | null; defaultInputTokens?: number; defaultOutputTokens?: number; }) { const [inputTokens, setInputTokens] = useState(defaultInputTokens ?? 1000); const [outputTokens, setOutputTokens] = useState(defaultOutputTokens ?? 500); const [numCalls, setNumCalls] = useState(1000); if (inputPrice === null && outputPrice === null) return null; const inputCost = inputTokens * (inputPrice ?? 0) * numCalls; const outputCost = outputTokens * (outputPrice ?? 0) * numCalls; const totalCost = inputCost + outputCost; return (
Cost Estimator
setInputTokens(parseInt(e.target.value) || 0)} />
setOutputTokens(parseInt(e.target.value) || 0)} />
setNumCalls(parseInt(e.target.value) || 0)} />
{formatModelCost(totalCost)}
Input: {formatModelCost(inputCost)} ({formatTokenCount(inputTokens * numCalls)} tokens x {formatModelPrice(inputPrice)}/1M)
Output: {formatModelCost(outputCost)} ({formatTokenCount(outputTokens * numCalls)}{" "} tokens x {formatModelPrice(outputPrice)}/1M)
); } // --- Overview Tab --- function OverviewTab({ model, userMetrics, }: { model: ReturnType>["model"]; userMetrics: ReturnType>["userMetrics"]; }) { return (
{/* Model Info */}
Model Info Provider {formatProviderName(model.provider)} Model Name {model.modelName} {model.description && ( Description {model.description} )} {model.contextWindow && ( Context Window {formatTokenCount(model.contextWindow)} tokens )} {model.maxOutputTokens && ( Max Output {formatTokenCount(model.maxOutputTokens)} tokens )} {model.features.length > 0 && ( Features
{model.features.map((f) => ( {formatFeature(f)} ))}
)} Match Pattern {model.matchPattern}
{/* Pricing */}
Pricing Input {formatModelPrice(model.inputPrice)} / 1M tokens Output {formatModelPrice(model.outputPrice)} / 1M tokens {model.pricingTiers.length > 1 && (

All pricing tiers

{model.pricingTiers.map((tier) => (
{tier.name} {tier.isDefault && ( default )}
{Object.entries(tier.prices).map(([usage, price]) => (
{usage}: ${(price * 1_000_000).toFixed(4)} / 1M
))}
))}
)}
{/* Cost Estimator */} 0 ? Math.round(userMetrics.totalInputTokens / userMetrics.totalCalls) : undefined } defaultOutputTokens={ userMetrics.totalCalls > 0 ? Math.round(userMetrics.totalOutputTokens / userMetrics.totalCalls) : undefined } />
); } // --- Global Metrics Tab --- function GlobalMetricsTab({ modelName, organizationId, projectId, environmentId, }: { modelName: string; organizationId: string; projectId: string; environmentId: string; }) { const widgetProps = { organizationId, projectId, environmentId, scope: "environment" as const, period: "7d", from: null, to: null, }; return (
{/* Big numbers */}
{/* Charts */}
Aggregated across all Trigger.dev users. No tenant-specific data is exposed.
); } // --- Your Usage Tab --- function YourUsageTab({ modelName, organizationId, projectId, environmentId, }: { modelName: string; organizationId: string; projectId: string; environmentId: string; }) { const widgetProps = { organizationId, projectId, environmentId, scope: "environment" as const, period: "7d", from: null, to: null, }; return (
{/* Big numbers */}
0`} config={bignumberConfig("avg_ttfc", { aggregation: "avg", suffix: "ms" })} {...widgetProps} />
0`} config={bignumberConfig("avg_tps", { aggregation: "avg" })} {...widgetProps} />
{/* Charts */}
{/* Task breakdown */}
); }