mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-26 13:19:34 +07:00
🐛 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:
@@ -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' })}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user