From ef4cbe72a37d64644f58d3bd7775a8d13162cf05 Mon Sep 17 00:00:00 2001 From: Jimmy Ben Klieve Date: Thu, 5 Mar 2026 20:47:29 +0800 Subject: [PATCH] refactor(ui): adjust global navigation bar style (#13419) ### What problem does this PR solve? Renovate global navigation bar, align styles to the design. (May causes minor layout issues in sub-pages, will check and fix soon) ### Type of change - [x] Refactoring --- web/.eslintrc.cjs | 3 + web/src/app.tsx | 11 +- web/src/components/embed-dialog/index.tsx | 109 ++++--- web/src/components/form-container.tsx | 7 +- .../components/highlight-markdown/index.tsx | 2 + web/src/components/list-filter-bar/index.tsx | 12 +- web/src/components/ui/button.tsx | 118 ++++--- web/src/components/ui/card.tsx | 15 +- web/src/components/ui/checkbox.tsx | 2 +- web/src/components/ui/dropdown-menu.tsx | 8 +- web/src/components/ui/skeleton.tsx | 12 +- web/src/components/what-is-this.tsx | 15 + web/src/global.less | 32 +- web/src/layouts/bell-button.tsx | 34 +- web/src/layouts/global-navbar.tsx | 158 +++++++++ web/src/layouts/index.module.less | 27 -- web/src/layouts/next-header.tsx | 237 +++++--------- web/src/layouts/next.tsx | 19 +- web/src/layouts/page-container.tsx | 19 ++ web/src/layouts/theme-button.tsx | 22 ++ web/src/pages/agents/agent-templates.tsx | 27 -- .../components/knowledge-chunk/index.tsx | 42 +-- .../pages/dataset/dataset-overview/index.tsx | 107 +++--- .../dataset-overview/overview-table.tsx | 20 +- .../pages/dataset/dataset-setting/index.tsx | 197 ++++++----- .../dataset/generate-button/generate.tsx | 67 ++-- web/src/pages/dataset/dataset/index.tsx | 4 +- web/src/pages/dataset/index.tsx | 44 +-- web/src/pages/dataset/sidebar/index.tsx | 81 +++-- web/src/pages/dataset/testing/index.tsx | 153 +++++---- .../pages/dataset/testing/testing-form.tsx | 81 +++-- .../pages/dataset/testing/testing-result.tsx | 98 +++--- web/src/pages/datasets/index.tsx | 4 +- web/src/pages/home/banner.tsx | 8 +- web/src/pages/home/index.tsx | 16 +- web/src/pages/login-next/index.tsx | 172 +++++----- .../chat/chat-box/next-multiple-chat-box.tsx | 2 +- web/src/pages/next-chats/chat/index.tsx | 136 +++----- web/src/pages/next-chats/chat/sessions.tsx | 40 ++- web/src/pages/next-search/index.tsx | 32 +- web/src/pages/user-setting/index.tsx | 47 +-- web/src/routes.tsx | 307 +++++++----------- web/src/utils/css-support.ts | 5 + web/tailwind.config.js | 24 +- web/tailwind.css | 8 + 45 files changed, 1334 insertions(+), 1250 deletions(-) create mode 100644 web/src/components/what-is-this.tsx create mode 100644 web/src/layouts/global-navbar.tsx delete mode 100644 web/src/layouts/index.module.less create mode 100644 web/src/layouts/page-container.tsx create mode 100644 web/src/layouts/theme-button.tsx create mode 100644 web/src/utils/css-support.ts diff --git a/web/.eslintrc.cjs b/web/.eslintrc.cjs index 689dec1fc..c30c88fe2 100644 --- a/web/.eslintrc.cjs +++ b/web/.eslintrc.cjs @@ -14,6 +14,9 @@ module.exports = { jsx: true, }, }, + includes: [ + './src', + ], settings: { react: { version: 'detect', diff --git a/web/src/app.tsx b/web/src/app.tsx index 4d84b8b3b..4f6823ccd 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -3,7 +3,7 @@ import { Toaster } from '@/components/ui/toaster'; import i18n, { changeLanguageAsync } from '@/locales/config'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { configResponsive } from 'ahooks'; -import { App, ConfigProvider, ConfigProviderProps, theme } from 'antd'; +import { ConfigProvider, ConfigProviderProps, theme } from 'antd'; import pt_BR from 'antd/lib/locale/pt_BR'; import arEG from 'antd/locale/ar_EG'; import deDE from 'antd/locale/de_DE'; @@ -22,7 +22,6 @@ import weekday from 'dayjs/plugin/weekday'; import React, { useEffect, useState } from 'react'; import { RouterProvider } from 'react-router'; import { ThemeProvider, useTheme } from './components/theme-provider'; -import { SidebarProvider } from './components/ui/sidebar'; import { TooltipProvider } from './components/ui/tooltip'; import { ThemeEnum } from './constants/common'; import { routers } from './routes'; @@ -125,10 +124,10 @@ function Root({ children }: React.PropsWithChildren) { }} locale={locale} > - - {children} - - + {children} + + + diff --git a/web/src/components/embed-dialog/index.tsx b/web/src/components/embed-dialog/index.tsx index 268b84f4b..3d2f8caff 100644 --- a/web/src/components/embed-dialog/index.tsx +++ b/web/src/components/embed-dialog/index.tsx @@ -1,5 +1,4 @@ import CopyToClipboard from '@/components/copy-to-clipboard'; -import HighLightMarkdown from '@/components/highlight-markdown'; import { SelectWithSearch } from '@/components/originui/select-with-search'; import { Button } from '@/components/ui/button'; import { @@ -32,7 +31,13 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { ExternalLink } from 'lucide-react'; import { memo, useCallback, useMemo } from 'react'; import { useForm, useWatch } from 'react-hook-form'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { + oneDark, + oneLight, +} from 'react-syntax-highlighter/dist/esm/styles/prism'; import { z } from 'zod'; +import { useIsDarkTheme } from '../theme-provider'; const FormSchema = z.object({ visibleAvatar: z.boolean(), @@ -56,8 +61,10 @@ function EmbedDialog({ from, beta = '', isAgent, + visible, }: IProps) { const { t } = useTranslate('chat'); + const isDarkTheme = useIsDarkTheme(); const form = useForm>({ resolver: zodResolver(FormSchema), @@ -95,23 +102,30 @@ function EmbedDialog({ : from === SharedFrom.Agent ? Routes.AgentShare : Routes.ChatShare; - let src = `${location.origin}${baseRoute}?shared_id=${token}&from=${from}&auth=${beta}`; + + const src = new URL(`${location.origin}${baseRoute}`); + src.searchParams.append('shared_id', token); + src.searchParams.append('from', from); + src.searchParams.append('auth', beta); + if (publishAvatar) { - src += '&release=true'; + src.searchParams.append('release', 'true'); } if (visibleAvatar) { - src += '&visible_avatar=1'; + src.searchParams.append('visible_avatar', '1'); } if (locale) { - src += `&locale=${locale}`; + src.searchParams.append('locale', locale); } - if (enableStreaming) { - src += '&streaming=true'; + if (embedType === 'widget') { + src.searchParams.append('mode', 'master'); + src.searchParams.append('streaming', String(enableStreaming)); } if (theme && embedType === 'fullscreen') { - src += `&theme=${theme}`; + src.searchParams.append('theme', theme); } - return src; + + return src.toString(); }, [beta, from, token, values]); const text = useMemo(() => { @@ -119,44 +133,36 @@ function EmbedDialog({ const { embedType } = values; if (embedType === 'widget') { - const { enableStreaming } = values; - const streamingParam = enableStreaming - ? '&streaming=true' - : '&streaming=false'; - return ` - ~~~ html - - -~~~ - `; + return ` + +`; } else { - return ` - ~~~ html - -~~~ - `; +> +`; } }, [generateIframeSrc, values]); @@ -166,13 +172,14 @@ function EmbedDialog({ }, [generateIframeSrc]); return ( - + {t('embedIntoSite', { keyPrefix: 'common' })} +
@@ -309,10 +316,16 @@ function EmbedDialog({ />
-
+
{t('embedCode', { keyPrefix: 'search' })} -
- {text} +
+ + {text} +
-
+
{token}
diff --git a/web/src/components/form-container.tsx b/web/src/components/form-container.tsx index 097e7196b..d0c8f78dd 100644 --- a/web/src/components/form-container.tsx +++ b/web/src/components/form-container.tsx @@ -12,7 +12,12 @@ export function FormContainer({ className, }: FormContainerProps) { return show ? ( -
+
{children}
) : ( diff --git a/web/src/components/highlight-markdown/index.tsx b/web/src/components/highlight-markdown/index.tsx index b19a31808..b2c5bfaab 100644 --- a/web/src/components/highlight-markdown/index.tsx +++ b/web/src/components/highlight-markdown/index.tsx @@ -19,8 +19,10 @@ import { useIsDarkTheme } from '../theme-provider'; import styles from './index.module.less'; const HighLightMarkdown = ({ + className, children, }: { + className?: string; children: string | null | undefined; }) => { const isDarkTheme = useIsDarkTheme(); diff --git a/web/src/components/list-filter-bar/index.tsx b/web/src/components/list-filter-bar/index.tsx index 145d17d6e..3ee505220 100644 --- a/web/src/components/list-filter-bar/index.tsx +++ b/web/src/components/list-filter-bar/index.tsx @@ -25,7 +25,12 @@ export const FilterButton = React.forwardRef< ButtonProps & { count?: number } >(({ count = 0, ...props }, ref) => { return ( - ); }); diff --git a/web/src/components/ui/button.tsx b/web/src/components/ui/button.tsx index ccdfb2827..bdf674790 100644 --- a/web/src/components/ui/button.tsx +++ b/web/src/components/ui/button.tsx @@ -4,40 +4,57 @@ import * as React from 'react'; import { cn } from '@/lib/utils'; import { LucideLoader2, Plus } from 'lucide-react'; +import { Link, LinkProps } from 'react-router'; const buttonVariants = cva( cn( 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-0', - 'disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*="size-"])]:size-4 shrink-0 [&_svg]:shrink-0', + 'disabled:pointer-events-none disabled:opacity-50 rounded border-0.5 border-transparent', + '[&_svg]:pointer-events-none [&_svg:not([class*="size-"])]:size-4 shrink-0 [&_svg]:shrink-0', ), { variants: { variant: { + // Solid variant series: + // Button has its own background color, may have borders default: 'bg-text-primary text-bg-base shadow-xs hover:bg-text-primary/90 focus-visible:bg-text-primary/90', + secondary: ` + bg-bg-card + hover:text-text-primary hover:bg-border-button + focus-visible:text-text-primary focus-visible:bg-border-button + `, + + highlighted: ` + bg-text-primary text-bg-base border-b-4 border-b-accent-primary + hover:bg-text-primary/90 focus-visible:bg-text-primary/90 + `, + + accent: ` + bg-accent-primary text-white + hover:bg-accent-primary/90 focus-visible:bg-accent-primary/90 + `, + destructive: ` bg-state-error text-white shadow-xs hover:bg-state-error/90 focus-visible:ring-state-error/20 dark:focus-visible:ring-state-error/40 `, + + // Outline variant series + // Button has transparent or greyish background, may have borders outline: ` text-text-secondary bg-bg-input border-0.5 border-border-button hover:text-text-primary hover:bg-border-button hover:border-border-default focus-visible:text-text-primary focus-visible:bg-border-button focus-visible:border-border-button - `, - secondary: - 'bg-bg-input text-text-primary shadow-xs hover:bg-bg-input/80 border border-border-button', + `, // light: bg=transparent, dark: bg-input - ghost: ` - text-text-secondary - hover:bg-border-button hover:text-text-primary - focus-visible:text-text-primary focus-visible:bg-border-button + dashed: ` + text-text-secondary border-border-button border-dashed + hover:text-text-primary hover:bg-border-button hover:border-border-default + focus-visible:text-text-primary focus-visible:bg-border-button focus-visible:border-border-button `, - link: 'text-primary underline-offset-4 hover:underline', - icon: 'bg-colors-background-inverse-standard text-foreground hover:bg-colors-background-inverse-standard/80', - dashed: 'border border-dashed border-border-button hover:bg-accent', - transparent: ` text-text-secondary bg-transparent border-0.5 border-border-button hover:text-text-primary hover:bg-border-button @@ -49,14 +66,12 @@ const buttonVariants = cva( hover:bg-state-error/10 focus-visible:bg-state-error/10 `, - accent: ` - bg-accent-primary text-white - hover:bg-accent-primary/90 focus-visible:bg-accent-primary/90 - `, - - highlighted: ` - bg-text-primary text-bg-base border-b-4 border-b-accent-primary - hover:bg-text-primary/90 focus-visible:bg-text-primary/90 + // Ghost variant series + // Button has transparent background, without borders + ghost: ` + text-text-secondary + hover:bg-border-button focus-visible:bg-border-button + hover:text-text-primary focus-visible:text-text-primary `, delete: ` @@ -64,15 +79,23 @@ const buttonVariants = cva( hover:bg-state-error-5 hover:text-state-error focus-visible:text-state-error focus-visible:bg-state-error-5 `, + + link: 'text-primary underline-offset-4 hover:underline', }, size: { - default: 'h-8 px-2.5 py-1.5 ', - sm: 'h-6 rounded-sm px-2', - lg: 'h-11 rounded-md px-8', - icon: 'h-10 w-10', auto: 'h-full px-1', - 'icon-sm': 'size-8', - 'icon-xs': 'size-7', + + xl: 'h-12 rounded-xl px-5', + lg: 'h-10 rounded-lg px-4', + default: 'h-8 rounded px-3', + sm: 'h-7 rounded-sm px-2', + xs: 'h-6 rounded-xs px-1', + + 'icon-xl': 'size-12 rounded-xl', + 'icon-lg': 'size-10 rounded-lg', + icon: 'size-8 rounded', + 'icon-sm': 'size-7 rounded-sm', + 'icon-xs': 'size-6 rounded-xs', }, }, defaultVariants: { @@ -82,45 +105,58 @@ const buttonVariants = cva( }, ); -export interface ButtonProps - extends - React.ButtonHTMLAttributes, - VariantProps { +export type ButtonProps = { asChild?: boolean; + asLink?: boolean; loading?: boolean; block?: boolean; -} + disabled?: boolean; + dot?: boolean; +} & VariantProps & + (IsAnchor extends true + ? LinkProps + : React.ButtonHTMLAttributes); -const Button = React.forwardRef( - ( +const Button = React.forwardRef( + ( { children, className, variant, size, + dot = false, asChild = false, + asLink = false, loading = false, disabled = false, block = false, ...props - }, - ref, + }: ButtonProps, + ref: React.ForwardedRef< + IsAnchor extends true ? HTMLAnchorElement : HTMLButtonElement + >, ) => { - const Comp = asChild ? Slot : 'button'; + const Comp = asChild ? Slot : asLink ? Link : 'button'; return ( } disabled={loading || disabled} {...props} > - {loading && } - {children} + <> + {dot && ( + + )} + {loading && } + {children} + ); }, diff --git a/web/src/components/ui/card.tsx b/web/src/components/ui/card.tsx index 281199a16..04e812047 100644 --- a/web/src/components/ui/card.tsx +++ b/web/src/components/ui/card.tsx @@ -30,15 +30,12 @@ const CardHeader = React.forwardRef< CardHeader.displayName = 'CardHeader'; const CardTitle = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
& { as?: React.ElementType } +>(({ className, as: As = 'div', ...props }, ref) => ( + )); @@ -50,7 +47,7 @@ const CardDescription = React.forwardRef< >(({ className, ...props }, ref) => (
)); diff --git a/web/src/components/ui/checkbox.tsx b/web/src/components/ui/checkbox.tsx index bebf2a6b6..206f55353 100644 --- a/web/src/components/ui/checkbox.tsx +++ b/web/src/components/ui/checkbox.tsx @@ -13,7 +13,7 @@ const Checkbox = React.forwardRef< ) { return (
); @@ -26,12 +26,10 @@ function ParagraphSkeleton() { function CardSkeleton() { return ( -
- -
- - -
+
+ + +
); } diff --git a/web/src/components/what-is-this.tsx b/web/src/components/what-is-this.tsx new file mode 100644 index 000000000..52d297aaa --- /dev/null +++ b/web/src/components/what-is-this.tsx @@ -0,0 +1,15 @@ +import { LucideCircleQuestionMark } from 'lucide-react'; + +import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; + +export default function WhatIsThis({ children }: React.PropsWithChildren<{}>) { + return ( + + + + + + {children} + + ); +} diff --git a/web/src/global.less b/web/src/global.less index 0fb973cc5..ddf5a1d89 100644 --- a/web/src/global.less +++ b/web/src/global.less @@ -1,28 +1,26 @@ @import url(./inter.less); -html { - height: 100%; - overflow: hidden; // The content of the DatasetSettings page is too high, which will cause scroll bars to appear on the html tags. Setting the maximum height in DatasetSettings does not work either. I don't understand. +:root { + --font-sans: 'Inter'; } -body { - font-family: - 'Inter', - system-ui, - -apple-system, - 'Segoe UI', - sans-serif; - margin: 0; - height: 100%; +@supports (font-variation-settings: normal) { + :root { + --font-sans: 'InterVariable'; + } } +html, +body, #root { - height: 100%; -} + width: 100vw; + width: 100dvw; + height: 100vh; + height: 100dvh; + overflow: hidden; -.ant-app { - height: 100%; - width: 100%; + margin: 0; + padding: 0; } .vue-office-excel { diff --git a/web/src/layouts/bell-button.tsx b/web/src/layouts/bell-button.tsx index 493391cc7..0f4929866 100644 --- a/web/src/layouts/bell-button.tsx +++ b/web/src/layouts/bell-button.tsx @@ -1,28 +1,18 @@ import { Button } from '@/components/ui/button'; -import { useNavigateWithFromState } from '@/hooks/route-hook'; -import { useListTenant } from '@/hooks/use-user-setting-request'; -import { TenantRole } from '@/pages/user-setting/constants'; +import { Routes } from '@/routes'; import { BellRing } from 'lucide-react'; -import { useCallback, useMemo } from 'react'; export function BellButton() { - const { data } = useListTenant(); - const navigate = useNavigateWithFromState(); - - const showBell = useMemo(() => { - return data.some((x) => x.role === TenantRole.Invite); - }, [data]); - - const handleBellClick = useCallback(() => { - navigate('/user-setting/team'); - }, [navigate]); - - return showBell ? ( - - ) : null; + ); } diff --git a/web/src/layouts/global-navbar.tsx b/web/src/layouts/global-navbar.tsx new file mode 100644 index 000000000..f52ed4928 --- /dev/null +++ b/web/src/layouts/global-navbar.tsx @@ -0,0 +1,158 @@ +import { useId, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Link, useLocation } from 'react-router'; + +import { LucideHouse } from 'lucide-react'; + +import { cn } from '@/lib/utils'; +import { Routes } from '@/routes'; +import { supportsCssAnchor } from '@/utils/css-support'; + +const PathMap = { + [Routes.Datasets]: [Routes.Datasets, Routes.DatasetBase], + [Routes.Chats]: [Routes.Chats, Routes.Chat], + [Routes.Searches]: [Routes.Searches, Routes.Search], + [Routes.Agents]: [Routes.Agents, Routes.AgentTemplates], + [Routes.Memories]: [Routes.Memories, Routes.Memory, Routes.MemoryMessage], + [Routes.Files]: [Routes.Files], +} as const; + +const menuItems = [ + { path: Routes.Root, name: 'header.Root', icon: LucideHouse }, + { path: Routes.Datasets, name: 'header.dataset' /* icon: Library, */ }, + { + path: Routes.Chats, + name: 'header.chat', + /* icon: MessageSquareText, */ 'data-testid': 'nav-chat', + }, + { + path: Routes.Searches, + name: 'header.search', + /* icon: Search, */ 'data-testid': 'nav-search', + }, + { + path: Routes.Agents, + name: 'header.flow', + /* icon: Cpu, */ 'data-testid': 'nav-agent', + }, + { path: Routes.Memories, name: 'header.memories' /* icon: Cpu, */ }, + { path: Routes.Files, name: 'header.fileManager' /* icon: File, */ }, +]; + +const GlobalNavbar = supportsCssAnchor + ? () => { + const { t } = useTranslation(); + const { pathname } = useLocation(); + const navbarAnchorNamePrefix = useId().replace(/:/g, ''); + + const activePath = useMemo(() => { + return ( + Object.keys(PathMap).find((x: string) => + PathMap[x as keyof typeof PathMap].some((y: string) => + pathname.includes(y), + ), + ) || pathname + ); + }, [pathname]); + + const activePathAnchorName = `--${navbarAnchorNamePrefix}${activePath === Routes.Root ? '-root' : activePath.replace('/', '-')}`; + + const hasAnyActive = useMemo( + () => menuItems.some(({ path }) => path === activePath), + [activePath], + ); + + return ( + + ); + } + : () => { + const { t } = useTranslation(); + const { pathname } = useLocation(); + + const activePath = useMemo(() => { + return ( + Object.keys(PathMap).find((x: string) => + PathMap[x as keyof typeof PathMap].some((y: string) => + pathname.includes(y), + ), + ) || pathname + ); + }, [pathname]); + + return ( + + ); + }; + +export default GlobalNavbar; diff --git a/web/src/layouts/index.module.less b/web/src/layouts/index.module.less deleted file mode 100644 index 342d2df81..000000000 --- a/web/src/layouts/index.module.less +++ /dev/null @@ -1,27 +0,0 @@ -.navs { - ul { - padding: 0; - list-style: none; - display: flex; - } - - li { - margin-right: 1em; - } -} - -.layout { - height: 100vh; -} - -body { - margin: 0; -} - -.divider { - margin: 0; -} - -.clickAvailable { - cursor: pointer; -} diff --git a/web/src/layouts/next-header.tsx b/web/src/layouts/next-header.tsx index 5c0cfbac6..170ca9ede 100644 --- a/web/src/layouts/next-header.tsx +++ b/web/src/layouts/next-header.tsx @@ -1,6 +1,5 @@ import { IconFontFill } from '@/components/icon-font'; import { RAGFlowAvatar } from '@/components/ragflow-avatar'; -import { useTheme } from '@/components/theme-provider'; import { Button } from '@/components/ui/button'; import { DropdownMenu, @@ -8,213 +7,143 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { Segmented, SegmentedValue } from '@/components/ui/segmented'; -import { LanguageList, LanguageMap, ThemeEnum } from '@/constants/common'; +import { LanguageList, LanguageMap } from '@/constants/common'; import { useChangeLanguage } from '@/hooks/logic-hooks'; -import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; -import { useNavigateWithFromState } from '@/hooks/route-hook'; -import { useFetchUserInfo } from '@/hooks/use-user-setting-request'; +import { + useFetchUserInfo, + useListTenant, +} from '@/hooks/use-user-setting-request'; +import { cn } from '@/lib/utils'; +import { TenantRole } from '@/pages/user-setting/constants'; import { Routes } from '@/routes'; import { camelCase } from 'lodash'; -import { - ChevronDown, - CircleHelp, - Cpu, - File, - House, - Library, - MessageSquareText, - Moon, - Search, - Sun, -} from 'lucide-react'; -import React, { useCallback, useMemo } from 'react'; +import { LucideChevronDown, LucideCircleHelp } from 'lucide-react'; +import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { useLocation } from 'react-router'; +import { Link, useLocation } from 'react-router'; import { BellButton } from './bell-button'; +import GlobalNavbar from './global-navbar'; +import ThemeButton from './theme-button'; -const handleDocHelpCLick = () => { - window.open('https://ragflow.io/docs/dev/category/guides', 'target'); -}; - -const PathMap = { - [Routes.Datasets]: [Routes.Datasets], - [Routes.Chats]: [Routes.Chats], - [Routes.Searches]: [Routes.Searches], - [Routes.Agents]: [Routes.Agents], - [Routes.Memories]: [Routes.Memories, Routes.Memory, Routes.MemoryMessage], - [Routes.Files]: [Routes.Files], -} as const; - -export function Header() { +export function Header({ + className, + ...props +}: React.HTMLAttributes) { const { t } = useTranslation(); const { pathname } = useLocation(); - const navigate = useNavigateWithFromState(); - const { navigateToOldProfile } = useNavigatePage(); const changeLanguage = useChangeLanguage(); - const { setTheme, theme } = useTheme(); const { data: { language = 'English', avatar, nickname }, } = useFetchUserInfo(); - const handleItemClick = (key: string) => () => { - changeLanguage(key); - }; + const { data: tenantData } = useListTenant(); + const hasNotification = useMemo( + () => tenantData?.some((x) => x.role === TenantRole.Invite), + [tenantData], + ); - const items = LanguageList.map((x) => ({ + const langItems = LanguageList.map((x) => ({ key: x, label: {LanguageMap[x as keyof typeof LanguageMap]}, })); - const onThemeClick = React.useCallback(() => { - setTheme(theme === ThemeEnum.Dark ? ThemeEnum.Light : ThemeEnum.Dark); - }, [setTheme, theme]); - - const tagsData = useMemo( - () => [ - { path: Routes.Root, name: t('header.Root'), icon: House }, - { path: Routes.Datasets, name: t('header.dataset'), icon: Library }, - { path: Routes.Chats, name: t('header.chat'), icon: MessageSquareText }, - { path: Routes.Searches, name: t('header.search'), icon: Search }, - { path: Routes.Agents, name: t('header.flow'), icon: Cpu }, - { path: Routes.Memories, name: t('header.memories'), icon: Cpu }, - { path: Routes.Files, name: t('header.fileManager'), icon: File }, - ], - [t], - ); - - const options = useMemo(() => { - return tagsData.map((tag) => { - const HeaderIcon = tag.icon; - - return { - label: - tag.path === Routes.Root ? ( - - ) : ( - - {tag.name} - - ), - value: tag.path, - }; - }); - }, [tagsData]); - - // const currentPath = useMemo(() => { - // return ( - // tagsData.find((x) => pathname.startsWith(x.path))?.path || Routes.Root - // ); - // }, [pathname, tagsData]); - - const handleChange = (path: SegmentedValue) => { - navigate(path as Routes); - }; - - const handleLogoClick = useCallback(() => { - navigate(Routes.Root); - }, [navigate]); - - const activePathName = useMemo(() => { - const name = Object.keys(PathMap).find((x: string) => { - const pathList = PathMap[x as keyof typeof PathMap]; - return pathList.some((y: string) => pathname.indexOf(y) > -1); - }); - if (name) { - return name; - } else { - return pathname; - } - }, [pathname]); - return ( -
-
- logo +
+ + RAGFlow logo +
- + + +
- + + - + + - -
+ +
+ + +
+ - {items.map((x) => ( - + {langItems.map((x) => ( + changeLanguage(x.key)} + > {x.label} ))}
- - - -
+ + + + {hasNotification && } + + + className="size-8" + /> {/* Temporarily hidden */} {/* Pro */} -
+
-
+ ); } diff --git a/web/src/layouts/next.tsx b/web/src/layouts/next.tsx index 925c4538a..a496cc184 100644 --- a/web/src/layouts/next.tsx +++ b/web/src/layouts/next.tsx @@ -1,11 +1,20 @@ import { Outlet } from 'react-router'; import { Header } from './next-header'; -export default function NextLayout() { +export function NextLayoutContainer({ children }: React.PropsWithChildren) { return ( -
-
- -
+
+
+ +
{children}
+
+ ); +} + +export default function NextLayout() { + return ( + + + ); } diff --git a/web/src/layouts/page-container.tsx b/web/src/layouts/page-container.tsx new file mode 100644 index 000000000..47f6e3332 --- /dev/null +++ b/web/src/layouts/page-container.tsx @@ -0,0 +1,19 @@ +import { cn } from '@/lib/utils'; + +/** + * Basic page container: + * - Full size + * - Padding x=2.5rem top=0.75rem + * - Auto scrollbar + */ +export function PageContainer({ + className, + ...props +}: React.PropsWithChildren>) { + return ( +
+ ); +} diff --git a/web/src/layouts/theme-button.tsx b/web/src/layouts/theme-button.tsx new file mode 100644 index 000000000..a4e8a2cd1 --- /dev/null +++ b/web/src/layouts/theme-button.tsx @@ -0,0 +1,22 @@ +import { LucideMoon, LucideSun } from 'lucide-react'; + +import { useTheme } from '@/components/theme-provider'; +import { Button } from '@/components/ui/button'; +import { ThemeEnum } from '@/constants/common'; + +export default function ThemeButton() { + const { setTheme, theme } = useTheme(); + + return ( + + ); +} diff --git a/web/src/pages/agents/agent-templates.tsx b/web/src/pages/agents/agent-templates.tsx index 4f287c5c0..35e060232 100644 --- a/web/src/pages/agents/agent-templates.tsx +++ b/web/src/pages/agents/agent-templates.tsx @@ -1,12 +1,3 @@ -import { PageHeader } from '@/components/page-header'; -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from '@/components/ui/breadcrumb'; import { useSetModalState } from '@/hooks/common-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useFetchAgentTemplates, useSetAgent } from '@/hooks/use-agent-request'; @@ -15,14 +6,11 @@ import { CardContainer } from '@/components/card-container'; import { AgentCategory } from '@/constants/agent'; import { IFlowTemplate } from '@/interfaces/database/agent'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; import { CreateAgentDialog } from './create-agent-dialog'; import { TemplateCard } from './template-card'; import { MenuItemKey, SideBar } from './template-sidebar'; export default function AgentTemplates() { - const { navigateToAgents } = useNavigatePage(); - const { t } = useTranslation(); const list = useFetchAgentTemplates(); const { loading, setAgent } = useSetAgent(); const [templateList, setTemplateList] = useState([]); @@ -99,21 +87,6 @@ export default function AgentTemplates() { return (
- - - - - - {t('flow.agent')} - - - - - {t('flow.createGraph')} - - - -
{ @@ -182,29 +176,15 @@ const Chunk = () => { return ( <> - - - - - {t('knowledgeDetails.dataset')} - - - - - - {dataset.name} - - - - - {documentInfo?.name} - - - +
diff --git a/web/src/pages/dataset/dataset-overview/index.tsx b/web/src/pages/dataset/dataset-overview/index.tsx index e27a013b3..5d6035939 100644 --- a/web/src/pages/dataset/dataset-overview/index.tsx +++ b/web/src/pages/dataset/dataset-overview/index.tsx @@ -2,10 +2,17 @@ import FileStatusBadge from '@/components/file-status-badge'; import { FilterCollection } from '@/components/list-filter-bar/interface'; import SvgIcon from '@/components/svg-icon'; import { useIsDarkTheme } from '@/components/theme-provider'; -import { AntToolTip } from '@/components/ui/tooltip'; + +import { + Card, + CardDescription, + CardFooter, + CardHeader, +} from '@/components/ui/card'; + +import WhatIsThis from '@/components/what-is-this'; import { RunningStatusMap } from '@/constants/knowledge'; import { useFetchDocumentList } from '@/hooks/use-document-request'; -import { CircleQuestionMark } from 'lucide-react'; import { FC, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { RunningStatus } from '../dataset/constant'; @@ -37,23 +44,35 @@ const StatCard: FC = ({ tooltip, }) => { return ( -
-
-

- {title} - {tooltip && ( - - - - )} -

- {icon} + + {icon} + +
+ +

+ {title} + + {tooltip && {tooltip}} +

+
+ + + {value} +
-
{value}
-
+ +
{children}
-
-
+ + ); }; @@ -64,38 +83,34 @@ const CardFooterProcess: FC = ({ failedTip, }) => { const { t } = useTranslation(); + return (
-
-
-
-
+
+
+
+
{t('knowledgeDetails.success')} - {successTip && ( - - - - )} + {successTip && {successTip}}
-
-
{success || 0}
+ + +
{success || 0}
-
-
-
+ +
+
+
{t('knowledgeDetails.failed')} - {failedTip && ( - - - - )} + {failedTip && {failedTip}}
-
-
{failed || 0}
+ + +
{failed || 0}
-
+
); }; @@ -247,9 +262,13 @@ const FileLogsPage: FC = () => { const isDark = useIsDarkTheme(); return ( -
+ {/* Stats Cards */} -
+
{ ) } > -
+
{topAllData.totalFiles.precent > 0 ? '+' : ''} {topAllData.totalFiles.precent}%{' '} - + {t('knowledgeConfiguration.lastWeek')}
@@ -330,7 +349,7 @@ const FileLogsPage: FC = () => { pageCount={10} active={active} /> -
+ ); }; diff --git a/web/src/pages/dataset/dataset-overview/overview-table.tsx b/web/src/pages/dataset/dataset-overview/overview-table.tsx index 3d7dfe3b0..6c5c93527 100644 --- a/web/src/pages/dataset/dataset-overview/overview-table.tsx +++ b/web/src/pages/dataset/dataset-overview/overview-table.tsx @@ -414,8 +414,8 @@ const FileLogsTable: FC = ({ }); return ( -
- +
+
{table.getHeaderGroups().map((headerGroup) => ( @@ -460,15 +460,15 @@ const FileLogsTable: FC = ({ )}
-
-
- setPagination({ page, pageSize })} - /> -
+ +
+ setPagination({ page, pageSize })} + />
+ {isModalVisible && ( - -
- -
- -
- -
- {t('knowledgeConfiguration.baseInfo')} -
- +
+ + +
+ {t('knowledgeDetails.configuration')} - -
- {t('knowledgeConfiguration.dataPipeline')} -
- - {parseType === 1 && ( - - )} - {parseType === 2 && ( - - )} + + {t('knowledgeConfiguration.titleDescription')} + - {/* */} - {parseType === 1 && } + {/* */} +
+
- {/* */} - - + + + +
+ +
+ {t('knowledgeConfiguration.baseInfo')} +
+ + + +
+ {t('knowledgeConfiguration.dataPipeline')} +
+ + {parseType === 1 && ( + + )} + {parseType === 2 && ( + + )} + + {/* */} + {parseType === 1 && } + + {/* - -
- {t('knowledgeConfiguration.globalIndex')} -
- - handleDeletePipelineTask(GenerateType.KnowledgeGraph) - } - > - - - handleDeletePipelineTask(GenerateType.Raptor) - } - > -
-
-
- - -
- - -
+ /> */} + + + +
+ {t('knowledgeConfiguration.globalIndex')} +
+ + handleDeletePipelineTask(GenerateType.KnowledgeGraph) + } + > + + + handleDeletePipelineTask(GenerateType.Raptor) + } + > + +
+ +
+ + + +
+ + +
+ +
{parseType === 1 && }
- -
-
+ + +
); } diff --git a/web/src/pages/dataset/dataset/generate-button/generate.tsx b/web/src/pages/dataset/dataset/generate-button/generate.tsx index c4935a901..0b62e8a37 100644 --- a/web/src/pages/dataset/dataset/generate-button/generate.tsx +++ b/web/src/pages/dataset/dataset/generate-button/generate.tsx @@ -7,6 +7,11 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Modal } from '@/components/ui/modal/modal'; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip'; import { cn } from '@/lib/utils'; import { toFixed } from '@/utils/common-util'; import { formatDate } from '@/utils/date'; @@ -25,6 +30,7 @@ import { useTraceGenerate, useUnBindTask, } from './hook'; + export enum GenerateType { KnowledgeGraph = 'KnowledgeGraph', Raptor = 'Raptor', @@ -191,45 +197,46 @@ const Generate: React.FC = (props) => { }; return ( -
- - -
+ + + + -
-
- - {Object.values(GenerateType).map((name) => { - const data = ( - name === GenerateType.KnowledgeGraph - ? graphRunData - : raptorRunData - ) as ITraceInfo; - return ( -
- -
- ); - })} -
-
-
+ + + {t('knowledgeDetails.generate')} + + + + {Object.values(GenerateType).map((name) => { + const data = ( + name === GenerateType.KnowledgeGraph ? graphRunData : raptorRunData + ) as ITraceInfo; + return ( +
+ +
+ ); + })} +
+ ); }; diff --git a/web/src/pages/dataset/dataset/index.tsx b/web/src/pages/dataset/dataset/index.tsx index 8f9b41e25..c397e70fb 100644 --- a/web/src/pages/dataset/dataset/index.tsx +++ b/web/src/pages/dataset/dataset/index.tsx @@ -137,9 +137,6 @@ export default function Dataset() { return ( <> -
- 0)} /> -
} + preChildren={ 0)} />} // preChildren={ // */} -
- {count === 1 ? ( -
-
-
- - {t('knowledgeDetails.testSetting')} - - {/* */} +
+ + +
+ + {t('knowledgeDetails.retrievalTesting')} + + + + {t('knowledgeDetails.testingDescription')} + + + {/* */} +
+
+ + {count === 1 ? ( + +
+
+

+ {t('knowledgeDetails.testSetting')} +

+ {/* */} +
+ +
+ +
+
+ +
+
-
+ + ) : ( + +
+
-
- -
- ) : ( -
-
- - -
-
- - -
-
- )} +
+ + +
+ + )} +
); } diff --git a/web/src/pages/dataset/testing/testing-form.tsx b/web/src/pages/dataset/testing/testing-form.tsx index 74f771b02..bd292ef22 100644 --- a/web/src/pages/dataset/testing/testing-form.tsx +++ b/web/src/pages/dataset/testing/testing-form.tsx @@ -90,43 +90,52 @@ export default function TestingForm({ return (
- - - - - - - - - ( - - {/* {t('knowledgeDetails.testText')} */} - - - - - - - )} - /> -
- - {/* {!loading && } */} - {t('knowledgeDetails.testingLabel')} - - + +
+ + + + + + +
+ +
+ ( + + {/* {t('knowledgeDetails.testText')} */} + + + + + + + )} + /> + +
+ + {/* {!loading && } */} + {t('knowledgeDetails.testingLabel')} + + +
+
); diff --git a/web/src/pages/dataset/testing/testing-result.tsx b/web/src/pages/dataset/testing/testing-result.tsx index 423a0a547..c0359f936 100644 --- a/web/src/pages/dataset/testing/testing-result.tsx +++ b/web/src/pages/dataset/testing/testing-result.tsx @@ -1,9 +1,9 @@ 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'; import { FilterPopover } from '@/components/list-filter-bar/filter-popover'; import { FilterCollection } from '@/components/list-filter-bar/interface'; +import { Card } from '@/components/ui/card'; import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; import { useTranslate } from '@/hooks/common-hooks'; import { useTestRetrieval } from '@/hooks/use-knowledge-request'; @@ -21,12 +21,12 @@ const similarityList: Array<{ field: keyof ITestingChunk; label: string }> = [ const ChunkTitle = ({ item }: { item: ITestingChunk }) => { const { t } = useTranslate('knowledgeDetails'); return ( -
+
{similarityList.map((x) => ( -
- {((item[x.field] as number) * 100).toFixed(2)} - {t(camelCase(x.field))} -
+

+ {((item[x.field] as number) * 100).toFixed(2)}{' '} + {t(camelCase(x.field))} +

))}
); @@ -68,11 +68,12 @@ export function TestingResult({ }, [data.doc_aggs]); return ( -
-
- +
+
+

{t('knowledgeDetails.testResults')} - +

+ -
- {data.chunks?.length > 0 && !loading && ( - <> -
- {data.chunks?.map((x) => ( - - -

{x.content_with_weight}

-
- ))} -
- - - )} - {!data.chunks?.length && !loading && ( -
-
- - {data.isRuned && ( -
- {t('knowledgeDetails.noTestResultsForRuned')} + + +
+ {data.chunks?.length > 0 && !loading && ( + <> +
+ {data.chunks?.map((x) => ( +
+ + +

{x.content_with_weight}

+
+
+ ))} +
+ + + )} + {!data.chunks?.length && !loading && ( +
+
+ +
+ {t( + data.isRuned + ? 'knowledgeDetails.noTestResultsForRuned' + : 'knowledgeDetails.noTestResultsForNotRuned', + )}
- )} - {!data.isRuned && ( -
- {t('knowledgeDetails.noTestResultsForNotRuned')} -
- )} -
+ +
-
- )} -
+ )} +
+ ); } diff --git a/web/src/pages/datasets/index.tsx b/web/src/pages/datasets/index.tsx index 0eca9e6dd..04f177aca 100644 --- a/web/src/pages/datasets/index.tsx +++ b/web/src/pages/datasets/index.tsx @@ -66,10 +66,10 @@ export default function Datasets() { searchUrl.delete('isCreate'); setSearchUrl(searchUrl); } - }, [isCreate, showModal, searchUrl, setSearchUrl]); + }, [isCreate, showModal, searchUrl, setSearchUrl, queryClient]); return ( <> -
+
{(!kbs?.length || kbs?.length <= 0) && !searchString && (
- {t('header.welcome')} - +

+ {t('header.welcome')} + RAGFlow -

+ ); } diff --git a/web/src/pages/home/index.tsx b/web/src/pages/home/index.tsx index 137f50ec5..313c54d3a 100644 --- a/web/src/pages/home/index.tsx +++ b/web/src/pages/home/index.tsx @@ -1,16 +1,18 @@ +import { PageContainer } from '@/layouts/page-container'; import { Applications } from './applications'; import { NextBanner } from './banner'; import { Datasets } from './datasets'; const Home = () => { return ( -
- -
- - -
-
+ +
+ +
+ + + +
); }; diff --git a/web/src/pages/login-next/index.tsx b/web/src/pages/login-next/index.tsx index 69c929b89..4a65b5841 100644 --- a/web/src/pages/login-next/index.tsx +++ b/web/src/pages/login-next/index.tsx @@ -71,43 +71,24 @@ function LoginFormContent({
{!disablePasswordLogin && ( -
- - ( - - {t('emailLabel')} - - - - - - )} - /> - {title === 'register' && ( + + ( - {t('nicknameLabel')} + {t('emailLabel')} @@ -115,73 +96,92 @@ function LoginFormContent({ )} /> - )} - - ( - - {t('passwordLabel')} - -
- -
-
- -
+ {title === 'register' && ( + ( + + {t('nicknameLabel')} + + + + + + )} + /> )} - /> - {title === 'login' && ( ( + {t('passwordLabel')} -
- { - field.onChange(checked); - }} +
+ - - {t('rememberMe')} -
)} /> - )} - - {title === 'login' ? t('login') : t('continue')} - - - + + {title === 'login' && ( + ( + + +
+ { + field.onChange(checked); + }} + /> + + {t('rememberMe')} + +
+
+ +
+ )} + /> + )} + + {title === 'login' ? t('login') : t('continue')} + + + )} {title === 'login' && channels && channels.length > 0 && ( @@ -361,7 +361,7 @@ const Login = () => {
-
+
{ ); }; -export default Login; \ No newline at end of file +export default Login; diff --git a/web/src/pages/next-chats/chat/chat-box/next-multiple-chat-box.tsx b/web/src/pages/next-chats/chat/chat-box/next-multiple-chat-box.tsx index 5d25461f1..f755a3c7b 100644 --- a/web/src/pages/next-chats/chat/chat-box/next-multiple-chat-box.tsx +++ b/web/src/pages/next-chats/chat/chat-box/next-multiple-chat-box.tsx @@ -181,7 +181,7 @@ const ChatCard = forwardRef(function ChatCard(
- + - + +
+
+ -
- + + + + + +
{currentConversationName}
- - - - - -
{currentConversationName}
- -
-
- - - -
+ +
+
+ + + +
- -
-
-
- - {embedVisible && ( - - )} -
+ + + + +
+ ); } diff --git a/web/src/pages/next-chats/chat/sessions.tsx b/web/src/pages/next-chats/chat/sessions.tsx index 95efe09db..b4e2b9e68 100644 --- a/web/src/pages/next-chats/chat/sessions.tsx +++ b/web/src/pages/next-chats/chat/sessions.tsx @@ -1,9 +1,17 @@ import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog'; +import EmbedDialog from '@/components/embed-dialog'; +import { useShowEmbedModal } from '@/components/embed-dialog/use-show-embed-dialog'; import { MoreButton } from '@/components/more-button'; import { RAGFlowAvatar } from '@/components/ragflow-avatar'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { SearchInput } from '@/components/ui/input'; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip'; +import { SharedFrom } from '@/constants/chat'; import { useSetModalState } from '@/hooks/common-hooks'; import { useFetchDialog, @@ -15,11 +23,13 @@ import { LucideListChecks, LucidePanelLeftClose, LucidePlus, + LucideSend, LucideTrash2, LucideUndo2, } from 'lucide-react'; import { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router'; import { useChatUrlParams } from '../hooks/use-chat-url'; import { useHandleClickConversationCard } from '../hooks/use-click-card'; import { useSelectDerivedConversationList } from '../hooks/use-select-conversation-list'; @@ -132,6 +142,10 @@ export function Sessions({ handleConversationCardClick }: SessionProps) { const selectedCount = useMemo(() => selectedIds.size, [selectedIds]); + const { id } = useParams(); + const { showEmbedModal, hideEmbedModal, embedVisible, beta } = + useShowEmbedModal(); + if (!visible) { return (
@@ -158,7 +172,7 @@ export function Sessions({ handleConversationCardClick }: SessionProps) { role="complementary" data-testid="chat-detail-sessions" > -
+
{data.name}
+ + + + + {t('common.embedIntoSite')} + + + +