From 9b8dabc072c8d85e883a4cde5a119593e6e9d748 Mon Sep 17 00:00:00 2001 From: Rdmclin2 Date: Fri, 27 Feb 2026 23:02:05 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20group=20agent=20rename=20?= =?UTF-8?q?problem=20(#12511)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: align agent rename usage with agent profile editor * fix: agent content emoji picker popupProps * chore: agent and agent group use emoji background color in session list * chore: remove fixed popupProps --- .../database/src/repositories/home/index.ts | 17 ++- .../Body/Agent/List/AgentGroupItem/index.tsx | 1 + .../List/AgentGroupItem/useDropdownMenu.tsx | 13 +- .../Body/Agent/List/AgentItem/index.tsx | 11 +- src/features/AgentGroupAvatar/index.tsx | 12 +- src/features/EditingPopover/AgentContent.tsx | 117 +++++++++++++++--- src/features/EditingPopover/GroupContent.tsx | 78 ++++++++++-- src/features/EditingPopover/index.tsx | 1 + src/features/EditingPopover/store.ts | 1 + src/features/GroupAvatar/index.tsx | 8 +- src/store/home/slices/sidebarUI/action.ts | 3 +- 11 files changed, 229 insertions(+), 33 deletions(-) diff --git a/packages/database/src/repositories/home/index.ts b/packages/database/src/repositories/home/index.ts index 48a75bb921..997e5f4654 100644 --- a/packages/database/src/repositories/home/index.ts +++ b/packages/database/src/repositories/home/index.ts @@ -1,4 +1,8 @@ -import { type SidebarAgentItem, type SidebarAgentListResponse, type SidebarGroup } from '@lobechat/types'; +import { + type SidebarAgentItem, + type SidebarAgentListResponse, + type SidebarGroup, +} from '@lobechat/types'; import { cleanObject } from '@lobechat/utils'; import { and, desc, eq, ilike, inArray, not, or } from 'drizzle-orm'; @@ -10,7 +14,7 @@ import { sessionGroups, sessions, } from '../../schemas'; -import { type LobeChatDatabase } from '../../type'; +import { type LobeChatDatabase } from '../../type'; // Re-export types for backward compatibility export type { @@ -41,6 +45,7 @@ export class HomeRepository { .select({ agentSessionGroupId: agents.sessionGroupId, avatar: agents.avatar, + backgroundColor: agents.backgroundColor, description: agents.description, id: agents.id, pinned: agents.pinned, @@ -94,6 +99,7 @@ export class HomeRepository { agentItems: Array<{ agentSessionGroupId: string | null; avatar: string | null; + backgroundColor: string | null; description: string | null; id: string; pinned: boolean | null; @@ -126,6 +132,7 @@ export class HomeRepository { const allItems: Array = [ ...agentItems.map((a) => ({ avatar: a.avatar, + backgroundColor: a.backgroundColor, description: a.description, groupId: a.agentSessionGroupId ?? a.sessionGroupId, id: a.id, @@ -138,7 +145,7 @@ export class HomeRepository { ...chatGroupItems.map((g) => ({ // If group has custom avatar, use it (string); otherwise fallback to member avatars (array) avatar: g.avatar ? g.avatar : (memberAvatarsMap.get(g.id) ?? null), - backgroundColor: g.avatar ? g.backgroundColor : null, + backgroundColor: g.backgroundColor, description: g.description, groupAvatar: g.avatar, groupId: g.groupId, @@ -198,6 +205,7 @@ export class HomeRepository { const agentResults = await this.db .select({ avatar: agents.avatar, + backgroundColor: agents.backgroundColor, description: agents.description, id: agents.id, pinned: agents.pinned, @@ -248,6 +256,7 @@ export class HomeRepository { ...agentResults.map((a) => cleanObject({ avatar: a.avatar, + backgroundColor: a.backgroundColor, description: a.description, id: a.id, pinned: a.pinned ?? a.sessionPinned ?? false, @@ -260,7 +269,7 @@ export class HomeRepository { ...chatGroupResults.map((g) => cleanObject({ avatar: g.avatar ? g.avatar : (memberAvatarsMap.get(g.id) ?? null), - backgroundColor: g.avatar ? g.backgroundColor : null, + backgroundColor: g.backgroundColor, description: g.description, id: g.id, pinned: g.pinned ?? false, diff --git a/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx b/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx index 58b2ce7fe6..499e0d895e 100644 --- a/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx +++ b/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx @@ -94,6 +94,7 @@ const GroupItem = memo(({ item, style, className }) => { const dropdownMenu = useGroupDropdownMenu({ anchor, avatar: customAvatar, + backgroundColor: backgroundColor || undefined, id, memberAvatars, pinned: pinned ?? false, diff --git a/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/useDropdownMenu.tsx b/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/useDropdownMenu.tsx index 848d16341e..a949e9b10a 100644 --- a/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/useDropdownMenu.tsx +++ b/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/useDropdownMenu.tsx @@ -12,6 +12,7 @@ import { useHomeStore } from '@/store/home'; interface UseGroupDropdownMenuParams { anchor: HTMLElement | null; avatar?: string; + backgroundColor?: string; id: string; memberAvatars?: { avatar?: string; background?: string }[]; pinned: boolean; @@ -21,6 +22,7 @@ interface UseGroupDropdownMenuParams { export const useGroupDropdownMenu = ({ anchor, avatar, + backgroundColor, id, memberAvatars, pinned, @@ -52,7 +54,15 @@ export const useGroupDropdownMenu = ({ onClick: (info: any) => { info.domEvent?.stopPropagation(); if (anchor) { - openEditingPopover({ anchor, avatar, id, memberAvatars, title, type: 'agentGroup' }); + openEditingPopover({ + anchor, + avatar, + backgroundColor, + id, + memberAvatars, + title, + type: 'agentGroup', + }); } }, }, @@ -97,6 +107,7 @@ export const useGroupDropdownMenu = ({ [ anchor, avatar, + backgroundColor, memberAvatars, t, pinned, diff --git a/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx b/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx index d27eeab58d..0e7f6010e2 100644 --- a/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +++ b/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx @@ -26,7 +26,7 @@ interface AgentItemProps { } const AgentItem = memo(({ item, style, className }) => { - const { id, avatar, title, pinned } = item; + const { id, avatar, backgroundColor, title, pinned } = item; const { t } = useTranslation('chat'); const { openCreateGroupModal } = useAgentModal(); const [anchor, setAnchor] = useState(null); @@ -83,8 +83,13 @@ const AgentItem = memo(({ item, style, className }) => { return ; } - return ; - }, [isUpdating, avatar]); + return ( + + ); + }, [isUpdating, avatar, backgroundColor]); const dropdownMenu = useAgentDropdownMenu({ anchor, diff --git a/src/features/AgentGroupAvatar/index.tsx b/src/features/AgentGroupAvatar/index.tsx index ca2ff7d8ef..86780794e0 100644 --- a/src/features/AgentGroupAvatar/index.tsx +++ b/src/features/AgentGroupAvatar/index.tsx @@ -28,10 +28,18 @@ const AgentGroupAvatar = memo( ({ avatar, backgroundColor, memberAvatars = [], size = 28 }) => { // If group has custom avatar, show it; otherwise show member avatars composition if (avatar) { - return ; + return ( + + ); } - return ; + return ; }, ); diff --git a/src/features/EditingPopover/AgentContent.tsx b/src/features/EditingPopover/AgentContent.tsx index 0c13768f86..8d38d2e992 100644 --- a/src/features/EditingPopover/AgentContent.tsx +++ b/src/features/EditingPopover/AgentContent.tsx @@ -1,15 +1,31 @@ -import { ActionIcon, Avatar, Block, Flexbox, Input, stopPropagation } from '@lobehub/ui'; -import { type InputRef } from 'antd'; -import { Check } from 'lucide-react'; +import { DEFAULT_AVATAR } from '@lobechat/const'; +import { + ActionIcon, + Avatar, + Block, + Flexbox, + Icon, + Input, + stopPropagation, + Tooltip, +} from '@lobehub/ui'; +import { type InputRef, message } from 'antd'; +import { Check, PaletteIcon } from 'lucide-react'; import { memo, useCallback, useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import EmojiPicker from '@/components/EmojiPicker'; +import BackgroundSwatches from '@/features/AgentSetting/AgentMeta/BackgroundSwatches'; import { useIsDark } from '@/hooks/useIsDark'; import { useAgentStore } from '@/store/agent'; +import { agentSelectors } from '@/store/agent/selectors'; +import { useFileStore } from '@/store/file'; import { useGlobalStore } from '@/store/global'; import { globalGeneralSelectors } from '@/store/global/selectors'; import { useHomeStore } from '@/store/home'; +const MAX_AVATAR_SIZE = 1024 * 1024; + interface AgentContentProps { avatar?: string; id: string; @@ -18,25 +34,31 @@ interface AgentContentProps { } const AgentContent = memo(({ id, title, avatar, onClose }) => { + const { t } = useTranslation('setting'); const locale = useGlobalStore(globalGeneralSelectors.currentLanguage); const isDarkMode = useIsDark(); + const uploadWithProgress = useFileStore((s) => s.uploadWithProgress); - const currentAvatar = avatar || ''; + const meta = useAgentStore(agentSelectors.getAgentMetaById(id)); const [newTitle, setNewTitle] = useState(title); - const [newAvatar, setNewAvatar] = useState(currentAvatar); + const [newAvatar, setNewAvatar] = useState(avatar); + const [newBackgroundColor, setNewBackgroundColor] = useState(meta.backgroundColor); + const [uploading, setUploading] = useState(false); const handleUpdate = useCallback(async () => { - const hasChanges = - (newTitle && title !== newTitle) || (newAvatar && currentAvatar !== newAvatar); + const titleChanged = newTitle && title !== newTitle; + const avatarChanged = newAvatar !== (avatar || undefined); + const backgroundColorChanged = newBackgroundColor !== meta.backgroundColor; - if (hasChanges) { + if (titleChanged || avatarChanged || backgroundColorChanged) { try { useHomeStore.getState().setAgentUpdatingId(id); - const updates: { avatar?: string; title?: string } = {}; - if (newTitle && title !== newTitle) updates.title = newTitle; - if (newAvatar && currentAvatar !== newAvatar) updates.avatar = newAvatar; + const updates: { avatar?: string; backgroundColor?: string; title?: string } = {}; + if (titleChanged) updates.title = newTitle; + if (avatarChanged) updates.avatar = newAvatar || undefined; + if (backgroundColorChanged) updates.backgroundColor = newBackgroundColor; await useAgentStore.getState().optimisticUpdateAgentMeta(id, updates); await useHomeStore.getState().refreshAgentList(); @@ -45,7 +67,32 @@ const AgentContent = memo(({ id, title, avatar, onClose }) => } } onClose(); - }, [newTitle, newAvatar, title, currentAvatar, id, onClose]); + }, [newTitle, newAvatar, newBackgroundColor, title, avatar, meta.backgroundColor, id, onClose]); + + const handleAvatarUpload = useCallback( + async (file: File) => { + if (file.size > MAX_AVATAR_SIZE) { + message.error(t('settingAgent.avatar.sizeExceeded')); + return; + } + + setUploading(true); + try { + const result = await uploadWithProgress({ file }); + if (result?.url) { + setNewAvatar(result.url); + } + } finally { + setUploading(false); + } + }, + [uploadWithProgress, t], + ); + + const handleAvatarDelete = useCallback(() => { + setNewAvatar(null); + }, []); + const inputRef = useRef(null); useEffect(() => { requestAnimationFrame(() => { @@ -56,12 +103,21 @@ const AgentContent = memo(({ id, title, avatar, onClose }) => }); }); }, []); + return ( ( (({ id, title, avatar, onClose }) => width={36} onClick={stopPropagation} > - + )} + customTabs={[ + { + label: ( + + + + ), + render: () => ( + + + + ), + value: 'background', + }, + ]} onChange={setNewAvatar} + onDelete={handleAvatarDelete} + onUpload={handleAvatarUpload} /> void; @@ -24,7 +35,7 @@ interface GroupContentProps { } const GroupContent = memo( - ({ id, title, avatar, memberAvatars, type, onClose }) => { + ({ id, title, avatar, backgroundColor, memberAvatars, type, onClose }) => { const { t } = useTranslation('setting'); const locale = useGlobalStore(globalGeneralSelectors.currentLanguage); const isDarkMode = useIsDark(); @@ -34,13 +45,15 @@ const GroupContent = memo( const [newTitle, setNewTitle] = useState(title); const [newAvatar, setNewAvatar] = useState(avatar); + const [newBackgroundColor, setNewBackgroundColor] = useState(backgroundColor); const [uploading, setUploading] = useState(false); const handleUpdate = useCallback(async () => { const titleChanged = newTitle && title !== newTitle; const avatarChanged = isAgentGroup && newAvatar !== avatar; + const backgroundColorChanged = isAgentGroup && newBackgroundColor !== backgroundColor; - if (titleChanged || avatarChanged) { + if (titleChanged || avatarChanged || backgroundColorChanged) { try { useHomeStore.getState().setGroupUpdatingId(id); @@ -49,14 +62,30 @@ const GroupContent = memo( } else { await useHomeStore .getState() - .renameAgentGroup(id, newTitle || title, avatarChanged ? newAvatar : undefined); + .renameAgentGroup( + id, + newTitle || title, + avatarChanged ? newAvatar : undefined, + backgroundColorChanged ? newBackgroundColor : undefined, + ); } } finally { useHomeStore.getState().setGroupUpdatingId(null); } } onClose(); - }, [newTitle, newAvatar, title, avatar, id, type, isAgentGroup, onClose]); + }, [ + newTitle, + newAvatar, + newBackgroundColor, + title, + avatar, + backgroundColor, + id, + type, + isAgentGroup, + onClose, + ]); const handleAvatarUpload = useCallback( async (file: File) => { @@ -103,6 +132,11 @@ const GroupContent = memo( locale={locale} shape={'square'} value={newAvatar ?? undefined} + background={ + newBackgroundColor && newBackgroundColor !== 'rgba(0,0,0,0)' + ? newBackgroundColor + : undefined + } customRender={(avatarValue) => ( ( avatar={avatarValue} shape={'square'} size={32} + background={ + newBackgroundColor && newBackgroundColor !== 'rgba(0,0,0,0)' + ? newBackgroundColor + : undefined + } /> ) : ( - + )} )} + customTabs={[ + { + label: ( + + + + ), + render: () => ( + + + + ), + value: 'background', + }, + ]} onChange={setNewAvatar} onDelete={handleAvatarDelete} onUpload={handleAvatarUpload} diff --git a/src/features/EditingPopover/index.tsx b/src/features/EditingPopover/index.tsx index 4e230c336b..b00c2b52af 100644 --- a/src/features/EditingPopover/index.tsx +++ b/src/features/EditingPopover/index.tsx @@ -30,6 +30,7 @@ const EditingPopover = () => { ) : target ? ( ( - ({ size = 28, avatars = [], loading, ...rest }) => { + ({ size = 28, avatars = [], background, loading, ...rest }) => { const [userAvatar, nickName, username] = useUserStore((s) => [ userProfileSelectors.userAvatar(s), userProfileSelectors.nickName(s), @@ -51,6 +52,11 @@ const GroupAvatarComponent = memo( background: agent?.backgroundColor || undefined, ...agent, }))} + style={ + background && background !== 'rgba(0,0,0,0)' + ? { background, borderRadius: '22%' } + : undefined + } {...rest} /> ); diff --git a/src/store/home/slices/sidebarUI/action.ts b/src/store/home/slices/sidebarUI/action.ts index 5dfb235949..cf0e201987 100644 --- a/src/store/home/slices/sidebarUI/action.ts +++ b/src/store/home/slices/sidebarUI/action.ts @@ -104,8 +104,9 @@ export class SidebarUIActionImpl { groupId: string, title: string, avatar?: string | null, + backgroundColor?: string, ): Promise => { - await chatGroupService.updateGroup(groupId, { avatar, title }); + await chatGroupService.updateGroup(groupId, { avatar, backgroundColor, title }); await this.#get().refreshAgentList(); };