mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-05-05 17:57:47 +08:00
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
This commit is contained in:
@ -14,6 +14,9 @@ module.exports = {
|
|||||||
jsx: true,
|
jsx: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
includes: [
|
||||||
|
'./src',
|
||||||
|
],
|
||||||
settings: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
version: 'detect',
|
version: 'detect',
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { Toaster } from '@/components/ui/toaster';
|
|||||||
import i18n, { changeLanguageAsync } from '@/locales/config';
|
import i18n, { changeLanguageAsync } from '@/locales/config';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { configResponsive } from 'ahooks';
|
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 pt_BR from 'antd/lib/locale/pt_BR';
|
||||||
import arEG from 'antd/locale/ar_EG';
|
import arEG from 'antd/locale/ar_EG';
|
||||||
import deDE from 'antd/locale/de_DE';
|
import deDE from 'antd/locale/de_DE';
|
||||||
@ -22,7 +22,6 @@ import weekday from 'dayjs/plugin/weekday';
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { RouterProvider } from 'react-router';
|
import { RouterProvider } from 'react-router';
|
||||||
import { ThemeProvider, useTheme } from './components/theme-provider';
|
import { ThemeProvider, useTheme } from './components/theme-provider';
|
||||||
import { SidebarProvider } from './components/ui/sidebar';
|
|
||||||
import { TooltipProvider } from './components/ui/tooltip';
|
import { TooltipProvider } from './components/ui/tooltip';
|
||||||
import { ThemeEnum } from './constants/common';
|
import { ThemeEnum } from './constants/common';
|
||||||
import { routers } from './routes';
|
import { routers } from './routes';
|
||||||
@ -125,10 +124,10 @@ function Root({ children }: React.PropsWithChildren) {
|
|||||||
}}
|
}}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
>
|
>
|
||||||
<SidebarProvider className="h-full">
|
{children}
|
||||||
<App className="w-full h-dvh relative">{children}</App>
|
|
||||||
</SidebarProvider>
|
<Sonner position="top-right" expand richColors closeButton />
|
||||||
<Sonner position={'top-right'} expand richColors closeButton></Sonner>
|
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import CopyToClipboard from '@/components/copy-to-clipboard';
|
import CopyToClipboard from '@/components/copy-to-clipboard';
|
||||||
import HighLightMarkdown from '@/components/highlight-markdown';
|
|
||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
@ -32,7 +31,13 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||||||
import { ExternalLink } from 'lucide-react';
|
import { ExternalLink } from 'lucide-react';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
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 { z } from 'zod';
|
||||||
|
import { useIsDarkTheme } from '../theme-provider';
|
||||||
|
|
||||||
const FormSchema = z.object({
|
const FormSchema = z.object({
|
||||||
visibleAvatar: z.boolean(),
|
visibleAvatar: z.boolean(),
|
||||||
@ -56,8 +61,10 @@ function EmbedDialog({
|
|||||||
from,
|
from,
|
||||||
beta = '',
|
beta = '',
|
||||||
isAgent,
|
isAgent,
|
||||||
|
visible,
|
||||||
}: IProps) {
|
}: IProps) {
|
||||||
const { t } = useTranslate('chat');
|
const { t } = useTranslate('chat');
|
||||||
|
const isDarkTheme = useIsDarkTheme();
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
@ -95,23 +102,30 @@ function EmbedDialog({
|
|||||||
: from === SharedFrom.Agent
|
: from === SharedFrom.Agent
|
||||||
? Routes.AgentShare
|
? Routes.AgentShare
|
||||||
: Routes.ChatShare;
|
: 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) {
|
if (publishAvatar) {
|
||||||
src += '&release=true';
|
src.searchParams.append('release', 'true');
|
||||||
}
|
}
|
||||||
if (visibleAvatar) {
|
if (visibleAvatar) {
|
||||||
src += '&visible_avatar=1';
|
src.searchParams.append('visible_avatar', '1');
|
||||||
}
|
}
|
||||||
if (locale) {
|
if (locale) {
|
||||||
src += `&locale=${locale}`;
|
src.searchParams.append('locale', locale);
|
||||||
}
|
}
|
||||||
if (enableStreaming) {
|
if (embedType === 'widget') {
|
||||||
src += '&streaming=true';
|
src.searchParams.append('mode', 'master');
|
||||||
|
src.searchParams.append('streaming', String(enableStreaming));
|
||||||
}
|
}
|
||||||
if (theme && embedType === 'fullscreen') {
|
if (theme && embedType === 'fullscreen') {
|
||||||
src += `&theme=${theme}`;
|
src.searchParams.append('theme', theme);
|
||||||
}
|
}
|
||||||
return src;
|
|
||||||
|
return src.toString();
|
||||||
}, [beta, from, token, values]);
|
}, [beta, from, token, values]);
|
||||||
|
|
||||||
const text = useMemo(() => {
|
const text = useMemo(() => {
|
||||||
@ -119,44 +133,36 @@ function EmbedDialog({
|
|||||||
const { embedType } = values;
|
const { embedType } = values;
|
||||||
|
|
||||||
if (embedType === 'widget') {
|
if (embedType === 'widget') {
|
||||||
const { enableStreaming } = values;
|
return `<iframe
|
||||||
const streamingParam = enableStreaming
|
src="${iframeSrc}"
|
||||||
? '&streaming=true'
|
style="position:fixed;bottom:0;right:0;width:100px;height:100px;border:none;background:transparent;z-index:9999"
|
||||||
: '&streaming=false';
|
frameborder="0"
|
||||||
return `
|
allow="microphone;camera"
|
||||||
~~~ html
|
></iframe>
|
||||||
<iframe src="${iframeSrc}&mode=master${streamingParam}"
|
<script>
|
||||||
style="position:fixed;bottom:0;right:0;width:100px;height:100px;border:none;background:transparent;z-index:9999"
|
window.addEventListener('message',e=>{
|
||||||
frameborder="0" allow="microphone;camera"></iframe>
|
if(e.origin!=='${location.origin.replace(/:\d+/, ':9222')}')return;
|
||||||
<script>
|
if(e.data.type==='CREATE_CHAT_WINDOW'){
|
||||||
window.addEventListener('message',e=>{
|
if(document.getElementById('chat-win'))return;
|
||||||
if(e.origin!=='${location.origin.replace(/:\d+/, ':9222')}')return;
|
const i=document.createElement('iframe');
|
||||||
if(e.data.type==='CREATE_CHAT_WINDOW'){
|
i.id='chat-win';i.src=e.data.src;
|
||||||
if(document.getElementById('chat-win'))return;
|
i.style.cssText='position:fixed;bottom:104px;right:24px;width:380px;height:500px;border:none;background:transparent;z-index:9998;display:none';
|
||||||
const i=document.createElement('iframe');
|
i.frameBorder='0';i.allow='microphone;camera';
|
||||||
i.id='chat-win';i.src=e.data.src;
|
document.body.appendChild(i);
|
||||||
i.style.cssText='position:fixed;bottom:104px;right:24px;width:380px;height:500px;border:none;background:transparent;z-index:9998;display:none';
|
}else if(e.data.type==='TOGGLE_CHAT'){
|
||||||
i.frameBorder='0';i.allow='microphone;camera';
|
const w=document.getElementById('chat-win');
|
||||||
document.body.appendChild(i);
|
if(w)w.style.display=e.data.isOpen?'block':'none';
|
||||||
}else if(e.data.type==='TOGGLE_CHAT'){
|
}else if(e.data.type==='SCROLL_PASSTHROUGH')window.scrollBy(0,e.data.deltaY);
|
||||||
const w=document.getElementById('chat-win');
|
});
|
||||||
if(w)w.style.display=e.data.isOpen?'block':'none';
|
</script>
|
||||||
}else if(e.data.type==='SCROLL_PASSTHROUGH')window.scrollBy(0,e.data.deltaY);
|
`;
|
||||||
});
|
|
||||||
</script>
|
|
||||||
~~~
|
|
||||||
`;
|
|
||||||
} else {
|
} else {
|
||||||
return `
|
return `<iframe
|
||||||
~~~ html
|
|
||||||
<iframe
|
|
||||||
src="${iframeSrc}"
|
src="${iframeSrc}"
|
||||||
style="width: 100%; height: 100%; min-height: 600px"
|
style="width: 100%; height: 100%; min-height: 600px"
|
||||||
frameborder="0"
|
frameborder="0"
|
||||||
>
|
></iframe>
|
||||||
</iframe>
|
`;
|
||||||
~~~
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
}, [generateIframeSrc, values]);
|
}, [generateIframeSrc, values]);
|
||||||
|
|
||||||
@ -166,13 +172,14 @@ function EmbedDialog({
|
|||||||
}, [generateIframeSrc]);
|
}, [generateIframeSrc]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onOpenChange={hideModal}>
|
<Dialog open={visible} onOpenChange={hideModal}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{t('embedIntoSite', { keyPrefix: 'common' })}
|
{t('embedIntoSite', { keyPrefix: 'common' })}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<section className="w-full overflow-auto space-y-5 text-sm text-text-secondary">
|
<section className="w-full overflow-auto space-y-5 text-sm text-text-secondary">
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form className="space-y-5">
|
<form className="space-y-5">
|
||||||
@ -309,10 +316,16 @@ function EmbedDialog({
|
|||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
<div className="max-h-[350px] overflow-auto">
|
<div>
|
||||||
<span>{t('embedCode', { keyPrefix: 'search' })}</span>
|
<span>{t('embedCode', { keyPrefix: 'search' })}</span>
|
||||||
<div className="max-h-full overflow-y-auto">
|
<div>
|
||||||
<HighLightMarkdown>{text}</HighLightMarkdown>
|
<SyntaxHighlighter
|
||||||
|
className="max-h-[350px] overflow-auto scrollbar-auto"
|
||||||
|
language="html"
|
||||||
|
style={isDarkTheme ? oneDark : oneLight}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</SyntaxHighlighter>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@ -327,7 +340,7 @@ function EmbedDialog({
|
|||||||
{t(isAgent ? 'flow' : 'chat', { keyPrefix: 'header' })}
|
{t(isAgent ? 'flow' : 'chat', { keyPrefix: 'header' })}
|
||||||
<span className="ml-1 inline-block">ID</span>
|
<span className="ml-1 inline-block">ID</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-bg-card rounded-lg flex justify-between p-2">
|
<div className="bg-bg-card rounded-lg flex items-center justify-between p-2">
|
||||||
<span>{token} </span>
|
<span>{token} </span>
|
||||||
<CopyToClipboard text={token}></CopyToClipboard>
|
<CopyToClipboard text={token}></CopyToClipboard>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -12,7 +12,12 @@ export function FormContainer({
|
|||||||
className,
|
className,
|
||||||
}: FormContainerProps) {
|
}: FormContainerProps) {
|
||||||
return show ? (
|
return show ? (
|
||||||
<section className={cn('border rounded-lg p-5 space-y-5', className)}>
|
<section
|
||||||
|
className={cn(
|
||||||
|
'border-0.5 border-border-button rounded-lg p-5 space-y-5',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</section>
|
</section>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -19,8 +19,10 @@ import { useIsDarkTheme } from '../theme-provider';
|
|||||||
import styles from './index.module.less';
|
import styles from './index.module.less';
|
||||||
|
|
||||||
const HighLightMarkdown = ({
|
const HighLightMarkdown = ({
|
||||||
|
className,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
|
className?: string;
|
||||||
children: string | null | undefined;
|
children: string | null | undefined;
|
||||||
}) => {
|
}) => {
|
||||||
const isDarkTheme = useIsDarkTheme();
|
const isDarkTheme = useIsDarkTheme();
|
||||||
|
|||||||
@ -25,7 +25,12 @@ export const FilterButton = React.forwardRef<
|
|||||||
ButtonProps & { count?: number }
|
ButtonProps & { count?: number }
|
||||||
>(({ count = 0, ...props }, ref) => {
|
>(({ count = 0, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<Button variant="secondary" {...props} ref={ref}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size={count > 0 ? 'default' : 'icon'}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
{/* <span
|
{/* <span
|
||||||
className={cn({
|
className={cn({
|
||||||
'text-text-primary': count > 0,
|
'text-text-primary': count > 0,
|
||||||
@ -34,12 +39,13 @@ export const FilterButton = React.forwardRef<
|
|||||||
>
|
>
|
||||||
Filter
|
Filter
|
||||||
</span> */}
|
</span> */}
|
||||||
|
<Funnel />
|
||||||
|
|
||||||
{count > 0 && (
|
{count > 0 && (
|
||||||
<span className="rounded-full bg-text-badge px-1 text-xs ">
|
<span className="rounded bg-text-badge px-1 py-0.5 text-xs leading-none text-text-primary">
|
||||||
{count}
|
{count}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<Funnel />
|
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,40 +4,57 @@ import * as React from 'react';
|
|||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { LucideLoader2, Plus } from 'lucide-react';
|
import { LucideLoader2, Plus } from 'lucide-react';
|
||||||
|
import { Link, LinkProps } from 'react-router';
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
cn(
|
cn(
|
||||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-0',
|
'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: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
// Solid variant series:
|
||||||
|
// Button has its own background color, may have borders
|
||||||
default:
|
default:
|
||||||
'bg-text-primary text-bg-base shadow-xs hover:bg-text-primary/90 focus-visible:bg-text-primary/90',
|
'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: `
|
destructive: `
|
||||||
bg-state-error text-white shadow-xs
|
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
|
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: `
|
outline: `
|
||||||
text-text-secondary bg-bg-input border-0.5 border-border-button
|
text-text-secondary bg-bg-input border-0.5 border-border-button
|
||||||
hover:text-text-primary hover:bg-border-button hover:border-border-default
|
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
|
focus-visible:text-text-primary focus-visible:bg-border-button focus-visible:border-border-button
|
||||||
`,
|
`, // light: bg=transparent, dark: bg-input
|
||||||
secondary:
|
|
||||||
'bg-bg-input text-text-primary shadow-xs hover:bg-bg-input/80 border border-border-button',
|
|
||||||
|
|
||||||
ghost: `
|
dashed: `
|
||||||
text-text-secondary
|
text-text-secondary border-border-button border-dashed
|
||||||
hover:bg-border-button hover:text-text-primary
|
hover:text-text-primary hover:bg-border-button hover:border-border-default
|
||||||
focus-visible:text-text-primary focus-visible:bg-border-button
|
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: `
|
transparent: `
|
||||||
text-text-secondary bg-transparent border-0.5 border-border-button
|
text-text-secondary bg-transparent border-0.5 border-border-button
|
||||||
hover:text-text-primary hover:bg-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
|
hover:bg-state-error/10 focus-visible:bg-state-error/10
|
||||||
`,
|
`,
|
||||||
|
|
||||||
accent: `
|
// Ghost variant series
|
||||||
bg-accent-primary text-white
|
// Button has transparent background, without borders
|
||||||
hover:bg-accent-primary/90 focus-visible:bg-accent-primary/90
|
ghost: `
|
||||||
`,
|
text-text-secondary
|
||||||
|
hover:bg-border-button focus-visible:bg-border-button
|
||||||
highlighted: `
|
hover:text-text-primary focus-visible:text-text-primary
|
||||||
bg-text-primary text-bg-base border-b-4 border-b-accent-primary
|
|
||||||
hover:bg-text-primary/90 focus-visible:bg-text-primary/90
|
|
||||||
`,
|
`,
|
||||||
|
|
||||||
delete: `
|
delete: `
|
||||||
@ -64,15 +79,23 @@ const buttonVariants = cva(
|
|||||||
hover:bg-state-error-5 hover:text-state-error
|
hover:bg-state-error-5 hover:text-state-error
|
||||||
focus-visible:text-state-error focus-visible:bg-state-error-5
|
focus-visible:text-state-error focus-visible:bg-state-error-5
|
||||||
`,
|
`,
|
||||||
|
|
||||||
|
link: 'text-primary underline-offset-4 hover:underline',
|
||||||
},
|
},
|
||||||
size: {
|
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',
|
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: {
|
defaultVariants: {
|
||||||
@ -82,45 +105,58 @@ const buttonVariants = cva(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export interface ButtonProps
|
export type ButtonProps<IsAnchor extends boolean = false> = {
|
||||||
extends
|
|
||||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
||||||
VariantProps<typeof buttonVariants> {
|
|
||||||
asChild?: boolean;
|
asChild?: boolean;
|
||||||
|
asLink?: boolean;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
block?: boolean;
|
block?: boolean;
|
||||||
}
|
disabled?: boolean;
|
||||||
|
dot?: boolean;
|
||||||
|
} & VariantProps<typeof buttonVariants> &
|
||||||
|
(IsAnchor extends true
|
||||||
|
? LinkProps
|
||||||
|
: React.ButtonHTMLAttributes<HTMLButtonElement>);
|
||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = React.forwardRef(
|
||||||
(
|
<IsAnchor extends boolean = false>(
|
||||||
{
|
{
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
variant,
|
variant,
|
||||||
size,
|
size,
|
||||||
|
dot = false,
|
||||||
asChild = false,
|
asChild = false,
|
||||||
|
asLink = false,
|
||||||
loading = false,
|
loading = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
block = false,
|
block = false,
|
||||||
...props
|
...props
|
||||||
},
|
}: ButtonProps<IsAnchor>,
|
||||||
ref,
|
ref: React.ForwardedRef<
|
||||||
|
IsAnchor extends true ? HTMLAnchorElement : HTMLButtonElement
|
||||||
|
>,
|
||||||
) => {
|
) => {
|
||||||
const Comp = asChild ? Slot : 'button';
|
const Comp = asChild ? Slot : asLink ? Link : 'button';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-bg-card',
|
|
||||||
{ 'block w-full': block },
|
|
||||||
buttonVariants({ variant, size, className }),
|
buttonVariants({ variant, size, className }),
|
||||||
|
{ 'block w-full': block },
|
||||||
|
{ relative: dot },
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
// @ts-ignore
|
||||||
|
ref={ref as React.RefObject<HTMLButtonElement | HTMLAnchorElement>}
|
||||||
disabled={loading || disabled}
|
disabled={loading || disabled}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{loading && <LucideLoader2 className="animate-spin" />}
|
<>
|
||||||
{children}
|
{dot && (
|
||||||
|
<span className="absolute size-[6px] rounded-full -right-[3px] -top-[3px] bg-state-error animate" />
|
||||||
|
)}
|
||||||
|
{loading && <LucideLoader2 className="animate-spin" />}
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
</Comp>
|
</Comp>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -30,15 +30,12 @@ const CardHeader = React.forwardRef<
|
|||||||
CardHeader.displayName = 'CardHeader';
|
CardHeader.displayName = 'CardHeader';
|
||||||
|
|
||||||
const CardTitle = React.forwardRef<
|
const CardTitle = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLElement,
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
React.HTMLAttributes<HTMLElement> & { as?: React.ElementType }
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, as: As = 'div', ...props }, ref) => (
|
||||||
<div
|
<As
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn('text-2xl leading-normal font-medium', className)}
|
||||||
'text-2xl font-semibold leading-none tracking-tight',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
@ -50,7 +47,7 @@ const CardDescription = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn('text-sm text-muted-foreground', className)}
|
className={cn('text-sm text-text-secondary', className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const Checkbox = React.forwardRef<
|
|||||||
<CheckboxPrimitive.Root
|
<CheckboxPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'peer size-4 shrink-0 rounded-sm border border-text-secondary outline-0 transition-colors bg-bg-component',
|
'peer size-4 shrink-0 rounded-2xs border border-text-disabled outline-0 transition-colors bg-transparent',
|
||||||
'hover:border-border-default hover:bg-border-button',
|
'hover:border-border-default hover:bg-border-button',
|
||||||
'focus-visible:border-border-default focus-visible:bg-border-default',
|
'focus-visible:border-border-default focus-visible:bg-border-default',
|
||||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
|
|||||||
@ -84,8 +84,12 @@ const DropdownMenuItem = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.Item
|
<DropdownMenuPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
'relative flex cursor-default select-none items-center gap-2',
|
||||||
inset && 'ps-8',
|
'rounded-sm px-2 py-1.5 text-sm text-text-secondary outline-none transition-colors',
|
||||||
|
'focus:bg-bg-card focus:text-text-primary',
|
||||||
|
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||||
|
'[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||||
|
inset && 'pl-8',
|
||||||
justifyBetween && 'flex justify-between',
|
justifyBetween && 'flex justify-between',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ function Skeleton({
|
|||||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn('animate-pulse rounded-md bg-muted', className)}
|
className={cn('animate-pulse rounded-md bg-bg-card', className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -26,12 +26,10 @@ function ParagraphSkeleton() {
|
|||||||
|
|
||||||
function CardSkeleton() {
|
function CardSkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-3">
|
<div className="w-64">
|
||||||
<Skeleton className="h-[125px] w-[250px] rounded-xl" />
|
<Skeleton className="mb-3 h-28 rounded-xl" />
|
||||||
<div className="space-y-2">
|
<Skeleton className="mb-2 h-4" />
|
||||||
<Skeleton className="h-4 w-[250px]" />
|
<Skeleton className="h-4 w-4/5" />
|
||||||
<Skeleton className="h-4 w-[200px]" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
15
web/src/components/what-is-this.tsx
Normal file
15
web/src/components/what-is-this.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { LucideCircleQuestionMark } from 'lucide-react';
|
||||||
|
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
|
||||||
|
|
||||||
|
export default function WhatIsThis({ children }: React.PropsWithChildren<{}>) {
|
||||||
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<LucideCircleQuestionMark className="size-[1em]" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
|
||||||
|
<TooltipContent>{children}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,28 +1,26 @@
|
|||||||
@import url(./inter.less);
|
@import url(./inter.less);
|
||||||
|
|
||||||
html {
|
:root {
|
||||||
height: 100%;
|
--font-sans: 'Inter';
|
||||||
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.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
@supports (font-variation-settings: normal) {
|
||||||
font-family:
|
:root {
|
||||||
'Inter',
|
--font-sans: 'InterVariable';
|
||||||
system-ui,
|
}
|
||||||
-apple-system,
|
|
||||||
'Segoe UI',
|
|
||||||
sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
#root {
|
#root {
|
||||||
height: 100%;
|
width: 100vw;
|
||||||
}
|
width: 100dvw;
|
||||||
|
height: 100vh;
|
||||||
|
height: 100dvh;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.ant-app {
|
margin: 0;
|
||||||
height: 100%;
|
padding: 0;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vue-office-excel {
|
.vue-office-excel {
|
||||||
|
|||||||
@ -1,28 +1,18 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useNavigateWithFromState } from '@/hooks/route-hook';
|
import { Routes } from '@/routes';
|
||||||
import { useListTenant } from '@/hooks/use-user-setting-request';
|
|
||||||
import { TenantRole } from '@/pages/user-setting/constants';
|
|
||||||
import { BellRing } from 'lucide-react';
|
import { BellRing } from 'lucide-react';
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
|
|
||||||
export function BellButton() {
|
export function BellButton() {
|
||||||
const { data } = useListTenant();
|
return (
|
||||||
const navigate = useNavigateWithFromState();
|
<Button
|
||||||
|
asLink
|
||||||
const showBell = useMemo(() => {
|
to={`${Routes.UserSetting}${Routes.Team}`}
|
||||||
return data.some((x) => x.role === TenantRole.Invite);
|
variant="ghost"
|
||||||
}, [data]);
|
size="icon"
|
||||||
|
className="group"
|
||||||
const handleBellClick = useCallback(() => {
|
dot
|
||||||
navigate('/user-setting/team');
|
>
|
||||||
}, [navigate]);
|
<BellRing className="size-4 animate-bell-shake group-hover:animate-none" />
|
||||||
|
|
||||||
return showBell ? (
|
|
||||||
<Button variant={'ghost'} onClick={handleBellClick}>
|
|
||||||
<div className="relative">
|
|
||||||
<BellRing className="size-4 " />
|
|
||||||
<span className="absolute size-1 rounded -right-1 -top-1 bg-red-600"></span>
|
|
||||||
</div>
|
|
||||||
</Button>
|
</Button>
|
||||||
) : null;
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
158
web/src/layouts/global-navbar.tsx
Normal file
158
web/src/layouts/global-navbar.tsx
Normal file
@ -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 (
|
||||||
|
<nav>
|
||||||
|
<ul className="relative flex items-center p-1 bg-bg-card rounded-full border border-border-button">
|
||||||
|
{menuItems.map(({ path, name, icon: Icon, ...props }) => {
|
||||||
|
const isActive = path === activePath;
|
||||||
|
const anchorName = `--${navbarAnchorNamePrefix}${path === Routes.Root ? '-root' : path.replace('/', '-')}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={path} className="relative" style={{ anchorName }}>
|
||||||
|
<Link
|
||||||
|
{...props}
|
||||||
|
to={path}
|
||||||
|
className={cn(
|
||||||
|
'h-10 px-6 text-base inline-flex items-center justify-center',
|
||||||
|
'hover:text-current focus-visible:text-current rounded-full transition-all',
|
||||||
|
isActive && '!text-bg-base',
|
||||||
|
)}
|
||||||
|
aria-current={isActive ? 'page' : undefined}
|
||||||
|
>
|
||||||
|
{Icon && <Icon className="size-6 stroke-[1.5]" />}
|
||||||
|
<span className={cn(Icon && 'sr-only')}>{t(name)}</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
<li
|
||||||
|
className={cn(
|
||||||
|
'absolute -z-[1] bg-text-primary border-b-2 border-b-accent-primary rounded-full opacity-0',
|
||||||
|
'transition-all',
|
||||||
|
hasAnyActive && 'opacity-100',
|
||||||
|
)}
|
||||||
|
role="presentation"
|
||||||
|
style={{
|
||||||
|
top: 'anchor(top)',
|
||||||
|
left: 'anchor(left)',
|
||||||
|
width: 'anchor-size(width)',
|
||||||
|
height: 'anchor-size(height)',
|
||||||
|
positionAnchor: activePathAnchorName,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: () => {
|
||||||
|
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 (
|
||||||
|
<nav>
|
||||||
|
<ul className="flex items-center p-1 bg-bg-card rounded-full border border-border-button">
|
||||||
|
{menuItems.map(({ path, name, icon: Icon, ...props }) => {
|
||||||
|
const isActive = path === activePath;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={path}>
|
||||||
|
<Link
|
||||||
|
{...props}
|
||||||
|
to={path}
|
||||||
|
className={cn(
|
||||||
|
'h-10 px-6 text-base inline-flex items-center justify-center',
|
||||||
|
'hover:text-current focus-visible:text-current rounded-full transition-all',
|
||||||
|
isActive &&
|
||||||
|
'!text-bg-base bg-text-primary border-b-2 border-b-accent-primary',
|
||||||
|
)}
|
||||||
|
aria-label={t(name)}
|
||||||
|
aria-current={isActive ? 'page' : undefined}
|
||||||
|
>
|
||||||
|
{Icon ? (
|
||||||
|
<Icon className="size-6 stroke-[1.5]" />
|
||||||
|
) : (
|
||||||
|
<span>{t(name)}</span>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GlobalNavbar;
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import { IconFontFill } from '@/components/icon-font';
|
import { IconFontFill } from '@/components/icon-font';
|
||||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@ -8,213 +7,143 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { Segmented, SegmentedValue } from '@/components/ui/segmented';
|
import { LanguageList, LanguageMap } from '@/constants/common';
|
||||||
import { LanguageList, LanguageMap, ThemeEnum } from '@/constants/common';
|
|
||||||
import { useChangeLanguage } from '@/hooks/logic-hooks';
|
import { useChangeLanguage } from '@/hooks/logic-hooks';
|
||||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
import {
|
||||||
import { useNavigateWithFromState } from '@/hooks/route-hook';
|
useFetchUserInfo,
|
||||||
import { useFetchUserInfo } from '@/hooks/use-user-setting-request';
|
useListTenant,
|
||||||
|
} from '@/hooks/use-user-setting-request';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { TenantRole } from '@/pages/user-setting/constants';
|
||||||
import { Routes } from '@/routes';
|
import { Routes } from '@/routes';
|
||||||
import { camelCase } from 'lodash';
|
import { camelCase } from 'lodash';
|
||||||
import {
|
import { LucideChevronDown, LucideCircleHelp } from 'lucide-react';
|
||||||
ChevronDown,
|
import React, { useMemo } from 'react';
|
||||||
CircleHelp,
|
|
||||||
Cpu,
|
|
||||||
File,
|
|
||||||
House,
|
|
||||||
Library,
|
|
||||||
MessageSquareText,
|
|
||||||
Moon,
|
|
||||||
Search,
|
|
||||||
Sun,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import React, { useCallback, useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useLocation } from 'react-router';
|
import { Link, useLocation } from 'react-router';
|
||||||
import { BellButton } from './bell-button';
|
import { BellButton } from './bell-button';
|
||||||
|
import GlobalNavbar from './global-navbar';
|
||||||
|
import ThemeButton from './theme-button';
|
||||||
|
|
||||||
const handleDocHelpCLick = () => {
|
export function Header({
|
||||||
window.open('https://ragflow.io/docs/dev/category/guides', 'target');
|
className,
|
||||||
};
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLElement>) {
|
||||||
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() {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const navigate = useNavigateWithFromState();
|
|
||||||
const { navigateToOldProfile } = useNavigatePage();
|
|
||||||
|
|
||||||
const changeLanguage = useChangeLanguage();
|
const changeLanguage = useChangeLanguage();
|
||||||
const { setTheme, theme } = useTheme();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { language = 'English', avatar, nickname },
|
data: { language = 'English', avatar, nickname },
|
||||||
} = useFetchUserInfo();
|
} = useFetchUserInfo();
|
||||||
|
|
||||||
const handleItemClick = (key: string) => () => {
|
const { data: tenantData } = useListTenant();
|
||||||
changeLanguage(key);
|
const hasNotification = useMemo(
|
||||||
};
|
() => tenantData?.some((x) => x.role === TenantRole.Invite),
|
||||||
|
[tenantData],
|
||||||
|
);
|
||||||
|
|
||||||
const items = LanguageList.map((x) => ({
|
const langItems = LanguageList.map((x) => ({
|
||||||
key: x,
|
key: x,
|
||||||
label: <span>{LanguageMap[x as keyof typeof LanguageMap]}</span>,
|
label: <span>{LanguageMap[x as keyof typeof LanguageMap]}</span>,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
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 ? (
|
|
||||||
<HeaderIcon className="size-6"></HeaderIcon>
|
|
||||||
) : (
|
|
||||||
<span
|
|
||||||
data-testid={
|
|
||||||
tag.path === Routes.Chats
|
|
||||||
? 'nav-chat'
|
|
||||||
: tag.path === Routes.Searches
|
|
||||||
? 'nav-search'
|
|
||||||
: tag.path === Routes.Agents
|
|
||||||
? 'nav-agent'
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{tag.name}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
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 (
|
return (
|
||||||
<section
|
<header
|
||||||
className="py-5 px-10 flex justify-between items-center "
|
key="app-navbar"
|
||||||
data-testid="top-nav"
|
className={cn(
|
||||||
|
'w-full grid grid-cols-[1fr_auto_1fr] grid-rows-1 items-center gap-8',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-4">
|
<div className="inline-flex items-center">
|
||||||
<img
|
<Link
|
||||||
src={'/logo.svg'}
|
to={Routes.Root}
|
||||||
alt="logo"
|
aria-current={pathname === Routes.Root ? 'page' : undefined}
|
||||||
className="size-10 mr-[12] cursor-pointer"
|
>
|
||||||
onClick={handleLogoClick}
|
<img src={'/logo.svg'} alt="RAGFlow logo" className="size-10" />
|
||||||
/>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<Segmented
|
|
||||||
rounded="xxxl"
|
<GlobalNavbar />
|
||||||
sizeType="xl"
|
|
||||||
buttonSize="xl"
|
|
||||||
options={options}
|
|
||||||
value={activePathName}
|
|
||||||
onChange={handleChange}
|
|
||||||
activeClassName="text-bg-base bg-metallic-gradient border-b-[#00BEB4] border-b-2"
|
|
||||||
></Segmented>
|
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-5 text-text-badge"
|
className="flex items-center justify-end gap-4 text-text-badge"
|
||||||
data-testid="auth-status"
|
data-testid="auth-status"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
|
className="p-2 text-text-secondary hover:text-text-primary focus-visible:text-text-primary"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://discord.com/invite/NjYzJD3GM3"
|
href="https://discord.com/invite/NjYzJD3GM3"
|
||||||
rel="noreferrer"
|
rel="noreferrer noopener"
|
||||||
>
|
>
|
||||||
<IconFontFill name="a-DiscordIconSVGVectorIcon"></IconFontFill>
|
<IconFontFill name="a-DiscordIconSVGVectorIcon" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
|
className="p-2 text-text-secondary hover:text-text-primary focus-visible:text-text-primary"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://github.com/infiniflow/ragflow"
|
href="https://github.com/infiniflow/ragflow"
|
||||||
rel="noreferrer"
|
rel="noreferrer noopener"
|
||||||
>
|
>
|
||||||
<IconFontFill name="GitHub"></IconFontFill>
|
<IconFontFill name="GitHub" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger asChild>
|
||||||
<div className="flex items-center gap-1">
|
<Button className="flex items-center gap-1" variant="ghost">
|
||||||
{t(`common.${camelCase(language)}`)}
|
{t(`common.${camelCase(language)}`)}
|
||||||
<ChevronDown className="size-4" />
|
|
||||||
</div>
|
<LucideChevronDown className="size-4" />
|
||||||
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
{items.map((x) => (
|
{langItems.map((x) => (
|
||||||
<DropdownMenuItem key={x.key} onClick={handleItemClick(x.key)}>
|
<DropdownMenuItem
|
||||||
|
key={x.key}
|
||||||
|
onClick={() => changeLanguage(x.key)}
|
||||||
|
>
|
||||||
{x.label}
|
{x.label}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<Button variant={'ghost'} onClick={handleDocHelpCLick}>
|
|
||||||
<CircleHelp />
|
<Button
|
||||||
|
asLink
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
to="https://ragflow.io/docs/dev/category/user-guides"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>
|
||||||
|
<LucideCircleHelp className="size-[1em]" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant={'ghost'} onClick={onThemeClick}>
|
|
||||||
{theme === 'light' ? <Sun /> : <Moon />}
|
<ThemeButton />
|
||||||
</Button>
|
|
||||||
<BellButton></BellButton>
|
{hasNotification && <BellButton />}
|
||||||
<div className="relative" data-testid="settings-entrypoint">
|
|
||||||
|
<Link
|
||||||
|
to={Routes.UserSetting}
|
||||||
|
className="relative ms-3"
|
||||||
|
data-testid="settings-entrypoint"
|
||||||
|
>
|
||||||
<RAGFlowAvatar
|
<RAGFlowAvatar
|
||||||
name={nickname}
|
name={nickname}
|
||||||
avatar={avatar}
|
avatar={avatar}
|
||||||
isPerson
|
isPerson
|
||||||
className="size-8 cursor-pointer"
|
className="size-8"
|
||||||
onClick={navigateToOldProfile}
|
/>
|
||||||
></RAGFlowAvatar>
|
|
||||||
{/* Temporarily hidden */}
|
{/* Temporarily hidden */}
|
||||||
{/* <Badge className="h-5 w-8 absolute font-normal p-0 justify-center -right-8 -top-2 text-bg-base bg-gradient-to-l from-[#42D7E7] to-[#478AF5]">
|
{/* <Badge className="h-5 w-8 absolute font-normal p-0 justify-center -right-8 -top-2 text-bg-base bg-gradient-to-l from-[#42D7E7] to-[#478AF5]">
|
||||||
Pro
|
Pro
|
||||||
</Badge> */}
|
</Badge> */}
|
||||||
</div>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,20 @@
|
|||||||
import { Outlet } from 'react-router';
|
import { Outlet } from 'react-router';
|
||||||
import { Header } from './next-header';
|
import { Header } from './next-header';
|
||||||
|
|
||||||
export default function NextLayout() {
|
export function NextLayoutContainer({ children }: React.PropsWithChildren) {
|
||||||
return (
|
return (
|
||||||
<main className="h-full flex flex-col">
|
<div className="size-full grid grid-rows-[auto_1fr] grid-cols-1 grid-flow-col">
|
||||||
<Header />
|
<Header className="px-5 py-4" />
|
||||||
<Outlet />
|
|
||||||
</main>
|
<main className="size-full overflow-hidden">{children}</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NextLayout() {
|
||||||
|
return (
|
||||||
|
<NextLayoutContainer>
|
||||||
|
<Outlet />
|
||||||
|
</NextLayoutContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
19
web/src/layouts/page-container.tsx
Normal file
19
web/src/layouts/page-container.tsx
Normal file
@ -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<React.HTMLAttributes<HTMLDivElement>>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn('size-full px-10 py-3 overflow-auto', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
web/src/layouts/theme-button.tsx
Normal file
22
web/src/layouts/theme-button.tsx
Normal file
@ -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 (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="relative"
|
||||||
|
onClick={() =>
|
||||||
|
setTheme(theme === ThemeEnum.Dark ? ThemeEnum.Light : ThemeEnum.Dark)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{theme === ThemeEnum.Light ? <LucideSun /> : <LucideMoon />}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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 { useSetModalState } from '@/hooks/common-hooks';
|
||||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||||
import { useFetchAgentTemplates, useSetAgent } from '@/hooks/use-agent-request';
|
import { useFetchAgentTemplates, useSetAgent } from '@/hooks/use-agent-request';
|
||||||
@ -15,14 +6,11 @@ import { CardContainer } from '@/components/card-container';
|
|||||||
import { AgentCategory } from '@/constants/agent';
|
import { AgentCategory } from '@/constants/agent';
|
||||||
import { IFlowTemplate } from '@/interfaces/database/agent';
|
import { IFlowTemplate } from '@/interfaces/database/agent';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { CreateAgentDialog } from './create-agent-dialog';
|
import { CreateAgentDialog } from './create-agent-dialog';
|
||||||
import { TemplateCard } from './template-card';
|
import { TemplateCard } from './template-card';
|
||||||
import { MenuItemKey, SideBar } from './template-sidebar';
|
import { MenuItemKey, SideBar } from './template-sidebar';
|
||||||
|
|
||||||
export default function AgentTemplates() {
|
export default function AgentTemplates() {
|
||||||
const { navigateToAgents } = useNavigatePage();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const list = useFetchAgentTemplates();
|
const list = useFetchAgentTemplates();
|
||||||
const { loading, setAgent } = useSetAgent();
|
const { loading, setAgent } = useSetAgent();
|
||||||
const [templateList, setTemplateList] = useState<IFlowTemplate[]>([]);
|
const [templateList, setTemplateList] = useState<IFlowTemplate[]>([]);
|
||||||
@ -99,21 +87,6 @@ export default function AgentTemplates() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<PageHeader>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbList>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink onClick={navigateToAgents}>
|
|
||||||
{t('flow.agent')}
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbPage>{t('flow.createGraph')}</BreadcrumbPage>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
</PageHeader>
|
|
||||||
<div className="flex flex-1 h-dvh">
|
<div className="flex flex-1 h-dvh">
|
||||||
<SideBar
|
<SideBar
|
||||||
change={handleSiderBarChange}
|
change={handleSiderBarChange}
|
||||||
|
|||||||
@ -23,14 +23,7 @@ import DocumentPreview from '@/components/document-preview';
|
|||||||
import DocumentHeader from '@/components/document-preview/document-header';
|
import DocumentHeader from '@/components/document-preview/document-header';
|
||||||
import { useGetDocumentUrl } from '@/components/document-preview/hooks';
|
import { useGetDocumentUrl } from '@/components/document-preview/hooks';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import {
|
import { Button } from '@/components/ui/button';
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbLink,
|
|
||||||
BreadcrumbList,
|
|
||||||
BreadcrumbPage,
|
|
||||||
BreadcrumbSeparator,
|
|
||||||
} from '@/components/ui/breadcrumb';
|
|
||||||
import message from '@/components/ui/message';
|
import message from '@/components/ui/message';
|
||||||
import {
|
import {
|
||||||
RAGFlowPagination,
|
RAGFlowPagination,
|
||||||
@ -42,6 +35,7 @@ import {
|
|||||||
useNavigatePage,
|
useNavigatePage,
|
||||||
} from '@/hooks/logic-hooks/navigate-hooks';
|
} from '@/hooks/logic-hooks/navigate-hooks';
|
||||||
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
|
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
|
||||||
|
import { LucideArrowBigLeft } from 'lucide-react';
|
||||||
import styles from './index.module.less';
|
import styles from './index.module.less';
|
||||||
|
|
||||||
const Chunk = () => {
|
const Chunk = () => {
|
||||||
@ -182,29 +176,15 @@ const Chunk = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader>
|
<PageHeader>
|
||||||
<Breadcrumb>
|
<Button
|
||||||
<BreadcrumbList>
|
variant="outline"
|
||||||
<BreadcrumbItem>
|
onClick={navigateToDataFile(
|
||||||
<BreadcrumbLink onClick={navigateToDatasetList}>
|
getQueryString(QueryStringMap.id) as string,
|
||||||
{t('knowledgeDetails.dataset')}
|
)}
|
||||||
</BreadcrumbLink>
|
>
|
||||||
</BreadcrumbItem>
|
<LucideArrowBigLeft />
|
||||||
<BreadcrumbSeparator />
|
{t('common.back')}
|
||||||
<BreadcrumbItem>
|
</Button>
|
||||||
<BreadcrumbLink
|
|
||||||
onClick={navigateToDataFile(
|
|
||||||
getQueryString(QueryStringMap.id) as string,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{dataset.name}
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbPage>{documentInfo?.name}</BreadcrumbPage>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<div className={styles.chunkPage}>
|
<div className={styles.chunkPage}>
|
||||||
<div className="flex flex-1 gap-8">
|
<div className="flex flex-1 gap-8">
|
||||||
|
|||||||
@ -2,10 +2,17 @@ import FileStatusBadge from '@/components/file-status-badge';
|
|||||||
import { FilterCollection } from '@/components/list-filter-bar/interface';
|
import { FilterCollection } from '@/components/list-filter-bar/interface';
|
||||||
import SvgIcon from '@/components/svg-icon';
|
import SvgIcon from '@/components/svg-icon';
|
||||||
import { useIsDarkTheme } from '@/components/theme-provider';
|
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 { RunningStatusMap } from '@/constants/knowledge';
|
||||||
import { useFetchDocumentList } from '@/hooks/use-document-request';
|
import { useFetchDocumentList } from '@/hooks/use-document-request';
|
||||||
import { CircleQuestionMark } from 'lucide-react';
|
|
||||||
import { FC, useEffect, useMemo, useState } from 'react';
|
import { FC, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { RunningStatus } from '../dataset/constant';
|
import { RunningStatus } from '../dataset/constant';
|
||||||
@ -37,23 +44,35 @@ const StatCard: FC<StatCardProps> = ({
|
|||||||
tooltip,
|
tooltip,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-bg-card p-4 rounded-lg border border-border flex flex-col gap-2">
|
<Card
|
||||||
<div className="flex items-center justify-between">
|
className="px-5 py-2.5 rounded-lg border-border-default grid grid-cols-[1fr_auto] grid-rows-[1fr_auto]"
|
||||||
<h3 className="flex items-center gap-1 text-sm font-medium text-text-secondary">
|
style={{
|
||||||
{title}
|
gridTemplateAreas: '"data icon" "footer footer"',
|
||||||
{tooltip && (
|
}}
|
||||||
<AntToolTip title={tooltip} trigger="hover">
|
>
|
||||||
<CircleQuestionMark size={12} />
|
<span style={{ gridArea: 'icon' }}>{icon}</span>
|
||||||
</AntToolTip>
|
|
||||||
)}
|
<div style={{ gridArea: 'data' }}>
|
||||||
</h3>
|
<CardHeader className="p-0">
|
||||||
{icon}
|
<h3 className="flex items-center gap-1 text-sm font-medium text-text-secondary">
|
||||||
|
{title}
|
||||||
|
|
||||||
|
{tooltip && <WhatIsThis>{tooltip}</WhatIsThis>}
|
||||||
|
</h3>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardDescription className="text-text-primary text-2xl font-medium leading-9">
|
||||||
|
<data value={value}>{value}</data>
|
||||||
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold text-text-primary">{value}</div>
|
|
||||||
<div className="h-12 w-full flex items-center">
|
<CardFooter
|
||||||
|
className="p-0 mt-1.5 h-8 w-full flex items-end"
|
||||||
|
style={{ gridArea: 'footer' }}
|
||||||
|
>
|
||||||
<div className="flex-1">{children}</div>
|
<div className="flex-1">{children}</div>
|
||||||
</div>
|
</CardFooter>
|
||||||
</div>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -64,38 +83,34 @@ const CardFooterProcess: FC<CardFooterProcessProps> = ({
|
|||||||
failedTip,
|
failedTip,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center flex-col gap-2">
|
<div className="flex items-center flex-col gap-2">
|
||||||
<div className="w-full flex justify-between gap-4 rounded-lg text-sm font-bold text-text-primary">
|
<dl className="w-full flex justify-between gap-4 rounded-lg text-sm font-bold text-text-primary">
|
||||||
<div className="flex items-center justify-between rounded-md w-1/2 p-2 bg-state-success-5">
|
<div className="flex items-center justify-between rounded-sm w-1/2 p-2 bg-state-success-5">
|
||||||
<div className="flex items-center rounded-lg gap-1">
|
<dt className="flex items-center rounded-lg gap-1">
|
||||||
<div className="w-2 h-2 rounded-full bg-state-success "></div>
|
<div className="w-1 h-1 rounded-full bg-state-success"></div>
|
||||||
<div className="font-normal text-text-secondary text-xs flex items-center gap-1">
|
<div className="font-normal text-text-secondary text-xs flex items-center gap-1">
|
||||||
{t('knowledgeDetails.success')}
|
{t('knowledgeDetails.success')}
|
||||||
{successTip && (
|
{successTip && <WhatIsThis>{successTip}</WhatIsThis>}
|
||||||
<AntToolTip title={successTip} trigger="hover">
|
|
||||||
<CircleQuestionMark size={12} />
|
|
||||||
</AntToolTip>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</dt>
|
||||||
<div>{success || 0}</div>
|
|
||||||
|
<dd className="font-normal">{success || 0}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between rounded-md w-1/2 bg-state-error-5 p-2">
|
|
||||||
<div className="flex items-center rounded-lg gap-1">
|
<div className="flex items-center justify-between rounded-sm w-1/2 bg-state-error-5 p-2">
|
||||||
<div className="w-2 h-2 rounded-full bg-state-error"></div>
|
<dt className="flex items-center rounded-lg gap-1">
|
||||||
|
<div className="w-1 h-1 rounded-full bg-state-error"></div>
|
||||||
<div className="font-normal text-text-secondary text-xs flex items-center gap-1">
|
<div className="font-normal text-text-secondary text-xs flex items-center gap-1">
|
||||||
{t('knowledgeDetails.failed')}
|
{t('knowledgeDetails.failed')}
|
||||||
{failedTip && (
|
{failedTip && <WhatIsThis>{failedTip}</WhatIsThis>}
|
||||||
<AntToolTip title={failedTip} trigger="hover">
|
|
||||||
<CircleQuestionMark size={12} />
|
|
||||||
</AntToolTip>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</dt>
|
||||||
<div>{failed || 0}</div>
|
|
||||||
|
<dd className="font-normal">{failed || 0}</dd>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -247,9 +262,13 @@ const FileLogsPage: FC = () => {
|
|||||||
const isDark = useIsDarkTheme();
|
const isDark = useIsDarkTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-5 min-w-[880px] border-border border rounded-lg mr-5">
|
<Card
|
||||||
|
className="
|
||||||
|
p-5 min-w-[880px] mr-5 mb-5 bg-transparent shadow-none
|
||||||
|
flex flex-col overflow-y-auto scrollbar-auto"
|
||||||
|
>
|
||||||
{/* Stats Cards */}
|
{/* Stats Cards */}
|
||||||
<div className="grid grid-cols-3 md:grid-cols-3 gap-4 mb-6">
|
<div className="grid grid-cols-3 md:grid-cols-3 gap-7 mb-6">
|
||||||
<StatCard
|
<StatCard
|
||||||
title={t('datasetOverview.totalFiles')}
|
title={t('datasetOverview.totalFiles')}
|
||||||
value={topAllData.totalFiles.value}
|
value={topAllData.totalFiles.value}
|
||||||
@ -261,12 +280,12 @@ const FileLogsPage: FC = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div>
|
<div className="text-xs">
|
||||||
<span className="text-accent-primary">
|
<span className="text-accent-primary">
|
||||||
{topAllData.totalFiles.precent > 0 ? '+' : ''}
|
{topAllData.totalFiles.precent > 0 ? '+' : ''}
|
||||||
{topAllData.totalFiles.precent}%{' '}
|
{topAllData.totalFiles.precent}%{' '}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-normal text-text-secondary text-xs">
|
<span className="font-normal text-text-secondary">
|
||||||
{t('knowledgeConfiguration.lastWeek')}
|
{t('knowledgeConfiguration.lastWeek')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -330,7 +349,7 @@ const FileLogsPage: FC = () => {
|
|||||||
pageCount={10}
|
pageCount={10}
|
||||||
active={active}
|
active={active}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -414,8 +414,8 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-[calc(100vh-360px)]">
|
<div className="size-full flex flex-col">
|
||||||
<Table rootClassName="max-h-[calc(100vh-380px)]">
|
<Table rootClassName="max-h-full mb-4">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id}>
|
<TableRow key={headerGroup.id}>
|
||||||
@ -460,15 +460,15 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
|
|||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
<div className="flex items-center justify-end absolute bottom-3 right-12">
|
|
||||||
<div className="space-x-2">
|
<div className="mt-auto flex items-center justify-end">
|
||||||
<RAGFlowPagination
|
<RAGFlowPagination
|
||||||
{...{ current: pagination.current, pageSize: pagination.pageSize }}
|
{...{ current: pagination.current, pageSize: pagination.pageSize }}
|
||||||
total={pagination.total}
|
total={pagination.total}
|
||||||
onChange={(page, pageSize) => setPagination({ page, pageSize })}
|
onChange={(page, pageSize) => setPagination({ page, pageSize })}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isModalVisible && (
|
{isModalVisible && (
|
||||||
<ProcessLogModal
|
<ProcessLogModal
|
||||||
title={active === LogTabs.FILE_LOGS ? t('fileLogs') : t('datasetLog')}
|
title={active === LogTabs.FILE_LOGS ? t('fileLogs') : t('datasetLog')}
|
||||||
|
|||||||
@ -2,6 +2,13 @@ import { DataFlowSelect } from '@/components/data-pipeline-select';
|
|||||||
import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields';
|
import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields';
|
||||||
import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields';
|
import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@/components/ui/card';
|
||||||
import Divider from '@/components/ui/divider';
|
import Divider from '@/components/ui/divider';
|
||||||
import { Form } from '@/components/ui/form';
|
import { Form } from '@/components/ui/form';
|
||||||
import { FormLayout } from '@/constants/form';
|
import { FormLayout } from '@/constants/form';
|
||||||
@ -15,7 +22,6 @@ import { createContext, useEffect, useState } from 'react';
|
|||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { TopTitle } from '../dataset-title';
|
|
||||||
import {
|
import {
|
||||||
GenerateType,
|
GenerateType,
|
||||||
IGenerateLogButtonProps,
|
IGenerateLogButtonProps,
|
||||||
@ -258,98 +264,113 @@ export default function DatasetSettings() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="p-5 h-full flex flex-col">
|
<div className="pr-5 pb-5">
|
||||||
<TopTitle
|
<Card className="p-0 h-full flex flex-col bg-transparent shadow-none">
|
||||||
title={t('knowledgeDetails.configuration')}
|
<CardHeader className="p-5 border-b-0.5 border-border-button">
|
||||||
description={t('knowledgeConfiguration.titleDescription')}
|
<header>
|
||||||
></TopTitle>
|
<CardTitle as="h1">{t('knowledgeDetails.configuration')}</CardTitle>
|
||||||
<div className="flex gap-14 flex-1 min-h-0">
|
|
||||||
<DataSetContext.Provider
|
|
||||||
value={{
|
|
||||||
loading: datasetSettingLoading,
|
|
||||||
knowledgeDetails: knowledgeDetails,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Form {...form}>
|
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6 ">
|
|
||||||
<div className="w-[768px] h-[calc(100vh-240px)] pr-1 overflow-y-auto scrollbar-auto">
|
|
||||||
<MainContainer className="text-text-secondary">
|
|
||||||
<div className="text-base font-medium text-text-primary">
|
|
||||||
{t('knowledgeConfiguration.baseInfo')}
|
|
||||||
</div>
|
|
||||||
<GeneralForm></GeneralForm>
|
|
||||||
|
|
||||||
<Divider />
|
<CardDescription>
|
||||||
<div className="text-base font-medium text-text-primary">
|
{t('knowledgeConfiguration.titleDescription')}
|
||||||
{t('knowledgeConfiguration.dataPipeline')}
|
</CardDescription>
|
||||||
</div>
|
|
||||||
<ParseTypeItem line={1} />
|
|
||||||
{parseType === 1 && (
|
|
||||||
<ChunkMethodItem line={1}></ChunkMethodItem>
|
|
||||||
)}
|
|
||||||
{parseType === 2 && (
|
|
||||||
<DataFlowSelect
|
|
||||||
isMult={false}
|
|
||||||
showToDataPipeline={true}
|
|
||||||
formFieldName="pipeline_id"
|
|
||||||
layout={FormLayout.Horizontal}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* <Divider /> */}
|
{/* <Button>Save as Preset</Button> */}
|
||||||
{parseType === 1 && <ChunkMethodForm />}
|
</header>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
{/* <LinkDataPipeline
|
<CardContent className="p-0 flex-1 h-0 flex divide-x-0.5">
|
||||||
data={pipelineData}
|
<DataSetContext.Provider
|
||||||
handleLinkOrEditSubmit={handleLinkOrEditSubmit}
|
value={{
|
||||||
/> */}
|
loading: datasetSettingLoading,
|
||||||
<Divider />
|
knowledgeDetails: knowledgeDetails,
|
||||||
<LinkDataSource
|
}}
|
||||||
data={sourceData}
|
>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
className="flex flex-col"
|
||||||
|
>
|
||||||
|
<div className="flex-1 h-0 w-[768px] px-5 pt-5 overflow-y-auto scrollbar-auto">
|
||||||
|
<MainContainer className="text-text-secondary">
|
||||||
|
<div className="text-base font-medium text-text-primary">
|
||||||
|
{t('knowledgeConfiguration.baseInfo')}
|
||||||
|
</div>
|
||||||
|
<GeneralForm></GeneralForm>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<div className="text-base font-medium text-text-primary">
|
||||||
|
{t('knowledgeConfiguration.dataPipeline')}
|
||||||
|
</div>
|
||||||
|
<ParseTypeItem line={1} />
|
||||||
|
{parseType === 1 && (
|
||||||
|
<ChunkMethodItem line={1}></ChunkMethodItem>
|
||||||
|
)}
|
||||||
|
{parseType === 2 && (
|
||||||
|
<DataFlowSelect
|
||||||
|
isMult={false}
|
||||||
|
showToDataPipeline={true}
|
||||||
|
formFieldName="pipeline_id"
|
||||||
|
layout={FormLayout.Horizontal}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* <Divider /> */}
|
||||||
|
{parseType === 1 && <ChunkMethodForm />}
|
||||||
|
|
||||||
|
{/* <LinkDataPipeline
|
||||||
|
data={pipelineData}
|
||||||
handleLinkOrEditSubmit={handleLinkOrEditSubmit}
|
handleLinkOrEditSubmit={handleLinkOrEditSubmit}
|
||||||
unbindFunc={unbindFunc}
|
/> */}
|
||||||
handleAutoParse={handleAutoParse}
|
<Divider />
|
||||||
/>
|
<LinkDataSource
|
||||||
<Divider />
|
data={sourceData}
|
||||||
<div className="text-base font-medium text-text-primary">
|
handleLinkOrEditSubmit={handleLinkOrEditSubmit}
|
||||||
{t('knowledgeConfiguration.globalIndex')}
|
unbindFunc={unbindFunc}
|
||||||
</div>
|
handleAutoParse={handleAutoParse}
|
||||||
<GraphRagItems
|
/>
|
||||||
className="border-none p-0"
|
<Divider />
|
||||||
data={graphRagGenerateData as IGenerateLogButtonProps}
|
<div className="text-base font-medium text-text-primary">
|
||||||
onDelete={() =>
|
{t('knowledgeConfiguration.globalIndex')}
|
||||||
handleDeletePipelineTask(GenerateType.KnowledgeGraph)
|
</div>
|
||||||
}
|
<GraphRagItems
|
||||||
></GraphRagItems>
|
className="border-none p-0"
|
||||||
<Divider />
|
data={graphRagGenerateData as IGenerateLogButtonProps}
|
||||||
<RaptorFormFields
|
onDelete={() =>
|
||||||
data={raptorGenerateData as IGenerateLogButtonProps}
|
handleDeletePipelineTask(GenerateType.KnowledgeGraph)
|
||||||
onDelete={() =>
|
}
|
||||||
handleDeletePipelineTask(GenerateType.Raptor)
|
></GraphRagItems>
|
||||||
}
|
<Divider />
|
||||||
></RaptorFormFields>
|
<RaptorFormFields
|
||||||
</MainContainer>
|
data={raptorGenerateData as IGenerateLogButtonProps}
|
||||||
</div>
|
onDelete={() =>
|
||||||
<div className="text-right items-center flex justify-end gap-3 w-[768px]">
|
handleDeletePipelineTask(GenerateType.Raptor)
|
||||||
<Button
|
}
|
||||||
type="reset"
|
></RaptorFormFields>
|
||||||
className="bg-transparent text-color-white hover:bg-transparent border-gray-500 border-[1px]"
|
</MainContainer>
|
||||||
data-testid="ds-settings-page-cancel-btn"
|
</div>
|
||||||
onClick={() => {
|
|
||||||
form.reset();
|
<div className="p-5 text-right items-center flex justify-end gap-3 w-[768px]">
|
||||||
}}
|
<Button
|
||||||
>
|
type="reset"
|
||||||
{t('knowledgeConfiguration.cancel')}
|
variant="transparent"
|
||||||
</Button>
|
onClick={() => {
|
||||||
<SavingButton></SavingButton>
|
form.reset();
|
||||||
</div>
|
}}
|
||||||
</form>
|
>
|
||||||
</Form>
|
{t('knowledgeConfiguration.cancel')}
|
||||||
<div className="flex-1">
|
</Button>
|
||||||
|
|
||||||
|
<SavingButton />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</DataSetContext.Provider>
|
||||||
|
|
||||||
|
<div className="flex-1 p-5 overflow-auto">
|
||||||
{parseType === 1 && <ChunkMethodLearnMore parserId={selectedTag} />}
|
{parseType === 1 && <ChunkMethodLearnMore parserId={selectedTag} />}
|
||||||
</div>
|
</div>
|
||||||
</DataSetContext.Provider>
|
</CardContent>
|
||||||
</div>
|
</Card>
|
||||||
</section>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,11 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { Modal } from '@/components/ui/modal/modal';
|
import { Modal } from '@/components/ui/modal/modal';
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from '@/components/ui/tooltip';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { toFixed } from '@/utils/common-util';
|
import { toFixed } from '@/utils/common-util';
|
||||||
import { formatDate } from '@/utils/date';
|
import { formatDate } from '@/utils/date';
|
||||||
@ -25,6 +30,7 @@ import {
|
|||||||
useTraceGenerate,
|
useTraceGenerate,
|
||||||
useUnBindTask,
|
useUnBindTask,
|
||||||
} from './hook';
|
} from './hook';
|
||||||
|
|
||||||
export enum GenerateType {
|
export enum GenerateType {
|
||||||
KnowledgeGraph = 'KnowledgeGraph',
|
KnowledgeGraph = 'KnowledgeGraph',
|
||||||
Raptor = 'Raptor',
|
Raptor = 'Raptor',
|
||||||
@ -191,45 +197,46 @@ const Generate: React.FC<GenerateProps> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="generate">
|
<DropdownMenu open={open} onOpenChange={handleOpenChange}>
|
||||||
<DropdownMenu open={open} onOpenChange={handleOpenChange}>
|
<DropdownMenuTrigger asChild disabled={disabled}>
|
||||||
<DropdownMenuTrigger asChild disabled={disabled}>
|
<Tooltip>
|
||||||
<div className={cn({ 'cursor-not-allowed': disabled })}>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
variant={'transparent'}
|
className={cn(disabled && '!cursor-not-allowed')}
|
||||||
|
variant="transparent"
|
||||||
|
size="icon"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
handleOpenChange(!open);
|
handleOpenChange(!open);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<WandSparkles className="mr-2 size-4" />
|
<WandSparkles />
|
||||||
{t('knowledgeDetails.generate')}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</TooltipTrigger>
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="w-[380px] p-5 flex flex-col gap-2 ">
|
<TooltipContent>{t('knowledgeDetails.generate')}</TooltipContent>
|
||||||
{Object.values(GenerateType).map((name) => {
|
</Tooltip>
|
||||||
const data = (
|
</DropdownMenuTrigger>
|
||||||
name === GenerateType.KnowledgeGraph
|
<DropdownMenuContent className="w-[380px] p-5 flex flex-col gap-2 ">
|
||||||
? graphRunData
|
{Object.values(GenerateType).map((name) => {
|
||||||
: raptorRunData
|
const data = (
|
||||||
) as ITraceInfo;
|
name === GenerateType.KnowledgeGraph ? graphRunData : raptorRunData
|
||||||
return (
|
) as ITraceInfo;
|
||||||
<div key={name}>
|
return (
|
||||||
<MenuItem
|
<div key={name}>
|
||||||
name={name}
|
<MenuItem
|
||||||
runGenerate={runGenerate}
|
name={name}
|
||||||
data={data}
|
runGenerate={runGenerate}
|
||||||
pauseGenerate={pauseGenerate}
|
data={data}
|
||||||
/>
|
pauseGenerate={pauseGenerate}
|
||||||
</div>
|
/>
|
||||||
);
|
</div>
|
||||||
})}
|
);
|
||||||
</DropdownMenuContent>
|
})}
|
||||||
</DropdownMenu>
|
</DropdownMenuContent>
|
||||||
</div>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -137,9 +137,6 @@ export default function Dataset() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="absolute top-4 right-5">
|
|
||||||
<Generate disabled={!(dataSetData.chunk_num > 0)} />
|
|
||||||
</div>
|
|
||||||
<section className="p-5 min-w-[880px]">
|
<section className="p-5 min-w-[880px]">
|
||||||
<ListFilterBar
|
<ListFilterBar
|
||||||
title={t('header.dataset')}
|
title={t('header.dataset')}
|
||||||
@ -158,6 +155,7 @@ export default function Dataset() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
preChildren={<Generate disabled={!(dataSetData.chunk_num > 0)} />}
|
||||||
// preChildren={
|
// preChildren={
|
||||||
// <Button
|
// <Button
|
||||||
// variant={'ghost'}
|
// variant={'ghost'}
|
||||||
|
|||||||
@ -1,51 +1,19 @@
|
|||||||
import { PageHeader } from '@/components/page-header';
|
|
||||||
import {
|
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbLink,
|
|
||||||
BreadcrumbList,
|
|
||||||
BreadcrumbPage,
|
|
||||||
BreadcrumbSeparator,
|
|
||||||
} from '@/components/ui/breadcrumb';
|
|
||||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
|
||||||
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
|
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
|
||||||
import { KnowledgeBaseProvider } from '@/pages/dataset/contexts/knowledge-base-context';
|
import { KnowledgeBaseProvider } from '@/pages/dataset/contexts/knowledge-base-context';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { Outlet } from 'react-router';
|
import { Outlet } from 'react-router';
|
||||||
import { SideBar } from './sidebar';
|
import { SideBar } from './sidebar';
|
||||||
|
|
||||||
export default function DatasetWrapper() {
|
export default function DatasetWrapper() {
|
||||||
const { navigateToDatasetList } = useNavigatePage();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { data, loading } = useFetchKnowledgeBaseConfiguration();
|
const { data, loading } = useFetchKnowledgeBaseConfiguration();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KnowledgeBaseProvider knowledgeBase={data} loading={loading}>
|
<KnowledgeBaseProvider knowledgeBase={data} loading={loading}>
|
||||||
<section className="flex h-full flex-col w-full">
|
<article className="pt-3 size-full grid grid-cols-[auto_1fr] grid-rows-1">
|
||||||
<PageHeader>
|
<SideBar />
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbList>
|
<Outlet />
|
||||||
<BreadcrumbItem>
|
</article>
|
||||||
<BreadcrumbLink onClick={navigateToDatasetList}>
|
|
||||||
{t('knowledgeDetails.dataset')}
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbPage className="w-28 whitespace-nowrap text-ellipsis overflow-hidden">
|
|
||||||
{data.name}
|
|
||||||
</BreadcrumbPage>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
</PageHeader>
|
|
||||||
<div className="flex flex-1 min-h-0">
|
|
||||||
<SideBar></SideBar>
|
|
||||||
<div className="flex-1 overflow-auto">
|
|
||||||
<Outlet />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</KnowledgeBaseProvider>
|
</KnowledgeBaseProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,15 @@
|
|||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import {
|
||||||
|
LucideFolderOpen,
|
||||||
|
LucideLogs,
|
||||||
|
LucideSettings,
|
||||||
|
LucideTextSearch,
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
import { IconFontFill } from '@/components/icon-font';
|
import { IconFontFill } from '@/components/icon-font';
|
||||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -9,11 +21,8 @@ import {
|
|||||||
import { cn, formatBytes } from '@/lib/utils';
|
import { cn, formatBytes } from '@/lib/utils';
|
||||||
import { Routes } from '@/routes';
|
import { Routes } from '@/routes';
|
||||||
import { formatPureDate } from '@/utils/date';
|
import { formatPureDate } from '@/utils/date';
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { Banknote, FileSearch2, FolderOpen, Logs } from 'lucide-react';
|
import { useParams } from 'react-router';
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useHandleMenuClick } from './hooks';
|
|
||||||
|
|
||||||
type PropType = {
|
type PropType = {
|
||||||
refreshCount?: number;
|
refreshCount?: number;
|
||||||
@ -21,7 +30,7 @@ type PropType = {
|
|||||||
|
|
||||||
export function SideBar({ refreshCount }: PropType) {
|
export function SideBar({ refreshCount }: PropType) {
|
||||||
const pathName = useSecondPathName();
|
const pathName = useSecondPathName();
|
||||||
const { handleMenuClick } = useHandleMenuClick();
|
const { id } = useParams();
|
||||||
// refreshCount: be for avatar img sync update on top left
|
// refreshCount: be for avatar img sync update on top left
|
||||||
const { data } = useFetchKnowledgeBaseConfiguration({ refreshCount });
|
const { data } = useFetchKnowledgeBaseConfiguration({ refreshCount });
|
||||||
const { data: routerData } = useFetchKnowledgeGraph();
|
const { data: routerData } = useFetchKnowledgeGraph();
|
||||||
@ -30,75 +39,91 @@ export function SideBar({ refreshCount }: PropType) {
|
|||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
const list = [
|
const list = [
|
||||||
{
|
{
|
||||||
icon: <FolderOpen className="size-4" />,
|
icon: <LucideFolderOpen className="size-[1em]" />,
|
||||||
label: t(`knowledgeDetails.subbarFiles`),
|
label: t(`knowledgeDetails.subbarFiles`),
|
||||||
key: Routes.DatasetBase,
|
key: Routes.DatasetBase,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FileSearch2 className="size-4" />,
|
icon: <LucideTextSearch className="size-[1em]" />,
|
||||||
label: t(`knowledgeDetails.testing`),
|
label: t(`knowledgeDetails.testing`),
|
||||||
key: Routes.DatasetTesting,
|
key: Routes.DatasetTesting,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Logs className="size-4" />,
|
icon: <LucideLogs className="size-[1em]" />,
|
||||||
label: t(`knowledgeDetails.overview`),
|
label: t(`knowledgeDetails.overview`),
|
||||||
key: Routes.DataSetOverview,
|
key: Routes.DataSetOverview,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Banknote className="size-4" />,
|
icon: <LucideSettings className="size-[1em]" />,
|
||||||
label: t(`knowledgeDetails.configuration`),
|
label: t(`knowledgeDetails.configuration`),
|
||||||
key: Routes.DataSetSetting,
|
key: Routes.DataSetSetting,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!isEmpty(routerData?.graph)) {
|
if (!isEmpty(routerData?.graph)) {
|
||||||
list.push({
|
list.push({
|
||||||
icon: <IconFontFill name="knowledgegraph" className="size-4" />,
|
icon: <IconFontFill name="knowledgegraph" className="size-[1em]" />,
|
||||||
label: t(`knowledgeDetails.knowledgeGraph`),
|
label: t(`knowledgeDetails.knowledgeGraph`),
|
||||||
key: Routes.KnowledgeGraph,
|
key: Routes.KnowledgeGraph,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}, [t, routerData]);
|
}, [t, routerData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="relative p-5 space-y-8">
|
<aside className="w-64 relative px-5 space-y-8">
|
||||||
<div className="flex gap-2.5 max-w-[200px] items-center">
|
<header
|
||||||
|
className="grid grid-cols-[auto_1fr] grid-rows-[auto_auto] gap-x-3"
|
||||||
|
style={{
|
||||||
|
gridTemplateAreas: '"avatar title" "avatar stats"',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<RAGFlowAvatar
|
<RAGFlowAvatar
|
||||||
avatar={data.avatar}
|
avatar={data.avatar}
|
||||||
name={data.name}
|
name={data.name}
|
||||||
className="size-16"
|
className="size-16"
|
||||||
></RAGFlowAvatar>
|
style={{ gridArea: 'avatar' }}
|
||||||
<div className=" text-text-secondary text-xs space-y-1 overflow-hidden">
|
/>
|
||||||
<h3 className="text-lg font-semibold line-clamp-1 text-text-primary text-ellipsis overflow-hidden">
|
|
||||||
{data.name}
|
<h3
|
||||||
</h3>
|
className="text-lg font-semibold line-clamp-1 text-text-primary text-ellipsis overflow-hidden"
|
||||||
|
style={{ gridArea: 'title' }}
|
||||||
|
>
|
||||||
|
{data.name}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="self-end text-text-secondary text-xs overflow-hidden"
|
||||||
|
style={{ gridArea: 'stats' }}
|
||||||
|
>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span>
|
<span>
|
||||||
{data.doc_num} {t('knowledgeDetails.files')}
|
{data.doc_num} {t('knowledgeDetails.files')}
|
||||||
</span>
|
</span>
|
||||||
<span>{formatBytes(data.size)}</span>
|
<span>{formatBytes(data.size)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
|
<div className="mt-0.5">
|
||||||
{t('knowledgeDetails.created')} {formatPureDate(data.create_time)}
|
{t('knowledgeDetails.created')} {formatPureDate(data.create_time)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
|
|
||||||
<div className="w-[200px] flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
{items.map((item, itemIdx) => {
|
{items.map((item, itemIdx) => {
|
||||||
const active = '/' + pathName === item.key;
|
const active = '/' + pathName === item.key;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
key={itemIdx}
|
key={itemIdx}
|
||||||
variant={active ? 'secondary' : 'ghost'}
|
asLink
|
||||||
|
variant="ghost"
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full justify-start gap-2.5 px-3 relative h-10 text-text-secondary',
|
'w-full justify-start gap-2.5 px-3 relative h-10 text-base',
|
||||||
{
|
active && 'bg-bg-card text-text-primary',
|
||||||
'bg-bg-card': active,
|
|
||||||
'text-text-primary': active,
|
|
||||||
},
|
|
||||||
)}
|
)}
|
||||||
onClick={handleMenuClick(item.key)}
|
to={`${Routes.DatasetBase}${item.key}/${id}`}
|
||||||
>
|
>
|
||||||
{item.icon}
|
{item.icon}
|
||||||
<span>{item.label}</span>
|
<span>{item.label}</span>
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@/components/ui/card';
|
||||||
import { useTestRetrieval } from '@/hooks/use-knowledge-request';
|
import { useTestRetrieval } from '@/hooks/use-knowledge-request';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { TopTitle } from '../dataset-title';
|
|
||||||
import TestingForm from './testing-form';
|
import TestingForm from './testing-form';
|
||||||
import { TestingResult } from './testing-result';
|
import { TestingResult } from './testing-result';
|
||||||
|
|
||||||
@ -21,79 +27,92 @@ export default function RetrievalTesting() {
|
|||||||
const [count] = useState(1);
|
const [count] = useState(1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-5">
|
<div className="pr-5 pb-5">
|
||||||
<section className="flex justify-between items-center">
|
<Card className="size-full bg-transparent shadow-none flex flex-col">
|
||||||
<TopTitle
|
<CardHeader className="p-5 border-b-0.5 border-border-button">
|
||||||
title={t('knowledgeDetails.retrievalTesting')}
|
<header>
|
||||||
description={t('knowledgeDetails.testingDescription')}
|
<CardTitle as="h1">
|
||||||
></TopTitle>
|
{t('knowledgeDetails.retrievalTesting')}
|
||||||
{/* <Button>Save as Preset</Button> */}
|
</CardTitle>
|
||||||
</section>
|
|
||||||
{count === 1 ? (
|
<CardDescription>
|
||||||
<section className="flex divide-x h-full">
|
{t('knowledgeDetails.testingDescription')}
|
||||||
<div className="p-4 flex-1">
|
</CardDescription>
|
||||||
<div className="flex justify-between pb-2.5">
|
|
||||||
<span className="text-text-primary font-semibold text-2xl">
|
{/* <Button>Save as Preset</Button> */}
|
||||||
{t('knowledgeDetails.testSetting')}
|
</header>
|
||||||
</span>
|
</CardHeader>
|
||||||
{/* <Button variant={'outline'} onClick={addCount}>
|
|
||||||
<Plus /> Add New Test
|
{count === 1 ? (
|
||||||
</Button> */}
|
<CardContent className="flex-1 overflow-hidden p-0 grid grid-rows-1 grid-cols-2 divide-x-0.5">
|
||||||
|
<article className="size-full flex-1 flex flex-col">
|
||||||
|
<header className="px-5 py-3">
|
||||||
|
<h2 className="font-semibold text-base leading-8">
|
||||||
|
{t('knowledgeDetails.testSetting')}
|
||||||
|
</h2>
|
||||||
|
{/* <Button variant={'outline'} onClick={addCount}>
|
||||||
|
<Plus /> Add New Test
|
||||||
|
</Button> */}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="flex-1 h-0">
|
||||||
|
<TestingForm
|
||||||
|
loading={loading}
|
||||||
|
setValues={setValues}
|
||||||
|
refetch={refetch}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<div className="flex-1">
|
||||||
|
<TestingResult
|
||||||
|
data={data}
|
||||||
|
page={page}
|
||||||
|
loading={loading}
|
||||||
|
pageSize={pageSize}
|
||||||
|
filterValue={filterValue}
|
||||||
|
handleFilterSubmit={handleFilterSubmit}
|
||||||
|
onPaginationChange={onPaginationChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-[calc(100vh-241px)] overflow-auto scrollbar-thin px-1">
|
</CardContent>
|
||||||
|
) : (
|
||||||
|
<CardContent className="p-0 flex gap-2">
|
||||||
|
<div className="flex-1">
|
||||||
<TestingForm
|
<TestingForm
|
||||||
loading={loading}
|
loading={loading}
|
||||||
setValues={setValues}
|
setValues={setValues}
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
></TestingForm>
|
></TestingForm>
|
||||||
|
<TestingResult
|
||||||
|
data={data}
|
||||||
|
page={page}
|
||||||
|
loading={loading}
|
||||||
|
pageSize={pageSize}
|
||||||
|
filterValue={filterValue}
|
||||||
|
handleFilterSubmit={handleFilterSubmit}
|
||||||
|
onPaginationChange={onPaginationChange}
|
||||||
|
></TestingResult>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex-1">
|
||||||
<TestingResult
|
<TestingForm
|
||||||
data={data}
|
loading={loading}
|
||||||
page={page}
|
setValues={setValues}
|
||||||
loading={loading}
|
refetch={refetch}
|
||||||
pageSize={pageSize}
|
></TestingForm>
|
||||||
filterValue={filterValue}
|
<TestingResult
|
||||||
handleFilterSubmit={handleFilterSubmit}
|
data={data}
|
||||||
onPaginationChange={onPaginationChange}
|
page={page}
|
||||||
></TestingResult>
|
loading={loading}
|
||||||
</section>
|
pageSize={pageSize}
|
||||||
) : (
|
filterValue={filterValue}
|
||||||
<section className="flex gap-2">
|
handleFilterSubmit={handleFilterSubmit}
|
||||||
<div className="flex-1">
|
onPaginationChange={onPaginationChange}
|
||||||
<TestingForm
|
></TestingResult>
|
||||||
loading={loading}
|
</div>
|
||||||
setValues={setValues}
|
</CardContent>
|
||||||
refetch={refetch}
|
)}
|
||||||
></TestingForm>
|
</Card>
|
||||||
<TestingResult
|
|
||||||
data={data}
|
|
||||||
page={page}
|
|
||||||
loading={loading}
|
|
||||||
pageSize={pageSize}
|
|
||||||
filterValue={filterValue}
|
|
||||||
handleFilterSubmit={handleFilterSubmit}
|
|
||||||
onPaginationChange={onPaginationChange}
|
|
||||||
></TestingResult>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<TestingForm
|
|
||||||
loading={loading}
|
|
||||||
setValues={setValues}
|
|
||||||
refetch={refetch}
|
|
||||||
></TestingForm>
|
|
||||||
<TestingResult
|
|
||||||
data={data}
|
|
||||||
page={page}
|
|
||||||
loading={loading}
|
|
||||||
pageSize={pageSize}
|
|
||||||
filterValue={filterValue}
|
|
||||||
handleFilterSubmit={handleFilterSubmit}
|
|
||||||
onPaginationChange={onPaginationChange}
|
|
||||||
></TestingResult>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -90,43 +90,52 @@ export default function TestingForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
<form
|
||||||
<FormContainer className="p-10">
|
className="size-full flex flex-col"
|
||||||
<SimilaritySliderFormField
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
isTooltipShown={true}
|
>
|
||||||
></SimilaritySliderFormField>
|
<div className="px-5 h-0 flex-1">
|
||||||
<RerankFormFields></RerankFormFields>
|
<FormContainer className="p-5 h-full overflow-auto">
|
||||||
<UseKnowledgeGraphFormField name="use_kg"></UseKnowledgeGraphFormField>
|
<SimilaritySliderFormField
|
||||||
<CrossLanguageFormField
|
isTooltipShown={true}
|
||||||
name={'cross_languages'}
|
></SimilaritySliderFormField>
|
||||||
></CrossLanguageFormField>
|
<RerankFormFields></RerankFormFields>
|
||||||
<MetadataFilter prefix=""></MetadataFilter>
|
<UseKnowledgeGraphFormField name="use_kg"></UseKnowledgeGraphFormField>
|
||||||
</FormContainer>
|
<CrossLanguageFormField
|
||||||
<FormField
|
name={'cross_languages'}
|
||||||
control={form.control}
|
></CrossLanguageFormField>
|
||||||
name="question"
|
<MetadataFilter prefix=""></MetadataFilter>
|
||||||
render={({ field }) => (
|
</FormContainer>
|
||||||
<FormItem>
|
|
||||||
{/* <FormLabel>{t('knowledgeDetails.testText')}</FormLabel> */}
|
|
||||||
<FormControl>
|
|
||||||
<Textarea {...field}></Textarea>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<ButtonLoading
|
|
||||||
type="submit"
|
|
||||||
disabled={!!!trim(question)}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
{/* {!loading && <CirclePlay />} */}
|
|
||||||
{t('knowledgeDetails.testingLabel')}
|
|
||||||
<Send />
|
|
||||||
</ButtonLoading>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<footer className="flex-0 p-5">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="question"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
{/* <FormLabel>{t('knowledgeDetails.testText')}</FormLabel> */}
|
||||||
|
<FormControl>
|
||||||
|
<Textarea {...field}></Textarea>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-2.5 text-end">
|
||||||
|
<ButtonLoading
|
||||||
|
type="submit"
|
||||||
|
disabled={!!!trim(question)}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
{/* {!loading && <CirclePlay />} */}
|
||||||
|
{t('knowledgeDetails.testingLabel')}
|
||||||
|
<Send />
|
||||||
|
</ButtonLoading>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { EmptyType } from '@/components/empty/constant';
|
import { EmptyType } from '@/components/empty/constant';
|
||||||
import Empty from '@/components/empty/empty';
|
import Empty from '@/components/empty/empty';
|
||||||
import { FormContainer } from '@/components/form-container';
|
|
||||||
import { FilterButton } from '@/components/list-filter-bar';
|
import { FilterButton } from '@/components/list-filter-bar';
|
||||||
import { FilterPopover } from '@/components/list-filter-bar/filter-popover';
|
import { FilterPopover } from '@/components/list-filter-bar/filter-popover';
|
||||||
import { FilterCollection } from '@/components/list-filter-bar/interface';
|
import { FilterCollection } from '@/components/list-filter-bar/interface';
|
||||||
|
import { Card } from '@/components/ui/card';
|
||||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
import { useTestRetrieval } from '@/hooks/use-knowledge-request';
|
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 ChunkTitle = ({ item }: { item: ITestingChunk }) => {
|
||||||
const { t } = useTranslate('knowledgeDetails');
|
const { t } = useTranslate('knowledgeDetails');
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-3 text-xs text-text-sub-title-invert italic">
|
<div className="text-xs text-text-sub-title-invert italic space-x-4 rtl:space-x-reverse">
|
||||||
{similarityList.map((x) => (
|
{similarityList.map((x) => (
|
||||||
<div key={x.field} className="space-x-1">
|
<p key={x.field} className="inline">
|
||||||
<span>{((item[x.field] as number) * 100).toFixed(2)}</span>
|
{((item[x.field] as number) * 100).toFixed(2)}{' '}
|
||||||
<span>{t(camelCase(x.field))}</span>
|
<dfn>{t(camelCase(x.field))}</dfn>
|
||||||
</div>
|
</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -68,11 +68,12 @@ export function TestingResult({
|
|||||||
}, [data.doc_aggs]);
|
}, [data.doc_aggs]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 flex-1">
|
<article className="size-full flex flex-col">
|
||||||
<div className="flex justify-between pb-2.5">
|
<header className="flex-0 px-5 py-3 flex justify-between">
|
||||||
<span className="text-text-primary font-semibold text-2xl">
|
<h2 className="font-semibold text-base leading-8">
|
||||||
{t('knowledgeDetails.testResults')}
|
{t('knowledgeDetails.testResults')}
|
||||||
</span>
|
</h2>
|
||||||
|
|
||||||
<FilterPopover
|
<FilterPopover
|
||||||
filters={filters}
|
filters={filters}
|
||||||
onChange={handleFilterSubmit}
|
onChange={handleFilterSubmit}
|
||||||
@ -80,43 +81,48 @@ export function TestingResult({
|
|||||||
>
|
>
|
||||||
<FilterButton></FilterButton>
|
<FilterButton></FilterButton>
|
||||||
</FilterPopover>
|
</FilterPopover>
|
||||||
</div>
|
</header>
|
||||||
{data.chunks?.length > 0 && !loading && (
|
|
||||||
<>
|
<div className="flex-1 h-0">
|
||||||
<section className="flex flex-col gap-5 overflow-auto h-[calc(100vh-241px)] scrollbar-thin mb-5">
|
{data.chunks?.length > 0 && !loading && (
|
||||||
{data.chunks?.map((x) => (
|
<>
|
||||||
<FormContainer key={x.chunk_id} className="px-5 py-2.5">
|
<section className="px-5 pb-5 flex flex-col gap-5 overflow-auto h-full scrollbar-thin">
|
||||||
<ChunkTitle item={x}></ChunkTitle>
|
{data.chunks?.map((x) => (
|
||||||
<p className="!mt-2.5"> {x.content_with_weight}</p>
|
<article>
|
||||||
</FormContainer>
|
<Card
|
||||||
))}
|
key={x.chunk_id}
|
||||||
</section>
|
className="px-5 py-2.5 bg-transparent shadow-none"
|
||||||
<RAGFlowPagination
|
>
|
||||||
total={data.total}
|
<ChunkTitle item={x}></ChunkTitle>
|
||||||
onChange={onPaginationChange}
|
<p className="!mt-2.5"> {x.content_with_weight}</p>
|
||||||
current={page}
|
</Card>
|
||||||
pageSize={pageSize}
|
</article>
|
||||||
></RAGFlowPagination>
|
))}
|
||||||
</>
|
</section>
|
||||||
)}
|
<RAGFlowPagination
|
||||||
{!data.chunks?.length && !loading && (
|
total={data.total}
|
||||||
<div className="flex justify-center items-center w-full h-[calc(100vh-280px)]">
|
onChange={onPaginationChange}
|
||||||
<div>
|
current={page}
|
||||||
<Empty type={EmptyType.SearchData} iconWidth={80}>
|
pageSize={pageSize}
|
||||||
{data.isRuned && (
|
></RAGFlowPagination>
|
||||||
<div className="text-text-secondary">
|
</>
|
||||||
{t('knowledgeDetails.noTestResultsForRuned')}
|
)}
|
||||||
|
{!data.chunks?.length && !loading && (
|
||||||
|
<div className="size-full p-5 flex justify-center items-center">
|
||||||
|
<div>
|
||||||
|
<Empty type={EmptyType.SearchData} iconWidth={80}>
|
||||||
|
<div className="text-text-secondary text-sm">
|
||||||
|
{t(
|
||||||
|
data.isRuned
|
||||||
|
? 'knowledgeDetails.noTestResultsForRuned'
|
||||||
|
: 'knowledgeDetails.noTestResultsForNotRuned',
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</Empty>
|
||||||
{!data.isRuned && (
|
</div>
|
||||||
<div className="text-text-secondary">
|
|
||||||
{t('knowledgeDetails.noTestResultsForNotRuned')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Empty>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</article>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,10 +66,10 @@ export default function Datasets() {
|
|||||||
searchUrl.delete('isCreate');
|
searchUrl.delete('isCreate');
|
||||||
setSearchUrl(searchUrl);
|
setSearchUrl(searchUrl);
|
||||||
}
|
}
|
||||||
}, [isCreate, showModal, searchUrl, setSearchUrl]);
|
}, [isCreate, showModal, searchUrl, setSearchUrl, queryClient]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className="py-4 flex-1 flex flex-col">
|
<section className="py-4 pt-8 flex-1 flex flex-col">
|
||||||
{(!kbs?.length || kbs?.length <= 0) && !searchString && (
|
{(!kbs?.length || kbs?.length <= 0) && !searchString && (
|
||||||
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
||||||
<EmptyAppCard
|
<EmptyAppCard
|
||||||
|
|||||||
@ -42,11 +42,11 @@ export function Banner() {
|
|||||||
export function NextBanner() {
|
export function NextBanner() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<section className="text-5xl pt-10 pb-14 font-bold px-10">
|
<h1 className="text-5xl font-bold leading-normal">
|
||||||
<span className="text-text-primary">{t('header.welcome')}</span>
|
<span className="text-text-primary">{t('header.welcome')} </span>
|
||||||
<span className="pl-3 text-transparent bg-clip-text bg-gradient-to-l from-[#40EBE3] to-[#4A51FF]">
|
<span className="text-transparent bg-clip-text bg-gradient-to-l from-[#40EBE3] to-[#4A51FF]">
|
||||||
RAGFlow
|
RAGFlow
|
||||||
</span>
|
</span>
|
||||||
</section>
|
</h1>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,18 @@
|
|||||||
|
import { PageContainer } from '@/layouts/page-container';
|
||||||
import { Applications } from './applications';
|
import { Applications } from './applications';
|
||||||
import { NextBanner } from './banner';
|
import { NextBanner } from './banner';
|
||||||
import { Datasets } from './datasets';
|
import { Datasets } from './datasets';
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
return (
|
return (
|
||||||
<section>
|
<PageContainer>
|
||||||
<NextBanner></NextBanner>
|
<header className="mb-8">
|
||||||
<section className="h-[calc(100dvh-260px)] overflow-auto px-10">
|
<NextBanner />
|
||||||
<Datasets></Datasets>
|
</header>
|
||||||
<Applications></Applications>
|
|
||||||
</section>
|
<Datasets />
|
||||||
</section>
|
<Applications />
|
||||||
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -71,43 +71,24 @@ function LoginFormContent({
|
|||||||
</div>
|
</div>
|
||||||
<div className=" w-full max-w-[540px] bg-bg-component backdrop-blur-sm rounded-2xl shadow-xl pt-14 pl-10 pr-10 pb-2 border border-border-button ">
|
<div className=" w-full max-w-[540px] bg-bg-component backdrop-blur-sm rounded-2xl shadow-xl pt-14 pl-10 pr-10 pb-2 border border-border-button ">
|
||||||
{!disablePasswordLogin && (
|
{!disablePasswordLogin && (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
className="flex flex-col gap-8 text-text-primary "
|
className="flex flex-col gap-8 text-text-primary "
|
||||||
data-testid="auth-form"
|
data-testid="auth-form"
|
||||||
data-active={isActiveFace ? 'true' : undefined}
|
data-active={isActiveFace ? 'true' : undefined}
|
||||||
onSubmit={form.handleSubmit(onCheck)}
|
onSubmit={form.handleSubmit(onCheck)}
|
||||||
>
|
>
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="email"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel required>{t('emailLabel')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
data-testid="auth-email"
|
|
||||||
placeholder={t('emailPlaceholder')}
|
|
||||||
autoComplete="email"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{title === 'register' && (
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="nickname"
|
name="email"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel required>{t('nicknameLabel')}</FormLabel>
|
<FormLabel required>{t('emailLabel')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
data-testid="auth-nickname"
|
data-testid="auth-email"
|
||||||
placeholder={t('nicknamePlaceholder')}
|
placeholder={t('emailPlaceholder')}
|
||||||
autoComplete="username"
|
autoComplete="email"
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -115,73 +96,92 @@ function LoginFormContent({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
{title === 'register' && (
|
||||||
|
<FormField
|
||||||
<FormField
|
control={form.control}
|
||||||
control={form.control}
|
name="nickname"
|
||||||
name="password"
|
render={({ field }) => (
|
||||||
render={({ field }) => (
|
<FormItem>
|
||||||
<FormItem>
|
<FormLabel required>{t('nicknameLabel')}</FormLabel>
|
||||||
<FormLabel required>{t('passwordLabel')}</FormLabel>
|
<FormControl>
|
||||||
<FormControl>
|
<Input
|
||||||
<div className="relative">
|
data-testid="auth-nickname"
|
||||||
<Input
|
placeholder={t('nicknamePlaceholder')}
|
||||||
data-testid="auth-password"
|
autoComplete="username"
|
||||||
type={'password'}
|
{...field}
|
||||||
placeholder={t('passwordPlaceholder')}
|
/>
|
||||||
autoComplete={
|
</FormControl>
|
||||||
title === 'login'
|
<FormMessage />
|
||||||
? 'current-password'
|
</FormItem>
|
||||||
: 'new-password'
|
)}
|
||||||
}
|
/>
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
)}
|
||||||
/>
|
|
||||||
|
|
||||||
{title === 'login' && (
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="remember"
|
name="password"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
|
<FormLabel required>{t('passwordLabel')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="flex gap-2">
|
<div className="relative">
|
||||||
<Checkbox
|
<Input
|
||||||
checked={field.value}
|
data-testid="auth-password"
|
||||||
onCheckedChange={(checked) => {
|
type={'password'}
|
||||||
field.onChange(checked);
|
placeholder={t('passwordPlaceholder')}
|
||||||
}}
|
autoComplete={
|
||||||
|
title === 'login'
|
||||||
|
? 'current-password'
|
||||||
|
: 'new-password'
|
||||||
|
}
|
||||||
|
{...field}
|
||||||
/>
|
/>
|
||||||
<FormLabel
|
|
||||||
className={cn(' hover:text-text-primary', {
|
|
||||||
'text-text-disabled': !field.value,
|
|
||||||
'text-text-primary': field.value,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{t('rememberMe')}
|
|
||||||
</FormLabel>
|
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
<ButtonLoading
|
{title === 'login' && (
|
||||||
data-testid="auth-submit"
|
<FormField
|
||||||
type="submit"
|
control={form.control}
|
||||||
loading={loading}
|
name="remember"
|
||||||
className="bg-metallic-gradient border-b-[#00BEB4] border-b-2 hover:bg-metallic-gradient hover:border-b-[#02bcdd] w-full my-8"
|
render={({ field }) => (
|
||||||
>
|
<FormItem>
|
||||||
{title === 'login' ? t('login') : t('continue')}
|
<FormControl>
|
||||||
</ButtonLoading>
|
<div className="flex gap-2">
|
||||||
</form>
|
<Checkbox
|
||||||
</Form>
|
checked={field.value}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
field.onChange(checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormLabel
|
||||||
|
className={cn(' hover:text-text-primary', {
|
||||||
|
'text-text-disabled': !field.value,
|
||||||
|
'text-text-primary': field.value,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{t('rememberMe')}
|
||||||
|
</FormLabel>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ButtonLoading
|
||||||
|
data-testid="auth-submit"
|
||||||
|
type="submit"
|
||||||
|
loading={loading}
|
||||||
|
className="bg-metallic-gradient border-b-[#00BEB4] border-b-2 hover:bg-metallic-gradient hover:border-b-[#02bcdd] w-full my-8"
|
||||||
|
>
|
||||||
|
{title === 'login' ? t('login') : t('continue')}
|
||||||
|
</ButtonLoading>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{title === 'login' && channels && channels.length > 0 && (
|
{title === 'login' && channels && channels.length > 0 && (
|
||||||
@ -361,7 +361,7 @@ const Login = () => {
|
|||||||
<div className=" h-[inherit] relative overflow-auto">
|
<div className=" h-[inherit] relative overflow-auto">
|
||||||
<BgSvg isPaused />
|
<BgSvg isPaused />
|
||||||
|
|
||||||
<div className="absolute top-3 flex flex-col items-center mb-12 w-full text-text-primary">
|
<div className="z-20 absolute top-3 flex flex-col items-center mb-12 w-full text-text-primary">
|
||||||
<div className="flex items-center mb-4 w-full pl-10 pt-10 ">
|
<div className="flex items-center mb-4 w-full pl-10 pt-10 ">
|
||||||
<div className="w-12 h-12 p-2 rounded-lg flex items-center justify-center mr-3">
|
<div className="w-12 h-12 p-2 rounded-lg flex items-center justify-center mr-3">
|
||||||
<img
|
<img
|
||||||
@ -399,4 +399,4 @@ const Login = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Login;
|
export default Login;
|
||||||
|
|||||||
@ -181,7 +181,7 @@ const ChatCard = forwardRef(function ChatCard(
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-x-2">
|
<div className="space-x-2">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
|
|||||||
@ -1,36 +1,18 @@
|
|||||||
import EmbedDialog from '@/components/embed-dialog';
|
|
||||||
import { useShowEmbedModal } from '@/components/embed-dialog/use-show-embed-dialog';
|
|
||||||
import { PageHeader } from '@/components/page-header';
|
|
||||||
import {
|
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbLink,
|
|
||||||
BreadcrumbList,
|
|
||||||
BreadcrumbPage,
|
|
||||||
BreadcrumbSeparator,
|
|
||||||
} from '@/components/ui/breadcrumb';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { SharedFrom } from '@/constants/chat';
|
|
||||||
import {
|
import {
|
||||||
useFetchConversationList,
|
useFetchConversationList,
|
||||||
useFetchConversationManually,
|
useFetchConversationManually,
|
||||||
useFetchDialog,
|
|
||||||
useGetChatSearchParams,
|
useGetChatSearchParams,
|
||||||
} from '@/hooks/use-chat-request';
|
} from '@/hooks/use-chat-request';
|
||||||
import { IClientConversation } from '@/interfaces/database/chat';
|
import { IClientConversation } from '@/interfaces/database/chat';
|
||||||
|
import { NextLayoutContainer } from '@/layouts/next';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Routes } from '@/routes';
|
|
||||||
import { useMount } from 'ahooks';
|
import { useMount } from 'ahooks';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import {
|
import { LucideArrowBigLeft, LucideArrowUpRight } from 'lucide-react';
|
||||||
LucideArrowBigLeft,
|
|
||||||
LucideArrowUpRight,
|
|
||||||
LucideSend,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams } from 'react-router';
|
|
||||||
import { useHandleClickConversationCard } from '../hooks/use-click-card';
|
import { useHandleClickConversationCard } from '../hooks/use-click-card';
|
||||||
import { ChatSettings } from './app-settings/chat-settings';
|
import { ChatSettings } from './app-settings/chat-settings';
|
||||||
import { MultipleChatBox } from './chat-box/next-multiple-chat-box';
|
import { MultipleChatBox } from './chat-box/next-multiple-chat-box';
|
||||||
@ -40,8 +22,6 @@ import { useAddChatBox } from './use-add-box';
|
|||||||
import { useSwitchDebugMode } from './use-switch-debug-mode';
|
import { useSwitchDebugMode } from './use-switch-debug-mode';
|
||||||
|
|
||||||
export default function Chat() {
|
export default function Chat() {
|
||||||
const { id } = useParams();
|
|
||||||
const { data } = useFetchDialog();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [currentConversation, setCurrentConversation] =
|
const [currentConversation, setCurrentConversation] =
|
||||||
useState<IClientConversation>({} as IClientConversation);
|
useState<IClientConversation>({} as IClientConversation);
|
||||||
@ -55,9 +35,6 @@ export default function Chat() {
|
|||||||
const { removeChatBox, addChatBox, chatBoxIds, hasSingleChatBox } =
|
const { removeChatBox, addChatBox, chatBoxIds, hasSingleChatBox } =
|
||||||
useAddChatBox(isDebugMode);
|
useAddChatBox(isDebugMode);
|
||||||
|
|
||||||
const { showEmbedModal, hideEmbedModal, embedVisible, beta } =
|
|
||||||
useShowEmbedModal();
|
|
||||||
|
|
||||||
const { conversationId, isNew } = useGetChatSearchParams();
|
const { conversationId, isNew } = useGetChatSearchParams();
|
||||||
|
|
||||||
const { data: dialogList } = useFetchConversationList();
|
const { data: dialogList } = useFetchConversationList();
|
||||||
@ -93,7 +70,7 @@ export default function Chat() {
|
|||||||
if (isDebugMode) {
|
if (isDebugMode) {
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className="pt-5 pb-16 h-[100vh] flex flex-col"
|
className="pt-5 pb-14 h-[100vh] flex flex-col"
|
||||||
data-testid="chat-detail-multimodel-root"
|
data-testid="chat-detail-multimodel-root"
|
||||||
>
|
>
|
||||||
<header className="px-10 pb-5">
|
<header className="px-10 pb-5">
|
||||||
@ -126,77 +103,46 @@ export default function Chat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="h-full flex flex-col" data-testid="chat-detail">
|
<NextLayoutContainer>
|
||||||
<PageHeader>
|
<section className="h-full flex flex-col" data-testid="chat-detail">
|
||||||
<Breadcrumb>
|
<article className="flex flex-1 min-h-0 pb-9">
|
||||||
<BreadcrumbList>
|
<Sessions handleConversationCardClick={handleSessionClick}></Sessions>
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink
|
|
||||||
// Not friendly for keyboard navigation
|
|
||||||
// onClick={navigateToChatList}
|
|
||||||
href={Routes.Chats}
|
|
||||||
>
|
|
||||||
{t('chat.chat')}
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbPage>{data.name}</BreadcrumbPage>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
<Button onClick={showEmbedModal} data-testid="chat-detail-embed-open">
|
|
||||||
<LucideSend />
|
|
||||||
{t('common.embedIntoSite')}
|
|
||||||
</Button>
|
|
||||||
</PageHeader>
|
|
||||||
|
|
||||||
<article className="flex flex-1 min-h-0 pb-9">
|
<Card className="flex-1 min-w-0 bg-transparent border-none shadow-none h-full">
|
||||||
<Sessions handleConversationCardClick={handleSessionClick}></Sessions>
|
<CardContent className="flex p-0 h-full">
|
||||||
|
<Card className="flex flex-col flex-1 bg-transparent min-w-0">
|
||||||
|
<CardHeader
|
||||||
|
className={cn('p-5', {
|
||||||
|
'border-b-0.5 border-border-button': hasSingleChatBox,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<CardTitle className="flex justify-between items-center text-base gap-2">
|
||||||
|
<div className="truncate">{currentConversationName}</div>
|
||||||
|
|
||||||
<Card className="flex-1 min-w-0 bg-transparent border-none shadow-none h-full">
|
<Button
|
||||||
<CardContent className="flex p-0 h-full">
|
variant="ghost"
|
||||||
<Card className="flex flex-col flex-1 bg-transparent min-w-0">
|
onClick={switchDebugMode}
|
||||||
<CardHeader
|
data-testid="chat-detail-multimodel-toggle"
|
||||||
className={cn('p-5', {
|
>
|
||||||
'border-b-0.5 border-border-button': hasSingleChatBox,
|
<LucideArrowUpRight />
|
||||||
})}
|
{t('chat.multipleModels')}
|
||||||
>
|
</Button>
|
||||||
<CardTitle className="flex justify-between items-center text-base gap-2">
|
</CardTitle>
|
||||||
<div className="truncate">{currentConversationName}</div>
|
</CardHeader>
|
||||||
<Button
|
<CardContent className="flex-1 p-0 min-h-0">
|
||||||
variant={'ghost'}
|
<SingleChatBox
|
||||||
onClick={switchDebugMode}
|
controller={controller}
|
||||||
data-testid="chat-detail-multimodel-toggle"
|
stopOutputMessage={stopOutputMessage}
|
||||||
>
|
conversation={currentConversation}
|
||||||
<LucideArrowUpRight /> {t('chat.multipleModels')}
|
/>
|
||||||
</Button>
|
</CardContent>
|
||||||
</CardTitle>
|
</Card>
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="flex-1 p-0 min-h-0">
|
|
||||||
<SingleChatBox
|
|
||||||
controller={controller}
|
|
||||||
stopOutputMessage={stopOutputMessage}
|
|
||||||
conversation={currentConversation}
|
|
||||||
/>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<ChatSettings hasSingleChatBox={hasSingleChatBox}></ChatSettings>
|
<ChatSettings hasSingleChatBox={hasSingleChatBox}></ChatSettings>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</article>
|
</article>
|
||||||
|
</section>
|
||||||
{embedVisible && (
|
</NextLayoutContainer>
|
||||||
<EmbedDialog
|
|
||||||
visible={embedVisible}
|
|
||||||
hideModal={hideEmbedModal}
|
|
||||||
token={id!}
|
|
||||||
from={SharedFrom.Chat}
|
|
||||||
beta={beta}
|
|
||||||
isAgent={false}
|
|
||||||
></EmbedDialog>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,17 @@
|
|||||||
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
|
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 { MoreButton } from '@/components/more-button';
|
||||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { SearchInput } from '@/components/ui/input';
|
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 { useSetModalState } from '@/hooks/common-hooks';
|
||||||
import {
|
import {
|
||||||
useFetchDialog,
|
useFetchDialog,
|
||||||
@ -15,11 +23,13 @@ import {
|
|||||||
LucideListChecks,
|
LucideListChecks,
|
||||||
LucidePanelLeftClose,
|
LucidePanelLeftClose,
|
||||||
LucidePlus,
|
LucidePlus,
|
||||||
|
LucideSend,
|
||||||
LucideTrash2,
|
LucideTrash2,
|
||||||
LucideUndo2,
|
LucideUndo2,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useParams } from 'react-router';
|
||||||
import { useChatUrlParams } from '../hooks/use-chat-url';
|
import { useChatUrlParams } from '../hooks/use-chat-url';
|
||||||
import { useHandleClickConversationCard } from '../hooks/use-click-card';
|
import { useHandleClickConversationCard } from '../hooks/use-click-card';
|
||||||
import { useSelectDerivedConversationList } from '../hooks/use-select-conversation-list';
|
import { useSelectDerivedConversationList } from '../hooks/use-select-conversation-list';
|
||||||
@ -132,6 +142,10 @@ export function Sessions({ handleConversationCardClick }: SessionProps) {
|
|||||||
|
|
||||||
const selectedCount = useMemo(() => selectedIds.size, [selectedIds]);
|
const selectedCount = useMemo(() => selectedIds.size, [selectedIds]);
|
||||||
|
|
||||||
|
const { id } = useParams();
|
||||||
|
const { showEmbedModal, hideEmbedModal, embedVisible, beta } =
|
||||||
|
useShowEmbedModal();
|
||||||
|
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
return (
|
return (
|
||||||
<div className="p-5">
|
<div className="p-5">
|
||||||
@ -158,7 +172,7 @@ export function Sessions({ handleConversationCardClick }: SessionProps) {
|
|||||||
role="complementary"
|
role="complementary"
|
||||||
data-testid="chat-detail-sessions"
|
data-testid="chat-detail-sessions"
|
||||||
>
|
>
|
||||||
<header className="flex items-center text-base justify-between gap-2">
|
<header className="flex items-center text-base justify-between gap-4">
|
||||||
<div className="flex gap-3 items-center min-w-0">
|
<div className="flex gap-3 items-center min-w-0">
|
||||||
<RAGFlowAvatar
|
<RAGFlowAvatar
|
||||||
avatar={data.icon}
|
avatar={data.icon}
|
||||||
@ -169,10 +183,32 @@ export function Sessions({ handleConversationCardClick }: SessionProps) {
|
|||||||
<span className="flex-1 truncate">{data.name}</span>
|
<span className="flex-1 truncate">{data.name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
onClick={showEmbedModal}
|
||||||
|
size="icon-xs"
|
||||||
|
data-testid="chat-detail-embed-open"
|
||||||
|
>
|
||||||
|
<LucideSend />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{t('common.embedIntoSite')}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<EmbedDialog
|
||||||
|
visible={embedVisible}
|
||||||
|
hideModal={hideEmbedModal}
|
||||||
|
token={id!}
|
||||||
|
from={SharedFrom.Chat}
|
||||||
|
beta={beta}
|
||||||
|
isAgent={false}
|
||||||
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="transparent"
|
variant="transparent"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
className="border-0"
|
className="border-0 ml-auto"
|
||||||
onClick={switchVisible}
|
onClick={switchVisible}
|
||||||
data-testid="chat-detail-sessions-close"
|
data-testid="chat-detail-sessions-close"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,16 +1,7 @@
|
|||||||
import { useFetchTokenListBeforeOtherStep } from '@/components/embed-dialog/use-show-embed-dialog';
|
import { useFetchTokenListBeforeOtherStep } from '@/components/embed-dialog/use-show-embed-dialog';
|
||||||
import { PageHeader } from '@/components/page-header';
|
|
||||||
import {
|
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbLink,
|
|
||||||
BreadcrumbList,
|
|
||||||
BreadcrumbPage,
|
|
||||||
BreadcrumbSeparator,
|
|
||||||
} from '@/components/ui/breadcrumb';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { SharedFrom } from '@/constants/chat';
|
import { SharedFrom } from '@/constants/chat';
|
||||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
|
||||||
import {
|
import {
|
||||||
useFetchTenantInfo,
|
useFetchTenantInfo,
|
||||||
useFetchUserInfo,
|
useFetchUserInfo,
|
||||||
@ -31,7 +22,6 @@ import { SearchSetting } from './search-setting';
|
|||||||
import SearchingPage from './searching';
|
import SearchingPage from './searching';
|
||||||
|
|
||||||
export default function SearchPage() {
|
export default function SearchPage() {
|
||||||
const { navigateToSearchList } = useNavigatePage();
|
|
||||||
const [isSearching, setIsSearching] = useState(false);
|
const [isSearching, setIsSearching] = useState(false);
|
||||||
const { data: SearchData } = useFetchSearchDetail();
|
const { data: SearchData } = useFetchSearchDetail();
|
||||||
const { beta, handleOperate } = useFetchTokenListBeforeOtherStep();
|
const { beta, handleOperate } = useFetchTokenListBeforeOtherStep();
|
||||||
@ -57,22 +47,7 @@ export default function SearchPage() {
|
|||||||
}, [isSearching]);
|
}, [isSearching]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section data-testid="search-detail">
|
<section className="size-full relative" data-testid="search-detail">
|
||||||
<PageHeader>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbList>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink onClick={navigateToSearchList}>
|
|
||||||
{t('header.search')}
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbPage>{SearchData?.name}</BreadcrumbPage>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
</PageHeader>
|
|
||||||
<div className="flex gap-3 w-full bg-bg-base">
|
<div className="flex gap-3 w-full bg-bg-base">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
{!isSearching && (
|
{!isSearching && (
|
||||||
@ -128,9 +103,8 @@ export default function SearchPage() {
|
|||||||
// ></EmbedDialog>
|
// ></EmbedDialog>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute end-5 top-4 ">
|
<div className="absolute end-5 top-4">
|
||||||
<Button
|
<Button
|
||||||
className="bg-text-primary text-bg-base border-b-accent-primary border-b-2"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleOperate().then((res) => {
|
handleOperate().then((res) => {
|
||||||
console.log(res, 'res');
|
console.log(res, 'res');
|
||||||
|
|||||||
@ -1,52 +1,15 @@
|
|||||||
import { Outlet } from 'react-router';
|
import { Outlet } from 'react-router';
|
||||||
import { SideBar } from './sidebar';
|
import { SideBar } from './sidebar';
|
||||||
|
|
||||||
import { PageHeader } from '@/components/page-header';
|
|
||||||
import {
|
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbLink,
|
|
||||||
BreadcrumbList,
|
|
||||||
BreadcrumbPage,
|
|
||||||
BreadcrumbSeparator,
|
|
||||||
} from '@/components/ui/breadcrumb';
|
|
||||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { House } from 'lucide-react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import styles from './index.module.less';
|
|
||||||
|
|
||||||
const UserSetting = () => {
|
const UserSetting = () => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const { navigateToHome } = useNavigatePage();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="flex flex-col h-full">
|
<section className="pt-8 size-full grid grid-cols-[auto_1fr] grid-rows-1">
|
||||||
<PageHeader>
|
<SideBar></SideBar>
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbList>
|
<div className={cn('pr-6 pb-6 flex flex-1 rounded-lg overflow-hidden')}>
|
||||||
<BreadcrumbItem>
|
<Outlet></Outlet>
|
||||||
<BreadcrumbLink onClick={navigateToHome}>
|
|
||||||
<House className="size-4" />
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbPage>{t('setting.profile')}</BreadcrumbPage>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
</PageHeader>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
styles.settingWrapper,
|
|
||||||
'overflow-auto flex flex-1 pt-4 pr-4 pb-4',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<SideBar></SideBar>
|
|
||||||
<div className={cn(styles.outletWrapper, 'flex flex-1 rounded-lg')}>
|
|
||||||
<Outlet></Outlet>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -165,62 +165,71 @@ const routeConfigOptions = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: Routes.Datasets,
|
path: Routes.Chat + '/:id',
|
||||||
layout: false,
|
Component: () => import('@/pages/next-chats/chat'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: Routes.Root,
|
||||||
Component: () => import('@/layouts/next'),
|
Component: () => import('@/layouts/next'),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: Routes.Datasets,
|
path: Routes.Datasets,
|
||||||
Component: () => import('@/pages/datasets'),
|
Component: () => import('@/pages/datasets'),
|
||||||
},
|
},
|
||||||
],
|
{
|
||||||
},
|
path: Routes.DatasetBase,
|
||||||
{
|
Component: () => import('@/pages/dataset'),
|
||||||
path: Routes.Chats,
|
children: [
|
||||||
layout: false,
|
{
|
||||||
Component: () => import('@/layouts/next'),
|
path: `${Routes.Dataset}/:id`,
|
||||||
children: [
|
Component: () => import('@/pages/dataset/dataset'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.DatasetBase}${Routes.DatasetTesting}/:id`,
|
||||||
|
Component: () => import('@/pages/dataset/testing'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.DatasetBase}${Routes.KnowledgeGraph}/:id`,
|
||||||
|
Component: () => import('@/pages/dataset/knowledge-graph'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.DatasetBase}${Routes.DataSetOverview}/:id`,
|
||||||
|
Component: () => import('@/pages/dataset/dataset-overview'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.DatasetBase}${Routes.DataSetSetting}/:id`,
|
||||||
|
Component: () => import('@/pages/dataset/dataset-setting'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: Routes.Chats,
|
path: Routes.Chats,
|
||||||
Component: () => import('@/pages/next-chats'),
|
Component: () => import('@/pages/next-chats'),
|
||||||
},
|
},
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: Routes.Chat + '/:id',
|
|
||||||
layout: false,
|
|
||||||
Component: () => import('@/pages/next-chats/chat'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: Routes.Searches,
|
|
||||||
layout: false,
|
|
||||||
Component: () => import('@/layouts/next'),
|
|
||||||
children: [
|
|
||||||
{
|
{
|
||||||
path: Routes.Searches,
|
path: Routes.Searches,
|
||||||
Component: () => import('@/pages/next-searches'),
|
Component: () => import('@/pages/next-searches'),
|
||||||
},
|
},
|
||||||
],
|
{
|
||||||
},
|
path: `${Routes.Search}/:id`,
|
||||||
{
|
layout: false,
|
||||||
path: Routes.Memories,
|
Component: () => import('@/pages/next-search'),
|
||||||
layout: false,
|
},
|
||||||
Component: () => import('@/layouts/next'),
|
{
|
||||||
children: [
|
path: Routes.Agents,
|
||||||
|
Component: () => import('@/pages/agents'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: Routes.AgentTemplates,
|
||||||
|
layout: false,
|
||||||
|
Component: () => import('@/pages/agents/agent-templates'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: Routes.Memories,
|
path: Routes.Memories,
|
||||||
Component: () => import('@/pages/memories'),
|
Component: () => import('@/pages/memories'),
|
||||||
},
|
},
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${Routes.Memory}`,
|
|
||||||
layout: false,
|
|
||||||
Component: () => import('@/layouts/next'),
|
|
||||||
children: [
|
|
||||||
{
|
{
|
||||||
path: `${Routes.Memory}`,
|
path: `${Routes.Memory}`,
|
||||||
layout: false,
|
|
||||||
Component: () => import('@/pages/memory'),
|
Component: () => import('@/pages/memory'),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@ -233,181 +242,105 @@ const routeConfigOptions = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${Routes.Search}/:id`,
|
|
||||||
layout: false,
|
|
||||||
Component: () => import('@/pages/next-search'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${Routes.SearchShare}`,
|
|
||||||
layout: false,
|
|
||||||
Component: () => import('@/pages/next-search/share'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: Routes.Agents,
|
|
||||||
layout: false,
|
|
||||||
Component: () => import('@/layouts/next'),
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: Routes.Agents,
|
|
||||||
Component: () => import('@/pages/agents'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${Routes.AgentLogPage}/:id`,
|
|
||||||
layout: false,
|
|
||||||
Component: () => import('@/pages/agents/agent-log-page'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${Routes.Agent}/:id`,
|
|
||||||
layout: false,
|
|
||||||
Component: () => import('@/pages/agent'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: Routes.AgentExplore,
|
|
||||||
layout: false,
|
|
||||||
Component: () => import('@/pages/agent/explore'),
|
|
||||||
errorElement: <FallbackComponent />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: Routes.AgentTemplates,
|
|
||||||
layout: false,
|
|
||||||
Component: () => import('@/pages/agents/agent-templates'),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
path: Routes.Files,
|
|
||||||
layout: false,
|
|
||||||
Component: () => import('@/layouts/next'),
|
|
||||||
children: [
|
|
||||||
{
|
{
|
||||||
path: Routes.Files,
|
path: Routes.Files,
|
||||||
Component: () => import('@/pages/files'),
|
Component: () => import('@/pages/files'),
|
||||||
},
|
},
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: Routes.DatasetBase,
|
|
||||||
layout: false,
|
|
||||||
Component: () => import('@/layouts/next'),
|
|
||||||
children: [
|
|
||||||
{
|
{
|
||||||
path: Routes.DatasetBase,
|
path: Routes.UserSetting,
|
||||||
element: <Navigate to={Routes.Dataset} replace />,
|
Component: () => import('@/pages/user-setting'),
|
||||||
},
|
layout: false,
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: Routes.DatasetBase,
|
|
||||||
layout: false,
|
|
||||||
Component: () => import('@/pages/dataset'),
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: `${Routes.Dataset}/:id`,
|
|
||||||
Component: () => import('@/pages/dataset/dataset'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${Routes.DatasetBase}${Routes.DatasetTesting}/:id`,
|
|
||||||
Component: () => import('@/pages/dataset/testing'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${Routes.DatasetBase}${Routes.KnowledgeGraph}/:id`,
|
|
||||||
Component: () => import('@/pages/dataset/knowledge-graph'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${Routes.DatasetBase}${Routes.DataSetOverview}/:id`,
|
|
||||||
Component: () => import('@/pages/dataset/dataset-overview'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${Routes.DatasetBase}${Routes.DataSetSetting}/:id`,
|
|
||||||
Component: () => import('@/pages/dataset/dataset-setting'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${Routes.DataflowResult}`,
|
|
||||||
layout: false,
|
|
||||||
Component: () => import('@/pages/dataflow-result'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${Routes.ParsedResult}/chunks`,
|
|
||||||
layout: false,
|
|
||||||
Component: () =>
|
|
||||||
import('@/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: Routes.Chunk,
|
|
||||||
layout: false,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: Routes.Chunk,
|
|
||||||
Component: () => import('@/pages/chunk'),
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: `${Routes.ChunkResult}/:id`,
|
path: Routes.UserSetting,
|
||||||
Component: () => import('@/pages/chunk/chunk-result'),
|
element: (
|
||||||
|
<Navigate to={`/user-setting${Routes.DataSource}`} replace />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${Routes.ResultView}/:id`,
|
path: `${Routes.UserSetting}/profile`,
|
||||||
Component: () => import('@/pages/chunk/result-view'),
|
Component: () => import('@/pages/user-setting/profile'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.UserSetting}/locale`,
|
||||||
|
Component: () => import('@/pages/user-setting/setting-locale'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.UserSetting}/model`,
|
||||||
|
Component: () => import('@/pages/user-setting/setting-model'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.UserSetting}/team`,
|
||||||
|
Component: () => import('@/pages/user-setting/setting-team'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.UserSetting}${Routes.Api}`,
|
||||||
|
Component: () => import('@/pages/user-setting/setting-api'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.UserSetting}${Routes.Mcp}`,
|
||||||
|
Component: () => import('@/pages/user-setting/mcp'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.UserSetting}${Routes.DataSource}${Routes.DataSourceDetailPage}`,
|
||||||
|
Component: () =>
|
||||||
|
import('@/pages/user-setting/data-source/data-source-detail-page'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.UserSetting}${Routes.DataSource}`,
|
||||||
|
Component: () => import('@/pages/user-setting/data-source'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: Routes.Chunk,
|
path: `${Routes.SearchShare}`,
|
||||||
layout: false,
|
Component: () => import('@/pages/next-search/share'),
|
||||||
Component: () => import('@/pages/chunk'),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/user-setting',
|
path: Routes.Agent,
|
||||||
Component: () => import('@/pages/user-setting'),
|
|
||||||
layout: false,
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/user-setting',
|
path: `${Routes.Agent}/:id`,
|
||||||
element: <Navigate to={`/user-setting${Routes.DataSource}`} replace />,
|
Component: () => import('@/pages/agent'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/user-setting/profile',
|
path: Routes.AgentExplore,
|
||||||
Component: () => import('@/pages/user-setting/profile'),
|
Component: () => import('@/pages/agent/explore'),
|
||||||
},
|
errorElement: <FallbackComponent />,
|
||||||
{
|
|
||||||
path: '/user-setting/locale',
|
|
||||||
Component: () => import('@/pages/user-setting/setting-locale'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/user-setting/model',
|
|
||||||
Component: () => import('@/pages/user-setting/setting-model'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/user-setting/team',
|
|
||||||
Component: () => import('@/pages/user-setting/setting-team'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `/user-setting${Routes.Api}`,
|
|
||||||
Component: () => import('@/pages/user-setting/setting-api'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `/user-setting${Routes.Mcp}`,
|
|
||||||
Component: () => import('@/pages/user-setting/mcp'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `/user-setting${Routes.DataSource}`,
|
|
||||||
Component: () => import('@/pages/user-setting/data-source'),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/user-setting${Routes.DataSource}${Routes.DataSourceDetailPage}`,
|
path: `${Routes.AgentLogPage}/:id`,
|
||||||
Component: () =>
|
Component: () => import('@/pages/agents/agent-log-page'),
|
||||||
import('@/pages/user-setting/data-source/data-source-detail-page'),
|
},
|
||||||
|
{
|
||||||
layout: false,
|
path: `${Routes.DataflowResult}`,
|
||||||
|
Component: () => import('@/pages/dataflow-result'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: Routes.Chunk,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: `${Routes.Chunk}`,
|
||||||
|
Component: () => import('@/pages/chunk'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.ParsedResult}/chunks`,
|
||||||
|
Component: () =>
|
||||||
|
import('@/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.ChunkResult}/:id`,
|
||||||
|
Component: () => import('@/pages/chunk/chunk-result'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.ResultView}/:id`,
|
||||||
|
Component: () => import('@/pages/chunk/result-view'),
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: Routes.Admin,
|
path: Routes.Admin,
|
||||||
@ -420,7 +353,6 @@ const routeConfigOptions = [
|
|||||||
{
|
{
|
||||||
path: Routes.Admin,
|
path: Routes.Admin,
|
||||||
Component: () => import('@/pages/admin/layouts/authorized-layout'),
|
Component: () => import('@/pages/admin/layouts/authorized-layout'),
|
||||||
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: `${Routes.AdminUserManagement}/:id`,
|
path: `${Routes.AdminUserManagement}/:id`,
|
||||||
@ -428,7 +360,6 @@ const routeConfigOptions = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: () => import('@/pages/admin/layouts/navigation-layout'),
|
Component: () => import('@/pages/admin/layouts/navigation-layout'),
|
||||||
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: Routes.AdminServices,
|
path: Routes.AdminServices,
|
||||||
|
|||||||
5
web/src/utils/css-support.ts
Normal file
5
web/src/utils/css-support.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const supportsCssAnchor =
|
||||||
|
CSS.supports('position-anchor', '--anchor-name') &&
|
||||||
|
CSS.supports('anchor-name', '--anchor-name') &&
|
||||||
|
CSS.supports('top', 'anchor(--anchor-name bottom)') &&
|
||||||
|
CSS.supports('width', 'anchor-size(--anchor-name width)');
|
||||||
@ -191,9 +191,18 @@ module.exports = {
|
|||||||
'linear-gradient(104deg, rgb(var(--text-primary)) 30%, var(--metallic) 50%, rgb(var(--text-primary)) 70%)',
|
'linear-gradient(104deg, rgb(var(--text-primary)) 30%, var(--metallic) 50%, rgb(var(--text-primary)) 70%)',
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: `var(--radius)`,
|
px: '1px',
|
||||||
md: `calc(var(--radius) - 2px)`,
|
|
||||||
sm: 'calc(var(--radius) - 4px)',
|
'4xl': '1rem' /* 16px */,
|
||||||
|
'3xl': '0.75rem' /* 12px */,
|
||||||
|
'2xl': '0.625rem' /* 10px */,
|
||||||
|
xl: '0.5rem' /* 8px */,
|
||||||
|
lg: '0.4375rem' /* 7px */,
|
||||||
|
DEFAULT: '0.375rem' /* 6px */,
|
||||||
|
sm: '0.3125rem' /* 5px */,
|
||||||
|
xs: '0.25rem' /* 4px */,
|
||||||
|
'2xs': '0.1875rem' /* 3px */,
|
||||||
|
'3xs': '0.125' /* 2px */,
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ['var(--font-sans)', ...fontFamily.sans],
|
sans: ['var(--font-sans)', ...fontFamily.sans],
|
||||||
@ -215,12 +224,21 @@ module.exports = {
|
|||||||
from: { transform: 'rotate(0deg)' },
|
from: { transform: 'rotate(0deg)' },
|
||||||
to: { transform: 'rotate(-360deg)' },
|
to: { transform: 'rotate(-360deg)' },
|
||||||
},
|
},
|
||||||
|
'bell-shake': {
|
||||||
|
'0%,25%': { transform: 'rotate(0)', transformOrigin: 'center 25% ' },
|
||||||
|
'3.125%': { transform: 'rotate(-12.5deg)' },
|
||||||
|
'9.375%': { transform: 'rotate(11deg)' },
|
||||||
|
'15.625%': { transform: 'rotate(-9.5deg)' },
|
||||||
|
'21.875%': { transform: 'rotate(7.5deg)' },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||||
'caret-blink': 'caret-blink 1.25s ease-out infinite',
|
'caret-blink': 'caret-blink 1.25s ease-out infinite',
|
||||||
'spin-reverse': 'spin-reverse 1s linear infinite',
|
'spin-reverse': 'spin-reverse 1s linear infinite',
|
||||||
|
'bell-shake':
|
||||||
|
'bell-shake 2s 1s cubic-bezier(0.33, 1, 0.68, 1) infinite',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -286,6 +286,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
/* Make this default */
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--border-default) var(--bg-card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
.scrollbar-auto {
|
.scrollbar-auto {
|
||||||
/* hide scrollbar */
|
/* hide scrollbar */
|
||||||
|
|||||||
Reference in New Issue
Block a user