Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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]);

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