From 8fbedb2bd064e7dfe366bca86b3db39043b17136 Mon Sep 17 00:00:00 2001 From: shiquda Date: Mon, 31 Mar 2025 23:44:35 +0800 Subject: [PATCH] feat: add support for title generation when exporting single message #3992 --- src/renderer/src/i18n/locales/en-us.json | 9 +++++++-- src/renderer/src/i18n/locales/ja-jp.json | 9 +++++++-- src/renderer/src/i18n/locales/ru-ru.json | 9 +++++++-- src/renderer/src/i18n/locales/zh-cn.json | 7 ++++++- src/renderer/src/i18n/locales/zh-tw.json | 9 +++++++-- .../pages/home/Messages/MessageMenubar.tsx | 13 +++++++------ .../DataSettings/MarkdownExportSettings.tsx | 19 ++++++++++++++++++- src/renderer/src/services/MessagesService.ts | 19 ++++++++++++++++++- src/renderer/src/store/settings.ts | 6 ++++++ src/renderer/src/utils/export.ts | 6 ++++-- 10 files changed, 87 insertions(+), 19 deletions(-) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index fd1885e9..ade0e11b 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -229,7 +229,10 @@ "topics.title": "Topics", "topics.unpinned": "Unpinned Topics", "translate": "Translate", - "topics.export.siyuan": "Export to Siyuan Note" + "topics.export.siyuan": "Export to Siyuan Note", + "topics.export.wait_for_title_naming": "Generating title...", + "topics.export.title_naming_success": "Title generated successfully", + "topics.export.title_naming_failed": "Failed to generate title, using default title" }, "code_block": { "collapse": "Collapse", @@ -919,7 +922,9 @@ "new_folder.button.confirm": "Confirm", "new_folder.button.cancel": "Cancel", "new_folder.button": "New Folder" - } + }, + "message_title.use_topic_naming.title": "Use topic naming model to create titles for exported messages", + "message_title.use_topic_naming.help": "When enabled, use topic naming model to create titles for exported messages. This will also affect all Markdown export methods." }, "display.assistant.title": "Assistant Settings", "display.custom.css": "Custom CSS", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index bbd89caf..a106bd13 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -229,7 +229,10 @@ "topics.title": "トピック", "topics.unpinned": "固定解除", "translate": "翻訳", - "topics.export.siyuan": "思源笔记にエクスポート" + "topics.export.siyuan": "思源笔记にエクスポート", + "topics.export.wait_for_title_naming": "タイトルを生成中...", + "topics.export.title_naming_success": "タイトルの生成に成功しました", + "topics.export.title_naming_failed": "タイトルの生成に失敗しました。デフォルトのタイトルを使用します" }, "code_block": { "collapse": "折りたたむ", @@ -1246,7 +1249,9 @@ }, "title": "ウェブ検索" }, - "general.auto_check_update.title": "自動更新チェックを有効にする" + "general.auto_check_update.title": "自動更新チェックを有効にする", + "message_title.use_topic_naming.title": "エクスポートされたメッセージのタイトル作成にトピック命名モデルを使用", + "message_title.use_topic_naming.help": "有効にすると、エクスポートされたメッセージのタイトル作成にトピック命名モデルを使用します。これはすべてのMarkdownエクスポート方式にも影響します。" }, "translate": { "any.language": "任意の言語", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 6df12b66..caadc7c2 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -229,7 +229,10 @@ "topics.title": "Топики", "topics.unpinned": "Открепленные темы", "translate": "Перевести", - "topics.export.siyuan": "Экспорт в Siyuan Note" + "topics.export.siyuan": "Экспорт в Siyuan Note", + "topics.export.wait_for_title_naming": "Создание заголовка...", + "topics.export.title_naming_success": "Заголовок успешно создан", + "topics.export.title_naming_failed": "Не удалось создать заголовок, используется заголовок по умолчанию" }, "code_block": { "collapse": "Свернуть", @@ -1246,7 +1249,9 @@ }, "title": "Поиск в Интернете" }, - "general.auto_check_update.title": "Включить автоматическую проверку обновлений" + "general.auto_check_update.title": "Включить автоматическую проверку обновлений", + "message_title.use_topic_naming.title": "Использовать модель именования тем для создания заголовков экспортируемых сообщений", + "message_title.use_topic_naming.help": "При включении использует модель именования тем для создания заголовков экспортируемых сообщений. Это также повлияет на все методы экспорта Markdown." }, "translate": { "any.language": "Любой язык", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index c8d45e14..71e8dfe0 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -229,7 +229,10 @@ "topics.title": "话题", "topics.unpinned": "取消固定", "translate": "翻译", - "topics.export.siyuan": "导出到思源笔记" + "topics.export.siyuan": "导出到思源笔记", + "topics.export.wait_for_title_naming": "正在生成标题...", + "topics.export.title_naming_success": "标题生成成功", + "topics.export.title_naming_failed": "标题生成失败,使用默认标题" }, "code_block": { "collapse": "收起", @@ -800,6 +803,8 @@ "markdown_export.path_placeholder": "导出路径", "markdown_export.select": "选择", "markdown_export.title": "Markdown 导出", + "message_title.use_topic_naming.title": "使用话题命名模型为导出的消息创建标题", + "message_title.use_topic_naming.help": "开启后,使用话题命名模型为导出的消息创建标题。该项也会影响所有通过Markdown导出的方式。", "minute_interval_one": "{{count}} 分钟", "minute_interval_other": "{{count}} 分钟", "notion.api_key": "Notion 密钥", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index bf28d0e4..153692bc 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -229,7 +229,10 @@ "topics.title": "話題", "topics.unpinned": "取消固定", "translate": "翻譯", - "topics.export.siyuan": "匯出到思源筆記" + "topics.export.siyuan": "匯出到思源筆記", + "topics.export.wait_for_title_naming": "正在生成標題...", + "topics.export.title_naming_success": "標題生成成功", + "topics.export.title_naming_failed": "標題生成失敗,使用預設標題" }, "code_block": { "collapse": "折疊", @@ -1246,7 +1249,9 @@ }, "title": "網路搜尋" }, - "general.auto_check_update.title": "啟用自動更新檢查" + "general.auto_check_update.title": "啟用自動更新檢查", + "message_title.use_topic_naming.title": "使用話題命名模型為匯出的訊息建立標題", + "message_title.use_topic_naming.help": "開啟後,使用話題命名模型為匯出的訊息建立標題。該項也會影響所有透過Markdown匯出的方式。" }, "translate": { "any.language": "任意語言", diff --git a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx index 220a5bec..f5fa7403 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -198,7 +198,7 @@ const MessageMenubar: FC = (props) => { key: 'image', onClick: async () => { const imageData = await captureScrollableDivAsDataURL(messageContainerRef) - const title = getMessageTitle(message) + const title = await getMessageTitle(message) if (title && imageData) { window.api.file.saveImage(title, imageData) } @@ -211,14 +211,15 @@ const MessageMenubar: FC = (props) => { key: 'word', onClick: async () => { const markdown = messageToMarkdown(message) - window.api.export.toWord(markdown, getMessageTitle(message)) + const title = await getMessageTitle(message) + window.api.export.toWord(markdown, title) } }, { label: t('chat.topics.export.notion'), key: 'notion', onClick: async () => { - const title = getMessageTitle(message) + const title = await getMessageTitle(message) const markdown = messageToMarkdown(message) exportMarkdownToNotion(title, markdown) } @@ -227,7 +228,7 @@ const MessageMenubar: FC = (props) => { label: t('chat.topics.export.yuque'), key: 'yuque', onClick: async () => { - const title = getMessageTitle(message) + const title = await getMessageTitle(message) const markdown = messageToMarkdown(message) exportMarkdownToYuque(title, markdown) } @@ -245,7 +246,7 @@ const MessageMenubar: FC = (props) => { label: t('chat.topics.export.joplin'), key: 'joplin', onClick: async () => { - const title = getMessageTitle(message) + const title = await getMessageTitle(message) const markdown = messageToMarkdown(message) exportMarkdownToJoplin(title, markdown) } @@ -254,7 +255,7 @@ const MessageMenubar: FC = (props) => { label: t('chat.topics.export.siyuan'), key: 'siyuan', onClick: async () => { - const title = getMessageTitle(message) + const title = await getMessageTitle(message) const markdown = messageToMarkdown(message) exportMarkdownToSiyuan(title, markdown) } diff --git a/src/renderer/src/pages/settings/DataSettings/MarkdownExportSettings.tsx b/src/renderer/src/pages/settings/DataSettings/MarkdownExportSettings.tsx index 65eae380..e4c81692 100644 --- a/src/renderer/src/pages/settings/DataSettings/MarkdownExportSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/MarkdownExportSettings.tsx @@ -2,7 +2,11 @@ import { DeleteOutlined, FolderOpenOutlined } from '@ant-design/icons' import { HStack } from '@renderer/components/Layout' import { useTheme } from '@renderer/context/ThemeProvider' import { RootState, useAppDispatch } from '@renderer/store' -import { setForceDollarMathInMarkdown, setmarkdownExportPath } from '@renderer/store/settings' +import { + setForceDollarMathInMarkdown, + setmarkdownExportPath, + setUseTopicNamingForMessageTitle +} from '@renderer/store/settings' import { Button, Switch } from 'antd' import Input from 'antd/es/input/Input' import { FC } from 'react' @@ -18,6 +22,7 @@ const MarkdownExportSettings: FC = () => { const markdownExportPath = useSelector((state: RootState) => state.settings.markdownExportPath) const forceDollarMathInMarkdown = useSelector((state: RootState) => state.settings.forceDollarMathInMarkdown) + const useTopicNamingForMessageTitle = useSelector((state: RootState) => state.settings.useTopicNamingForMessageTitle) const handleSelectFolder = async () => { const path = await window.api.file.selectFolder() @@ -34,6 +39,10 @@ const MarkdownExportSettings: FC = () => { dispatch(setForceDollarMathInMarkdown(checked)) } + const handleToggleTopicNaming = (checked: boolean) => { + dispatch(setUseTopicNamingForMessageTitle(checked)) + } + return ( {t('settings.data.markdown_export.title')} @@ -69,6 +78,14 @@ const MarkdownExportSettings: FC = () => { {t('settings.data.markdown_export.force_dollar_math.help')} + + + {t('settings.data.message_title.use_topic_naming.title')} + + + + {t('settings.data.message_title.use_topic_naming.help')} + ) } diff --git a/src/renderer/src/services/MessagesService.ts b/src/renderer/src/services/MessagesService.ts index 567efaa2..14c024d9 100644 --- a/src/renderer/src/services/MessagesService.ts +++ b/src/renderer/src/services/MessagesService.ts @@ -2,6 +2,7 @@ import SearchPopup from '@renderer/components/Popups/SearchPopup' import { DEFAULT_CONTEXTCOUNT } from '@renderer/config/constant' import { getTopicById } from '@renderer/hooks/useTopic' import i18n from '@renderer/i18n' +import { fetchMessagesSummary } from '@renderer/services/ApiService' import store from '@renderer/store' import { Assistant, Message, Model, Topic } from '@renderer/types' import { getTitleFromString, uuid } from '@renderer/utils' @@ -214,7 +215,22 @@ export function resetAssistantMessage(message: Message, model?: Model): Message } } -export function getMessageTitle(message: Message, length = 30) { +export async function getMessageTitle(message: Message, length = 30): Promise { + // 检查 Redux 设置,若开启话题命名则调用 summaries 方法 + if ((store.getState().settings as any).useTopicNamingForMessageTitle) { + try { + window.message.loading({ content: t('chat.topics.export.wait_for_title_naming'), key: 'message-title-naming' }) + const title = await fetchMessagesSummary({ messages: [message], assistant: {} as Assistant }) + if (title) { + window.message.success({ content: t('chat.topics.export.title_naming_success'), key: 'message-title-naming' }) + return title + } + } catch (e) { + window.message.error({ content: t('chat.topics.export.title_naming_failed'), key: 'message-title-naming' }) + console.error('Failed to generate title using topic naming, downgraded to default logic', e) + } + } + let title = getTitleFromString(message.content, length) if (!title) { @@ -223,6 +239,7 @@ export function getMessageTitle(message: Message, length = 30) { return title } + export function checkRateLimit(assistant: Assistant): boolean { const provider = getAssistantProvider(assistant) diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index f88d135f..44dace68 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -85,6 +85,7 @@ export interface SettingsState { notionPageNameKey: string | null markdownExportPath: string | null forceDollarMathInMarkdown: boolean + useTopicNamingForMessageTitle: boolean thoughtAutoCollapse: boolean notionAutoSplit: boolean notionSplitSize: number @@ -167,6 +168,7 @@ const initialState: SettingsState = { notionPageNameKey: 'Name', markdownExportPath: null, forceDollarMathInMarkdown: false, + useTopicNamingForMessageTitle: false, thoughtAutoCollapse: true, notionAutoSplit: false, notionSplitSize: 90, @@ -372,6 +374,9 @@ const settingsSlice = createSlice({ setForceDollarMathInMarkdown: (state, action: PayloadAction) => { state.forceDollarMathInMarkdown = action.payload }, + setUseTopicNamingForMessageTitle: (state, action: PayloadAction) => { + state.useTopicNamingForMessageTitle = action.payload + }, setThoughtAutoCollapse: (state, action: PayloadAction) => { state.thoughtAutoCollapse = action.payload }, @@ -483,6 +488,7 @@ export const { setNotionPageNameKey, setmarkdownExportPath, setForceDollarMathInMarkdown, + setUseTopicNamingForMessageTitle, setThoughtAutoCollapse, setNotionAutoSplit, setNotionSplitSize, diff --git a/src/renderer/src/utils/export.ts b/src/renderer/src/utils/export.ts index 3583d384..188205c8 100644 --- a/src/renderer/src/utils/export.ts +++ b/src/renderer/src/utils/export.ts @@ -66,7 +66,8 @@ export const exportMessageAsMarkdown = async (message: Message) => { const { markdownExportPath } = store.getState().settings if (!markdownExportPath) { try { - const fileName = removeSpecialCharactersForFileName(getMessageTitle(message)) + '.md' + const title = await getMessageTitle(message) + const fileName = removeSpecialCharactersForFileName(title) + '.md' const markdown = messageToMarkdown(message) const result = await window.api.file.save(fileName, markdown) if (result) { @@ -81,7 +82,8 @@ export const exportMessageAsMarkdown = async (message: Message) => { } else { try { const timestamp = dayjs().format('YYYY-MM-DD-HH-mm-ss') - const fileName = removeSpecialCharactersForFileName(getMessageTitle(message)) + ` ${timestamp}.md` + const title = await getMessageTitle(message) + const fileName = removeSpecialCharactersForFileName(title) + ` ${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' })