diff --git a/src/app/[variants]/(main)/group/profile/features/GroupProfile/index.tsx b/src/app/[variants]/(main)/group/profile/features/GroupProfile/index.tsx index 754a20be0a..9e34f7a43c 100644 --- a/src/app/[variants]/(main)/group/profile/features/GroupProfile/index.tsx +++ b/src/app/[variants]/(main)/group/profile/features/GroupProfile/index.tsx @@ -3,7 +3,7 @@ import { Button, Flexbox } from '@lobehub/ui'; import { Divider } from 'antd'; import { PlayIcon } from 'lucide-react'; -import { memo, useCallback } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import urlJoin from 'url-join'; @@ -43,6 +43,15 @@ const GroupProfile = memo(() => { handleContentChange(saveContent); }, [handleContentChange, saveContent]); + // Stabilize editorData object reference to prevent unnecessary re-renders + const editorData = useMemo( + () => ({ + content: currentGroup?.content ?? undefined, + editorData: currentGroup?.editorData, + }), + [currentGroup?.content, currentGroup?.editorData], + ); + return ( <> { {/* Group Content Editor */} diff --git a/src/app/[variants]/(main)/group/profile/features/MemberProfile/index.tsx b/src/app/[variants]/(main)/group/profile/features/MemberProfile/index.tsx index 958002c90a..9b308c10ac 100644 --- a/src/app/[variants]/(main)/group/profile/features/MemberProfile/index.tsx +++ b/src/app/[variants]/(main)/group/profile/features/MemberProfile/index.tsx @@ -34,8 +34,8 @@ const MemberProfile = memo(() => { const updateAgentConfigById = useAgentStore((s) => s.updateAgentConfigById); const groupId = useAgentGroupStore(agentGroupSelectors.activeGroupId); - const currentGroup = useAgentGroupStore(agentGroupSelectors.currentGroup); - const currentGroupAgents = useAgentGroupStore(agentGroupSelectors.currentGroupAgents); + const currentGroup = useAgentGroupStore(agentGroupSelectors.currentGroup, isEqual); + const currentGroupAgents = useAgentGroupStore(agentGroupSelectors.currentGroupAgents, isEqual); const router = useQueryRoute(); // Check if the current agent is the supervisor @@ -47,6 +47,15 @@ const MemberProfile = memo(() => { return agent ? !agent.isSupervisor && !agent.virtual : false; }, [currentGroupAgents, agentId]); + // Stabilize editorData object reference to prevent unnecessary re-renders + const editorData = useMemo( + () => ({ + content: config?.systemRole, + editorData: config?.editorData, + }), + [config?.systemRole, config?.editorData], + ); + // Wrap updateAgentConfigById for saving editor content const updateContent = useCallback( async (payload: { content: string; editorData: Record }) => { @@ -136,11 +145,8 @@ const MemberProfile = memo(() => { {/* Main Content: Prompt Editor */} ( - ({ editor, documentId, editorData, ...props }) => { + ({ editor, documentId, editorData, entityId, ...props }) => { // documentId mode - fetch and render with loading/error states if (documentId) { return ( @@ -127,7 +135,7 @@ export const EditorCanvas = memo( if (editorData) { return ( - + ); } diff --git a/src/features/EditorCanvas/EditorDataMode.tsx b/src/features/EditorCanvas/EditorDataMode.tsx index 9ba3d01d72..da6a0341a8 100644 --- a/src/features/EditorCanvas/EditorDataMode.tsx +++ b/src/features/EditorCanvas/EditorDataMode.tsx @@ -10,6 +10,7 @@ import InternalEditor from './InternalEditor'; export interface EditorDataModeProps extends EditorCanvasProps { editor: IEditor | undefined; editorData: NonNullable; + entityId?: string; } const loadEditorContent = ( @@ -35,47 +36,55 @@ const loadEditorContent = ( * EditorCanvas with editorData mode - uses provided data directly */ const EditorDataMode = memo( - ({ editor, editorData, onContentChange, onInit, style, ...editorProps }) => { + ({ editor, editorData, entityId, onContentChange, onInit, style, ...editorProps }) => { const { t } = useTranslation('file'); const isEditorReadyRef = useRef(false); - // Track loaded content to support re-loading when data changes - const loadedContentRef = useRef(undefined); + // Track the current entityId to detect entity changes + const currentEntityIdRef = useRef(undefined); - // Check if content has actually changed - const hasDataChanged = loadedContentRef.current !== editorData.content; + // Check if we're editing a different entity + // When entityId is undefined, always consider it as "changed" (backward compatibility) + // When entityId is provided, check if it actually changed + const isEntityChanged = entityId === undefined || currentEntityIdRef.current !== entityId; const handleInit = useCallback( (editorInstance: IEditor) => { isEditorReadyRef.current = true; - // Try to load content if editorData is available and hasn't been loaded yet - if (hasDataChanged) { - try { - if (loadEditorContent(editorInstance, editorData)) { - loadedContentRef.current = editorData.content; - } - } catch (err) { - console.error('[EditorCanvas] Failed to load content:', err); + // Always load content on init + try { + if (isEntityChanged && loadEditorContent(editorInstance, editorData)) { + currentEntityIdRef.current = entityId; } + } catch (err) { + console.error('[EditorCanvas] Failed to load content:', err); } onInit?.(editorInstance); }, - [editorData, hasDataChanged, onInit], + [editorData, entityId, onInit], ); - // Load content when editorData changes after editor is ready + // Load content only when entityId changes (switching to a different entity) + // Ignore editorData changes for the same entity to prevent focus loss during auto-save useEffect(() => { - if (!editor || !isEditorReadyRef.current || !hasDataChanged) return; + if (!editor || !isEditorReadyRef.current) return; + // Only reload if entityId changed (switching entities) + if (!isEntityChanged) { + // Same entity - don't reload, user is still editing + return; + } + + // Different entity - load new content try { if (loadEditorContent(editor, editorData)) { - loadedContentRef.current = editorData.content; + currentEntityIdRef.current = entityId; } } catch (err) { console.error('[EditorCanvas] Failed to load content:', err); } - }, [editor, editorData, hasDataChanged]); + }, [editor, entityId, isEntityChanged, editorData]); if (!editor) return null; diff --git a/src/store/agentGroup/action.ts b/src/store/agentGroup/action.ts index 72dd86b560..3ebef8564c 100644 --- a/src/store/agentGroup/action.ts +++ b/src/store/agentGroup/action.ts @@ -214,10 +214,21 @@ const chatGroupInternalSlice: StateCreator< ); // Sync group agents to agentStore for builtin agent resolution (e.g., supervisor slug) + // Use smart merge: only overwrite if server data is newer to prevent race conditions const agentStore = getAgentStoreState(); for (const agent of groupDetail.agents) { - // AgentGroupMember extends AgentItem which shares fields with LobeAgentConfig - agentStore.internal_dispatchAgentMap(agent.id, agent as any); + const currentAgentInStore = agentStore.agentMap[agent.id]; + + // Only overwrite if: + // 1. Agent doesn't exist in store + // 2. Server data is newer than store data (based on updatedAt) + if ( + !currentAgentInStore || + new Date(agent.updatedAt) > new Date(currentAgentInStore.updatedAt || 0) + ) { + // AgentGroupMember extends AgentItem which shares fields with LobeAgentConfig + agentStore.internal_dispatchAgentMap(agent.id, agent as any); + } } // Set activeAgentId to supervisor for correct model resolution in sendMessage