mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 22:10:21 +08:00
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.
This commit is contained in:
parent
9128a8019d
commit
f3fc9a23fe
@ -313,6 +313,7 @@
|
|||||||
"topics.export.md.reason": "导出为 Markdown (包含思考)",
|
"topics.export.md.reason": "导出为 Markdown (包含思考)",
|
||||||
"topics.export.notion": "导出到 Notion",
|
"topics.export.notion": "导出到 Notion",
|
||||||
"topics.export.obsidian": "导出到 Obsidian",
|
"topics.export.obsidian": "导出到 Obsidian",
|
||||||
|
"topics.export.notes": "导出到笔记",
|
||||||
"topics.export.obsidian_atributes": "配置笔记属性",
|
"topics.export.obsidian_atributes": "配置笔记属性",
|
||||||
"topics.export.obsidian_btn": "确定",
|
"topics.export.obsidian_btn": "确定",
|
||||||
"topics.export.obsidian_created": "创建时间",
|
"topics.export.obsidian_created": "创建时间",
|
||||||
@ -713,6 +714,7 @@
|
|||||||
"error.siyuan.no_config": "未配置思源笔记 API 地址或令牌",
|
"error.siyuan.no_config": "未配置思源笔记 API 地址或令牌",
|
||||||
"error.yuque.export": "导出语雀错误,请检查连接状态并对照文档检查配置",
|
"error.yuque.export": "导出语雀错误,请检查连接状态并对照文档检查配置",
|
||||||
"error.yuque.no_config": "未配置语雀 Token 或 知识库 URL",
|
"error.yuque.no_config": "未配置语雀 Token 或 知识库 URL",
|
||||||
|
"error.notes.export": "导出笔记失败",
|
||||||
"group.delete.content": "删除分组消息会删除用户提问和所有助手的回答",
|
"group.delete.content": "删除分组消息会删除用户提问和所有助手的回答",
|
||||||
"group.delete.title": "删除分组消息",
|
"group.delete.title": "删除分组消息",
|
||||||
"ignore.knowledge.base": "联网模式开启,忽略知识库",
|
"ignore.knowledge.base": "联网模式开启,忽略知识库",
|
||||||
@ -747,6 +749,7 @@
|
|||||||
"success.notion.export": "成功导出到 Notion",
|
"success.notion.export": "成功导出到 Notion",
|
||||||
"success.siyuan.export": "导出到思源笔记成功",
|
"success.siyuan.export": "导出到思源笔记成功",
|
||||||
"success.yuque.export": "成功导出到语雀",
|
"success.yuque.export": "成功导出到语雀",
|
||||||
|
"success.notes.export": "成功导出到笔记",
|
||||||
"switch.disabled": "请等待当前回复完成后操作",
|
"switch.disabled": "请等待当前回复完成后操作",
|
||||||
"tools": {
|
"tools": {
|
||||||
"abort_failed": "工具调用中断失败",
|
"abort_failed": "工具调用中断失败",
|
||||||
@ -1405,7 +1408,8 @@
|
|||||||
"plain_text": "复制为纯文本",
|
"plain_text": "复制为纯文本",
|
||||||
"siyuan": "导出到思源笔记",
|
"siyuan": "导出到思源笔记",
|
||||||
"title": "导出菜单设置",
|
"title": "导出菜单设置",
|
||||||
"yuque": "导出到语雀"
|
"yuque": "导出到语雀",
|
||||||
|
"notes": "导出到笔记"
|
||||||
},
|
},
|
||||||
"hour_interval_one": "{{count}} 小时",
|
"hour_interval_one": "{{count}} 小时",
|
||||||
"hour_interval_other": "{{count}} 小时",
|
"hour_interval_other": "{{count}} 小时",
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import {
|
|||||||
exportMarkdownToSiyuan,
|
exportMarkdownToSiyuan,
|
||||||
exportMarkdownToYuque,
|
exportMarkdownToYuque,
|
||||||
exportMessageAsMarkdown,
|
exportMessageAsMarkdown,
|
||||||
|
exportMessageToNotes,
|
||||||
exportMessageToNotion,
|
exportMessageToNotion,
|
||||||
messageToMarkdown
|
messageToMarkdown
|
||||||
} from '@renderer/utils/export'
|
} from '@renderer/utils/export'
|
||||||
@ -322,6 +323,15 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
const markdown = messageToMarkdown(message)
|
const markdown = messageToMarkdown(message)
|
||||||
exportMarkdownToSiyuan(title, markdown)
|
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)
|
].filter(Boolean)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import {
|
|||||||
exportMarkdownToSiyuan,
|
exportMarkdownToSiyuan,
|
||||||
exportMarkdownToYuque,
|
exportMarkdownToYuque,
|
||||||
exportTopicAsMarkdown,
|
exportTopicAsMarkdown,
|
||||||
|
exportTopicToNotes,
|
||||||
exportTopicToNotion,
|
exportTopicToNotion,
|
||||||
topicToMarkdown
|
topicToMarkdown
|
||||||
} from '@renderer/utils/export'
|
} from '@renderer/utils/export'
|
||||||
@ -374,6 +375,13 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
|||||||
const markdown = await topicToMarkdown(topic)
|
const markdown = await topicToMarkdown(topic)
|
||||||
exportMarkdownToSiyuan(topic.name, markdown)
|
exportMarkdownToSiyuan(topic.name, markdown)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
exportMenuOptions.notes && {
|
||||||
|
label: t('chat.topics.export.notes'),
|
||||||
|
key: 'notes',
|
||||||
|
onClick: async () => {
|
||||||
|
exportTopicToNotes(topic)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
].filter(Boolean) as ItemType<MenuItemType>[]
|
].filter(Boolean) as ItemType<MenuItemType>[]
|
||||||
}
|
}
|
||||||
@ -419,6 +427,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
|||||||
exportMenuOptions.obsidian,
|
exportMenuOptions.obsidian,
|
||||||
exportMenuOptions.joplin,
|
exportMenuOptions.joplin,
|
||||||
exportMenuOptions.siyuan,
|
exportMenuOptions.siyuan,
|
||||||
|
exportMenuOptions.notes,
|
||||||
assistants,
|
assistants,
|
||||||
assistant,
|
assistant,
|
||||||
updateTopic,
|
updateTopic,
|
||||||
@ -533,23 +542,28 @@ const TopicListItem = styled.div`
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
|
||||||
width: calc(var(--assistants-width) - 20px);
|
width: calc(var(--assistants-width) - 20px);
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
color: var(--color-text-3);
|
color: var(--color-text-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-list-item-hover);
|
background-color: var(--color-list-item-hover);
|
||||||
transition: background-color 0.1s;
|
transition: background-color 0.1s;
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background-color: var(--color-list-item);
|
background-color: var(--color-list-item);
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--color-text-2);
|
color: var(--color-text-2);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,7 +84,6 @@ const ExportMenuOptions: FC = () => {
|
|||||||
<SettingRowTitle>{t('settings.data.export_menu.docx')}</SettingRowTitle>
|
<SettingRowTitle>{t('settings.data.export_menu.docx')}</SettingRowTitle>
|
||||||
<Switch checked={exportMenuOptions.docx} onChange={(checked) => handleToggleOption('docx', checked)} />
|
<Switch checked={exportMenuOptions.docx} onChange={(checked) => handleToggleOption('docx', checked)} />
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
@ -94,6 +93,12 @@ const ExportMenuOptions: FC = () => {
|
|||||||
onChange={(checked) => handleToggleOption('plain_text', checked)}
|
onChange={(checked) => handleToggleOption('plain_text', checked)}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
<SettingDivider />
|
||||||
|
|
||||||
|
<SettingRow>
|
||||||
|
<SettingRowTitle>{t('settings.data.export_menu.notes')}</SettingRowTitle>
|
||||||
|
<Switch checked={exportMenuOptions.notes} onChange={(checked) => handleToggleOption('notes', checked)} />
|
||||||
|
</SettingRow>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1824,6 +1824,10 @@ const migrateConfig = {
|
|||||||
if (state.settings && state.settings.showWorkspace === undefined) {
|
if (state.settings && state.settings.showWorkspace === undefined) {
|
||||||
state.settings.showWorkspace = true
|
state.settings.showWorkspace = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.settings && state.settings.exportMenuOptions.notes === undefined) {
|
||||||
|
state.settings.exportMenuOptions.notes = true
|
||||||
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -187,6 +187,7 @@ export interface SettingsState {
|
|||||||
siyuan: boolean
|
siyuan: boolean
|
||||||
docx: boolean
|
docx: boolean
|
||||||
plain_text: boolean
|
plain_text: boolean
|
||||||
|
notes: boolean
|
||||||
}
|
}
|
||||||
// OpenAI
|
// OpenAI
|
||||||
openAI: {
|
openAI: {
|
||||||
@ -345,7 +346,8 @@ export const initialState: SettingsState = {
|
|||||||
obsidian: true,
|
obsidian: true,
|
||||||
siyuan: true,
|
siyuan: true,
|
||||||
docx: true,
|
docx: true,
|
||||||
plain_text: true
|
plain_text: true,
|
||||||
|
notes: true
|
||||||
},
|
},
|
||||||
// OpenAI
|
// OpenAI
|
||||||
openAI: {
|
openAI: {
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { Client } from '@notionhq/client'
|
import { Client } from '@notionhq/client'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
|
import { NotesService } from '@renderer/pages/notes/utils/NotesService'
|
||||||
import { getMessageTitle } from '@renderer/services/MessagesService'
|
import { getMessageTitle } from '@renderer/services/MessagesService'
|
||||||
import store from '@renderer/store'
|
import store from '@renderer/store'
|
||||||
import { setExportState } from '@renderer/store/runtime'
|
import { setExportState } from '@renderer/store/runtime'
|
||||||
import type { Topic } from '@renderer/types'
|
import type { Topic } from '@renderer/types'
|
||||||
import type { Message } from '@renderer/types/newMessage'
|
import type { Message } from '@renderer/types/newMessage'
|
||||||
|
import { NotesTreeNode } from '@renderer/types/note'
|
||||||
import { removeSpecialCharactersForFileName } from '@renderer/utils/file'
|
import { removeSpecialCharactersForFileName } from '@renderer/utils/file'
|
||||||
import { convertMathFormula, markdownToPlainText } from '@renderer/utils/markdown'
|
import { convertMathFormula, markdownToPlainText } from '@renderer/utils/markdown'
|
||||||
import { getCitationContent, getMainTextContent, getThinkingContent } from '@renderer/utils/messageUtils/find'
|
import { getCitationContent, getMainTextContent, getThinkingContent } from '@renderer/utils/messageUtils/find'
|
||||||
@ -736,3 +738,55 @@ async function createSiyuanDoc(
|
|||||||
|
|
||||||
return data.data
|
return data.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出消息到笔记工作区
|
||||||
|
* @returns 创建的笔记节点
|
||||||
|
* @param title
|
||||||
|
* @param content
|
||||||
|
*/
|
||||||
|
export const exportMessageToNotes = async (title: string, content: string): Promise<NotesTreeNode | null> => {
|
||||||
|
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<NotesTreeNode | null> => {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user