@@ -298,7 +298,7 @@ async function renderBlock(block: MermaidBlock) {
298298 }
299299
300300 diagramHost . innerHTML = ''
301- diagramHost . append ( createErrorNotice ( diagramHost . ownerDocument , err ) )
301+ diagramHost . append ( createErrorNotice ( diagramHost . ownerDocument , err , source ) )
302302 container . dataset [ BLOCK_DATA_STATUS ] = 'error'
303303 registry . lastSvg = null
304304 registry . lastRenderId = undefined
@@ -652,7 +652,7 @@ function loadImage(url: string): Promise<HTMLImageElement> {
652652 } )
653653}
654654
655- function createErrorNotice ( doc : Document , err : unknown ) : HTMLElement {
655+ function createErrorNotice ( doc : Document , err : unknown , source : string ) : HTMLElement {
656656 const wrapper = doc . createElement ( 'div' )
657657 wrapper . style . display = 'flex'
658658 wrapper . style . flexDirection = 'column'
@@ -665,7 +665,8 @@ function createErrorNotice(doc: Document, err: unknown): HTMLElement {
665665 title . style . color = isDarkMode ( ) ? '#f87171' : '#b91c1c'
666666
667667 const details = doc . createElement ( 'pre' )
668- details . textContent = err instanceof Error ? err . message : String ( err )
668+ const errorMessage = err instanceof Error ? err . message : String ( err )
669+ details . textContent = errorMessage
669670 details . style . margin = '0'
670671 details . style . whiteSpace = 'pre-wrap'
671672 details . style . fontSize = '0.75rem'
@@ -680,10 +681,124 @@ function createErrorNotice(doc: Document, err: unknown): HTMLElement {
680681 hint . style . fontSize = '0.75rem'
681682 hint . style . color = isDarkMode ( ) ? 'rgba(226, 232, 240, 0.75)' : '#6b7280'
682683
683- wrapper . append ( title , details , hint )
684+ const promptSection = doc . createElement ( 'div' )
685+ promptSection . style . display = 'flex'
686+ promptSection . style . flexDirection = 'column'
687+ promptSection . style . gap = '0.35rem'
688+
689+ const promptButton = createActionButton ( doc , 'Copy fix prompt' )
690+ promptButton . style . alignSelf = 'flex-start'
691+
692+ const promptStatus = doc . createElement ( 'span' )
693+ promptStatus . style . margin = '0'
694+ promptStatus . style . fontSize = '0.7rem'
695+ promptStatus . style . display = 'none'
696+
697+ const promptText = buildMermaidFixPrompt ( errorMessage , source )
698+
699+ const promptPreview = doc . createElement ( 'textarea' )
700+ promptPreview . value = promptText
701+ promptPreview . readOnly = true
702+ promptPreview . spellcheck = false
703+ promptPreview . rows = Math . min ( 12 , Math . max ( 4 , promptText . split ( '\n' ) . length + 1 ) )
704+ promptPreview . style . display = 'none'
705+ promptPreview . style . width = '100%'
706+ promptPreview . style . padding = '0.75rem'
707+ promptPreview . style . borderRadius = '0.5rem'
708+ promptPreview . style . border = getBorderColor ( )
709+ promptPreview . style . background = isDarkMode ( ) ? 'rgba(30, 41, 59, 0.85)' : 'rgba(248, 250, 252, 0.9)'
710+ promptPreview . style . color = getPrimaryTextColor ( )
711+ promptPreview . style . fontSize = '0.75rem'
712+ promptPreview . style . lineHeight = '1.4'
713+ promptPreview . style . fontFamily =
714+ 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
715+ promptPreview . style . resize = 'vertical'
716+
717+ const defaultLabel = promptButton . dataset [ 'coderchartLabel' ] || 'Copy fix prompt'
718+
719+ promptButton . addEventListener ( 'click' , async ( ) => {
720+ promptButton . disabled = true
721+ promptButton . textContent = 'Copying...'
722+ promptPreview . style . display = 'block'
723+
724+ const copied = await copyTextToClipboard ( doc , promptText )
725+
726+ promptStatus . style . display = 'block'
727+ if ( copied ) {
728+ promptStatus . textContent = 'Prompt copied to your clipboard. Paste it back into ChatGPT to request a fix.'
729+ promptStatus . style . color = isDarkMode ( ) ? 'rgba(134, 239, 172, 0.9)' : '#166534'
730+ promptButton . textContent = 'Prompt copied!'
731+ } else {
732+ promptStatus . textContent = 'Clipboard access was blocked. Copy the prompt below manually.'
733+ promptStatus . style . color = isDarkMode ( ) ? 'rgba(248, 113, 113, 0.9)' : '#b91c1c'
734+ promptButton . textContent = 'Copy fix prompt'
735+ promptPreview . focus ( )
736+ promptPreview . select ( )
737+ }
738+
739+ setTimeout ( ( ) => {
740+ promptButton . disabled = false
741+ promptButton . textContent = defaultLabel
742+ } , 2000 )
743+ } )
744+
745+ promptSection . append ( promptButton , promptStatus , promptPreview )
746+
747+ wrapper . append ( title , details , hint , promptSection )
684748 return wrapper
685749}
686750
751+ function buildMermaidFixPrompt ( errorMessage : string , source : string ) : string {
752+ const trimmedSource = source . trim ( )
753+ const formattedSource = trimmedSource ? `\n\n\`\`\`mermaid\n${ trimmedSource } \n\`\`\`` : ''
754+ return (
755+ 'The Mermaid diagram you generated could not be rendered by the CoderChart extension.' +
756+ `\nParse error: ${ errorMessage } ` +
757+ '\nPlease acknowledge that your earlier response included invalid Mermaid syntax and provide a corrected diagram.' +
758+ '\nRespond with only the Mermaid code block using valid Mermaid syntax.' +
759+ ( formattedSource
760+ ? `${ formattedSource } \n`
761+ : '\nIf you need the original code, please restate it before sending the fix.\n' )
762+ )
763+ }
764+
765+ async function copyTextToClipboard ( doc : Document , text : string ) : Promise < boolean > {
766+ if ( navigator . clipboard ?. writeText ) {
767+ try {
768+ await navigator . clipboard . writeText ( text )
769+ return true
770+ } catch ( err ) {
771+ console . warn ( 'Failed to copy prompt via navigator.clipboard' , err )
772+ }
773+ }
774+
775+ if ( ! doc . body ) {
776+ return false
777+ }
778+
779+ const textarea = doc . createElement ( 'textarea' )
780+ textarea . value = text
781+ textarea . setAttribute ( 'readonly' , '' )
782+ textarea . style . position = 'fixed'
783+ textarea . style . opacity = '0'
784+ textarea . style . pointerEvents = 'none'
785+ textarea . style . top = '0'
786+ textarea . style . left = '0'
787+ doc . body . appendChild ( textarea )
788+ textarea . focus ( )
789+ textarea . select ( )
790+
791+ let copied = false
792+ try {
793+ copied = doc . execCommand ( 'copy' )
794+ } catch ( err ) {
795+ console . warn ( 'Failed to copy prompt via execCommand' , err )
796+ }
797+
798+ textarea . remove ( )
799+ return copied
800+ }
801+
687802function clearRenderedBlocks ( ) {
688803 processedBlocks . forEach ( ( entry ) => {
689804 entry . container . remove ( )
0 commit comments