feat(Topic): Implement auto-renaming topic with enhanced logic

- Add `autoRenameTopic` function in useTopic hook
- Support automatic topic naming with or without AI summary
- Integrate with store and event system for dynamic topic renaming
- Remove local implementation of auto-rename in Messages component
This commit is contained in:
kangfenmao 2025-03-11 20:44:06 +08:00
parent 74567d5e17
commit 151a08d0dd
3 changed files with 44 additions and 47 deletions

View File

@ -1,20 +1,25 @@
import db from '@renderer/databases'
import i18n from '@renderer/i18n'
import { deleteMessageFiles } from '@renderer/services/MessagesService'
import store from '@renderer/store'
import { updateTopic } from '@renderer/store/assistants'
import { prepareTopicMessages } from '@renderer/store/messages'
import { Assistant, Topic } from '@renderer/types'
import { find } from 'lodash'
import { find, isEmpty } from 'lodash'
import { useEffect, useState } from 'react'
import { useAssistant } from './useAssistant'
import { getStoreSetting } from './useSettings'
let _activeTopic: Topic
let _setActiveTopic: (topic: Topic) => void
export function useActiveTopic(_assistant: Assistant, topic?: Topic) {
const { assistant } = useAssistant(_assistant.id)
const [activeTopic, setActiveTopic] = useState(topic || _activeTopic || assistant?.topics[0])
_activeTopic = activeTopic
_setActiveTopic = setActiveTopic
useEffect(() => {
if (activeTopic) {
@ -48,6 +53,35 @@ export async function getTopicById(topicId: string) {
return { ...topic, messages } as Topic
}
export const autoRenameTopic = async (assistant: Assistant, topicId: string) => {
const topic = await getTopicById(topicId)
const enableTopicNaming = getStoreSetting('enableTopicNaming')
if (isEmpty(topic.messages)) {
return
}
if (!enableTopicNaming) {
const topicName = topic.messages[0]?.content.substring(0, 50)
if (topicName) {
const data = { ...topic, name: topicName } as Topic
_setActiveTopic(data)
store.dispatch(updateTopic({ assistantId: assistant.id, topic: data }))
}
return
}
if (topic && topic.name === i18n.t('chat.default.topic.name') && topic.messages.length >= 2) {
const { fetchMessagesSummary } = await import('@renderer/services/ApiService')
const summaryText = await fetchMessagesSummary({ messages: topic.messages, assistant })
if (summaryText) {
const data = { ...topic, name: summaryText }
_setActiveTopic(data)
store.dispatch(updateTopic({ assistantId: assistant.id, topic: data }))
}
}
}
// Convert class to object with functions since class only has static methods
// 只有静态方法,没必要用class可以export {}
export const TopicManager = {

View File

@ -5,8 +5,7 @@ import { useAssistant } from '@renderer/hooks/useAssistant'
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
import { useSettings } from '@renderer/hooks/useSettings'
import { useShortcut } from '@renderer/hooks/useShortcuts'
import { getTopic } from '@renderer/hooks/useTopic'
import { fetchMessagesSummary } from '@renderer/services/ApiService'
import { autoRenameTopic, getTopic } from '@renderer/hooks/useTopic'
import { getDefaultTopic } from '@renderer/services/AssistantService'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { getContextCount, getGroupedMessages, getUserMessage } from '@renderer/services/MessagesService'
@ -19,7 +18,7 @@ import {
removeSpecialCharactersForFileName,
runAsyncFunction
} from '@renderer/utils'
import { flatten, isEmpty, last, take } from 'lodash'
import { flatten, last, take } from 'lodash'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import InfiniteScroll from 'react-infinite-scroll-component'
@ -39,7 +38,7 @@ interface MessagesProps {
const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic }) => {
const { t } = useTranslation()
const { showTopics, topicPosition, showAssistants, enableTopicNaming } = useSettings()
const { showTopics, topicPosition, showAssistants } = useSettings()
const { updateTopic, addTopic } = useAssistant(assistant.id)
const dispatch = useAppDispatch()
const containerRef = useRef<HTMLDivElement>(null)
@ -67,36 +66,6 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic })
setTimeout(() => containerRef.current?.scrollTo({ top: containerRef.current.scrollHeight, behavior: 'auto' }), 50)
}, [])
const autoRenameTopic = useCallback(async () => {
const _topic = getTopic(assistant, topic.id)
if (isEmpty(messages)) {
return
}
const filteredMessages = messages.filter((m) => m.status === 'success')
if (!enableTopicNaming) {
const topicName = filteredMessages[0]?.content.substring(0, 50)
if (topicName) {
const data = { ..._topic, name: topicName } as Topic
setActiveTopic(data)
updateTopic(data)
}
return
}
if (_topic && _topic.name === t('chat.default.topic.name') && filteredMessages.length >= 2) {
const summaryText = await fetchMessagesSummary({ messages: filteredMessages, assistant })
if (summaryText) {
const data = { ..._topic, name: summaryText }
setActiveTopic(data)
updateTopic(data)
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [assistant, topic.id, enableTopicNaming, t, setActiveTopic, updateTopic, messages])
useEffect(() => {
const unsubscribes = [
EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, () => {
@ -157,7 +126,7 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic })
await db.topics.add({ id: newTopic.id, messages: branchMessages })
addTopic(newTopic)
setActiveTopic(newTopic)
autoRenameTopic()
autoRenameTopic(assistant, newTopic.id)
// 由于复制了消息,消息中附带的文件的总数变了,需要更新
const filesArr = branchMessages.map((m) => m.files)
@ -171,17 +140,9 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic })
]
return () => unsubscribes.forEach((unsub) => unsub())
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [assistant, dispatch, scrollToBottom, topic, updateTopic])
useEffect(() => {
const unsubscribes = [EventEmitter.on(EVENT_NAMES.AI_AUTO_RENAME, autoRenameTopic)]
return () => {
for (const unsub of unsubscribes) {
unsub()
}
}
}, [autoRenameTopic])
useEffect(() => {
runAsyncFunction(async () => {
EventEmitter.emit(EVENT_NAMES.ESTIMATED_TOKEN_COUNT, {

View File

@ -1,6 +1,6 @@
import { createAsyncThunk, createSelector, createSlice, type PayloadAction } from '@reduxjs/toolkit'
import db from '@renderer/databases'
import { TopicManager } from '@renderer/hooks/useTopic'
import { autoRenameTopic, TopicManager } from '@renderer/hooks/useTopic'
import { fetchChatCompletion } from '@renderer/services/ApiService'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { getAssistantMessage, resetAssistantMessage } from '@renderer/services/MessagesService'
@ -205,6 +205,7 @@ export const {
} = messagesSlice.actions
const handleResponseMessageUpdate = (
assistant: Assistant,
message: Message,
topicId: string,
dispatch: AppDispatch,
@ -214,7 +215,7 @@ const handleResponseMessageUpdate = (
if (message.status !== 'pending') {
// When message is complete, commit to messages and sync with DB
if (message.status === 'success') {
EventEmitter.emit(EVENT_NAMES.AI_AUTO_RENAME)
autoRenameTopic(assistant, topicId)
}
if (message.status !== 'sending') {
dispatch(commitStreamMessage({ topicId, messageId: message.id }))
@ -347,6 +348,7 @@ export const sendMessage =
// 创建节流函数限制Redux更新频率
// 使用节流函数更新Redux
throttledDispatch(
assistant,
{
...assistantMessage,
...updateMessage