@@ -13,6 +13,8 @@ import Terminal from "lucide-react/dist/esm/icons/terminal";
1313import Users from "lucide-react/dist/esm/icons/users" ;
1414import Wrench from "lucide-react/dist/esm/icons/wrench" ;
1515import X from "lucide-react/dist/esm/icons/x" ;
16+ import { exportMarkdownFile } from "@services/tauri" ;
17+ import { pushErrorToast } from "@services/toasts" ;
1618import type { ConversationItem } from "../../../types" ;
1719import { languageFromPath } from "../../../utils/syntax" ;
1820import { DiffBlock } from "../../git/components/DiffBlock" ;
@@ -273,6 +275,19 @@ function toolIconForSummary(
273275 return Wrench ;
274276}
275277
278+ function buildPlanExportFileName ( itemId : string ) {
279+ const normalized = itemId
280+ . trim ( )
281+ . toLowerCase ( )
282+ . replace ( / [ ^ a - z 0 - 9 _ - ] + / g, "-" )
283+ . replace ( / ^ - + | - + $ / g, "" )
284+ . slice ( 0 , 48 ) ;
285+ if ( ! normalized ) {
286+ return "plan.md" ;
287+ }
288+ return normalized . startsWith ( "plan-" ) ? `${ normalized } .md` : `plan-${ normalized } .md` ;
289+ }
290+
276291export const WorkingIndicator = memo ( function WorkingIndicator ( {
277292 isThinking,
278293 processingStartedAt = null ,
@@ -531,6 +546,7 @@ export const ToolRow = memo(function ToolRow({
531546} : ToolRowProps ) {
532547 const isFileChange = item . toolType === "fileChange" ;
533548 const isCommand = item . toolType === "commandExecution" ;
549+ const isPlan = item . toolType === "plan" ;
534550 const commandText = isCommand
535551 ? item . title . replace ( / ^ C o m m a n d : \s * / i, "" ) . trim ( )
536552 : "" ;
@@ -562,6 +578,7 @@ export const ToolRow = memo(function ToolRow({
562578 typeof item . durationMs === "number" ? item . durationMs : null ;
563579 const isLongRunning = commandDurationMs !== null && commandDurationMs >= 1200 ;
564580 const [ showLiveOutput , setShowLiveOutput ] = useState ( false ) ;
581+ const [ isExportingPlan , setIsExportingPlan ] = useState ( false ) ;
565582
566583 useEffect ( ( ) => {
567584 if ( ! isCommandRunning ) {
@@ -587,6 +604,30 @@ export const ToolRow = memo(function ToolRow({
587604 }
588605 } , [ isCommandRunning , onRequestAutoScroll , showCommandOutput , showLiveOutput ] ) ;
589606
607+ const handlePlanExport = useCallback (
608+ async ( event : MouseEvent < HTMLButtonElement > ) => {
609+ event . preventDefault ( ) ;
610+ event . stopPropagation ( ) ;
611+ const output = ( summary . output ?? "" ) . trim ( ) ;
612+ if ( ! output ) {
613+ return ;
614+ }
615+ setIsExportingPlan ( true ) ;
616+ try {
617+ await exportMarkdownFile ( output , buildPlanExportFileName ( item . id ) ) ;
618+ } catch ( error ) {
619+ const message = error instanceof Error ? error . message : "Unable to export plan." ;
620+ pushErrorToast ( {
621+ title : "Plan export failed" ,
622+ message,
623+ } ) ;
624+ } finally {
625+ setIsExportingPlan ( false ) ;
626+ }
627+ } ,
628+ [ item . id , summary . output ] ,
629+ ) ;
630+
590631 return (
591632 < div className = { `tool-inline ${ isExpanded ? "tool-inline-expanded" : "" } ` } >
592633 < button
@@ -688,6 +729,18 @@ export const ToolRow = memo(function ToolRow({
688729 onOpenThreadLink = { onOpenThreadLink }
689730 />
690731 ) }
732+ { showToolOutput && isPlan && ( summary . output ?? "" ) . trim ( ) && (
733+ < div className = "tool-inline-actions" >
734+ < button
735+ type = "button"
736+ className = "ghost tool-inline-action"
737+ onClick = { handlePlanExport }
738+ disabled = { isExportingPlan }
739+ >
740+ { isExportingPlan ? "Exporting..." : "Export .md" }
741+ </ button >
742+ </ div >
743+ ) }
691744 </ div >
692745 </ div >
693746 ) ;
0 commit comments