import { join } from 'path' import React, { useCallback, useState } from 'react' import type { ExitState } from '../hooks/useExitOnCtrlCDWithKeybindings.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' import { setClipboard } from '../ink/termio/osc.js' import { Box, Text } from '../ink.js' import { useKeybinding } from '../keybindings/useKeybinding.js' import { getCwd } from '../utils/cwd.js' import { writeFileSync_DEPRECATED } from '../utils/slowOperations.js' import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js' import { Select } from './CustomSelect/select.js' import { Byline } from './design-system/Byline.js' import { Dialog } from './design-system/Dialog.js' import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js' import TextInput from './TextInput.js' type ExportDialogProps = { content: string defaultFilename: string onDone: (result: { success: boolean; message: string }) => void } type ExportOption = 'clipboard' | 'file' export function ExportDialog({ content, defaultFilename, onDone, }: ExportDialogProps): React.ReactNode { const [, setSelectedOption] = useState(null) const [filename, setFilename] = useState(defaultFilename) const [cursorOffset, setCursorOffset] = useState( defaultFilename.length, ) const [showFilenameInput, setShowFilenameInput] = useState(false) const { columns } = useTerminalSize() // Handle going back from filename input to option selection const handleGoBack = useCallback(() => { setShowFilenameInput(false) setSelectedOption(null) }, []) const handleSelectOption = async (value: string): Promise => { if (value === 'clipboard') { // Copy to clipboard immediately const raw = await setClipboard(content) if (raw) process.stdout.write(raw) onDone({ success: true, message: 'Conversation copied to clipboard' }) } else if (value === 'file') { setSelectedOption('file') setShowFilenameInput(true) } } const handleFilenameSubmit = () => { const finalFilename = filename.endsWith('.txt') ? filename : filename.replace(/\.[^.]+$/, '') + '.txt' const filepath = join(getCwd(), finalFilename) try { writeFileSync_DEPRECATED(filepath, content, { encoding: 'utf-8', flush: true, }) onDone({ success: true, message: `Conversation exported to: ${filepath}`, }) } catch (error) { onDone({ success: false, message: `Failed to export conversation: ${error instanceof Error ? error.message : 'Unknown error'}`, }) } } // Dialog calls onCancel when Escape is pressed. If we are in the filename // input sub-screen, go back to the option list instead of closing entirely. const handleCancel = useCallback(() => { if (showFilenameInput) { handleGoBack() } else { onDone({ success: false, message: 'Export cancelled' }) } }, [showFilenameInput, handleGoBack, onDone]) const options = [ { label: 'Copy to clipboard', value: 'clipboard', description: 'Copy the conversation to your system clipboard', }, { label: 'Save to file', value: 'file', description: 'Save the conversation to a file in the current directory', }, ] // Custom input guide that changes based on dialog state function renderInputGuide(exitState: ExitState): React.ReactNode { if (showFilenameInput) { return ( ) } if (exitState.pending) { return Press {exitState.keyName} again to exit } return ( ) } // Use Settings context so 'n' key doesn't cancel (allows typing 'n' in filename input) useKeybinding('confirm:no', handleCancel, { context: 'Settings', isActive: showFilenameInput, }) return ( {!showFilenameInput ? (