Drop docx export to fix CRA beta build
docx@9 ESM trips Babel during react-scripts build; keep PDF/CSV/XLSX/TXT.
This commit is contained in:
@@ -309,7 +309,6 @@ const MessageActions = ({
|
||||
{(
|
||||
[
|
||||
['pdf', 'PDF'],
|
||||
['docx', 'Word (.docx)'],
|
||||
['csv', 'CSV'],
|
||||
['xlsx', 'Excel (.xlsx)'],
|
||||
['txt', 'Plain text'],
|
||||
|
||||
@@ -575,7 +575,6 @@ const AsyncDashboardInner = (): JSX.Element => {
|
||||
{(
|
||||
[
|
||||
['pdf', 'PDF'],
|
||||
['docx', 'Word (.docx)'],
|
||||
['csv', 'CSV'],
|
||||
['xlsx', 'Excel (.xlsx)'],
|
||||
['txt', 'Plain text'],
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
type MdBlock,
|
||||
} from './markdownUtils';
|
||||
|
||||
export type ExportFormat = 'pdf' | 'docx' | 'csv' | 'xlsx' | 'txt';
|
||||
export type ExportFormat = 'pdf' | 'csv' | 'xlsx' | 'txt';
|
||||
|
||||
export type ExportTurn = {
|
||||
role: 'user' | 'assistant';
|
||||
@@ -52,7 +52,7 @@ function citationFootnotes(citations?: Citation[]): string {
|
||||
|
||||
export async function exportChat(options: ExportOptions): Promise<void> {
|
||||
const { format, scope, title, turns } = options;
|
||||
const filename = buildExportFilename(title, format === 'docx' ? 'docx' : format);
|
||||
const filename = buildExportFilename(title, format);
|
||||
const exportedAt = new Date().toISOString();
|
||||
|
||||
// Yield so large conversations don't freeze the main thread (#97).
|
||||
@@ -71,9 +71,6 @@ export async function exportChat(options: ExportOptions): Promise<void> {
|
||||
case 'pdf':
|
||||
await exportPdf(title, turns, exportedAt, filename);
|
||||
break;
|
||||
case 'docx':
|
||||
await exportDocx(title, turns, exportedAt, filename);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported export format: ${format}`);
|
||||
}
|
||||
@@ -304,169 +301,6 @@ async function exportPdf(
|
||||
});
|
||||
}
|
||||
|
||||
async function exportDocx(
|
||||
title: string,
|
||||
turns: ExportTurn[],
|
||||
exportedAt: string,
|
||||
filename: string,
|
||||
): Promise<void> {
|
||||
const {
|
||||
Document,
|
||||
Packer,
|
||||
Paragraph,
|
||||
TextRun,
|
||||
HeadingLevel,
|
||||
Table,
|
||||
TableRow,
|
||||
TableCell,
|
||||
WidthType,
|
||||
Header,
|
||||
} = await import('docx');
|
||||
|
||||
const children: InstanceType<typeof Paragraph | typeof Table>[] = [
|
||||
new Paragraph({
|
||||
text: title,
|
||||
heading: HeadingLevel.TITLE,
|
||||
}),
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun({ text: `Exported ${exportedAt}`, italics: true, size: 18, color: '666666' }),
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
for (let i = 0; i < turns.length; i += 1) {
|
||||
if (i > 0 && i % 20 === 0) await yieldToUi();
|
||||
const turn = turns[i];
|
||||
if (turns.length > 1) {
|
||||
children.push(
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun({
|
||||
text: `${roleLabel(turn.role)}${turn.timestamp ? ` · ${formatTs(turn.timestamp)}` : ''}`,
|
||||
bold: true,
|
||||
}),
|
||||
],
|
||||
spacing: { before: 240 },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
for (const block of parseMarkdownBlocks(turn.message)) {
|
||||
switch (block.type) {
|
||||
case 'heading': {
|
||||
const level =
|
||||
block.level === 1
|
||||
? HeadingLevel.HEADING_1
|
||||
: block.level === 2
|
||||
? HeadingLevel.HEADING_2
|
||||
: HeadingLevel.HEADING_3;
|
||||
children.push(new Paragraph({ text: block.text, heading: level }));
|
||||
break;
|
||||
}
|
||||
case 'paragraph':
|
||||
children.push(new Paragraph({ text: block.text }));
|
||||
break;
|
||||
case 'code':
|
||||
children.push(
|
||||
new Paragraph({
|
||||
children: [new TextRun({ text: block.text, font: 'Courier New', size: 18 })],
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case 'list':
|
||||
block.items.forEach((item, idx) => {
|
||||
children.push(
|
||||
new Paragraph({
|
||||
text: block.ordered ? `${idx + 1}. ${item}` : `• ${item}`,
|
||||
}),
|
||||
);
|
||||
});
|
||||
break;
|
||||
case 'table':
|
||||
children.push(
|
||||
new Table({
|
||||
width: { size: 100, type: WidthType.PERCENTAGE },
|
||||
rows: [
|
||||
new TableRow({
|
||||
children: block.headers.map(
|
||||
(h) =>
|
||||
new TableCell({
|
||||
children: [
|
||||
new Paragraph({
|
||||
children: [new TextRun({ text: h, bold: true })],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
),
|
||||
}),
|
||||
...block.rows.map(
|
||||
(row) =>
|
||||
new TableRow({
|
||||
children: block.headers.map(
|
||||
(_, col) =>
|
||||
new TableCell({
|
||||
children: [new Paragraph({ text: row[col] ?? '' })],
|
||||
}),
|
||||
),
|
||||
}),
|
||||
),
|
||||
],
|
||||
}),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (turn.citations?.length) {
|
||||
children.push(
|
||||
new Paragraph({
|
||||
text: 'Sources',
|
||||
heading: HeadingLevel.HEADING_3,
|
||||
}),
|
||||
);
|
||||
turn.citations
|
||||
.slice()
|
||||
.sort((a, b) => a.index - b.index)
|
||||
.forEach((c) => {
|
||||
children.push(
|
||||
new Paragraph({
|
||||
text: `[${c.index}] ${c.title}${c.url ? ` — ${c.url}` : ''}`,
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const doc = new Document({
|
||||
sections: [
|
||||
{
|
||||
headers: {
|
||||
default: new Header({
|
||||
children: [
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun({
|
||||
text: `${title} · ${exportedAt}`,
|
||||
size: 16,
|
||||
color: '888888',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
children,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const blob = await Packer.toBlob(doc);
|
||||
downloadBlob(blob, filename);
|
||||
}
|
||||
|
||||
/** Decide CSV/XLSX matrix strategy — exported for unit tests. */
|
||||
export function selectTabularStrategy(
|
||||
scope: ExportScope,
|
||||
|
||||
Reference in New Issue
Block a user