From 7c719f836561364f41977bfa2777703fb05b7ad0 Mon Sep 17 00:00:00 2001 From: chanx <1243304602@qq.com> Date: Thu, 7 Aug 2025 11:07:04 +0800 Subject: [PATCH] fix: Optimized popups and the search page (#9297) ### What problem does this PR solve? fix: Optimized popups and the search page #3221 - Added a new PortalModal component - Refactored the Modal component, adding show and hide methods to support popups - Updated the search page, adding a new query function and optimizing the search card style - Localized, added search-related translations ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) --- web/src/components/ui/modal/modal-manage.tsx | 102 ++++++++++++++++++ web/src/components/ui/{ => modal}/modal.tsx | 99 ++++++++--------- web/src/locales/en.ts | 3 + web/src/locales/zh.ts | 3 + .../pages/agents/agent-log-detail-modal.tsx | 2 +- .../components/chunk-creating-modal/index.tsx | 2 +- .../components/chunk-result-bar/index.tsx | 2 +- web/src/pages/dataset/setting/index.tsx | 10 +- .../next-chats/share/parameter-dialog.tsx | 2 +- web/src/pages/next-searches/index.tsx | 62 +++++++++-- web/src/pages/next-searches/search-card.tsx | 65 +++++------ 11 files changed, 254 insertions(+), 98 deletions(-) create mode 100644 web/src/components/ui/modal/modal-manage.tsx rename web/src/components/ui/{ => modal}/modal.tsx (72%) diff --git a/web/src/components/ui/modal/modal-manage.tsx b/web/src/components/ui/modal/modal-manage.tsx new file mode 100644 index 000000000..54af5a7c3 --- /dev/null +++ b/web/src/components/ui/modal/modal-manage.tsx @@ -0,0 +1,102 @@ +import { ReactNode, useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { createRoot } from 'react-dom/client'; +import { Modal, ModalProps } from './modal'; + +type PortalModalProps = Omit & { + visible: boolean; + onVisibleChange: (visible: boolean) => void; + container?: HTMLElement; + children: ReactNode; + [key: string]: any; +}; + +const PortalModal = ({ + visible, + onVisibleChange, + container, + children, + ...restProps +}: PortalModalProps) => { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + return () => setMounted(false); + }, []); + + if (!mounted || !visible) return null; + console.log('PortalModal:', visible); + return createPortal( + + {children} + , + container || document.body, + ); +}; + +export const createPortalModal = () => { + let container = document.createElement('div'); + document.body.appendChild(container); + + let currentProps: any = {}; + let isVisible = false; + let root: ReturnType | null = null; + + root = createRoot(container); + const destroy = () => { + if (root && container) { + root.unmount(); + if (container.parentNode) { + container.parentNode.removeChild(container); + } + root = null; + } + isVisible = false; + currentProps = {}; + }; + const render = () => { + const { onVisibleChange, ...props } = currentProps; + const modalParam = { + visible: isVisible, + + onVisibleChange: (visible: boolean) => { + isVisible = visible; + if (onVisibleChange) { + onVisibleChange(visible); + } + + if (!visible) { + render(); + } + }, + ...props, + }; + root?.render(isVisible ? : null); + }; + + const show = (props: PortalModalProps) => { + if (!container) { + container = document.createElement('div'); + document.body.appendChild(container); + } + if (!root) { + root = createRoot(container); + } + currentProps = { ...currentProps, ...props }; + isVisible = true; + render(); + }; + + const hide = () => { + isVisible = false; + render(); + }; + + const update = (props = {}) => { + currentProps = { ...currentProps, ...props }; + render(); + }; + + return { show, hide, update, destroy }; +}; diff --git a/web/src/components/ui/modal.tsx b/web/src/components/ui/modal/modal.tsx similarity index 72% rename from web/src/components/ui/modal.tsx rename to web/src/components/ui/modal/modal.tsx index 716d3352a..f018b8988 100644 --- a/web/src/components/ui/modal.tsx +++ b/web/src/components/ui/modal/modal.tsx @@ -1,15 +1,19 @@ // src/components/ui/modal.tsx +import { cn } from '@/lib/utils'; import * as DialogPrimitive from '@radix-ui/react-dialog'; import { Loader, X } from 'lucide-react'; import { FC, ReactNode, useCallback, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; +import { createPortalModal } from './modal-manage'; -interface ModalProps { +export interface ModalProps { open: boolean; onOpenChange?: (open: boolean) => void; title?: ReactNode; + titleClassName?: string; children: ReactNode; footer?: ReactNode; + footerClassName?: string; showfooter?: boolean; className?: string; size?: 'small' | 'default' | 'large'; @@ -24,13 +28,19 @@ interface ModalProps { onOk?: () => void; onCancel?: () => void; } +export interface ModalType extends FC { + show: typeof modalIns.show; + hide: typeof modalIns.hide; +} -export const Modal: FC = ({ +const Modal: ModalType = ({ open, onOpenChange, title, + titleClassName, children, footer, + footerClassName, showfooter = true, className = '', size = 'default', @@ -74,6 +84,7 @@ export const Modal: FC = ({ }, [onOpenChange, onOk]); const handleChange = (open: boolean) => { onOpenChange?.(open); + console.log('open', open, onOpenChange); if (open) { handleOk(); } @@ -113,7 +124,12 @@ export const Modal: FC = ({ ); } return ( -
+
{footerTemp}
); @@ -126,6 +142,7 @@ export const Modal: FC = ({ handleCancel, handleOk, showfooter, + footerClassName, ]); return ( @@ -139,11 +156,23 @@ export const Modal: FC = ({ onClick={(e) => e.stopPropagation()} > {/* title */} - {title && ( -
- - {title} - + {(title || closable) && ( +
+ {title && ( + + {title} + + )} {closable && (
)} - {/* title */} - {!title && ( - - )} {/* content */} -
+
{destroyOnClose && !open ? null : children}
@@ -175,43 +200,13 @@ export const Modal: FC = ({ ); }; -// example usage -/* -import { Modal } from '@/components/ui/modal'; +let modalIns = createPortalModal(); +Modal.show = modalIns + ? modalIns.show + : () => { + modalIns = createPortalModal(); + return modalIns.show; + }; +Modal.hide = modalIns.hide; -function Demo() { - const [open, setOpen] = useState(false); - - return ( -
- - - - - -
- } - > -
弹窗内容区域
- - -
弹窗内容区域
-
-
- ); -} -*/ +export { Modal }; diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index fd2ebda5c..083e2fe4d 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -1376,5 +1376,8 @@ This delimiter is used to split the input text into several text pieces echo of addMCP: 'Add MCP', editMCP: 'Edit MCP', }, + search: { + createSearch: 'Create Search', + }, }, }; diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index b65148b4a..6223f248b 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -1310,4 +1310,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 okText: '确认', cancelText: '取消', }, + search: { + createSearch: '新建查询', + }, }; diff --git a/web/src/pages/agents/agent-log-detail-modal.tsx b/web/src/pages/agents/agent-log-detail-modal.tsx index 1d8a32eb0..beabe8c5f 100644 --- a/web/src/pages/agents/agent-log-detail-modal.tsx +++ b/web/src/pages/agents/agent-log-detail-modal.tsx @@ -1,5 +1,5 @@ import MessageItem from '@/components/next-message-item'; -import { Modal } from '@/components/ui/modal'; +import { Modal } from '@/components/ui/modal/modal'; import { useFetchAgent } from '@/hooks/use-agent-request'; import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; import { IAgentLogMessage } from '@/interfaces/database/agent'; diff --git a/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx b/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx index fb076cf52..f3be633f0 100644 --- a/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx +++ b/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx @@ -13,7 +13,7 @@ import { HoverCardContent, HoverCardTrigger, } from '@/components/ui/hover-card'; -import { Modal } from '@/components/ui/modal'; +import { Modal } from '@/components/ui/modal/modal'; import Space from '@/components/ui/space'; import { Switch } from '@/components/ui/switch'; import { Textarea } from '@/components/ui/textarea'; diff --git a/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-result-bar/index.tsx b/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-result-bar/index.tsx index fc7a33cad..e80e9f0e4 100644 --- a/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-result-bar/index.tsx +++ b/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-result-bar/index.tsx @@ -61,7 +61,7 @@ export default ({ }; return (
-
+
{textSelectOptions.map((option) => (
- + -
- +
+ General
@@ -113,8 +113,8 @@ export default function DatasetSettings() { value="chunkMethodForm" className="group bg-transparent p-0 !border-transparent" > -
- +
+ Chunk Method
diff --git a/web/src/pages/next-chats/share/parameter-dialog.tsx b/web/src/pages/next-chats/share/parameter-dialog.tsx index a0f249b69..7b363836e 100644 --- a/web/src/pages/next-chats/share/parameter-dialog.tsx +++ b/web/src/pages/next-chats/share/parameter-dialog.tsx @@ -1,4 +1,4 @@ -import { Modal } from '@/components/ui/modal'; +import { Modal } from '@/components/ui/modal/modal'; import { IModalProps } from '@/interfaces/common'; import DebugContent from '@/pages/agent/debug-content'; import { buildBeginInputListFromObject } from '@/pages/agent/form/begin-form/utils'; diff --git a/web/src/pages/next-searches/index.tsx b/web/src/pages/next-searches/index.tsx index ff7962b6b..1b825fe24 100644 --- a/web/src/pages/next-searches/index.tsx +++ b/web/src/pages/next-searches/index.tsx @@ -1,23 +1,73 @@ import ListFilterBar from '@/components/list-filter-bar'; +import { Input } from '@/components/originui/input'; import { Button } from '@/components/ui/button'; +import { Modal } from '@/components/ui/modal/modal'; +import { useTranslate } from '@/hooks/common-hooks'; import { useFetchFlowList } from '@/hooks/flow-hooks'; -import { Plus } from 'lucide-react'; +import { Plus, Search } from 'lucide-react'; +import { useState } from 'react'; import { SearchCard } from './search-card'; export default function SearchList() { const { data } = useFetchFlowList(); - + const { t } = useTranslate('search'); + const [searchName, setSearchName] = useState(''); + const handleSearchChange = (value: string) => { + console.log(value); + }; return (
- -
+ } + title="Search apps" + showFilter={false} + onSearchChange={(e) => handleSearchChange(e.target.value)} + > +
-
+
{data.map((x) => { return ; })} diff --git a/web/src/pages/next-searches/search-card.tsx b/web/src/pages/next-searches/search-card.tsx index e09adba1d..3f8188a63 100644 --- a/web/src/pages/next-searches/search-card.tsx +++ b/web/src/pages/next-searches/search-card.tsx @@ -1,4 +1,5 @@ -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { MoreButton } from '@/components/more-button'; +import { RAGFlowAvatar } from '@/components/ragflow-avatar'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; @@ -15,38 +16,40 @@ export function SearchCard({ data }: IProps) { return ( - +
- {data.avatar ? ( -
- ) : ( - - - CN - - )} + +
+
+
+
+ {data.title} +
+ +
+ +
An app that does things An app that does things
+
+
+ Search app +

+ {formatPureDate(data.update_time)} +

+
+
+ + +
+
-

{data.title}

-

An app that does things An app that does things

-
-
- Search app -

- {formatPureDate(data.update_time)} -

-
-
- - -
-
);