feat: add support for title generation when exporting single message

#3992
This commit is contained in:
shiquda 2025-03-31 23:44:35 +08:00 committed by 亢奋猫
parent 750247aef8
commit 8fbedb2bd0
10 changed files with 87 additions and 19 deletions

View File

@ -229,7 +229,10 @@
"topics.title": "Topics", "topics.title": "Topics",
"topics.unpinned": "Unpinned Topics", "topics.unpinned": "Unpinned Topics",
"translate": "Translate", "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": { "code_block": {
"collapse": "Collapse", "collapse": "Collapse",
@ -919,7 +922,9 @@
"new_folder.button.confirm": "Confirm", "new_folder.button.confirm": "Confirm",
"new_folder.button.cancel": "Cancel", "new_folder.button.cancel": "Cancel",
"new_folder.button": "New Folder" "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.assistant.title": "Assistant Settings",
"display.custom.css": "Custom CSS", "display.custom.css": "Custom CSS",

View File

@ -229,7 +229,10 @@
"topics.title": "トピック", "topics.title": "トピック",
"topics.unpinned": "固定解除", "topics.unpinned": "固定解除",
"translate": "翻訳", "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": { "code_block": {
"collapse": "折りたたむ", "collapse": "折りたたむ",
@ -1246,7 +1249,9 @@
}, },
"title": "ウェブ検索" "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": { "translate": {
"any.language": "任意の言語", "any.language": "任意の言語",

View File

@ -229,7 +229,10 @@
"topics.title": "Топики", "topics.title": "Топики",
"topics.unpinned": "Открепленные темы", "topics.unpinned": "Открепленные темы",
"translate": "Перевести", "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": { "code_block": {
"collapse": "Свернуть", "collapse": "Свернуть",
@ -1246,7 +1249,9 @@
}, },
"title": "Поиск в Интернете" "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": { "translate": {
"any.language": "Любой язык", "any.language": "Любой язык",

View File

@ -229,7 +229,10 @@
"topics.title": "话题", "topics.title": "话题",
"topics.unpinned": "取消固定", "topics.unpinned": "取消固定",
"translate": "翻译", "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": { "code_block": {
"collapse": "收起", "collapse": "收起",
@ -800,6 +803,8 @@
"markdown_export.path_placeholder": "导出路径", "markdown_export.path_placeholder": "导出路径",
"markdown_export.select": "选择", "markdown_export.select": "选择",
"markdown_export.title": "Markdown 导出", "markdown_export.title": "Markdown 导出",
"message_title.use_topic_naming.title": "使用话题命名模型为导出的消息创建标题",
"message_title.use_topic_naming.help": "开启后使用话题命名模型为导出的消息创建标题。该项也会影响所有通过Markdown导出的方式。",
"minute_interval_one": "{{count}} 分钟", "minute_interval_one": "{{count}} 分钟",
"minute_interval_other": "{{count}} 分钟", "minute_interval_other": "{{count}} 分钟",
"notion.api_key": "Notion 密钥", "notion.api_key": "Notion 密钥",

View File

@ -229,7 +229,10 @@
"topics.title": "話題", "topics.title": "話題",
"topics.unpinned": "取消固定", "topics.unpinned": "取消固定",
"translate": "翻譯", "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": { "code_block": {
"collapse": "折疊", "collapse": "折疊",
@ -1246,7 +1249,9 @@
}, },
"title": "網路搜尋" "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": { "translate": {
"any.language": "任意語言", "any.language": "任意語言",

View File

@ -198,7 +198,7 @@ const MessageMenubar: FC<Props> = (props) => {
key: 'image', key: 'image',
onClick: async () => { onClick: async () => {
const imageData = await captureScrollableDivAsDataURL(messageContainerRef) const imageData = await captureScrollableDivAsDataURL(messageContainerRef)
const title = getMessageTitle(message) const title = await getMessageTitle(message)
if (title && imageData) { if (title && imageData) {
window.api.file.saveImage(title, imageData) window.api.file.saveImage(title, imageData)
} }
@ -211,14 +211,15 @@ const MessageMenubar: FC<Props> = (props) => {
key: 'word', key: 'word',
onClick: async () => { onClick: async () => {
const markdown = messageToMarkdown(message) 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'), label: t('chat.topics.export.notion'),
key: 'notion', key: 'notion',
onClick: async () => { onClick: async () => {
const title = getMessageTitle(message) const title = await getMessageTitle(message)
const markdown = messageToMarkdown(message) const markdown = messageToMarkdown(message)
exportMarkdownToNotion(title, markdown) exportMarkdownToNotion(title, markdown)
} }
@ -227,7 +228,7 @@ const MessageMenubar: FC<Props> = (props) => {
label: t('chat.topics.export.yuque'), label: t('chat.topics.export.yuque'),
key: 'yuque', key: 'yuque',
onClick: async () => { onClick: async () => {
const title = getMessageTitle(message) const title = await getMessageTitle(message)
const markdown = messageToMarkdown(message) const markdown = messageToMarkdown(message)
exportMarkdownToYuque(title, markdown) exportMarkdownToYuque(title, markdown)
} }
@ -245,7 +246,7 @@ const MessageMenubar: FC<Props> = (props) => {
label: t('chat.topics.export.joplin'), label: t('chat.topics.export.joplin'),
key: 'joplin', key: 'joplin',
onClick: async () => { onClick: async () => {
const title = getMessageTitle(message) const title = await getMessageTitle(message)
const markdown = messageToMarkdown(message) const markdown = messageToMarkdown(message)
exportMarkdownToJoplin(title, markdown) exportMarkdownToJoplin(title, markdown)
} }
@ -254,7 +255,7 @@ const MessageMenubar: FC<Props> = (props) => {
label: t('chat.topics.export.siyuan'), label: t('chat.topics.export.siyuan'),
key: 'siyuan', key: 'siyuan',
onClick: async () => { onClick: async () => {
const title = getMessageTitle(message) const title = await getMessageTitle(message)
const markdown = messageToMarkdown(message) const markdown = messageToMarkdown(message)
exportMarkdownToSiyuan(title, markdown) exportMarkdownToSiyuan(title, markdown)
} }

View File

@ -2,7 +2,11 @@ import { DeleteOutlined, FolderOpenOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { RootState, useAppDispatch } from '@renderer/store' 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 { Button, Switch } from 'antd'
import Input from 'antd/es/input/Input' import Input from 'antd/es/input/Input'
import { FC } from 'react' import { FC } from 'react'
@ -18,6 +22,7 @@ const MarkdownExportSettings: FC = () => {
const markdownExportPath = useSelector((state: RootState) => state.settings.markdownExportPath) const markdownExportPath = useSelector((state: RootState) => state.settings.markdownExportPath)
const forceDollarMathInMarkdown = useSelector((state: RootState) => state.settings.forceDollarMathInMarkdown) const forceDollarMathInMarkdown = useSelector((state: RootState) => state.settings.forceDollarMathInMarkdown)
const useTopicNamingForMessageTitle = useSelector((state: RootState) => state.settings.useTopicNamingForMessageTitle)
const handleSelectFolder = async () => { const handleSelectFolder = async () => {
const path = await window.api.file.selectFolder() const path = await window.api.file.selectFolder()
@ -34,6 +39,10 @@ const MarkdownExportSettings: FC = () => {
dispatch(setForceDollarMathInMarkdown(checked)) dispatch(setForceDollarMathInMarkdown(checked))
} }
const handleToggleTopicNaming = (checked: boolean) => {
dispatch(setUseTopicNamingForMessageTitle(checked))
}
return ( return (
<SettingGroup theme={theme}> <SettingGroup theme={theme}>
<SettingTitle>{t('settings.data.markdown_export.title')}</SettingTitle> <SettingTitle>{t('settings.data.markdown_export.title')}</SettingTitle>
@ -69,6 +78,14 @@ const MarkdownExportSettings: FC = () => {
<SettingRow> <SettingRow>
<SettingHelpText>{t('settings.data.markdown_export.force_dollar_math.help')}</SettingHelpText> <SettingHelpText>{t('settings.data.markdown_export.force_dollar_math.help')}</SettingHelpText>
</SettingRow> </SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.message_title.use_topic_naming.title')}</SettingRowTitle>
<Switch checked={useTopicNamingForMessageTitle} onChange={handleToggleTopicNaming} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.message_title.use_topic_naming.help')}</SettingHelpText>
</SettingRow>
</SettingGroup> </SettingGroup>
) )
} }

View File

@ -2,6 +2,7 @@ import SearchPopup from '@renderer/components/Popups/SearchPopup'
import { DEFAULT_CONTEXTCOUNT } from '@renderer/config/constant' import { DEFAULT_CONTEXTCOUNT } from '@renderer/config/constant'
import { getTopicById } from '@renderer/hooks/useTopic' import { getTopicById } from '@renderer/hooks/useTopic'
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
import { fetchMessagesSummary } from '@renderer/services/ApiService'
import store from '@renderer/store' import store from '@renderer/store'
import { Assistant, Message, Model, Topic } from '@renderer/types' import { Assistant, Message, Model, Topic } from '@renderer/types'
import { getTitleFromString, uuid } from '@renderer/utils' 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<string> {
// 检查 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) let title = getTitleFromString(message.content, length)
if (!title) { if (!title) {
@ -223,6 +239,7 @@ export function getMessageTitle(message: Message, length = 30) {
return title return title
} }
export function checkRateLimit(assistant: Assistant): boolean { export function checkRateLimit(assistant: Assistant): boolean {
const provider = getAssistantProvider(assistant) const provider = getAssistantProvider(assistant)

View File

@ -85,6 +85,7 @@ export interface SettingsState {
notionPageNameKey: string | null notionPageNameKey: string | null
markdownExportPath: string | null markdownExportPath: string | null
forceDollarMathInMarkdown: boolean forceDollarMathInMarkdown: boolean
useTopicNamingForMessageTitle: boolean
thoughtAutoCollapse: boolean thoughtAutoCollapse: boolean
notionAutoSplit: boolean notionAutoSplit: boolean
notionSplitSize: number notionSplitSize: number
@ -167,6 +168,7 @@ const initialState: SettingsState = {
notionPageNameKey: 'Name', notionPageNameKey: 'Name',
markdownExportPath: null, markdownExportPath: null,
forceDollarMathInMarkdown: false, forceDollarMathInMarkdown: false,
useTopicNamingForMessageTitle: false,
thoughtAutoCollapse: true, thoughtAutoCollapse: true,
notionAutoSplit: false, notionAutoSplit: false,
notionSplitSize: 90, notionSplitSize: 90,
@ -372,6 +374,9 @@ const settingsSlice = createSlice({
setForceDollarMathInMarkdown: (state, action: PayloadAction<boolean>) => { setForceDollarMathInMarkdown: (state, action: PayloadAction<boolean>) => {
state.forceDollarMathInMarkdown = action.payload state.forceDollarMathInMarkdown = action.payload
}, },
setUseTopicNamingForMessageTitle: (state, action: PayloadAction<boolean>) => {
state.useTopicNamingForMessageTitle = action.payload
},
setThoughtAutoCollapse: (state, action: PayloadAction<boolean>) => { setThoughtAutoCollapse: (state, action: PayloadAction<boolean>) => {
state.thoughtAutoCollapse = action.payload state.thoughtAutoCollapse = action.payload
}, },
@ -483,6 +488,7 @@ export const {
setNotionPageNameKey, setNotionPageNameKey,
setmarkdownExportPath, setmarkdownExportPath,
setForceDollarMathInMarkdown, setForceDollarMathInMarkdown,
setUseTopicNamingForMessageTitle,
setThoughtAutoCollapse, setThoughtAutoCollapse,
setNotionAutoSplit, setNotionAutoSplit,
setNotionSplitSize, setNotionSplitSize,

View File

@ -66,7 +66,8 @@ export const exportMessageAsMarkdown = async (message: Message) => {
const { markdownExportPath } = store.getState().settings const { markdownExportPath } = store.getState().settings
if (!markdownExportPath) { if (!markdownExportPath) {
try { try {
const fileName = removeSpecialCharactersForFileName(getMessageTitle(message)) + '.md' const title = await getMessageTitle(message)
const fileName = removeSpecialCharactersForFileName(title) + '.md'
const markdown = messageToMarkdown(message) const markdown = messageToMarkdown(message)
const result = await window.api.file.save(fileName, markdown) const result = await window.api.file.save(fileName, markdown)
if (result) { if (result) {
@ -81,7 +82,8 @@ export const exportMessageAsMarkdown = async (message: Message) => {
} else { } else {
try { try {
const timestamp = dayjs().format('YYYY-MM-DD-HH-mm-ss') 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) const markdown = messageToMarkdown(message)
await window.api.file.write(markdownExportPath + '/' + fileName, markdown) await window.api.file.write(markdownExportPath + '/' + fileName, markdown)
window.message.success({ content: i18n.t('message.success.markdown.export.preconf'), key: 'markdown-success' }) window.message.success({ content: i18n.t('message.success.markdown.export.preconf'), key: 'markdown-success' })