|
1 | 1 | <script setup lang="ts">
|
| 2 | + import printJS from 'print-js' |
2 | 3 | import { marked } from 'marked'
|
3 | 4 | import { writeFinalReport } from '~/lib/deep-research'
|
4 |
| - import jsPDF from 'jspdf' |
5 | 5 | import {
|
6 | 6 | feedbackInjectionKey,
|
7 | 7 | formInjectionKey,
|
|
33 | 33 | loadingExportPdf.value ||
|
34 | 34 | loadingExportMarkdown.value,
|
35 | 35 | )
|
36 |
| - let pdf: jsPDF | undefined |
37 | 36 |
|
38 | 37 | async function generateReport() {
|
39 | 38 | loading.value = true
|
|
55 | 54 | } else if (chunk.type === 'text-delta') {
|
56 | 55 | reportContent.value += chunk.textDelta
|
57 | 56 | } else if (chunk.type === 'error') {
|
58 |
| - error.value = t('researchReport.error', [ |
| 57 | + error.value = t('researchReport.generateFailed', [ |
59 | 58 | chunk.error instanceof Error
|
60 | 59 | ? chunk.error.message
|
61 | 60 | : String(chunk.error),
|
|
67 | 66 | )}\n\n${visitedUrls.map((url) => `- ${url}`).join('\n')}`
|
68 | 67 | } catch (e: any) {
|
69 | 68 | console.error(`Generate report failed`, e)
|
70 |
| - error.value = t('researchReport.error', [e.message]) |
| 69 | + error.value = t('researchReport.generateFailed', [e.message]) |
71 | 70 | } finally {
|
72 | 71 | loading.value = false
|
73 | 72 | }
|
74 | 73 | }
|
75 | 74 |
|
76 | 75 | async function exportToPdf() {
|
77 |
| - const element = document.getElementById('report-content') |
78 |
| - if (!element) return |
79 |
| -
|
80 |
| - loadingExportPdf.value = true |
81 |
| -
|
82 |
| - try { |
83 |
| - // 创建 PDF 实例 |
84 |
| - if (!pdf) { |
85 |
| - pdf = new jsPDF({ |
86 |
| - orientation: 'portrait', |
87 |
| - unit: 'mm', |
88 |
| - format: 'a4', |
89 |
| - }) |
90 |
| - } |
91 |
| -
|
92 |
| - // Load Chinese font |
93 |
| - if (locale.value === 'zh') { |
94 |
| - try { |
95 |
| - if (!pdf.getFontList().SourceHanSans?.length) { |
96 |
| - toast.add({ |
97 |
| - title: t('researchReport.downloadingFonts'), |
98 |
| - duration: 5000, |
99 |
| - color: 'info', |
100 |
| - }) |
101 |
| - // Wait for 100ms to avoid toast being blocked by PDF generation |
102 |
| - await new Promise((resolve) => setTimeout(resolve, 100)) |
103 |
| - const fontUrl = '/fonts/SourceHanSansCN-VF.ttf' |
104 |
| - pdf.addFont(fontUrl, 'SourceHanSans', 'normal') |
105 |
| - pdf.setFont('SourceHanSans') |
106 |
| - } |
107 |
| - } catch (e: any) { |
108 |
| - toast.add({ |
109 |
| - title: t('researchReport.downloadFontFailed'), |
110 |
| - description: e.message, |
111 |
| - duration: 8000, |
112 |
| - color: 'error', |
113 |
| - }) |
114 |
| - console.warn( |
115 |
| - 'Failed to load Chinese font, fallback to default font:', |
116 |
| - e, |
117 |
| - ) |
118 |
| - } |
119 |
| - } |
120 |
| -
|
121 |
| - // 设置字体大小和行高 |
122 |
| - const fontSize = 10.5 |
123 |
| - const lineHeight = 1.5 |
124 |
| - pdf.setFontSize(fontSize) |
125 |
| -
|
126 |
| - // 设置页面边距(单位:mm) |
127 |
| - const margin = { |
128 |
| - top: 20, |
129 |
| - right: 20, |
130 |
| - bottom: 20, |
131 |
| - left: 20, |
132 |
| - } |
133 |
| -
|
134 |
| - // 获取纯文本内容 |
135 |
| - const content = element.innerText |
136 |
| -
|
137 |
| - // 计算可用宽度(mm) |
138 |
| - const pageWidth = pdf.internal.pageSize.getWidth() |
139 |
| - const maxWidth = pageWidth - margin.left - margin.right |
140 |
| -
|
141 |
| - // 分割文本为行 |
142 |
| - const lines = pdf.splitTextToSize(content, maxWidth) |
143 |
| -
|
144 |
| - // 计算当前位置 |
145 |
| - let y = margin.top |
146 |
| -
|
147 |
| - // 逐行添加文本 |
148 |
| - for (const line of lines) { |
149 |
| - // 检查是否需要新页 |
150 |
| - if (y > pdf.internal.pageSize.getHeight() - margin.bottom) { |
151 |
| - pdf.addPage() |
152 |
| - y = margin.top |
153 |
| - } |
154 |
| -
|
155 |
| - // 添加文本 |
156 |
| - pdf.text(line, margin.left, y) |
157 |
| - y += fontSize * lineHeight |
158 |
| - } |
159 |
| -
|
160 |
| - pdf.save('research-report.pdf') |
161 |
| - } catch (error) { |
162 |
| - console.error('Export to PDF failed:', error) |
163 |
| - } finally { |
| 76 | + // Change the title back |
| 77 | + const cleanup = () => { |
| 78 | + useHead({ |
| 79 | + title: 'Deep Research Web UI', |
| 80 | + }) |
164 | 81 | loadingExportPdf.value = false
|
165 | 82 | }
|
| 83 | + loadingExportPdf.value = true |
| 84 | + // Temporarily change the document title, which will be used as the filename |
| 85 | + useHead({ |
| 86 | + title: `Deep Research Report - ${form.value.query ?? 'Untitled'}`, |
| 87 | + }) |
| 88 | + // Wait after title is changed |
| 89 | + await new Promise((r) => setTimeout(r, 100)) |
| 90 | +
|
| 91 | + printJS({ |
| 92 | + printable: reportHtml.value, |
| 93 | + type: 'raw-html', |
| 94 | + showModal: true, |
| 95 | + onIncompatibleBrowser() { |
| 96 | + toast.add({ |
| 97 | + title: t('researchReport.incompatibleBrowser'), |
| 98 | + description: t('researchReport.incompatibleBrowserDescription'), |
| 99 | + duration: 10_000, |
| 100 | + }) |
| 101 | + cleanup() |
| 102 | + }, |
| 103 | + onError(error, xmlHttpRequest) { |
| 104 | + console.error(`[Export PDF] failed:`, error, xmlHttpRequest) |
| 105 | + toast.add({ |
| 106 | + title: t('researchReport.exportFailed'), |
| 107 | + description: error instanceof Error ? error.message : String(error), |
| 108 | + duration: 10_000, |
| 109 | + }) |
| 110 | + cleanup() |
| 111 | + }, |
| 112 | + onPrintDialogClose() { |
| 113 | + cleanup() |
| 114 | + }, |
| 115 | + }) |
| 116 | + return |
166 | 117 | }
|
167 | 118 |
|
168 | 119 | async function exportToMarkdown() {
|
|
210 | 161 | </div>
|
211 | 162 | </template>
|
212 | 163 |
|
213 |
| - <div v-if="error" class="text-red-500">{{ error }}</div> |
| 164 | + <UAlert |
| 165 | + v-if="error" |
| 166 | + :title="$t('researchReport.exportFailed')" |
| 167 | + :description="error" |
| 168 | + color="error" |
| 169 | + variant="soft" |
| 170 | + /> |
214 | 171 |
|
215 | 172 | <div class="flex mb-4 justify-end">
|
216 | 173 | <UButton
|
|
246 | 203 |
|
247 | 204 | <div
|
248 | 205 | v-if="reportContent"
|
249 |
| - id="report-content" |
250 | 206 | class="prose prose-sm max-w-none break-words p-6 bg-gray-50 dark:bg-gray-800 dark:prose-invert dark:text-white rounded-lg shadow"
|
251 | 207 | v-html="reportHtml"
|
252 | 208 | />
|
|
0 commit comments