diff --git a/components/download-report-button.tsx b/components/download-report-button.tsx index 7485f05c..7c7b5e4f 100644 --- a/components/download-report-button.tsx +++ b/components/download-report-button.tsx @@ -36,8 +36,20 @@ export const DownloadReportButton = () => { setShowTemplate(true) - // Wait for React to render the template - await new Promise(resolve => setTimeout(resolve, 500)) + // Poll until the portal has committed to the DOM, up to 3 seconds + await new Promise((resolve, reject) => { + const start = Date.now() + const check = () => { + if (document.getElementById('report-template')) { + resolve() + } else if (Date.now() - start > 3000) { + reject(new Error('Element with id report-template not found')) + } else { + requestAnimationFrame(check) + } + } + requestAnimationFrame(check) + }) let chatTitle = 'Untitled Chat' if (aiState.messages.length > 0) { diff --git a/components/report-template.tsx b/components/report-template.tsx index 8b23eb5a..9cf1edb4 100644 --- a/components/report-template.tsx +++ b/components/report-template.tsx @@ -3,6 +3,20 @@ import { AIMessage } from '@/lib/types' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' +// AIMessage content can be a string or an array of content parts ({type, text} | {type, image, ...}) +function getContentString(content: AIMessage['content']): string { + if (typeof content === 'string') { + return content + } + if (Array.isArray(content)) { + return content + .filter((part: any) => part.type === 'text') + .map((part: any) => part.text ?? '') + .join('\n') + } + return '' +} + export interface ReportTemplateProps { messages: AIMessage[] drawnFeatures?: Array<{ @@ -50,11 +64,12 @@ export const ReportTemplate: React.FC = ({ {filteredMessages.map((message, index) => { if (message.type === 'input' || message.type === 'input_related') { let content = '' + const rawContent = getContentString(message.content) try { - const json = JSON.parse(message.content as string) - content = message.type === 'input' ? json.input : json.related_query + const json = JSON.parse(rawContent) + content = message.type === 'input' ? (json.input ?? rawContent) : (json.related_query ?? rawContent) } catch (e) { - content = message.content as string + content = rawContent } return (
@@ -67,17 +82,17 @@ export const ReportTemplate: React.FC = ({

AI Response

- {message.content as string} + {getContentString(message.content)}
) } else if (message.type === 'resolution_search_result') { try { - const result = JSON.parse(message.content as string) + const result = JSON.parse(getContentString(message.content)) return (

Analysis Result

- {result.summary && ( +
{result.summary}