Fix: Optimized the login page and fixed some known issues. #9869 (#10514)

### 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:
chanx
2025-10-13 15:31:36 +08:00
committed by GitHub
parent 9c53b3336a
commit 77481ab3ab
17 changed files with 347 additions and 265 deletions

View File

@ -1,10 +1,14 @@
import { useIsDarkTheme } from '@/components/theme-provider'; import { useIsDarkTheme } from '@/components/theme-provider';
import { parseColorToRGB } from '@/utils/common-util';
import React from 'react'; import React from 'react';
interface SpotlightProps { interface SpotlightProps {
className?: string; className?: string;
opcity?: number; opcity?: number;
coverage?: number; coverage?: number;
X?: string;
Y?: string;
color?: string;
} }
/** /**
* *
@ -16,9 +20,20 @@ const Spotlight: React.FC<SpotlightProps> = ({
className, className,
opcity = 0.5, opcity = 0.5,
coverage = 60, coverage = 60,
X = '50%',
Y = '190%',
color,
}) => { }) => {
const isDark = useIsDarkTheme(); 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 ( return (
<div <div
className={`absolute inset-0 opacity-80 ${className} rounded-lg`} className={`absolute inset-0 opacity-80 ${className} rounded-lg`}
@ -30,7 +45,7 @@ const Spotlight: React.FC<SpotlightProps> = ({
<div <div
className="absolute inset-0" className="absolute inset-0"
style={{ 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', pointerEvents: 'none',
}} }}
></div> ></div>

View File

@ -57,6 +57,8 @@ export default {
}, },
}, },
login: { login: {
loginTitle: 'Sign in to Your Account',
signUpTitle: 'Create an Account',
login: 'Sign in', login: 'Sign in',
signUp: 'Sign up', signUp: 'Sign up',
loginDescription: 'Were so excited to see you again!', loginDescription: 'Were so excited to see you again!',
@ -72,7 +74,8 @@ export default {
nicknamePlaceholder: 'Please input nickname', nicknamePlaceholder: 'Please input nickname',
register: 'Create an account', register: 'Create an account',
continue: 'Continue', continue: 'Continue',
title: 'Start building your smart assistants.', title: 'A leading RAG engine for LLM context',
start: "Let's get started",
description: description:
'Sign up for free to explore top RAG technology. Create knowledge bases and AIs to empower your business.', 'Sign up for free to explore top RAG technology. Create knowledge bases and AIs to empower your business.',
review: 'from 500+ reviews', review: 'from 500+ reviews',
@ -114,7 +117,7 @@ export default {
generateRaptor: generateRaptor:
'This will extract entities and relationships from all your documents in this dataset. The process may take a while to complete.', 'This will extract entities and relationships from all your documents in this dataset. The process may take a while to complete.',
generate: 'Generate', generate: 'Generate',
raptor: 'Raptor', raptor: 'RAPTOR',
processingType: 'Processing Type', processingType: 'Processing Type',
dataPipeline: 'Ingestion pipeline', dataPipeline: 'Ingestion pipeline',
operations: 'Operations', operations: 'Operations',
@ -128,7 +131,7 @@ export default {
fileName: 'File Name', fileName: 'File Name',
datasetLogs: 'Dataset', datasetLogs: 'Dataset',
fileLogs: 'File', fileLogs: 'File',
overview: 'Overview', overview: 'Logs',
success: 'Success', success: 'Success',
failed: 'Failed', failed: 'Failed',
completed: 'Completed', completed: 'Completed',
@ -270,7 +273,7 @@ export default {
reRankModelWaring: 'Re-rank model is very time consuming.', reRankModelWaring: 'Re-rank model is very time consuming.',
}, },
knowledgeConfiguration: { knowledgeConfiguration: {
tocExtraction: 'toc toggle', tocExtraction: 'TOC Enhance',
tocExtractionTip: 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.", " 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: ` deleteGenerateModalContent: `
@ -1817,9 +1820,13 @@ Important structured information may include: names, dates, locations, events, k
}, },
datasetOverview: { datasetOverview: {
downloadTip: 'Files being downloaded from data sources. ', downloadTip: 'Files being downloaded from data sources. ',
processingTip: 'Files being processed by data flows.', processingTip: 'Files being processed by data pipelines.',
totalFiles: 'Total Files', totalFiles: 'Total Files',
downloading: 'Downloading', downloading: 'Downloading',
downloadSuccessTip: 'Total successful downloads',
downloadFailedTip: 'Total failed downloads',
processingSuccessTip: 'Total successfully processed files',
processingFailedTip: 'Total failed processes',
processing: 'Processing', processing: 'Processing',
}, },
}, },

View File

@ -49,6 +49,8 @@ export default {
promptPlaceholder: '请输入或使用 / 快速插入变量。', promptPlaceholder: '请输入或使用 / 快速插入变量。',
}, },
login: { login: {
loginTitle: '登录账户',
signUpTitle: '创建账户',
login: '登录', login: '登录',
signUp: '注册', signUp: '注册',
loginDescription: '很高兴再次见到您!', loginDescription: '很高兴再次见到您!',
@ -64,7 +66,8 @@ export default {
nicknamePlaceholder: '请输入名称', nicknamePlaceholder: '请输入名称',
register: '创建账户', register: '创建账户',
continue: '继续', continue: '继续',
title: '开始构建您的智能助手', title: 'A leading RAG engine for LLM context',
start: '立即开始',
description: description:
'免费注册以探索顶级 RAG 技术。 创建知识库和人工智能来增强您的业务', '免费注册以探索顶级 RAG 技术。 创建知识库和人工智能来增强您的业务',
review: '来自 500 多条评论', review: '来自 500 多条评论',
@ -116,7 +119,7 @@ export default {
fileName: '文件名', fileName: '文件名',
datasetLogs: '数据集', datasetLogs: '数据集',
fileLogs: '文件', fileLogs: '文件',
overview: '概览', overview: '日志',
success: '成功', success: '成功',
failed: '失败', failed: '失败',
completed: '已完成', completed: '已完成',
@ -255,7 +258,7 @@ export default {
theDocumentBeingParsedCannotBeDeleted: '正在解析的文档不能被删除', theDocumentBeingParsedCannotBeDeleted: '正在解析的文档不能被删除',
}, },
knowledgeConfiguration: { knowledgeConfiguration: {
tocExtraction: '目录提取', tocExtraction: '目录增强',
tocExtractionTip: tocExtractionTip:
'对于已有的chunk生成层级结构的目录信息每个文件一个目录。在查询时激活`目录增强`后系统会用大模型去判断用户问题和哪些目录项相关从而找到相关的chunk。', '对于已有的chunk生成层级结构的目录信息每个文件一个目录。在查询时激活`目录增强`后系统会用大模型去判断用户问题和哪些目录项相关从而找到相关的chunk。',
deleteGenerateModalContent: ` deleteGenerateModalContent: `
@ -1713,6 +1716,10 @@ Tokenizer 会根据所选方式将内容存储为对应的数据结构。`,
totalFiles: '文件总数', totalFiles: '文件总数',
downloading: '正在下载', downloading: '正在下载',
processing: '正在处理', processing: '正在处理',
downloadSuccessTip: '下载成功总数',
downloadFailedTip: '下载失败总数',
processingSuccessTip: '处理成功的文件总数',
processingFailedTip: '处理失败的文件总数',
}, },
}, },
}; };

View File

@ -1,7 +1,7 @@
.documentContainer { .documentContainer {
width: 100%; width: 100%;
// height: calc(100vh - 284px); // height: calc(100vh - 284px);
height: calc(100vh - 170px); height: calc(100vh - 180px);
position: relative; position: relative;
:global(.PdfHighlighter) { :global(.PdfHighlighter) {
overflow-x: hidden; overflow-x: hidden;

View File

@ -1,7 +1,7 @@
.documentContainer { .documentContainer {
width: 100%; width: 100%;
// height: calc(100vh - 284px); // height: calc(100vh - 284px);
height: calc(100vh - 170px); height: calc(100vh - 180px);
position: relative; position: relative;
:global(.PdfHighlighter) { :global(.PdfHighlighter) {
overflow-x: hidden; overflow-x: hidden;

View File

@ -4,7 +4,6 @@ import CSVFileViewer from './csv-preview';
import { DocPreviewer } from './doc-preview'; import { DocPreviewer } from './doc-preview';
import { ExcelCsvPreviewer } from './excel-preview'; import { ExcelCsvPreviewer } from './excel-preview';
import { ImagePreviewer } from './image-preview'; import { ImagePreviewer } from './image-preview';
import styles from './index.less';
import PdfPreviewer, { IProps } from './pdf-preview'; import PdfPreviewer, { IProps } from './pdf-preview';
import { PptPreviewer } from './ppt-preview'; import { PptPreviewer } from './ppt-preview';
import { TxtPreviewer } from './txt-preview'; import { TxtPreviewer } from './txt-preview';
@ -24,7 +23,7 @@ const Preview = ({
return ( return (
<> <>
{fileType === 'pdf' && highlights && setWidthAndHeight && ( {fileType === 'pdf' && highlights && setWidthAndHeight && (
<section className={styles.documentPreview}> <section>
<PdfPreviewer <PdfPreviewer
highlights={highlights} highlights={highlights}
setWidthAndHeight={setWidthAndHeight} setWidthAndHeight={setWidthAndHeight}

View File

@ -24,6 +24,8 @@ interface StatCardProps {
interface CardFooterProcessProps { interface CardFooterProcessProps {
success: number; success: number;
failed: number; failed: number;
successTip?: string;
failedTip?: string;
} }
const StatCard: FC<StatCardProps> = ({ const StatCard: FC<StatCardProps> = ({
@ -56,7 +58,9 @@ const StatCard: FC<StatCardProps> = ({
const CardFooterProcess: FC<CardFooterProcessProps> = ({ const CardFooterProcess: FC<CardFooterProcessProps> = ({
success = 0, success = 0,
successTip,
failed = 0, failed = 0,
failedTip,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( 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 justify-between rounded-md w-1/2 p-2 bg-state-success-5">
<div className="flex items-center rounded-lg gap-1"> <div className="flex items-center rounded-lg gap-1">
<div className="w-2 h-2 rounded-full bg-state-success "></div> <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')} {t('knowledgeDetails.success')}
{successTip && (
<AntToolTip title={successTip} trigger="hover">
<CircleQuestionMark size={12} />
</AntToolTip>
)}
</div> </div>
</div> </div>
<div>{success || 0}</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 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 rounded-lg gap-1">
<div className="w-2 h-2 rounded-full bg-state-error"></div> <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')} {t('knowledgeDetails.failed')}
{failedTip && (
<AntToolTip title={failedTip} trigger="hover">
<CircleQuestionMark size={12} />
</AntToolTip>
)}
</div> </div>
</div> </div>
<div>{failed || 0}</div> <div>{failed || 0}</div>
@ -259,7 +273,9 @@ const FileLogsPage: FC = () => {
> >
<CardFooterProcess <CardFooterProcess
success={topAllData.downloads.success} success={topAllData.downloads.success}
successTip={t('datasetOverview.downloadSuccessTip')}
failed={topAllData.downloads.failed} failed={topAllData.downloads.failed}
failedTip={t('datasetOverview.downloadFailedTip')}
/> />
</StatCard> </StatCard>
<StatCard <StatCard
@ -276,7 +292,9 @@ const FileLogsPage: FC = () => {
> >
<CardFooterProcess <CardFooterProcess
success={topAllData.processing.success} success={topAllData.processing.success}
successTip={t('datasetOverview.processingSuccessTip')}
failed={topAllData.processing.failed} failed={topAllData.processing.failed}
failedTip={t('datasetOverview.processingFailedTip')}
/> />
</StatCard> </StatCard>
</div> </div>

View File

@ -144,7 +144,12 @@ export function ParseTypeItem({ line = 2 }: { line?: number }) {
> >
<FormControl> <FormControl>
<Radio.Group {...field}> <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={1}>{t('builtIn')}</Radio>
<Radio value={2}>{t('manualSetup')}</Radio> <Radio value={2}>{t('manualSetup')}</Radio>
</div> </div>

View File

@ -6,7 +6,7 @@ import { t } from 'i18next';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useParams } from 'umi'; import { useParams } from 'umi';
import { ProcessingType } from '../../dataset-overview/dataset-common'; import { ProcessingType } from '../../dataset-overview/dataset-common';
import { GenerateType } from './generate'; import { GenerateType, GenerateTypeMap } from './generate';
export const generateStatus = { export const generateStatus = {
running: 'running', running: 'running',
completed: 'completed', completed: 'completed',
@ -103,9 +103,28 @@ export const useTraceGenerate = ({ open }: { open: boolean }) => {
raptorRunloading, 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 = () => { export const useDatasetGenerate = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { id } = useParams(); const { id } = useParams();
const { handleUnbindTask } = useUnBindTask();
const { const {
data, data,
isPending: loading, isPending: loading,
@ -143,8 +162,12 @@ export const useDatasetGenerate = () => {
type: GenerateType; type: GenerateType;
}) => { }) => {
const { data } = await agentService.cancelDataflow(task_id); 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({ queryClient.invalidateQueries({
queryKey: [type], queryKey: [type],
}); });
@ -154,21 +177,3 @@ export const useDatasetGenerate = () => {
}); });
return { runGenerate: mutateAsync, pauseGenerate, data, loading }; 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 };
};

View File

@ -10,7 +10,7 @@ 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 { isEmpty } from 'lodash';
import { Banknote, DatabaseZap, FileSearch2, FolderOpen } from 'lucide-react'; import { Banknote, FileSearch2, FolderOpen, Logs } from 'lucide-react';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHandleMenuClick } from './hooks'; import { useHandleMenuClick } from './hooks';
@ -40,7 +40,7 @@ export function SideBar({ refreshCount }: PropType) {
key: Routes.DatasetTesting, key: Routes.DatasetTesting,
}, },
{ {
icon: <DatabaseZap className="size-4" />, icon: <Logs className="size-4" />,
label: t(`knowledgeDetails.overview`), label: t(`knowledgeDetails.overview`),
key: Routes.DataSetOverview, key: Routes.DataSetOverview,
}, },

View File

@ -17,7 +17,6 @@ import {
} from '@/components/ui/form'; } from '@/components/ui/form';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { FormLayout } from '@/constants/form'; import { FormLayout } from '@/constants/form';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { IModalProps } from '@/interfaces/common'; import { IModalProps } from '@/interfaces/common';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useEffect } from 'react'; import { useEffect } from 'react';
@ -103,7 +102,6 @@ export function InputForm({ onOk }: IModalProps<any>) {
form.setValue('pipeline_id', ''); form.setValue('pipeline_id', '');
} }
}, [parseType, form]); }, [parseType, form]);
const { navigateToAgents } = useNavigatePage();
return ( return (
<Form {...form}> <Form {...form}>
@ -157,7 +155,7 @@ export function DatasetCreatingDialog({
return ( return (
<Dialog open onOpenChange={hideModal}> <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> <DialogHeader>
<DialogTitle>{t('knowledgeList.createKnowledgeBase')}</DialogTitle> <DialogTitle>{t('knowledgeList.createKnowledgeBase')}</DialogTitle>
</DialogHeader> </DialogHeader>

View 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;

View File

@ -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);
}

View File

@ -25,11 +25,12 @@ import {
} from '@/components/ui/form'; } from '@/components/ui/form';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { Eye, EyeOff } from 'lucide-react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import { BgSvg } from './bg'; import { BgSvg } from './bg';
import FlipCard3D from './card';
import './index.less'; import './index.less';
import { SpotlightTopLeft, SpotlightTopRight } from './spotlight-top';
const Login = () => { const Login = () => {
const [title, setTitle] = useState('login'); const [title, setTitle] = useState('login');
@ -40,6 +41,8 @@ const Login = () => {
const { login: loginWithChannel, loading: loginWithChannelLoading } = const { login: loginWithChannel, loading: loginWithChannelLoading } =
useLoginWithChannel(); useLoginWithChannel();
const { t } = useTranslation('translation', { keyPrefix: 'login' }); const { t } = useTranslation('translation', { keyPrefix: 'login' });
const [isLoginPage, setIsLoginPage] = useState(true);
const [showPassword, setShowPassword] = useState(false);
const loading = const loading =
signLoading || signLoading ||
registerLoading || registerLoading ||
@ -60,10 +63,15 @@ const Login = () => {
}; };
const changeTitle = () => { const changeTitle = () => {
setIsLoginPage(title !== 'login');
if (title === 'login' && !registerEnabled) { if (title === 'login' && !registerEnabled) {
return; return;
} }
setTitle((title) => (title === 'login' ? 'register' : 'login'));
setTimeout(() => {
setTitle(title === 'login' ? 'register' : 'login');
}, 200);
// setTitle((title) => (title === 'login' ? 'register' : 'login'));
}; };
const FormSchema = z const FormSchema = z
@ -129,177 +137,214 @@ const Login = () => {
return ( return (
<div className="min-h-screen relative overflow-hidden"> <div className="min-h-screen relative overflow-hidden">
<BgSvg /> <BgSvg />
<Spotlight opcity={0.6} coverage={60} /> <Spotlight opcity={0.4} coverage={60} color={'rgb(128, 255, 248)'} />
<SpotlightTopLeft opcity={0.2} coverage={20} /> <Spotlight
<SpotlightTopRight opcity={0.2} coverage={20} /> 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="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-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 <img
src={'/logo.svg'} src={'/logo.svg'}
alt="logo" alt="logo"
className="size-10 mr-[12] cursor-pointer" className="size-8 mr-[12] cursor-pointer"
/> />
</div> </div>
<span className="text-xl font-bold self-end">RAGFlow</span> <div className="text-xl font-bold self-center">RAGFlow</div>
</div> </div>
<h1 className="text-2xl font-bold text-center mb-2"> <h1 className="text-2xl font-bold text-center mb-2">{t('title')}</h1>
A Leading RAG engine with Agent for superior LLM context.
</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"> <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> </div>
<div className="relative z-10 flex flex-col items-center justify-center min-h-screen px-4 sm:px-6 lg:px-8"> <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 */} {/* Logo and Header */}
{/* Login Form */} {/* Login Form */}
<div className="text-center mb-8"> <FlipCard3D isLoginPage={isLoginPage}>
<h2 className="text-xl font-semibold text-text-primary"> <div className="flex flex-col items-center justify-center w-full">
{title === 'login' <div className="text-center mb-8">
? 'Sign in to Your Account' <h2 className="text-xl font-semibold text-text-primary">
: 'Create an Account'} {title === 'login' ? t('loginTitle') : t('signUpTitle')}
</h2> </h2>
</div> </div>
<div className="w-full max-w-md bg-bg-base backdrop-blur-sm rounded-2xl shadow-xl p-8 border border-border-button"> <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 {...form}>
<form <form
className="space-y-6" className="flex flex-col gap-6 text-text-primary"
onSubmit={form.handleSubmit((data) => onCheck(data))} onSubmit={form.handleSubmit((data) => onCheck(data))}
> >
<FormField <FormField
control={form.control} control={form.control}
name="email" name="email"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel required>{t('emailLabel')}</FormLabel> <FormLabel required>{t('emailLabel')}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('emailPlaceholder')} {...field} /> <Input
</FormControl> placeholder={t('emailPlaceholder')}
<FormMessage /> autoComplete="email"
</FormItem> {...field}
)}
/>
{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);
}}
/> />
<FormLabel>{t('rememberMe')}</FormLabel> </FormControl>
</div> <FormMessage />
</FormControl> </FormItem>
<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>
)}
/>
)} )}
/>
)} <FormField
<ButtonLoading control={form.control}
type="submit" name="password"
loading={loading} render={({ field }) => (
className="bg-metallic-gradient border-b-[#00BEB4] border-b-2 hover:bg-metallic-gradient hover:border-b-[#02bcdd] w-full" <FormItem>
> <FormLabel required>{t('passwordLabel')}</FormLabel>
{title === 'login' ? t('login') : t('continue')} <FormControl>
</ButtonLoading> <div className="relative">
{title === 'login' && channels && channels.length > 0 && ( <Input
<div className="mt-3 border"> type={showPassword ? 'text' : 'password'}
{channels.map((item) => ( 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 <Button
variant={'transparent'} variant={'transparent'}
key={item.channel} onClick={changeTitle}
onClick={() => handleLoginWithChannel(item.channel)} className="text-cyan-600 hover:text-cyan-800 font-medium border-none transition-colors duration-200"
style={{ marginTop: 10 }}
> >
<div className="flex items-center"> {t('signUp')}
<SvgIcon
name={item.icon || 'sso'}
width={20}
height={20}
style={{ marginRight: 5 }}
/>
Sign in with {item.display_name}
</div>
</Button> </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>
)} )}
</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> </div>
)} </div>
{title === 'register' && ( </FlipCard3D>
<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> </div>
</div> </div>
); );

View File

@ -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>
);
};

View File

@ -1,6 +1,6 @@
export enum Routes { export enum Routes {
Root = '/', Root = '/',
Login = '/login', Login = '/login-next',
Logout = '/logout', Logout = '/logout',
Home = '/home', Home = '/home',
Datasets = '/datasets', Datasets = '/datasets',
@ -52,7 +52,7 @@ export enum Routes {
const routes = [ const routes = [
{ {
path: '/login', path: '/login',
component: '@/pages/login', component: '@/pages/login-next',
layout: false, layout: false,
}, },
{ {

View File

@ -220,7 +220,7 @@ export function parseColorToRGB(color: string): [number, number, number] {
// Handling RGB colors (e.g., rgb(255, 87, 51)) // Handling RGB colors (e.g., rgb(255, 87, 51))
if (colorStr.startsWith('rgb')) { 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) { if (rgbMatch) {
return [ return [
parseInt(rgbMatch[1]), parseInt(rgbMatch[1]),