feat: enable one-click export for simple markdown exporting (#3137)

* feat: enable one-click export for simple markdown exporting

* feat: optimize ui for simple markdown export
This commit is contained in:
happyZYM 2025-03-11 09:59:54 +08:00 committed by GitHub
parent b9d97e8a35
commit 9b79051ea5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 153 additions and 7 deletions

View File

@ -411,6 +411,8 @@
"error.invalid.enter.model": "Please select a model",
"error.invalid.proxy.url": "Invalid proxy URL",
"error.invalid.webdav": "Invalid WebDAV settings",
"error.markdown.export.preconf": "Failed to export the Markdown file to the preconfigured path",
"error.markdown.export.specified": "Failed to export the Markdown file",
"error.notion.export": "Failed to export to Notion. Please check connection status and configuration according to documentation",
"error.notion.no_api_key": "Notion ApiKey or Notion DatabaseID is not configured",
"error.yuque.export": "Failed to export to Yuque. Please check connection status and configuration according to documentation",
@ -441,6 +443,8 @@
"restore.success": "Restored successfully",
"save.success.title": "Saved successfully",
"searching": "Searching the internet...",
"success.markdown.export.preconf": "Successfully exported the Markdown file to the preconfigured path",
"success.markdown.export.specified": "Successfully exported the Markdown file",
"success.notion.export": "Successfully exported to Notion",
"success.yuque.export": "Successfully exported to Yuque",
"switch.disabled": "Please wait for the current reply to complete",
@ -666,6 +670,11 @@
"hour_interval_other": "{{count}} hours",
"minute_interval_one": "{{count}} minute",
"minute_interval_other": "{{count}} minutes",
"markdown_export.title": "Markdown Export",
"markdown_export.path": "Default Export Path",
"markdown_export.path_placeholder": "Export Path",
"markdown_export.select": "Select",
"markdown_export.help": "If provided, exports will be automatically saved to this path; otherwise, a save dialog will appear.",
"notion.api_key": "Notion API Key",
"notion.api_key_placeholder": "Enter Notion API Key",
"notion.auto_split": "Auto split when exporting",

View File

@ -411,6 +411,8 @@
"error.invalid.enter.model": "モデルを選択してください",
"error.invalid.proxy.url": "無効なプロキシURL",
"error.invalid.webdav": "無効なWebDAV設定",
"error.markdown.export.preconf": "Markdown ファイルを事前設定されたパスにエクスポートできませんでした",
"error.markdown.export.specified": "Markdown ファイルのエクスポートに失敗しました",
"error.notion.export": "Notionへのエクスポートに失敗しました。接続状態と設定を確認してください",
"error.notion.no_api_key": "Notion ApiKey または Notion DatabaseID が設定されていません",
"error.yuque.export": "語雀へのエクスポートに失敗しました。接続状態と設定を確認してください",
@ -441,6 +443,8 @@
"restore.success": "復元に成功しました",
"save.success.title": "保存に成功しました",
"searching": "インターネットで検索中...",
"success.markdown.export.preconf": "Markdown ファイルを事前設定されたパスに正常にエクスポートしました",
"success.markdown.export.specified": "Markdown ファイルを正常にエクスポートしました",
"success.notion.export": "Notionへのエクスポートに成功しました",
"success.yuque.export": "語雀へのエクスポートに成功しました",
"switch.disabled": "現在の応答が完了するまで切り替えを無効にします",
@ -666,6 +670,11 @@
"hour_interval_other": "{{count}} 時間",
"minute_interval_one": "{{count}} 分",
"minute_interval_other": "{{count}} 分",
"markdown_export.title": "Markdown エクスポート",
"markdown_export.path": "デフォルトのエクスポートパス",
"markdown_export.path_placeholder": "エクスポートパス",
"markdown_export.select": "選択",
"markdown_export.help": "入力された場合、エクスポート時に自動的にこのパスに保存されます。未入力の場合、保存ダイアログが表示されます。",
"notion.api_key": "Notion APIキー",
"notion.api_key_placeholder": "Notion APIキーを入力してください",
"notion.auto_split": "내보내기 시 자동 분할",

View File

@ -411,6 +411,8 @@
"error.invalid.enter.model": "Пожалуйста, выберите модель",
"error.invalid.proxy.url": "Неверный URL прокси",
"error.invalid.webdav": "Неверные настройки WebDAV",
"error.markdown.export.preconf": "Не удалось экспортировать файл Markdown в предуказанный путь",
"error.markdown.export.specified": "Не удалось экспортировать файл Markdown",
"error.notion.export": "Ошибка экспорта в Notion, пожалуйста, проверьте состояние подключения и настройки в документации",
"error.notion.no_api_key": "Notion ApiKey или Notion DatabaseID не настроен",
"error.yuque.export": "Ошибка экспорта в Yuque, пожалуйста, проверьте состояние подключения и настройки в документации",
@ -441,6 +443,8 @@
"restore.success": "Успешно восстановлено",
"save.success.title": "Успешно сохранено",
"searching": "Поиск в Интернете...",
"success.markdown.export.preconf": "Файл Markdown успешно экспортирован в предуказанный путь",
"success.markdown.export.specified": "Файл Markdown успешно экспортирован",
"success.notion.export": "Успешный экспорт в Notion",
"success.yuque.export": "Успешный экспорт в Yuque",
"switch.disabled": "Пожалуйста, дождитесь завершения текущего ответа",
@ -666,6 +670,11 @@
"hour_interval_other": "{{count}} часов",
"minute_interval_one": "{{count}} минута",
"minute_interval_other": "{{count}} минут",
"markdown_export.title": "Экспорт в Markdown",
"markdown_export.path": "Путь экспорта по умолчанию",
"markdown_export.path_placeholder": "Путь экспорта",
"markdown_export.select": "Выбрать",
"markdown_export.help": "Если указано, файлы будут автоматически сохраняться в этот путь; в противном случае появится диалоговое окно сохранения.",
"notion.api_key": "Ключ API Notion",
"notion.api_key_placeholder": "Введите ключ API Notion",
"notion.auto_split": "Автоматическое разбиение на страницы при экспорте диалога",

View File

@ -411,6 +411,8 @@
"error.invalid.enter.model": "请选择一个模型",
"error.invalid.proxy.url": "无效的代理地址",
"error.invalid.webdav": "无效的 WebDAV 设置",
"error.markdown.export.preconf": "导出Markdown文件到预先设定的路径失败",
"error.markdown.export.specified": "导出Markdown文件失败",
"error.notion.export": "导出 Notion 错误,请检查连接状态并对照文档检查配置",
"error.notion.no_api_key": "未配置 Notion API Key 或 Notion Database ID",
"error.yuque.export": "导出语雀错误,请检查连接状态并对照文档检查配置",
@ -441,6 +443,8 @@
"restore.success": "恢复成功",
"save.success.title": "保存成功",
"searching": "正在联网搜索...",
"success.markdown.export.preconf": "成功导出Markdown文件到预先设定的路径",
"success.markdown.export.specified": "成功导出Markdown文件",
"success.notion.export": "成功导出到Notion",
"success.yuque.export": "成功导出到语雀",
"switch.disabled": "请等待当前回复完成后操作",
@ -666,6 +670,11 @@
"hour_interval_other": "{{count}} 小时",
"minute_interval_one": "{{count}} 分钟",
"minute_interval_other": "{{count}} 分钟",
"markdown_export.title": "Markdown 导出",
"markdown_export.path": "默认导出路径",
"markdown_export.path_placeholder": "导出路径",
"markdown_export.select": "选择",
"markdown_export.help": "若填入,则每次导出时将自动保存到该路径;否则,将弹出保存对话框",
"notion.api_key": "Notion 密钥",
"notion.api_key_placeholder": "请输入Notion 密钥",
"notion.auto_split": "导出对话时自动分页",

View File

@ -411,6 +411,8 @@
"error.invalid.enter.model": "請選擇一個模型",
"error.invalid.proxy.url": "無效的代理伺服器 URL",
"error.invalid.webdav": "無效的 WebDAV 設定",
"error.markdown.export.preconf": "導出 Markdown 文件到預先設定的路徑失敗",
"error.markdown.export.specified": "導出 Markdown 文件失敗",
"error.notion.export": "匯出 Notion 錯誤,請檢查連接狀態並對照文件檢查設定",
"error.notion.no_api_key": "未設定 Notion API Key 或 Notion Database ID",
"error.yuque.export": "匯出語雀錯誤,請檢查連接狀態並對照文件檢查設定",
@ -441,6 +443,8 @@
"restore.success": "恢復成功",
"save.success.title": "儲存成功",
"searching": "正在網路上搜尋...",
"success.markdown.export.preconf": "成功導出 Markdown 文件到預先設定的路徑",
"success.markdown.export.specified": "成功導出 Markdown 文件",
"success.notion.export": "成功匯出到 Notion",
"success.yuque.export": "成功匯出到語雀",
"switch.disabled": "請等待當前回覆完成",
@ -666,6 +670,11 @@
"hour_interval_other": "{{count}} 小時",
"minute_interval_one": "{{count}} 分鐘",
"minute_interval_other": "{{count}} 分鐘",
"markdown_export.title": "Markdown 匯出",
"markdown_export.path": "預設匯出路徑",
"markdown_export.path_placeholder": "匯出路徑",
"markdown_export.select": "選擇",
"markdown_export.help": "若填入,每次匯出時將自動儲存至該路徑;否則,將彈出儲存對話框。",
"notion.api_key": "Notion 金鑰",
"notion.api_key_placeholder": "請輸入 Notion 金鑰",
"notion.auto_split": "匯出對話時自動分頁",

View File

@ -1,4 +1,10 @@
import { FileSearchOutlined, FolderOpenOutlined, InfoCircleOutlined, SaveOutlined } from '@ant-design/icons'
import {
DeleteOutlined,
FileSearchOutlined,
FolderOpenOutlined,
InfoCircleOutlined,
SaveOutlined
} from '@ant-design/icons'
import { Client } from '@notionhq/client'
import { HStack } from '@renderer/components/Layout'
import MinApp from '@renderer/components/MinApp'
@ -9,6 +15,7 @@ import { useKnowledgeFiles } from '@renderer/hooks/useKnowledgeFiles'
import { reset } from '@renderer/services/BackupService'
import { RootState, useAppDispatch } from '@renderer/store'
import {
setmarkdownExportPath,
setNotionApiKey,
setNotionAutoSplit,
setNotionDatabaseID,
@ -38,6 +45,56 @@ import {
} from '..'
import WebDavSettings from './WebDavSettings'
// 新增的 MarkdownExportSettings 组件
const MarkdownExportSettings: FC = () => {
const { t } = useTranslation()
const { theme } = useTheme()
const dispatch = useAppDispatch()
const markdownExportPath = useSelector((state: RootState) => state.settings.markdownExportPath)
const handleSelectFolder = async () => {
const path = await window.api.file.selectFolder()
if (path) {
dispatch(setmarkdownExportPath(path))
}
}
const handleClearPath = () => {
dispatch(setmarkdownExportPath(null))
}
return (
<SettingGroup theme={theme}>
<SettingTitle>{t('settings.data.markdown_export.title')}</SettingTitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.markdown_export.path')}</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<Input
type="text"
value={markdownExportPath || ''}
readOnly
style={{ width: 250 }}
placeholder={t('settings.data.markdown_export.path_placeholder')}
suffix={
markdownExportPath ? (
<DeleteOutlined onClick={handleClearPath} style={{ color: 'var(--color-error)', cursor: 'pointer' }} />
) : null
}
/>
<Button onClick={handleSelectFolder} icon={<FolderOpenOutlined />}>
{t('settings.data.markdown_export.select')}
</Button>
</HStack>
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.markdown_export.help')}</SettingHelpText>
</SettingRow>
</SettingGroup>
)
}
// 新增的 NotionSettings 组件
const NotionSettings: FC = () => {
const { t } = useTranslation()
@ -382,6 +439,7 @@ const DataSettings: FC = () => {
<SettingGroup theme={theme}>
<WebDavSettings />
</SettingGroup>
<MarkdownExportSettings />
<NotionSettings />
<YuqueSettings />
<SettingGroup theme={theme}>

View File

@ -72,6 +72,7 @@ export interface SettingsState {
notionDatabaseID: string | null
notionApiKey: string | null
notionPageNameKey: string | null
markdownExportPath: string | null
thoughtAutoCollapse: boolean
notionAutoSplit: boolean
notionSplitSize: number
@ -136,6 +137,7 @@ const initialState: SettingsState = {
notionDatabaseID: '',
notionApiKey: '',
notionPageNameKey: 'Name',
markdownExportPath: null,
thoughtAutoCollapse: true,
notionAutoSplit: false,
notionSplitSize: 90,
@ -310,6 +312,9 @@ const settingsSlice = createSlice({
setNotionPageNameKey: (state, action: PayloadAction<string>) => {
state.notionPageNameKey = action.payload
},
setmarkdownExportPath: (state, action: PayloadAction<string | null>) => {
state.markdownExportPath = action.payload
},
setThoughtAutoCollapse: (state, action: PayloadAction<boolean>) => {
state.thoughtAutoCollapse = action.payload
},
@ -384,6 +389,7 @@ export const {
setNotionDatabaseID,
setNotionApiKey,
setNotionPageNameKey,
setmarkdownExportPath,
setThoughtAutoCollapse,
setNotionAutoSplit,
setNotionSplitSize,

View File

@ -6,6 +6,7 @@ import store from '@renderer/store'
import { setExportState } from '@renderer/store/runtime'
import { Message, Topic } from '@renderer/types'
import { removeSpecialCharactersForFileName } from '@renderer/utils/index'
import dayjs from 'dayjs'
export const messageToMarkdown = (message: Message) => {
const roleText = message.role === 'user' ? '🧑‍💻 User' : '🤖 Assistant'
@ -31,15 +32,51 @@ export const topicToMarkdown = async (topic: Topic) => {
}
export const exportTopicAsMarkdown = async (topic: Topic) => {
const fileName = removeSpecialCharactersForFileName(topic.name) + '.md'
const markdown = await topicToMarkdown(topic)
window.api.file.save(fileName, markdown)
const { markdownExportPath } = store.getState().settings
if (!markdownExportPath) {
try {
const fileName = removeSpecialCharactersForFileName(topic.name) + '.md'
const markdown = await topicToMarkdown(topic)
await window.api.file.save(fileName, markdown)
window.message.success({ content: i18n.t('message.success.markdown.export.specified'), key: 'markdown-success' })
} catch (error: any) {
window.message.error({ content: i18n.t('message.error.markdown.export.specified'), key: 'markdown-error' })
}
} else {
try {
const timestamp = dayjs().format('YYYY-MM-DD-HH-mm-ss')
const fileName = removeSpecialCharactersForFileName(topic.name) + ` ${timestamp}.md`
const markdown = await topicToMarkdown(topic)
await window.api.file.write(markdownExportPath + '/' + fileName, markdown)
window.message.success({ content: i18n.t('message.success.markdown.export.preconf'), key: 'markdown-success' })
} catch (error: any) {
window.message.error({ content: i18n.t('message.error.markdown.export.preconf'), key: 'markdown error' })
}
}
}
export const exportMessageAsMarkdown = async (message: Message) => {
const fileName = getMessageTitle(message) + '.md'
const markdown = messageToMarkdown(message)
window.api.file.save(fileName, markdown)
const { markdownExportPath } = store.getState().settings
if (!markdownExportPath) {
try {
const fileName = removeSpecialCharactersForFileName(getMessageTitle(message)) + '.md'
const markdown = messageToMarkdown(message)
await window.api.file.save(fileName, markdown)
window.message.success({ content: i18n.t('message.success.markdown.export.specified'), key: 'markdown-success' })
} catch (error: any) {
window.message.error({ content: i18n.t('message.error.markdown.export.specified'), key: 'markdown error' })
}
} else {
try {
const timestamp = dayjs().format('YYYY-MM-DD-HH-mm-ss')
const fileName = removeSpecialCharactersForFileName(getMessageTitle(message)) + ` ${timestamp}.md`
const markdown = messageToMarkdown(message)
await window.api.file.write(markdownExportPath + '/' + fileName, markdown)
window.message.success({ content: i18n.t('message.success.markdown.export.preconf'), key: 'markdown-success' })
} catch (error: any) {
window.message.error({ content: i18n.t('message.error.markdown.export.preconf'), key: 'markdown-error' })
}
}
}
// 修改 splitNotionBlocks 函数