@@ -68,6 +68,7 @@ import { usePromptRef } from "../../context/prompt"
6868import { Filesystem } from "@/util/filesystem"
6969import { PermissionPrompt } from "./permission"
7070import { DialogExportOptions } from "../../ui/dialog-export-options"
71+ import { formatTranscript } from "../../util/transcript"
7172
7273addDefaultParsers ( parsers . parsers )
7374
@@ -134,6 +135,7 @@ export function Session() {
134135 const [ showTimestamps , setShowTimestamps ] = createSignal ( kv . get ( "timestamps" , "hide" ) === "show" )
135136 const [ usernameVisible , setUsernameVisible ] = createSignal ( kv . get ( "username_visible" , true ) )
136137 const [ showDetails , setShowDetails ] = createSignal ( kv . get ( "tool_details_visibility" , true ) )
138+ const [ showAssistantMetadata , setShowAssistantMetadata ] = createSignal ( kv . get ( "assistant_metadata_visibility" , true ) )
137139 const [ showScrollbar , setShowScrollbar ] = createSignal ( kv . get ( "scrollbar_visible" , false ) )
138140 const [ diffWrapMode , setDiffWrapMode ] = createSignal < "word" | "none" > ( "word" )
139141 const [ animationsEnabled , setAnimationsEnabled ] = createSignal ( kv . get ( "animations_enabled" , true ) )
@@ -712,47 +714,17 @@ export function Session() {
712714 category : "Session" ,
713715 onSelect : async ( dialog ) => {
714716 try {
715- // Format session transcript as markdown
716717 const sessionData = session ( )
717718 const sessionMessages = messages ( )
718-
719- let transcript = `# ${ sessionData . title } \n\n`
720- transcript += `**Session ID:** ${ sessionData . id } \n`
721- transcript += `**Created:** ${ new Date ( sessionData . time . created ) . toLocaleString ( ) } \n`
722- transcript += `**Updated:** ${ new Date ( sessionData . time . updated ) . toLocaleString ( ) } \n\n`
723- transcript += `---\n\n`
724-
725- for ( const msg of sessionMessages ) {
726- const parts = sync . data . part [ msg . id ] ?? [ ]
727- const role = msg . role === "user" ? "User" : "Assistant"
728- transcript += `## ${ role } \n\n`
729-
730- for ( const part of parts ) {
731- if ( part . type === "text" && ! part . synthetic ) {
732- transcript += `${ part . text } \n\n`
733- } else if ( part . type === "reasoning" ) {
734- if ( showThinking ( ) ) {
735- transcript += `_Thinking:_\n\n${ part . text } \n\n`
736- }
737- } else if ( part . type === "tool" ) {
738- transcript += `\`\`\`\nTool: ${ part . tool } \n`
739- if ( showDetails ( ) && part . state . input ) {
740- transcript += `\n**Input:**\n\`\`\`json\n${ JSON . stringify ( part . state . input , null , 2 ) } \n\`\`\``
741- }
742- if ( showDetails ( ) && part . state . status === "completed" && part . state . output ) {
743- transcript += `\n**Output:**\n\`\`\`\n${ part . state . output } \n\`\`\``
744- }
745- if ( showDetails ( ) && part . state . status === "error" && part . state . error ) {
746- transcript += `\n**Error:**\n\`\`\`\n${ part . state . error } \n\`\`\``
747- }
748- transcript += `\n\`\`\`\n\n`
749- }
750- }
751-
752- transcript += `---\n\n`
753- }
754-
755- // Copy to clipboard
719+ const transcript = formatTranscript (
720+ sessionData ,
721+ sessionMessages . map ( ( msg ) => ( { info : msg , parts : sync . data . part [ msg . id ] ?? [ ] } ) ) ,
722+ {
723+ thinking : showThinking ( ) ,
724+ toolDetails : showDetails ( ) ,
725+ assistantMetadata : showAssistantMetadata ( ) ,
726+ } ,
727+ )
756728 await Clipboard . copy ( transcript )
757729 toast . show ( { message : "Session transcript copied to clipboard!" , variant : "success" } )
758730 } catch ( error ) {
@@ -762,75 +734,56 @@ export function Session() {
762734 } ,
763735 } ,
764736 {
765- title : "Export session transcript to file " ,
737+ title : "Export session transcript" ,
766738 value : "session.export" ,
767739 keybind : "session_export" ,
768740 category : "Session" ,
769741 onSelect : async ( dialog ) => {
770742 try {
771- // Format session transcript as markdown
772743 const sessionData = session ( )
773744 const sessionMessages = messages ( )
774745
775746 const defaultFilename = `session-${ sessionData . id . slice ( 0 , 8 ) } .md`
776747
777- const options = await DialogExportOptions . show ( dialog , defaultFilename , showThinking ( ) , showDetails ( ) )
748+ const options = await DialogExportOptions . show (
749+ dialog ,
750+ defaultFilename ,
751+ showThinking ( ) ,
752+ showDetails ( ) ,
753+ showAssistantMetadata ( ) ,
754+ false ,
755+ )
778756
779757 if ( options === null ) return
780758
781- const { filename : customFilename , thinking : includeThinking , toolDetails : includeToolDetails } = options
782-
783- let transcript = `# ${ sessionData . title } \n\n`
784- transcript += `**Session ID:** ${ sessionData . id } \n`
785- transcript += `**Created:** ${ new Date ( sessionData . time . created ) . toLocaleString ( ) } \n`
786- transcript += `**Updated:** ${ new Date ( sessionData . time . updated ) . toLocaleString ( ) } \n\n`
787- transcript += `---\n\n`
788-
789- for ( const msg of sessionMessages ) {
790- const parts = sync . data . part [ msg . id ] ?? [ ]
791- const role = msg . role === "user" ? "User" : "Assistant"
792- transcript += `## ${ role } \n\n`
793-
794- for ( const part of parts ) {
795- if ( part . type === "text" && ! part . synthetic ) {
796- transcript += `${ part . text } \n\n`
797- } else if ( part . type === "reasoning" ) {
798- if ( includeThinking ) {
799- transcript += `_Thinking:_\n\n${ part . text } \n\n`
800- }
801- } else if ( part . type === "tool" ) {
802- transcript += `\`\`\`\nTool: ${ part . tool } \n`
803- if ( includeToolDetails && part . state . input ) {
804- transcript += `\n**Input:**\n\`\`\`json\n${ JSON . stringify ( part . state . input , null , 2 ) } \n\`\`\``
805- }
806- if ( includeToolDetails && part . state . status === "completed" && part . state . output ) {
807- transcript += `\n**Output:**\n\`\`\`\n${ part . state . output } \n\`\`\``
808- }
809- if ( includeToolDetails && part . state . status === "error" && part . state . error ) {
810- transcript += `\n**Error:**\n\`\`\`\n${ part . state . error } \n\`\`\``
811- }
812- transcript += `\n\`\`\`\n\n`
813- }
814- }
759+ const transcript = formatTranscript (
760+ sessionData ,
761+ sessionMessages . map ( ( msg ) => ( { info : msg , parts : sync . data . part [ msg . id ] ?? [ ] } ) ) ,
762+ {
763+ thinking : options . thinking ,
764+ toolDetails : options . toolDetails ,
765+ assistantMetadata : options . assistantMetadata ,
766+ } ,
767+ )
815768
816- transcript += `---\n\n`
817- }
769+ if ( options . openWithoutSaving ) {
770+ // Just open in editor without saving
771+ await Editor . open ( { value : transcript , renderer } )
772+ } else {
773+ const exportDir = process . cwd ( )
774+ const filename = options . filename . trim ( )
775+ const filepath = path . join ( exportDir , filename )
818776
819- // Save to file in current working directory
820- const exportDir = process . cwd ( )
821- const filename = customFilename . trim ( )
822- const filepath = path . join ( exportDir , filename )
777+ await Bun . write ( filepath , transcript )
823778
824- await Bun . write ( filepath , transcript )
779+ // Open with EDITOR if available
780+ const result = await Editor . open ( { value : transcript , renderer } )
781+ if ( result !== undefined ) {
782+ await Bun . write ( filepath , result )
783+ }
825784
826- // Open with EDITOR if available
827- const result = await Editor . open ( { value : transcript , renderer } )
828- if ( result !== undefined ) {
829- // User edited the file, save the changes
830- await Bun . write ( filepath , result )
785+ toast . show ( { message : `Session exported to ${ filename } ` , variant : "success" } )
831786 }
832-
833- toast . show ( { message : `Session exported to ${ filename } ` , variant : "success" } )
834787 } catch ( error ) {
835788 toast . show ( { message : "Failed to export session" , variant : "error" } )
836789 }
0 commit comments