import { memo, useState, useId } from "react"; import { buildCollapsedProposedPlanPreviewMarkdown, buildProposedPlanMarkdownFilename, downloadPlanAsTextFile, normalizePlanMarkdownForExport, proposedPlanTitle, stripDisplayedPlanMarkdown, } from "../../proposedPlan"; import ChatMarkdown from "../ChatMarkdown"; import { EllipsisIcon } from "lucide-react"; import { Button } from "../ui/button"; import { Input } from "../ui/input"; import { Menu, MenuItem, MenuPopup, MenuTrigger } from "../ui/menu"; import { cn } from "~/lib/utils"; import { Badge } from "../ui/badge"; import { Dialog, DialogDescription, DialogFooter, DialogHeader, DialogPanel, DialogPopup, DialogTitle, } from "../ui/dialog"; import { toastManager } from "../ui/toast"; import { readNativeApi } from "~/nativeApi"; export const ProposedPlanCard = memo(function ProposedPlanCard({ planMarkdown, cwd, workspaceRoot, }: { planMarkdown: string; cwd: string | undefined; workspaceRoot: string | undefined; }) { const [expanded, setExpanded] = useState(false); const [isSaveDialogOpen, setIsSaveDialogOpen] = useState(false); const [savePath, setSavePath] = useState(""); const [isSavingToWorkspace, setIsSavingToWorkspace] = useState(false); const savePathInputId = useId(); const title = proposedPlanTitle(planMarkdown) ?? "Proposed plan"; const lineCount = planMarkdown.split("\n").length; const canCollapse = planMarkdown.length > 900 || lineCount > 20; const displayedPlanMarkdown = stripDisplayedPlanMarkdown(planMarkdown); const collapsedPreview = canCollapse ? buildCollapsedProposedPlanPreviewMarkdown(planMarkdown, { maxLines: 10 }) : null; const downloadFilename = buildProposedPlanMarkdownFilename(planMarkdown); const saveContents = normalizePlanMarkdownForExport(planMarkdown); const handleDownload = () => { downloadPlanAsTextFile(downloadFilename, saveContents); }; const openSaveDialog = () => { if (!workspaceRoot) { toastManager.add({ type: "error", title: "Workspace path is unavailable", description: "This thread does not have a workspace path to save into.", }); return; } setSavePath((existing) => (existing.length > 0 ? existing : downloadFilename)); setIsSaveDialogOpen(true); }; const handleSaveToWorkspace = () => { const api = readNativeApi(); const relativePath = savePath.trim(); if (!api || !workspaceRoot) { return; } if (!relativePath) { toastManager.add({ type: "warning", title: "Enter a workspace path", }); return; } setIsSavingToWorkspace(true); void api.projects .writeFile({ cwd: workspaceRoot, relativePath, contents: saveContents, }) .then((result) => { setIsSaveDialogOpen(false); toastManager.add({ type: "success", title: "Plan saved to workspace", description: result.relativePath, }); }) .catch((error) => { toastManager.add({ type: "error", title: "Could not save plan", description: error instanceof Error ? error.message : "An error occurred while saving.", }); }) .then( () => { setIsSavingToWorkspace(false); }, () => { setIsSavingToWorkspace(false); }, ); }; return (
{title}