Skip to content

Commit d7efcb8

Browse files
committed
feat: prompt management with service layer, dashboard UI, and AI span integration
1 parent 1cfc296 commit d7efcb8

69 files changed

Lines changed: 6106 additions & 243 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/webapp/app/components/BlankStatePanels.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import {
44
BookOpenIcon,
55
ChatBubbleLeftRightIcon,
66
ClockIcon,
7+
DocumentTextIcon,
78
PlusIcon,
89
QuestionMarkCircleIcon,
910
RectangleGroupIcon,
1011
RectangleStackIcon,
1112
ServerStackIcon,
13+
SparklesIcon,
1214
Squares2X2Icon,
1315
} from "@heroicons/react/20/solid";
1416
import { useLocation } from "react-use";
@@ -686,3 +688,55 @@ function DeploymentOnboardingSteps() {
686688
</PackageManagerProvider>
687689
);
688690
}
691+
692+
export function PromptsNone() {
693+
return (
694+
<InfoPanel
695+
title="Define your first prompt"
696+
icon={SparklesIcon}
697+
iconClassName="text-purple-500"
698+
panelClassName="max-w-lg"
699+
accessory={
700+
<LinkButton to={docsPath("prompt-management")} variant="docs/small" LeadingIcon={BookOpenIcon}>
701+
Prompt docs
702+
</LinkButton>
703+
}
704+
>
705+
<Paragraph spacing variant="small">
706+
Managed prompts let you define AI prompts in code with typesafe variables, then edit and
707+
version them from the dashboard without redeploying.
708+
</Paragraph>
709+
<Paragraph spacing variant="small">
710+
Add a prompt to your project using <InlineCode variant="small">prompts.define()</InlineCode>:
711+
</Paragraph>
712+
<div className="rounded border border-grid-dimmed bg-charcoal-900 p-3">
713+
<pre className="text-xs leading-relaxed text-text-dimmed">
714+
<span className="text-purple-400">import</span>
715+
{" { prompts } "}
716+
<span className="text-purple-400">from</span>
717+
{' "@trigger.dev/sdk";\n'}
718+
<span className="text-purple-400">import</span>
719+
{" { z } "}
720+
<span className="text-purple-400">from</span>
721+
{' "zod";\n\n'}
722+
<span className="text-purple-400">export const</span>
723+
{" myPrompt = "}
724+
<span className="text-blue-400">prompts.define</span>
725+
{"({\n"}
726+
{" id: "}
727+
<span className="text-green-400">"my-prompt"</span>
728+
{",\n"}
729+
{" variables: z.object({\n"}
730+
{" name: z.string(),\n"}
731+
{" }),\n"}
732+
{" content: "}
733+
<span className="text-green-400">{"`Hello {{name}}!`"}</span>
734+
{",\n"});</pre>
735+
</div>
736+
<Paragraph variant="small" className="mt-2">
737+
Deploy your project and your prompts will appear here with version history and a live
738+
editor.
739+
</Paragraph>
740+
</InfoPanel>
741+
);
742+
}

apps/webapp/app/components/code/TSQLResultsTable.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -460,10 +460,12 @@ function CellValueWrapper({
460460
value,
461461
column,
462462
prettyFormatting,
463+
row,
463464
}: {
464465
value: unknown;
465466
column: OutputColumnMetadata;
466467
prettyFormatting: boolean;
468+
row?: Record<string, unknown>;
467469
}) {
468470
const [hovered, setHovered] = useState(false);
469471

@@ -478,6 +480,7 @@ function CellValueWrapper({
478480
column={column}
479481
prettyFormatting={prettyFormatting}
480482
hovered={hovered}
483+
row={row}
481484
/>
482485
</span>
483486
);
@@ -491,11 +494,13 @@ function CellValue({
491494
column,
492495
prettyFormatting = true,
493496
hovered = false,
497+
row,
494498
}: {
495499
value: unknown;
496500
column: OutputColumnMetadata;
497501
prettyFormatting?: boolean;
498502
hovered?: boolean;
503+
row?: Record<string, unknown>;
499504
}) {
500505
// Plain text mode - render everything as monospace text with truncation
501506
if (!prettyFormatting) {
@@ -562,12 +567,20 @@ function CellValue({
562567
switch (column.customRenderType) {
563568
case "runId": {
564569
if (typeof value === "string") {
570+
const spanId = row?.["span_id"];
571+
const runPath = v3RunPathFromFriendlyId(value);
572+
const href = typeof spanId === "string" && spanId
573+
? `${runPath}?span=${spanId}`
574+
: runPath;
575+
const tooltip = typeof spanId === "string" && spanId
576+
? "Jump to span"
577+
: "Jump to run";
565578
return (
566579
<SimpleTooltip
567-
content="Jump to run"
580+
content={tooltip}
568581
disableHoverableContent
569582
hidden={!hovered}
570-
button={<TextLink to={v3RunPathFromFriendlyId(value)}>{value}</TextLink>}
583+
button={<TextLink to={href}>{value}</TextLink>}
571584
/>
572585
);
573586
}
@@ -1010,13 +1023,16 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
10101023
prettyFormatting = true,
10111024
sorting: defaultSorting = [],
10121025
showHeaderOnEmpty = false,
1026+
hiddenColumns,
10131027
}: {
10141028
rows: Record<string, unknown>[];
10151029
columns: OutputColumnMetadata[];
10161030
prettyFormatting?: boolean;
10171031
sorting?: SortingState;
10181032
/** When true, show column headers + "No results" on empty data. When false, show a blank state icon. */
10191033
showHeaderOnEmpty?: boolean;
1034+
/** Column names to hide from display but keep in row data (useful for linking) */
1035+
hiddenColumns?: string[];
10201036
}) {
10211037
const tableContainerRef = useRef<HTMLDivElement>(null);
10221038

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

10311047
// Create TanStack Table column definitions from OutputColumnMetadata
10321048
// Calculate column widths based on content
1049+
const visibleColumns = useMemo(
1050+
() => hiddenColumns?.length ? columns.filter((col) => !hiddenColumns.includes(col.name)) : columns,
1051+
[columns, hiddenColumns]
1052+
);
10331053
const columnDefs = useMemo<ColumnDef<RowData, unknown>[]>(
10341054
() =>
1035-
columns.map((col) => ({
1055+
visibleColumns.map((col) => ({
10361056
id: col.name,
10371057
accessorKey: col.name,
10381058
header: () => col.name,
@@ -1041,6 +1061,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
10411061
value={info.getValue()}
10421062
column={col}
10431063
prettyFormatting={prettyFormatting}
1064+
row={info.row.original}
10441065
/>
10451066
),
10461067
meta: {
@@ -1050,7 +1071,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
10501071
size: calculateColumnWidth(col.name, rows, col),
10511072
filterFn: fuzzyFilter,
10521073
})),
1053-
[columns, rows, prettyFormatting]
1074+
[visibleColumns, rows, prettyFormatting]
10541075
);
10551076

10561077
// Initialize TanStack Table
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import type { ViewUpdate } from "@codemirror/view";
2+
import { EditorView, lineNumbers } from "@codemirror/view";
3+
import { CheckIcon, ClipboardIcon } from "@heroicons/react/20/solid";
4+
import type { ReactCodeMirrorProps, UseCodeMirror } from "@uiw/react-codemirror";
5+
import { useCodeMirror } from "@uiw/react-codemirror";
6+
import { useCallback, useEffect, useRef, useState } from "react";
7+
import { cn } from "~/utils/cn";
8+
import { Button } from "../primitives/Buttons";
9+
import { getEditorSetup } from "./codeMirrorSetup";
10+
import { darkTheme } from "./codeMirrorTheme";
11+
12+
export interface TextEditorProps extends Omit<ReactCodeMirrorProps, "onBlur"> {
13+
defaultValue?: string;
14+
readOnly?: boolean;
15+
onChange?: (value: string) => void;
16+
onUpdate?: (update: ViewUpdate) => void;
17+
showCopyButton?: boolean;
18+
additionalActions?: React.ReactNode;
19+
}
20+
21+
export function TextEditor(opts: TextEditorProps) {
22+
const {
23+
defaultValue = "",
24+
readOnly = false,
25+
onChange,
26+
onUpdate,
27+
autoFocus,
28+
showCopyButton = true,
29+
additionalActions,
30+
} = opts;
31+
32+
// Don't use default line numbers from setup — add our own with proper sizing
33+
const extensions = getEditorSetup(false);
34+
extensions.push(EditorView.lineWrapping);
35+
extensions.push(
36+
lineNumbers({
37+
formatNumber: (n) => String(n),
38+
})
39+
);
40+
extensions.push(
41+
EditorView.theme({
42+
".cm-lineNumbers": {
43+
minWidth: "40px",
44+
},
45+
})
46+
);
47+
48+
const editor = useRef<HTMLDivElement>(null);
49+
const settings: Omit<UseCodeMirror, "onBlur"> = {
50+
...opts,
51+
container: editor.current,
52+
extensions,
53+
editable: !readOnly,
54+
contentEditable: !readOnly,
55+
value: defaultValue,
56+
autoFocus,
57+
theme: darkTheme(),
58+
indentWithTab: false,
59+
basicSetup: false,
60+
onChange,
61+
onUpdate,
62+
};
63+
const { setContainer, view } = useCodeMirror(settings);
64+
const [copied, setCopied] = useState(false);
65+
66+
useEffect(() => {
67+
if (editor.current) {
68+
setContainer(editor.current);
69+
}
70+
}, [setContainer]);
71+
72+
useEffect(() => {
73+
if (view !== undefined) {
74+
if (view.state.doc.toString() === defaultValue) return;
75+
view.dispatch({
76+
changes: { from: 0, to: view.state.doc.length, insert: defaultValue },
77+
});
78+
}
79+
}, [defaultValue, view]);
80+
81+
const copy = useCallback(() => {
82+
if (view === undefined) return;
83+
navigator.clipboard.writeText(view.state.doc.toString());
84+
setCopied(true);
85+
setTimeout(() => setCopied(false), 1500);
86+
}, [view]);
87+
88+
const showToolbar = showCopyButton || additionalActions;
89+
90+
return (
91+
<div
92+
className={cn(
93+
"grid",
94+
showToolbar ? "grid-rows-[2.5rem_1fr]" : "grid-rows-[1fr]",
95+
opts.className
96+
)}
97+
>
98+
{showToolbar && (
99+
<div className="mx-3 flex items-center justify-between gap-2 border-b border-grid-dimmed">
100+
<div className="flex items-center">{additionalActions}</div>
101+
<div className="flex items-center gap-2">
102+
{showCopyButton && (
103+
<Button
104+
type="button"
105+
variant="minimal/small"
106+
TrailingIcon={copied ? CheckIcon : ClipboardIcon}
107+
trailingIconClassName={
108+
copied ? "text-green-500 group-hover:text-green-500" : undefined
109+
}
110+
onClick={(event) => {
111+
event.preventDefault();
112+
event.stopPropagation();
113+
copy();
114+
}}
115+
>
116+
Copy
117+
</Button>
118+
)}
119+
</div>
120+
</div>
121+
)}
122+
<div className="min-h-0 min-w-0 overflow-auto" ref={editor} />
123+
</div>
124+
);
125+
}

0 commit comments

Comments
 (0)