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 + } +}