mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-29 14:31:35 +08:00
fix: 修复从未打开过的话题导出markdown为空的问题 (#8103)
* feat: 优化导出功能使用 TopicManager 确保消息正确加载 - 移除对 db 的直接依赖,改用 TopicManager.getTopicMessages - 修复从未打开过的话题导出为空的问题 Signed-off-by: luoxu1314 <xiaoluoxu@163.com> * Update export.test.ts 修复相关测试mock使用TopicManager而非db.topics.get,确保测试与新的消息加载方式兼容 * Update export.ts * Update export.test.ts 完善测试 * style(test): 移除多余空行 --------- Signed-off-by: luoxu1314 <xiaoluoxu@163.com> Co-authored-by: GeorgeDong32 <georgedong32@qq.com>
This commit is contained in:
parent
ea40cc7692
commit
186bdb486f
@ -59,11 +59,10 @@ vi.mock('@renderer/utils/messageUtils/find', () => ({
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/databases', () => ({
|
||||
default: {
|
||||
topics: {
|
||||
get: vi.fn()
|
||||
}
|
||||
// Mock TopicManager for dynamic import
|
||||
vi.mock('@renderer/hooks/useTopic', () => ({
|
||||
TopicManager: {
|
||||
getTopicMessages: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
@ -76,7 +75,6 @@ vi.mock('@renderer/utils/markdown', async (importOriginal) => {
|
||||
})
|
||||
|
||||
// Import the functions to test AFTER setting up mocks
|
||||
import db from '@renderer/databases'
|
||||
import { Topic } from '@renderer/types'
|
||||
import { markdownToPlainText } from '@renderer/utils/markdown'
|
||||
|
||||
@ -415,6 +413,18 @@ describe('export', () => {
|
||||
})
|
||||
|
||||
describe('formatMessageAsPlainText (via topicToPlainText)', () => {
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks()
|
||||
vi.resetModules()
|
||||
|
||||
// Re-mock TopicManager for this test suite
|
||||
vi.doMock('@renderer/hooks/useTopic', () => ({
|
||||
TopicManager: {
|
||||
getTopicMessages: vi.fn()
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
it('should format user and assistant messages correctly to plain text with roles', async () => {
|
||||
const userMsg = createMessage({ role: 'user', id: 'u_plain_formatted' }, [
|
||||
{ type: MessageBlockType.MAIN_TEXT, content: '# User Content Formatted' }
|
||||
@ -430,7 +440,9 @@ describe('export', () => {
|
||||
createdAt: '',
|
||||
updatedAt: ''
|
||||
}
|
||||
;(db.topics.get as any).mockResolvedValue({ messages: [userMsg, assistantMsg] })
|
||||
// Mock TopicManager.getTopicMessages to return the expected messages
|
||||
const { TopicManager } = await import('@renderer/hooks/useTopic')
|
||||
;(TopicManager.getTopicMessages as any).mockResolvedValue([userMsg, assistantMsg])
|
||||
// Specific mock for this test to check formatting
|
||||
;(markdownToPlainText as any).mockImplementation((str: string) => str.replace(/[#*]/g, ''))
|
||||
|
||||
@ -501,6 +513,18 @@ describe('export', () => {
|
||||
})
|
||||
|
||||
describe('messagesToPlainText (via topicToPlainText)', () => {
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks() // Clear mocks before each test in this suite
|
||||
vi.resetModules() // Reset module cache
|
||||
|
||||
// Re-import and re-mock TopicManager to ensure clean state
|
||||
vi.doMock('@renderer/hooks/useTopic', () => ({
|
||||
TopicManager: {
|
||||
getTopicMessages: vi.fn()
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
it('should join multiple formatted plain text messages with double newlines', async () => {
|
||||
const msg1 = createMessage({ role: 'user', id: 'm_plain1_formatted' }, [
|
||||
{ type: MessageBlockType.MAIN_TEXT, content: 'Msg1 Formatted' }
|
||||
@ -516,7 +540,9 @@ describe('export', () => {
|
||||
createdAt: '',
|
||||
updatedAt: ''
|
||||
}
|
||||
;(db.topics.get as any).mockResolvedValue({ messages: [msg1, msg2] })
|
||||
// Mock TopicManager.getTopicMessages to return the expected messages
|
||||
const { TopicManager } = await import('@renderer/hooks/useTopic')
|
||||
;(TopicManager.getTopicMessages as any).mockResolvedValue([msg1, msg2])
|
||||
;(markdownToPlainText as any).mockImplementation((str: string) => str) // Pass-through
|
||||
|
||||
const plainText = await topicToPlainText(testTopic)
|
||||
@ -549,7 +575,9 @@ describe('export', () => {
|
||||
createdAt: '',
|
||||
updatedAt: ''
|
||||
}
|
||||
;(db.topics.get as any).mockResolvedValue({ messages: [msgWithEmpty] })
|
||||
// Mock TopicManager.getTopicMessages to return the expected messages
|
||||
const { TopicManager } = await import('@renderer/hooks/useTopic')
|
||||
;(TopicManager.getTopicMessages as any).mockResolvedValue([msgWithEmpty])
|
||||
;(markdownToPlainText as any).mockImplementation((str: string) => str)
|
||||
|
||||
const result = await topicToPlainText(testTopic)
|
||||
@ -568,7 +596,9 @@ describe('export', () => {
|
||||
createdAt: '',
|
||||
updatedAt: ''
|
||||
}
|
||||
;(db.topics.get as any).mockResolvedValue({ messages: [msgWithSpecial] })
|
||||
// Mock TopicManager.getTopicMessages to return the expected messages
|
||||
const { TopicManager } = await import('@renderer/hooks/useTopic')
|
||||
;(TopicManager.getTopicMessages as any).mockResolvedValue([msgWithSpecial])
|
||||
;(markdownToPlainText as any).mockImplementation((str: string) => str)
|
||||
|
||||
const result = await topicToPlainText(testTopic)
|
||||
@ -592,11 +622,12 @@ describe('export', () => {
|
||||
createdAt: '',
|
||||
updatedAt: ''
|
||||
}
|
||||
;(db.topics.get as any).mockResolvedValue({ messages: [msg1, msg2] })
|
||||
// Mock TopicManager.getTopicMessages to return the expected messages
|
||||
const { TopicManager } = await import('@renderer/hooks/useTopic')
|
||||
;(TopicManager.getTopicMessages as any).mockResolvedValue([msg1, msg2])
|
||||
;(markdownToPlainText as any).mockImplementation((str: string) => str.replace(/[#*_]/g, ''))
|
||||
|
||||
const result = await topicToPlainText(testTopic)
|
||||
expect(db.topics.get).toHaveBeenCalledWith('topic1_plain')
|
||||
expect(markdownToPlainText).toHaveBeenCalledWith('# Topic One')
|
||||
expect(markdownToPlainText).toHaveBeenCalledWith('**Hello**')
|
||||
expect(markdownToPlainText).toHaveBeenCalledWith('_World_')
|
||||
@ -612,7 +643,9 @@ describe('export', () => {
|
||||
createdAt: '',
|
||||
updatedAt: ''
|
||||
}
|
||||
;(db.topics.get as any).mockResolvedValue({ messages: [] })
|
||||
// Mock TopicManager.getTopicMessages to return empty array
|
||||
const { TopicManager } = await import('@renderer/hooks/useTopic')
|
||||
;(TopicManager.getTopicMessages as any).mockResolvedValue([])
|
||||
;(markdownToPlainText as any).mockImplementation((str: string) => str.replace(/[#*_]/g, ''))
|
||||
|
||||
const result = await topicToPlainText(testTopic)
|
||||
@ -629,10 +662,12 @@ describe('export', () => {
|
||||
createdAt: '',
|
||||
updatedAt: ''
|
||||
}
|
||||
;(db.topics.get as any).mockResolvedValue(null)
|
||||
// Mock TopicManager.getTopicMessages to return empty array for null case
|
||||
const { TopicManager } = await import('@renderer/hooks/useTopic')
|
||||
;(TopicManager.getTopicMessages as any).mockResolvedValue([])
|
||||
|
||||
const result = await topicToPlainText(testTopic)
|
||||
expect(result).toBe('')
|
||||
expect(result).toBe('Null Messages Topic')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { Client } from '@notionhq/client'
|
||||
import db from '@renderer/databases'
|
||||
import i18n from '@renderer/i18n'
|
||||
import { getMessageTitle } from '@renderer/services/MessagesService'
|
||||
import store from '@renderer/store'
|
||||
@ -13,6 +12,17 @@ import { markdownToBlocks } from '@tryfabric/martian'
|
||||
import dayjs from 'dayjs'
|
||||
import { appendBlocks } from 'notion-helper' // 引入 notion-helper 的 appendBlocks 函数
|
||||
|
||||
/**
|
||||
* 获取话题的消息列表,使用TopicManager确保消息被正确加载
|
||||
* 这样可以避免从未打开过的话题导出为空的问题
|
||||
* @param topicId 话题ID
|
||||
* @returns 话题消息列表
|
||||
*/
|
||||
async function fetchTopicMessages(topicId: string): Promise<Message[]> {
|
||||
const { TopicManager } = await import('@renderer/hooks/useTopic')
|
||||
return await TopicManager.getTopicMessages(topicId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 从消息内容中提取标题,限制长度并处理换行和标点符号。用于导出功能。
|
||||
* @param {string} str 输入字符串
|
||||
@ -143,28 +153,26 @@ const messagesToPlainText = (messages: Message[]): string => {
|
||||
|
||||
export const topicToMarkdown = async (topic: Topic, exportReasoning?: boolean) => {
|
||||
const topicName = `# ${topic.name}`
|
||||
const topicMessages = await db.topics.get(topic.id)
|
||||
|
||||
if (topicMessages) {
|
||||
return topicName + '\n\n' + messagesToMarkdown(topicMessages.messages, exportReasoning)
|
||||
const messages = await fetchTopicMessages(topic.id)
|
||||
|
||||
if (messages && messages.length > 0) {
|
||||
return topicName + '\n\n' + messagesToMarkdown(messages, exportReasoning)
|
||||
}
|
||||
|
||||
return ''
|
||||
return topicName
|
||||
}
|
||||
|
||||
export const topicToPlainText = async (topic: Topic): Promise<string> => {
|
||||
const topicName = markdownToPlainText(topic.name).trim()
|
||||
const topicMessages = await db.topics.get(topic.id)
|
||||
|
||||
if (topicMessages && topicMessages.messages.length > 0) {
|
||||
return topicName + '\n\n' + messagesToPlainText(topicMessages.messages)
|
||||
const topicMessages = await fetchTopicMessages(topic.id)
|
||||
|
||||
if (topicMessages && topicMessages.length > 0) {
|
||||
return topicName + '\n\n' + messagesToPlainText(topicMessages)
|
||||
}
|
||||
|
||||
if (topicMessages && topicMessages.messages.length === 0) {
|
||||
return topicName
|
||||
}
|
||||
|
||||
return ''
|
||||
return topicName
|
||||
}
|
||||
|
||||
export const exportTopicAsMarkdown = async (topic: Topic, exportReasoning?: boolean) => {
|
||||
@ -365,9 +373,7 @@ export const exportMessageToNotion = async (title: string, content: string, mess
|
||||
export const exportTopicToNotion = async (topic: Topic) => {
|
||||
const { notionExportReasoning } = store.getState().settings
|
||||
|
||||
// 获取话题消息
|
||||
const topicRecord = await db.topics.get(topic.id)
|
||||
const topicMessages = topicRecord?.messages || []
|
||||
const topicMessages = await fetchTopicMessages(topic.id)
|
||||
|
||||
// 创建话题标题块
|
||||
const titleBlocks = await convertMarkdownToNotionBlocks(`# ${topic.name}`)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user