import type { IssueDocument } from "@paperclipai/shared"; import { Lightbulb, Maximize2 } from "lucide-react"; import { cn } from "@/lib/utils"; import type { TaskChatProviderActivityItem } from "./task-chat-model"; const PLAN_PREVIEW_LINE_COUNT = 3; export function planPreviewContent(markdown: string) { const lines = markdown .split("\n") .map((line) => line.trim()) .filter(Boolean); const headingIndex = lines.findIndex((line) => /^#\s+/.test(line)); const title = headingIndex >= 0 ? lines[headingIndex]!.replace(/^#\s+/, "").trim() : "Plan"; const preview = lines .filter((line, index) => index !== headingIndex && !/^(```|~~~|---+$|\*\*\*+$|___+$)/.test(line)) .map((line) => line .replace(/^#{1,6}\s+/, "") .replace(/^[-*+]\s+/, "") .replace(/^\d+[.)]\s+/, "") .replace(/^>\s*/, "") .replace(/`([^`]+)`/g, "$1") .replace(/\*\*([^*]+)\*\*/g, "$1") .replace(/__([^_]+)__/g, "$1") .replace(/\[([^\]]+)]\([^)]*\)/g, "$1") .trim()) .filter(Boolean) .slice(0, PLAN_PREVIEW_LINE_COUNT); return { title, preview }; } export type TaskChatPlanPreviewSource = | { kind: "saved"; document?: IssueDocument | null; revision?: number | null; fallbackTitle?: string | null; } | { kind: "live"; activity: TaskChatProviderActivityItem; }; export interface TaskChatPlanPreviewCardProps { source: TaskChatPlanPreviewSource; href?: string | null; ariaLabel?: string; testId?: string; className?: string; } function livePlanContent(activity: TaskChatProviderActivityItem) { const title = activity.title.trim() && activity.title.trim().toLowerCase() !== "plan" ? activity.title.trim() : "Plan"; const preview = activity.steps .map((step) => step.label.trim()) .filter(Boolean) .slice(0, PLAN_PREVIEW_LINE_COUNT); if (preview.length === 0 && activity.summary?.trim()) preview.push(activity.summary.trim()); return { title, preview }; } function planRevision(source: TaskChatPlanPreviewSource): number | null { if (source.kind === "saved") { return source.revision ?? source.document?.latestRevisionNumber ?? null; } const detail = source.activity.details.find(({ label }) => label === "Revision" || label === "Document Revision", ); if (!detail) return null; const revision = Number.parseInt(detail.value, 10); return Number.isFinite(revision) ? revision : null; } export function TaskChatPlanPreviewCard({ source, href = source.kind === "saved" ? "#document-plan" : null, ariaLabel, testId = "task-chat-plan-preview", className, }: TaskChatPlanPreviewCardProps) { const revision = planRevision(source); const content = source.kind === "saved" && source.document ? planPreviewContent(source.document.body) : source.kind === "saved" ? { title: source.fallbackTitle?.trim() || "Plan", preview: [] as string[] } : livePlanContent(source.activity); const live = source.kind === "live"; const headerDetail = revision == null ? null : `rev ${revision}`; const body = ( <>
{live ? "Waiting for the first plan step…" : "Open the synchronized plan to review it."}
)}