Python/agent/application/pages/index.py

458 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
首页
"""
import reflex as rx
from application.pages import conversation, knowledge_base
from application.states import AuthState
# 导航按钮字典
nav_buttons: dict = {
"conversation": {"src": "/conversation.svg", "text": "会话"},
"knowledge_base": {"src": "/knowledge_base.svg", "text": "知识库"},
}
"""
约定:
1 若组件仅用于当前页面则无需抽象为通用组件(application.components.*)
"""
def nav_button(
nav_button: str,
) -> rx.Component:
"""
导航按钮
:param nav_button: 指定的导航按钮
:return : Component
"""
# 导航按钮激活状态
is_actived = nav_button == AuthState.activated_nav_button
return rx.vstack(
# 图标
rx.image(
nav_buttons[nav_button]["src"],
width="36px",
height="36px",
object_fit="contain",
padding="6px",
background_color=rx.cond(
is_actived,
"var(--prismui-background-color-1)",
"transparent",
),
border_radius="var(--prismui-border-radius-3)",
box_shadow=rx.cond(is_actived, "var(--prismui-box-shadow-2)", "none"),
),
# 标签
rx.text(
nav_buttons[nav_button]["text"],
line_height="20px",
font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-1)",
),
align_items="center",
gap="4px",
cursor="pointer",
# 点击事件:将指定导航按钮设置为激活的导航按钮
on_click=AuthState.switch_activated_nav_button(nav_button),
)
def settings_button():
"""
设置按钮
:return: Component
"""
return rx.box(
rx.hover_card.root(
# 触发事件:点击自定义按钮
rx.hover_card.trigger(
rx.box(
rx.icon(
"settings",
stroke_width=1.5,
width="20px",
height="20px",
color="var(--prismui-color-1)",
),
display="flex",
flex_flow="column",
justify_content="center",
align_items="center",
width="36px",
height="36px",
border_radius="var(--prismui-border-radius-3)",
style={
"&:hover": {
"background": "var(--prismui-background-color-5)",
}, # 鼠标悬停背景颜色
},
cursor="pointer",
)
),
# 悬停卡片
rx.hover_card.content(
rx.box(
rx.box(
transform="translateX(-50%) rotate(45deg)",
position="absolute",
top="calc(50% - 4px)",
left="0",
width="8px",
height="8px",
background_color="var(--prismui-background-color-1)",
),
rx.box(
"退出登录",
display="flex",
justify_content="center",
align_items="center",
width="100%",
height="24px",
line_height="16px",
border_radius="var(--prismui-border-radius-1)",
style={
"_hover": {
"background_color": "var(--prismui-background-color-6)",
},
},
# 点击事件:退出登录
on_click=lambda: AuthState.logout(),
),
cursor="pointer",
),
side="left",
side_offset=8,
position="relative",
align="center",
padding="4px 12px",
border_radius="var(--prismui-border-radius-1)",
box_shadow="var(--prismui-box-shadow-3)",
font_family="var(--prismui-font-family)",
overflow="visible",
style={
"font-size": "var(--prismui-font-size-1) !important",
"color": "var(--prismui-color-1) !important",
},
),
open=AuthState.is_settings_hover_card_open,
# 悬停卡片打开状态变化事件:设置设置悬停卡片打开状态
on_open_change=AuthState.set_settings_hover_card_open,
),
min_width="14px",
cursor="pointer",
)
def sidebar() -> rx.Component:
"""
侧边栏
:return: Component
"""
return rx.vstack(
rx.vstack(
rx.vstack(
# 品牌图标
rx.image(
src="/logo.png",
width="32px",
height="32px",
object_fit="contain",
),
# 品牌名称
rx.text(
"棱镜球",
line_height="20px",
font_size="calc(var(--prismui-font-size-1) - 1px)", # 较导航按钮标签弱化
font_weight="var(--prismui-font-weight-2)",
),
align_items="center",
gap="4px",
),
# 分隔线
rx.divider(
width="32px",
height="1px",
margin="16px 0",
background_color="var(--prismui-background-color-4)",
),
rx.vstack(
# 会话导航按钮
nav_button("conversation"),
# 知识库导航按钮
nav_button("knowledge_base"),
gap="8px",
),
align_items="center",
width="100%",
margin_top="12px",
),
rx.vstack(
rx.vstack(
# 设置按钮
settings_button(),
),
padding_bottom="16px",
gap="8px",
),
flex_shrink="0",
justify_content="space-between",
align_items="center",
width="60px",
min_width="60px",
max_width="60px",
height="100%",
)
def login_popup() -> rx.Component:
"""
登录弹窗
:return: Component
"""
return rx.center(
rx.hstack(
rx.vstack(
# 标题
rx.text(
"登录解锁更多功能",
margin_bottom="24px",
line_height="32px",
font_size="var(--prismui-font-size-5)",
font_weight="var(--prismui-font-weight-3)",
),
# 邮箱输入框
rx.input(
placeholder="请输入邮箱",
# 值绑定邮箱
value=AuthState.email,
# 值变化事件:设置邮箱
on_change=AuthState.set_email,
# 获取焦点事件:重置登录错误信息
on_focus=AuthState.reset_login_error_message,
width="100%",
height="45px",
padding="4px",
margin_bottom="24px",
border_radius="var(--prismui-border-radius-4)",
line_height="16px",
),
# 验证码输入框
rx.hstack(
rx.input(
placeholder="请输入验证码",
# 值绑定验证码
value=AuthState.captcha,
max_length=6,
# 值变化事件:设置验证码
on_change=AuthState.set_captcha,
# 获取焦点事件:重置登录错误信息
on_focus=AuthState.reset_login_error_message,
flex=1,
height="100%",
padding="4px",
border_radius="var(--prismui-border-radius-4)",
line_height="16px",
border="none !important",
),
rx.button(
# 若再次发送验证码倒计时大于0则显示倒计时否则显示获取验证码
rx.cond(
AuthState.resend_captcha_countdown > 0,
f"{AuthState.resend_captcha_countdown} s",
"获取验证码",
),
# 点击事件:发送验证码
on_click=AuthState.send_captcha,
# 是否禁用绑定验证码发送禁用状态
disabled=AuthState.is_captcha_sending_disabled,
margin="0 8px",
background_color="var(--prismui-background-color-1)",
line_height="16px",
# 若验证码发送禁用状态为是则颜色设置为 color-4否则设置为 color-5主题色
color=rx.cond(
AuthState.is_captcha_sending_disabled,
"var(--prismui-color-4)",
"var(--prismui-color-5)",
),
cursor=rx.cond(
AuthState.is_captcha_sending_disabled,
"not-allowed",
"pointer",
),
),
width="100%",
height="45px",
align_items="center",
border="var(--prismui-border-1)",
border_radius="var(--prismui-border-radius-4)",
),
rx.text(
AuthState.login_error_message,
width="100%",
min_height="20px",
text_align="left",
line_height="20px",
font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-6)",
),
rx.box(
rx.cond(
AuthState.is_captcha_sent,
rx.hstack(
rx.icon(
"circle-check",
width="12px",
height="12px",
color="var(--prismui-color-2)",
),
rx.text(
"验证码已发送,请耐心等待",
line_height="14px",
font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-2)",
),
margin="0 0 10px 0",
gap="4px",
),
),
display="flex",
align_items="center",
min_height="34px",
),
rx.hstack(
rx.checkbox(
checked=AuthState.is_policies_agreed,
# 值变化事件:切换协议同意状态
on_change=AuthState.toggle_policies_agreed,
cursor="pointer",
style={
"&::before": {
"width": "12px !important",
"height": "12px !important",
},
"& svg": {
"width": "8px !important",
"height": "8px !important",
},
"&[data-state='checked']::before": {
"background-color": "var(--prismui-color-5) !important",
},
},
),
rx.text(
"我已阅读并同意",
color="var(--prismui-color-2)",
line_height="14px",
),
rx.link(
"用户协议、隐私政策",
href="#",
line_height="14px",
color="var(--prismui-color-5)",
cursor="pointer",
),
margin_bottom="10px",
align_items="center",
gap="4px",
font_size="var(--prismui-font-size-1)",
),
rx.button(
"登录",
width="100%",
height="38px",
border_radius="var(--prismui-border-radius-4)",
background_color="var(--prismui-color-5)",
color="var(--prismui-color-9)",
cursor="pointer",
on_click=AuthState.login,
loading=AuthState.is_logging_in,
),
rx.text(
"登录遇到问题?加他微信去吐槽",
margin="20px 0 40px 0",
line_height="14px",
font_size="12px",
color="var(--prismui-color-2)",
),
width="396px",
height="100%",
padding="40px 48px 0 48px",
align_items="center",
),
rx.vstack(
rx.image(
src="/qrcode.png",
width="208px",
height="208px",
padding="20px",
background_color="var(--prismui-background-color-1)",
border="var(--prismui-border-2)",
border_radius="var(--prismui-border-radius-6)",
),
rx.text(
"加微信好友",
margin="10px 0 12px 0",
line_height="32px",
font_size="var(--prismui-font-size-2)",
font_weight="var(--prismui-font-weight-2)",
),
align_items="center",
justify_content="center",
width="396px",
height="100%",
padding="72px 32px",
),
background="var(--prismui-background-2)",
border="1px solid #2222220f",
border_radius="var(--prismui-border-radius-6)",
box_shadow="0 4px 64px 0 #0000001a",
overflow="hidden",
),
position="absolute",
top="0",
left="0",
z_index="9999",
width="100vw",
height="100vh",
backdrop_filter="blur(4px)",
)
def index() -> rx.Component:
"""
首页(默认显示会话页面)
"""
return rx.fragment(
rx.hstack(
# 侧边栏
sidebar(),
# 根据侧边栏状态中激活的导航按钮相应页面(本项目采用卡片布局)
rx.match(
AuthState.activated_nav_button,
# 知识库页面
("knowledge_base", knowledge_base()),
# 会话页面
conversation(),
),
width="100%",
height="100vh",
overflow="auto",
padding="8px 8px 8px 0",
background="var(--prismui-background-1)",
),
# 若用户唯一标识为空则登录窗
rx.cond(
AuthState.user_id,
rx.fragment(),
login_popup(),
),
)