From 7d05d4ced7e929ce5f111070488fff23ce3d6955 Mon Sep 17 00:00:00 2001 From: chanx <1243304602@qq.com> Date: Fri, 28 Nov 2025 14:03:20 +0800 Subject: [PATCH] Fix: Added styles for empty states on the page. #10703 (#11588) ### What problem does this PR solve? Fix: Added styles for empty states on the page. ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) --- .../svg/empty/no-data-bri.svg} | 0 .../svg/empty/no-data-dark.svg} | 0 .../assets/svg/empty/no-search-data-bri.svg | 35 +++ .../assets/svg/empty/no-search-data-dark.svg | 39 ++++ web/src/components/empty/constant.tsx | 33 +++ web/src/components/empty/empty.tsx | 157 +++++++------ web/src/components/empty/interface.ts | 18 ++ web/src/hooks/llm-hooks.tsx | 2 +- web/src/hooks/logic-hooks/navigate-hooks.ts | 13 +- web/src/hooks/user-setting-hooks.tsx | 3 +- web/src/locales/en.ts | 14 +- web/src/locales/zh.ts | 23 +- web/src/pages/agents/index.tsx | 206 ++++++++++-------- .../dataset-overview/overview-table.tsx | 8 +- .../pages/dataset/dataset/dataset-table.tsx | 4 +- .../dataset/use-dataset-table-columns.tsx | 2 +- .../pages/dataset/testing/testing-result.tsx | 3 +- web/src/pages/datasets/index.tsx | 139 +++++++----- web/src/pages/home/agent-list.tsx | 16 +- web/src/pages/home/applications.tsx | 62 +++++- web/src/pages/home/chat-list.tsx | 16 +- web/src/pages/home/datasets.tsx | 40 +++- web/src/pages/home/search-list.tsx | 16 +- web/src/pages/next-chats/index.tsx | 96 +++++--- .../next-search/retrieval-documents/index.tsx | 78 ++++--- web/src/pages/next-search/search-view.tsx | 17 ++ web/src/pages/next-searches/index.tsx | 116 ++++++---- web/src/pages/user-setting/mcp/index.tsx | 125 ++++++----- .../setting-model/components/modal-card.tsx | 4 +- 29 files changed, 866 insertions(+), 419 deletions(-) rename web/src/{components/empty/no data bri.svg => assets/svg/empty/no-data-bri.svg} (100%) rename web/src/{components/empty/no data.svg => assets/svg/empty/no-data-dark.svg} (100%) create mode 100644 web/src/assets/svg/empty/no-search-data-bri.svg create mode 100644 web/src/assets/svg/empty/no-search-data-dark.svg create mode 100644 web/src/components/empty/constant.tsx create mode 100644 web/src/components/empty/interface.ts diff --git a/web/src/components/empty/no data bri.svg b/web/src/assets/svg/empty/no-data-bri.svg similarity index 100% rename from web/src/components/empty/no data bri.svg rename to web/src/assets/svg/empty/no-data-bri.svg diff --git a/web/src/components/empty/no data.svg b/web/src/assets/svg/empty/no-data-dark.svg similarity index 100% rename from web/src/components/empty/no data.svg rename to web/src/assets/svg/empty/no-data-dark.svg diff --git a/web/src/assets/svg/empty/no-search-data-bri.svg b/web/src/assets/svg/empty/no-search-data-bri.svg new file mode 100644 index 000000000..4ec531868 --- /dev/null +++ b/web/src/assets/svg/empty/no-search-data-bri.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/assets/svg/empty/no-search-data-dark.svg b/web/src/assets/svg/empty/no-search-data-dark.svg new file mode 100644 index 000000000..448543b90 --- /dev/null +++ b/web/src/assets/svg/empty/no-search-data-dark.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/components/empty/constant.tsx b/web/src/components/empty/constant.tsx new file mode 100644 index 000000000..fa2add87a --- /dev/null +++ b/web/src/components/empty/constant.tsx @@ -0,0 +1,33 @@ +import { t } from 'i18next'; +import { HomeIcon } from '../svg-icon'; + +export enum EmptyType { + Data = 'data', + SearchData = 'search-data', +} + +export enum EmptyCardType { + Agent = 'agent', + Dataset = 'dataset', + Chat = 'chat', + Search = 'search', +} + +export const EmptyCardData = { + [EmptyCardType.Agent]: { + icon: , + title: t('empty.agentTitle'), + }, + [EmptyCardType.Dataset]: { + icon: , + title: t('empty.datasetTitle'), + }, + [EmptyCardType.Chat]: { + icon: , + title: t('empty.chatTitle'), + }, + [EmptyCardType.Search]: { + icon: , + title: t('empty.searchTitle'), + }, +}; diff --git a/web/src/components/empty/empty.tsx b/web/src/components/empty/empty.tsx index b637c261f..abf28dd3a 100644 --- a/web/src/components/empty/empty.tsx +++ b/web/src/components/empty/empty.tsx @@ -2,79 +2,33 @@ import { cn } from '@/lib/utils'; import { t } from 'i18next'; import { useIsDarkTheme } from '../theme-provider'; -import noDataIcon from './no data bri.svg'; -import noDataIconDark from './no data.svg'; - -type EmptyProps = { - className?: string; - children?: React.ReactNode; -}; - -const EmptyIcon = () => { - const isDarkTheme = useIsDarkTheme(); +import { Plus } from 'lucide-react'; +import { useMemo } from 'react'; +import SvgIcon from '../svg-icon'; +import { EmptyCardData, EmptyCardType, EmptyType } from './constant'; +import { EmptyCardProps, EmptyProps } from './interface'; +const EmptyIcon = ({ name, width }: { name: string; width?: number }) => { return ( - {t('common.noData')} - ); - - return ( - - {t('common.noData')} - - - - - - - - - - - - - - - + // {t('common.noData')} + ); }; const Empty = (props: EmptyProps) => { - const { className, children } = props; + const { className, children, type, text, iconWidth } = props; + const isDarkTheme = useIsDarkTheme(); + + const name = useMemo(() => { + return isDarkTheme + ? `empty/no-${type || EmptyType.Data}-dark` + : `empty/no-${type || EmptyType.Data}-bri`; + }, [isDarkTheme, type]); + return (
{ className, )} > - + {!children && ( -
- {t('common.noData')} +
+ {text || + (type === 'data' ? t('common.noData') : t('common.noResults'))}
)} {children} @@ -95,3 +50,65 @@ const Empty = (props: EmptyProps) => { }; export default Empty; + +export const EmptyCard = (props: EmptyCardProps) => { + const { icon, className, children, title, description, style } = props; + return ( +
+ {icon} + {title &&
{title}
} + {description && ( +
{description}
+ )} + {children} +
+ ); +}; + +export const EmptyAppCard = (props: { + type: EmptyCardType; + onClick?: () => void; + showIcon?: boolean; + className?: string; + size?: 'small' | 'large'; +}) => { + const { type, showIcon, className } = props; + let defaultClass = ''; + let style = {}; + switch (props.size) { + case 'small': + style = { width: '256px' }; + defaultClass = 'mt-1'; + break; + case 'large': + style = { width: '480px' }; + defaultClass = 'mt-5'; + break; + default: + defaultClass = ''; + break; + } + return ( +
+ +
+ +
+
+
+ ); +}; diff --git a/web/src/components/empty/interface.ts b/web/src/components/empty/interface.ts new file mode 100644 index 000000000..73fef1d60 --- /dev/null +++ b/web/src/components/empty/interface.ts @@ -0,0 +1,18 @@ +import { EmptyType } from './constant'; + +export type EmptyProps = { + className?: string; + children?: React.ReactNode; + type?: EmptyType; + text?: string; + iconWidth?: number; +}; + +export type EmptyCardProps = { + icon?: React.ReactNode; + className?: string; + children?: React.ReactNode; + title?: string; + description?: string; + style?: React.CSSProperties; +}; diff --git a/web/src/hooks/llm-hooks.tsx b/web/src/hooks/llm-hooks.tsx index d3e38427c..fb83d2974 100644 --- a/web/src/hooks/llm-hooks.tsx +++ b/web/src/hooks/llm-hooks.tsx @@ -58,7 +58,7 @@ export const useSelectLlmOptions = () => { function buildLlmOptionsWithIcon(x: IThirdOAIModel) { return { label: ( -
+
{ const [searchParams] = useSearchParams(); const { id } = useParams(); - const navigateToDatasetList = useCallback(() => { - navigate(Routes.Datasets); - }, [navigate]); + const navigateToDatasetList = useCallback( + ({ isCreate = false }: { isCreate?: boolean }) => { + if (isCreate) { + navigate(Routes.Datasets + '?isCreate=true'); + } else { + navigate(Routes.Datasets); + } + }, + [navigate], + ); const navigateToDataset = useCallback( (id: string) => () => { diff --git a/web/src/hooks/user-setting-hooks.tsx b/web/src/hooks/user-setting-hooks.tsx index bc4e177db..c43f5005b 100644 --- a/web/src/hooks/user-setting-hooks.tsx +++ b/web/src/hooks/user-setting-hooks.tsx @@ -54,7 +54,7 @@ export const useFetchTenantInfo = ( ): ResponseGetType => { const { t } = useTranslation(); const { data, isFetching: loading } = useQuery({ - queryKey: ['tenantInfo'], + queryKey: ['tenantInfo', showEmptyModelWarn], initialData: {}, gcTime: 0, queryFn: async () => { @@ -99,7 +99,6 @@ export const useSelectParserList = (): Array<{ label: string; }> => { const { data: tenantInfo } = useFetchTenantInfo(true); - const parserList = useMemo(() => { const parserArray: Array = tenantInfo?.parser_ids?.split(',') ?? []; return parserArray.map((x) => { diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 9a952a54c..b05c43b7d 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -3,7 +3,7 @@ export default { common: { confirm: 'Confirm', back: 'Back', - noResults: 'No results.', + noResults: 'No results found', selectPlaceholder: 'select value', selectAll: 'Select all', delete: 'Delete', @@ -51,7 +51,7 @@ export default { remove: 'Remove', search: 'Search', noDataFound: 'No data found.', - noData: 'No data', + noData: 'No data available', promptPlaceholder: `Please input or use / to quickly insert variables.`, mcp: { namePlaceholder: 'My MCP Server', @@ -2026,6 +2026,7 @@ Important structured information may include: names, dates, locations, events, k processingSuccessTip: 'Total successfully processed files', processingFailedTip: 'Total failed processes', processing: 'Processing', + noData: 'No log yet', }, deleteModal: { @@ -2039,6 +2040,15 @@ Important structured information may include: names, dates, locations, events, k delMember: 'Delete member', }, + empty: { + noMCP: 'No MCP servers available', + agentTitle: 'No agent app created yet', + datasetTitle: 'No dataset created yet', + chatTitle: 'No chat app created yet', + searchTitle: 'No search app created yet', + addNow: 'Add Now', + }, + admin: { loginTitle: 'Admin Console', title: 'RAGFlow', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index da264db7a..7601cc3c4 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -3,7 +3,7 @@ export default { common: { confirm: '确定', back: '返回', - noResults: '无结果。', + noResults: '未查到结果', selectPlaceholder: '请选择', selectAll: '全选', delete: '删除', @@ -1878,6 +1878,27 @@ Tokenizer 会根据所选方式将内容存储为对应的数据结构。`, downloadFailedTip: '下载失败总数', processingSuccessTip: '处理成功的文件总数', processingFailedTip: '处理失败的文件总数', + noData: '暂无日志', + }, + + deleteModal: { + delAgent: '删除智能体', + delDataset: '删除知识库', + delSearch: '删除搜索', + delFile: '删除文件', + delFiles: '删除文件', + delFilesContent: '已选择 {{count}} 个文件', + delChat: '删除聊天', + delMember: '删除成员', + }, + + empty: { + noMCP: '暂无 MCP 服务器可用', + agentTitle: '尚未创建智能体', + datasetTitle: '尚未创建数据集', + chatTitle: '尚未创建聊天应用', + searchTitle: '尚未创建搜索应用', + addNow: '立即添加', }, deleteModal: { diff --git a/web/src/pages/agents/index.tsx b/web/src/pages/agents/index.tsx index b3858926c..3600d3bd6 100644 --- a/web/src/pages/agents/index.tsx +++ b/web/src/pages/agents/index.tsx @@ -1,4 +1,6 @@ import { CardContainer } from '@/components/card-container'; +import { EmptyCardType } from '@/components/empty/constant'; +import { EmptyAppCard } from '@/components/empty/empty'; import ListFilterBar from '@/components/list-filter-bar'; import { RenameDialog } from '@/components/rename-dialog'; import { Button } from '@/components/ui/button'; @@ -14,7 +16,8 @@ import { useFetchAgentListByPage } from '@/hooks/use-agent-request'; import { t } from 'i18next'; import { pick } from 'lodash'; import { Clipboard, ClipboardPlus, FileInput, Plus } from 'lucide-react'; -import { useCallback } from 'react'; +import { useCallback, useEffect } from 'react'; +import { useSearchParams } from 'umi'; import { AgentCard } from './agent-card'; import { CreateAgentDialog } from './create-agent-dialog'; import { useCreateAgentOrPipeline } from './hooks/use-create-agent'; @@ -67,95 +70,120 @@ export default function Agents() { }, [setPagination], ); - + const [searchUrl, setSearchUrl] = useSearchParams(); + const isCreate = searchUrl.get('isCreate') === 'true'; + useEffect(() => { + if (isCreate) { + showCreatingModal(); + searchUrl.delete('isCreate'); + setSearchUrl(searchUrl); + } + }, [isCreate, showCreatingModal, searchUrl, setSearchUrl]); return ( -
-
- - - - - - - - - {t('flow.createFromBlank')} - - - - {t('flow.createFromTemplate')} - - - - {t('flow.importJsonFile')} - - - - -
-
- - {data.map((x) => { - return ( - - ); - })} - -
-
- -
- {agentRenameVisible && ( - + <> + {(!data?.length || data?.length <= 0) && ( +
+ showCreatingModal()} + /> +
)} - {creatingVisible && ( - - )} - {fileUploadVisible && ( - - )} -
+
+ {!!data?.length && ( + <> +
+ + + + + + + + + {t('flow.createFromBlank')} + + + + {t('flow.createFromTemplate')} + + + + {t('flow.importJsonFile')} + + + + +
+
+ + {data.map((x) => { + return ( + + ); + })} + +
+
+ +
+ + )} + {agentRenameVisible && ( + + )} + {creatingVisible && ( + + )} + {fileUploadVisible && ( + + )} +
+ ); } diff --git a/web/src/pages/dataset/dataset-overview/overview-table.tsx b/web/src/pages/dataset/dataset-overview/overview-table.tsx index 26e89cd61..a0aa79702 100644 --- a/web/src/pages/dataset/dataset-overview/overview-table.tsx +++ b/web/src/pages/dataset/dataset-overview/overview-table.tsx @@ -1,3 +1,5 @@ +import { EmptyType } from '@/components/empty/constant'; +import Empty from '@/components/empty/empty'; import FileStatusBadge from '@/components/file-status-badge'; import { FileIcon, IconFontFill } from '@/components/icon-font'; import { RAGFlowAvatar } from '@/components/ragflow-avatar'; @@ -344,6 +346,7 @@ const FileLogsTable: FC = ({ const [columnFilters, setColumnFilters] = useState([]); const [rowSelection, setRowSelection] = useState({}); const { t } = useTranslate('knowledgeDetails'); + const { t: tDatasetOverview } = useTranslate('datasetOverview'); const [isModalVisible, setIsModalVisible] = useState(false); const { navigateToDataflowResult } = useNavigatePage(); const [logInfo, setLogInfo] = useState(); @@ -445,7 +448,10 @@ const FileLogsTable: FC = ({ ) : ( - No results. + )} diff --git a/web/src/pages/dataset/dataset/dataset-table.tsx b/web/src/pages/dataset/dataset/dataset-table.tsx index cdbeaa3d3..a49d40ddc 100644 --- a/web/src/pages/dataset/dataset/dataset-table.tsx +++ b/web/src/pages/dataset/dataset/dataset-table.tsx @@ -14,6 +14,8 @@ import { import * as React from 'react'; import { ChunkMethodDialog } from '@/components/chunk-method-dialog'; +import { EmptyType } from '@/components/empty/constant'; +import Empty from '@/components/empty/empty'; import { RenameDialog } from '@/components/rename-dialog'; import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; import { @@ -164,7 +166,7 @@ export function DatasetTable({ ) : ( - No results. + )} diff --git a/web/src/pages/dataset/dataset/use-dataset-table-columns.tsx b/web/src/pages/dataset/dataset/use-dataset-table-columns.tsx index 3259cbe74..845c9e76a 100644 --- a/web/src/pages/dataset/dataset/use-dataset-table-columns.tsx +++ b/web/src/pages/dataset/dataset/use-dataset-table-columns.tsx @@ -136,7 +136,7 @@ export function useDatasetTableColumns({ { DataSourceInfo[ row.original.source_type as keyof typeof DataSourceInfo - ].icon + ]?.icon }
)} diff --git a/web/src/pages/dataset/testing/testing-result.tsx b/web/src/pages/dataset/testing/testing-result.tsx index 3c913ed33..853120cf5 100644 --- a/web/src/pages/dataset/testing/testing-result.tsx +++ b/web/src/pages/dataset/testing/testing-result.tsx @@ -1,3 +1,4 @@ +import { EmptyType } from '@/components/empty/constant'; import Empty from '@/components/empty/empty'; import { FormContainer } from '@/components/form-container'; import { FilterButton } from '@/components/list-filter-bar'; @@ -101,7 +102,7 @@ export function TestingResult({ {!data.chunks?.length && !loading && (
- + {data.isRuned && (
{t('knowledgeDetails.noTestResultsForRuned')} diff --git a/web/src/pages/datasets/index.tsx b/web/src/pages/datasets/index.tsx index 865c2dca0..c0515fc99 100644 --- a/web/src/pages/datasets/index.tsx +++ b/web/src/pages/datasets/index.tsx @@ -1,13 +1,17 @@ import { CardContainer } from '@/components/card-container'; +import { EmptyCardType } from '@/components/empty/constant'; +import { EmptyAppCard } from '@/components/empty/empty'; import ListFilterBar from '@/components/list-filter-bar'; import { RenameDialog } from '@/components/rename-dialog'; import { Button } from '@/components/ui/button'; import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; import { useFetchNextKnowledgeListByPage } from '@/hooks/use-knowledge-request'; +import { useQueryClient } from '@tanstack/react-query'; import { pick } from 'lodash'; import { Plus } from 'lucide-react'; -import { useCallback } from 'react'; +import { useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; +import { useSearchParams } from 'umi'; import { DatasetCard } from './dataset-card'; import { DatasetCreatingDialog } from './dataset-creating-dialog'; import { useSaveKnowledge } from './hooks'; @@ -52,59 +56,86 @@ export default function Datasets() { }, [setPagination], ); - + const [searchUrl, setSearchUrl] = useSearchParams(); + const isCreate = searchUrl.get('isCreate') === 'true'; + const queryClient = useQueryClient(); + useEffect(() => { + if (isCreate) { + queryClient.invalidateQueries({ queryKey: ['tenantInfo'] }); + showModal(); + searchUrl.delete('isCreate'); + setSearchUrl(searchUrl); + } + }, [isCreate, showModal, searchUrl, setSearchUrl]); return ( -
- - - -
- - {kbs.map((dataset) => { - return ( - - ); - })} - -
-
- -
- {visible && ( - - )} - {datasetRenameVisible && ( - - )} -
+ <> +
+ {(!kbs?.length || kbs?.length <= 0) && ( +
+ showModal()} + /> +
+ )} + {!!kbs?.length && ( + <> + + + +
+ + {kbs.map((dataset) => { + return ( + + ); + })} + +
+
+ +
+ + )} + {visible && ( + + )} + {datasetRenameVisible && ( + + )} +
+ ); } diff --git a/web/src/pages/home/agent-list.tsx b/web/src/pages/home/agent-list.tsx index 5b74c3db0..7c0e60128 100644 --- a/web/src/pages/home/agent-list.tsx +++ b/web/src/pages/home/agent-list.tsx @@ -3,11 +3,18 @@ import { MoreButton } from '@/components/more-button'; import { RenameDialog } from '@/components/rename-dialog'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useFetchAgentListByPage } from '@/hooks/use-agent-request'; +import { useEffect } from 'react'; import { AgentDropdown } from '../agents/agent-dropdown'; import { useRenameAgent } from '../agents/use-rename-agent'; -export function Agents() { - const { data } = useFetchAgentListByPage(); +export function Agents({ + setListLength, + setLoading, +}: { + setListLength: (length: number) => void; + setLoading?: (loading: boolean) => void; +}) { + const { data, loading } = useFetchAgentListByPage(); const { navigateToAgent } = useNavigatePage(); const { agentRenameLoading, @@ -18,6 +25,11 @@ export function Agents() { showAgentRenameModal, } = useRenameAgent(); + useEffect(() => { + setListLength(data?.length || 0); + setLoading?.(loading || false); + }, [data, setListLength, loading, setLoading]); + return ( <> {data.slice(0, 10).map((x) => ( diff --git a/web/src/pages/home/applications.tsx b/web/src/pages/home/applications.tsx index 4c07f160e..5686fc04f 100644 --- a/web/src/pages/home/applications.tsx +++ b/web/src/pages/home/applications.tsx @@ -1,4 +1,6 @@ import { CardSineLineContainer } from '@/components/card-singleline-container'; +import { EmptyCardType } from '@/components/empty/constant'; +import { EmptyAppCard } from '@/components/empty/empty'; import { HomeIcon } from '@/components/svg-icon'; import { Segmented, SegmentedValue } from '@/components/ui/segmented'; import { Routes } from '@/routes'; @@ -16,14 +18,29 @@ const IconMap = { [Routes.Agents]: 'agents', }; +const EmptyTypeMap = { + [Routes.Chats]: EmptyCardType.Chat, + [Routes.Searches]: EmptyCardType.Search, + [Routes.Agents]: EmptyCardType.Agent, +}; + export function Applications() { const [val, setVal] = useState(Routes.Chats); const { t } = useTranslation(); const navigate = useNavigate(); + const [listLength, setListLength] = useState(0); + const [loading, setLoading] = useState(false); - const handleNavigate = useCallback(() => { - navigate(val); - }, [navigate, val]); + const handleNavigate = useCallback( + ({ isCreate }: { isCreate?: boolean }) => { + if (isCreate) { + navigate(val + '?isCreate=true'); + } else { + navigate(val); + } + }, + [navigate, val], + ); const options = useMemo( () => [ @@ -36,16 +53,14 @@ export function Applications() { const handleChange = (path: SegmentedValue) => { setVal(path as Routes); + setListLength(0); + setLoading(true); }; return (

- {/* */} {/*
*/} - {val === Routes.Agents && } - {val === Routes.Chats && } - {val === Routes.Searches && } - {} + {val === Routes.Agents && ( + setListLength(length)} + setLoading={(loading: boolean) => setLoading(loading)} + > + )} + {val === Routes.Chats && ( + setListLength(length)} + setLoading={(loading: boolean) => setLoading(loading)} + > + )} + {val === Routes.Searches && ( + setListLength(length)} + setLoading={(loading: boolean) => setLoading(loading)} + > + )} + {listLength > 0 && ( + handleNavigate({ isCreate: false })} + > + )} + {listLength <= 0 && !loading && ( + handleNavigate({ isCreate: true })} + /> + )} {/*
*/}

); diff --git a/web/src/pages/home/chat-list.tsx b/web/src/pages/home/chat-list.tsx index 6178f85d7..c53ea1708 100644 --- a/web/src/pages/home/chat-list.tsx +++ b/web/src/pages/home/chat-list.tsx @@ -3,13 +3,20 @@ import { MoreButton } from '@/components/more-button'; import { RenameDialog } from '@/components/rename-dialog'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useFetchDialogList } from '@/hooks/use-chat-request'; +import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { ChatDropdown } from '../next-chats/chat-dropdown'; import { useRenameChat } from '../next-chats/hooks/use-rename-chat'; -export function ChatList() { +export function ChatList({ + setListLength, + setLoading, +}: { + setListLength: (length: number) => void; + setLoading?: (loading: boolean) => void; +}) { const { t } = useTranslation(); - const { data } = useFetchDialogList(); + const { data, loading } = useFetchDialogList(); const { navigateToChat } = useNavigatePage(); const { @@ -20,7 +27,10 @@ export function ChatList() { onChatRenameOk, chatRenameLoading, } = useRenameChat(); - + useEffect(() => { + setListLength(data?.dialogs?.length || 0); + setLoading?.(loading || false); + }, [data, setListLength, loading, setLoading]); return ( <> {data.dialogs.slice(0, 10).map((x) => ( diff --git a/web/src/pages/home/datasets.tsx b/web/src/pages/home/datasets.tsx index 4d78d34d0..34c4b9267 100644 --- a/web/src/pages/home/datasets.tsx +++ b/web/src/pages/home/datasets.tsx @@ -1,4 +1,6 @@ import { CardSineLineContainer } from '@/components/card-singleline-container'; +import { EmptyCardType } from '@/components/empty/constant'; +import { EmptyAppCard } from '@/components/empty/empty'; import { RenameDialog } from '@/components/rename-dialog'; import { HomeIcon } from '@/components/svg-icon'; import { CardSkeleton } from '@/components/ui/skeleton'; @@ -35,18 +37,32 @@ export function Datasets() {
) : ( - - {kbs - ?.slice(0, 6) - .map((dataset) => ( - - ))} - {} - + <> + {kbs?.length > 0 && ( + + {kbs + ?.slice(0, 6) + .map((dataset) => ( + + ))} + { + navigateToDatasetList({ isCreate: false })} + > + } + + )} + {kbs?.length <= 0 && ( + navigateToDatasetList({ isCreate: true })} + /> + )} + //
)}
diff --git a/web/src/pages/home/search-list.tsx b/web/src/pages/home/search-list.tsx index 90cc098d3..9474a54a0 100644 --- a/web/src/pages/home/search-list.tsx +++ b/web/src/pages/home/search-list.tsx @@ -3,11 +3,18 @@ import { IconFont } from '@/components/icon-font'; import { MoreButton } from '@/components/more-button'; import { RenameDialog } from '@/components/rename-dialog'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; +import { useEffect } from 'react'; import { useFetchSearchList, useRenameSearch } from '../next-searches/hooks'; import { SearchDropdown } from '../next-searches/search-dropdown'; -export function SearchList() { - const { data, refetch: refetchList } = useFetchSearchList(); +export function SearchList({ + setListLength, + setLoading, +}: { + setListLength: (length: number) => void; + setLoading?: (loading: boolean) => void; +}) { + const { data, refetch: refetchList, isLoading } = useFetchSearchList(); const { navigateToSearch } = useNavigatePage(); const { openCreateModal, @@ -22,6 +29,11 @@ export function SearchList() { refetchList(); }); }; + + useEffect(() => { + setListLength(data?.data?.search_apps?.length || 0); + setLoading?.(isLoading || false); + }, [data, setListLength, isLoading, setLoading]); return ( <> {data?.data.search_apps.slice(0, 10).map((x) => ( diff --git a/web/src/pages/next-chats/index.tsx b/web/src/pages/next-chats/index.tsx index 4b54ee74b..e6667252f 100644 --- a/web/src/pages/next-chats/index.tsx +++ b/web/src/pages/next-chats/index.tsx @@ -1,4 +1,6 @@ import { CardContainer } from '@/components/card-container'; +import { EmptyCardType } from '@/components/empty/constant'; +import { EmptyAppCard } from '@/components/empty/empty'; import ListFilterBar from '@/components/list-filter-bar'; import { RenameDialog } from '@/components/rename-dialog'; import { Button } from '@/components/ui/button'; @@ -6,8 +8,9 @@ import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; import { useFetchDialogList } from '@/hooks/use-chat-request'; import { pick } from 'lodash'; import { Plus } from 'lucide-react'; -import { useCallback } from 'react'; +import { useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; +import { useSearchParams } from 'umi'; import { ChatCard } from './chat-card'; import { useRenameChat } from './hooks/use-rename-chat'; @@ -35,41 +38,66 @@ export default function ChatList() { showChatRenameModal(); }, [showChatRenameModal]); + const [searchParams, setSearchParams] = useSearchParams(); + const isCreate = searchParams.get('isCreate') === 'true'; + useEffect(() => { + if (isCreate) { + handleShowCreateModal(); + searchParams.delete('isCreate'); + setSearchParams(searchParams); + } + }, [isCreate, handleShowCreateModal, searchParams, setSearchParams]); + return (
-
- - - -
-
- - {data.dialogs.map((x) => { - return ( - - ); - })} - -
-
- -
+ {data.dialogs?.length <= 0 && ( +
+ handleShowCreateModal()} + /> +
+ )} + {data.dialogs?.length > 0 && ( + <> +
+ + + +
+
+ + {data.dialogs.map((x) => { + return ( + + ); + })} + +
+
+ +
+ + )} {chatRenameVisible && ( void; } const RetrievalDocuments = ({ onTesting, selectedDocumentIds, setSelectedDocumentIds, + setLoading, }: IProps) => { const { documents: documentsAll } = useAllTestingResult(); const { documents } = useSelectTestingResult(); + const isTesting = useChunkIsTesting(); const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + useEffect(() => { + if (isTesting) { + setLoading?.(true); + } else { + setLoading?.(false); + } + }, [isTesting, setLoading]); + const { documents: useDocuments } = { documents: documentsAll?.length > documents?.length ? documentsAll : documents, @@ -45,6 +58,9 @@ const RetrievalDocuments = ({ useState(selectedDocumentIds); const multiOptions = useMemo(() => { + if (!useDocuments || !useDocuments.length) { + return []; + } return useDocuments?.map((item) => { return { label: item.doc_name, @@ -97,36 +113,38 @@ const RetrievalDocuments = ({ return ( -
- + + )} (''); + const [retrievalLoading, setRetrievalLoading] = useState(false); useEffect(() => { setSearchtext(searchStr); @@ -182,6 +185,9 @@ export default function SearchingView({ selectedDocumentIds={selectedDocumentIds} setSelectedDocumentIds={setSelectedDocumentIds} onTesting={handleTestChunk} + setLoading={(loading: boolean) => { + setRetrievalLoading(loading); + }} >
{/*
*/} @@ -264,6 +270,17 @@ export default function SearchingView({ )}
+ {!isSearchStrEmpty && + !retrievalLoading && + !answer.answer && + !sendingLoading && + total <= 0 && + chunks?.length <= 0 && + relatedQuestions?.length <= 0 && ( +
+ +
+ )} {total > 0 && ( diff --git a/web/src/pages/next-searches/index.tsx b/web/src/pages/next-searches/index.tsx index b4e28152b..4267dbbe7 100644 --- a/web/src/pages/next-searches/index.tsx +++ b/web/src/pages/next-searches/index.tsx @@ -1,4 +1,6 @@ import { CardContainer } from '@/components/card-container'; +import { EmptyCardType } from '@/components/empty/constant'; +import { EmptyAppCard } from '@/components/empty/empty'; import { IconFont } from '@/components/icon-font'; import ListFilterBar from '@/components/list-filter-bar'; import { RenameDialog } from '@/components/rename-dialog'; @@ -6,6 +8,8 @@ import { Button } from '@/components/ui/button'; import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; import { useTranslate } from '@/hooks/common-hooks'; import { Plus } from 'lucide-react'; +import { useCallback, useEffect } from 'react'; +import { useSearchParams } from 'umi'; import { useFetchSearchList, useRenameSearch } from './hooks'; import { SearchCard } from './search-card'; @@ -27,6 +31,7 @@ export default function SearchList() { onSearchRenameOk, initialSearchName, } = useRenameSearch(); + const handleSearchChange = (value: string) => { console.log(value); }; @@ -35,61 +40,86 @@ export default function SearchList() { refetchList(); }); }; - const openCreateModalFun = () => { + const openCreateModalFun = useCallback(() => { // setIsEdit(false); showSearchRenameModal(); - }; + }, [showSearchRenameModal]); const handlePageChange = (page: number, pageSize: number) => { // setIsEdit(false); setSearchListParams({ ...searchParams, page, page_size: pageSize }); }; + const [searchUrl, setSearchUrl] = useSearchParams(); + const isCreate = searchUrl.get('isCreate') === 'true'; + useEffect(() => { + if (isCreate) { + openCreateModalFun(); + searchUrl.delete('isCreate'); + setSearchUrl(searchUrl); + } + }, [isCreate, openCreateModalFun, searchUrl, setSearchUrl]); + return (
-
- handleSearchChange(e.target.value)} - > - - -
-
- - {list?.data.search_apps.map((x) => { - return ( - { - showSearchRenameModal(x); - }} - > - ); - })} - -
- {list?.data.total && list?.data.total > 0 && ( -
- + openCreateModalFun()} />
)} - + {!!list?.data?.search_apps?.length && ( + <> +
+ handleSearchChange(e.target.value)} + > + + +
+
+ + {list?.data.search_apps.map((x) => { + return ( + { + showSearchRenameModal(x); + }} + > + ); + })} + +
+ {list?.data.total && list?.data.total > 0 && ( +
+ +
+ )} + + )} {openCreateModal && ( } > - {isSelectionMode && ( -
- - - - {t('mcp.selected')} {selectedList.length} - -
- - - ), - }} - > - - -
-
+ {!data.mcp_servers?.length && ( +
+
{t('empty.noMCP')}
+ +
+ )} + {!!data.mcp_servers?.length && ( + <> + {isSelectionMode && ( +
+ + + + {t('mcp.selected')} {selectedList.length} + +
+ + + ), + }} + > + + +
+
+ )} + + {data.mcp_servers.map((item) => ( + + ))} + +
+ +
+ )} - - {data.mcp_servers.map((item) => ( - - ))} - -
- -
{editVisible && ( = ({
-
{item.name}
+
+ {item.name} +