fix(export): robustly export reasoning and handle errors (#9221)

* fix(export): robustly export reasoning and handle errors

* fix(export): normalize <br> to newline before notion parsing

* feat(i18n): add notion truncation and unify export warn keys

* refactor(export): add typing, state guards, and error logging

* fix(export): preserve existing <br> in reasoning when convert to html

* feat(export): add DOMPurify sanitization for reasoning content

* chore(deps): remove unused @types/dompurify dev dep

* chore(deps): remove dompurify dependency

Remove dompurify from package.json and yarn. The changes
delete dompurify entries and simplify the lockfile resolution so the
project no longer declares dompurify as a direct dependency.

This cleans up unused dependency declarations and prevents installing
dompurify when it is not required.
This commit is contained in:
George·Dong 2025-08-17 00:41:48 +08:00 committed by GitHub
parent 8b5a3f734c
commit 4dad2a593b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 511 additions and 210 deletions

View File

@ -179,7 +179,6 @@
"dexie-react-hooks": "^1.1.7", "dexie-react-hooks": "^1.1.7",
"diff": "^7.0.0", "diff": "^7.0.0",
"docx": "^9.0.2", "docx": "^9.0.2",
"dompurify": "^3.2.6",
"dotenv-cli": "^7.4.2", "dotenv-cli": "^7.4.2",
"electron": "37.2.3", "electron": "37.2.3",
"electron-builder": "26.0.15", "electron-builder": "26.0.15",

View File

@ -840,6 +840,9 @@
"created": "Created", "created": "Created",
"last_updated": "Last Updated", "last_updated": "Last Updated",
"messages": "Messages", "messages": "Messages",
"notion": {
"reasoning_truncated": "Chain of thought cannot be chunked and has been truncated."
},
"user": "User" "user": "User"
}, },
"files": { "files": {
@ -1257,7 +1260,8 @@
}, },
"notion": { "notion": {
"export": "Failed to export to Notion. Please check connection status and configuration according to documentation", "export": "Failed to export to Notion. Please check connection status and configuration according to documentation",
"no_api_key": "Notion ApiKey or Notion DatabaseID is not configured" "no_api_key": "Notion ApiKey or Notion DatabaseID is not configured",
"no_content": "There is nothing to export to Notion."
}, },
"siyuan": { "siyuan": {
"export": "Failed to export to Siyuan Note, please check connection status and configuration according to documentation", "export": "Failed to export to Siyuan Note, please check connection status and configuration according to documentation",
@ -1383,14 +1387,8 @@
} }
}, },
"warn": { "warn": {
"notion": { "export": {
"exporting": "Exporting to Notion, please do not request export repeatedly!" "exporting": "Another export is in progress. Please wait for the previous export to complete and then try again."
},
"siyuan": {
"exporting": "Exporting to Siyuan Note, please do not request export repeatedly!"
},
"yuque": {
"exporting": "Exporting to Yuque, please do not request export repeatedly!"
} }
}, },
"warning": { "warning": {

View File

@ -840,6 +840,9 @@
"created": "作成日", "created": "作成日",
"last_updated": "最終更新日", "last_updated": "最終更新日",
"messages": "メッセージ", "messages": "メッセージ",
"notion": {
"reasoning_truncated": "思考過程がブロック分割できません。切り捨てられています。"
},
"user": "ユーザー" "user": "ユーザー"
}, },
"files": { "files": {
@ -1257,7 +1260,8 @@
}, },
"notion": { "notion": {
"export": "Notionへのエクスポートに失敗しました。接続状態と設定を確認してください", "export": "Notionへのエクスポートに失敗しました。接続状態と設定を確認してください",
"no_api_key": "Notion ApiKey または Notion DatabaseID が設定されていません" "no_api_key": "Notion ApiKey または Notion DatabaseID が設定されていません",
"no_content": "Notionにエクスポートできる内容がありません。"
}, },
"siyuan": { "siyuan": {
"export": "思源ノートのエクスポートに失敗しました。接続状態を確認し、ドキュメントに従って設定を確認してください", "export": "思源ノートのエクスポートに失敗しました。接続状態を確認し、ドキュメントに従って設定を確認してください",
@ -1383,14 +1387,8 @@
} }
}, },
"warn": { "warn": {
"notion": { "export": {
"exporting": "Notionにエクスポート中です。重複してエクスポートしないでください! " "exporting": "他のエクスポートが実行中です。前のエクスポートが完了するまでお待ちください。"
},
"siyuan": {
"exporting": "思源ノートにエクスポート中です。重複してエクスポートしないでください!"
},
"yuque": {
"exporting": "語雀にエクスポート中です。重複してエクスポートしないでください!"
} }
}, },
"warning": { "warning": {

View File

@ -840,6 +840,9 @@
"created": "Создано", "created": "Создано",
"last_updated": "Последнее обновление", "last_updated": "Последнее обновление",
"messages": "Сообщения", "messages": "Сообщения",
"notion": {
"reasoning_truncated": "Цепочка мыслей не может быть разбита на блоки, обрезана"
},
"user": "Пользователь" "user": "Пользователь"
}, },
"files": { "files": {
@ -1257,7 +1260,8 @@
}, },
"notion": { "notion": {
"export": "Ошибка экспорта в Notion, пожалуйста, проверьте состояние подключения и настройки в документации", "export": "Ошибка экспорта в Notion, пожалуйста, проверьте состояние подключения и настройки в документации",
"no_api_key": "Notion ApiKey или Notion DatabaseID не настроен" "no_api_key": "Notion ApiKey или Notion DatabaseID не настроен",
"no_content": "Нет содержимого для экспорта в Notion"
}, },
"siyuan": { "siyuan": {
"export": "Ошибка экспорта в Siyuan, пожалуйста, проверьте состояние подключения и настройки в документации", "export": "Ошибка экспорта в Siyuan, пожалуйста, проверьте состояние подключения и настройки в документации",
@ -1383,14 +1387,8 @@
} }
}, },
"warn": { "warn": {
"notion": { "export": {
"exporting": "Экспортируется в Notion, пожалуйста, не отправляйте повторные запросы!" "exporting": "Выполняется другая экспортация, подождите завершения предыдущей операции экспорта и повторите попытку"
},
"siyuan": {
"exporting": "Экспортируется в Siyuan, пожалуйста, не отправляйте повторные запросы!"
},
"yuque": {
"exporting": "Экспортируется в Yuque, пожалуйста, не отправляйте повторные запросы!"
} }
}, },
"warning": { "warning": {

View File

@ -840,6 +840,9 @@
"created": "创建时间", "created": "创建时间",
"last_updated": "最后更新", "last_updated": "最后更新",
"messages": "消息数", "messages": "消息数",
"notion": {
"reasoning_truncated": "思维链无法分块,已截断"
},
"user": "用户" "user": "用户"
}, },
"files": { "files": {
@ -1257,7 +1260,8 @@
}, },
"notion": { "notion": {
"export": "导出 Notion 错误,请检查连接状态并对照文档检查配置", "export": "导出 Notion 错误,请检查连接状态并对照文档检查配置",
"no_api_key": "未配置 Notion API Key 或 Notion Database ID" "no_api_key": "未配置 Notion API Key 或 Notion Database ID",
"no_content": "无可导出到 Notion 的内容"
}, },
"siyuan": { "siyuan": {
"export": "导出思源笔记失败,请检查连接状态并对照文档检查配置", "export": "导出思源笔记失败,请检查连接状态并对照文档检查配置",
@ -1383,14 +1387,8 @@
} }
}, },
"warn": { "warn": {
"notion": { "export": {
"exporting": "正在导出到 Notion, 请勿重复请求导出!" "exporting": "正在进行其他导出,请等待上一导出完成后重试"
},
"siyuan": {
"exporting": "正在导出到思源笔记,请勿重复请求导出!"
},
"yuque": {
"exporting": "正在导出语雀,请勿重复请求导出!"
} }
}, },
"warning": { "warning": {

View File

@ -840,6 +840,9 @@
"created": "建立時間", "created": "建立時間",
"last_updated": "最後更新", "last_updated": "最後更新",
"messages": "訊息數", "messages": "訊息數",
"notion": {
"reasoning_truncated": "思維鏈無法分塊,已截斷"
},
"user": "使用者" "user": "使用者"
}, },
"files": { "files": {
@ -1257,7 +1260,8 @@
}, },
"notion": { "notion": {
"export": "匯出 Notion 錯誤,請檢查連接狀態並對照文件檢查設定", "export": "匯出 Notion 錯誤,請檢查連接狀態並對照文件檢查設定",
"no_api_key": "未設定 Notion API Key 或 Notion Database ID" "no_api_key": "未設定 Notion API Key 或 Notion Database ID",
"no_content": "沒有可匯出至 Notion 的內容"
}, },
"siyuan": { "siyuan": {
"export": "導出思源筆記失敗,請檢查連接狀態並對照文檔檢查配置", "export": "導出思源筆記失敗,請檢查連接狀態並對照文檔檢查配置",
@ -1383,14 +1387,8 @@
} }
}, },
"warn": { "warn": {
"notion": { "export": {
"exporting": "正在匯出到 Notion請勿重複請求匯出" "exporting": "正在進行其他匯出,請等待上一次匯出完成後再試"
},
"siyuan": {
"exporting": "正在導出到思源筆記,請勿重複請求導出!"
},
"yuque": {
"exporting": "正在導出語雀,請勿重複請求導出!"
} }
}, },
"warning": { "warning": {

View File

@ -648,6 +648,31 @@
}, },
"translate": "Μετάφραση" "translate": "Μετάφραση"
}, },
"code": {
"auto_update_to_latest": "Έλεγχος για ενημερώσεις και εγκατάσταση της τελευταίας έκδοσης",
"bun_required_message": "Για τη λειτουργία του εργαλείου CLI πρέπει να εγκαταστήσετε το περιβάλλον Bun",
"cli_tool": "Εργαλείο CLI",
"cli_tool_placeholder": "Επιλέξτε το CLI εργαλείο που θέλετε να χρησιμοποιήσετε",
"description": "Εκκίνηση γρήγορα πολλών εργαλείων CLI κώδικα, για αύξηση της αποδοτικότητας ανάπτυξης",
"folder_placeholder": "Επιλέξτε κατάλογο εργασίας",
"install_bun": "Εγκατάσταση Bun",
"installing_bun": "Εγκατάσταση...",
"launch": {
"bun_required": "Παρακαλώ εγκαταστήστε πρώτα το περιβάλλον Bun πριν εκκινήσετε το εργαλείο CLI",
"error": "Η εκκίνηση απέτυχε, παρακαλώ δοκιμάστε ξανά",
"label": "Εκκίνηση",
"success": "Επιτυχής εκκίνηση",
"validation_error": "Συμπληρώστε όλα τα υποχρεωτικά πεδία: εργαλείο CLI, μοντέλο και κατάλογος εργασίας"
},
"launching": "Εκκίνηση...",
"model": "μοντέλο",
"model_placeholder": "Επιλέξτε το μοντέλο που θα χρησιμοποιήσετε",
"model_required": "Επιλέξτε μοντέλο",
"select_folder": "Επιλογή φακέλου",
"title": "Εργαλεία κώδικα",
"update_options": "Ενημέρωση επιλογών",
"working_directory": "κατάλογος εργασίας"
},
"code_block": { "code_block": {
"collapse": "συμπεριληφθείς", "collapse": "συμπεριληφθείς",
"copy": { "copy": {
@ -815,6 +840,9 @@
"created": "Ημερομηνία Δημιουργίας", "created": "Ημερομηνία Δημιουργίας",
"last_updated": "Τελευταία ενημέρωση", "last_updated": "Τελευταία ενημέρωση",
"messages": "Αριθμός Μηνυμάτων", "messages": "Αριθμός Μηνυμάτων",
"notion": {
"reasoning_truncated": "Η αλυσίδα σκέψης δεν μπορεί να διαιρεθεί, έχει κοπεί"
},
"user": "Χρήστης" "user": "Χρήστης"
}, },
"files": { "files": {
@ -1232,7 +1260,8 @@
}, },
"notion": { "notion": {
"export": "Σφάλμα στην εξαγωγή του Notion, παρακαλείστε να ελέγξετε τη σύνδεση και τη διαμόρφωση κατά τη διατύπωση του χειρισμού", "export": "Σφάλμα στην εξαγωγή του Notion, παρακαλείστε να ελέγξετε τη σύνδεση και τη διαμόρφωση κατά τη διατύπωση του χειρισμού",
"no_api_key": "Δεν έχετε διαθέσιμο το API Key του Notion ή το ID της βάσης του Notion" "no_api_key": "Δεν έχετε διαθέσιμο το API Key του Notion ή το ID της βάσης του Notion",
"no_content": "Δεν υπάρχει περιεχόμενο για εξαγωγή στο Notion"
}, },
"siyuan": { "siyuan": {
"export": "Η έκθεση σημειώσεων Siyuan απέτυχε, ελέγξτε την κατάσταση σύνδεσης και τις ρυθμίσεις σύμφωνα με τα έγγραφα", "export": "Η έκθεση σημειώσεων Siyuan απέτυχε, ελέγξτε την κατάσταση σύνδεσης και τις ρυθμίσεις σύμφωνα με τα έγγραφα",
@ -1358,14 +1387,8 @@
} }
}, },
"warn": { "warn": {
"notion": { "export": {
"exporting": "Εξαγωγή στο Notion, μην επαναλάβετε την διαδικασία εξαγωγής!" "exporting": "Παρακαλώ περιμένετε την ολοκλήρωση της προηγούμενης εξαγωγής. Εκτελείται άλλη εξαγωγή."
},
"siyuan": {
"exporting": "Γίνεται εξαγωγή στις σημειώσεις Siyuan· μην ξαναζητήσετε την έκθεση!"
},
"yuque": {
"exporting": "Γίνεται έκθεση Yuque· μην ξαναζητήσετε την έκθεση!"
} }
}, },
"warning": { "warning": {
@ -2690,6 +2713,17 @@
"title": "Εκκίνηση", "title": "Εκκίνηση",
"totray": "Εισαγωγή στην συνδρομή κατά την εκκίνηση" "totray": "Εισαγωγή στην συνδρομή κατά την εκκίνηση"
}, },
"math": {
"engine": {
"label": "Μηχανισμός μαθηματικών τύπων",
"none": "κανένα"
},
"single_dollar": {
"label": "ενεργοποίηση $...$",
"tip": "Επεξεργασία μαθηματικών τύπων που περικλείονται σε ένα μόνο σύμβολο δολαρίου $...$, προεπιλογή ενεργοποιημένη."
},
"title": "Ρύθμιση μαθηματικών τύπων"
},
"mcp": { "mcp": {
"actions": "Ενέργειες", "actions": "Ενέργειες",
"active": "Ενεργοποίηση", "active": "Ενεργοποίηση",
@ -2920,10 +2954,6 @@
"title": "Ρυθμίσεις εισαγωγής" "title": "Ρυθμίσεις εισαγωγής"
}, },
"markdown_rendering_input_message": "Markdown Rendering Input Message", "markdown_rendering_input_message": "Markdown Rendering Input Message",
"math_engine": {
"label": "Μηχανική μαθηματικών εξισώσεων",
"none": "Κανένα"
},
"metrics": "Χρόνος πρώτου χαρακτήρα {{time_first_token_millsec}}ms | {{token_speed}} tokens ανά δευτερόλεπτο", "metrics": "Χρόνος πρώτου χαρακτήρα {{time_first_token_millsec}}ms | {{token_speed}} tokens ανά δευτερόλεπτο",
"model": { "model": {
"title": "Ρυθμίσεις μοντέλου" "title": "Ρυθμίσεις μοντέλου"
@ -2935,6 +2965,7 @@
"none": "Χωρίς εμφάνιση" "none": "Χωρίς εμφάνιση"
}, },
"prompt": "Λήμμα προτροπής", "prompt": "Λήμμα προτροπής",
"show_message_outline": "Εμφάνιση πλαισίου μηνύματος",
"title": "Ρυθμίσεις μηνυμάτων", "title": "Ρυθμίσεις μηνυμάτων",
"use_serif_font": "Χρήση μορφής Serif" "use_serif_font": "Χρήση μορφής Serif"
}, },
@ -3561,6 +3592,7 @@
"title": { "title": {
"agents": "Πράκτορες", "agents": "Πράκτορες",
"apps": "Εφαρμογές", "apps": "Εφαρμογές",
"code": "Κώδικας",
"files": "Αρχεία", "files": "Αρχεία",
"home": "Αρχική Σελίδα", "home": "Αρχική Σελίδα",
"knowledge": "Βάση Γνώσης", "knowledge": "Βάση Γνώσης",

View File

@ -648,6 +648,31 @@
}, },
"translate": "Traducir" "translate": "Traducir"
}, },
"code": {
"auto_update_to_latest": "Comprobar actualizaciones e instalar la versión más reciente",
"bun_required_message": "Se requiere instalar el entorno Bun para ejecutar la herramienta de línea de comandos",
"cli_tool": "Herramienta de línea de comandos",
"cli_tool_placeholder": "Seleccione la herramienta de línea de comandos que desea utilizar",
"description": "Inicia rápidamente múltiples herramientas de línea de comandos para código, aumentando la eficiencia del desarrollo",
"folder_placeholder": "Seleccionar directorio de trabajo",
"install_bun": "Instalar Bun",
"installing_bun": "Instalando...",
"launch": {
"bun_required": "Instale el entorno Bun antes de iniciar la herramienta de línea de comandos",
"error": "Error al iniciar, intente nuevamente",
"label": "Iniciar",
"success": "Inicio exitoso",
"validation_error": "Complete all required fields: CLI tool, model, and working directory"
},
"launching": "Iniciando...",
"model": "modelo",
"model_placeholder": "Seleccionar el modelo que se va a utilizar",
"model_required": "Seleccione el modelo",
"select_folder": "Seleccionar carpeta",
"title": "Herramientas de código",
"update_options": "Opciones de actualización",
"working_directory": "directorio de trabajo"
},
"code_block": { "code_block": {
"collapse": "Replegar", "collapse": "Replegar",
"copy": { "copy": {
@ -815,6 +840,9 @@
"created": "Fecha de creación", "created": "Fecha de creación",
"last_updated": "Última actualización", "last_updated": "Última actualización",
"messages": "Mensajes", "messages": "Mensajes",
"notion": {
"reasoning_truncated": "La cadena de pensamiento no se puede dividir en bloques, ha sido truncada"
},
"user": "Usuario" "user": "Usuario"
}, },
"files": { "files": {
@ -1232,7 +1260,8 @@
}, },
"notion": { "notion": {
"export": "Error de exportación de Notion, verifique el estado de conexión y la configuración según la documentación", "export": "Error de exportación de Notion, verifique el estado de conexión y la configuración según la documentación",
"no_api_key": "No se ha configurado la clave API de Notion o la ID de la base de datos de Notion" "no_api_key": "No se ha configurado la clave API de Notion o la ID de la base de datos de Notion",
"no_content": "No hay contenido que exportar a Notion"
}, },
"siyuan": { "siyuan": {
"export": "Error al exportar la nota de Siyuan, verifique el estado de la conexión y revise la configuración según la documentación", "export": "Error al exportar la nota de Siyuan, verifique el estado de la conexión y revise la configuración según la documentación",
@ -1358,14 +1387,8 @@
} }
}, },
"warn": { "warn": {
"notion": { "export": {
"exporting": "Se está exportando a Notion, ¡no solicite nuevamente la exportación!" "exporting": "Realizando otra exportación, espere a que finalice la anterior para intentarlo de nuevo"
},
"siyuan": {
"exporting": "Exportando a Siyuan, ¡no solicite la exportación nuevamente!"
},
"yuque": {
"exporting": "Exportando Yuque, ¡no solicite la exportación nuevamente!"
} }
}, },
"warning": { "warning": {
@ -2690,6 +2713,17 @@
"title": "Inicio", "title": "Inicio",
"totray": "Minimizar a la bandeja al iniciar" "totray": "Minimizar a la bandeja al iniciar"
}, },
"math": {
"engine": {
"label": "Motor de fórmulas matemáticas",
"none": "sin contenido"
},
"single_dollar": {
"label": "habilitar $...$",
"tip": "Renderiza fórmulas matemáticas encerradas entre un único símbolo de dólar $...$, habilitado por defecto."
},
"title": "Configuración de fórmulas matemáticas"
},
"mcp": { "mcp": {
"actions": "Acciones", "actions": "Acciones",
"active": "Activar", "active": "Activar",
@ -2920,10 +2954,6 @@
"title": "Configuración de entrada" "title": "Configuración de entrada"
}, },
"markdown_rendering_input_message": "Renderizar mensajes de entrada en Markdown", "markdown_rendering_input_message": "Renderizar mensajes de entrada en Markdown",
"math_engine": {
"label": "Motor de fórmulas matemáticas",
"none": "Ninguno"
},
"metrics": "Retraso inicial {{time_first_token_millsec}}ms | {{token_speed}} tokens por segundo", "metrics": "Retraso inicial {{time_first_token_millsec}}ms | {{token_speed}} tokens por segundo",
"model": { "model": {
"title": "Configuración del modelo" "title": "Configuración del modelo"
@ -2935,6 +2965,7 @@
"none": "No mostrar" "none": "No mostrar"
}, },
"prompt": "Palabra de indicación", "prompt": "Palabra de indicación",
"show_message_outline": "Mostrar esquema del mensaje",
"title": "Configuración de mensajes", "title": "Configuración de mensajes",
"use_serif_font": "Usar fuente serif" "use_serif_font": "Usar fuente serif"
}, },
@ -3561,6 +3592,7 @@
"title": { "title": {
"agents": "Agentes", "agents": "Agentes",
"apps": "Aplicaciones", "apps": "Aplicaciones",
"code": "Código",
"files": "Archivos", "files": "Archivos",
"home": "Inicio", "home": "Inicio",
"knowledge": "Base de conocimiento", "knowledge": "Base de conocimiento",

View File

@ -648,6 +648,31 @@
}, },
"translate": "Traduire" "translate": "Traduire"
}, },
"code": {
"auto_update_to_latest": "Vérifier les mises à jour et installer la dernière version",
"bun_required_message": "L'exécution de l'outil en ligne de commande nécessite l'installation de l'environnement Bun",
"cli_tool": "Outil CLI",
"cli_tool_placeholder": "Sélectionnez l'outil CLI à utiliser",
"description": "Lancer rapidement plusieurs outils CLI de code pour améliorer l'efficacité du développement",
"folder_placeholder": "Sélectionner le répertoire de travail",
"install_bun": "Installer Bun",
"installing_bun": "Installation en cours...",
"launch": {
"bun_required": "Veuillez d'abord installer l'environnement Bun avant de lancer l'outil en ligne de commande",
"error": "Échec du démarrage, veuillez réessayer",
"label": "Démarrer",
"success": "Démarrage réussi",
"validation_error": "Veuillez remplir tous les champs obligatoires : outil CLI, modèle et répertoire de travail"
},
"launching": "En cours de démarrage...",
"model": "modèle",
"model_placeholder": "Sélectionnez le modèle à utiliser",
"model_required": "Veuillez sélectionner le modèle",
"select_folder": "Sélectionner le dossier",
"title": "Outils de code",
"update_options": "Options de mise à jour",
"working_directory": "répertoire de travail"
},
"code_block": { "code_block": {
"collapse": "Réduire", "collapse": "Réduire",
"copy": { "copy": {
@ -815,6 +840,9 @@
"created": "Date de création", "created": "Date de création",
"last_updated": "Dernière mise à jour", "last_updated": "Dernière mise à jour",
"messages": "Messages", "messages": "Messages",
"notion": {
"reasoning_truncated": "La chaîne de pensée ne peut pas être fractionnée, elle a été tronquée."
},
"user": "Utilisateur" "user": "Utilisateur"
}, },
"files": { "files": {
@ -1232,7 +1260,8 @@
}, },
"notion": { "notion": {
"export": "Erreur lors de l'exportation vers Notion, veuillez vérifier l'état de la connexion et la configuration dans la documentation", "export": "Erreur lors de l'exportation vers Notion, veuillez vérifier l'état de la connexion et la configuration dans la documentation",
"no_api_key": "Aucune clé API Notion ou ID de base de données Notion configurée" "no_api_key": "Aucune clé API Notion ou ID de base de données Notion configurée",
"no_content": "Aucun contenu à exporter vers Notion"
}, },
"siyuan": { "siyuan": {
"export": "Échec de l'exportation de la note Siyuan, veuillez vérifier l'état de la connexion et la configuration indiquée dans le document", "export": "Échec de l'exportation de la note Siyuan, veuillez vérifier l'état de la connexion et la configuration indiquée dans le document",
@ -1358,14 +1387,8 @@
} }
}, },
"warn": { "warn": {
"notion": { "export": {
"exporting": "Exportation en cours vers Notion, veuillez ne pas faire plusieurs demandes d'exportation!" "exporting": "Une autre exportation est en cours, veuillez patienter jusqu'à la fin de l'exportation précédente pour réessayer."
},
"siyuan": {
"exporting": "Exportation vers Siyuan en cours, veuillez ne pas demander à exporter à nouveau !"
},
"yuque": {
"exporting": "Exportation Yuque en cours, veuillez ne pas demander à exporter à nouveau !"
} }
}, },
"warning": { "warning": {
@ -2690,6 +2713,17 @@
"title": "Démarrage", "title": "Démarrage",
"totray": "Minimiser dans la barre d'état système au démarrage" "totray": "Minimiser dans la barre d'état système au démarrage"
}, },
"math": {
"engine": {
"label": "Moteur de formules mathématiques",
"none": "Aucun"
},
"single_dollar": {
"label": "activer $...$",
"tip": "Rendu des formules mathématiques encapsulées par un seul symbole dollar $...$, activé par défaut."
},
"title": "Configuration des formules mathématiques"
},
"mcp": { "mcp": {
"actions": "Actions", "actions": "Actions",
"active": "Activer", "active": "Activer",
@ -2920,10 +2954,6 @@
"title": "Paramètres d'entrée" "title": "Paramètres d'entrée"
}, },
"markdown_rendering_input_message": "Rendu Markdown des messages d'entrée", "markdown_rendering_input_message": "Rendu Markdown des messages d'entrée",
"math_engine": {
"label": "Moteur de formules mathématiques",
"none": "Aucun"
},
"metrics": "Latence initiale {{time_first_token_millsec}}ms | Vitesse de tokenisation {{token_speed}} tokens/s", "metrics": "Latence initiale {{time_first_token_millsec}}ms | Vitesse de tokenisation {{token_speed}} tokens/s",
"model": { "model": {
"title": "Paramètres du modèle" "title": "Paramètres du modèle"
@ -2935,6 +2965,7 @@
"none": "Ne pas afficher" "none": "Ne pas afficher"
}, },
"prompt": "Mot-clé d'affichage", "prompt": "Mot-clé d'affichage",
"show_message_outline": "Afficher le plan du message",
"title": "Paramètres des messages", "title": "Paramètres des messages",
"use_serif_font": "Utiliser une police serif" "use_serif_font": "Utiliser une police serif"
}, },
@ -3561,6 +3592,7 @@
"title": { "title": {
"agents": "Agent intelligent", "agents": "Agent intelligent",
"apps": "Mini-programmes", "apps": "Mini-programmes",
"code": "Code",
"files": "Fichiers", "files": "Fichiers",
"home": "Page d'accueil", "home": "Page d'accueil",
"knowledge": "Base de connaissances", "knowledge": "Base de connaissances",

View File

@ -648,6 +648,31 @@
}, },
"translate": "Traduzir" "translate": "Traduzir"
}, },
"code": {
"auto_update_to_latest": "Verificar atualizações e instalar a versão mais recente",
"bun_required_message": "Executar a ferramenta CLI requer a instalação do ambiente Bun",
"cli_tool": "Ferramenta de linha de comando",
"cli_tool_placeholder": "Selecione a ferramenta de linha de comando a ser utilizada",
"description": "Inicie rapidamente várias ferramentas de linha de comando de código, aumentando a eficiência do desenvolvimento",
"folder_placeholder": "Selecionar diretório de trabalho",
"install_bun": "Instalar o Bun",
"installing_bun": "Instalando...",
"launch": {
"bun_required": "Instale o ambiente Bun antes de iniciar a ferramenta de linha de comando",
"error": "Falha ao iniciar, tente novamente",
"label": "iniciar",
"success": "Início bem-sucedido",
"validation_error": "Preencha todos os campos obrigatórios: ferramenta CLI, modelo e diretório de trabalho"
},
"launching": "Iniciando...",
"model": "modelo",
"model_placeholder": "Selecione o modelo a ser utilizado",
"model_required": "Selecione o modelo",
"select_folder": "Selecionar pasta",
"title": "Ferramenta de código",
"update_options": "Opções de atualização",
"working_directory": "diretório de trabalho"
},
"code_block": { "code_block": {
"collapse": "Recolher", "collapse": "Recolher",
"copy": { "copy": {
@ -815,6 +840,9 @@
"created": "Criado em", "created": "Criado em",
"last_updated": "Última Atualização", "last_updated": "Última Atualização",
"messages": "Mensagens", "messages": "Mensagens",
"notion": {
"reasoning_truncated": "A cadeia de pensamento não pode ser dividida em partes, foi interrompida"
},
"user": "Usuário" "user": "Usuário"
}, },
"files": { "files": {
@ -1232,7 +1260,8 @@
}, },
"notion": { "notion": {
"export": "Erro ao exportar Notion, verifique o status da conexão e a configuração de acordo com a documentação", "export": "Erro ao exportar Notion, verifique o status da conexão e a configuração de acordo com a documentação",
"no_api_key": "API Key ou Notion Database ID não configurados" "no_api_key": "API Key ou Notion Database ID não configurados",
"no_content": "Nenhum conteúdo para exportar para o Notion"
}, },
"siyuan": { "siyuan": {
"export": "Falha ao exportar nota do Siyuan, verifique o estado da conexão e confira a configuração no documento", "export": "Falha ao exportar nota do Siyuan, verifique o estado da conexão e confira a configuração no documento",
@ -1358,14 +1387,8 @@
} }
}, },
"warn": { "warn": {
"notion": { "export": {
"exporting": "Exportando para Notion, não solicite novamente a exportação!" "exporting": "A exportação de outros arquivos está em andamento, aguarde a conclusão da exportação anterior e tente novamente."
},
"siyuan": {
"exporting": "Exportando para o Siyuan, por favor não solicite a exportação novamente!"
},
"yuque": {
"exporting": "Exportando para Yuque, por favor não solicite a exportação novamente!"
} }
}, },
"warning": { "warning": {
@ -2690,6 +2713,17 @@
"title": "Inicialização", "title": "Inicialização",
"totray": "Minimizar para bandeja ao iniciar" "totray": "Minimizar para bandeja ao iniciar"
}, },
"math": {
"engine": {
"label": "Motor de fórmulas matemáticas",
"none": "sem conteúdo"
},
"single_dollar": {
"label": "ativar $...$",
"tip": "Renderiza fórmulas matemáticas delimitadas por um único sinal de dólar $...$, habilitado por padrão."
},
"title": "Configuração de fórmulas matemáticas"
},
"mcp": { "mcp": {
"actions": "Ações", "actions": "Ações",
"active": "Ativar", "active": "Ativar",
@ -2920,10 +2954,6 @@
"title": "Configurações de entrada" "title": "Configurações de entrada"
}, },
"markdown_rendering_input_message": "Renderização de markdown na entrada de mensagens", "markdown_rendering_input_message": "Renderização de markdown na entrada de mensagens",
"math_engine": {
"label": "Motor de fórmulas matemáticas",
"none": "Nenhum"
},
"metrics": "Atraso inicial {{time_first_token_millsec}}ms | Taxa de token por segundo {{token_speed}} tokens", "metrics": "Atraso inicial {{time_first_token_millsec}}ms | Taxa de token por segundo {{token_speed}} tokens",
"model": { "model": {
"title": "Configurações de modelo" "title": "Configurações de modelo"
@ -2935,6 +2965,7 @@
"none": "Não mostrar" "none": "Não mostrar"
}, },
"prompt": "Exibir palavra-chave", "prompt": "Exibir palavra-chave",
"show_message_outline": "Exibir esboço da mensagem",
"title": "Configurações de mensagem", "title": "Configurações de mensagem",
"use_serif_font": "Usar fonte serif" "use_serif_font": "Usar fonte serif"
}, },
@ -3561,6 +3592,7 @@
"title": { "title": {
"agents": "Agentes", "agents": "Agentes",
"apps": "Miniaplicativos", "apps": "Miniaplicativos",
"code": "Código",
"files": "Arquivos", "files": "Arquivos",
"home": "Página Inicial", "home": "Página Inicial",
"knowledge": "Base de Conhecimento", "knowledge": "Base de Conhecimento",

View File

@ -12,10 +12,112 @@ import { convertMathFormula, markdownToPlainText } from '@renderer/utils/markdow
import { getCitationContent, getMainTextContent, getThinkingContent } from '@renderer/utils/messageUtils/find' import { getCitationContent, getMainTextContent, getThinkingContent } from '@renderer/utils/messageUtils/find'
import { markdownToBlocks } from '@tryfabric/martian' import { markdownToBlocks } from '@tryfabric/martian'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import DOMPurify from 'dompurify'
import { appendBlocks } from 'notion-helper' // 引入 notion-helper 的 appendBlocks 函数 import { appendBlocks } from 'notion-helper' // 引入 notion-helper 的 appendBlocks 函数
const logger = loggerService.withContext('Utils:export') const logger = loggerService.withContext('Utils:export')
// 全局的导出状态获取函数
const getExportState = () => store.getState().runtime.export.isExporting
// 全局的导出状态设置函数,使用 dispatch 保障 Redux 状态更新正确
const setExportingState = (isExporting: boolean) => {
store.dispatch(setExportState({ isExporting }))
}
/**
* HTML <br>
*
*
* - br, p, div, span, h1-h6, blockquote
* - strong, b, em, i, u, s, del, mark, small, sup, sub
* - ul, ol, li
* - code, pre, kbd, var, samp
* - table, thead, tbody, tfoot, tr, td, th
*
* @param content
* @returns
*/
const sanitizeReasoningContent = (content: string): string => {
// 先处理换行符转换为 <br>
const contentWithBr = content.replace(/\n/g, '<br>')
// 使用 DOMPurify 清理内容,保留常用的安全标签和属性
const cleanContent = DOMPurify.sanitize(contentWithBr, {
ALLOWED_TAGS: [
// 换行和基础结构
'br',
'p',
'div',
'span',
// 文本格式化
'strong',
'b',
'em',
'i',
'u',
's',
'del',
'mark',
'small',
// 上标下标(数学公式、引用等)
'sup',
'sub',
// 标题
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
// 引用
'blockquote',
// 列表
'ul',
'ol',
'li',
// 代码相关
'code',
'pre',
'kbd',
'var',
'samp',
// 表格AI输出中可能包含表格
'table',
'thead',
'tbody',
'tfoot',
'tr',
'td',
'th',
// 分隔线
'hr'
],
ALLOWED_ATTR: [
// 安全的通用属性
'class',
'title',
'lang',
'dir',
// code 标签的语言属性
'data-language',
// 表格属性
'colspan',
'rowspan',
// 列表属性
'start',
'type'
],
KEEP_CONTENT: true, // 保留被移除标签的文本内容
RETURN_DOM: false,
SANITIZE_DOM: true,
// 允许的协议(预留,虽然目前没有允许链接标签)
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?):|[^a-z]|[a-z+.-]+(?:[^a-z+.-:]|$))/i
})
return cleanContent
}
/** /**
* 使TopicManager确保消息被正确加载 * 使TopicManager确保消息被正确加载
* *
@ -33,7 +135,7 @@ async function fetchTopicMessages(topicId: string): Promise<Message[]> {
* @param {number} [length=80] 80 * @param {number} [length=80] 80
* @returns {string} * @returns {string}
*/ */
export function getTitleFromString(str: string, length: number = 80) { export function getTitleFromString(str: string, length: number = 80): string {
let title = str.trimStart().split('\n')[0] let title = str.trimStart().split('\n')[0]
if (title.includes('。')) { if (title.includes('。')) {
@ -57,7 +159,7 @@ export function getTitleFromString(str: string, length: number = 80) {
return title return title
} }
const getRoleText = (role: string, modelName?: string, providerId?: string) => { const getRoleText = (role: string, modelName?: string, providerId?: string): string => {
const { showModelNameInMarkdown, showModelProviderInMarkdown } = store.getState().settings const { showModelNameInMarkdown, showModelProviderInMarkdown } = store.getState().settings
if (role === 'user') { if (role === 'user') {
@ -166,7 +268,7 @@ const createBaseMarkdown = (
includeReasoning: boolean = false, includeReasoning: boolean = false,
excludeCitations: boolean = false, excludeCitations: boolean = false,
normalizeCitations: boolean = true normalizeCitations: boolean = true
) => { ): { titleSection: string; reasoningSection: string; contentSection: string; citation: string } => {
const { forceDollarMathInMarkdown } = store.getState().settings const { forceDollarMathInMarkdown } = store.getState().settings
const roleText = getRoleText(message.role, message.model?.name, message.model?.provider) const roleText = getRoleText(message.role, message.model?.name, message.model?.provider)
const titleSection = `## ${roleText}` const titleSection = `## ${roleText}`
@ -180,13 +282,8 @@ const createBaseMarkdown = (
} else if (reasoningContent.startsWith('<think>')) { } else if (reasoningContent.startsWith('<think>')) {
reasoningContent = reasoningContent.substring(7) reasoningContent = reasoningContent.substring(7)
} }
reasoningContent = reasoningContent // 使用 DOMPurify 安全地处理思维链内容
.replace(/&/g, '&amp;') reasoningContent = sanitizeReasoningContent(reasoningContent)
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/\n/g, '<br>')
if (forceDollarMathInMarkdown) { if (forceDollarMathInMarkdown) {
reasoningContent = convertMathFormula(reasoningContent) reasoningContent = convertMathFormula(reasoningContent)
} }
@ -216,7 +313,7 @@ const createBaseMarkdown = (
return { titleSection, reasoningSection, contentSection: processedContent, citation } return { titleSection, reasoningSection, contentSection: processedContent, citation }
} }
export const messageToMarkdown = (message: Message, excludeCitations?: boolean) => { export const messageToMarkdown = (message: Message, excludeCitations?: boolean): string => {
const { excludeCitationsInExport, standardizeCitationsInExport } = store.getState().settings const { excludeCitationsInExport, standardizeCitationsInExport } = store.getState().settings
const shouldExcludeCitations = excludeCitations ?? excludeCitationsInExport const shouldExcludeCitations = excludeCitations ?? excludeCitationsInExport
const { titleSection, contentSection, citation } = createBaseMarkdown( const { titleSection, contentSection, citation } = createBaseMarkdown(
@ -228,7 +325,7 @@ export const messageToMarkdown = (message: Message, excludeCitations?: boolean)
return [titleSection, '', contentSection, citation].join('\n') return [titleSection, '', contentSection, citation].join('\n')
} }
export const messageToMarkdownWithReasoning = (message: Message, excludeCitations?: boolean) => { export const messageToMarkdownWithReasoning = (message: Message, excludeCitations?: boolean): string => {
const { excludeCitationsInExport, standardizeCitationsInExport } = store.getState().settings const { excludeCitationsInExport, standardizeCitationsInExport } = store.getState().settings
const shouldExcludeCitations = excludeCitations ?? excludeCitationsInExport const shouldExcludeCitations = excludeCitations ?? excludeCitationsInExport
const { titleSection, reasoningSection, contentSection, citation } = createBaseMarkdown( const { titleSection, reasoningSection, contentSection, citation } = createBaseMarkdown(
@ -237,10 +334,14 @@ export const messageToMarkdownWithReasoning = (message: Message, excludeCitation
shouldExcludeCitations, shouldExcludeCitations,
standardizeCitationsInExport standardizeCitationsInExport
) )
return [titleSection, '', reasoningSection + contentSection, citation].join('\n') return [titleSection, '', reasoningSection, contentSection, citation].join('\n')
} }
export const messagesToMarkdown = (messages: Message[], exportReasoning?: boolean, excludeCitations?: boolean) => { export const messagesToMarkdown = (
messages: Message[],
exportReasoning?: boolean,
excludeCitations?: boolean
): string => {
return messages return messages
.map((message) => .map((message) =>
exportReasoning exportReasoning
@ -266,7 +367,11 @@ const messagesToPlainText = (messages: Message[]): string => {
return messages.map(formatMessageAsPlainText).join('\n\n') return messages.map(formatMessageAsPlainText).join('\n\n')
} }
export const topicToMarkdown = async (topic: Topic, exportReasoning?: boolean, excludeCitations?: boolean) => { export const topicToMarkdown = async (
topic: Topic,
exportReasoning?: boolean,
excludeCitations?: boolean
): Promise<string> => {
const topicName = `# ${topic.name}` const topicName = `# ${topic.name}`
const messages = await fetchTopicMessages(topic.id) const messages = await fetchTopicMessages(topic.id)
@ -290,7 +395,18 @@ export const topicToPlainText = async (topic: Topic): Promise<string> => {
return topicName return topicName
} }
export const exportTopicAsMarkdown = async (topic: Topic, exportReasoning?: boolean, excludeCitations?: boolean) => { export const exportTopicAsMarkdown = async (
topic: Topic,
exportReasoning?: boolean,
excludeCitations?: boolean
): Promise<void> => {
if (getExportState()) {
window.message.warning({ content: i18n.t('message.warn.export.exporting'), key: 'markdown-exporting' })
return
}
setExportingState(true)
const { markdownExportPath } = store.getState().settings const { markdownExportPath } = store.getState().settings
if (!markdownExportPath) { if (!markdownExportPath) {
try { try {
@ -305,7 +421,9 @@ export const exportTopicAsMarkdown = async (topic: Topic, exportReasoning?: bool
} }
} catch (error: any) { } catch (error: any) {
window.message.error({ content: i18n.t('message.error.markdown.export.specified'), key: 'markdown-error' }) window.message.error({ content: i18n.t('message.error.markdown.export.specified'), key: 'markdown-error' })
logger.debug(error) logger.error('Failed to export topic as markdown:', error)
} finally {
setExportingState(false)
} }
} else { } else {
try { try {
@ -316,7 +434,9 @@ export const exportTopicAsMarkdown = async (topic: Topic, exportReasoning?: bool
window.message.success({ content: i18n.t('message.success.markdown.export.preconf'), key: 'markdown-success' }) window.message.success({ content: i18n.t('message.success.markdown.export.preconf'), key: 'markdown-success' })
} catch (error: any) { } catch (error: any) {
window.message.error({ content: i18n.t('message.error.markdown.export.preconf'), key: 'markdown-error' }) window.message.error({ content: i18n.t('message.error.markdown.export.preconf'), key: 'markdown-error' })
logger.debug(error) logger.error('Failed to export topic as markdown:', error)
} finally {
setExportingState(false)
} }
} }
} }
@ -325,7 +445,14 @@ export const exportMessageAsMarkdown = async (
message: Message, message: Message,
exportReasoning?: boolean, exportReasoning?: boolean,
excludeCitations?: boolean excludeCitations?: boolean
) => { ): Promise<void> => {
if (getExportState()) {
window.message.warning({ content: i18n.t('message.warn.export.exporting'), key: 'markdown-exporting' })
return
}
setExportingState(true)
const { markdownExportPath } = store.getState().settings const { markdownExportPath } = store.getState().settings
if (!markdownExportPath) { if (!markdownExportPath) {
try { try {
@ -343,7 +470,9 @@ export const exportMessageAsMarkdown = async (
} }
} catch (error: any) { } catch (error: any) {
window.message.error({ content: i18n.t('message.error.markdown.export.specified'), key: 'markdown-error' }) window.message.error({ content: i18n.t('message.error.markdown.export.specified'), key: 'markdown-error' })
logger.debug(error) logger.error('Failed to export message as markdown:', error)
} finally {
setExportingState(false)
} }
} else { } else {
try { try {
@ -357,12 +486,14 @@ export const exportMessageAsMarkdown = async (
window.message.success({ content: i18n.t('message.success.markdown.export.preconf'), key: 'markdown-success' }) window.message.success({ content: i18n.t('message.success.markdown.export.preconf'), key: 'markdown-success' })
} catch (error: any) { } catch (error: any) {
window.message.error({ content: i18n.t('message.error.markdown.export.preconf'), key: 'markdown-error' }) window.message.error({ content: i18n.t('message.error.markdown.export.preconf'), key: 'markdown-error' })
logger.debug(error) logger.error('Failed to export message as markdown:', error)
} finally {
setExportingState(false)
} }
} }
} }
const convertMarkdownToNotionBlocks = async (markdown: string) => { const convertMarkdownToNotionBlocks = async (markdown: string): Promise<any[]> => {
return markdownToBlocks(markdown) return markdownToBlocks(markdown)
} }
@ -371,77 +502,109 @@ const convertThinkingToNotionBlocks = async (thinkingContent: string): Promise<a
return [] return []
} }
const thinkingBlocks = [ try {
{ // 预处理思维链内容将HTML的<br>标签转换为真正的换行符
object: 'block', const processedContent = thinkingContent.replace(/<br\s*\/?>/g, '\n')
type: 'toggle',
toggle: {
rich_text: [
{
type: 'text',
text: {
content: '🤔 ' + i18n.t('common.reasoning_content')
},
annotations: {
bold: true
}
}
],
children: [
{
object: 'block',
type: 'paragraph',
paragraph: {
rich_text: [
{
type: 'text',
text: {
content: thinkingContent
}
}
]
}
}
]
}
}
]
return thinkingBlocks // 使用 markdownToBlocks 处理思维链内容
const childrenBlocks = markdownToBlocks(processedContent)
return [
{
object: 'block',
type: 'toggle',
toggle: {
rich_text: [
{
type: 'text',
text: {
content: '🤔 ' + i18n.t('common.reasoning_content')
},
annotations: {
bold: true
}
}
],
children: childrenBlocks
}
}
]
} catch (error) {
logger.error('failed to process reasoning content:', error as Error)
// 发生错误时,回退到简单的段落处理
return [
{
object: 'block',
type: 'toggle',
toggle: {
rich_text: [
{
type: 'text',
text: {
content: '🤔 ' + i18n.t('common.reasoning_content')
},
annotations: {
bold: true
}
}
],
children: [
{
object: 'block',
type: 'paragraph',
paragraph: {
rich_text: [
{
type: 'text',
text: {
content:
thinkingContent.length > 1800
? thinkingContent.substring(0, 1800) + '...\n' + i18n.t('export.notion.reasoning_truncated')
: thinkingContent
}
}
]
}
}
]
}
}
]
}
} }
const executeNotionExport = async (title: string, allBlocks: any[]): Promise<any> => { const executeNotionExport = async (title: string, allBlocks: any[]): Promise<boolean> => {
const { isExporting } = store.getState().runtime.export if (getExportState()) {
if (isExporting) { window.message.warning({ content: i18n.t('message.warn.export.exporting'), key: 'notion-exporting' })
window.message.warning({ content: i18n.t('message.warn.notion.exporting'), key: 'notion-exporting' }) return false
return null
} }
setExportState({ isExporting: true })
title = title.slice(0, 29) + '...'
const { notionDatabaseID, notionApiKey } = store.getState().settings const { notionDatabaseID, notionApiKey } = store.getState().settings
if (!notionApiKey || !notionDatabaseID) { if (!notionApiKey || !notionDatabaseID) {
window.message.error({ content: i18n.t('message.error.notion.no_api_key'), key: 'notion-no-apikey-error' }) window.message.error({ content: i18n.t('message.error.notion.no_api_key'), key: 'notion-no-apikey-error' })
setExportState({ isExporting: false }) return false
return null }
if (allBlocks.length === 0) {
window.message.error({ content: i18n.t('message.error.notion.export'), key: 'notion-no-content-error' })
return false
}
setExportingState(true)
// 限制标题长度
if (title.length > 32) {
title = title.slice(0, 29) + '...'
} }
try { try {
const notion = new Client({ auth: notionApiKey }) const notion = new Client({ auth: notionApiKey })
if (allBlocks.length === 0) {
throw new Error('No content to export')
}
window.message.loading({ window.message.loading({
content: i18n.t('message.loading.notion.preparing'), content: i18n.t('message.loading.notion.preparing'),
key: 'notion-preparing', key: 'notion-preparing',
duration: 0 duration: 0
}) })
let mainPageResponse: any = null
let parentBlockId: string | null = null
const response = await notion.pages.create({ const response = await notion.pages.create({
parent: { database_id: notionDatabaseID }, parent: { database_id: notionDatabaseID },
@ -451,34 +614,37 @@ const executeNotionExport = async (title: string, allBlocks: any[]): Promise<any
} }
} }
}) })
mainPageResponse = response
parentBlockId = response.id
window.message.destroy('notion-preparing') window.message.destroy('notion-preparing')
window.message.loading({ window.message.loading({
content: i18n.t('message.loading.notion.exporting_progress'), content: i18n.t('message.loading.notion.exporting_progress'),
key: 'notion-exporting', key: 'notion-exporting',
duration: 0 duration: 0
}) })
if (allBlocks.length > 0) {
await appendBlocks({ await appendBlocks({
block_id: parentBlockId, block_id: response.id,
children: allBlocks, children: allBlocks,
client: notion client: notion
}) })
}
window.message.destroy('notion-exporting') window.message.destroy('notion-exporting')
window.message.success({ content: i18n.t('message.success.notion.export'), key: 'notion-success' }) window.message.success({ content: i18n.t('message.success.notion.export'), key: 'notion-success' })
return mainPageResponse return true
} catch (error: any) { } catch (error: any) {
window.message.error({ content: i18n.t('message.error.notion.export'), key: 'notion-export-progress' }) // 清理可能存在的loading消息
logger.debug(error) window.message.destroy('notion-preparing')
return null window.message.destroy('notion-exporting')
logger.error('Notion export failed:', error)
window.message.error({ content: i18n.t('message.error.notion.export'), key: 'notion-export-error' })
return false
} finally { } finally {
setExportState({ isExporting: false }) setExportingState(false)
} }
} }
export const exportMessageToNotion = async (title: string, content: string, message?: Message) => { export const exportMessageToNotion = async (title: string, content: string, message?: Message): Promise<boolean> => {
const { notionExportReasoning } = store.getState().settings const { notionExportReasoning } = store.getState().settings
const notionBlocks = await convertMarkdownToNotionBlocks(content) const notionBlocks = await convertMarkdownToNotionBlocks(content)
@ -498,7 +664,7 @@ export const exportMessageToNotion = async (title: string, content: string, mess
return executeNotionExport(title, notionBlocks) return executeNotionExport(title, notionBlocks)
} }
export const exportTopicToNotion = async (topic: Topic) => { export const exportTopicToNotion = async (topic: Topic): Promise<boolean> => {
const { notionExportReasoning, excludeCitationsInExport } = store.getState().settings const { notionExportReasoning, excludeCitationsInExport } = store.getState().settings
const topicMessages = await fetchTopicMessages(topic.id) const topicMessages = await fetchTopicMessages(topic.id)
@ -532,12 +698,11 @@ export const exportTopicToNotion = async (topic: Topic) => {
return executeNotionExport(topic.name, allBlocks) return executeNotionExport(topic.name, allBlocks)
} }
export const exportMarkdownToYuque = async (title: string, content: string) => { export const exportMarkdownToYuque = async (title: string, content: string): Promise<any | null> => {
const { isExporting } = store.getState().runtime.export
const { yuqueToken, yuqueRepoId } = store.getState().settings const { yuqueToken, yuqueRepoId } = store.getState().settings
if (isExporting) { if (getExportState()) {
window.message.warning({ content: i18n.t('message.warn.yuque.exporting'), key: 'yuque-exporting' }) window.message.warning({ content: i18n.t('message.warn.export.exporting'), key: 'yuque-exporting' })
return return
} }
@ -546,7 +711,7 @@ export const exportMarkdownToYuque = async (title: string, content: string) => {
return return
} }
setExportState({ isExporting: true }) setExportingState(true)
try { try {
const response = await fetch(`https://www.yuque.com/api/v2/repos/${yuqueRepoId}/docs`, { const response = await fetch(`https://www.yuque.com/api/v2/repos/${yuqueRepoId}/docs`, {
@ -602,7 +767,7 @@ export const exportMarkdownToYuque = async (title: string, content: string) => {
}) })
return null return null
} finally { } finally {
setExportState({ isExporting: false }) setExportingState(false)
} }
} }
@ -617,7 +782,14 @@ export const exportMarkdownToYuque = async (title: string, content: string) => {
* @param attributes.folder * @param attributes.folder
* @param attributes.vault Vault名称 * @param attributes.vault Vault名称
*/ */
export const exportMarkdownToObsidian = async (attributes: any) => { export const exportMarkdownToObsidian = async (attributes: any): Promise<void> => {
if (getExportState()) {
window.message.warning({ content: i18n.t('message.warn.export.exporting'), key: 'obsidian-exporting' })
return
}
setExportingState(true)
try { try {
// 从参数获取Vault名称 // 从参数获取Vault名称
const obsidianVault = attributes.vault const obsidianVault = attributes.vault
@ -669,8 +841,10 @@ export const exportMarkdownToObsidian = async (attributes: any) => {
window.open(obsidianUrl) window.open(obsidianUrl)
window.message.success(i18n.t('chat.topics.export.obsidian_export_success')) window.message.success(i18n.t('chat.topics.export.obsidian_export_success'))
} catch (error) { } catch (error) {
logger.error('导出到Obsidian失败:', error as Error) logger.error('Failed to export to Obsidian:', error as Error)
window.message.error(i18n.t('chat.topics.export.obsidian_export_failed')) window.message.error(i18n.t('chat.topics.export.obsidian_export_failed'))
} finally {
setExportingState(false)
} }
} }
@ -719,14 +893,24 @@ function transformObsidianFileName(fileName: string): string {
return sanitized return sanitized
} }
export const exportMarkdownToJoplin = async (title: string, contentOrMessages: string | Message | Message[]) => { export const exportMarkdownToJoplin = async (
title: string,
contentOrMessages: string | Message | Message[]
): Promise<any | null> => {
const { joplinUrl, joplinToken, joplinExportReasoning, excludeCitationsInExport } = store.getState().settings const { joplinUrl, joplinToken, joplinExportReasoning, excludeCitationsInExport } = store.getState().settings
if (getExportState()) {
window.message.warning({ content: i18n.t('message.warn.export.exporting'), key: 'joplin-exporting' })
return
}
if (!joplinUrl || !joplinToken) { if (!joplinUrl || !joplinToken) {
window.message.error(i18n.t('message.error.joplin.no_config')) window.message.error(i18n.t('message.error.joplin.no_config'))
return return
} }
setExportingState(true)
let content: string let content: string
if (typeof contentOrMessages === 'string') { if (typeof contentOrMessages === 'string') {
content = contentOrMessages content = contentOrMessages
@ -763,11 +947,13 @@ export const exportMarkdownToJoplin = async (title: string, contentOrMessages: s
} }
window.message.success(i18n.t('message.success.joplin.export')) window.message.success(i18n.t('message.success.joplin.export'))
return return data
} catch (error: any) { } catch (error: any) {
logger.error('Failed to export to Joplin:', error)
window.message.error(i18n.t('message.error.joplin.export')) window.message.error(i18n.t('message.error.joplin.export'))
logger.debug(error) return null
return } finally {
setExportingState(false)
} }
} }
@ -776,12 +962,11 @@ export const exportMarkdownToJoplin = async (title: string, contentOrMessages: s
* @param title * @param title
* @param content * @param content
*/ */
export const exportMarkdownToSiyuan = async (title: string, content: string) => { export const exportMarkdownToSiyuan = async (title: string, content: string): Promise<void> => {
const { isExporting } = store.getState().runtime.export
const { siyuanApiUrl, siyuanToken, siyuanBoxId, siyuanRootPath } = store.getState().settings const { siyuanApiUrl, siyuanToken, siyuanBoxId, siyuanRootPath } = store.getState().settings
if (isExporting) { if (getExportState()) {
window.message.warning({ content: i18n.t('message.warn.siyuan.exporting'), key: 'siyuan-exporting' }) window.message.warning({ content: i18n.t('message.warn.export.exporting'), key: 'siyuan-exporting' })
return return
} }
@ -790,7 +975,7 @@ export const exportMarkdownToSiyuan = async (title: string, content: string) =>
return return
} }
setExportState({ isExporting: true }) setExportingState(true)
try { try {
// test connection // test connection
@ -826,13 +1011,13 @@ export const exportMarkdownToSiyuan = async (title: string, content: string) =>
key: 'siyuan-success' key: 'siyuan-success'
}) })
} catch (error) { } catch (error) {
logger.error('导出到思源笔记失败:', error as Error) logger.error('Failed to export to Siyuan:', error as Error)
window.message.error({ window.message.error({
content: i18n.t('message.error.siyuan.export') + (error instanceof Error ? `: ${error.message}` : ''), content: i18n.t('message.error.siyuan.export') + (error instanceof Error ? `: ${error.message}` : ''),
key: 'siyuan-error' key: 'siyuan-error'
}) })
} finally { } finally {
setExportState({ isExporting: false }) setExportingState(false)
} }
} }
/** /**

View File

@ -8552,7 +8552,6 @@ __metadata:
dexie-react-hooks: "npm:^1.1.7" dexie-react-hooks: "npm:^1.1.7"
diff: "npm:^7.0.0" diff: "npm:^7.0.0"
docx: "npm:^9.0.2" docx: "npm:^9.0.2"
dompurify: "npm:^3.2.6"
dotenv-cli: "npm:^7.4.2" dotenv-cli: "npm:^7.4.2"
electron: "npm:37.2.3" electron: "npm:37.2.3"
electron-builder: "npm:26.0.15" electron-builder: "npm:26.0.15"
@ -11446,7 +11445,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"dompurify@npm:^3.2.5, dompurify@npm:^3.2.6": "dompurify@npm:^3.2.5":
version: 3.2.6 version: 3.2.6
resolution: "dompurify@npm:3.2.6" resolution: "dompurify@npm:3.2.6"
dependencies: dependencies: