Fix: Refactoring and enhancing the functionality of the delete confirmation dialog component #10703 (#11542)

### What problem does this PR solve?

Fix: Refactoring and enhancing the functionality of the delete
confirmation dialog component

- Refactoring and enhancing the functionality of the delete confirmation
dialog component
- Modifying the style of the user center

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2025-11-26 19:49:21 +08:00
committed by GitHub
parent 89ba7abe30
commit 376eb15c63
25 changed files with 393 additions and 112 deletions

View File

@ -1,4 +1,7 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import {
ConfirmDeleteDialog,
ConfirmDeleteDialogNode,
} from '@/components/confirm-delete-dialog';
import {
DropdownMenu,
DropdownMenuContent,
@ -45,7 +48,18 @@ export function AgentDropdown({
{t('common.rename')} <PenLine />
</DropdownMenuItem>
<DropdownMenuSeparator />
<ConfirmDeleteDialog onOk={handleDelete}>
<ConfirmDeleteDialog
onOk={handleDelete}
title={t('deleteModal.delAgent')}
content={{
node: (
<ConfirmDeleteDialogNode
avatar={{ avatar: agent.avatar, name: agent.title }}
name={agent.title}
/>
),
}}
>
<DropdownMenuItem
className="text-state-error"
onSelect={(e) => {

View File

@ -3,6 +3,7 @@ import isEmpty from 'lodash/isEmpty';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { buildNodesAndCombos } from './util';
import { useIsDarkTheme } from '@/components/theme-provider';
import styles from './index.less';
const TooltipColorMap = {
@ -19,7 +20,7 @@ interface IProps {
const ForceGraph = ({ data, show }: IProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const graphRef = useRef<Graph | null>(null);
const isDark = useIsDarkTheme();
const nextData = useMemo(() => {
if (!isEmpty(data)) {
const graphData = data;
@ -80,8 +81,13 @@ const ForceGraph = ({ data, show }: IProps) => {
},
node: {
style: {
size: 150,
size: (d) => {
let size = 100 + ((d.rank as number) || 0) * 5;
size = size > 300 ? 300 : size;
return size;
},
labelText: (d) => d.id,
labelFill: isDark ? 'rgba(255,255,255,1)' : 'rgba(0,0,0,1)',
// labelPadding: 30,
labelFontSize: 40,
// labelOffsetX: 20,
@ -101,8 +107,9 @@ const ForceGraph = ({ data, show }: IProps) => {
const weight: number = Number(model?.weight) || 2;
const lineWeight = weight * 4;
return {
stroke: '#99ADD1',
lineWidth: lineWeight > 10 ? 10 : lineWeight,
stroke: isDark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)',
lineDash: [10, 10],
lineWidth: lineWeight > 8 ? 8 : lineWeight,
};
},
},

View File

@ -1,4 +1,7 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import {
ConfirmDeleteDialog,
ConfirmDeleteDialogNode,
} from '@/components/confirm-delete-dialog';
import {
DropdownMenu,
DropdownMenuContent,
@ -45,7 +48,18 @@ export function DatasetDropdown({
{t('common.rename')} <PenLine />
</DropdownMenuItem>
<DropdownMenuSeparator />
<ConfirmDeleteDialog onOk={handleDelete}>
<ConfirmDeleteDialog
onOk={handleDelete}
title={t('deleteModal.delDataset')}
content={{
node: (
<ConfirmDeleteDialogNode
avatar={{ avatar: dataset.avatar, name: dataset.name }}
name={dataset.name}
/>
),
}}
>
<DropdownMenuItem
className="text-state-error"
onSelect={(e) => {

View File

@ -1,13 +1,19 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import {
ConfirmDeleteDialog,
ConfirmDeleteDialogNode,
} from '@/components/confirm-delete-dialog';
import { FileIcon } from '@/components/icon-font';
import NewDocumentLink from '@/components/new-document-link';
import { Button } from '@/components/ui/button';
import { useDownloadFile } from '@/hooks/file-manager-hooks';
import { IFile } from '@/interfaces/database/file-manager';
import { cn } from '@/lib/utils';
import {
getExtension,
isSupportedPreviewDocumentType,
} from '@/utils/document-util';
import { CellContext } from '@tanstack/react-table';
import { t } from 'i18next';
import {
ArrowDownToLine,
Eye,
@ -38,6 +44,9 @@ export function ActionCell({
}: IProps) {
const record = row.original;
const documentId = record.id;
const name: string = row.getValue('name');
const type = record.type;
const { downloadFile } = useDownloadFile();
const isFolder = isFolderType(record.type);
const extension = getExtension(record.name);
@ -151,7 +160,28 @@ export function ActionCell({
</DropdownMenuContent>
</DropdownMenu> */}
{isKnowledgeBase || (
<ConfirmDeleteDialog onOk={onRemoveFile}>
<ConfirmDeleteDialog
onOk={onRemoveFile}
title={t('deleteModal.delFile')}
content={{
node: (
<ConfirmDeleteDialogNode>
<div className="flex items-center gap-2 text-text-secondary">
<span className="size-4">
<FileIcon name={name} type={type}></FileIcon>
</span>
<span
className={cn('truncate text-xs', {
['cursor-pointer']: isFolder,
})}
>
{name}
</span>
</div>
</ConfirmDeleteDialogNode>
),
}}
>
<Button
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"

View File

@ -1,4 +1,7 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import {
ConfirmDeleteDialog,
ConfirmDeleteDialogNode,
} from '@/components/confirm-delete-dialog';
import {
DropdownMenu,
DropdownMenuContent,
@ -45,7 +48,18 @@ export function ChatDropdown({
{t('common.rename')} <PenLine />
</DropdownMenuItem>
<DropdownMenuSeparator />
<ConfirmDeleteDialog onOk={handleDelete}>
<ConfirmDeleteDialog
onOk={handleDelete}
title={t('deleteModal.delChat')}
content={{
node: (
<ConfirmDeleteDialogNode
avatar={{ avatar: chat.icon, name: chat.name }}
name={chat.name}
/>
),
}}
>
<DropdownMenuItem
className="text-state-error"
onSelect={(e) => {

View File

@ -1,4 +1,7 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import {
ConfirmDeleteDialog,
ConfirmDeleteDialogNode,
} from '@/components/confirm-delete-dialog';
import {
DropdownMenu,
DropdownMenuContent,
@ -41,9 +44,20 @@ export function SearchDropdown({
{t('common.rename')} <PenLine />
</DropdownMenuItem>
<DropdownMenuSeparator />
<ConfirmDeleteDialog onOk={handleDelete}>
<ConfirmDeleteDialog
onOk={handleDelete}
title={t('deleteModal.delSearch')}
content={{
node: (
<ConfirmDeleteDialogNode
avatar={{ avatar: dataset.avatar, name: dataset.name }}
name={dataset.name}
/>
),
}}
>
<DropdownMenuItem
className="text-text-delete-red"
className="text-state-error"
onSelect={(e) => {
e.preventDefault();
}}

View File

@ -31,7 +31,7 @@ export const AddedSourceCard = (props: IAddedSourceCardProps) => {
key={item.id}
className="flex flex-row items-center justify-between rounded-md bg-bg-card px-[10px] py-4"
>
<div className="text-sm text-text-secondary ">{item.name}</div>
<div className="text-sm text-text-primary ">{item.name}</div>
<div className="text-sm text-text-secondary flex gap-2">
<Button
variant={'ghost'}

View File

@ -21,28 +21,33 @@ export const delSourceModal = <T extends IDataSourceBase>(
? t('setting.deleteSourceModalTitle')
: t('dataflowParser.unlinkSourceModalTitle'),
content: (
<div className="px-2 py-6">
<div className="flex items-center gap-1 p-2 border border-border-button rounded-md mb-3">
<div className="w-6 h-6 flex-shrink-0">
{data?.source ? DataSourceInfo[data?.source].icon : ''}
</div>
<div>{data?.name}</div>
</div>
<div className="px-2 space-y-6 pt-5 pb-3">
{type === 'delete' ? (
<div
className="text-sm text-text-secondary"
className="text-base text-text-primary"
dangerouslySetInnerHTML={{
__html: t('setting.deleteSourceModalContent'),
}}
></div>
) : (
<div
className="text-sm text-text-secondary"
className="text-base text-text-primary"
dangerouslySetInnerHTML={{
__html: t('dataflowParser.unlinkSourceModalContent'),
}}
/>
)}
<div className="flex items-center gap-1 p-2 border border-border-button rounded-md mb-3">
<div className="w-6 h-6 flex-shrink-0">
{data?.source ? DataSourceInfo[data?.source].icon : ''}
</div>
<div className="flex items-center gap-2 text-text-secondary text-xs">
{/* <div className="h-6 flex-shrink-0 text-text-primary text-base">
{data?.source ? DataSourceInfo[data?.source].name : ''}
</div> */}
{data?.name}
</div>
</div>
</div>
),
confirmText:
@ -56,12 +61,13 @@ export const delSourceModal = <T extends IDataSourceBase>(
...otherProps,
title: config.title,
children: config.content,
titleClassName: 'border-b border-border-button',
onVisibleChange: () => {
Modal.hide();
Modal.destroy();
},
footer: (
<div className="flex justify-end gap-2">
<Button variant={'outline'} onClick={() => Modal.hide()}>
<Button variant={'outline'} onClick={() => Modal.destroy()}>
{t('dataflowParser.changeStepModalCancelText')}
</Button>
<Button
@ -69,7 +75,7 @@ export const delSourceModal = <T extends IDataSourceBase>(
className="!bg-state-error text-text-base"
onClick={() => {
onOk?.(data);
Modal.hide();
Modal.destroy();
}}
>
{config.confirmText}

View File

@ -84,7 +84,7 @@ const DataSource = () => {
>
<Spotlight />
<div className="relative">
<div className=" flex flex-col gap-4 max-h-[calc(100vh-230px)] overflow-y-auto overflow-x-hidden scrollbar-auto">
<div className=" flex flex-col gap-4 max-h-[calc(100vh-235px)] overflow-y-auto overflow-x-hidden scrollbar-auto">
<div className="flex flex-col gap-3">
{categorizedList?.length <= 0 && (
<div className="text-text-secondary w-full flex justify-center items-center h-20">

View File

@ -1,5 +1,8 @@
import { CardContainer } from '@/components/card-container';
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import {
ConfirmDeleteDialog,
ConfirmDeleteDialogNode,
} from '@/components/confirm-delete-dialog';
import Spotlight from '@/components/spotlight';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
@ -110,7 +113,18 @@ export default function McpServer() {
<Upload className="size-3.5"></Upload>
{t('mcp.export')}
</Button>
<ConfirmDeleteDialog onOk={handleDelete}>
<ConfirmDeleteDialog
onOk={handleDelete}
title={t('common.delete') + ' ' + t('mcp.mcpServers')}
content={{
title: t('common.deleteThem'),
node: (
<ConfirmDeleteDialogNode
name={`${t('mcp.selected')} ${selectedList.length} ${t('mcp.mcpServers')}`}
></ConfirmDeleteDialogNode>
),
}}
>
<Button variant={'danger'}>
<Trash2 className="size-3.5 cursor-pointer" />
{t('common.delete')}

View File

@ -54,7 +54,7 @@ export function McpCard({
/>
) : (
<McpOperation
mcpId={data.id}
mcp={data}
showEditModal={showEditModal}
></McpOperation>
)}

View File

@ -1,5 +1,9 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import {
ConfirmDeleteDialog,
ConfirmDeleteDialogNode,
} from '@/components/confirm-delete-dialog';
import { useDeleteMcpServer } from '@/hooks/use-mcp-request';
import { IMcpServer } from '@/interfaces/database/mcp';
import { PenLine, Trash2, Upload } from 'lucide-react';
import { MouseEventHandler, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@ -7,33 +11,41 @@ import { UseEditMcpReturnType } from './use-edit-mcp';
import { useExportMcp } from './use-export-mcp';
export function McpOperation({
mcpId,
mcp,
showEditModal,
}: { mcpId: string } & Pick<UseEditMcpReturnType, 'showEditModal'>) {
}: { mcp: IMcpServer } & Pick<UseEditMcpReturnType, 'showEditModal'>) {
const { t } = useTranslation();
const { deleteMcpServer } = useDeleteMcpServer();
const { handleExportMcpJson } = useExportMcp();
const handleDelete: MouseEventHandler<HTMLDivElement> = useCallback(() => {
deleteMcpServer([mcpId]);
}, [deleteMcpServer, mcpId]);
deleteMcpServer([mcp.id]);
}, [deleteMcpServer, mcp.id]);
return (
<div className="hidden gap-1 group-hover:flex text-text-secondary">
{/* <RAGFlowTooltip tooltip={t('mcp.export')}> */}
<Upload
className="size-5 cursor-pointer p-1 rounded-sm hover:text-text-primary hover:bg-bg-card"
onClick={handleExportMcpJson([mcpId])}
onClick={handleExportMcpJson([mcp.id])}
/>
{/* </RAGFlowTooltip>
<RAGFlowTooltip tooltip={t('common.edit')}> */}
<PenLine
className="size-5 cursor-pointer p-1 rounded-sm hover:text-text-primary hover:bg-bg-card"
onClick={showEditModal(mcpId)}
onClick={showEditModal(mcp.id)}
/>
{/* </RAGFlowTooltip>
<RAGFlowTooltip tooltip={t('common.delete')}> */}
<ConfirmDeleteDialog onOk={handleDelete}>
<ConfirmDeleteDialog
onOk={handleDelete}
title={t('common.delete') + ' ' + t('mcp.mcpServer')}
content={{
node: (
<ConfirmDeleteDialogNode name={mcp.name}></ConfirmDeleteDialogNode>
),
}}
>
<Trash2 className="size-5 cursor-pointer p-1 rounded-sm hover:text-state-error hover:bg-state-error-5" />
</ConfirmDeleteDialog>
{/* </RAGFlowTooltip> */}

View File

@ -1,4 +1,8 @@
// src/components/ModelProviderCard.tsx
import {
ConfirmDeleteDialog,
ConfirmDeleteDialogNode,
} from '@/components/confirm-delete-dialog';
import { LlmIcon } from '@/components/svg-icon';
import { Button } from '@/components/ui/button';
import { Switch } from '@/components/ui/switch';
@ -54,7 +58,7 @@ export const ModelProviderCard: FC<IModelCardProps> = ({
const { visible, switchVisible } = useSetModalState();
const { t } = useTranslate('setting');
const { handleEnableLlm } = useHandleEnableLlm(item.name);
const { handleDeleteFactory } = useHandleDeleteFactory(item.name);
const { deleteFactory } = useHandleDeleteFactory(item.name);
const handleApiKeyClick = () => {
clickApiKey(item.name);
@ -102,16 +106,31 @@ export const ModelProviderCard: FC<IModelCardProps> = ({
{!visible ? <ChevronsDown /> : <ChevronsUp />}
</Button>
<Button
variant={'ghost'}
onClick={(e) => {
e.stopPropagation();
handleDeleteFactory();
<ConfirmDeleteDialog
onOk={() => deleteFactory({ llm_factory: item.name })}
title={t('deleteModel')}
content={{
node: (
<ConfirmDeleteDialogNode>
<div className="flex items-center gap-2">
<LlmIcon name={item.name} />
{item.name}
</div>
</ConfirmDeleteDialogNode>
),
}}
className=" hover:text-state-error hover:bg-state-error-5 transition-colors border border-border-default"
>
<Trash2 />
</Button>
<Button
variant={'ghost'}
// onClick={(e) => {
// e.stopPropagation();
// handleDeleteFactory(item);
// }}
className=" hover:text-state-error hover:bg-state-error-5 transition-colors border border-border-default"
>
<Trash2 />
</Button>
</ConfirmDeleteDialog>
</div>
</div>

View File

@ -1,10 +1,10 @@
// src/components/AvailableModels.tsx
import { LlmIcon } from '@/components/svg-icon';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { SearchInput } from '@/components/ui/input';
import { useTranslate } from '@/hooks/common-hooks';
import { useSelectLlmList } from '@/hooks/llm-hooks';
import { Plus, Search } from 'lucide-react';
import { Plus } from 'lucide-react';
import { FC, useMemo, useState } from 'react';
type TagType =
@ -77,16 +77,16 @@ export const AvailableModels: FC<{
</div>
{/* Search Bar */}
<div className="mb-6">
<div className="relative">
<Input
type="text"
placeholder={t('search')}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full px-4 py-2 pl-10 bg-bg-input border border-border-default rounded-lg focus:outline-none focus:ring-1 focus:ring-border-button transition-colors"
/>
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-text-secondary" />
</div>
{/* <div className="relative"> */}
<SearchInput
type="text"
placeholder={t('search')}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full px-4 py-2 pl-10 bg-bg-input border border-border-default rounded-lg focus:outline-none focus:ring-1 focus:ring-border-button transition-colors"
/>
{/* <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-text-secondary" /> */}
{/* </div> */}
</div>
{/* Tags Filter */}

View File

@ -457,5 +457,5 @@ export const useHandleDeleteFactory = (llmFactory: string) => {
});
};
return { handleDeleteFactory };
return { handleDeleteFactory, deleteFactory };
};

View File

@ -51,7 +51,7 @@ export const useHandleDeleteUser = () => {
});
};
return { handleDeleteTenantUser, loading };
return { handleDeleteTenantUser, deleteTenantUser, loading };
};
export const useHandleAgreeTenant = () => {

View File

@ -1,3 +1,7 @@
import {
ConfirmDeleteDialog,
ConfirmDeleteDialogNode,
} from '@/components/confirm-delete-dialog';
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@ -26,7 +30,7 @@ const ColorMap: Record<string, string> = {
const UserTable = ({ searchUser }: { searchUser: string }) => {
const { data, loading } = useListTenantUser();
const { handleDeleteTenantUser } = useHandleDeleteUser();
const { deleteTenantUser } = useHandleDeleteUser();
const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | null>(null);
const { t } = useTranslation();
const sortedData = useMemo(() => {
@ -134,14 +138,35 @@ const UserTable = ({ searchUser }: { searchUser: string }) => {
)}
</TableCell>
<TableCell className="p-4">
<Button
variant="ghost"
size="icon"
className="h-8 w-8 p-0 hover:bg-state-error-5 hover:text-state-error"
onClick={handleDeleteTenantUser(record.user_id)}
<ConfirmDeleteDialog
title={t('deleteModal.delMember')}
onOk={async () => {
await deleteTenantUser({
userId: record.user_id,
});
return;
}}
content={{
node: (
<ConfirmDeleteDialogNode
avatar={{
avatar: record.avatar,
name: record.nickname,
isPerson: true,
}}
name={record.email}
></ConfirmDeleteDialogNode>
),
}}
>
<Trash2 className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 p-0 hover:bg-state-error-5 hover:text-state-error"
>
<Trash2 className="h-4 w-4" />
</Button>
</ConfirmDeleteDialog>
</TableCell>
</TableRow>
))