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 { 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>
|
||||||
|
|||||||
@ -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: 'We’re so excited to see you again!',
|
loginDescription: 'We’re 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',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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: '处理失败的文件总数',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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 };
|
|
||||||
};
|
|
||||||
|
|||||||
@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
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';
|
} 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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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 {
|
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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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]),
|
||||||
|
|||||||
Reference in New Issue
Block a user