diff --git a/apps/dashboard/app/(tenant)/agents/page.tsx b/apps/dashboard/app/(tenant)/agents/page.tsx index bbb2ad1..39e8010 100644 --- a/apps/dashboard/app/(tenant)/agents/page.tsx +++ b/apps/dashboard/app/(tenant)/agents/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import Link from "next/link"; import { CopyPill } from "@ainfera/ui"; +import { Crumbs } from "@/components/v15/Crumbs"; import { getOwnerHandle } from "@/lib/ainfera/proxy"; import { listAgents } from "@/lib/v15/api"; @@ -13,6 +14,14 @@ export default async function AgentsPage() { return ( <> + {/* AIN-251 W4 · shared for cross-page parity. */} + +

Agents

diff --git a/apps/dashboard/app/(tenant)/inferences/[id]/InferenceDetailClient.tsx b/apps/dashboard/app/(tenant)/inferences/[id]/InferenceDetailClient.tsx index 4502527..beadccc 100644 --- a/apps/dashboard/app/(tenant)/inferences/[id]/InferenceDetailClient.tsx +++ b/apps/dashboard/app/(tenant)/inferences/[id]/InferenceDetailClient.tsx @@ -215,6 +215,74 @@ export function InferenceDetailClient({ inferenceId, initial }: Props) { ) : null}
+ {/* AIN-266 W3 · candidates card — the routing decision artifact. + * Surfaces every model Ainfera Inference considered for this call, + * the rank order, q_prior, projected cost, and drop reasons. + * Renders only when the api returned a non-empty candidates list + * (null on pre-§16 rows + pinned-passthroughs that bypass the + * brain). Authed-tenant-only on the wire (§D3 lock). */} + {inf.candidates && inf.candidates.length > 0 ? ( +
+
+

+ Candidates ({inf.candidates.length}) +

+ + the decision · ranked + +
+
+ + + + + + + + + + + + + + {inf.candidates.map((c, i) => { + const chosen = c.model_slug === inf.model_used; + return ( + + + + + + + + + + ); + })} + +
#ModelBrandq_priorproj. costeligibledrop reason
{c.rank ?? i + 1} + {c.model_slug ?? "—"} + {chosen ? ( + + chosen + + ) : null} + {c.brand_slug ?? "—"}{c.q_prior ?? "—"} + {c.projected_cost_usd ? fmtCost(c.projected_cost_usd) : "—"} + + {c.m_allowed === true ? "yes" : c.m_allowed === false ? "no" : "—"} + {c.drop_reason ?? "—"}
+
+
+ ) : null} + {/* Audit chain block — AIN-182 §4 "Verify with curl" requirement. */}
diff --git a/apps/dashboard/app/(tenant)/inferences/[id]/types.ts b/apps/dashboard/app/(tenant)/inferences/[id]/types.ts index 327b044..1cb48f9 100644 --- a/apps/dashboard/app/(tenant)/inferences/[id]/types.ts +++ b/apps/dashboard/app/(tenant)/inferences/[id]/types.ts @@ -35,4 +35,25 @@ export type InferenceDetail = { policy_version?: string | null; cell?: string | null; routing_rationale?: string | null; + /** AIN-266 W3 · routing candidate set. Mirrors §16 + * routing_outcomes.candidates JSONB. NULL on pre-2026-05-21 rows + + * pinned-passthrough calls. AUTHED-tenant-only on the wire (§D3 lock + * — never on /v1/audit/public). */ + candidates?: InferenceCandidate[] | null; +}; + +/** One row of the §16 candidate set. Keys mirror the JSONB shape on + * routing_outcomes exactly. Decimals come over as strings to preserve + * precision through JSON. */ +export type InferenceCandidate = { + model_id?: string | null; + model_slug?: string | null; + brand_slug?: string | null; + q_prior?: string | null; + price_in_per_mtok_usd?: string | null; + price_out_per_mtok_usd?: string | null; + m_allowed?: boolean | null; + projected_cost_usd?: string | null; + drop_reason?: string | null; + rank?: number | null; }; diff --git a/apps/dashboard/app/(tenant)/inferences/page.tsx b/apps/dashboard/app/(tenant)/inferences/page.tsx index 16785a8..5bd71c0 100644 --- a/apps/dashboard/app/(tenant)/inferences/page.tsx +++ b/apps/dashboard/app/(tenant)/inferences/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import Link from "next/link"; +import { Crumbs } from "@/components/v15/Crumbs"; import { getOwnerHandle } from "@/lib/ainfera/proxy"; import { publicAuditFeed, publicLeaderboard } from "@/lib/v15/api"; @@ -25,15 +26,14 @@ export default async function InferencesIndexPage() { return ( <> -
-
-
- workspace - / - inferences -
-
-
+ {/* AIN-251 W4 · shared for cross-page parity. */} + +
diff --git a/apps/dashboard/app/(tenant)/workflows/page.tsx b/apps/dashboard/app/(tenant)/workflows/page.tsx index de8d940..ced96a9 100644 --- a/apps/dashboard/app/(tenant)/workflows/page.tsx +++ b/apps/dashboard/app/(tenant)/workflows/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import Link from "next/link"; +import { Crumbs } from "@/components/v15/Crumbs"; import { getOwnerHandle } from "@/lib/ainfera/proxy"; import { listWorkflows } from "@/lib/v15/api"; @@ -12,6 +13,14 @@ export default async function WorkflowsPage() { return ( <> + {/* AIN-251 W4 · shared for cross-page parity. */} + +

Workflows