@lobehub/lobehub 2.0.0-next.330 → 2.0.0-next.331

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.331](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.330...v2.0.0-next.331)
6
+
7
+ <sup>Released on **2026-01-21**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Slove the agent group editor not focus in editdata area.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Slove the agent group editor not focus in editdata area, closes [#11677](https://github.com/lobehub/lobe-chat/issues/11677) ([9ac84e6](https://github.com/lobehub/lobe-chat/commit/9ac84e6))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ## [Version 2.0.0-next.330](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.329...v2.0.0-next.330)
6
31
 
7
32
  <sup>Released on **2026-01-21**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Slove the agent group editor not focus in editdata area."
6
+ ]
7
+ },
8
+ "date": "2026-01-21",
9
+ "version": "2.0.0-next.331"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "fixes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.330",
3
+ "version": "2.0.0-next.331",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -3,7 +3,7 @@
3
3
  import { Button, Flexbox } from '@lobehub/ui';
4
4
  import { Divider } from 'antd';
5
5
  import { PlayIcon } from 'lucide-react';
6
- import { memo, useCallback } from 'react';
6
+ import { memo, useCallback, useMemo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import urlJoin from 'url-join';
9
9
 
@@ -43,6 +43,15 @@ const GroupProfile = memo(() => {
43
43
  handleContentChange(saveContent);
44
44
  }, [handleContentChange, saveContent]);
45
45
 
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
+
46
55
  return (
47
56
  <>
48
57
  <Flexbox
@@ -83,11 +92,8 @@ const GroupProfile = memo(() => {
83
92
  {/* Group Content Editor */}
84
93
  <EditorCanvas
85
94
  editor={editor}
86
- editorData={{
87
- content: currentGroup?.content ?? undefined,
88
- editorData: currentGroup?.editorData,
89
- }}
90
- key={groupId}
95
+ editorData={editorData}
96
+ entityId={groupId}
91
97
  onContentChange={onContentChange}
92
98
  placeholder={t('group.profile.contentPlaceholder', { ns: 'chat' })}
93
99
  />
@@ -34,8 +34,8 @@ const MemberProfile = memo(() => {
34
34
  const updateAgentConfigById = useAgentStore((s) => s.updateAgentConfigById);
35
35
 
36
36
  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);
39
39
  const router = useQueryRoute();
40
40
 
41
41
  // Check if the current agent is the supervisor
@@ -47,6 +47,15 @@ const MemberProfile = memo(() => {
47
47
  return agent ? !agent.isSupervisor && !agent.virtual : false;
48
48
  }, [currentGroupAgents, agentId]);
49
49
 
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
+
50
59
  // Wrap updateAgentConfigById for saving editor content
51
60
  const updateContent = useCallback(
52
61
  async (payload: { content: string; editorData: Record<string, any> }) => {
@@ -136,11 +145,8 @@ const MemberProfile = memo(() => {
136
145
  {/* Main Content: Prompt Editor */}
137
146
  <EditorCanvas
138
147
  editor={editor}
139
- editorData={{
140
- content: config?.systemRole,
141
- editorData: config?.editorData,
142
- }}
143
- key={agentId}
148
+ editorData={editorData}
149
+ entityId={agentId}
144
150
  onContentChange={onContentChange}
145
151
  placeholder={
146
152
  isSupervisor
@@ -38,6 +38,14 @@ export interface EditorCanvasProps {
38
38
  editorData?: unknown;
39
39
  };
40
40
 
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
+
41
49
  /**
42
50
  * Extra plugins to prepend to BASE_PLUGINS (e.g., ReactLiteXmlPlugin)
43
51
  */
@@ -113,7 +121,7 @@ export interface EditorCanvasWithEditorProps extends EditorCanvasProps {
113
121
  * - AutoSave hint display (documentId mode)
114
122
  */
115
123
  export const EditorCanvas = memo<EditorCanvasWithEditorProps>(
116
- ({ editor, documentId, editorData, ...props }) => {
124
+ ({ editor, documentId, editorData, entityId, ...props }) => {
117
125
  // documentId mode - fetch and render with loading/error states
118
126
  if (documentId) {
119
127
  return (
@@ -127,7 +135,7 @@ export const EditorCanvas = memo<EditorCanvasWithEditorProps>(
127
135
  if (editorData) {
128
136
  return (
129
137
  <EditorErrorBoundary>
130
- <EditorDataMode editor={editor} editorData={editorData} {...props} />
138
+ <EditorDataMode editor={editor} editorData={editorData} entityId={entityId} {...props} />
131
139
  </EditorErrorBoundary>
132
140
  );
133
141
  }
@@ -10,6 +10,7 @@ import InternalEditor from './InternalEditor';
10
10
  export interface EditorDataModeProps extends EditorCanvasProps {
11
11
  editor: IEditor | undefined;
12
12
  editorData: NonNullable<EditorCanvasProps['editorData']>;
13
+ entityId?: string;
13
14
  }
14
15
 
15
16
  const loadEditorContent = (
@@ -35,47 +36,55 @@ const loadEditorContent = (
35
36
  * EditorCanvas with editorData mode - uses provided data directly
36
37
  */
37
38
  const EditorDataMode = memo<EditorDataModeProps>(
38
- ({ editor, editorData, onContentChange, onInit, style, ...editorProps }) => {
39
+ ({ editor, editorData, entityId, onContentChange, onInit, style, ...editorProps }) => {
39
40
  const { t } = useTranslation('file');
40
41
  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);
43
44
 
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;
46
49
 
47
50
  const handleInit = useCallback(
48
51
  (editorInstance: IEditor) => {
49
52
  isEditorReadyRef.current = true;
50
53
 
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;
59
58
  }
59
+ } catch (err) {
60
+ console.error('[EditorCanvas] Failed to load content:', err);
60
61
  }
61
62
 
62
63
  onInit?.(editorInstance);
63
64
  },
64
- [editorData, hasDataChanged, onInit],
65
+ [editorData, entityId, onInit],
65
66
  );
66
67
 
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
68
70
  useEffect(() => {
69
- if (!editor || !isEditorReadyRef.current || !hasDataChanged) return;
71
+ if (!editor || !isEditorReadyRef.current) return;
70
72
 
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
71
80
  try {
72
81
  if (loadEditorContent(editor, editorData)) {
73
- loadedContentRef.current = editorData.content;
82
+ currentEntityIdRef.current = entityId;
74
83
  }
75
84
  } catch (err) {
76
85
  console.error('[EditorCanvas] Failed to load content:', err);
77
86
  }
78
- }, [editor, editorData, hasDataChanged]);
87
+ }, [editor, entityId, isEntityChanged, editorData]);
79
88
 
80
89
  if (!editor) return null;
81
90
 
@@ -214,10 +214,21 @@ const chatGroupInternalSlice: StateCreator<
214
214
  );
215
215
 
216
216
  // 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
217
218
  const agentStore = getAgentStoreState();
218
219
  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
+ }
221
232
  }
222
233
 
223
234
  // Set activeAgentId to supervisor for correct model resolution in sendMessage