mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-06 05:09:09 +08:00
refactor: optimize notion export (#7228)
* fix(export): Initial fix for the multi-level list export issue in Notion * fix(getMessageTitle): optimize loading message * refactor(notion export): optimize notion export - import notion-helper - strengthen the robustness of the Notion Export function * fix(i18n): optimize notion export infos
This commit is contained in:
parent
bd87b8a002
commit
acf78e8383
@ -62,6 +62,7 @@
|
|||||||
"@libsql/win32-x64-msvc": "^0.4.7",
|
"@libsql/win32-x64-msvc": "^0.4.7",
|
||||||
"@strongtz/win32-arm64-msvc": "^0.4.7",
|
"@strongtz/win32-arm64-msvc": "^0.4.7",
|
||||||
"jsdom": "26.1.0",
|
"jsdom": "26.1.0",
|
||||||
|
"notion-helper": "^1.3.22",
|
||||||
"os-proxy-config": "^1.1.2",
|
"os-proxy-config": "^1.1.2",
|
||||||
"selection-hook": "^0.9.23",
|
"selection-hook": "^0.9.23",
|
||||||
"turndown": "7.2.0"
|
"turndown": "7.2.0"
|
||||||
|
|||||||
@ -653,8 +653,7 @@
|
|||||||
"group.delete.content": "Deleting a group message will delete the user's question and all assistant's answers",
|
"group.delete.content": "Deleting a group message will delete the user's question and all assistant's answers",
|
||||||
"group.delete.title": "Delete Group Message",
|
"group.delete.title": "Delete Group Message",
|
||||||
"ignore.knowledge.base": "Web search mode is enabled, ignore knowledge base",
|
"ignore.knowledge.base": "Web search mode is enabled, ignore knowledge base",
|
||||||
"info.notion.block_reach_limit": "Dialogue too long, exporting to Notion in pages",
|
"loading.notion.exporting_progress": "Exporting to Notion ...",
|
||||||
"loading.notion.exporting_progress": "Exporting to Notion ({{current}}/{{total}})...",
|
|
||||||
"loading.notion.preparing": "Preparing to export to Notion...",
|
"loading.notion.preparing": "Preparing to export to Notion...",
|
||||||
"mention.title": "Switch model answer",
|
"mention.title": "Switch model answer",
|
||||||
"message.code_style": "Code style",
|
"message.code_style": "Code style",
|
||||||
|
|||||||
@ -651,8 +651,7 @@
|
|||||||
"group.delete.content": "分組メッセージを削除するとユーザーの質問と助け手の回答がすべて削除されます",
|
"group.delete.content": "分組メッセージを削除するとユーザーの質問と助け手の回答がすべて削除されます",
|
||||||
"group.delete.title": "分組メッセージを削除",
|
"group.delete.title": "分組メッセージを削除",
|
||||||
"ignore.knowledge.base": "インターネットモードが有効になっています。ナレッジベースを無視します",
|
"ignore.knowledge.base": "インターネットモードが有効になっています。ナレッジベースを無視します",
|
||||||
"info.notion.block_reach_limit": "会話が長すぎます。Notionにページごとにエクスポートしています",
|
"loading.notion.exporting_progress": "Notionにエクスポート中 ...",
|
||||||
"loading.notion.exporting_progress": "Notionにエクスポート中 ({{current}}/{{total}})...",
|
|
||||||
"loading.notion.preparing": "Notionへのエクスポートを準備中...",
|
"loading.notion.preparing": "Notionへのエクスポートを準備中...",
|
||||||
"mention.title": "モデルを切り替える",
|
"mention.title": "モデルを切り替える",
|
||||||
"message.code_style": "コードスタイル",
|
"message.code_style": "コードスタイル",
|
||||||
|
|||||||
@ -652,8 +652,7 @@
|
|||||||
"group.delete.content": "Удаление группы сообщений удалит пользовательский вопрос и все ответы помощника",
|
"group.delete.content": "Удаление группы сообщений удалит пользовательский вопрос и все ответы помощника",
|
||||||
"group.delete.title": "Удалить группу сообщений",
|
"group.delete.title": "Удалить группу сообщений",
|
||||||
"ignore.knowledge.base": "Режим сети включен, игнорировать базу знаний",
|
"ignore.knowledge.base": "Режим сети включен, игнорировать базу знаний",
|
||||||
"info.notion.block_reach_limit": "Диалог слишком длинный, экспортируется в Notion по страницам",
|
"loading.notion.exporting_progress": "Экспорт в Notion ...",
|
||||||
"loading.notion.exporting_progress": "Экспорт в Notion ({{current}}/{{total}})...",
|
|
||||||
"loading.notion.preparing": "Подготовка к экспорту в Notion...",
|
"loading.notion.preparing": "Подготовка к экспорту в Notion...",
|
||||||
"mention.title": "Переключить модель ответа",
|
"mention.title": "Переключить модель ответа",
|
||||||
"message.code_style": "Стиль кода",
|
"message.code_style": "Стиль кода",
|
||||||
|
|||||||
@ -653,8 +653,7 @@
|
|||||||
"group.delete.content": "删除分组消息会删除用户提问和所有助手的回答",
|
"group.delete.content": "删除分组消息会删除用户提问和所有助手的回答",
|
||||||
"group.delete.title": "删除分组消息",
|
"group.delete.title": "删除分组消息",
|
||||||
"ignore.knowledge.base": "联网模式开启,忽略知识库",
|
"ignore.knowledge.base": "联网模式开启,忽略知识库",
|
||||||
"info.notion.block_reach_limit": "对话过长,正在分段导出到Notion",
|
"loading.notion.exporting_progress": "正在导出到Notion ...",
|
||||||
"loading.notion.exporting_progress": "正在导出到Notion ({{current}}/{{total}})...",
|
|
||||||
"loading.notion.preparing": "正在准备导出到Notion...",
|
"loading.notion.preparing": "正在准备导出到Notion...",
|
||||||
"mention.title": "切换模型回答",
|
"mention.title": "切换模型回答",
|
||||||
"message.code_style": "代码风格",
|
"message.code_style": "代码风格",
|
||||||
|
|||||||
@ -653,8 +653,7 @@
|
|||||||
"group.delete.content": "刪除分組訊息會刪除使用者提問和所有助手的回答",
|
"group.delete.content": "刪除分組訊息會刪除使用者提問和所有助手的回答",
|
||||||
"group.delete.title": "刪除分組訊息",
|
"group.delete.title": "刪除分組訊息",
|
||||||
"ignore.knowledge.base": "網路模式開啟,忽略知識庫",
|
"ignore.knowledge.base": "網路模式開啟,忽略知識庫",
|
||||||
"info.notion.block_reach_limit": "對話過長,自動分頁匯出到 Notion",
|
"loading.notion.exporting_progress": "正在匯出到 Notion ...",
|
||||||
"loading.notion.exporting_progress": "正在匯出到 Notion ({{current}}/{{total}})...",
|
|
||||||
"loading.notion.preparing": "正在準備匯出到 Notion...",
|
"loading.notion.preparing": "正在準備匯出到 Notion...",
|
||||||
"mention.title": "切換模型回答",
|
"mention.title": "切換模型回答",
|
||||||
"message.code_style": "程式碼風格",
|
"message.code_style": "程式碼風格",
|
||||||
|
|||||||
@ -214,7 +214,11 @@ export async function getMessageTitle(message: Message, length = 30): Promise<st
|
|||||||
|
|
||||||
if ((store.getState().settings as any).useTopicNamingForMessageTitle) {
|
if ((store.getState().settings as any).useTopicNamingForMessageTitle) {
|
||||||
try {
|
try {
|
||||||
window.message.loading({ content: t('chat.topics.export.wait_for_title_naming'), key: 'message-title-naming' })
|
window.message.loading({
|
||||||
|
content: t('chat.topics.export.wait_for_title_naming'),
|
||||||
|
key: 'message-title-naming',
|
||||||
|
duration: 0
|
||||||
|
})
|
||||||
|
|
||||||
const tempMessage = resetMessage(message, {
|
const tempMessage = resetMessage(message, {
|
||||||
status: AssistantMessageStatus.SUCCESS,
|
status: AssistantMessageStatus.SUCCESS,
|
||||||
@ -226,7 +230,7 @@ export async function getMessageTitle(message: Message, length = 30): Promise<st
|
|||||||
// store.dispatch(messageBlocksActions.upsertOneBlock(tempTextBlock))
|
// store.dispatch(messageBlocksActions.upsertOneBlock(tempTextBlock))
|
||||||
|
|
||||||
// store.dispatch(messageBlocksActions.removeOneBlock(tempTextBlock.id))
|
// store.dispatch(messageBlocksActions.removeOneBlock(tempTextBlock.id))
|
||||||
|
window.message.destroy('message-title-naming')
|
||||||
if (title) {
|
if (title) {
|
||||||
window.message.success({ content: t('chat.topics.export.title_naming_success'), key: 'message-title-naming' })
|
window.message.success({ content: t('chat.topics.export.title_naming_success'), key: 'message-title-naming' })
|
||||||
return title
|
return title
|
||||||
|
|||||||
@ -11,6 +11,7 @@ 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 { appendBlocks } from 'notion-helper' // 引入 notion-helper 的 appendBlocks 函数
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从消息内容中提取标题,限制长度并处理换行和标点符号。用于导出功能。
|
* 从消息内容中提取标题,限制长度并处理换行和标点符号。用于导出功能。
|
||||||
@ -230,29 +231,6 @@ const convertMarkdownToNotionBlocks = async (markdown: string) => {
|
|||||||
return markdownToBlocks(markdown)
|
return markdownToBlocks(markdown)
|
||||||
}
|
}
|
||||||
|
|
||||||
const splitNotionBlocks = (blocks: any[]) => {
|
|
||||||
// Notion API限制单次传输100块
|
|
||||||
const notionSplitSize = 95
|
|
||||||
|
|
||||||
const pages: any[][] = []
|
|
||||||
let currentPage: any[] = []
|
|
||||||
|
|
||||||
blocks.forEach((block) => {
|
|
||||||
if (currentPage.length >= notionSplitSize) {
|
|
||||||
window.message.info({ content: i18n.t('message.info.notion.block_reach_limit'), key: 'notion-block-reach-limit' })
|
|
||||||
pages.push(currentPage)
|
|
||||||
currentPage = []
|
|
||||||
}
|
|
||||||
currentPage.push(block)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (currentPage.length > 0) {
|
|
||||||
pages.push(currentPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pages
|
|
||||||
}
|
|
||||||
|
|
||||||
const convertThinkingToNotionBlocks = async (thinkingContent: string): Promise<any[]> => {
|
const convertThinkingToNotionBlocks = async (thinkingContent: string): Promise<any[]> => {
|
||||||
if (!thinkingContent.trim()) {
|
if (!thinkingContent.trim()) {
|
||||||
return []
|
return []
|
||||||
@ -306,6 +284,8 @@ const executeNotionExport = async (title: string, allBlocks: any[]): Promise<any
|
|||||||
|
|
||||||
setExportState({ isExporting: true })
|
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' })
|
||||||
@ -315,62 +295,44 @@ const executeNotionExport = async (title: string, allBlocks: any[]): Promise<any
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const notion = new Client({ auth: notionApiKey })
|
const notion = new Client({ auth: notionApiKey })
|
||||||
const blockPages = splitNotionBlocks(allBlocks)
|
|
||||||
|
|
||||||
if (blockPages.length === 0) {
|
if (allBlocks.length === 0) {
|
||||||
throw new Error('No content to export')
|
throw new Error('No content to export')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建主页面和子页面
|
window.message.loading({
|
||||||
|
content: i18n.t('message.loading.notion.preparing'),
|
||||||
|
key: 'notion-preparing',
|
||||||
|
duration: 0
|
||||||
|
})
|
||||||
let mainPageResponse: any = null
|
let mainPageResponse: any = null
|
||||||
let parentBlockId: string | null = null
|
let parentBlockId: string | null = null
|
||||||
|
|
||||||
for (let i = 0; i < blockPages.length; i++) {
|
const response = await notion.pages.create({
|
||||||
const pageBlocks = blockPages[i]
|
parent: { database_id: notionDatabaseID },
|
||||||
|
properties: {
|
||||||
// 导出进度提示
|
[store.getState().settings.notionPageNameKey || 'Name']: {
|
||||||
if (blockPages.length > 1) {
|
title: [{ text: { content: title } }]
|
||||||
window.message.loading({
|
|
||||||
content: i18n.t('message.loading.notion.exporting_progress', {
|
|
||||||
current: i + 1,
|
|
||||||
total: blockPages.length
|
|
||||||
}),
|
|
||||||
key: 'notion-export-progress'
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
window.message.loading({
|
|
||||||
content: i18n.t('message.loading.notion.preparing'),
|
|
||||||
key: 'notion-export-progress'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i === 0) {
|
|
||||||
// 创建主页面
|
|
||||||
const response = await notion.pages.create({
|
|
||||||
parent: { database_id: notionDatabaseID },
|
|
||||||
properties: {
|
|
||||||
[store.getState().settings.notionPageNameKey || 'Name']: {
|
|
||||||
title: [{ text: { content: title } }]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
children: pageBlocks
|
|
||||||
})
|
|
||||||
mainPageResponse = response
|
|
||||||
parentBlockId = response.id
|
|
||||||
} else {
|
|
||||||
// 追加后续页面的块到主页面
|
|
||||||
if (!parentBlockId) {
|
|
||||||
throw new Error('Parent block ID is null')
|
|
||||||
}
|
}
|
||||||
await notion.blocks.children.append({
|
|
||||||
block_id: parentBlockId,
|
|
||||||
children: pageBlocks
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
mainPageResponse = response
|
||||||
|
parentBlockId = response.id
|
||||||
|
window.message.destroy('notion-preparing')
|
||||||
|
window.message.loading({
|
||||||
|
content: i18n.t('message.loading.notion.exporting_progress'),
|
||||||
|
key: 'notion-exporting',
|
||||||
|
duration: 0
|
||||||
|
})
|
||||||
|
if (allBlocks.length > 0) {
|
||||||
|
await appendBlocks({
|
||||||
|
block_id: parentBlockId,
|
||||||
|
children: allBlocks,
|
||||||
|
client: notion
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
window.message.destroy('notion-exporting')
|
||||||
const messageKey = blockPages.length > 1 ? 'notion-export-progress' : 'notion-success'
|
window.message.success({ content: i18n.t('message.success.notion.export'), key: 'notion-success' })
|
||||||
window.message.success({ content: i18n.t('message.success.notion.export'), key: messageKey })
|
|
||||||
return mainPageResponse
|
return mainPageResponse
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
window.message.error({ content: i18n.t('message.error.notion.export'), key: 'notion-export-progress' })
|
window.message.error({ content: i18n.t('message.error.notion.export'), key: 'notion-export-progress' })
|
||||||
|
|||||||
@ -133,7 +133,10 @@ export const getCitationContent = (message: Message): string => {
|
|||||||
return citationBlocks
|
return citationBlocks
|
||||||
.map((block) => formatCitationsFromBlock(block))
|
.map((block) => formatCitationsFromBlock(block))
|
||||||
.flat()
|
.flat()
|
||||||
.map((citation) => `[${citation.number}] [${citation.title || citation.url}](${citation.url})`)
|
.map(
|
||||||
|
(citation) =>
|
||||||
|
`[${citation.number}] [${citation.title || citation.url.slice(0, 1999)}](${citation.url.slice(0, 1999)})`
|
||||||
|
)
|
||||||
.join('\n\n')
|
.join('\n\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
yarn.lock
13
yarn.lock
@ -5682,6 +5682,7 @@ __metadata:
|
|||||||
mime: "npm:^4.0.4"
|
mime: "npm:^4.0.4"
|
||||||
motion: "npm:^12.10.5"
|
motion: "npm:^12.10.5"
|
||||||
node-stream-zip: "npm:^1.15.0"
|
node-stream-zip: "npm:^1.15.0"
|
||||||
|
notion-helper: "npm:^1.3.22"
|
||||||
npx-scope-finder: "npm:^1.2.0"
|
npx-scope-finder: "npm:^1.2.0"
|
||||||
officeparser: "npm:^4.1.1"
|
officeparser: "npm:^4.1.1"
|
||||||
openai: "patch:openai@npm%3A5.1.0#~/.yarn/patches/openai-npm-5.1.0-0e7b3ccb07.patch"
|
openai: "patch:openai@npm%3A5.1.0#~/.yarn/patches/openai-npm-5.1.0-0e7b3ccb07.patch"
|
||||||
@ -13861,6 +13862,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"notion-helper@npm:^1.3.22":
|
||||||
|
version: 1.3.22
|
||||||
|
resolution: "notion-helper@npm:1.3.22"
|
||||||
|
peerDependencies:
|
||||||
|
"@notionhq/client": ^2.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@notionhq/client":
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/4afad1d6610ec910fe3fba0cb204431a1e5f3b45b5294c5ac3c0108611859a5919597e0400f500550fad709d291b7931cfe2766a49eb59638305584b90c02463
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"npm-run-path@npm:^5.1.0":
|
"npm-run-path@npm:^5.1.0":
|
||||||
version: 5.3.0
|
version: 5.3.0
|
||||||
resolution: "npm-run-path@npm:5.3.0"
|
resolution: "npm-run-path@npm:5.3.0"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user