import { ChevronRightIcon } from "@heroicons/react/20/solid"; import type { TaskEventStyle } from "@trigger.dev/core/v3"; import type { TaskEventLevel } from "@trigger.dev/database"; import { Fragment } from "react"; import { cn } from "~/utils/cn"; import { tablerIcons } from "~/utils/tablerIcons"; import tablerSpritePath from "~/components/primitives/tabler-sprite.svg"; type SpanTitleProps = { message: string; isError: boolean; style: TaskEventStyle; level: TaskEventLevel; isPartial: boolean; size: "small" | "large"; hideAccessory?: boolean; overrideDimmed?: boolean; /** * Mark the span as belonging to an AGENT-kind task so the label renders * in the agents colour, matching the agent icon in the tree row. */ isAgentRun?: boolean; }; export function SpanTitle(event: SpanTitleProps) { const textClass = event.isAgentRun ? "text-agents" : eventTextClassName(event); const finalTextClass = event.overrideDimmed && textClass === "text-text-dimmed" ? "text-text-bright" : textClass; // Only dimmed labels brighten on row hover; colored labels (blue/amber/error) // already carry meaning and should keep their hue. const hoverClass = finalTextClass === "text-text-dimmed" ? "group-hover/spannode:text-text-bright" : undefined; return ( {event.message}{" "} {!event.hideAccessory && ( )} ); } function SpanAccessory({ accessory, size, }: { accessory: TaskEventStyle["accessory"]; size: SpanTitleProps["size"]; }) { if (!accessory) { return null; } switch (accessory.style) { case "codepath": { return ( ); } case "pills": { return ( {accessory.items .filter((item) => typeof item.text === "string") .map((item, index) => ( ))} ); } default: { return ( {accessory.items .filter((item) => typeof item.text === "string") .map((item, index) => ( {item.text} ))} ); } } } function SpanPill({ text, icon }: { text: string; icon?: string }) { const hasIcon = icon && tablerIcons.has(icon); return ( {hasIcon && ( )} {text} ); } export function SpanCodePathAccessory({ accessory, className, }: { accessory: NonNullable; className?: string; }) { return ( {accessory.items .filter((item) => typeof item.text === "string") .map((item, index, filtered) => ( {item.text} {index < filtered.length - 1 && ( )} ))} ); } function eventTextClassName(event: Pick) { // Wait/suspended spans keep their icon and label in the same sky tone so // the row reads as a single "suspended" unit. Matches `wait` in RunIcon. if (event.style.icon === "wait") { return "text-sky-500"; } switch (event.level) { case "TRACE": { return textClassNameForVariant(event.style.variant); } case "LOG": case "INFO": case "DEBUG": { return textClassNameForVariant(event.style.variant); } case "WARN": { return "text-amber-400"; } case "ERROR": { return "text-error"; } default: { return textClassNameForVariant(event.style.variant); } } } type RunEvent = { isError: boolean; style: TaskEventStyle; level: TaskEventLevel; isPartial: boolean; isCancelled: boolean; }; export function eventBackgroundClassName(event: RunEvent) { if (event.isError) { return "bg-error"; } if (event.isCancelled) { return "bg-surface-control"; } switch (event.level) { case "TRACE": { return backgroundClassNameForVariant(event.style.variant, event.isPartial); } case "LOG": case "INFO": case "DEBUG": { return backgroundClassNameForVariant(event.style.variant, event.isPartial); } case "WARN": { return "bg-amber-400"; } case "ERROR": { return "bg-error"; } default: { return backgroundClassNameForVariant(event.style.variant, event.isPartial); } } } export function eventBorderClassName(event: RunEvent) { if (event.isError) { return "border-error"; } if (event.isCancelled) { return "border-border-bright"; } switch (event.level) { case "TRACE": { return borderClassNameForVariant(event.style.variant, event.isPartial); } case "LOG": case "INFO": case "DEBUG": { return borderClassNameForVariant(event.style.variant, event.isPartial); } case "WARN": { return "border-amber-400"; } case "ERROR": { return "border-error"; } default: { return borderClassNameForVariant(event.style.variant, event.isPartial); } } } function textClassNameForVariant(variant: TaskEventStyle["variant"]) { switch (variant) { case "primary": { return "text-blue-500"; } default: { return "text-text-dimmed"; } } } function backgroundClassNameForVariant(variant: TaskEventStyle["variant"], isPartial: boolean) { switch (variant) { case "primary": { if (isPartial) { return "bg-blue-500"; } return "bg-success"; } default: { return "bg-surface-control-active"; } } } function borderClassNameForVariant(variant: TaskEventStyle["variant"], isPartial: boolean) { switch (variant) { case "primary": { if (isPartial) { return "border-blue-500"; } return "border-success"; } default: { return "border-border-brightest"; } } }