"use client"; import { Position, type Node, type NodeProps } from "@xyflow/react"; import { useTranslations } from "next-intl"; import BaseNodeWrapper from "./base-node-wrapper"; import CanvasHandle from "@/components/canvas/canvas-handle"; type AgentOutputNodeData = { isSkeleton?: boolean; stepId?: string; stepIndex?: number; stepTotal?: number; title?: string; channel?: string; artifactType?: string; previewText?: string; sections?: Array<{ id?: string; label?: string; content?: string; }>; metadata?: Record; metadataLabels?: Record; qualityChecks?: string[]; outputType?: string; body?: string; _status?: string; _statusMessage?: string; }; type AgentOutputNodeType = Node; function tryFormatJsonBody(body: string): string | null { const trimmed = body.trim(); if (!trimmed) { return null; } const looksLikeJsonObject = trimmed.startsWith("{") && trimmed.endsWith("}"); const looksLikeJsonArray = trimmed.startsWith("[") && trimmed.endsWith("]"); if (!looksLikeJsonObject && !looksLikeJsonArray) { return null; } try { const parsed = JSON.parse(trimmed) as unknown; return JSON.stringify(parsed, null, 2); } catch { return null; } } function normalizeSections(raw: AgentOutputNodeData["sections"]) { if (!Array.isArray(raw)) { return [] as Array<{ id: string; label: string; content: string }>; } const sections: Array<{ id: string; label: string; content: string }> = []; for (const item of raw) { const label = typeof item?.label === "string" ? item.label.trim() : ""; const content = typeof item?.content === "string" ? item.content.trim() : ""; if (label === "" || content === "") { continue; } const id = typeof item.id === "string" && item.id.trim() !== "" ? item.id.trim() : label; sections.push({ id, label, content }); } return sections; } function normalizeMetadata(raw: AgentOutputNodeData["metadata"]) { if (!raw || typeof raw !== "object" || Array.isArray(raw)) { return [] as Array<[string, string]>; } const entries: Array<[string, string]> = []; for (const [rawKey, rawValue] of Object.entries(raw)) { const key = rawKey.trim(); if (key === "") { continue; } if (typeof rawValue === "string") { const value = rawValue.trim(); if (value !== "") { entries.push([key, value]); } continue; } if (Array.isArray(rawValue)) { const values = rawValue .filter((value): value is string => typeof value === "string") .map((value) => value.trim()) .filter((value) => value !== ""); if (values.length > 0) { entries.push([key, values.join(", ")]); } } } return entries; } function resolveMetadataLabel( key: string, rawLabels: AgentOutputNodeData["metadataLabels"], ): string { if (!rawLabels || typeof rawLabels !== "object" || Array.isArray(rawLabels)) { return key; } const candidate = rawLabels[key]; return typeof candidate === "string" && candidate.trim() !== "" ? candidate.trim() : key; } function normalizeQualityChecks(raw: AgentOutputNodeData["qualityChecks"]): string[] { if (!Array.isArray(raw)) { return []; } return raw .filter((value): value is string => typeof value === "string") .map((value) => value.trim()) .filter((value) => value !== ""); } function normalizeSectionToken(value: string): string { return value.toLowerCase().replace(/[^a-z0-9]+/g, ""); } function partitionSections( sections: Array<{ id: string; label: string; content: string }>, artifactType: string, ) { const artifactToken = normalizeSectionToken(artifactType); const priorityTokens = artifactToken === "socialcaptionpack" ? ["caption", "hashtags", "cta"] : []; const isSecondaryNote = (label: string) => { const token = normalizeSectionToken(label); return token.includes("formatnote") || token.includes("assumption"); }; const primaryWithIndex: Array<{ section: (typeof sections)[number]; index: number }> = []; const secondary: Array<{ id: string; label: string; content: string }> = []; sections.forEach((section, index) => { if (isSecondaryNote(section.label)) { secondary.push(section); return; } primaryWithIndex.push({ section, index }); }); if (priorityTokens.length === 0) { return { primary: primaryWithIndex.map((entry) => entry.section), secondary, }; } const priorityIndexByToken = new Map(priorityTokens.map((token, index) => [token, index])); const primary = [...primaryWithIndex] .sort((left, right) => { const leftToken = normalizeSectionToken(left.section.label); const rightToken = normalizeSectionToken(right.section.label); const leftPriority = priorityIndexByToken.get(leftToken); const rightPriority = priorityIndexByToken.get(rightToken); if (leftPriority !== undefined && rightPriority !== undefined) { return leftPriority - rightPriority; } if (leftPriority !== undefined) { return -1; } if (rightPriority !== undefined) { return 1; } return left.index - right.index; }) .map((entry) => entry.section); return { primary, secondary, }; } export default function AgentOutputNode({ id, data, selected }: NodeProps) { const t = useTranslations("agentOutputNode"); const nodeData = data as AgentOutputNodeData; const isSkeleton = nodeData.isSkeleton === true; const hasStepCounter = typeof nodeData.stepIndex === "number" && Number.isFinite(nodeData.stepIndex) && typeof nodeData.stepTotal === "number" && Number.isFinite(nodeData.stepTotal) && nodeData.stepTotal > 0; const safeStepIndex = typeof nodeData.stepIndex === "number" && Number.isFinite(nodeData.stepIndex) ? Math.max(0, Math.floor(nodeData.stepIndex)) : 0; const safeStepTotal = typeof nodeData.stepTotal === "number" && Number.isFinite(nodeData.stepTotal) ? Math.max(1, Math.floor(nodeData.stepTotal)) : 1; const stepCounter = hasStepCounter ? `${safeStepIndex + 1}/${safeStepTotal}` : null; const resolvedTitle = nodeData.title ?? (isSkeleton ? t("plannedOutputDefaultTitle") : t("defaultTitle")); const body = nodeData.body ?? ""; const artifactType = nodeData.artifactType ?? nodeData.outputType ?? ""; const sections = normalizeSections(nodeData.sections); const { primary: primarySections, secondary: secondarySections } = partitionSections( sections, artifactType, ); const metadataEntries = normalizeMetadata(nodeData.metadata); const metadataLabels = nodeData.metadataLabels; const qualityChecks = normalizeQualityChecks(nodeData.qualityChecks); const previewText = typeof nodeData.previewText === "string" && nodeData.previewText.trim() !== "" ? nodeData.previewText.trim() : primarySections[0]?.content ?? sections[0]?.content ?? ""; const hasStructuredOutput = sections.length > 0 || metadataEntries.length > 0 || qualityChecks.length > 0 || previewText !== ""; const hasMetaValues = (typeof nodeData.channel === "string" && nodeData.channel.trim() !== "") || artifactType.trim() !== ""; const hasDetailsContent = secondarySections.length > 0 || metadataEntries.length > 0 || qualityChecks.length > 0 || hasMetaValues; const formattedJsonBody = isSkeleton ? null : tryFormatJsonBody(body); return (

{resolvedTitle}

{isSkeleton ? ( {t("skeletonBadge")} ) : null}
{isSkeleton ? (

{t("plannedOutputLabel")} {stepCounter ? ` - ${stepCounter}` : ""} {nodeData.stepId ? ` - ${nodeData.stepId}` : ""}

) : null}
{isSkeleton ? (

{t("bodyLabel")}

{t("plannedContent")}

) : hasStructuredOutput ? ( <> {previewText !== "" ? (

{t("previewLabel")}

{previewText}

) : null} {primarySections.length > 0 ? (

{t("sectionsLabel")}

{primarySections.map((section) => (

{section.label}

{section.content}

))}
) : null} {hasDetailsContent ? (
{t("detailsLabel")}
{hasMetaValues ? (

{t("channelLabel")}

{nodeData.channel ?? t("emptyValue")}

{t("artifactTypeLabel")}

{artifactType || t("emptyValue")}

) : null} {secondarySections.length > 0 ? (

{t("sectionsLabel")}

{secondarySections.map((section) => (

{section.label}

{section.content}

))}
) : null} {metadataEntries.length > 0 ? (

{t("metadataLabel")}

{metadataEntries.map(([key, value]) => (

{resolveMetadataLabel(key, metadataLabels)}: {value}

))}
) : null} {qualityChecks.length > 0 ? (

{t("qualityChecksLabel")}

{qualityChecks.map((qualityCheck) => ( {qualityCheck} ))}
) : null}
) : null} ) : formattedJsonBody ? (

{t("bodyLabel")}

              {formattedJsonBody}
            
) : (

{t("bodyLabel")}

{body}

)}
); }