205 lines
6.7 KiB
Python
205 lines
6.7 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
聊天页面相关组件
|
||
"""
|
||
import reflex as rx
|
||
|
||
from application.models import PartType, Part, Dialog
|
||
from application.state import ChatState
|
||
|
||
|
||
def render_user_prompt(user_prompt: str) -> rx.Component:
|
||
"""
|
||
渲染用户提示词
|
||
:param user_prompt: 用户提示词
|
||
:return: Component
|
||
"""
|
||
return rx.markdown(
|
||
user_prompt,
|
||
color=rx.color("gray", 12), # 文字颜色
|
||
background_color=rx.color("gray", 2), # 背景颜色
|
||
display="inline-block", # 布局模式:自适应文本宽度
|
||
max_width="85%", # 最大宽度
|
||
padding_x="1.25em", # 水平内边距
|
||
padding_y="0.5em", # 垂直内边距
|
||
margin_left="auto", # 左侧外边距自动调整
|
||
margin_bottom="8px", # 底部外边距
|
||
border_radius="12px", # 圆角
|
||
)
|
||
|
||
|
||
def render_part(dialog_id: str, part_id: str, part: Part):
|
||
"""
|
||
渲染片段
|
||
:param dialog_id: 对话唯一标识
|
||
:param part_id: 片段唯一标识
|
||
:param part: 片段实例
|
||
:return: Component
|
||
"""
|
||
return rx.match(
|
||
part.part_type,
|
||
(
|
||
PartType.TEXT,
|
||
rx.markdown(
|
||
part.content,
|
||
font_size="0.90rem",
|
||
color=rx.color("gray", 12), # 字体颜色
|
||
background_color="transparent", # 背景颜色:设置为透明以继承父元素背景颜色
|
||
display="block", # 布局模式:铺满
|
||
max_width="85%", # 最大宽度
|
||
padding="0", # 内边距
|
||
margin_right="auto", # 右侧外边距自动调整
|
||
margin_bottom="12px", # 底部外边距
|
||
key=part_id,
|
||
),
|
||
), # 文本片段
|
||
(PartType.FINISHED, rx.fragment(key=part_id)), # 结束片段
|
||
rx.box(
|
||
rx.cond(
|
||
part.is_open,
|
||
rx.hstack(
|
||
rx.text(
|
||
part.content,
|
||
font_size="0.90rem",
|
||
bold=True,
|
||
color=rx.color("gray", 10),
|
||
),
|
||
rx.spacer(),
|
||
rx.icon("chevron_up", size=16, color=rx.color("gray", 6)),
|
||
width="100%",
|
||
cursor="pointer",
|
||
padding_x="1em",
|
||
padding_y="0.6em",
|
||
background_color=rx.color("gray", 2),
|
||
border_radius="8px",
|
||
on_click=ChatState.toggle_collapse_panel(dialog_id, part_id),
|
||
), # 折叠面板打开时标题栏
|
||
rx.hstack(
|
||
rx.hstack(
|
||
rx.text(
|
||
loading_prefix,
|
||
font_size="0.90rem",
|
||
bold=True,
|
||
color=rx.color("gray", 10),
|
||
),
|
||
loading_dot_group(part.is_streaming),
|
||
spacing="8", # 间距
|
||
align_items="center",
|
||
),
|
||
rx.spacer(),
|
||
rx.icon("chevron_down", size=16, color=rx.color("gray", 6)),
|
||
width="100%",
|
||
cursor="pointer",
|
||
on_click=ChatState.toggle_collapse_panel(dialog_id, part_id),
|
||
padding_x="1em",
|
||
padding_y="0.6em",
|
||
background_color=rx.color("gray", 2),
|
||
border_radius="8px",
|
||
), # 折叠面板关闭时标题栏
|
||
),
|
||
rx.cond(part.is_open, detail_box, rx.fragment()),
|
||
width="100%",
|
||
max_width="85%",
|
||
margin_bottom="10px",
|
||
cursor="pointer",
|
||
key=part_id,
|
||
),
|
||
)
|
||
|
||
|
||
def render_dialog_item(dialog_id: str, dialog: Dialog) -> rx.Component:
|
||
"""
|
||
渲染对话项
|
||
:param dialog: 对话实例
|
||
:return: Component
|
||
"""
|
||
return rx.box(
|
||
rx.box(
|
||
render_user_prompt(user_prompt=dialog.user_prompt),
|
||
text_align="right",
|
||
width="100%",
|
||
margin_bottom="8px",
|
||
),
|
||
rx.box(
|
||
rx.foreach(
|
||
dialog.output,
|
||
lambda part_id, part: render_part(
|
||
dialog_id=dialog_id, part_id=part_id, part=part
|
||
),
|
||
),
|
||
text_align="left",
|
||
width="100%",
|
||
margin_bottom="8px",
|
||
),
|
||
width="min(100%, 50em)", # 最大宽度:父级元素最大宽度和50em中较小值
|
||
margin_x="auto", # 水平外边距:自动调整
|
||
key=dialog_id,
|
||
)
|
||
|
||
|
||
def render_dialog_list() -> rx.Component:
|
||
"""
|
||
渲染对话列表
|
||
:return: Component
|
||
"""
|
||
return rx.auto_scroll(
|
||
rx.foreach(
|
||
ChatState.get_dialogs,
|
||
lambda dialog_id, dialog: render_dialog_item(
|
||
dialog_id=dialog_id, dialog=dialog
|
||
),
|
||
),
|
||
flex="1",
|
||
padding="8px",
|
||
overflow_y="auto",
|
||
)
|
||
|
||
|
||
def render_input_bar() -> rx.Component:
|
||
"""
|
||
渲染输入栏
|
||
"""
|
||
return rx.center(
|
||
rx.vstack(
|
||
rx.form(
|
||
rx.hstack(
|
||
rx.input(
|
||
name="user_prompt",
|
||
placeholder="请输入...",
|
||
flex="auto",
|
||
),
|
||
rx.button(
|
||
"发送",
|
||
type="submit",
|
||
loading=ChatState.get_current_chat_status, # 正在流式输出时按钮显示为 loading
|
||
disabled=ChatState.get_current_chat_status, # 正在流式输出时按钮禁用
|
||
),
|
||
max_width="50em",
|
||
margin="0 auto",
|
||
align_items="center",
|
||
),
|
||
on_submit=ChatState.process_input, # 处理输入,返回流式输出
|
||
reset_on_submit=True, # 提交后清空输入框
|
||
),
|
||
rx.text(
|
||
"抹茶兔兔工作室",
|
||
text_align="center",
|
||
font_size=".75em",
|
||
color=rx.color("mauve", 10),
|
||
),
|
||
width="100%",
|
||
padding_x="16px",
|
||
align="stretch",
|
||
),
|
||
position="sticky",
|
||
bottom="0",
|
||
left="0",
|
||
padding_y="16px",
|
||
backdrop_filter="auto",
|
||
backdrop_blur="lg",
|
||
border_top=f"1px solid {rx.color('mauve', 3)}",
|
||
background_color=rx.color("mauve", 2),
|
||
align="stretch",
|
||
width="100%",
|
||
) # rx.center 等价 rx.box(display="flex", align_items="center", justify_content="center")
|