This commit is contained in:
liubiren 2026-10-19 20:12:52 +08:00
parent 06e6caab26
commit 0b4b02a43d
6 changed files with 334 additions and 228 deletions

View File

@ -6,6 +6,7 @@ import reflex as rx
from application.states import AuthState from application.states import AuthState
def login_page() -> rx.Component: def login_page() -> rx.Component:
""" """
登录页面 登录页面
@ -32,12 +33,12 @@ def login_page() -> rx.Component:
on_change=AuthState.set_email, on_change=AuthState.set_email,
# 获取焦点事件:重置错误信息 # 获取焦点事件:重置错误信息
on_focus=AuthState.reset_error, on_focus=AuthState.reset_error,
line_height="16px",
width="100%", width="100%",
height="45px", height="45px",
padding="4px", padding="4px",
margin_bottom="24px", margin_bottom="24px",
border_radius="var(--prismui-border-radius-4)", border_radius="var(--prismui-border-radius-4)",
line_height="16px",
), ),
# 验证码输入框 # 验证码输入框
rx.hstack( rx.hstack(
@ -58,30 +59,40 @@ def login_page() -> rx.Component:
border="none !important", border="none !important",
), ),
rx.button( rx.button(
# 若再次发送验证码倒计时大于0则显示倒计时,否则显示获取验证码 rx.text(
rx.cond( rx.cond(
AuthState.resend_captcha_code_countdown > 0, AuthState.resend_captcha_code_countdown > 0,
f"{AuthState.resend_captcha_code_countdown} s", f"{AuthState.resend_captcha_code_countdown} s",
"获取验证码", "获取验证码",
), ),
# 点击事件:发送验证码
on_click=AuthState.send_captcha_code,
# 是否禁用绑定验证码发送禁用状态
disabled=AuthState.is_captcha_code_sending_button_disabled,
margin="0 8px",
background_color="var(--prismui-background-color-1)",
line_height="16px", line_height="16px",
white_space="nowrap",
# 若验证码发送禁用状态为是则颜色设置为 color-4,否则设置为 color-5(主题色) # 若验证码发送禁用状态为是则颜色设置为 color-4,否则设置为 color-5(主题色)
color=rx.cond( color=rx.cond(
AuthState.is_captcha_code_sending_button_disabled, AuthState.is_captcha_code_sending_button_disabled,
"var(--prismui-color-4)", "var(--prismui-color-4)",
"var(--prismui-color-5)", "var(--prismui-color-5)",
), ),
padding="0 12px",
),
# 若再次发送验证码倒计时大于0则显示倒计时,否则显示获取验证码
# 点击事件:发送验证码
on_click=AuthState.send_captcha_code,
# 是否禁用绑定验证码发送禁用状态
disabled=AuthState.is_captcha_code_sending_button_disabled,
margin="0 8px",
background_color="var(--prismui-background-color-1)",
border="none",
cursor=rx.cond( cursor=rx.cond(
AuthState.is_captcha_code_sending_button_disabled, AuthState.is_captcha_code_sending_button_disabled,
"not-allowed", "not-allowed",
"pointer", "pointer",
), ),
style={
"_hover": {
"background-color": "var(--prismui-background-color-1)"
}
},
), ),
width="100%", width="100%",
height="45px", height="45px",
@ -161,14 +172,19 @@ def login_page() -> rx.Component:
), ),
rx.button( 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, on_click=AuthState.login,
loading=AuthState.is_logging_in, loading=AuthState.is_logging_in,
color="var(--prismui-color-9)",
width="100%",
height="38px",
background_color="var(--prismui-background-color-4)",
border_radius="var(--prismui-border-radius-4)",
cursor="pointer",
style={
"_hover": {
"background-color": "var(--prismui-background-color-5)"
}
},
), ),
rx.text( rx.text(
"登录遇到问题?加他微信去吐槽", "登录遇到问题?加他微信去吐槽",

View File

@ -8,9 +8,9 @@ from application.states import AuthState, TaskState
from application.states.models import MessageHistoryItem, MessageType, WorkflowType from application.states.models import MessageHistoryItem, MessageType, WorkflowType
def workflow_button(workflow_type: WorkflowType): def shortcut(workflow_type: WorkflowType):
""" """
工作流按钮 快捷方式
:param workflow_type: 工作类型 :param workflow_type: 工作类型
:return: rx.Component :return: rx.Component
""" """
@ -19,17 +19,16 @@ def workflow_button(workflow_type: WorkflowType):
# 点击事件:设置工作流 # 点击事件:设置工作流
on_click=lambda: TaskState.set_workflow(workflow_type), on_click=lambda: TaskState.set_workflow(workflow_type),
padding="10px 16px", padding="10px 16px",
background_color="var(--prismui-background-color-6)", background_color="var(--prismui-background-color-9)",
border_radius="var(--prismui-border-radius-9)", border_radius="var(--prismui-border-radius-9)",
color="var(--prismui-color-10)",
cursor="pointer", cursor="pointer",
) )
def use_guidance() -> rx.Component: def welcome() -> rx.Component:
""" """
使用指引 欢迎
:return: Component :return: rx.Component
""" """
return rx.vstack( return rx.vstack(
# 品牌图标、名称和介绍 # 品牌图标、名称和介绍
@ -77,13 +76,13 @@ def use_guidance() -> rx.Component:
), ),
rx.hstack( rx.hstack(
# 工作流按钮:预定航班 # 工作流按钮:预定航班
workflow_button(WorkflowType.BOOK_FLIGHT), shortcut(WorkflowType.BOOK_FLIGHT),
# 工作流按钮:银行客户服务 # 工作流按钮:银行客户服务
workflow_button(WorkflowType.BANK_CUSTOMER_SERVICE), shortcut(WorkflowType.BANK_CUSTOMER_SERVICE),
# 工作流按钮:联网检索 # 工作流按钮:联网检索
workflow_button(WorkflowType.WEB_RETRIEVE), shortcut(WorkflowType.WEB_RETRIEVE),
# 工作流按钮:知识库检索 # 工作流按钮:知识库检索
workflow_button(WorkflowType.KNOWLEDGE_BASE_RETRIEVE), shortcut(WorkflowType.KNOWLEDGE_BASE_RETRIEVE),
flex_wrap="wrap", flex_wrap="wrap",
align_items="center", align_items="center",
gap="12px", gap="12px",
@ -194,7 +193,7 @@ def thinking(item: MessageHistoryItem) -> rx.Component:
width="100%", width="100%",
line_height="1.6", line_height="1.6",
font_size="var(--prismui-font-size-1)", font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-3)", color="var(--prismui-color-2)",
), ),
rx.fragment(), rx.fragment(),
), ),
@ -305,7 +304,7 @@ def tool_call(item: MessageHistoryItem) -> rx.Component:
True, item.id True, item.id
), ),
padding="0 12px", padding="0 12px",
background_color="var(--prismui-background-color-7)", background_color="var(--prismui-background-color-9)",
border_radius="var(--prismui-border-radius-3)", border_radius="var(--prismui-border-radius-3)",
line_height="18px", line_height="18px",
font_size="var(--prismui-font-size-1)", font_size="var(--prismui-font-size-1)",
@ -368,7 +367,7 @@ def text(item: MessageHistoryItem) -> rx.Component:
"code": lambda text: rx.text( "code": lambda text: rx.text(
text, text,
display="inline", display="inline",
background_color="var(--prismui-background-color-6)", background_color="var(--prismui-background-color-9)",
border_radius="var(--prismui-border-radius-1)", border_radius="var(--prismui-border-radius-1)",
color="var(--prismui-color-5)", color="var(--prismui-color-5)",
), ),
@ -415,7 +414,7 @@ def text(item: MessageHistoryItem) -> rx.Component:
width="4.5px", width="4.5px",
height="4.5px", height="4.5px",
margin_right="8px", margin_right="8px",
background_color="var(--prismui-background-color-7)", background_color="var(--prismui-background-color-4)",
border_radius="100%", border_radius="100%",
), ),
rx.text( rx.text(
@ -445,7 +444,7 @@ def text(item: MessageHistoryItem) -> rx.Component:
"th": lambda children: rx.el.th( "th": lambda children: rx.el.th(
children, children,
padding="8px 12px", padding="8px 12px",
background_color="var(--prismui-background-color-6)", background_color="var(--prismui-background-color-9)",
border="1px solid var(--prismui-background-color-2)", border="1px solid var(--prismui-background-color-2)",
text_align="left", text_align="left",
), ),
@ -487,10 +486,10 @@ def message_history() -> rx.Component:
:return: Component :return: Component
""" """
return rx.vstack( return rx.vstack(
# 若消息历史为空则显示使用指引,否则渲染消息历史项 # 若消息历史为空则渲染欢迎,否则渲染消息历史项
rx.cond( rx.cond(
TaskState.message_history.length() == 0, TaskState.message_history.length() == 0,
use_guidance(), welcome(),
rx.vstack( rx.vstack(
rx.hstack( rx.hstack(
rx.hstack( rx.hstack(
@ -518,8 +517,9 @@ def message_history() -> rx.Component:
TaskState.task_last_run_at, TaskState.task_last_run_at,
line_height="20px", line_height="20px",
font_size="var(--prismui-font-size-1)", font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-3)", color="var(--prismui-color-2)",
), ),
# 渲染产品面板按钮
rx.tooltip( rx.tooltip(
rx.box( rx.box(
rx.icon( rx.icon(
@ -544,8 +544,7 @@ def message_history() -> rx.Component:
), ),
content="产物面板", content="产物面板",
), ),
rx.hstack( # 渲染更多按钮
# 渲染更多下拉菜单
rx.menu.root( rx.menu.root(
rx.menu.trigger( rx.menu.trigger(
rx.box( rx.box(
@ -588,7 +587,10 @@ def message_history() -> rx.Component:
height="28px", height="28px",
width="100%", width="100%",
cursor="pointer", cursor="pointer",
on_click=TaskState.show_task_delete_confirm, # 点击事件:显示当前任务删除确认
on_select=TaskState.toggle_task_delete_confirm(
True
).stop_propagation,
style={ style={
"_hover": { "_hover": {
"background_color": "var(--prismui-background-color-9)" "background_color": "var(--prismui-background-color-9)"
@ -597,16 +599,24 @@ def message_history() -> rx.Component:
), ),
), ),
), ),
# 渲染删除确认弹窗 # 渲染任务删除确认
rx.popover.root( rx.popover.root(
rx.popover.trigger(rx.box(width="0", height="32px")), rx.popover.trigger(rx.box(width="0", height="32px")),
rx.popover.content( rx.popover.content(
rx.vstack( rx.vstack(
rx.text("删除任务?", line_height="20px"), rx.vstack(
rx.text(
"删除任务?",
line_height="20px",
font_weight="var(--prismui-font-weight-3)",
),
rx.text( rx.text(
"删除后,任务将不可恢复。", "删除后,任务将不可恢复。",
line_height="20px", line_height="20px",
font_size="var(--prismui-font-size-1)", font_size="var(--prismui-font-size-1) !important",
),
gap="8px",
width="100%",
), ),
rx.hstack( rx.hstack(
rx.spacer(), rx.spacer(),
@ -614,21 +624,37 @@ def message_history() -> rx.Component:
rx.popover.close( rx.popover.close(
rx.button( rx.button(
"取消", "取消",
# 点击事件:隐藏当前任务删除确认 style={
on_click=TaskState.hide_task_delete_confirm, "_hover": {
"background_color": "var(--prismui-background-color-9)"
}
},
) )
), ),
rx.popover.close(rx.button("确认")), rx.button(
"确认",
color="var(--prismui-color-9)",
background_color="var(--prismui-background-color-6)",
style={
"_hover": {
"background_color": "var(--prismui-background-color-7)"
}
},
on_click=TaskState.delete_task,
),
), ),
align_items="space-between", align_items="space-between",
width="100%", width="100%",
), ),
gap="24px",
width="100%", width="100%",
) ),
width="240px",
padding="16px",
), ),
# 显示绑定:显示当前任务删除确认 # 显示绑定:显示当前任务删除确认
open=TaskState.is_task_delete_confirm_shown, open=TaskState.is_task_delete_confirm_shown,
), on_open_change=TaskState.toggle_task_delete_confirm,
), ),
align_items="center", align_items="center",
gap="4px", gap="4px",
@ -748,8 +774,8 @@ def user_prompt_sending_box() -> rx.Component:
padding="0 12px", padding="0 12px",
background_color=rx.cond( background_color=rx.cond(
TaskState.is_user_prompt_sending_button_disabled, TaskState.is_user_prompt_sending_button_disabled,
"var(--prismui-color-11)", "var(--prismui-background-color-8)",
"var(--prismui-color-5)", "var(--prismui-background-color-4)",
), ),
border="none", border="none",
border_radius="var(--prismui-border-radius-5)", border_radius="var(--prismui-border-radius-5)",
@ -761,6 +787,14 @@ def user_prompt_sending_box() -> rx.Component:
"not-allowed", "not-allowed",
"pointer", "pointer",
), ),
style={
"_hover": {
"background-color": "var(--prismui-background-color-5)",
},
"&:disabled:hover": {
"background-color": "var(--prismui-background-color-8)",
},
},
), ),
justify_content="flex-end", justify_content="flex-end",
align_items="center", align_items="center",

View File

@ -103,7 +103,7 @@ def task_history_item(
item.last_run_at, item.last_run_at,
line_height="20px", line_height="20px",
font_size="var(--prismui-font-size-1)", font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-3)", color="var(--prismui-color-2)",
flex_shrink=0, # 禁止缩放 flex_shrink=0, # 禁止缩放
), ),
align_items="center", align_items="center",
@ -148,91 +148,6 @@ def task_history() -> rx.Component:
) )
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",
),
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: def sidebar() -> rx.Component:
""" """
侧边栏 侧边栏
@ -241,6 +156,17 @@ def sidebar() -> rx.Component:
return rx.vstack( return rx.vstack(
rx.vstack( rx.vstack(
rx.alert_dialog.root(
rx.alert_dialog.trigger(rx.box(height="0", width="100%")),
rx.alert_dialog.content(
rx.alert_dialog.title("确认退出登录?"),
rx.alert_dialog.description(
"退出登录不会丢失任何数据,你仍可以登录此账号。"
),
rx.alert_dialog.cancel("取消"),
rx.alert_dialog.action("退出登录"),
),
),
# 渲染系统图标和名称 # 渲染系统图标和名称
rx.hstack( rx.hstack(
# 系统图标 # 系统图标
@ -291,12 +217,128 @@ def sidebar() -> rx.Component:
width="100%", width="100%",
), ),
rx.vstack( rx.vstack(
rx.vstack( rx.divider(
# 设置按钮 width="100%", background_color="var(--prismui-background-color-3)"
settings_button(),
), ),
padding_bottom="16px", rx.hstack(
rx.popover.root(
rx.popover.trigger(
rx.hstack(
rx.avatar(
fallback="M",
height="28px",
width="28px",
color="var(--prismui-color-5)",
border_radius="100%",
),
rx.text(
"刘弼仁",
line_height="20px",
font_size="var(--prismui-font-size-1)",
font_weight="var(--prismui-font-weight-3)",
),
flex="1",
align_items="center",
gap="10px",
padding="6px 8px",
margin="8px 8px 8px 0",
border_radius="var(--prismui-border-radius-3)",
cursor="pointer",
style={
"_hover": {
"background": "var(--prismui-background-color-2)",
},
},
),
),
rx.popover.content(
rx.vstack(
rx.hstack(
rx.avatar(
fallback="M",
height="40px",
width="40px",
color="var(--prismui-color-5)",
border_radius="100%",
),
rx.text(
"刘弼仁",
line_height="24px",
font_size="var(--prismui-font-size-3) !important",
font_weight="var(--prismui-font-weight-3)",
),
align_items="center",
width="100%",
padding="16px",
background_color="var(--prismui-background-color-1)",
border_radius="var(--prismui-border-radius-3)",
),
rx.vstack(
rx.popover.close(
rx.hstack(
rx.icon(
"log-out",
height="16px",
width="16px",
object_fit="contain",
color="var(--prismui-color-1)",
),
rx.text("退出登录", line_height="20px"),
align_items="center",
gap="8px", gap="8px",
width="100%",
padding="5px 8px",
border_radius="var(--prismui-border-radius-3)",
cursor="pointer",
style={
"_hover": {
"background": "var(--prismui-background-color-2)",
},
},
),
),
width="100%",
padding="8px",
background_color="var(--prismui-background-color-1)",
border_radius="var(--prismui-border-radius-3)",
),
),
width="320px",
padding="8px",
background_color="var(--prismui-background-color-9)",
),
),
rx.tooltip(
rx.box(
rx.icon(
"bell",
height="16px",
width="16px",
object_fit="contain",
color="var(--prismui-color-1)",
),
display="flex",
align_items="center",
justify_content="center",
height="32px",
flex_shrink="0",
width="32px",
border_radius="var(--prismui-border-radius-3)",
cursor="pointer",
style={
"_hover": {
"background": "var(--prismui-background-color-2)",
},
},
),
content="消息中心",
),
align_items="center",
justify_content="space-between",
gap="4px",
width="100%",
),
width="100%",
), ),
flex_shrink="0", flex_shrink="0",
justify_content="space-between", justify_content="space-between",

View File

@ -4,7 +4,7 @@
""" """
from datetime import date as Date, datetime from datetime import date as Date, datetime
from typing import AsyncGenerator from typing import AsyncGenerator
from reflex_base.event import EventSpec
from pydantic_ai import DeferredToolRequests, DeferredToolResults, ToolApproved from pydantic_ai import DeferredToolRequests, DeferredToolResults, ToolApproved
from pydantic_ai.messages import ( from pydantic_ai.messages import (
FunctionToolResultEvent, FunctionToolResultEvent,
@ -51,8 +51,10 @@ class TaskState(rx.State):
# 临时用户提示词 # 临时用户提示词
temporary_user_prompt: str = "" temporary_user_prompt: str = ""
# 显示当前任务删除确认 # 当前任务删除确认显示
is_task_delete_confirm_shown: bool = False is_task_delete_confirm_shown: bool = False
# 上一次任务删除确认显示日期时间
last_task_delete_confirm_show_at: float = 0
# 当前数据库状态(私有变量) # 当前数据库状态(私有变量)
# 私有变量:reflex 约定以 _ 开头的变量为私有变量,后端不序列化,前端不可使用 # 私有变量:reflex 约定以 _ 开头的变量为私有变量,后端不序列化,前端不可使用
@ -155,26 +157,26 @@ class TaskState(rx.State):
return f"更新于 {task.last_run_at.strftime("%Y-%m-%d %H:%M:%S")}" return f"更新于 {task.last_run_at.strftime("%Y-%m-%d %H:%M:%S")}"
@rx.event @rx.event
async def show_task_delete_confirm(self) -> None: async def toggle_task_delete_confirm(self, is_shown: bool) -> None:
""" """
显示当前任务删除确认 切换当前任务删除确认
:param is_shown: 显示当前任务删除确认
:return: None :return: None
""" """
self.is_task_delete_confirm_shown = True # 当前日期时间
now = datetime.now().timestamp()
# 根据当前时间与上一次任务删除确认显示日期时间间隔防抖
if now - self.last_task_delete_confirm_show_at < 0.2:
return
self.is_task_delete_confirm_shown = is_shown
self.last_task_delete_confirm_show_at = now
@rx.event @rx.event
async def hide_task_delete_confirm(self) -> None: async def delete_task(self) -> EventSpec | None:
""" """
隐藏当前任务删除确认 删除当前任务
:return: None :return: RX事件
"""
self.is_task_delete_confirm_shown = False
@rx.event
async def confirm_task_delete(self) -> None:
"""
确认当前任务删除
:return: None
""" """
from application.states import SidebarState from application.states import SidebarState
@ -195,6 +197,9 @@ class TaskState(rx.State):
sidebar_state.nav_button = "" sidebar_state.nav_button = ""
# 将空设置为当前任务唯一标识 # 将空设置为当前任务唯一标识
self.task_id = "" self.task_id = ""
# 将任务删除确认显示设置为否
self.is_task_delete_confirm_shown = False
return rx.toast.success("删除成功")
@rx.event @rx.event
def set_task_id(self, task_id: str) -> None: def set_task_id(self, task_id: str) -> None:

View File

@ -2,10 +2,10 @@
--prismui-background-color-1: #ffffff; --prismui-background-color-1: #ffffff;
--prismui-background-color-2: #e6e6e6; --prismui-background-color-2: #e6e6e6;
--prismui-background-color-3: rgb(230, 230, 230); --prismui-background-color-3: rgb(230, 230, 230);
--prismui-background-color-4: #babbc0; --prismui-background-color-4: #5e7ce0;
--prismui-background-color-5: #ffffff33; --prismui-background-color-5: #758ee0;
--prismui-background-color-6: #f2f2f3; --prismui-background-color-6: rgb(232, 92, 92);
--prismui-background-color-7: #5e7ce0; --prismui-background-color-7: rgb(236, 116, 116);
--prismui-background-color-8: #beccfa; --prismui-background-color-8: #beccfa;
--prismui-background-color-9: rgb(242, 242, 242); --prismui-background-color-9: rgb(242, 242, 242);
/* /*
@ -26,7 +26,7 @@
--prismui-font-weight-4: 700; --prismui-font-weight-4: 700;
--prismui-color-1: #252b3a; --prismui-color-1: #252b3a;
--prismui-color-2: #575d6c; --prismui-color-2: #575d6c;
--prismui-color-3: #babbc0; --prismui-color-3: rgb(222, 222, 222);
--prismui-color-4: rgba(37, 43, 58, 0.5); --prismui-color-4: rgba(37, 43, 58, 0.5);
--prismui-color-5: #5e7ce0; --prismui-color-5: #5e7ce0;
--prismui-color-6: #f66f6a; --prismui-color-6: #f66f6a;
@ -147,7 +147,16 @@ pre, pre code {
/* Button */ /* Button */
.rt-Button { .rt-Button {
font-family: var(--prismui-font-family) !important; font-family: var(--prismui-font-family);
font-size: var(--prismui-font-size-2); font-size: var(--prismui-font-size-2);
color: var(--prismui-color-1); color: var(--prismui-color-1);
height: 32px;
width: 68px;
background-color: var(--prismui-background-color-1);
border: 1px solid var(--prismui-color-3);
border-radius: var(--prismui-border-radius-3);
cursor: pointer;
}
.rt-Button:hover {
background-color: inherit;
} }

Binary file not shown.