From f3fc9a23fee1426d6f2876942e1eff49c1c9b7a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=87=AA=E7=94=B1=E7=9A=84=E4=B8=96=E7=95=8C=E4=BA=BA?= <3196812536@qq.com> Date: Fri, 18 Jul 2025 14:34:16 +0800 Subject: [PATCH] feat: add export to Notes feature Introduced the ability to export messages and topics to the Notes workspace. Updated UI components, i18n strings, settings, migration logic, and export utilities to support the new export option. --- src/renderer/src/i18n/locales/zh-cn.json | 6 ++- .../pages/home/Messages/MessageMenubar.tsx | 10 ++++ .../src/pages/home/Tabs/TopicsTab.tsx | 16 +++++- .../DataSettings/ExportMenuSettings.tsx | 7 ++- src/renderer/src/store/migrate.ts | 4 ++ src/renderer/src/store/settings.ts | 4 +- src/renderer/src/utils/export.ts | 54 +++++++++++++++++++ 7 files changed, 97 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 31aa2e52c4..8195e01fd4 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -313,6 +313,7 @@ "topics.export.md.reason": "导出为 Markdown (包含思考)", "topics.export.notion": "导出到 Notion", "topics.export.obsidian": "导出到 Obsidian", + "topics.export.notes": "导出到笔记", "topics.export.obsidian_atributes": "配置笔记属性", "topics.export.obsidian_btn": "确定", "topics.export.obsidian_created": "创建时间", @@ -713,6 +714,7 @@ "error.siyuan.no_config": "未配置思源笔记 API 地址或令牌", "error.yuque.export": "导出语雀错误,请检查连接状态并对照文档检查配置", "error.yuque.no_config": "未配置语雀 Token 或 知识库 URL", + "error.notes.export": "导出笔记失败", "group.delete.content": "删除分组消息会删除用户提问和所有助手的回答", "group.delete.title": "删除分组消息", "ignore.knowledge.base": "联网模式开启,忽略知识库", @@ -747,6 +749,7 @@ "success.notion.export": "成功导出到 Notion", "success.siyuan.export": "导出到思源笔记成功", "success.yuque.export": "成功导出到语雀", + "success.notes.export": "成功导出到笔记", "switch.disabled": "请等待当前回复完成后操作", "tools": { "abort_failed": "工具调用中断失败", @@ -1405,7 +1408,8 @@ "plain_text": "复制为纯文本", "siyuan": "导出到思源笔记", "title": "导出菜单设置", - "yuque": "导出到语雀" + "yuque": "导出到语雀", + "notes": "导出到笔记" }, "hour_interval_one": "{{count}} 小时", "hour_interval_other": "{{count}} 小时", diff --git a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx index 0f0c1f01b7..5b73007974 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -23,6 +23,7 @@ import { exportMarkdownToSiyuan, exportMarkdownToYuque, exportMessageAsMarkdown, + exportMessageToNotes, exportMessageToNotion, messageToMarkdown } from '@renderer/utils/export' @@ -322,6 +323,15 @@ const MessageMenubar: FC = (props) => { const markdown = messageToMarkdown(message) exportMarkdownToSiyuan(title, markdown) } + }, + exportMenuOptions.notes && { + label: t('chat.topics.export.notes'), + key: 'notes', + onClick: async () => { + const title = await getMessageTitle(message) + const markdown = messageToMarkdown(message) + exportMessageToNotes(title, markdown) + } } ].filter(Boolean) } diff --git a/src/renderer/src/pages/home/Tabs/TopicsTab.tsx b/src/renderer/src/pages/home/Tabs/TopicsTab.tsx index d296ccdcf9..eb9fdc23dd 100644 --- a/src/renderer/src/pages/home/Tabs/TopicsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/TopicsTab.tsx @@ -31,6 +31,7 @@ import { exportMarkdownToSiyuan, exportMarkdownToYuque, exportTopicAsMarkdown, + exportTopicToNotes, exportTopicToNotion, topicToMarkdown } from '@renderer/utils/export' @@ -374,6 +375,13 @@ const Topics: FC = ({ assistant: _assistant, activeTopic, setActiveTopic const markdown = await topicToMarkdown(topic) exportMarkdownToSiyuan(topic.name, markdown) } + }, + exportMenuOptions.notes && { + label: t('chat.topics.export.notes'), + key: 'notes', + onClick: async () => { + exportTopicToNotes(topic) + } } ].filter(Boolean) as ItemType[] } @@ -419,6 +427,7 @@ const Topics: FC = ({ assistant: _assistant, activeTopic, setActiveTopic exportMenuOptions.obsidian, exportMenuOptions.joplin, exportMenuOptions.siyuan, + exportMenuOptions.notes, assistants, assistant, updateTopic, @@ -533,23 +542,28 @@ const TopicListItem = styled.div` justify-content: space-between; position: relative; cursor: pointer; - position: relative; width: calc(var(--assistants-width) - 20px); + .menu { opacity: 0; color: var(--color-text-3); } + &:hover { background-color: var(--color-list-item-hover); transition: background-color 0.1s; + .menu { opacity: 1; } } + &.active { background-color: var(--color-list-item); + .menu { opacity: 1; + &:hover { color: var(--color-text-2); } diff --git a/src/renderer/src/pages/settings/DataSettings/ExportMenuSettings.tsx b/src/renderer/src/pages/settings/DataSettings/ExportMenuSettings.tsx index e5215915ed..120e20c7ff 100644 --- a/src/renderer/src/pages/settings/DataSettings/ExportMenuSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/ExportMenuSettings.tsx @@ -84,7 +84,6 @@ const ExportMenuOptions: FC = () => { {t('settings.data.export_menu.docx')} handleToggleOption('docx', checked)} /> - @@ -94,6 +93,12 @@ const ExportMenuOptions: FC = () => { onChange={(checked) => handleToggleOption('plain_text', checked)} /> + + + + {t('settings.data.export_menu.notes')} + handleToggleOption('notes', checked)} /> + ) } diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index c580045cca..36d6be8daa 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -1824,6 +1824,10 @@ const migrateConfig = { if (state.settings && state.settings.showWorkspace === undefined) { state.settings.showWorkspace = true } + + if (state.settings && state.settings.exportMenuOptions.notes === undefined) { + state.settings.exportMenuOptions.notes = true + } return state } } diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index bce2750c0c..0860ed4eb0 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -187,6 +187,7 @@ export interface SettingsState { siyuan: boolean docx: boolean plain_text: boolean + notes: boolean } // OpenAI openAI: { @@ -345,7 +346,8 @@ export const initialState: SettingsState = { obsidian: true, siyuan: true, docx: true, - plain_text: true + plain_text: true, + notes: true }, // OpenAI openAI: { diff --git a/src/renderer/src/utils/export.ts b/src/renderer/src/utils/export.ts index 61b9696f33..5f50ea7b2e 100644 --- a/src/renderer/src/utils/export.ts +++ b/src/renderer/src/utils/export.ts @@ -1,10 +1,12 @@ import { Client } from '@notionhq/client' import i18n from '@renderer/i18n' +import { NotesService } from '@renderer/pages/notes/utils/NotesService' import { getMessageTitle } from '@renderer/services/MessagesService' import store from '@renderer/store' import { setExportState } from '@renderer/store/runtime' import type { Topic } from '@renderer/types' import type { Message } from '@renderer/types/newMessage' +import { NotesTreeNode } from '@renderer/types/note' import { removeSpecialCharactersForFileName } from '@renderer/utils/file' import { convertMathFormula, markdownToPlainText } from '@renderer/utils/markdown' import { getCitationContent, getMainTextContent, getThinkingContent } from '@renderer/utils/messageUtils/find' @@ -736,3 +738,55 @@ async function createSiyuanDoc( return data.data } + +/** + * 导出消息到笔记工作区 + * @returns 创建的笔记节点 + * @param title + * @param content + */ +export const exportMessageToNotes = async (title: string, content: string): Promise => { + try { + const note = await NotesService.createNote(title, content) + + window.message.success({ + content: i18n.t('message.success.notes.export'), + key: 'notes-export-success' + }) + + return note + } catch (error) { + console.error('导出到笔记失败:', error) + window.message.error({ + content: i18n.t('message.error.notes.export'), + key: 'notes-export-error' + }) + return null + } +} + +/** + * 导出话题到笔记工作区 + * @param topic 要导出的话题 + * @returns 创建的笔记节点 + */ +export const exportTopicToNotes = async (topic: Topic): Promise => { + try { + const content = await topicToMarkdown(topic) + const note = await NotesService.createNote(topic.name, content) + + window.message.success({ + content: i18n.t('message.success.notes.export'), + key: 'notes-export-success' + }) + + return note + } catch (error) { + console.error('导出到笔记失败:', error) + window.message.error({ + content: i18n.t('message.error.notes.export'), + key: 'notes-export-error' + }) + return null + } +}