(
{children}
;
+export function PageContainer({
+ children,
+ className,
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return (
+
+ {children}
+
+ );
}
export function PageBody({
diff --git a/apps/webapp/app/components/metrics/ModelsFilter.tsx b/apps/webapp/app/components/metrics/ModelsFilter.tsx
index 6250cd20c99..e641f826ae3 100644
--- a/apps/webapp/app/components/metrics/ModelsFilter.tsx
+++ b/apps/webapp/app/components/metrics/ModelsFilter.tsx
@@ -16,7 +16,7 @@ import { tablerIcons } from "~/utils/tablerIcons";
import tablerSpritePath from "~/components/primitives/tabler-sprite.svg";
import { AnthropicLogoIcon } from "~/assets/icons/AnthropicLogoIcon";
-const shortcut = { key: "m" };
+const shortcut = { key: "l" };
export type ModelOption = {
model: string;
diff --git a/apps/webapp/app/components/metrics/ProvidersFilter.tsx b/apps/webapp/app/components/metrics/ProvidersFilter.tsx
index f73a6f00da3..fe018eefb98 100644
--- a/apps/webapp/app/components/metrics/ProvidersFilter.tsx
+++ b/apps/webapp/app/components/metrics/ProvidersFilter.tsx
@@ -13,7 +13,7 @@ import {
import { useSearchParams } from "~/hooks/useSearchParam";
import { appliedSummary, FilterMenuProvider } from "~/components/runs/v3/SharedFilters";
-const shortcut = { key: "v" };
+const shortcut = { key: "r" };
interface ProvidersFilterProps {
possibleProviders: string[];
diff --git a/apps/webapp/app/components/navigation/SideMenu.tsx b/apps/webapp/app/components/navigation/SideMenu.tsx
index 1a1521bc68b..18a4387c996 100644
--- a/apps/webapp/app/components/navigation/SideMenu.tsx
+++ b/apps/webapp/app/components/navigation/SideMenu.tsx
@@ -11,7 +11,6 @@ import {
Cog8ToothIcon,
CogIcon,
ExclamationTriangleIcon,
- PuzzlePieceIcon,
FolderIcon,
FolderOpenIcon,
GlobeAmericasIcon,
@@ -19,19 +18,20 @@ import {
KeyIcon,
PencilSquareIcon,
PlusIcon,
+ PuzzlePieceIcon,
RectangleStackIcon,
- DocumentTextIcon,
ServerStackIcon,
- SparklesIcon,
Squares2X2Icon,
TableCellsIcon,
UsersIcon,
- BugAntIcon,
} from "@heroicons/react/20/solid";
import { Link, useFetcher, useNavigation } from "@remix-run/react";
+import { IconBugFilled } from "@tabler/icons-react";
import { LayoutGroup, motion } from "framer-motion";
import { type ReactNode, useCallback, useEffect, useRef, useState } from "react";
import simplur from "simplur";
+import { AIMetricsIcon } from "~/assets/icons/AIMetricsIcon";
+import { AIPromptsIcon } from "~/assets/icons/AIPromptsIcon";
import { ConcurrencyIcon } from "~/assets/icons/ConcurrencyIcon";
import { DropdownIcon } from "~/assets/icons/DropdownIcon";
import { BranchEnvironmentIconSmall } from "~/assets/icons/EnvironmentIcons";
@@ -75,13 +75,13 @@ import {
v3DeploymentsPath,
v3EnvironmentPath,
v3EnvironmentVariablesPath,
- v3LogsPath,
v3ErrorsPath,
- v3PromptsPath,
+ v3LogsPath,
v3ProjectAlertsPath,
v3ProjectPath,
v3ProjectSettingsGeneralPath,
v3ProjectSettingsIntegrationsPath,
+ v3PromptsPath,
v3QueuesPath,
v3RunsPath,
v3SchedulesPath,
@@ -117,7 +117,6 @@ import { SideMenuHeader } from "./SideMenuHeader";
import { SideMenuItem } from "./SideMenuItem";
import { SideMenuSection } from "./SideMenuSection";
import { type SideMenuSectionId } from "./sideMenuTypes";
-import { IconBugFilled } from "@tabler/icons-react";
/** Get the collapsed state for a specific side menu section from user preferences */
function getSectionCollapsed(
@@ -461,26 +460,25 @@ export function SideMenu({
title="AI"
isSideMenuCollapsed={isCollapsed}
itemSpacingClassName="space-y-0"
- initialCollapsed={getSectionCollapsed(
- user.dashboardPreferences.sideMenu,
- "ai"
- )}
+ initialCollapsed={getSectionCollapsed(user.dashboardPreferences.sideMenu, "ai")}
onCollapseToggle={handleSectionToggle("ai")}
>
) => {
- return (
-
- );
-};
+const ResizablePanelGroup = ({ className, ...props }: React.ComponentProps) => (
+
+);
const ResizablePanel = Panel;
@@ -28,36 +25,42 @@ const ResizableHandle = ({
withHandle?: boolean;
}) => (
{
+ e.preventDefault();
+ }}
className={cn(
- // Base styles
"group relative flex items-center justify-center focus-custom",
- // Horizontal orientation (default)
- "w-0.75 after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2",
- // Vertical orientation
+ // Horizontal size
+ "w-0.75",
+ // Vertical size
"data-[handle-orientation=vertical]:h-0.75 data-[handle-orientation=vertical]:w-full",
+ // Normal-state line (::before) — 1px, centered in the 3px handle
+ "before:absolute before:left-px before:top-0 before:h-full before:w-px before:bg-grid-bright",
+ "data-[handle-orientation=vertical]:before:left-0 data-[handle-orientation=vertical]:before:top-px data-[handle-orientation=vertical]:before:h-px data-[handle-orientation=vertical]:before:w-full",
+ // Hit area (::after pseudo) for easier grabbing
+ "after:absolute after:inset-y-0 after:left-1/2 after:w-3 after:-translate-x-1/2",
"data-[handle-orientation=vertical]:after:inset-x-0 data-[handle-orientation=vertical]:after:inset-y-auto",
"data-[handle-orientation=vertical]:after:left-0 data-[handle-orientation=vertical]:after:top-1/2",
- "data-[handle-orientation=vertical]:after:h-1 data-[handle-orientation=vertical]:after:w-full",
+ "data-[handle-orientation=vertical]:after:h-3 data-[handle-orientation=vertical]:after:w-full",
"data-[handle-orientation=vertical]:after:-translate-y-1/2 data-[handle-orientation=vertical]:after:translate-x-0",
className
)}
size="3px"
{...props}
>
- {/* Horizontal orientation line indicator */}
-
- {/* Vertical orientation line indicator */}
-
+ {/* Indigo hover overlay — absolutely positioned on top of everything */}
+
+
{withHandle && (
<>
{/* Horizontal orientation dots (vertical arrangement) */}
-
+
{Array.from({ length: 3 }).map((_, index) => (
))}
{/* Vertical orientation dots (horizontal arrangement) */}
-
+
{Array.from({ length: 3 }).map((_, index) => (
))}
diff --git a/apps/webapp/app/components/primitives/TextArea.tsx b/apps/webapp/app/components/primitives/TextArea.tsx
index f5350a510bc..06dc84622da 100644
--- a/apps/webapp/app/components/primitives/TextArea.tsx
+++ b/apps/webapp/app/components/primitives/TextArea.tsx
@@ -8,7 +8,7 @@ export function TextArea({ className, rows, ...props }: TextAreaProps) {
{...props}
rows={rows ?? 6}
className={cn(
- "placeholder:text-muted-foreground w-full rounded border border-charcoal-800 bg-charcoal-750 px-3 text-sm text-text-bright transition focus-custom focus-custom file:border-0 file:bg-transparent file:text-base file:font-medium hover:border-charcoal-600 hover:bg-charcoal-650 disabled:cursor-not-allowed disabled:opacity-50",
+ "placeholder:text-muted-foreground w-full rounded border border-charcoal-800 bg-charcoal-750 px-3 text-sm text-text-bright transition focus-custom file:border-0 file:bg-transparent file:text-base file:font-medium hover:border-charcoal-600 hover:bg-charcoal-650 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
/>
diff --git a/apps/webapp/app/components/runs/v3/PromptSpanDetails.tsx b/apps/webapp/app/components/runs/v3/PromptSpanDetails.tsx
index 304f47220b7..9645087b859 100644
--- a/apps/webapp/app/components/runs/v3/PromptSpanDetails.tsx
+++ b/apps/webapp/app/components/runs/v3/PromptSpanDetails.tsx
@@ -10,6 +10,7 @@ import { useProject } from "~/hooks/useProject";
import { v3PromptPath } from "~/utils/pathBuilder";
import { TabButton, TabContainer } from "~/components/primitives/Tabs";
import type { PromptSpanData } from "~/presenters/v3/SpanPresenter.server";
+import { SpanHorizontalTimeline } from "~/components/runs/v3/SpanHorizontalTimeline";
const StreamdownRenderer = lazy(() =>
import("streamdown").then((mod) => ({
@@ -23,7 +24,15 @@ const StreamdownRenderer = lazy(() =>
type PromptTab = "overview" | "input" | "template";
-export function PromptSpanDetails({ promptData }: { promptData: PromptSpanData }) {
+export function PromptSpanDetails({
+ promptData,
+ startTime,
+ duration,
+}: {
+ promptData: PromptSpanData;
+ startTime?: string | Date;
+ duration?: number | null;
+}) {
const organization = useOrganization();
const project = useProject();
const environment = useEnvironment();
@@ -73,6 +82,7 @@ export function PromptSpanDetails({ promptData }: { promptData: PromptSpanData }
{tab === "overview" && (
+ {startTime &&
}
void;
@@ -363,6 +366,7 @@ export function TimeFilter({
to,
labelName = "Created",
hideLabel = false,
+ shortcut,
applyShortcut,
onValueChange,
maxPeriodDays,
@@ -372,6 +376,16 @@ export function TimeFilter({
const periodValue = period ?? value("period");
const fromValue = from ?? value("from");
const toValue = to ?? value("to");
+ const triggerRef = useRef(null);
+
+ useShortcutKeys({
+ shortcut,
+ action: (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ triggerRef.current?.click();
+ },
+ });
const constrained = timeFilters({
period: periodValue,
@@ -386,16 +400,37 @@ export function TimeFilter({
{() => (
}>
-
-
+
+ }
+ />
+ }
+ >
+
+
+ {shortcut && (
+
+
+ Filter by time period
+
+
+
+ )}
+
}
period={constrained.period}
from={constrained.from}
diff --git a/apps/webapp/app/components/runs/v3/SpanHorizontalTimeline.tsx b/apps/webapp/app/components/runs/v3/SpanHorizontalTimeline.tsx
new file mode 100644
index 00000000000..b4acf2a1e7b
--- /dev/null
+++ b/apps/webapp/app/components/runs/v3/SpanHorizontalTimeline.tsx
@@ -0,0 +1,54 @@
+import { DateTimeAccurate } from "~/components/primitives/DateTime";
+
+function formatSpanDuration(nanoseconds: number): string {
+ const ms = nanoseconds / 1_000_000;
+ if (ms < 1000) return `${Math.round(ms)}ms`;
+ if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;
+ const totalSecs = Math.round(ms / 1000);
+ const mins = Math.floor(totalSecs / 60);
+ const secs = totalSecs % 60;
+ return `${mins}m ${secs}s`;
+}
+
+export function SpanHorizontalTimeline({
+ startTime,
+ duration,
+}: {
+ startTime: string | Date;
+ duration: number | null;
+}) {
+ const startDate = startTime instanceof Date ? startTime : new Date(startTime);
+ const endDate = duration != null ? new Date(startDate.getTime() + duration / 1_000_000) : null;
+
+ return (
+
+
+
+ Started
+ Finished
+
+
+
+
+
+
+
+ {duration != null && (
+
+ {formatSpanDuration(duration)}
+
+ )}
+
+
+
+ {endDate ? (
+
+ ) : (
+ —
+ )}
+
+
+
+
+ );
+}
diff --git a/apps/webapp/app/components/runs/v3/SpanTitle.tsx b/apps/webapp/app/components/runs/v3/SpanTitle.tsx
index 2be7bfdcbdd..c54b93cb690 100644
--- a/apps/webapp/app/components/runs/v3/SpanTitle.tsx
+++ b/apps/webapp/app/components/runs/v3/SpanTitle.tsx
@@ -1,5 +1,5 @@
import { ChevronRightIcon } from "@heroicons/react/20/solid";
-import { TaskEventStyle } from "@trigger.dev/core/v3";
+import { type TaskEventStyle } from "@trigger.dev/core/v3";
import type { TaskEventLevel } from "@trigger.dev/database";
import { Fragment } from "react";
import { cn } from "~/utils/cn";
@@ -19,7 +19,7 @@ type SpanTitleProps = {
export function SpanTitle(event: SpanTitleProps) {
return (
- {event.message} {" "}
+ {event.message} {" "}
{!event.hideAccessory && (
)}
diff --git a/apps/webapp/app/components/runs/v3/ai/AISpanDetails.tsx b/apps/webapp/app/components/runs/v3/ai/AISpanDetails.tsx
index c2fc4df6069..5e8bb65688f 100644
--- a/apps/webapp/app/components/runs/v3/ai/AISpanDetails.tsx
+++ b/apps/webapp/app/components/runs/v3/ai/AISpanDetails.tsx
@@ -13,12 +13,12 @@ import { useProject } from "~/hooks/useProject";
import { useHasAdminAccess } from "~/hooks/useUser";
import { v3PromptPath } from "~/utils/pathBuilder";
import { CodeBlock } from "~/components/code/CodeBlock";
-import { AIChatMessages, AssistantResponse, ChatBubble } from "./AIChatMessages";
-import type { PromptLink } from "./AIChatMessages";
+import { AIChatMessages, AssistantResponse, ChatBubble, type PromptLink } from "./AIChatMessages";
import { AIStatsSummary, AITagsRow } from "./AIModelSummary";
import { AIToolsInventory } from "./AIToolsInventory";
import type { AISpanData, DisplayItem } from "./types";
import type { PromptSpanData } from "~/presenters/v3/SpanPresenter.server";
+import { SpanHorizontalTimeline } from "~/components/runs/v3/SpanHorizontalTimeline";
const StreamdownRenderer = lazy(() =>
import("streamdown").then((mod) => ({
@@ -36,10 +36,14 @@ export function AISpanDetails({
aiData,
promptVersionData,
rawProperties,
+ startTime,
+ duration,
}: {
aiData: AISpanData;
promptVersionData?: PromptSpanData;
rawProperties?: string;
+ startTime?: string | Date;
+ duration?: number | null;
}) {
const [tab, setTab] = useState("overview");
const isAdmin = useHasAdminAccess();
@@ -109,7 +113,12 @@ export function AISpanDetails({
{/* Tab content */}
- {tab === "overview" &&
}
+ {tab === "overview" && (
+ <>
+ {startTime &&
}
+
+ >
+ )}
{tab === "messages" &&
}
{tab === "tools" &&
}
{tab === "prompt" && promptVersionData && (
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.prompts.$promptSlug/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.prompts.$promptSlug/route.tsx
index dc84de22bae..5a953c0199b 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.prompts.$promptSlug/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.prompts.$promptSlug/route.tsx
@@ -1,5 +1,6 @@
import * as Ariakit from "@ariakit/react";
-import { ArrowPathIcon, SparklesIcon } from "@heroicons/react/20/solid";
+import { ArrowPathIcon, ChevronUpDownIcon } from "@heroicons/react/20/solid";
+import { DialogClose } from "@radix-ui/react-dialog";
import { type MetaFunction, useFetcher } from "@remix-run/react";
import {
type ActionFunctionArgs,
@@ -7,35 +8,47 @@ import {
type LoaderFunctionArgs,
redirect,
} from "@remix-run/server-runtime";
-import { DialogClose } from "@radix-ui/react-dialog";
+import { AnimatePresence, motion } from "framer-motion";
+import { ClipboardCheckIcon, ClipboardIcon, GitBranchPlusIcon } from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { CodeBlock } from "~/components/code/CodeBlock";
import { TextEditor } from "~/components/code/TextEditor";
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
+import { ModelsFilter } from "~/components/metrics/ModelsFilter";
+import { OperationsFilter } from "~/components/metrics/OperationsFilter";
+import { ProvidersFilter } from "~/components/metrics/ProvidersFilter";
+import { AppliedFilter } from "~/components/primitives/AppliedFilter";
import { Badge } from "~/components/primitives/Badge";
+import { Button, LinkButton } from "~/components/primitives/Buttons";
import { DateTime } from "~/components/primitives/DateTime";
-import { Button } from "~/components/primitives/Buttons";
-import { Header2, Header3 } from "~/components/primitives/Headers";
import { Dialog, DialogContent, DialogHeader } from "~/components/primitives/Dialog";
+import { Header3 } from "~/components/primitives/Headers";
import { Hint } from "~/components/primitives/Hint";
import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
import { Paragraph } from "~/components/primitives/Paragraph";
-import { Spinner } from "~/components/primitives/Spinner";
+import { Popover, PopoverContent, PopoverTrigger } from "~/components/primitives/Popover";
import * as Property from "~/components/primitives/PropertyTable";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableCellMenu,
+ TableHeader,
+ TableHeaderCell,
+ TableRow,
+} from "~/components/primitives/Table";
+import { RadioButtonCircle } from "~/components/primitives/RadioButton";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
type ResizableSnapshot,
} from "~/components/primitives/Resizable";
-import { TabButton, TabContainer } from "~/components/primitives/Tabs";
-import { CopyButton } from "~/components/primitives/CopyButton";
-import { CopyableText } from "~/components/primitives/CopyableText";
import {
SelectItem,
SelectList,
@@ -43,35 +56,35 @@ import {
SelectProvider,
SelectTrigger,
} from "~/components/primitives/Select";
+import { Spinner } from "~/components/primitives/Spinner";
+import { TabButton, TabContainer } from "~/components/primitives/Tabs";
import { TextArea } from "~/components/primitives/TextArea";
-import { ModelsFilter } from "~/components/metrics/ModelsFilter";
-import { OperationsFilter } from "~/components/metrics/OperationsFilter";
-import { ProvidersFilter } from "~/components/metrics/ProvidersFilter";
-import { AppliedFilter } from "~/components/primitives/AppliedFilter";
-import tablerSpritePath from "~/components/primitives/tabler-sprite.svg";
import { TimeFilter } from "~/components/runs/v3/SharedFilters";
-import { SpanView } from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route";
+import { prisma } from "~/db.server";
import { useEnvironment } from "~/hooks/useEnvironment";
+import { useInterval } from "~/hooks/useInterval";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
-import { prisma } from "~/db.server";
-import { clickhouseClient } from "~/services/clickhouseInstance.server";
-import { useInterval } from "~/hooks/useInterval";
import { useSearchParams } from "~/hooks/useSearchParam";
import { findProjectBySlug } from "~/models/project.server";
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
-import { PromptPresenter, type GenerationRow } from "~/presenters/v3/PromptPresenter.server";
+import { type GenerationRow, PromptPresenter } from "~/presenters/v3/PromptPresenter.server";
+import { SpanView } from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route";
+import { clickhouseClient } from "~/services/clickhouseInstance.server";
import { getResizableSnapshot } from "~/services/resizablePanel.server";
import { requireUserId } from "~/services/session.server";
import { PromptService } from "~/v3/services/promptService.server";
-import { MetricWidget } from "~/routes/resources.metric";
-import { InfoPanel } from "~/components/primitives/InfoPanel";
+import { z } from "zod";
+import { AIPromptsIcon } from "~/assets/icons/AIPromptsIcon";
+import { RunsIcon } from "~/assets/icons/RunsIcon";
import { InlineCode } from "~/components/code/InlineCode";
-import { TextLink } from "~/components/primitives/TextLink";
+import { InfoPanel } from "~/components/primitives/InfoPanel";
+import { SimpleTooltip } from "~/components/primitives/Tooltip";
+import { MetricWidget } from "~/routes/resources.metric";
+import { cn } from "~/utils/cn";
import { EnvironmentParamSchema, v3PromptsPath, v3RunSpanPath } from "~/utils/pathBuilder";
import { parsePeriodToMs } from "~/utils/periods";
-import { z } from "zod";
const ParamSchema = EnvironmentParamSchema.extend({
promptSlug: z.string(),
@@ -233,7 +246,11 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
let generations: Awaited
>["generations"] = [];
let generationsPagination: { next?: string } = {};
try {
- const urlVersions = url.searchParams.getAll("versions").filter(Boolean).map(Number).filter((n) => !isNaN(n));
+ const urlVersions = url.searchParams
+ .getAll("versions")
+ .filter(Boolean)
+ .map(Number)
+ .filter((n) => !isNaN(n));
const urlModels = url.searchParams.getAll("models").filter(Boolean);
const urlOperations = url.searchParams.getAll("operations").filter(Boolean);
const urlProviders = url.searchParams.getAll("providers").filter(Boolean);
@@ -284,7 +301,11 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const possibleProviders = provsErr ? [] : provsRows.map((r) => r.val);
return typedjson({
- resizable: { outer: resizableOuter, vertical: resizableVertical, generations: resizableGenerations },
+ resizable: {
+ outer: resizableOuter,
+ vertical: resizableVertical,
+ generations: resizableGenerations,
+ },
prompt: {
id: prompt.id,
friendlyId: prompt.friendlyId,
@@ -460,33 +481,44 @@ export default function PromptDetailPage() {
};
return (
-
+
- {prompt.slug}
-
-
-
-
+
}
backButton={{ to: v3PromptsPath(organization, project, environment), text: "Prompts" }}
/>
{selectedVersion && (
-
- v{selectedVersion.version}
- {isCurrent && current }
+
+
+
v{selectedVersion.version}
+ {isCurrent &&
current }
{selectedVersion.labels.includes("override") && (
-
override
+
+ override
+
)}
-
+
)}
{selectedVersion && !isCurrent && selectedVersion.source === "code" && (
handlePromote(selectedVersion.id)}
disabled={fetcher.state !== "idle"}
>
@@ -497,7 +529,7 @@ export default function PromptDetailPage() {
selectedVersion.source !== "code" &&
!selectedVersion.labels.includes("override") && (
fetcher.submit(
{ intent: "reactivateOverride", versionId: selectedVersion.id },
@@ -510,37 +542,48 @@ export default function PromptDetailPage() {
)}
{!overrideVersion && (
- setOverrideDialogOpen(true)}>
- Create Override
+ setOverrideDialogOpen(true)}>
+ Create override
)}
- {overrideVersion && (
-
-
- Override v{overrideVersion.version} is active. API calls resolve this version instead of the deployed prompt.
-
-
-
setOverrideDialogOpen(true)}
- >
- Edit
-
-
- fetcher.submit({ intent: "removeOverride" }, { method: "POST" })
- }
- disabled={fetcher.state !== "idle"}
+
+
+ {overrideVersion && (
+
- Remove
-
-
-
- )}
+
+ Override v{overrideVersion.version} is active. API calls resolve to this version
+ instead of the deployed prompt.
+
+
+ setOverrideDialogOpen(true)}
+ >
+ Edit
+
+ fetcher.submit({ intent: "removeOverride" }, { method: "POST" })}
+ disabled={fetcher.state !== "idle"}
+ >
+ Remove
+
+
+
+ )}
+
+
{/* Template panel */}
-
- {/* Sticky header */}
-
-
- Template
- {content && }
-
+ {content ? (
+
+ ) : (
+
- {/* Scrollable content */}
- {content ? (
-
-
-
- ) : (
-
- )}
-
+ )}
@@ -593,8 +626,8 @@ export default function PromptDetailPage() {
{/* Tab bar */}
-
-
+
+
-
+
({ model: m, system: "" }))}
@@ -640,12 +673,18 @@ export default function PromptDetailPage() {
labelName="Period"
hideLabel
valueClassName="text-text-bright"
+ shortcut={{ key: "w" }}
/>
{/* Tab content */}
-
+
{contentTab === "generations" && (
{/* Sidebar */}
-