🐛 fix: slove the agent group editor not focus in editdata area (#11677)

fix: slove the agent group editor not focus in editdata area
This commit is contained in:
Shinji-Li
2026-01-21 16:40:45 +08:00
committed by GitHub
parent 4e8b3e8fa8
commit 9ac84e6ac8
5 changed files with 75 additions and 35 deletions

View File

@@ -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 (
<>
<Flexbox
@@ -83,11 +92,8 @@ const GroupProfile = memo(() => {
{/* Group Content Editor */}
<EditorCanvas
editor={editor}
editorData={{
content: currentGroup?.content ?? undefined,
editorData: currentGroup?.editorData,
}}
key={groupId}
editorData={editorData}
entityId={groupId}
onContentChange={onContentChange}
placeholder={t('group.profile.contentPlaceholder', { ns: 'chat' })}
/>

View File

@@ -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<string, any> }) => {
@@ -136,11 +145,8 @@ const MemberProfile = memo(() => {
{/* Main Content: Prompt Editor */}
<EditorCanvas
editor={editor}
editorData={{
content: config?.systemRole,
editorData: config?.editorData,
}}
key={agentId}
editorData={editorData}
entityId={agentId}
onContentChange={onContentChange}
placeholder={
isSupervisor

View File

@@ -38,6 +38,14 @@ export interface EditorCanvasProps {
editorData?: unknown;
};
/**
* Entity ID (e.g., agentId, groupId) to track which entity is being edited.
* When entityId changes, editor content will be reloaded.
* When entityId stays the same, editorData changes won't trigger reload.
* This prevents focus loss during auto-save and optimistic updates.
*/
entityId?: string;
/**
* Extra plugins to prepend to BASE_PLUGINS (e.g., ReactLiteXmlPlugin)
*/
@@ -113,7 +121,7 @@ export interface EditorCanvasWithEditorProps extends EditorCanvasProps {
* - AutoSave hint display (documentId mode)
*/
export const EditorCanvas = memo<EditorCanvasWithEditorProps>(
({ 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<EditorCanvasWithEditorProps>(
if (editorData) {
return (
<EditorErrorBoundary>
<EditorDataMode editor={editor} editorData={editorData} {...props} />
<EditorDataMode editor={editor} editorData={editorData} entityId={entityId} {...props} />
</EditorErrorBoundary>
);
}

View File

@@ -10,6 +10,7 @@ import InternalEditor from './InternalEditor';
export interface EditorDataModeProps extends EditorCanvasProps {
editor: IEditor | undefined;
editorData: NonNullable<EditorCanvasProps['editorData']>;
entityId?: string;
}
const loadEditorContent = (
@@ -35,47 +36,55 @@ const loadEditorContent = (
* EditorCanvas with editorData mode - uses provided data directly
*/
const EditorDataMode = memo<EditorDataModeProps>(
({ 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<string | undefined>(undefined);
// Track the current entityId to detect entity changes
const currentEntityIdRef = useRef<string | undefined>(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;

View File

@@ -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