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";
}
}
}