149 lines
5.5 KiB
TypeScript
149 lines
5.5 KiB
TypeScript
import { Suspense, useState } from "react";
|
|
import { CodeBlock } from "~/components/code/CodeBlock";
|
|
import { StreamdownRenderer } from "~/components/code/StreamdownRenderer";
|
|
import { Header3 } from "~/components/primitives/Headers";
|
|
import { TextLink } from "~/components/primitives/TextLink";
|
|
import { tryPrettyJson } from "./ai/aiHelpers";
|
|
import { SpanMetricRow as MetricRow } from "./ai/SpanMetricRow";
|
|
import { useEnvironment } from "~/hooks/useEnvironment";
|
|
import { useOrganization } from "~/hooks/useOrganizations";
|
|
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";
|
|
|
|
type PromptTab = "overview" | "input" | "template";
|
|
|
|
export function PromptSpanDetails({
|
|
promptData,
|
|
startTime,
|
|
duration,
|
|
}: {
|
|
promptData: PromptSpanData;
|
|
startTime?: string | Date;
|
|
duration?: number | null;
|
|
}) {
|
|
const organization = useOrganization();
|
|
const project = useProject();
|
|
const environment = useEnvironment();
|
|
|
|
const promptPath =
|
|
organization && project && environment
|
|
? v3PromptPath(organization, project, environment, promptData.slug, promptData.version)
|
|
: undefined;
|
|
|
|
const hasInput = !!promptData.input;
|
|
const hasTemplate = !!promptData.template;
|
|
|
|
const availableTabs: PromptTab[] = [
|
|
"overview",
|
|
...(hasInput ? (["input"] as const) : []),
|
|
...(hasTemplate ? (["template"] as const) : []),
|
|
];
|
|
const [tab, setTab] = useState<PromptTab>("overview");
|
|
|
|
const labels = promptData.labels
|
|
? promptData.labels
|
|
.split(",")
|
|
.map((l) => l.trim())
|
|
.filter(Boolean)
|
|
: [];
|
|
|
|
return (
|
|
<div className="flex h-full flex-col overflow-hidden">
|
|
<div className="shrink-0 overflow-x-auto px-3 py-1 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control">
|
|
<TabContainer>
|
|
{availableTabs.map((t) => (
|
|
<TabButton
|
|
key={t}
|
|
isActive={tab === t}
|
|
layoutId="prompt-span"
|
|
onClick={() => setTab(t)}
|
|
shortcut={
|
|
t === "overview" ? { key: "o" } : t === "input" ? { key: "i" } : { key: "t" }
|
|
}
|
|
>
|
|
{t === "overview" ? "Overview" : t === "input" ? "Input" : "Template"}
|
|
</TabButton>
|
|
))}
|
|
</TabContainer>
|
|
</div>
|
|
|
|
<div className="scrollbar-gutter-stable min-h-0 flex-1 overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control">
|
|
{tab === "overview" && (
|
|
<div className="flex flex-col px-3">
|
|
{startTime && (
|
|
<SpanHorizontalTimeline startTime={startTime} duration={duration ?? null} />
|
|
)}
|
|
<div className="flex flex-col gap-1 py-2.5">
|
|
<div className="flex flex-col text-xs @container">
|
|
<MetricRow
|
|
label="Prompt"
|
|
value={
|
|
promptPath ? (
|
|
<TextLink to={promptPath}>{promptData.slug}</TextLink>
|
|
) : (
|
|
promptData.slug
|
|
)
|
|
}
|
|
/>
|
|
<MetricRow label="Version" value={`v${promptData.version}`} />
|
|
{labels.length > 0 && <MetricRow label="Labels" value={labels.join(", ")} />}
|
|
{promptData.model && <MetricRow label="Model" value={promptData.model} />}
|
|
</div>
|
|
</div>
|
|
|
|
{promptData.text && (
|
|
<div className="flex flex-col gap-1.5 py-2.5">
|
|
<Header3>Resolved content</Header3>
|
|
<div className="rounded-md border border-grid-bright bg-background-hover/50 px-3.5 py-2">
|
|
<div className="font-sans text-sm font-normal text-text-dimmed streamdown-container">
|
|
<Suspense
|
|
fallback={
|
|
<span className="whitespace-pre-wrap">
|
|
{promptData.text.length > 300
|
|
? promptData.text.slice(0, 300) + "..."
|
|
: promptData.text}
|
|
</span>
|
|
}
|
|
>
|
|
<StreamdownRenderer>{promptData.text}</StreamdownRenderer>
|
|
</Suspense>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{tab === "input" && hasInput && (
|
|
<div className="px-3 py-2.5">
|
|
<CodeBlock
|
|
code={tryPrettyJson(promptData.input!)}
|
|
maxLines={30}
|
|
showLineNumbers={false}
|
|
showCopyButton
|
|
language="json"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{tab === "template" && hasTemplate && (
|
|
<div className="px-3 py-2.5">
|
|
<div className="rounded-md border border-grid-bright bg-background-hover/50 px-3.5 py-2">
|
|
<div className="font-sans text-sm font-normal text-text-dimmed streamdown-container">
|
|
<Suspense
|
|
fallback={<span className="whitespace-pre-wrap">{promptData.template!}</span>}
|
|
>
|
|
<StreamdownRenderer>{promptData.template!}</StreamdownRenderer>
|
|
</Suspense>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|