mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-02 00:25:06 +08:00
Compare commits
5 Commits
534fa60b2a
...
a0d5f81098
| Author | SHA1 | Date | |
|---|---|---|---|
| a0d5f81098 | |||
| 52f26f4643 | |||
| 313e92dd9b | |||
| fee757eb41 | |||
| b5ddc7ca05 |
@ -85,13 +85,7 @@ class PubMed(ToolBase, ABC):
|
||||
self._retrieve_chunks(pubmedcnt.findall("PubmedArticle"),
|
||||
get_title=lambda child: child.find("MedlineCitation").find("Article").find("ArticleTitle").text,
|
||||
get_url=lambda child: "https://pubmed.ncbi.nlm.nih.gov/" + child.find("MedlineCitation").find("PMID").text,
|
||||
get_content=lambda child: child.find("MedlineCitation") \
|
||||
.find("Article") \
|
||||
.find("Abstract") \
|
||||
.find("AbstractText").text \
|
||||
if child.find("MedlineCitation")\
|
||||
.find("Article").find("Abstract") \
|
||||
else "No abstract available")
|
||||
get_content=lambda child: self._format_pubmed_content(child),)
|
||||
return self.output("formalized_content")
|
||||
except Exception as e:
|
||||
last_e = e
|
||||
@ -104,5 +98,50 @@ class PubMed(ToolBase, ABC):
|
||||
|
||||
assert False, self.output()
|
||||
|
||||
def _format_pubmed_content(self, child):
|
||||
"""Extract structured reference info from PubMed XML"""
|
||||
def safe_find(path):
|
||||
node = child
|
||||
for p in path.split("/"):
|
||||
if node is None:
|
||||
return None
|
||||
node = node.find(p)
|
||||
return node.text if node is not None and node.text else None
|
||||
|
||||
title = safe_find("MedlineCitation/Article/ArticleTitle") or "No title"
|
||||
abstract = safe_find("MedlineCitation/Article/Abstract/AbstractText") or "No abstract available"
|
||||
journal = safe_find("MedlineCitation/Article/Journal/Title") or "Unknown Journal"
|
||||
volume = safe_find("MedlineCitation/Article/Journal/JournalIssue/Volume") or "-"
|
||||
issue = safe_find("MedlineCitation/Article/Journal/JournalIssue/Issue") or "-"
|
||||
pages = safe_find("MedlineCitation/Article/Pagination/MedlinePgn") or "-"
|
||||
|
||||
# Authors
|
||||
authors = []
|
||||
for author in child.findall(".//AuthorList/Author"):
|
||||
lastname = safe_find("LastName") or ""
|
||||
forename = safe_find("ForeName") or ""
|
||||
fullname = f"{forename} {lastname}".strip()
|
||||
if fullname:
|
||||
authors.append(fullname)
|
||||
authors_str = ", ".join(authors) if authors else "Unknown Authors"
|
||||
|
||||
# DOI
|
||||
doi = None
|
||||
for eid in child.findall(".//ArticleId"):
|
||||
if eid.attrib.get("IdType") == "doi":
|
||||
doi = eid.text
|
||||
break
|
||||
|
||||
return (
|
||||
f"Title: {title}\n"
|
||||
f"Authors: {authors_str}\n"
|
||||
f"Journal: {journal}\n"
|
||||
f"Volume: {volume}\n"
|
||||
f"Issue: {issue}\n"
|
||||
f"Pages: {pages}\n"
|
||||
f"DOI: {doi or '-'}\n"
|
||||
f"Abstract: {abstract.strip()}"
|
||||
)
|
||||
|
||||
def thoughts(self) -> str:
|
||||
return "Looking for scholarly papers on `{}`,” prioritising reputable sources.".format(self.get_input().get("query", "-_-!"))
|
||||
|
||||
@ -16,7 +16,7 @@ import os
|
||||
import urllib.request
|
||||
import argparse
|
||||
|
||||
def get_urls(use_china_mirrors=False) -> Union[str, list[str]]:
|
||||
def get_urls(use_china_mirrors=False) -> list[Union[str, list[str]]]:
|
||||
if use_china_mirrors:
|
||||
return [
|
||||
"http://mirrors.tuna.tsinghua.edu.cn/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb",
|
||||
|
||||
@ -1188,8 +1188,36 @@ class GoogleChat(Base):
|
||||
del gen_conf[k]
|
||||
return gen_conf
|
||||
|
||||
def _get_thinking_config(self, gen_conf):
|
||||
"""Extract and create ThinkingConfig from gen_conf.
|
||||
|
||||
Default behavior for Vertex AI Generative Models: thinking_budget=0 (disabled)
|
||||
unless explicitly specified by the user. This does not apply to Claude models.
|
||||
|
||||
Users can override by setting thinking_budget in gen_conf/llm_setting:
|
||||
- 0: Disabled (default)
|
||||
- 1-24576: Manual budget
|
||||
- -1: Auto (model decides)
|
||||
"""
|
||||
# Claude models don't support ThinkingConfig
|
||||
if "claude" in self.model_name:
|
||||
gen_conf.pop("thinking_budget", None)
|
||||
return None
|
||||
|
||||
# For Vertex AI Generative Models, default to thinking disabled
|
||||
thinking_budget = gen_conf.pop("thinking_budget", 0)
|
||||
|
||||
if thinking_budget is not None:
|
||||
try:
|
||||
import vertexai.generative_models as glm # type: ignore
|
||||
return glm.ThinkingConfig(thinking_budget=thinking_budget)
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
def _chat(self, history, gen_conf={}, **kwargs):
|
||||
system = history[0]["content"] if history and history[0]["role"] == "system" else ""
|
||||
thinking_config = self._get_thinking_config(gen_conf)
|
||||
gen_conf = self._clean_conf(gen_conf)
|
||||
if "claude" in self.model_name:
|
||||
response = self.client.messages.create(
|
||||
@ -1223,7 +1251,10 @@ class GoogleChat(Base):
|
||||
}
|
||||
]
|
||||
|
||||
response = self.client.generate_content(hist, generation_config=gen_conf)
|
||||
if thinking_config:
|
||||
response = self.client.generate_content(hist, generation_config=gen_conf, thinking_config=thinking_config)
|
||||
else:
|
||||
response = self.client.generate_content(hist, generation_config=gen_conf)
|
||||
ans = response.text
|
||||
return ans, response.usage_metadata.total_token_count
|
||||
|
||||
@ -1255,6 +1286,7 @@ class GoogleChat(Base):
|
||||
response = None
|
||||
total_tokens = 0
|
||||
self.client._system_instruction = system
|
||||
thinking_config = self._get_thinking_config(gen_conf)
|
||||
if "max_tokens" in gen_conf:
|
||||
gen_conf["max_output_tokens"] = gen_conf["max_tokens"]
|
||||
del gen_conf["max_tokens"]
|
||||
@ -1272,7 +1304,10 @@ class GoogleChat(Base):
|
||||
]
|
||||
ans = ""
|
||||
try:
|
||||
response = self.client.generate_content(history, generation_config=gen_conf, stream=True)
|
||||
if thinking_config:
|
||||
response = self.client.generate_content(history, generation_config=gen_conf, thinking_config=thinking_config, stream=True)
|
||||
else:
|
||||
response = self.client.generate_content(history, generation_config=gen_conf, stream=True)
|
||||
for resp in response:
|
||||
ans = resp.text
|
||||
total_tokens += num_tokens_from_string(ans)
|
||||
|
||||
@ -1619,12 +1619,12 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
tokenizerRequired: '请先添加Tokenizer节点',
|
||||
tokenizerDescription:
|
||||
'根据所选的搜索方法,将文本转换为所需的数据结构(例如,用于嵌入搜索的向量嵌入)。',
|
||||
splitter: '分词器拆分器',
|
||||
splitter: '按字符分割',
|
||||
splitterDescription:
|
||||
'根据分词器长度将文本拆分成块,并带有可选的分隔符和重叠。',
|
||||
hierarchicalMergerDescription:
|
||||
'使用正则表达式规则按标题层次结构将文档拆分成多个部分,以实现更精细的控制。',
|
||||
hierarchicalMerger: '标题拆分器',
|
||||
hierarchicalMerger: '按标题分割',
|
||||
extractor: '提取器',
|
||||
extractorDescription:
|
||||
'使用 LLM 从文档块(例如摘要、分类等)中提取结构化见解。',
|
||||
|
||||
@ -29,9 +29,8 @@ function InnerButtonEdge({
|
||||
data,
|
||||
sourceHandleId,
|
||||
}: EdgeProps<Edge<{ isHovered: boolean }>>) {
|
||||
const deleteEdgeById = useGraphStore((state) => state.deleteEdgeById);
|
||||
const highlightedPlaceholderEdgeId = useGraphStore(
|
||||
(state) => state.highlightedPlaceholderEdgeId,
|
||||
const { deleteEdgeById, getOperatorTypeFromId } = useGraphStore(
|
||||
(state) => state,
|
||||
);
|
||||
|
||||
const [edgePath, labelX, labelY] = getBezierPath({
|
||||
@ -48,12 +47,16 @@ function InnerButtonEdge({
|
||||
: {};
|
||||
}, [selected]);
|
||||
|
||||
const isTargetPlaceholder = useMemo(() => {
|
||||
return getOperatorTypeFromId(target) === Operator.Placeholder;
|
||||
}, [getOperatorTypeFromId, target]);
|
||||
|
||||
const placeholderHighlightStyle = useMemo(() => {
|
||||
const isHighlighted = highlightedPlaceholderEdgeId === id;
|
||||
const isHighlighted = isTargetPlaceholder;
|
||||
return isHighlighted
|
||||
? { strokeWidth: 2, stroke: 'var(--accent-primary)' }
|
||||
? { strokeWidth: 2, stroke: 'rgb(var(--accent-primary))' }
|
||||
: {};
|
||||
}, [highlightedPlaceholderEdgeId, id]);
|
||||
}, [isTargetPlaceholder]);
|
||||
|
||||
const onEdgeClick = () => {
|
||||
deleteEdgeById(id);
|
||||
@ -83,9 +86,10 @@ function InnerButtonEdge({
|
||||
data?.isHovered &&
|
||||
sourceHandleId !== NodeHandleId.Tool &&
|
||||
sourceHandleId !== NodeHandleId.AgentBottom && // The connection between the agent node and the tool node does not need to display the delete button
|
||||
!target.startsWith(Operator.Tool)
|
||||
!target.startsWith(Operator.Tool) &&
|
||||
!isTargetPlaceholder
|
||||
);
|
||||
}, [data?.isHovered, sourceHandleId, target]);
|
||||
}, [data?.isHovered, isTargetPlaceholder, sourceHandleId, target]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { Connection, Position } from '@xyflow/react';
|
||||
import { Connection, OnConnectEnd, Position } from '@xyflow/react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useDropdownManager } from '../canvas/context';
|
||||
import { Operator, PREVENT_CLOSE_DELAY } from '../constant';
|
||||
import useGraphStore from '../store';
|
||||
import { useAddNode } from './use-add-node';
|
||||
|
||||
interface ConnectionStartParams {
|
||||
@ -40,7 +39,6 @@ export const useConnectionDrag = (
|
||||
|
||||
const { addCanvasNode } = useAddNode(reactFlowInstance);
|
||||
const { setActiveDropdown } = useDropdownManager();
|
||||
const { setHighlightedPlaceholderEdgeId } = useGraphStore();
|
||||
|
||||
/**
|
||||
* Connection start handler function
|
||||
@ -66,8 +64,8 @@ export const useConnectionDrag = (
|
||||
/**
|
||||
* Connection end handler function
|
||||
*/
|
||||
const onConnectEnd = useCallback(
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
const onConnectEnd: OnConnectEnd = useCallback(
|
||||
(event) => {
|
||||
if ('clientX' in event && 'clientY' in event) {
|
||||
const { clientX, clientY } = event;
|
||||
setDropdownPosition({ x: clientX, y: clientY });
|
||||
@ -113,11 +111,6 @@ export const useConnectionDrag = (
|
||||
|
||||
if (newNodeId) {
|
||||
setCreatedPlaceholderRef(newNodeId);
|
||||
|
||||
if (connectionStartRef.current) {
|
||||
const edgeId = `xy-edge__${connectionStartRef.current.nodeId}${connectionStartRef.current.handleId}-${newNodeId}end`;
|
||||
setHighlightedPlaceholderEdgeId(edgeId);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate placeholder node position and display dropdown menu
|
||||
@ -154,7 +147,6 @@ export const useConnectionDrag = (
|
||||
calculateDropdownPosition,
|
||||
setActiveDropdown,
|
||||
showModal,
|
||||
setHighlightedPlaceholderEdgeId,
|
||||
checkAndRemoveExistingPlaceholder,
|
||||
removePlaceholderNode,
|
||||
hideModal,
|
||||
@ -206,13 +198,7 @@ export const useConnectionDrag = (
|
||||
removePlaceholderNode();
|
||||
hideModal();
|
||||
clearActiveDropdown();
|
||||
setHighlightedPlaceholderEdgeId(null);
|
||||
}, [
|
||||
removePlaceholderNode,
|
||||
hideModal,
|
||||
clearActiveDropdown,
|
||||
setHighlightedPlaceholderEdgeId,
|
||||
]);
|
||||
}, [removePlaceholderNode, hideModal, clearActiveDropdown]);
|
||||
|
||||
return {
|
||||
onConnectStart,
|
||||
|
||||
@ -42,9 +42,6 @@ export const usePlaceholderManager = (reactFlowInstance: any) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Clear highlighted placeholder edge
|
||||
useGraphStore.getState().setHighlightedPlaceholderEdgeId(null);
|
||||
|
||||
// Update ref reference
|
||||
if (createdPlaceholderRef.current === existingPlaceholder.id) {
|
||||
createdPlaceholderRef.current = null;
|
||||
@ -62,8 +59,7 @@ export const usePlaceholderManager = (reactFlowInstance: any) => {
|
||||
reactFlowInstance &&
|
||||
!userSelectedNodeRef.current
|
||||
) {
|
||||
const { nodes, edges, setHighlightedPlaceholderEdgeId } =
|
||||
useGraphStore.getState();
|
||||
const { nodes, edges } = useGraphStore.getState();
|
||||
|
||||
// Remove edges related to placeholder
|
||||
const edgesToRemove = edges.filter(
|
||||
@ -84,8 +80,6 @@ export const usePlaceholderManager = (reactFlowInstance: any) => {
|
||||
});
|
||||
}
|
||||
|
||||
setHighlightedPlaceholderEdgeId(null);
|
||||
|
||||
createdPlaceholderRef.current = null;
|
||||
}
|
||||
|
||||
@ -101,13 +95,7 @@ export const usePlaceholderManager = (reactFlowInstance: any) => {
|
||||
(newNodeId: string) => {
|
||||
// First establish connection between new node and source, then delete placeholder
|
||||
if (createdPlaceholderRef.current && reactFlowInstance) {
|
||||
const {
|
||||
nodes,
|
||||
edges,
|
||||
addEdge,
|
||||
updateNode,
|
||||
setHighlightedPlaceholderEdgeId,
|
||||
} = useGraphStore.getState();
|
||||
const { nodes, edges, addEdge, updateNode } = useGraphStore.getState();
|
||||
|
||||
// Find placeholder node to get its position
|
||||
const placeholderNode = nodes.find(
|
||||
@ -157,8 +145,6 @@ export const usePlaceholderManager = (reactFlowInstance: any) => {
|
||||
edges: edgesToRemove,
|
||||
});
|
||||
}
|
||||
|
||||
setHighlightedPlaceholderEdgeId(null);
|
||||
}
|
||||
|
||||
// Mark that user has selected a node
|
||||
|
||||
@ -39,7 +39,6 @@ export type RFState = {
|
||||
selectedEdgeIds: string[];
|
||||
clickedNodeId: string; // currently selected node
|
||||
clickedToolId: string; // currently selected tool id
|
||||
highlightedPlaceholderEdgeId: string | null;
|
||||
onNodesChange: OnNodesChange<RAGFlowNodeType>;
|
||||
onEdgesChange: OnEdgesChange;
|
||||
onEdgeMouseEnter?: EdgeMouseHandler<Edge>;
|
||||
@ -90,7 +89,6 @@ export type RFState = {
|
||||
) => void; // Deleting a condition of a classification operator will delete the related edge
|
||||
findAgentToolNodeById: (id: string | null) => string | undefined;
|
||||
selectNodeIds: (nodeIds: string[]) => void;
|
||||
setHighlightedPlaceholderEdgeId: (edgeId: string | null) => void;
|
||||
};
|
||||
|
||||
// this is our useStore hook that we can use in our components to get parts of the store and call actions
|
||||
@ -103,7 +101,6 @@ const useGraphStore = create<RFState>()(
|
||||
selectedEdgeIds: [] as string[],
|
||||
clickedNodeId: '',
|
||||
clickedToolId: '',
|
||||
highlightedPlaceholderEdgeId: null,
|
||||
onNodesChange: (changes) => {
|
||||
set({
|
||||
nodes: applyNodeChanges(changes, get().nodes),
|
||||
@ -530,9 +527,6 @@ const useGraphStore = create<RFState>()(
|
||||
})),
|
||||
);
|
||||
},
|
||||
setHighlightedPlaceholderEdgeId: (edgeId) => {
|
||||
set({ highlightedPlaceholderEdgeId: edgeId });
|
||||
},
|
||||
})),
|
||||
{ name: 'graph', trace: true },
|
||||
),
|
||||
|
||||
159
web/src/pages/login-next/bg.tsx
Normal file
159
web/src/pages/login-next/bg.tsx
Normal file
@ -0,0 +1,159 @@
|
||||
import './index.less';
|
||||
const aspectRatio = {
|
||||
top: 240,
|
||||
middle: 466,
|
||||
bottom: 704,
|
||||
};
|
||||
|
||||
export const BgSvg = () => {
|
||||
const def = (
|
||||
path: string,
|
||||
id: number | string = '',
|
||||
type: keyof typeof aspectRatio,
|
||||
) => {
|
||||
return (
|
||||
<svg
|
||||
className="w-full h-full"
|
||||
// style={{ aspectRatio: `1440/${aspectRatio[type]}` }}
|
||||
// preserveAspectRatio="xMinYMid meet"
|
||||
preserveAspectRatio="none"
|
||||
// viewBox={`${getPathBounds(path).minX} 0 ${
|
||||
// getPathBounds(path).width
|
||||
// } ${height}`}
|
||||
viewBox={`0 0 1440 ${aspectRatio[type]}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id={`glow${id}`} x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stopColor="#80FFF8" stopOpacity="0" />
|
||||
<stop offset="50%" stopColor="#80FFF8" stopOpacity="1" />
|
||||
<stop offset="100%" stopColor="#80FFF8" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="strokeWidthGradient"
|
||||
x1="0%"
|
||||
y1="0%"
|
||||
x2="100%"
|
||||
y2="0%"
|
||||
>
|
||||
<stop offset="0%" stopColor="#000" />
|
||||
<stop offset="10%" stopColor="#fff" />
|
||||
<stop offset="50%" stopColor="#fff" />
|
||||
<stop offset="90%" stopColor="#fff" />
|
||||
<stop offset="100%" stopColor="#000" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient
|
||||
id={`highlight${id}`}
|
||||
x1="0%"
|
||||
y1="0%"
|
||||
x2="100%"
|
||||
y2="0%"
|
||||
>
|
||||
<stop offset="45%" stopColor="#FFF" stopOpacity="0.2" />
|
||||
<stop offset="48%" stopColor="#FFD700" stopOpacity="0.3" />
|
||||
</linearGradient>
|
||||
|
||||
<filter
|
||||
id={`glowFilter${id}`}
|
||||
x="-10%"
|
||||
y="-10%"
|
||||
width="120%"
|
||||
height="120%"
|
||||
>
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="5.2" />
|
||||
{/* <feBlend
|
||||
in="blur"
|
||||
in2="SourceGraphic"
|
||||
mode="screen"
|
||||
result="glow"
|
||||
/> */}
|
||||
</filter>
|
||||
<filter
|
||||
id={`highlightFilter${id}`}
|
||||
x="-5%"
|
||||
y="-5%"
|
||||
width="110%"
|
||||
height="110%"
|
||||
>
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="5.5" />
|
||||
</filter>
|
||||
<mask id={`glowMask${id}`}>
|
||||
<rect width="100%" height="100%" fill="transparent" />
|
||||
<path
|
||||
d={path}
|
||||
fill="none"
|
||||
stroke="url(#strokeWidthGradient)"
|
||||
strokeWidth="1"
|
||||
strokeDasharray="50,600"
|
||||
strokeDashoffset="0"
|
||||
filter={`url(#glowFilter${id})`}
|
||||
className="animate-glow mask-path"
|
||||
/>
|
||||
<path
|
||||
d={path}
|
||||
fill="none"
|
||||
stroke={`url(#highlight${id})`}
|
||||
strokeWidth="0.5"
|
||||
strokeDasharray="50,600"
|
||||
strokeDashoffset="16"
|
||||
filter={`url(#highlightFilter${id})`}
|
||||
className="animate-highlight mask-path"
|
||||
/>
|
||||
</mask>
|
||||
</defs>
|
||||
<path
|
||||
d={path}
|
||||
stroke="#00BEB4"
|
||||
strokeWidth="1"
|
||||
fill="none"
|
||||
opacity="0.1"
|
||||
/>
|
||||
<path
|
||||
d={path}
|
||||
stroke={`url(#glow${id})`}
|
||||
strokeWidth="2"
|
||||
fill="none"
|
||||
opacity="0.8"
|
||||
mask={`url(#glowMask${id})`}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none ">
|
||||
<div className="absolute top-0 left-0 right-0 w-full">
|
||||
<div
|
||||
className={`w-full ml-10`}
|
||||
style={{ height: aspectRatio['top'] + 'px' }}
|
||||
>
|
||||
{def(
|
||||
'M1282.81 -45L999.839 147.611C988.681 155.206 975.496 159.267 961.999 159.267H746.504H330.429C317.253 159.267 304.368 155.397 293.373 148.137L0.88623 -45',
|
||||
0,
|
||||
'top',
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`w-full -mt-40`}
|
||||
style={{ height: aspectRatio['middle'] + 'px' }}
|
||||
>
|
||||
{def(
|
||||
'M0 1L203.392 203.181C215.992 215.705 233.036 222.736 250.802 222.736H287.103C305.94 222.736 323.913 230.636 336.649 244.514L425.401 341.222C438.137 355.1 456.11 363 474.947 363H976.902C996.333 363 1014.81 354.595 1027.59 339.95L1104.79 251.424C1116.14 238.4 1132.08 230.248 1149.29 228.659L1191.13 224.796C1205.62 223.458 1219.28 217.461 1230.06 207.704L1440 17.7981',
|
||||
1,
|
||||
'middle',
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`w-full -mt-52`}
|
||||
style={{ height: aspectRatio['bottom'] + 'px' }}
|
||||
>
|
||||
{def(
|
||||
'M-10 1L57.1932 71.1509C67.7929 82.2171 74.2953 96.5714 75.6239 111.837L79.8042 159.87C81.3312 177.416 89.68 193.662 103.057 205.117L399.311 458.829C411.497 469.265 427.011 475 443.054 475H972.606C988.463 475 1003.81 469.396 1015.94 459.179L1310.78 210.75C1323.01 200.451 1331.16 186.136 1333.79 170.369L1341.87 121.837C1344.06 108.691 1350.11 96.492 1359.24 86.7885L1440 1',
|
||||
2,
|
||||
'bottom',
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1,267 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { toast } from '@/components/hooks/use-toast';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
InputOTP,
|
||||
InputOTPGroup,
|
||||
InputOTPSlot,
|
||||
} from '@/components/ui/input-otp';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
export function SignUpForm() {
|
||||
const { t } = useTranslate('login');
|
||||
|
||||
const FormSchema = z.object({
|
||||
email: z.string().email({
|
||||
message: t('emailPlaceholder'),
|
||||
}),
|
||||
nickname: z.string({ required_error: t('nicknamePlaceholder') }),
|
||||
password: z.string({ required_error: t('passwordPlaceholder') }),
|
||||
agree: z.boolean({ required_error: t('passwordPlaceholder') }),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
email: '',
|
||||
},
|
||||
});
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
console.log('🚀 ~ onSubmit ~ data:', data);
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('emailLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('emailPlaceholder')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="nickname"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('nicknameLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('nicknamePlaceholder')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('passwordLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type={'password'}
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="agree"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel>
|
||||
I understand and agree to the Terms of Service and Privacy
|
||||
Policy.
|
||||
</FormLabel>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit" className="w-full">
|
||||
{t('signUp')}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export function SignInForm() {
|
||||
const { t } = useTranslate('login');
|
||||
|
||||
const FormSchema = z.object({
|
||||
email: z.string().email({
|
||||
message: t('emailPlaceholder'),
|
||||
}),
|
||||
password: z.string({ required_error: t('passwordPlaceholder') }),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
email: '',
|
||||
},
|
||||
});
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
console.log('🚀 ~ onSubmit ~ data:', data);
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('emailLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('emailPlaceholder')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('passwordLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type={'password'}
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
{t('rememberMe')}
|
||||
</label>
|
||||
</div>
|
||||
<Button type="submit" className="w-full">
|
||||
{t('login')}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export function VerifyEmailForm() {
|
||||
const FormSchema = z.object({
|
||||
pin: z.string().min(6, {
|
||||
message: 'Your one-time password must be 6 characters.',
|
||||
}),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
pin: '',
|
||||
},
|
||||
});
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
console.log('🚀 ~ onSubmit ~ data:', data);
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="pin"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>One-Time Password</FormLabel>
|
||||
<FormControl>
|
||||
<InputOTP maxLength={6} {...field}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type="submit" className="w-full">
|
||||
Verify
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useSearchParams } from 'umi';
|
||||
|
||||
export enum Step {
|
||||
SignIn,
|
||||
SignUp,
|
||||
ForgotPassword,
|
||||
ResetPassword,
|
||||
VerifyEmail,
|
||||
}
|
||||
|
||||
export const useSwitchStep = (step: Step) => {
|
||||
const [_, setSearchParams] = useSearchParams();
|
||||
console.log('🚀 ~ useSwitchStep ~ _:', _);
|
||||
const switchStep = useCallback(() => {
|
||||
setSearchParams(new URLSearchParams({ step: step.toString() }));
|
||||
}, [setSearchParams, step]);
|
||||
|
||||
return { switchStep };
|
||||
};
|
||||
42
web/src/pages/login-next/index.less
Normal file
42
web/src/pages/login-next/index.less
Normal file
@ -0,0 +1,42 @@
|
||||
.animate-glow {
|
||||
animation: glow 16s infinite linear;
|
||||
}
|
||||
.mask-path {
|
||||
stroke-width: 8;
|
||||
::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
stroke-dasharray: 660;
|
||||
stroke-dashoffset: 0;
|
||||
stroke: #d11818;
|
||||
stroke-width: 8;
|
||||
fill: none;
|
||||
}
|
||||
}
|
||||
@keyframes glow {
|
||||
0% {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: -650;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes highlight-flow {
|
||||
0% {
|
||||
stroke-dashoffset: 50;
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: -600;
|
||||
} /* 15+300-30=285 */
|
||||
}
|
||||
|
||||
.animate-highlight {
|
||||
animation: highlight-flow 16s linear infinite;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@ -1,107 +1,305 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { DiscordLogoIcon, GitHubLogoIcon } from '@radix-ui/react-icons';
|
||||
import { useSearchParams } from 'umi';
|
||||
import { SignInForm, SignUpForm, VerifyEmailForm } from './form';
|
||||
import { Step, useSwitchStep } from './hooks';
|
||||
import SvgIcon from '@/components/svg-icon';
|
||||
import { useAuth } from '@/hooks/auth-hooks';
|
||||
import {
|
||||
useLogin,
|
||||
useLoginChannels,
|
||||
useLoginWithChannel,
|
||||
useRegister,
|
||||
} from '@/hooks/login-hooks';
|
||||
import { useSystemConfig } from '@/hooks/system-hooks';
|
||||
import { rsaPsw } from '@/utils';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'umi';
|
||||
|
||||
function LoginFooter() {
|
||||
return (
|
||||
<section className="pt-4">
|
||||
<Separator />
|
||||
<p className="text-center pt-4">or continue with</p>
|
||||
<div className="flex gap-4 justify-center pt-[20px]">
|
||||
<GitHubLogoIcon className="w-8 h-8"></GitHubLogoIcon>
|
||||
<DiscordLogoIcon className="w-8 h-8"></DiscordLogoIcon>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function SignUpCard() {
|
||||
const { t } = useTranslate('login');
|
||||
|
||||
const { switchStep } = useSwitchStep(Step.SignIn);
|
||||
|
||||
return (
|
||||
<Card className="w-[400px]">
|
||||
<CardHeader>
|
||||
<CardTitle>{t('signUp')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<SignUpForm></SignUpForm>
|
||||
<div className="text-center">
|
||||
<Button variant={'link'} className="pt-6" onClick={switchStep}>
|
||||
Already have an account? Log In
|
||||
</Button>
|
||||
</div>
|
||||
<LoginFooter></LoginFooter>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export function SignInCard() {
|
||||
const { t } = useTranslate('login');
|
||||
const { switchStep } = useSwitchStep(Step.SignUp);
|
||||
|
||||
return (
|
||||
<Card className="w-[400px]">
|
||||
<CardHeader>
|
||||
<CardTitle>{t('login')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<SignInForm></SignInForm>
|
||||
<Button
|
||||
className="w-full mt-2"
|
||||
onClick={switchStep}
|
||||
variant={'secondary'}
|
||||
>
|
||||
{t('signUp')}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export function VerifyEmailCard() {
|
||||
// const { t } = useTranslate('login');
|
||||
|
||||
return (
|
||||
<Card className="w-[400px]">
|
||||
<CardHeader>
|
||||
<CardTitle>Verify email</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<section className="flex gap-y-6 flex-col">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex-1 space-y-1">
|
||||
<p className="text-sm font-medium leading-none">
|
||||
We’ve sent a 6-digit code to
|
||||
</p>
|
||||
<p className="text-sm text-blue-500">yifanwu92@gmail.com.</p>
|
||||
</div>
|
||||
<Button>Resend</Button>
|
||||
</div>
|
||||
<VerifyEmailForm></VerifyEmailForm>
|
||||
</section>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
import Spotlight from '@/components/spotlight';
|
||||
import { Button, ButtonLoading } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { BgSvg } from './bg';
|
||||
import './index.less';
|
||||
import { SpotlightTopLeft, SpotlightTopRight } from './spotlight-top';
|
||||
|
||||
const Login = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const step = Number((searchParams.get('step') ?? Step.SignIn) as Step);
|
||||
const [title, setTitle] = useState('login');
|
||||
const navigate = useNavigate();
|
||||
const { login, loading: signLoading } = useLogin();
|
||||
const { register, loading: registerLoading } = useRegister();
|
||||
const { channels, loading: channelsLoading } = useLoginChannels();
|
||||
const { login: loginWithChannel, loading: loginWithChannelLoading } =
|
||||
useLoginWithChannel();
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'login' });
|
||||
const loading =
|
||||
signLoading ||
|
||||
registerLoading ||
|
||||
channelsLoading ||
|
||||
loginWithChannelLoading;
|
||||
const { config } = useSystemConfig();
|
||||
const registerEnabled = config?.registerEnabled !== 0;
|
||||
|
||||
const { isLogin } = useAuth();
|
||||
useEffect(() => {
|
||||
if (isLogin) {
|
||||
navigate('/');
|
||||
}
|
||||
}, [isLogin, navigate]);
|
||||
|
||||
const handleLoginWithChannel = async (channel: string) => {
|
||||
await loginWithChannel(channel);
|
||||
};
|
||||
|
||||
const changeTitle = () => {
|
||||
if (title === 'login' && !registerEnabled) {
|
||||
return;
|
||||
}
|
||||
setTitle((title) => (title === 'login' ? 'register' : 'login'));
|
||||
};
|
||||
|
||||
const FormSchema = z
|
||||
.object({
|
||||
nickname: z.string().optional(),
|
||||
email: z
|
||||
.string()
|
||||
.email()
|
||||
.min(1, { message: t('emailPlaceholder') }),
|
||||
password: z.string().min(1, { message: t('passwordPlaceholder') }),
|
||||
remember: z.boolean().optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (title === 'register' && !data.nickname) {
|
||||
ctx.addIssue({
|
||||
path: ['nickname'],
|
||||
message: 'nicknamePlaceholder',
|
||||
code: z.ZodIssueCode.custom,
|
||||
});
|
||||
}
|
||||
});
|
||||
const form = useForm({
|
||||
defaultValues: {
|
||||
nickname: '',
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
remember: false,
|
||||
},
|
||||
resolver: zodResolver(FormSchema),
|
||||
});
|
||||
|
||||
const onCheck = async (params) => {
|
||||
console.log('params', params);
|
||||
try {
|
||||
// const params = await form.validateFields();
|
||||
|
||||
const rsaPassWord = rsaPsw(params.password) as string;
|
||||
|
||||
if (title === 'login') {
|
||||
const code = await login({
|
||||
email: `${params.email}`.trim(),
|
||||
password: rsaPassWord,
|
||||
});
|
||||
if (code === 0) {
|
||||
navigate('/');
|
||||
}
|
||||
} else {
|
||||
const code = await register({
|
||||
nickname: params.nickname,
|
||||
email: params.email,
|
||||
password: rsaPassWord,
|
||||
});
|
||||
if (code === 0) {
|
||||
setTitle('login');
|
||||
}
|
||||
}
|
||||
} catch (errorInfo) {
|
||||
console.log('Failed:', errorInfo);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex items-center pl-[15%] bg-[url('@/assets/svg/next-login-bg.svg')] bg-cover bg-center">
|
||||
<div className="inline-block bg-colors-background-neutral-standard rounded-lg">
|
||||
{step === Step.SignIn && <SignInCard></SignInCard>}
|
||||
{step === Step.SignUp && <SignUpCard></SignUpCard>}
|
||||
{step === Step.VerifyEmail && <VerifyEmailCard></VerifyEmailCard>}
|
||||
<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} />
|
||||
<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">
|
||||
<img
|
||||
src={'/logo.svg'}
|
||||
alt="logo"
|
||||
className="size-10 mr-[12] cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xl font-bold self-end">RAGFlow</span>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-center mb-2">
|
||||
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">
|
||||
Let's get started
|
||||
</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);
|
||||
}}
|
||||
/>
|
||||
<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"
|
||||
>
|
||||
{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'}
|
||||
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>
|
||||
</div>
|
||||
);
|
||||
|
||||
70
web/src/pages/login-next/spotlight-top.tsx
Normal file
70
web/src/pages/login-next/spotlight-top.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user