Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { NextResponse, NextRequest } from "next/server";

export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ topic_relevance_config_id: string }> },
{ params }: { params: Promise<{ llm_prompt_config_id: string }> },
) {
try {
const { topic_relevance_config_id } = await params;
const { llm_prompt_config_id } = await params;
const { status, data } = await guardrailsUserClient(
request,
`/api/v1/guardrails/topic_relevance_configs/${topic_relevance_config_id}`,
`/api/v1/guardrails/llm_prompt_configs/${llm_prompt_config_id}`,
);
return NextResponse.json(data, { status });
} catch (e: unknown) {
Expand All @@ -20,18 +20,18 @@ export async function GET(
}
}

export async function PUT(
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ topic_relevance_config_id: string }> },
{ params }: { params: Promise<{ llm_prompt_config_id: string }> },
) {
try {
const { topic_relevance_config_id } = await params;
const { llm_prompt_config_id } = await params;
const body = await request.json();
const { status, data } = await guardrailsUserClient(
request,
`/api/v1/guardrails/topic_relevance_configs/${topic_relevance_config_id}`,
`/api/v1/guardrails/llm_prompt_configs/${llm_prompt_config_id}`,
{
method: "PUT",
method: "PATCH",
body: JSON.stringify(body),
},
);
Expand All @@ -46,13 +46,13 @@ export async function PUT(

export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ topic_relevance_config_id: string }> },
{ params }: { params: Promise<{ llm_prompt_config_id: string }> },
) {
try {
const { topic_relevance_config_id } = await params;
const { llm_prompt_config_id } = await params;
const { status, data } = await guardrailsUserClient(
request,
`/api/v1/guardrails/topic_relevance_configs/${topic_relevance_config_id}`,
`/api/v1/guardrails/llm_prompt_configs/${llm_prompt_config_id}`,
{
method: "DELETE",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { NextResponse, NextRequest } from "next/server";

export async function GET(request: NextRequest) {
try {
const { status, data } = await guardrailsUserClient(
request,
"/api/v1/guardrails/topic_relevance_configs/",
);
const url = new URL(request.url);
const qs = url.searchParams.toString();
const path = `/api/v1/guardrails/llm_prompt_configs/${qs ? `?${qs}` : ""}`;
const { status, data } = await guardrailsUserClient(request, path);
return NextResponse.json(data, { status });
} catch (e: unknown) {
return NextResponse.json(
Expand All @@ -21,7 +21,7 @@ export async function POST(request: NextRequest) {
const body = await request.json();
const { status, data } = await guardrailsUserClient(
request,
"/api/v1/guardrails/topic_relevance_configs/",
"/api/v1/guardrails/llm_prompt_configs/",
{
method: "POST",
body: JSON.stringify(body),
Expand Down
4 changes: 2 additions & 2 deletions app/components/analytics/AnalyticsChartCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import BreakdownPanel from "./BreakdownPanel";
import LineTrendChart from "./LineTrendChart";

const SERIES_COLORS = [
"#2563eb",
"#e11d48",
"#1f4496",
"#ffce00",
"#16a34a",
"#f59e0b",
"#7c3aed",
Expand Down
40 changes: 19 additions & 21 deletions app/components/analytics/AnalyticsTotalsRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@

import { ReactNode } from "react";
import { InfoTooltip, Loader } from "@/app/components/ui";
import { AnalyticsTotalsRowProps } from "@/app/lib/types/analytics";
import {
AnalyticsCardAccent,
AnalyticsTotalsRowProps,
} from "@/app/lib/types/analytics";

const ACCENT_CARD: Record<AnalyticsCardAccent, string> = {
cost: "bg-status-success-bg/40 border-status-success-border/40",
usage: "bg-accent-primary/5 border-accent-primary/20",
activity: "bg-status-warning-bg/40 border-status-warning-border/40",
};

function formatTokens(n: number): string {
if (!Number.isFinite(n)) return "0";
Expand Down Expand Up @@ -52,29 +61,26 @@ export default function AnalyticsTotalsRow({
<h3 className="text-sm font-semibold text-text-primary tracking-tight">
All-time totals
</h3>
<p className="text-[11px] text-text-secondary">
Real users on the left, quality checks on the right.
</p>
</div>

<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2.5">
<StatCard
accent="cost"
label="Production cost"
label="AI cost"
value={formatCurrency(totals.cost.value)}
hint="Real users using your AI"
tooltip="Money spent on AI requests made by your real users (excludes any testing or quality checks you ran)."
/>
<StatCard
accent="usage"
label="Production tokens"
label="AI usage tokens"
value={`${formatTokens(totals.cost.totalTokens)} tokens`}
hint={`${formatCount(totals.cost.inputTokens)} in · ${formatCount(totals.cost.outputTokens)} out`}
tooltip="The amount of text your AI processed for real users. Tokens are how AI providers measure work and bill you — roughly one word ≈ 1.3 tokens."
/>
<StatCard
accent="activity"
label="Production requests"
label="AI requests"
value={formatCount(totals.requests.value)}
hint="Real-user AI requests"
tooltip="How many times your real users asked the AI to do something."
Expand All @@ -83,36 +89,28 @@ export default function AnalyticsTotalsRow({
accent="cost"
label="Eval cost"
value={formatCurrency(totals.eval_cost.value)}
hint="Quality checks you ran"
tooltip="Money spent on AI requests used for testing and quality checks (separate from real-user activity)."
hint="Evaluations you ran"
tooltip="Money spent on AI requests used for running evaluations (separate from user activity)."
/>
<StatCard
accent="usage"
label="Eval tokens"
value={`${formatTokens(totals.eval_cost.totalTokens)} tokens`}
hint={`${formatCount(totals.eval_cost.inputTokens)} in · ${formatCount(totals.eval_cost.outputTokens)} out`}
tooltip="The amount of text processed during your quality checks. Tokens are how AI providers measure work and bill you."
tooltip="The amount of text processed during your evaluations. Tokens are how AI providers measure work and bill you."
/>
<StatCard
accent="activity"
label="Eval runs"
value={formatCount(totals.eval_runs.value)}
hint="Quality-check batches"
tooltip="How many times you ran a quality check on your AI. Each run can test the AI on many cases at once."
hint="Eval-run activity"
tooltip="How many times you ran an evaluation on your AI. Each run can test it on many cases at once."
/>
</div>
</div>
);
}

type Accent = "cost" | "usage" | "activity";

const ACCENT_CARD: Record<Accent, string> = {
cost: "bg-status-success-bg/40 border-status-success-border/40",
usage: "bg-accent-primary/5 border-accent-primary/20",
activity: "bg-status-warning-bg/40 border-status-warning-border/40",
};

function StatCard({
label,
value,
Expand All @@ -124,7 +122,7 @@ function StatCard({
value: string;
hint?: string;
tooltip?: ReactNode;
accent: Accent;
accent: AnalyticsCardAccent;
}) {
return (
<div className={`rounded-lg border p-3 ${ACCENT_CARD[accent]}`}>
Expand Down
31 changes: 16 additions & 15 deletions app/components/guardrails/TopicRelevanceField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,25 @@ export default function TopicRelevanceField({

guardrailsFetch<{
data?:
| { topic_relevance_configs?: TopicRelevanceConfig[] }
| { llm_prompt_configs?: TopicRelevanceConfig[] }
| TopicRelevanceConfig[];
topic_relevance_configs?: TopicRelevanceConfig[];
}>("/api/guardrails/topic_relevance_configs", apiKey)
llm_prompt_configs?: TopicRelevanceConfig[];
}>(
"/api/guardrails/llm_prompt_configs?validator_name=topic_relevance",
apiKey,
)
.then((data) => {
const nested = data?.data;
const list: TopicRelevanceConfig[] = Array.isArray(
(nested as { topic_relevance_configs?: TopicRelevanceConfig[] })
?.topic_relevance_configs,
(nested as { llm_prompt_configs?: TopicRelevanceConfig[] })
?.llm_prompt_configs,
)
? (
nested as {
topic_relevance_configs: TopicRelevanceConfig[];
}
).topic_relevance_configs
? (nested as { llm_prompt_configs: TopicRelevanceConfig[] })
.llm_prompt_configs
: Array.isArray(nested)
? (nested as TopicRelevanceConfig[])
: Array.isArray(data?.topic_relevance_configs)
? data.topic_relevance_configs!
: Array.isArray(data?.llm_prompt_configs)
? data.llm_prompt_configs!
: [];
setConfigs(list);
})
Expand Down Expand Up @@ -101,16 +101,17 @@ export default function TopicRelevanceField({
let cancelled = false;
setDetailsLoading(true);
guardrailsFetch<{
data?: TopicRelevanceConfigDetails;
data?: TopicRelevanceConfigDetails & { llm_prompt?: string | null };
description?: string | null;
configuration?: string | null;
}>(`/api/guardrails/topic_relevance_configs/${value}`, apiKey)
llm_prompt?: string | null;
}>(`/api/guardrails/llm_prompt_configs/${value}`, apiKey)
.then((d) => {
if (cancelled) return;
const entity = d?.data ?? d;
setDetails({
description: entity?.description ?? null,
configuration: entity?.configuration ?? null,
configuration: entity?.llm_prompt ?? entity?.configuration ?? null,
});
})
.catch(() => {
Expand Down
5 changes: 3 additions & 2 deletions app/components/guardrails/TopicRelevanceModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,14 @@ export default function TopicRelevanceModal({
try {
setIsSaving(true);
const body = {
validator_name: "topic_relevance",
name: name.trim(),
description: description.trim(),
configuration: configuration.trim(),
llm_prompt: configuration.trim(),
prompt_schema_version: promptSchemaVersion,
};
const data = await guardrailsFetch<{ id: string; name?: string }>(
"/api/guardrails/topic_relevance_configs",
"/api/guardrails/llm_prompt_configs",
apiKey,
{ method: "POST", body: JSON.stringify(body) },
);
Expand Down
2 changes: 2 additions & 0 deletions app/lib/types/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ export interface AnalyticsTotalsRowProps {
error: string | null;
}

export type AnalyticsCardAccent = "cost" | "usage" | "activity";

export interface AnalyticsChartRow {
month: string;
monthIso: string;
Expand Down
Loading