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 { 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>

View File

@ -57,6 +57,8 @@ export default {
},
},
login: {
loginTitle: 'Sign in to Your Account',
signUpTitle: 'Create an Account',
login: 'Sign in',
signUp: 'Sign up',
loginDescription: 'Were 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',
},
},

View File

@ -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: '处理失败的文件总数',
},
},
};

View File

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

View File

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

View File

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

View File

@ -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>

View File

@ -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>

View File

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

View File

@ -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,
},

View File

@ -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>

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';
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>
);

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 {
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,
},
{

View File

@ -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]),