mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Fix: Optimized the login page and fixed some known issues. #9869 - Added the FlipCard3D component to implement a 3D flip effect on the login/registration forms. - Adjusted the Spotlight component to support custom positioning and color configurations. - Updated the route to point to the new login page /login-next. - Added a cancel interface to the auto-generate function. - Fixed scroll bar issues in PDF preview. ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
@ -1,10 +1,14 @@
|
||||
import { useIsDarkTheme } from '@/components/theme-provider';
|
||||
import { parseColorToRGB } from '@/utils/common-util';
|
||||
import React from 'react';
|
||||
|
||||
interface SpotlightProps {
|
||||
className?: string;
|
||||
opcity?: number;
|
||||
coverage?: number;
|
||||
X?: string;
|
||||
Y?: string;
|
||||
color?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -16,9 +20,20 @@ const Spotlight: React.FC<SpotlightProps> = ({
|
||||
className,
|
||||
opcity = 0.5,
|
||||
coverage = 60,
|
||||
X = '50%',
|
||||
Y = '190%',
|
||||
color,
|
||||
}) => {
|
||||
const isDark = useIsDarkTheme();
|
||||
const rgb = isDark ? '255, 255, 255' : '194, 221, 243';
|
||||
let realColor: [number, number, number] | undefined = undefined;
|
||||
if (color) {
|
||||
realColor = parseColorToRGB(color);
|
||||
}
|
||||
const rgb = realColor
|
||||
? realColor.join(',')
|
||||
: isDark
|
||||
? '255, 255, 255'
|
||||
: '194, 221, 243';
|
||||
return (
|
||||
<div
|
||||
className={`absolute inset-0 opacity-80 ${className} rounded-lg`}
|
||||
@ -30,7 +45,7 @@ const Spotlight: React.FC<SpotlightProps> = ({
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background: `radial-gradient(circle at 50% 190%, rgba(${rgb},${opcity}) 0%, rgba(${rgb},0) ${coverage}%)`,
|
||||
background: `radial-gradient(circle at ${X} ${Y}, rgba(${rgb},${opcity}) 0%, rgba(${rgb},0) ${coverage}%)`,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
></div>
|
||||
|
||||
@ -57,6 +57,8 @@ export default {
|
||||
},
|
||||
},
|
||||
login: {
|
||||
loginTitle: 'Sign in to Your Account',
|
||||
signUpTitle: 'Create an Account',
|
||||
login: 'Sign in',
|
||||
signUp: 'Sign up',
|
||||
loginDescription: 'We’re so excited to see you again!',
|
||||
@ -72,7 +74,8 @@ export default {
|
||||
nicknamePlaceholder: 'Please input nickname',
|
||||
register: 'Create an account',
|
||||
continue: 'Continue',
|
||||
title: 'Start building your smart assistants.',
|
||||
title: 'A leading RAG engine for LLM context',
|
||||
start: "Let's get started",
|
||||
description:
|
||||
'Sign up for free to explore top RAG technology. Create knowledge bases and AIs to empower your business.',
|
||||
review: 'from 500+ reviews',
|
||||
@ -114,7 +117,7 @@ export default {
|
||||
generateRaptor:
|
||||
'This will extract entities and relationships from all your documents in this dataset. The process may take a while to complete.',
|
||||
generate: 'Generate',
|
||||
raptor: 'Raptor',
|
||||
raptor: 'RAPTOR',
|
||||
processingType: 'Processing Type',
|
||||
dataPipeline: 'Ingestion pipeline',
|
||||
operations: 'Operations',
|
||||
@ -128,7 +131,7 @@ export default {
|
||||
fileName: 'File Name',
|
||||
datasetLogs: 'Dataset',
|
||||
fileLogs: 'File',
|
||||
overview: 'Overview',
|
||||
overview: 'Logs',
|
||||
success: 'Success',
|
||||
failed: 'Failed',
|
||||
completed: 'Completed',
|
||||
@ -270,7 +273,7 @@ export default {
|
||||
reRankModelWaring: 'Re-rank model is very time consuming.',
|
||||
},
|
||||
knowledgeConfiguration: {
|
||||
tocExtraction: 'toc toggle',
|
||||
tocExtraction: 'TOC Enhance',
|
||||
tocExtractionTip:
|
||||
" For existing chunks, generate a hierarchical table of contents (one directory per file). During queries, when Directory Enhancement is activated, the system will use a large model to determine which directory items are relevant to the user's question, thereby identifying the relevant chunks.",
|
||||
deleteGenerateModalContent: `
|
||||
@ -1817,9 +1820,13 @@ Important structured information may include: names, dates, locations, events, k
|
||||
},
|
||||
datasetOverview: {
|
||||
downloadTip: 'Files being downloaded from data sources. ',
|
||||
processingTip: 'Files being processed by data flows.',
|
||||
processingTip: 'Files being processed by data pipelines.',
|
||||
totalFiles: 'Total Files',
|
||||
downloading: 'Downloading',
|
||||
downloadSuccessTip: 'Total successful downloads',
|
||||
downloadFailedTip: 'Total failed downloads',
|
||||
processingSuccessTip: 'Total successfully processed files',
|
||||
processingFailedTip: 'Total failed processes',
|
||||
processing: 'Processing',
|
||||
},
|
||||
},
|
||||
|
||||
@ -49,6 +49,8 @@ export default {
|
||||
promptPlaceholder: '请输入或使用 / 快速插入变量。',
|
||||
},
|
||||
login: {
|
||||
loginTitle: '登录账户',
|
||||
signUpTitle: '创建账户',
|
||||
login: '登录',
|
||||
signUp: '注册',
|
||||
loginDescription: '很高兴再次见到您!',
|
||||
@ -64,7 +66,8 @@ export default {
|
||||
nicknamePlaceholder: '请输入名称',
|
||||
register: '创建账户',
|
||||
continue: '继续',
|
||||
title: '开始构建您的智能助手',
|
||||
title: 'A leading RAG engine for LLM context',
|
||||
start: '立即开始',
|
||||
description:
|
||||
'免费注册以探索顶级 RAG 技术。 创建知识库和人工智能来增强您的业务',
|
||||
review: '来自 500 多条评论',
|
||||
@ -116,7 +119,7 @@ export default {
|
||||
fileName: '文件名',
|
||||
datasetLogs: '数据集',
|
||||
fileLogs: '文件',
|
||||
overview: '概览',
|
||||
overview: '日志',
|
||||
success: '成功',
|
||||
failed: '失败',
|
||||
completed: '已完成',
|
||||
@ -255,7 +258,7 @@ export default {
|
||||
theDocumentBeingParsedCannotBeDeleted: '正在解析的文档不能被删除',
|
||||
},
|
||||
knowledgeConfiguration: {
|
||||
tocExtraction: '目录提取',
|
||||
tocExtraction: '目录增强',
|
||||
tocExtractionTip:
|
||||
'对于已有的chunk生成层级结构的目录信息(每个文件一个目录)。在查询时,激活`目录增强`后,系统会用大模型去判断用户问题和哪些目录项相关,从而找到相关的chunk。',
|
||||
deleteGenerateModalContent: `
|
||||
@ -1713,6 +1716,10 @@ Tokenizer 会根据所选方式将内容存储为对应的数据结构。`,
|
||||
totalFiles: '文件总数',
|
||||
downloading: '正在下载',
|
||||
processing: '正在处理',
|
||||
downloadSuccessTip: '下载成功总数',
|
||||
downloadFailedTip: '下载失败总数',
|
||||
processingSuccessTip: '处理成功的文件总数',
|
||||
processingFailedTip: '处理失败的文件总数',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
.documentContainer {
|
||||
width: 100%;
|
||||
// height: calc(100vh - 284px);
|
||||
height: calc(100vh - 170px);
|
||||
height: calc(100vh - 180px);
|
||||
position: relative;
|
||||
:global(.PdfHighlighter) {
|
||||
overflow-x: hidden;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
.documentContainer {
|
||||
width: 100%;
|
||||
// height: calc(100vh - 284px);
|
||||
height: calc(100vh - 170px);
|
||||
height: calc(100vh - 180px);
|
||||
position: relative;
|
||||
:global(.PdfHighlighter) {
|
||||
overflow-x: hidden;
|
||||
|
||||
@ -4,7 +4,6 @@ import CSVFileViewer from './csv-preview';
|
||||
import { DocPreviewer } from './doc-preview';
|
||||
import { ExcelCsvPreviewer } from './excel-preview';
|
||||
import { ImagePreviewer } from './image-preview';
|
||||
import styles from './index.less';
|
||||
import PdfPreviewer, { IProps } from './pdf-preview';
|
||||
import { PptPreviewer } from './ppt-preview';
|
||||
import { TxtPreviewer } from './txt-preview';
|
||||
@ -24,7 +23,7 @@ const Preview = ({
|
||||
return (
|
||||
<>
|
||||
{fileType === 'pdf' && highlights && setWidthAndHeight && (
|
||||
<section className={styles.documentPreview}>
|
||||
<section>
|
||||
<PdfPreviewer
|
||||
highlights={highlights}
|
||||
setWidthAndHeight={setWidthAndHeight}
|
||||
|
||||
@ -24,6 +24,8 @@ interface StatCardProps {
|
||||
interface CardFooterProcessProps {
|
||||
success: number;
|
||||
failed: number;
|
||||
successTip?: string;
|
||||
failedTip?: string;
|
||||
}
|
||||
|
||||
const StatCard: FC<StatCardProps> = ({
|
||||
@ -56,7 +58,9 @@ const StatCard: FC<StatCardProps> = ({
|
||||
|
||||
const CardFooterProcess: FC<CardFooterProcessProps> = ({
|
||||
success = 0,
|
||||
successTip,
|
||||
failed = 0,
|
||||
failedTip,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
@ -65,8 +69,13 @@ const CardFooterProcess: FC<CardFooterProcessProps> = ({
|
||||
<div className="flex items-center justify-between rounded-md w-1/2 p-2 bg-state-success-5">
|
||||
<div className="flex items-center rounded-lg gap-1">
|
||||
<div className="w-2 h-2 rounded-full bg-state-success "></div>
|
||||
<div className="font-normal text-text-secondary text-xs">
|
||||
<div className="font-normal text-text-secondary text-xs flex items-center gap-1">
|
||||
{t('knowledgeDetails.success')}
|
||||
{successTip && (
|
||||
<AntToolTip title={successTip} trigger="hover">
|
||||
<CircleQuestionMark size={12} />
|
||||
</AntToolTip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>{success || 0}</div>
|
||||
@ -74,8 +83,13 @@ const CardFooterProcess: FC<CardFooterProcessProps> = ({
|
||||
<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="w-2 h-2 rounded-full bg-state-error"></div>
|
||||
<div className="font-normal text-text-secondary text-xs">
|
||||
<div className="font-normal text-text-secondary text-xs flex items-center gap-1">
|
||||
{t('knowledgeDetails.failed')}
|
||||
{failedTip && (
|
||||
<AntToolTip title={failedTip} trigger="hover">
|
||||
<CircleQuestionMark size={12} />
|
||||
</AntToolTip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>{failed || 0}</div>
|
||||
@ -259,7 +273,9 @@ const FileLogsPage: FC = () => {
|
||||
>
|
||||
<CardFooterProcess
|
||||
success={topAllData.downloads.success}
|
||||
successTip={t('datasetOverview.downloadSuccessTip')}
|
||||
failed={topAllData.downloads.failed}
|
||||
failedTip={t('datasetOverview.downloadFailedTip')}
|
||||
/>
|
||||
</StatCard>
|
||||
<StatCard
|
||||
@ -276,7 +292,9 @@ const FileLogsPage: FC = () => {
|
||||
>
|
||||
<CardFooterProcess
|
||||
success={topAllData.processing.success}
|
||||
successTip={t('datasetOverview.processingSuccessTip')}
|
||||
failed={topAllData.processing.failed}
|
||||
failedTip={t('datasetOverview.processingFailedTip')}
|
||||
/>
|
||||
</StatCard>
|
||||
</div>
|
||||
|
||||
@ -144,7 +144,12 @@ export function ParseTypeItem({ line = 2 }: { line?: number }) {
|
||||
>
|
||||
<FormControl>
|
||||
<Radio.Group {...field}>
|
||||
<div className="w-1/2 flex gap-2 justify-between text-muted-foreground">
|
||||
<div
|
||||
className={cn(
|
||||
'flex gap-2 justify-between text-muted-foreground',
|
||||
line === 1 ? 'w-1/2' : 'w-3/4',
|
||||
)}
|
||||
>
|
||||
<Radio value={1}>{t('builtIn')}</Radio>
|
||||
<Radio value={2}>{t('manualSetup')}</Radio>
|
||||
</div>
|
||||
|
||||
@ -6,7 +6,7 @@ import { t } from 'i18next';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'umi';
|
||||
import { ProcessingType } from '../../dataset-overview/dataset-common';
|
||||
import { GenerateType } from './generate';
|
||||
import { GenerateType, GenerateTypeMap } from './generate';
|
||||
export const generateStatus = {
|
||||
running: 'running',
|
||||
completed: 'completed',
|
||||
@ -103,9 +103,28 @@ export const useTraceGenerate = ({ open }: { open: boolean }) => {
|
||||
raptorRunloading,
|
||||
};
|
||||
};
|
||||
|
||||
export const useUnBindTask = () => {
|
||||
const { id } = useParams();
|
||||
const { mutateAsync: handleUnbindTask } = useMutation({
|
||||
mutationKey: [DatasetKey.pauseGenerate],
|
||||
mutationFn: async ({ type }: { type: ProcessingType }) => {
|
||||
const { data } = await deletePipelineTask({ kb_id: id as string, type });
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.operated'));
|
||||
// queryClient.invalidateQueries({
|
||||
// queryKey: [type],
|
||||
// });
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
return { handleUnbindTask };
|
||||
};
|
||||
export const useDatasetGenerate = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { id } = useParams();
|
||||
const { handleUnbindTask } = useUnBindTask();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
@ -143,8 +162,12 @@ export const useDatasetGenerate = () => {
|
||||
type: GenerateType;
|
||||
}) => {
|
||||
const { data } = await agentService.cancelDataflow(task_id);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.operated'));
|
||||
|
||||
const unbindData = await handleUnbindTask({
|
||||
type: GenerateTypeMap[type as GenerateType],
|
||||
});
|
||||
if (data.code === 0 && unbindData.code === 0) {
|
||||
// message.success(t('message.operated'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [type],
|
||||
});
|
||||
@ -154,21 +177,3 @@ export const useDatasetGenerate = () => {
|
||||
});
|
||||
return { runGenerate: mutateAsync, pauseGenerate, data, loading };
|
||||
};
|
||||
|
||||
export const useUnBindTask = () => {
|
||||
const { id } = useParams();
|
||||
const { mutateAsync: handleUnbindTask } = useMutation({
|
||||
mutationKey: [DatasetKey.pauseGenerate],
|
||||
mutationFn: async ({ type }: { type: ProcessingType }) => {
|
||||
const { data } = await deletePipelineTask({ kb_id: id as string, type });
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.operated'));
|
||||
// queryClient.invalidateQueries({
|
||||
// queryKey: [type],
|
||||
// });
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
return { handleUnbindTask };
|
||||
};
|
||||
|
||||
@ -10,7 +10,7 @@ import { cn, formatBytes } from '@/lib/utils';
|
||||
import { Routes } from '@/routes';
|
||||
import { formatPureDate } from '@/utils/date';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Banknote, DatabaseZap, FileSearch2, FolderOpen } from 'lucide-react';
|
||||
import { Banknote, FileSearch2, FolderOpen, Logs } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHandleMenuClick } from './hooks';
|
||||
@ -40,7 +40,7 @@ export function SideBar({ refreshCount }: PropType) {
|
||||
key: Routes.DatasetTesting,
|
||||
},
|
||||
{
|
||||
icon: <DatabaseZap className="size-4" />,
|
||||
icon: <Logs className="size-4" />,
|
||||
label: t(`knowledgeDetails.overview`),
|
||||
key: Routes.DataSetOverview,
|
||||
},
|
||||
|
||||
@ -17,7 +17,6 @@ import {
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { FormLayout } from '@/constants/form';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useEffect } from 'react';
|
||||
@ -103,7 +102,6 @@ export function InputForm({ onOk }: IModalProps<any>) {
|
||||
form.setValue('pipeline_id', '');
|
||||
}
|
||||
}, [parseType, form]);
|
||||
const { navigateToAgents } = useNavigatePage();
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
@ -157,7 +155,7 @@ export function DatasetCreatingDialog({
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={hideModal}>
|
||||
<DialogContent className="sm:max-w-[425px] focus-visible:!outline-none">
|
||||
<DialogContent className="sm:max-w-[425px] focus-visible:!outline-none flex flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('knowledgeList.createKnowledgeBase')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
39
web/src/pages/login-next/card.tsx
Normal file
39
web/src/pages/login-next/card.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import './index.less';
|
||||
|
||||
type IProps = {
|
||||
children: React.ReactNode;
|
||||
isLoginPage: boolean;
|
||||
};
|
||||
const FlipCard3D = (props: IProps) => {
|
||||
const { children, isLoginPage } = props;
|
||||
const [isFlipped, setIsFlipped] = useState(false);
|
||||
useEffect(() => {
|
||||
console.log('title', isLoginPage);
|
||||
if (isLoginPage) {
|
||||
setIsFlipped(false);
|
||||
} else {
|
||||
setIsFlipped(true);
|
||||
}
|
||||
}, [isLoginPage]);
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full perspective-1000">
|
||||
<div
|
||||
className={`relative w-full h-full transition-transform transform-style-3d ${isFlipped ? 'rotate-y-180' : ''}`}
|
||||
>
|
||||
{/* Front Face */}
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-blue-500 text-white rounded-xl backface-hidden">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{/* Back Face */}
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-green-500 text-white rounded-xl backface-hidden rotate-y-180">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FlipCard3D;
|
||||
@ -40,3 +40,17 @@
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
.perspective-1000 {
|
||||
perspective: 1000px;
|
||||
}
|
||||
.transform-style-3d {
|
||||
transform-style: preserve-3d;
|
||||
transition-duration: 0.4s;
|
||||
}
|
||||
.backface-hidden {
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.rotate-y-180 {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
@ -25,11 +25,12 @@ import {
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Eye, EyeOff } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { BgSvg } from './bg';
|
||||
import FlipCard3D from './card';
|
||||
import './index.less';
|
||||
import { SpotlightTopLeft, SpotlightTopRight } from './spotlight-top';
|
||||
|
||||
const Login = () => {
|
||||
const [title, setTitle] = useState('login');
|
||||
@ -40,6 +41,8 @@ const Login = () => {
|
||||
const { login: loginWithChannel, loading: loginWithChannelLoading } =
|
||||
useLoginWithChannel();
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'login' });
|
||||
const [isLoginPage, setIsLoginPage] = useState(true);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const loading =
|
||||
signLoading ||
|
||||
registerLoading ||
|
||||
@ -60,10 +63,15 @@ const Login = () => {
|
||||
};
|
||||
|
||||
const changeTitle = () => {
|
||||
setIsLoginPage(title !== 'login');
|
||||
if (title === 'login' && !registerEnabled) {
|
||||
return;
|
||||
}
|
||||
setTitle((title) => (title === 'login' ? 'register' : 'login'));
|
||||
|
||||
setTimeout(() => {
|
||||
setTitle(title === 'login' ? 'register' : 'login');
|
||||
}, 200);
|
||||
// setTitle((title) => (title === 'login' ? 'register' : 'login'));
|
||||
};
|
||||
|
||||
const FormSchema = z
|
||||
@ -129,177 +137,214 @@ const Login = () => {
|
||||
return (
|
||||
<div className="min-h-screen relative overflow-hidden">
|
||||
<BgSvg />
|
||||
<Spotlight opcity={0.6} coverage={60} />
|
||||
<SpotlightTopLeft opcity={0.2} coverage={20} />
|
||||
<SpotlightTopRight opcity={0.2} coverage={20} />
|
||||
<Spotlight opcity={0.4} coverage={60} color={'rgb(128, 255, 248)'} />
|
||||
<Spotlight
|
||||
opcity={0.3}
|
||||
coverage={12}
|
||||
X={'10%'}
|
||||
Y={'-10%'}
|
||||
color={'rgb(128, 255, 248)'}
|
||||
/>
|
||||
<Spotlight
|
||||
opcity={0.3}
|
||||
coverage={12}
|
||||
X={'90%'}
|
||||
Y={'-10%'}
|
||||
color={'rgb(128, 255, 248)'}
|
||||
/>
|
||||
|
||||
{/* <SpotlightTopRight opcity={0.7} coverage={10} /> */}
|
||||
<div className="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="w-10 h-10 rounded-lg border flex items-center justify-center mr-3">
|
||||
<div className="w-12 h-12 p-2 rounded-lg border-2 border-border flex items-center justify-center mr-3">
|
||||
<img
|
||||
src={'/logo.svg'}
|
||||
alt="logo"
|
||||
className="size-10 mr-[12] cursor-pointer"
|
||||
className="size-8 mr-[12] cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xl font-bold self-end">RAGFlow</span>
|
||||
<div className="text-xl font-bold self-center">RAGFlow</div>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-center mb-2">
|
||||
A Leading RAG engine with Agent for superior LLM context.
|
||||
</h1>
|
||||
<h1 className="text-2xl font-bold text-center mb-2">{t('title')}</h1>
|
||||
<div className="mt-4 px-6 py-1 text-sm font-medium text-cyan-600 border border-accent-primary rounded-full hover:bg-cyan-50 transition-colors duration-200 border-glow relative overflow-hidden">
|
||||
Let's get started
|
||||
{t('start')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative z-10 flex flex-col items-center justify-center min-h-screen px-4 sm:px-6 lg:px-8">
|
||||
{/* Logo and Header */}
|
||||
|
||||
{/* Login Form */}
|
||||
<div className="text-center mb-8">
|
||||
<h2 className="text-xl font-semibold text-text-primary">
|
||||
{title === 'login'
|
||||
? 'Sign in to Your Account'
|
||||
: 'Create an Account'}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="w-full max-w-md bg-bg-base backdrop-blur-sm rounded-2xl shadow-xl p-8 border border-border-button">
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-6"
|
||||
onSubmit={form.handleSubmit((data) => onCheck(data))}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>{t('emailLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('emailPlaceholder')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{title === 'register' && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="nickname"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>{t('nicknameLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('nicknamePlaceholder')}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>{t('passwordLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{title === 'login' && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="remember"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<div className="flex gap-2">
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={(checked) => {
|
||||
field.onChange(checked);
|
||||
}}
|
||||
<FlipCard3D isLoginPage={isLoginPage}>
|
||||
<div className="flex flex-col items-center justify-center w-full">
|
||||
<div className="text-center mb-8">
|
||||
<h2 className="text-xl font-semibold text-text-primary">
|
||||
{title === 'login' ? t('loginTitle') : t('signUpTitle')}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="w-full max-w-md bg-bg-base backdrop-blur-sm rounded-2xl shadow-xl pt-14 pl-8 pr-8 pb-2 border border-border-button ">
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="flex flex-col gap-6 text-text-primary"
|
||||
onSubmit={form.handleSubmit((data) => onCheck(data))}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>{t('emailLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('emailPlaceholder')}
|
||||
autoComplete="email"
|
||||
{...field}
|
||||
/>
|
||||
<FormLabel>{t('rememberMe')}</FormLabel>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{title === 'register' && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="nickname"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>{t('nicknameLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('nicknamePlaceholder')}
|
||||
autoComplete="username"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<ButtonLoading
|
||||
type="submit"
|
||||
loading={loading}
|
||||
className="bg-metallic-gradient border-b-[#00BEB4] border-b-2 hover:bg-metallic-gradient hover:border-b-[#02bcdd] w-full"
|
||||
>
|
||||
{title === 'login' ? t('login') : t('continue')}
|
||||
</ButtonLoading>
|
||||
{title === 'login' && channels && channels.length > 0 && (
|
||||
<div className="mt-3 border">
|
||||
{channels.map((item) => (
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>{t('passwordLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
autoComplete={
|
||||
title === 'login'
|
||||
? 'current-password'
|
||||
: 'new-password'
|
||||
}
|
||||
{...field}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="absolute inset-y-0 right-0 pr-3 flex items-center"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
>
|
||||
{showPassword ? (
|
||||
<EyeOff className="h-4 w-4 text-gray-500" />
|
||||
) : (
|
||||
<Eye className="h-4 w-4 text-gray-500" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{title === 'login' && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="remember"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<div className="flex gap-2">
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={(checked) => {
|
||||
field.onChange(checked);
|
||||
}}
|
||||
/>
|
||||
<FormLabel>{t('rememberMe')}</FormLabel>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<ButtonLoading
|
||||
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>
|
||||
{title === 'login' && channels && channels.length > 0 && (
|
||||
<div className="mt-3 border">
|
||||
{channels.map((item) => (
|
||||
<Button
|
||||
variant={'transparent'}
|
||||
key={item.channel}
|
||||
onClick={() => handleLoginWithChannel(item.channel)}
|
||||
style={{ marginTop: 10 }}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<SvgIcon
|
||||
name={item.icon || 'sso'}
|
||||
width={20}
|
||||
height={20}
|
||||
style={{ marginRight: 5 }}
|
||||
/>
|
||||
Sign in with {item.display_name}
|
||||
</div>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
{title === 'login' && registerEnabled && (
|
||||
<div className="mt-6 text-right">
|
||||
<p className="text-text-disabled text-sm">
|
||||
{t('signInTip')}
|
||||
<Button
|
||||
variant={'transparent'}
|
||||
key={item.channel}
|
||||
onClick={() => handleLoginWithChannel(item.channel)}
|
||||
style={{ marginTop: 10 }}
|
||||
onClick={changeTitle}
|
||||
className="text-cyan-600 hover:text-cyan-800 font-medium border-none transition-colors duration-200"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<SvgIcon
|
||||
name={item.icon || 'sso'}
|
||||
width={20}
|
||||
height={20}
|
||||
style={{ marginRight: 5 }}
|
||||
/>
|
||||
Sign in with {item.display_name}
|
||||
</div>
|
||||
{t('signUp')}
|
||||
</Button>
|
||||
))}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{title === 'register' && (
|
||||
<div className="mt-6 text-right">
|
||||
<p className="text-text-disabled text-sm">
|
||||
{t('signUpTip')}
|
||||
<Button
|
||||
variant={'transparent'}
|
||||
onClick={changeTitle}
|
||||
className="text-cyan-600 hover:text-cyan-800 font-medium border-none transition-colors duration-200"
|
||||
>
|
||||
{t('login')}
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
{title === 'login' && registerEnabled && (
|
||||
<div className="mt-6 text-right">
|
||||
<p className="text-text-disabled text-sm">
|
||||
{t('signInTip')}
|
||||
<Button
|
||||
variant={'transparent'}
|
||||
onClick={changeTitle}
|
||||
className="text-cyan-600 hover:text-cyan-800 font-medium border-none transition-colors duration-200"
|
||||
>
|
||||
{t('signUp')}
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{title === 'register' && (
|
||||
<div className="mt-6 text-right">
|
||||
<p className="text-text-disabled text-sm">
|
||||
{t('signUpTip')}
|
||||
<Button
|
||||
variant={'transparent'}
|
||||
onClick={changeTitle}
|
||||
className="text-cyan-600 hover:text-cyan-800 font-medium border-none transition-colors duration-200"
|
||||
>
|
||||
{t('login')}
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</FlipCard3D>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
import { useIsDarkTheme } from '@/components/theme-provider';
|
||||
import React from 'react';
|
||||
|
||||
interface SpotlightProps {
|
||||
className?: string;
|
||||
opcity?: number;
|
||||
coverage?: number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param opcity 0~1 default 0.5
|
||||
* @param coverage 0~100 default 60
|
||||
* @returns
|
||||
*/
|
||||
export const SpotlightTopLeft: React.FC<SpotlightProps> = ({
|
||||
className,
|
||||
opcity = 0.5,
|
||||
coverage = 60,
|
||||
}) => {
|
||||
const isDark = useIsDarkTheme();
|
||||
const rgb = isDark ? '255, 255, 255' : '194, 221, 243';
|
||||
return (
|
||||
<div
|
||||
className={`absolute inset-0 opacity-80 ${className} rounded-lg`}
|
||||
style={{
|
||||
backdropFilter: 'blur(30px)',
|
||||
zIndex: -1,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background: `radial-gradient(circle at 10% -10%, rgba(${rgb},${opcity}) 0%, rgba(${rgb},0) ${coverage}%)`,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param opcity 0~1 default 0.5
|
||||
* @param coverage 0~100 default 60
|
||||
* @returns
|
||||
*/
|
||||
export const SpotlightTopRight: React.FC<SpotlightProps> = ({
|
||||
className,
|
||||
opcity = 0.5,
|
||||
coverage = 60,
|
||||
}) => {
|
||||
const isDark = useIsDarkTheme();
|
||||
const rgb = isDark ? '255, 255, 255' : '194, 221, 243';
|
||||
return (
|
||||
<div
|
||||
className={`absolute inset-0 opacity-80 ${className} rounded-lg`}
|
||||
style={{
|
||||
backdropFilter: 'blur(30px)',
|
||||
zIndex: -1,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background: `radial-gradient(circle at 90% -10%, rgba(${rgb},${opcity}) 0%, rgba(${rgb},0) ${coverage}%)`,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
export enum Routes {
|
||||
Root = '/',
|
||||
Login = '/login',
|
||||
Login = '/login-next',
|
||||
Logout = '/logout',
|
||||
Home = '/home',
|
||||
Datasets = '/datasets',
|
||||
@ -52,7 +52,7 @@ export enum Routes {
|
||||
const routes = [
|
||||
{
|
||||
path: '/login',
|
||||
component: '@/pages/login',
|
||||
component: '@/pages/login-next',
|
||||
layout: false,
|
||||
},
|
||||
{
|
||||
|
||||
@ -220,7 +220,7 @@ export function parseColorToRGB(color: string): [number, number, number] {
|
||||
|
||||
// Handling RGB colors (e.g., rgb(255, 87, 51))
|
||||
if (colorStr.startsWith('rgb')) {
|
||||
const rgbMatch = colorStr.match(/rgb$$(\d+),\s*(\d+),\s*(\d+)$$/);
|
||||
const rgbMatch = colorStr.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
||||
if (rgbMatch) {
|
||||
return [
|
||||
parseInt(rgbMatch[1]),
|
||||
|
||||
Reference in New Issue
Block a user