Skip to content

Commit 9ac84e6

Browse files
authored
🐛 fix: slove the agent group editor not focus in editdata area (lobehub#11677)
fix: slove the agent group editor not focus in editdata area
1 parent 4e8b3e8 commit 9ac84e6

File tree

5 files changed

+75
-35
lines changed

5 files changed

+75
-35
lines changed

src/app/[variants]/(main)/group/profile/features/GroupProfile/index.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { Button, Flexbox } from '@lobehub/ui';
44
import { Divider } from 'antd';
55
import { PlayIcon } from 'lucide-react';
6-
import { memo, useCallback } from 'react';
6+
import { memo, useCallback, useMemo } from 'react';
77
import { useTranslation } from 'react-i18next';
88
import urlJoin from 'url-join';
99

@@ -43,6 +43,15 @@ const GroupProfile = memo(() => {
4343
handleContentChange(saveContent);
4444
}, [handleContentChange, saveContent]);
4545

46+
// Stabilize editorData object reference to prevent unnecessary re-renders
47+
const editorData = useMemo(
48+
() => ({
49+
content: currentGroup?.content ?? undefined,
50+
editorData: currentGroup?.editorData,
51+
}),
52+
[currentGroup?.content, currentGroup?.editorData],
53+
);
54+
4655
return (
4756
<>
4857
<Flexbox
@@ -83,11 +92,8 @@ const GroupProfile = memo(() => {
8392
{/* Group Content Editor */}
8493
<EditorCanvas
8594
editor={editor}
86-
editorData={{
87-
content: currentGroup?.content ?? undefined,
88-
editorData: currentGroup?.editorData,
89-
}}
90-
key={groupId}
95+
editorData={editorData}
96+
entityId={groupId}
9197
onContentChange={onContentChange}
9298
placeholder={t('group.profile.contentPlaceholder', { ns: 'chat' })}
9399
/>

src/app/[variants]/(main)/group/profile/features/MemberProfile/index.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ const MemberProfile = memo(() => {
3434
const updateAgentConfigById = useAgentStore((s) => s.updateAgentConfigById);
3535

3636
const groupId = useAgentGroupStore(agentGroupSelectors.activeGroupId);
37-
const currentGroup = useAgentGroupStore(agentGroupSelectors.currentGroup);
38-
const currentGroupAgents = useAgentGroupStore(agentGroupSelectors.currentGroupAgents);
37+
const currentGroup = useAgentGroupStore(agentGroupSelectors.currentGroup, isEqual);
38+
const currentGroupAgents = useAgentGroupStore(agentGroupSelectors.currentGroupAgents, isEqual);
3939
const router = useQueryRoute();
4040

4141
// Check if the current agent is the supervisor
@@ -47,6 +47,15 @@ const MemberProfile = memo(() => {
4747
return agent ? !agent.isSupervisor && !agent.virtual : false;
4848
}, [currentGroupAgents, agentId]);
4949

50+
// Stabilize editorData object reference to prevent unnecessary re-renders
51+
const editorData = useMemo(
52+
() => ({
53+
content: config?.systemRole,
54+
editorData: config?.editorData,
55+
}),
56+
[config?.systemRole, config?.editorData],
57+
);
58+
5059
// Wrap updateAgentConfigById for saving editor content
5160
const updateContent = useCallback(
5261
async (payload: { content: string; editorData: Record<string, any> }) => {
@@ -136,11 +145,8 @@ const MemberProfile = memo(() => {
136145
{/* Main Content: Prompt Editor */}
137146
<EditorCanvas
138147
editor={editor}
139-
editorData={{
140-
content: config?.systemRole,
141-
editorData: config?.editorData,
142-
}}
143-
key={agentId}
148+
editorData={editorData}
149+
entityId={agentId}
144150
onContentChange={onContentChange}
145151
placeholder={
146152
isSupervisor

src/features/EditorCanvas/EditorCanvas.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ export interface EditorCanvasProps {
3838
editorData?: unknown;
3939
};
4040

41+
/**
42+
* Entity ID (e.g., agentId, groupId) to track which entity is being edited.
43+
* When entityId changes, editor content will be reloaded.
44+
* When entityId stays the same, editorData changes won't trigger reload.
45+
* This prevents focus loss during auto-save and optimistic updates.
46+
*/
47+
entityId?: string;
48+
4149
/**
4250
* Extra plugins to prepend to BASE_PLUGINS (e.g., ReactLiteXmlPlugin)
4351
*/
@@ -113,7 +121,7 @@ export interface EditorCanvasWithEditorProps extends EditorCanvasProps {
113121
* - AutoSave hint display (documentId mode)
114122
*/
115123
export const EditorCanvas = memo<EditorCanvasWithEditorProps>(
116-
({ editor, documentId, editorData, ...props }) => {
124+
({ editor, documentId, editorData, entityId, ...props }) => {
117125
// documentId mode - fetch and render with loading/error states
118126
if (documentId) {
119127
return (
@@ -127,7 +135,7 @@ export const EditorCanvas = memo<EditorCanvasWithEditorProps>(
127135
if (editorData) {
128136
return (
129137
<EditorErrorBoundary>
130-
<EditorDataMode editor={editor} editorData={editorData} {...props} />
138+
<EditorDataMode editor={editor} editorData={editorData} entityId={entityId} {...props} />
131139
</EditorErrorBoundary>
132140
);
133141
}

src/features/EditorCanvas/EditorDataMode.tsx

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import InternalEditor from './InternalEditor';
1010
export interface EditorDataModeProps extends EditorCanvasProps {
1111
editor: IEditor | undefined;
1212
editorData: NonNullable<EditorCanvasProps['editorData']>;
13+
entityId?: string;
1314
}
1415

1516
const loadEditorContent = (
@@ -35,47 +36,55 @@ const loadEditorContent = (
3536
* EditorCanvas with editorData mode - uses provided data directly
3637
*/
3738
const EditorDataMode = memo<EditorDataModeProps>(
38-
({ editor, editorData, onContentChange, onInit, style, ...editorProps }) => {
39+
({ editor, editorData, entityId, onContentChange, onInit, style, ...editorProps }) => {
3940
const { t } = useTranslation('file');
4041
const isEditorReadyRef = useRef(false);
41-
// Track loaded content to support re-loading when data changes
42-
const loadedContentRef = useRef<string | undefined>(undefined);
42+
// Track the current entityId to detect entity changes
43+
const currentEntityIdRef = useRef<string | undefined>(undefined);
4344

44-
// Check if content has actually changed
45-
const hasDataChanged = loadedContentRef.current !== editorData.content;
45+
// Check if we're editing a different entity
46+
// When entityId is undefined, always consider it as "changed" (backward compatibility)
47+
// When entityId is provided, check if it actually changed
48+
const isEntityChanged = entityId === undefined || currentEntityIdRef.current !== entityId;
4649

4750
const handleInit = useCallback(
4851
(editorInstance: IEditor) => {
4952
isEditorReadyRef.current = true;
5053

51-
// Try to load content if editorData is available and hasn't been loaded yet
52-
if (hasDataChanged) {
53-
try {
54-
if (loadEditorContent(editorInstance, editorData)) {
55-
loadedContentRef.current = editorData.content;
56-
}
57-
} catch (err) {
58-
console.error('[EditorCanvas] Failed to load content:', err);
54+
// Always load content on init
55+
try {
56+
if (isEntityChanged && loadEditorContent(editorInstance, editorData)) {
57+
currentEntityIdRef.current = entityId;
5958
}
59+
} catch (err) {
60+
console.error('[EditorCanvas] Failed to load content:', err);
6061
}
6162

6263
onInit?.(editorInstance);
6364
},
64-
[editorData, hasDataChanged, onInit],
65+
[editorData, entityId, onInit],
6566
);
6667

67-
// Load content when editorData changes after editor is ready
68+
// Load content only when entityId changes (switching to a different entity)
69+
// Ignore editorData changes for the same entity to prevent focus loss during auto-save
6870
useEffect(() => {
69-
if (!editor || !isEditorReadyRef.current || !hasDataChanged) return;
71+
if (!editor || !isEditorReadyRef.current) return;
7072

73+
// Only reload if entityId changed (switching entities)
74+
if (!isEntityChanged) {
75+
// Same entity - don't reload, user is still editing
76+
return;
77+
}
78+
79+
// Different entity - load new content
7180
try {
7281
if (loadEditorContent(editor, editorData)) {
73-
loadedContentRef.current = editorData.content;
82+
currentEntityIdRef.current = entityId;
7483
}
7584
} catch (err) {
7685
console.error('[EditorCanvas] Failed to load content:', err);
7786
}
78-
}, [editor, editorData, hasDataChanged]);
87+
}, [editor, entityId, isEntityChanged, editorData]);
7988

8089
if (!editor) return null;
8190

src/store/agentGroup/action.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,21 @@ const chatGroupInternalSlice: StateCreator<
214214
);
215215

216216
// Sync group agents to agentStore for builtin agent resolution (e.g., supervisor slug)
217+
// Use smart merge: only overwrite if server data is newer to prevent race conditions
217218
const agentStore = getAgentStoreState();
218219
for (const agent of groupDetail.agents) {
219-
// AgentGroupMember extends AgentItem which shares fields with LobeAgentConfig
220-
agentStore.internal_dispatchAgentMap(agent.id, agent as any);
220+
const currentAgentInStore = agentStore.agentMap[agent.id];
221+
222+
// Only overwrite if:
223+
// 1. Agent doesn't exist in store
224+
// 2. Server data is newer than store data (based on updatedAt)
225+
if (
226+
!currentAgentInStore ||
227+
new Date(agent.updatedAt) > new Date(currentAgentInStore.updatedAt || 0)
228+
) {
229+
// AgentGroupMember extends AgentItem which shares fields with LobeAgentConfig
230+
agentStore.internal_dispatchAgentMap(agent.id, agent as any);
231+
}
221232
}
222233

223234
// Set activeAgentId to supervisor for correct model resolution in sendMessage

0 commit comments

Comments
 (0)