feat(assistants): place copied assistant after the original one (#8557)

* feat(assistants): 复制助手功能插入到原助手后

添加 insertAssistant 方法用于在指定位置插入助手
实现 copyAssistant 功能用于复制助手并插入到原助手后面
更新相关组件以使用新的复制功能

* fix(useAssistant): 修复助手索引检查逻辑错误

原条件判断错误导致未找到索引时仍执行更新操作,现修正为仅在索引存在时更新

* fix(useAssistant): 添加错误处理及国际化支持

捕获插入助手时的异常并显示错误提示
添加react-i18next的翻译功能用于错误消息
This commit is contained in:
Phantom 2025-07-27 11:17:45 +08:00 committed by GitHub
parent dfceed8751
commit 46d98c2b22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 51 additions and 12 deletions

View File

@ -1,9 +1,11 @@
import { db } from '@renderer/databases'
import { getDefaultTopic } from '@renderer/services/AssistantService'
import { loggerService } from '@renderer/services/LoggerService'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import {
addAssistant,
addTopic,
insertAssistant,
removeAllTopics,
removeAssistant,
removeTopic,
@ -17,18 +19,44 @@ import {
} from '@renderer/store/assistants'
import { setDefaultModel, setTopicNamingModel, setTranslateModel } from '@renderer/store/llm'
import { Assistant, AssistantSettings, Model, Topic } from '@renderer/types'
import { uuid } from '@renderer/utils'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { TopicManager } from './useTopic'
export function useAssistants() {
const { t } = useTranslation()
const { assistants } = useAppSelector((state) => state.assistants)
const dispatch = useAppDispatch()
const logger = loggerService.withContext('useAssistants')
return {
assistants,
updateAssistants: (assistants: Assistant[]) => dispatch(updateAssistants(assistants)),
addAssistant: (assistant: Assistant) => dispatch(addAssistant(assistant)),
insertAssistant: (index: number, assistant: Assistant) => dispatch(insertAssistant({ index, assistant })),
copyAssistant: (assistant: Assistant): Assistant | undefined => {
if (!assistant) {
logger.error("assistant doesn't exists.")
return
}
const index = assistants.findIndex((_assistant) => _assistant.id === assistant.id)
const _assistant: Assistant = { ...assistant, id: uuid(), topics: [getDefaultTopic(assistant.id)] }
if (index === -1) {
logger.warn("Origin assistant's id not found. Fallback to addAssistant.")
dispatch(addAssistant(_assistant))
} else {
// 插入到后面
try {
dispatch(insertAssistant({ index: index + 1, assistant: _assistant }))
} catch (e) {
logger.error('Failed to insert assistant', e as Error)
window.message.error(t('message.error.copy'))
}
}
return _assistant
},
removeAssistant: (id: string) => {
dispatch(removeAssistant({ id }))
const assistant = assistants.find((a) => a.id === id)

View File

@ -25,7 +25,7 @@ const Assistants: FC<AssistantsTabProps> = ({
onCreateAssistant,
onCreateDefaultAssistant
}) => {
const { assistants, removeAssistant, addAssistant, updateAssistants } = useAssistants()
const { assistants, removeAssistant, copyAssistant, updateAssistants } = useAssistants()
const [dragging, setDragging] = useState(false)
const { addAgent } = useAgents()
const { t } = useTranslation()
@ -106,7 +106,7 @@ const Assistants: FC<AssistantsTabProps> = ({
onSwitch={setActiveAssistant}
onDelete={onDelete}
addAgent={addAgent}
addAssistant={addAssistant}
copyAssistant={copyAssistant}
onCreateDefaultAssistant={onCreateDefaultAssistant}
handleSortByChange={handleSortByChange}
/>
@ -143,7 +143,7 @@ const Assistants: FC<AssistantsTabProps> = ({
onSwitch={setActiveAssistant}
onDelete={onDelete}
addAgent={addAgent}
addAssistant={addAssistant}
copyAssistant={copyAssistant}
onCreateDefaultAssistant={onCreateDefaultAssistant}
handleSortByChange={handleSortByChange}
/>

View File

@ -17,7 +17,7 @@ import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings'
import { useTags } from '@renderer/hooks/useTags'
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
import { getDefaultModel, getDefaultTopic } from '@renderer/services/AssistantService'
import { getDefaultModel } from '@renderer/services/AssistantService'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { Assistant, AssistantsSortType } from '@renderer/types'
import { getLeadingEmoji, uuid } from '@renderer/utils'
@ -40,7 +40,7 @@ interface AssistantItemProps {
onDelete: (assistant: Assistant) => void
onCreateDefaultAssistant: () => void
addAgent: (agent: any) => void
addAssistant: (assistant: Assistant) => void
copyAssistant: (assistant: Assistant) => void
onTagClick?: (tag: string) => void
handleSortByChange?: (sortType: AssistantsSortType) => void
}
@ -52,7 +52,7 @@ const AssistantItem: FC<AssistantItemProps> = ({
onSwitch,
onDelete,
addAgent,
addAssistant,
copyAssistant,
handleSortByChange
}) => {
const { t } = useTranslation()
@ -91,7 +91,7 @@ const AssistantItem: FC<AssistantItemProps> = ({
assistants,
updateAssistants,
addAgent,
addAssistant,
copyAssistant,
onSwitch,
onDelete,
removeAllTopics,
@ -108,7 +108,7 @@ const AssistantItem: FC<AssistantItemProps> = ({
assistants,
updateAssistants,
addAgent,
addAssistant,
copyAssistant,
onSwitch,
onDelete,
removeAllTopics,
@ -246,7 +246,7 @@ function getMenuItems({
assistants,
updateAssistants,
addAgent,
addAssistant,
copyAssistant,
onSwitch,
onDelete,
removeAllTopics,
@ -268,9 +268,10 @@ function getMenuItems({
key: 'duplicate',
icon: <CopyIcon />,
onClick: async () => {
const _assistant: Assistant = { ...assistant, id: uuid(), topics: [getDefaultTopic(assistant.id)] }
addAssistant(_assistant)
onSwitch(_assistant)
const _assistant = copyAssistant(assistant)
if (_assistant) {
onSwitch(_assistant)
}
}
},
{

View File

@ -32,6 +32,15 @@ const assistantsSlice = createSlice({
addAssistant: (state, action: PayloadAction<Assistant>) => {
state.assistants.push(action.payload)
},
insertAssistant: (state, action: PayloadAction<{ index: number; assistant: Assistant }>) => {
const { index, assistant } = action.payload
if (index < 0 || index > state.assistants.length) {
throw new Error(`InsertAssistant: index ${index} is out of bounds [0, ${state.assistants.length}]`)
}
state.assistants.splice(index, 0, assistant)
},
removeAssistant: (state, action: PayloadAction<{ id: string }>) => {
state.assistants = state.assistants.filter((c) => c.id !== action.payload.id)
},
@ -170,6 +179,7 @@ export const {
updateDefaultAssistant,
updateAssistants,
addAssistant,
insertAssistant,
removeAssistant,
updateAssistant,
addTopic,