Skip to content

Commit 3c696b7

Browse files
committed
feat: operations/providers filters, prompt version model display, override fixes, llm pricing sync
1 parent d7efcb8 commit 3c696b7

12 files changed

Lines changed: 660 additions & 64 deletions

File tree

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { CommandLineIcon } from "@heroicons/react/20/solid";
2+
import * as Ariakit from "@ariakit/react";
3+
import { type ReactNode, useMemo } from "react";
4+
import { AppliedFilter } from "~/components/primitives/AppliedFilter";
5+
import {
6+
ComboBox,
7+
SelectItem,
8+
SelectList,
9+
SelectPopover,
10+
SelectProvider,
11+
SelectTrigger,
12+
} from "~/components/primitives/Select";
13+
import { useSearchParams } from "~/hooks/useSearchParam";
14+
import { appliedSummary, FilterMenuProvider } from "~/components/runs/v3/SharedFilters";
15+
16+
const shortcut = { key: "n" };
17+
18+
interface OperationsFilterProps {
19+
possibleOperations: string[];
20+
}
21+
22+
/** Pretty-print an operation ID like "ai.generateText.doGenerate" → "generateText" */
23+
function formatOperation(op: string): string {
24+
const parts = op.split(".");
25+
// ai.generateText.doGenerate → generateText
26+
// ai.streamText.doStream → streamText
27+
if (parts.length >= 2 && parts[0] === "ai") {
28+
return parts[1];
29+
}
30+
return op;
31+
}
32+
33+
export function OperationsFilter({ possibleOperations }: OperationsFilterProps) {
34+
const { values, replace, del } = useSearchParams();
35+
const selectedOperations = values("operations");
36+
37+
if (selectedOperations.length === 0 || selectedOperations.every((v) => v === "")) {
38+
return (
39+
<FilterMenuProvider>
40+
{(search, setSearch) => (
41+
<OperationsDropdown
42+
trigger={
43+
<SelectTrigger
44+
icon={<CommandLineIcon className="size-4" />}
45+
variant="secondary/small"
46+
shortcut={shortcut}
47+
tooltipTitle="Filter by operation"
48+
>
49+
<span className="ml-0.5">Operations</span>
50+
</SelectTrigger>
51+
}
52+
searchValue={search}
53+
clearSearchValue={() => setSearch("")}
54+
possibleOperations={possibleOperations}
55+
/>
56+
)}
57+
</FilterMenuProvider>
58+
);
59+
}
60+
61+
return (
62+
<FilterMenuProvider>
63+
{(search, setSearch) => (
64+
<OperationsDropdown
65+
trigger={
66+
<Ariakit.Select render={<div className="group cursor-pointer focus-custom" />}>
67+
<AppliedFilter
68+
label="Operation"
69+
icon={<CommandLineIcon className="size-4" />}
70+
value={appliedSummary(selectedOperations.map(formatOperation))}
71+
onRemove={() => del(["operations"])}
72+
variant="secondary/small"
73+
/>
74+
</Ariakit.Select>
75+
}
76+
searchValue={search}
77+
clearSearchValue={() => setSearch("")}
78+
possibleOperations={possibleOperations}
79+
/>
80+
)}
81+
</FilterMenuProvider>
82+
);
83+
}
84+
85+
function OperationsDropdown({
86+
trigger,
87+
clearSearchValue,
88+
searchValue,
89+
onClose,
90+
possibleOperations,
91+
}: {
92+
trigger: ReactNode;
93+
clearSearchValue: () => void;
94+
searchValue: string;
95+
onClose?: () => void;
96+
possibleOperations: string[];
97+
}) {
98+
const { values, replace } = useSearchParams();
99+
100+
const handleChange = (values: string[]) => {
101+
clearSearchValue();
102+
replace({ operations: values });
103+
};
104+
105+
const filtered = useMemo(() => {
106+
const q = searchValue.toLowerCase();
107+
return possibleOperations.filter(
108+
(op) => op.toLowerCase().includes(q) || formatOperation(op).toLowerCase().includes(q)
109+
);
110+
}, [searchValue, possibleOperations]);
111+
112+
return (
113+
<SelectProvider value={values("operations")} setValue={handleChange} virtualFocus={true}>
114+
{trigger}
115+
<SelectPopover
116+
className="min-w-0 max-w-[min(360px,var(--popover-available-width))]"
117+
hideOnEscape={() => {
118+
if (onClose) {
119+
onClose();
120+
return false;
121+
}
122+
return true;
123+
}}
124+
>
125+
<ComboBox placeholder="Filter by operation..." value={searchValue} />
126+
<SelectList>
127+
{filtered.map((op) => (
128+
<SelectItem key={op} value={op} icon={<CommandLineIcon className="size-4" />}>
129+
{formatOperation(op)}
130+
</SelectItem>
131+
))}
132+
{filtered.length === 0 && <SelectItem disabled>No operations found</SelectItem>}
133+
</SelectList>
134+
</SelectPopover>
135+
</SelectProvider>
136+
);
137+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { ServerIcon } from "@heroicons/react/20/solid";
2+
import * as Ariakit from "@ariakit/react";
3+
import { type ReactNode, useMemo } from "react";
4+
import { AppliedFilter } from "~/components/primitives/AppliedFilter";
5+
import {
6+
ComboBox,
7+
SelectItem,
8+
SelectList,
9+
SelectPopover,
10+
SelectProvider,
11+
SelectTrigger,
12+
} from "~/components/primitives/Select";
13+
import { useSearchParams } from "~/hooks/useSearchParam";
14+
import { appliedSummary, FilterMenuProvider } from "~/components/runs/v3/SharedFilters";
15+
16+
const shortcut = { key: "v" };
17+
18+
interface ProvidersFilterProps {
19+
possibleProviders: string[];
20+
}
21+
22+
export function ProvidersFilter({ possibleProviders }: ProvidersFilterProps) {
23+
const { values, replace, del } = useSearchParams();
24+
const selectedProviders = values("providers");
25+
26+
if (selectedProviders.length === 0 || selectedProviders.every((v) => v === "")) {
27+
return (
28+
<FilterMenuProvider>
29+
{(search, setSearch) => (
30+
<ProvidersDropdown
31+
trigger={
32+
<SelectTrigger
33+
icon={<ServerIcon className="size-4" />}
34+
variant="secondary/small"
35+
shortcut={shortcut}
36+
tooltipTitle="Filter by provider"
37+
>
38+
<span className="ml-0.5">Providers</span>
39+
</SelectTrigger>
40+
}
41+
searchValue={search}
42+
clearSearchValue={() => setSearch("")}
43+
possibleProviders={possibleProviders}
44+
/>
45+
)}
46+
</FilterMenuProvider>
47+
);
48+
}
49+
50+
return (
51+
<FilterMenuProvider>
52+
{(search, setSearch) => (
53+
<ProvidersDropdown
54+
trigger={
55+
<Ariakit.Select render={<div className="group cursor-pointer focus-custom" />}>
56+
<AppliedFilter
57+
label="Provider"
58+
icon={<ServerIcon className="size-4" />}
59+
value={appliedSummary(selectedProviders)}
60+
onRemove={() => del(["providers"])}
61+
variant="secondary/small"
62+
/>
63+
</Ariakit.Select>
64+
}
65+
searchValue={search}
66+
clearSearchValue={() => setSearch("")}
67+
possibleProviders={possibleProviders}
68+
/>
69+
)}
70+
</FilterMenuProvider>
71+
);
72+
}
73+
74+
function ProvidersDropdown({
75+
trigger,
76+
clearSearchValue,
77+
searchValue,
78+
onClose,
79+
possibleProviders,
80+
}: {
81+
trigger: ReactNode;
82+
clearSearchValue: () => void;
83+
searchValue: string;
84+
onClose?: () => void;
85+
possibleProviders: string[];
86+
}) {
87+
const { values, replace } = useSearchParams();
88+
89+
const handleChange = (values: string[]) => {
90+
clearSearchValue();
91+
replace({ providers: values });
92+
};
93+
94+
const filtered = useMemo(() => {
95+
return possibleProviders.filter((p) => p.toLowerCase().includes(searchValue.toLowerCase()));
96+
}, [searchValue, possibleProviders]);
97+
98+
return (
99+
<SelectProvider value={values("providers")} setValue={handleChange} virtualFocus={true}>
100+
{trigger}
101+
<SelectPopover
102+
className="min-w-0 max-w-[min(360px,var(--popover-available-width))]"
103+
hideOnEscape={() => {
104+
if (onClose) {
105+
onClose();
106+
return false;
107+
}
108+
return true;
109+
}}
110+
>
111+
<ComboBox placeholder="Filter by provider..." value={searchValue} />
112+
<SelectList>
113+
{filtered.map((provider) => (
114+
<SelectItem key={provider} value={provider} icon={<ServerIcon className="size-4" />}>
115+
{provider}
116+
</SelectItem>
117+
))}
118+
{filtered.length === 0 && <SelectItem disabled>No providers found</SelectItem>}
119+
</SelectList>
120+
</SelectPopover>
121+
</SelectProvider>
122+
);
123+
}

apps/webapp/app/presenters/v3/BuiltInDashboards.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ const overviewDashboard: BuiltInDashboard = {
217217
const llmDashboard: BuiltInDashboard = {
218218
key: "llm",
219219
title: "AI Metrics",
220-
filters: ["tasks", "models", "prompts"],
220+
filters: ["tasks", "models", "prompts", "operations", "providers"],
221221
layout: {
222222
version: "1",
223223
layout: [

apps/webapp/app/presenters/v3/MetricDashboardPresenter.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export type CustomDashboard = {
5858
defaultPeriod: string;
5959
};
6060

61-
export type BuiltInDashboardFilter = "tasks" | "queues" | "models" | "prompts";
61+
export type BuiltInDashboardFilter = "tasks" | "queues" | "models" | "prompts" | "operations" | "providers";
6262

6363
export type BuiltInDashboard = {
6464
key: string;

apps/webapp/app/presenters/v3/PromptPresenter.server.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export class PromptPresenter extends BasePresenter {
5656
select: {
5757
version: true,
5858
labels: true,
59+
model: true,
5960
},
6061
},
6162
},
@@ -64,15 +65,20 @@ export class PromptPresenter extends BasePresenter {
6465

6566
return prompts.map((p) => {
6667
const currentVersion = p.versions.find((v) => v.labels.includes("current"));
67-
const hasOverride = p.versions.some((v) => v.labels.includes("override"));
68+
const overrideVersion = p.versions.find((v) => v.labels.includes("override"));
69+
const hasOverride = !!overrideVersion;
70+
71+
// Effective model: override > current version > prompt default
72+
const effectiveModel =
73+
overrideVersion?.model ?? currentVersion?.model ?? p.defaultModel;
6874

6975
return {
7076
id: p.id,
7177
friendlyId: p.friendlyId,
7278
slug: p.slug,
7379
description: p.description,
7480
tags: p.tags,
75-
defaultModel: p.defaultModel,
81+
defaultModel: effectiveModel,
7682
currentVersion: currentVersion ? { version: currentVersion.version } : null,
7783
hasOverride,
7884
updatedAt: p.updatedAt,

0 commit comments

Comments
 (0)