refactor(assistant): enhance assistant creation by automatically adding default topics; streamline default assistant handling

This commit is contained in:
suyao 2025-06-12 21:03:17 +08:00
parent cb1fcf7d2d
commit d41f175a05
No known key found for this signature in database
5 changed files with 29 additions and 81 deletions

View File

@ -1,5 +1,4 @@
import { db } from '@renderer/databases' import { db } from '@renderer/databases'
import { getDefaultTopic } from '@renderer/services/AssistantService'
import { useAppDispatch, useAppSelector } from '@renderer/store' import { useAppDispatch, useAppSelector } from '@renderer/store'
import { import {
addAssistant, addAssistant,
@ -22,7 +21,10 @@ export function useAssistants() {
return { return {
assistants, assistants,
updateAssistants: (assistants: Assistant[]) => dispatch(updateAssistants(assistants)), updateAssistants: (assistants: Assistant[]) => dispatch(updateAssistants(assistants)),
addAssistant: (assistant: Assistant) => dispatch(addAssistant(assistant)), addAssistant: (assistant: Assistant) => {
dispatch(addAssistant(assistant))
dispatch(topicsActions.addDefaultTopic({ assistantId: assistant.id }))
},
removeAssistant: (id: string) => { removeAssistant: (id: string) => {
dispatch(removeAssistant({ id })) dispatch(removeAssistant({ id }))
// Remove all topics for this assistant // Remove all topics for this assistant
@ -85,24 +87,15 @@ export function useTopicsForAssistant(assistantId: string) {
return useAppSelector((state) => selectTopicsForAssistant(state, assistantId)) return useAppSelector((state) => selectTopicsForAssistant(state, assistantId))
} }
/**
*
*/
export function useDefaultAssistant() { export function useDefaultAssistant() {
const defaultAssistant = useAppSelector((state) => state.assistants.defaultAssistant) const defaultAssistant = useAppSelector((state) => state.assistants.defaultAssistant)
const topics = useTopicsForAssistant(defaultAssistant.id)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
// Ensure default assistant has at least one topic
const finalTopics = useMemo(() => {
if (topics.length > 0) {
return topics
}
return [getDefaultTopic(defaultAssistant.id)]
}, [topics, defaultAssistant.id])
return { return {
defaultAssistant: { defaultAssistant,
...defaultAssistant,
topics: finalTopics
},
updateDefaultAssistant: (assistant: Assistant) => dispatch(updateDefaultAssistant({ assistant })) updateDefaultAssistant: (assistant: Assistant) => dispatch(updateDefaultAssistant({ assistant }))
} }
} }

View File

@ -2,6 +2,7 @@ import { DEFAULT_CONTEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
import store from '@renderer/store' import store from '@renderer/store'
import { addAssistant } from '@renderer/store/assistants' import { addAssistant } from '@renderer/store/assistants'
import { topicsActions } from '@renderer/store/topics'
import type { Agent, Assistant, AssistantSettings, Model, Provider, Topic } from '@renderer/types' import type { Agent, Assistant, AssistantSettings, Model, Provider, Topic } from '@renderer/types'
import { uuid } from '@renderer/utils' import { uuid } from '@renderer/utils'
@ -11,7 +12,7 @@ export function getDefaultAssistant(): Assistant {
name: i18n.t('chat.default.name'), name: i18n.t('chat.default.name'),
emoji: '😀', emoji: '😀',
prompt: '', prompt: '',
topics: [getDefaultTopic('default')], topics: [],
messages: [], messages: [],
type: 'assistant', type: 'assistant',
regularPhrases: [] // Added regularPhrases regularPhrases: [] // Added regularPhrases
@ -124,13 +125,13 @@ export async function createAssistantFromAgent(agent: Agent) {
id: assistantId, id: assistantId,
name: agent.name, name: agent.name,
emoji: agent.emoji, emoji: agent.emoji,
topics: [topic],
model: agent.defaultModel, model: agent.defaultModel,
type: 'assistant', type: 'assistant',
regularPhrases: agent.regularPhrases || [] // Ensured regularPhrases regularPhrases: agent.regularPhrases || [] // Ensured regularPhrases
} }
store.dispatch(addAssistant(assistant)) store.dispatch(addAssistant(assistant))
store.dispatch(topicsActions.addTopic({ assistantId, topic }))
window.message.success({ window.message.success({
content: i18n.t('message.assistant.added.content'), content: i18n.t('message.assistant.added.content'),

View File

@ -9,14 +9,9 @@ export interface AssistantsState {
tagsOrder: string[] tagsOrder: string[]
} }
// 之前的两个实例会导致两个助手不一致的问题
// FIXME: 更彻底的办法在这次重构就直接把二者合并了
// Create a single default assistant instance to ensure consistency
const defaultAssistant = getDefaultAssistant()
const initialState: AssistantsState = { const initialState: AssistantsState = {
defaultAssistant: defaultAssistant, defaultAssistant: getDefaultAssistant(), // 这个是模型设置的默认助手
assistants: [defaultAssistant], // Share the same reference assistants: [getDefaultAssistant()], // 这个是主页列表的默认助手
tagsOrder: [] tagsOrder: []
} }
@ -27,21 +22,9 @@ const assistantsSlice = createSlice({
updateDefaultAssistant: (state, action: PayloadAction<{ assistant: Assistant }>) => { updateDefaultAssistant: (state, action: PayloadAction<{ assistant: Assistant }>) => {
const assistant = action.payload.assistant const assistant = action.payload.assistant
state.defaultAssistant = assistant state.defaultAssistant = assistant
// Also update the corresponding assistant in the array
const index = state.assistants.findIndex((a) => a.id === assistant.id)
if (index !== -1) {
state.assistants[index] = assistant
}
}, },
updateAssistants: (state, action: PayloadAction<Assistant[]>) => { updateAssistants: (state, action: PayloadAction<Assistant[]>) => {
state.assistants = action.payload state.assistants = action.payload
// Update defaultAssistant if it exists in the new array
const defaultInArray = action.payload.find((a) => a.id === state.defaultAssistant.id)
if (defaultInArray) {
state.defaultAssistant = defaultInArray
}
}, },
addAssistant: (state, action: PayloadAction<Assistant>) => { addAssistant: (state, action: PayloadAction<Assistant>) => {
state.assistants.push(action.payload) state.assistants.push(action.payload)
@ -52,11 +35,6 @@ const assistantsSlice = createSlice({
updateAssistant: (state, action: PayloadAction<Assistant>) => { updateAssistant: (state, action: PayloadAction<Assistant>) => {
const assistant = action.payload const assistant = action.payload
state.assistants = state.assistants.map((c) => (c.id === assistant.id ? assistant : c)) state.assistants = state.assistants.map((c) => (c.id === assistant.id ? assistant : c))
// Also update defaultAssistant if it's the same assistant
if (state.defaultAssistant.id === assistant.id) {
state.defaultAssistant = assistant
}
}, },
updateAssistantSettings: ( updateAssistantSettings: (
state, state,
@ -91,14 +69,6 @@ const assistantsSlice = createSlice({
} }
: assistant : assistant
) )
// Also update defaultAssistant if it's the same assistant
if (state.defaultAssistant.id === assistantId) {
state.defaultAssistant = {
...state.defaultAssistant,
model: model
}
}
}, },
setTagsOrder: (state, action: PayloadAction<string[]>) => { setTagsOrder: (state, action: PayloadAction<string[]>) => {
state.tagsOrder = action.payload state.tagsOrder = action.payload

View File

@ -1570,26 +1570,21 @@ const migrateConfig = {
}, },
'113': (state: RootState) => { '113': (state: RootState) => {
try { try {
// Step 1: Merge defaultAssistant and assistants[0] topics to ensure consistency // Step 1: 把默认助手模板下面的话题合并到主页列表的默认助手Id下面保持默认助手模板的纯粹性
// This fixes any inconsistencies from backup restores or previous versions
if (state.assistants?.defaultAssistant && state.assistants?.assistants?.length > 0) { if (state.assistants?.defaultAssistant && state.assistants?.assistants?.length > 0) {
const defaultAssistantId = state.assistants.defaultAssistant.id const defaultAssistantId = state['assistants'].defaultAssistant.id
const defaultAssistantInArray = state.assistants.assistants.find((a) => a.id === defaultAssistantId) const defaultAssistantInArray = state.assistants.assistants.find((a) => a.id === defaultAssistantId)
if (defaultAssistantInArray) { if (defaultAssistantInArray) {
// Merge topics from both defaultAssistant and assistants[0] const defaultTopics = state['assistants'].defaultAssistant.topics || []
const defaultTopics = state.assistants.defaultAssistant.topics || []
const arrayTopics = defaultAssistantInArray.topics || [] const arrayTopics = defaultAssistantInArray.topics || []
// Create a map to avoid duplicates (by topic id)
const topicsMap = new Map<string, Topic>() const topicsMap = new Map<string, Topic>()
// Add topics from both sources const allTopics = [...arrayTopics, ...defaultTopics]
const allTopics = [...defaultTopics, ...arrayTopics]
allTopics.forEach((topic) => { allTopics.forEach((topic) => {
if (topic && topic.id) { if (topic && topic.id) {
// Keep the one with more recent updatedAt, or prefer the one from assistants array
const existing = topicsMap.get(topic.id) const existing = topicsMap.get(topic.id)
if ( if (
!existing || !existing ||
@ -1603,19 +1598,14 @@ const migrateConfig = {
const mergedTopics = Array.from(topicsMap.values()) const mergedTopics = Array.from(topicsMap.values())
// Update both defaultAssistant and the assistant in array state['assistants'].defaultAssistant.topics = []
state.assistants.defaultAssistant.topics = mergedTopics
defaultAssistantInArray.topics = mergedTopics defaultAssistantInArray.topics = mergedTopics
} else { } else {
// defaultAssistant not found in array, add it // 如果默认助手不存在,说明被用户删掉了
state.assistants.assistants.unshift(state.assistants.defaultAssistant)
} }
} }
// Step 2: Migrate from nested topic structure to flattened topic structure // Step 2: 迁移话题结构,从嵌套结构迁移到扁平结构
// This should run after v112 which ensures defaultAssistant and assistants[0] consistency
// Initialize the new topics slice if it doesn't exist
if (!state.topics) { if (!state.topics) {
state.topics = { state.topics = {
ids: [], ids: [],
@ -1632,8 +1622,8 @@ const migrateConfig = {
const topicIdsByAssistant: Record<string, string[]> = {} const topicIdsByAssistant: Record<string, string[]> = {}
// Process regular assistants // Process regular assistants
if (state.assistants?.assistants) { if (state['assistants'].assistants && state['assistants'].assistants.length > 0) {
state.assistants.assistants.forEach((assistant) => { state['assistants'].assistants.forEach((assistant) => {
const legacyAssistant = assistant as LegacyAssistant const legacyAssistant = assistant as LegacyAssistant
if (legacyAssistant.topics && Array.isArray(legacyAssistant.topics) && legacyAssistant.topics.length > 0) { if (legacyAssistant.topics && Array.isArray(legacyAssistant.topics) && legacyAssistant.topics.length > 0) {
allTopics.push(...legacyAssistant.topics) allTopics.push(...legacyAssistant.topics)
@ -1653,14 +1643,6 @@ const migrateConfig = {
}) })
} }
// Process default assistant - should already be consistent after v112
if (state.assistants?.defaultAssistant) {
const legacyDefaultAssistant = state.assistants.defaultAssistant as LegacyAssistant
// Since v112 already ensured consistency, just clear the deprecated field
legacyDefaultAssistant.topics = []
}
// Populate the new topics slice // Populate the new topics slice
const topicEntities: Record<string, Topic> = {} const topicEntities: Record<string, Topic> = {}
const topicIds: string[] = [] const topicIds: string[] = []
@ -1670,7 +1652,6 @@ const migrateConfig = {
topicIds.push(topic.id) topicIds.push(topic.id)
}) })
// Update topics slice
state.topics = { state.topics = {
ids: topicIds, ids: topicIds,
entities: topicEntities, entities: topicEntities,
@ -1679,7 +1660,6 @@ const migrateConfig = {
return state return state
} catch (error) { } catch (error) {
console.error('Migration 112 failed:', error)
return state return state
} }
} }

View File

@ -130,9 +130,7 @@ const topicsSlice = createSlice({
topicsAdapter.removeMany(state, topicIds) topicsAdapter.removeMany(state, topicIds)
// Create default topic // Create default topic
const defaultTopic = getDefaultTopic(assistantId) topicsActions.addDefaultTopic({ assistantId })
topicsAdapter.addOne(state, defaultTopic)
state.topicIdsByAssistant[assistantId] = [defaultTopic.id]
}, },
moveTopic(state, action: PayloadAction<MoveTopicPayload>) { moveTopic(state, action: PayloadAction<MoveTopicPayload>) {
const { fromAssistantId, toAssistantId, topicId } = action.payload const { fromAssistantId, toAssistantId, topicId } = action.payload
@ -154,6 +152,12 @@ const topicsSlice = createSlice({
state.topicIdsByAssistant[toAssistantId] = [] state.topicIdsByAssistant[toAssistantId] = []
} }
state.topicIdsByAssistant[toAssistantId].unshift(topicId) state.topicIdsByAssistant[toAssistantId].unshift(topicId)
},
addDefaultTopic(state, action: PayloadAction<{ assistantId: string }>) {
const { assistantId } = action.payload
const defaultTopic = getDefaultTopic(assistantId)
topicsAdapter.addOne(state, defaultTopic)
state.topicIdsByAssistant[assistantId] = [defaultTopic.id]
} }
} }
}) })