Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d7efcb8
feat: prompt management with service layer, dashboard UI, and AI span…
ericallam Mar 20, 2026
3c696b7
feat: operations/providers filters, prompt version model display, ove…
ericallam Mar 20, 2026
86beeb9
feat: generations/metrics across all versions by default, add version…
ericallam Mar 20, 2026
db26f63
feat: prompts list redesign with separate columns, bar sparklines, ve…
ericallam Mar 20, 2026
8d5b28f
feat: full-width override banner, cleaner header for prompt detail page
ericallam Mar 21, 2026
1a81db4
feat: add timestamp and duration to AI span inspector header
ericallam Mar 21, 2026
37e3367
feat: custom span inspectors for top-level AI SDK spans (generateText…
ericallam Mar 21, 2026
3935f7e
fix: a11y label on expand button, try-catch request.json, add missing…
ericallam Mar 21, 2026
7a681c2
fix: TypeScript strict mode errors across AI prompts feature
ericallam Mar 21, 2026
2228c9e
coderabbit fixes
ericallam Mar 22, 2026
96e1426
fix: transaction safety, parsePeriodToMs consistency, SQL injection p…
ericallam Mar 22, 2026
a2adc21
fix: parameterize prompt slug in SQL queries, add ownership check to …
ericallam Mar 22, 2026
f78675f
fix: replace SQL string interpolation with parameterized promptVersio…
ericallam Mar 22, 2026
6586bc6
fix: move distinct operations/providers queries to PromptPresenter, p…
ericallam Mar 22, 2026
f267bba
fix: increase generations polling interval from 5s to 10s
ericallam Mar 22, 2026
dc7f1ad
fix: remove unused private methods from PromptService
ericallam Mar 22, 2026
543bf2c
refactor: extract shared AI span helpers, SpanMetricRow, parsePeriodT…
ericallam Mar 22, 2026
251e46f
fix: wrap prompt version label removal + creation in transaction to p…
ericallam Mar 22, 2026
dcdad1e
fixed 2 validateDOMNesting console errors
ericallam Mar 22, 2026
ceb6391
fix: pre-fill override dialog model from current override instead of …
ericallam Mar 22, 2026
3e63357
fix: use $transaction helper instead of prisma.$transaction for Prism…
ericallam Mar 22, 2026
224af6f
chore: add changeset and server-changes for AI prompt management
ericallam Mar 22, 2026
5ba393a
fix: include prompt entity type in isAiInspector to avoid nested scro…
ericallam Mar 22, 2026
8cd61cf
fix: override edit dialog uses override version content instead of se…
ericallam Mar 22, 2026
7533941
fix: add createMultiMethodApiRoute helper, rewrite override API with …
ericallam Mar 22, 2026
4f396c7
feat: add prompt management SDK methods (list, versions, resolve, ove…
ericallam Mar 22, 2026
e583e5b
fix: promptHandle.resolve() always uses API when client is available,…
ericallam Mar 22, 2026
7a5d9d9
feat: add tracing spans to all prompt management SDK methods
ericallam Mar 22, 2026
ad226d4
fix: remove codepath accessory from prompts.list() span
ericallam Mar 22, 2026
5865db8
feat: typesafe prompts.resolve<typeof myPrompt>() with PromptIdentifi…
ericallam Mar 22, 2026
410e397
fix: use != null check in resolveVersion to handle version 0 correctly
ericallam Mar 22, 2026
c5f88fe
fix: use UTC arithmetic for sparkline bucket keys to avoid timezone m…
ericallam Mar 22, 2026
4cf009a
refactor: migrate MCP prompt tools from raw fetchClient to typed API …
ericallam Mar 22, 2026
9084f98
fix: use z.coerce.number() for MCP prompt version inputs to handle st…
ericallam Mar 22, 2026
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
54 changes: 54 additions & 0 deletions apps/webapp/app/components/BlankStatePanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import {
BookOpenIcon,
ChatBubbleLeftRightIcon,
ClockIcon,
DocumentTextIcon,
PlusIcon,
QuestionMarkCircleIcon,
RectangleGroupIcon,
RectangleStackIcon,
ServerStackIcon,
SparklesIcon,
Squares2X2Icon,
} from "@heroicons/react/20/solid";
import { useLocation } from "react-use";
Expand Down Expand Up @@ -686,3 +688,55 @@ function DeploymentOnboardingSteps() {
</PackageManagerProvider>
);
}

export function PromptsNone() {
return (
<InfoPanel
title="Define your first prompt"
icon={SparklesIcon}
iconClassName="text-purple-500"
panelClassName="max-w-lg"
accessory={
<LinkButton to={docsPath("prompt-management")} variant="docs/small" LeadingIcon={BookOpenIcon}>
Prompt docs
</LinkButton>
}
>
<Paragraph spacing variant="small">
Managed prompts let you define AI prompts in code with typesafe variables, then edit and
version them from the dashboard without redeploying.
</Paragraph>
<Paragraph spacing variant="small">
Add a prompt to your project using <InlineCode variant="small">prompts.define()</InlineCode>:
</Paragraph>
<div className="rounded border border-grid-dimmed bg-charcoal-900 p-3">
<pre className="text-xs leading-relaxed text-text-dimmed">
<span className="text-purple-400">import</span>
{" { prompts } "}
<span className="text-purple-400">from</span>
{' "@trigger.dev/sdk";\n'}
<span className="text-purple-400">import</span>
{" { z } "}
<span className="text-purple-400">from</span>
{' "zod";\n\n'}
<span className="text-purple-400">export const</span>
{" myPrompt = "}
<span className="text-blue-400">prompts.define</span>
{"({\n"}
{" id: "}
<span className="text-green-400">"my-prompt"</span>
{",\n"}
{" variables: z.object({\n"}
{" name: z.string(),\n"}
{" }),\n"}
{" content: "}
<span className="text-green-400">{"`Hello {{name}}!`"}</span>
{",\n"});</pre>
</div>
<Paragraph variant="small" className="mt-2">
Deploy your project and your prompts will appear here with version history and a live
editor.
</Paragraph>
</InfoPanel>
);
}
29 changes: 25 additions & 4 deletions apps/webapp/app/components/code/TSQLResultsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -460,10 +460,12 @@ function CellValueWrapper({
value,
column,
prettyFormatting,
row,
}: {
value: unknown;
column: OutputColumnMetadata;
prettyFormatting: boolean;
row?: Record<string, unknown>;
}) {
const [hovered, setHovered] = useState(false);

Expand All @@ -478,6 +480,7 @@ function CellValueWrapper({
column={column}
prettyFormatting={prettyFormatting}
hovered={hovered}
row={row}
/>
</span>
);
Expand All @@ -491,11 +494,13 @@ function CellValue({
column,
prettyFormatting = true,
hovered = false,
row,
}: {
value: unknown;
column: OutputColumnMetadata;
prettyFormatting?: boolean;
hovered?: boolean;
row?: Record<string, unknown>;
}) {
// Plain text mode - render everything as monospace text with truncation
if (!prettyFormatting) {
Expand Down Expand Up @@ -562,12 +567,20 @@ function CellValue({
switch (column.customRenderType) {
case "runId": {
if (typeof value === "string") {
const spanId = row?.["span_id"];
const runPath = v3RunPathFromFriendlyId(value);
const href = typeof spanId === "string" && spanId
? `${runPath}?span=${spanId}`
: runPath;
const tooltip = typeof spanId === "string" && spanId
? "Jump to span"
: "Jump to run";
return (
<SimpleTooltip
content="Jump to run"
content={tooltip}
disableHoverableContent
hidden={!hovered}
button={<TextLink to={v3RunPathFromFriendlyId(value)}>{value}</TextLink>}
button={<TextLink to={href}>{value}</TextLink>}
/>
);
}
Expand Down Expand Up @@ -1010,13 +1023,16 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
prettyFormatting = true,
sorting: defaultSorting = [],
showHeaderOnEmpty = false,
hiddenColumns,
}: {
rows: Record<string, unknown>[];
columns: OutputColumnMetadata[];
prettyFormatting?: boolean;
sorting?: SortingState;
/** When true, show column headers + "No results" on empty data. When false, show a blank state icon. */
showHeaderOnEmpty?: boolean;
/** Column names to hide from display but keep in row data (useful for linking) */
hiddenColumns?: string[];
}) {
const tableContainerRef = useRef<HTMLDivElement>(null);

Expand All @@ -1030,9 +1046,13 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({

// Create TanStack Table column definitions from OutputColumnMetadata
// Calculate column widths based on content
const visibleColumns = useMemo(
() => hiddenColumns?.length ? columns.filter((col) => !hiddenColumns.includes(col.name)) : columns,
[columns, hiddenColumns]
);
const columnDefs = useMemo<ColumnDef<RowData, unknown>[]>(
() =>
columns.map((col) => ({
visibleColumns.map((col) => ({
id: col.name,
accessorKey: col.name,
header: () => col.name,
Expand All @@ -1041,6 +1061,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
value={info.getValue()}
column={col}
prettyFormatting={prettyFormatting}
row={info.row.original}
/>
),
meta: {
Expand All @@ -1050,7 +1071,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
size: calculateColumnWidth(col.name, rows, col),
filterFn: fuzzyFilter,
})),
[columns, rows, prettyFormatting]
[visibleColumns, rows, prettyFormatting]
);

// Initialize TanStack Table
Expand Down
125 changes: 125 additions & 0 deletions apps/webapp/app/components/code/TextEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import type { ViewUpdate } from "@codemirror/view";
import { EditorView, lineNumbers } from "@codemirror/view";
import { CheckIcon, ClipboardIcon } from "@heroicons/react/20/solid";
import type { ReactCodeMirrorProps, UseCodeMirror } from "@uiw/react-codemirror";
import { useCodeMirror } from "@uiw/react-codemirror";
import { useCallback, useEffect, useRef, useState } from "react";
import { cn } from "~/utils/cn";
import { Button } from "../primitives/Buttons";
import { getEditorSetup } from "./codeMirrorSetup";
import { darkTheme } from "./codeMirrorTheme";

export interface TextEditorProps extends Omit<ReactCodeMirrorProps, "onBlur"> {
defaultValue?: string;
readOnly?: boolean;
onChange?: (value: string) => void;
onUpdate?: (update: ViewUpdate) => void;
showCopyButton?: boolean;
additionalActions?: React.ReactNode;
}

export function TextEditor(opts: TextEditorProps) {
const {
defaultValue = "",
readOnly = false,
onChange,
onUpdate,
autoFocus,
showCopyButton = true,
additionalActions,
} = opts;

// Don't use default line numbers from setup — add our own with proper sizing
const extensions = getEditorSetup(false);
extensions.push(EditorView.lineWrapping);
extensions.push(
lineNumbers({
formatNumber: (n) => String(n),
})
);
extensions.push(
EditorView.theme({
".cm-lineNumbers": {
minWidth: "40px",
},
})
);

const editor = useRef<HTMLDivElement>(null);
const settings: Omit<UseCodeMirror, "onBlur"> = {
...opts,
container: editor.current,
extensions,
editable: !readOnly,
contentEditable: !readOnly,
value: defaultValue,
autoFocus,
theme: darkTheme(),
indentWithTab: false,
basicSetup: false,
onChange,
onUpdate,
};
const { setContainer, view } = useCodeMirror(settings);
const [copied, setCopied] = useState(false);

useEffect(() => {
if (editor.current) {
setContainer(editor.current);
}
}, [setContainer]);

useEffect(() => {
if (view !== undefined) {
if (view.state.doc.toString() === defaultValue) return;
view.dispatch({
changes: { from: 0, to: view.state.doc.length, insert: defaultValue },
});
}
}, [defaultValue, view]);

const copy = useCallback(() => {
if (view === undefined) return;
navigator.clipboard.writeText(view.state.doc.toString());
setCopied(true);
setTimeout(() => setCopied(false), 1500);
}, [view]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const showToolbar = showCopyButton || additionalActions;

return (
<div
className={cn(
"grid",
showToolbar ? "grid-rows-[2.5rem_1fr]" : "grid-rows-[1fr]",
opts.className
)}
>
{showToolbar && (
<div className="mx-3 flex items-center justify-between gap-2 border-b border-grid-dimmed">
<div className="flex items-center">{additionalActions}</div>
<div className="flex items-center gap-2">
{showCopyButton && (
<Button
type="button"
variant="minimal/small"
TrailingIcon={copied ? CheckIcon : ClipboardIcon}
trailingIconClassName={
copied ? "text-green-500 group-hover:text-green-500" : undefined
}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
copy();
}}
>
Copy
</Button>
)}
</div>
</div>
)}
<div className="min-h-0 min-w-0 overflow-auto" ref={editor} />
</div>
);
}
Loading
Loading