Python/产品需求文档AI生成/application/components/chat.py

205 lines
6.7 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.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")