diff --git a/.changeset/ai-prompt-management.md b/.changeset/ai-prompt-management.md new file mode 100644 index 00000000000..d3250bebda7 --- /dev/null +++ b/.changeset/ai-prompt-management.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/sdk": patch +--- + +Define and manage AI prompts with `prompts.define()`. Create typesafe prompt templates with variables, resolve them at runtime, and manage versions and overrides from the dashboard without redeploying. diff --git a/.server-changes/ai-prompt-management.md b/.server-changes/ai-prompt-management.md new file mode 100644 index 00000000000..624ec391047 --- /dev/null +++ b/.server-changes/ai-prompt-management.md @@ -0,0 +1,30 @@ +--- +area: webapp +type: feature +--- + +AI prompt management dashboard and enhanced span inspectors. + +**Prompt management:** +- Prompts list page with version status, model, override indicators, and 24h usage sparklines +- Prompt detail page with template viewer, variable preview, version history timeline, and override editor +- Create, edit, and remove overrides to change prompt content or model without redeploying +- Promote any code-deployed version to current +- Generations tab with infinite scroll, live polling, and inline span inspector +- Per-prompt metrics: total generations, avg tokens, avg cost, latency, with version-level breakdowns + +**AI span inspectors:** +- Custom inspectors for `ai.generateText`, `ai.streamText`, `ai.generateObject`, `ai.streamObject` parent spans +- `ai.toolCall` inspector showing tool name, call ID, and input arguments +- `ai.embed` inspector showing model, provider, and input text +- Prompt tab on AI spans linking to prompt version with template and input variables +- Compact timestamp and duration header on all AI span inspectors + +**AI metrics dashboard:** +- Operations, Providers, and Prompts filters on the AI Metrics dashboard +- Cost by prompt widget +- "AI" section in the sidebar with Prompts and AI Metrics links + +**Other improvements:** +- Resizable panel sizes now persist across page refreshes +- Fixed `
` inside `

` DOM nesting warnings in span titles and chat messages diff --git a/CLAUDE.md b/CLAUDE.md index 688ef4d8d80..68ae918f5f7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,6 +18,16 @@ pnpm run dev --filter webapp # Run webapp (http://localhost:3030) pnpm run dev --filter trigger.dev --filter "@trigger.dev/*" # Watch CLI and packages ``` +### Verifying Webapp Changes + +**Never run `pnpm run build --filter webapp` to verify changes.** Building proves almost nothing about correctness. Instead, run typecheck from the repo root: + +```bash +pnpm run typecheck --filter webapp # ~1-2 minutes +``` + +Only run typecheck after major changes (new files, significant refactors, schema changes). For small edits, trust the types and let CI catch issues. + ## Testing We use vitest exclusively. **Never mock anything** - use testcontainers instead. diff --git a/apps/webapp/CLAUDE.md b/apps/webapp/CLAUDE.md index d1533e9808a..6f245ae6a9d 100644 --- a/apps/webapp/CLAUDE.md +++ b/apps/webapp/CLAUDE.md @@ -2,6 +2,46 @@ Remix 2.1.0 app serving as the main API, dashboard, and orchestration engine. Uses an Express server (`server.ts`). +## Verifying Changes + +**Never run `pnpm run build --filter webapp` to verify changes.** Building proves almost nothing about correctness. Instead, run typecheck from the repo root: + +```bash +pnpm run typecheck --filter webapp # ~1-2 minutes +``` + +Only run typecheck after major changes (new files, significant refactors, schema changes). For small edits, trust the types and let CI catch issues. + +## Testing Dashboard Changes with Chrome DevTools MCP + +Use the `chrome-devtools` MCP server to visually verify local dashboard changes. The webapp must be running (`pnpm run dev --filter webapp` from repo root). + +### Login + +``` +1. mcp__chrome-devtools__new_page(url: "http://localhost:3030") + → Redirects to /login +2. mcp__chrome-devtools__click the "Continue with Email" link +3. mcp__chrome-devtools__fill the email field with "local@trigger.dev" +4. mcp__chrome-devtools__click "Send a magic link" + → Auto-logs in and redirects to the dashboard (no email verification needed locally) +``` + +### Navigating and Verifying + +- **take_snapshot**: Get an a11y tree of the page (text content, element UIDs for interaction). Prefer this over screenshots for understanding page structure. +- **take_screenshot**: Capture what the page looks like visually. Use to verify styling, layout, and visual changes. +- **navigate_page**: Go to specific URLs, e.g. `http://localhost:3030/orgs/references-bc08/projects/hello-world-SiWs/env/dev/runs` +- **click / fill**: Interact with elements using UIDs from `take_snapshot`. +- **evaluate_script**: Run JS in the browser console for debugging. +- **list_console_messages**: Check for console errors after navigating. + +### Tips + +- Snapshots can be very large on complex pages (200K+ chars). Use `take_screenshot` first to orient, then `take_snapshot` only when you need element UIDs to interact. +- The local seeded user email is `local@trigger.dev`. +- Dashboard URL pattern: `http://localhost:3030/orgs/{orgSlug}/projects/{projectSlug}/env/{envSlug}/{section}` + ## Key File Locations - **Trigger API**: `app/routes/api.v1.tasks.$taskId.trigger.ts` diff --git a/apps/webapp/app/components/BlankStatePanels.tsx b/apps/webapp/app/components/BlankStatePanels.tsx index 7423bd61ac8..111e4efebb8 100644 --- a/apps/webapp/app/components/BlankStatePanels.tsx +++ b/apps/webapp/app/components/BlankStatePanels.tsx @@ -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"; @@ -686,3 +688,55 @@ function DeploymentOnboardingSteps() { ); } + +export function PromptsNone() { + return ( + + Prompt docs + + } + > + + Managed prompts let you define AI prompts in code with typesafe variables, then edit and + version them from the dashboard without redeploying. + + + Add a prompt to your project using prompts.define(): + +

+
+          import
+          {" { prompts } "}
+          from
+          {' "@trigger.dev/sdk";\n'}
+          import
+          {" { z } "}
+          from
+          {' "zod";\n\n'}
+          export const
+          {" myPrompt = "}
+          prompts.define
+          {"({\n"}
+          {"  id: "}
+          "my-prompt"
+          {",\n"}
+          {"  variables: z.object({\n"}
+          {"    name: z.string(),\n"}
+          {"  }),\n"}
+          {"  content: "}
+          {"`Hello {{name}}!`"}
+          {",\n"});
+
+ + Deploy your project and your prompts will appear here with version history and a live + editor. + + + ); +} diff --git a/apps/webapp/app/components/code/TSQLResultsTable.tsx b/apps/webapp/app/components/code/TSQLResultsTable.tsx index b2caf74dac6..73ca07180bf 100644 --- a/apps/webapp/app/components/code/TSQLResultsTable.tsx +++ b/apps/webapp/app/components/code/TSQLResultsTable.tsx @@ -460,10 +460,12 @@ function CellValueWrapper({ value, column, prettyFormatting, + row, }: { value: unknown; column: OutputColumnMetadata; prettyFormatting: boolean; + row?: Record; }) { const [hovered, setHovered] = useState(false); @@ -478,6 +480,7 @@ function CellValueWrapper({ column={column} prettyFormatting={prettyFormatting} hovered={hovered} + row={row} /> ); @@ -491,11 +494,13 @@ function CellValue({ column, prettyFormatting = true, hovered = false, + row, }: { value: unknown; column: OutputColumnMetadata; prettyFormatting?: boolean; hovered?: boolean; + row?: Record; }) { // Plain text mode - render everything as monospace text with truncation if (!prettyFormatting) { @@ -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 (