296 lines
8.9 KiB
Python
296 lines
8.9 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
渲染对话相关组件
|
||
"""
|
||
import reflex as rx
|
||
|
||
from application.models import Run, Reasoning
|
||
from application.state.conversation import ConversationState
|
||
|
||
|
||
def render_user_prompt(run: Run) -> rx.Component:
|
||
"""
|
||
渲染用户提示词
|
||
:param run: 运行实例
|
||
:return: Component
|
||
"""
|
||
return rx.box(
|
||
rx.markdown(
|
||
run.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", # 圆角
|
||
),
|
||
text_align="right",
|
||
width="100%",
|
||
margin_bottom="8px",
|
||
)
|
||
|
||
|
||
def render_reasoning(reasoning_id: int, reasoning: Reasoning):
|
||
"""
|
||
渲染推理
|
||
:param reasoning_id: 对话唯一标识
|
||
:param reasoning: 推理实例
|
||
:return: Component
|
||
"""
|
||
return rx.hstack(
|
||
rx.vstack(
|
||
rx.box(
|
||
width="8px",
|
||
height="8px",
|
||
background_color=rx.color("blue", 7),
|
||
),
|
||
rx.box(width="2px", flex=1, background_color=rx.color("blue", 3)),
|
||
align_items="center",
|
||
spacing="0",
|
||
height="100%",
|
||
),
|
||
rx.markdown(reasoning.content, color=rx.color("gray", 11), padding_y="4px"),
|
||
align_items="flex-start",
|
||
spacing="2",
|
||
width="100%",
|
||
key=reasoning_id,
|
||
)
|
||
|
||
|
||
def render_reasoning_panel(run_id: str, run: Run) -> rx.Component:
|
||
"""
|
||
渲染推理面板
|
||
:param run_id: 运行唯一标识
|
||
:param run: 运行实例
|
||
:return: Component
|
||
"""
|
||
# 推理字典
|
||
reasonings = run.reasonings
|
||
# 推理面板展开状态
|
||
is_reasoning_panel_open = run.is_reasoning_panel_open
|
||
|
||
return rx.cond(
|
||
not reasonings,
|
||
rx.fragment(),
|
||
rx.box(
|
||
# 标题栏
|
||
rx.hstack(
|
||
rx.cond(
|
||
run.is_reasoning,
|
||
rx.badge("正在推理"),
|
||
rx.badge("推理完成"),
|
||
),
|
||
rx.spacer(),
|
||
# 若推理面板展开则渲染上箭头,否则渲染下箭头
|
||
rx.icon(
|
||
rx.cond(is_reasoning_panel_open, "chevron_up", "chevron_down"),
|
||
size=16,
|
||
color=rx.color("mauve", 6),
|
||
),
|
||
width="100%",
|
||
margin_bottom="6px",
|
||
on_click=lambda: ConversationState.toggle_reasoning_panel(
|
||
run_id
|
||
), # 点击事件:展开/折叠指定运行唯一标识的推理面板
|
||
),
|
||
# 若推理面板展开则遍历渲染推理,否则不渲染
|
||
rx.cond(
|
||
is_reasoning_panel_open,
|
||
rx.foreach(
|
||
reasonings,
|
||
lambda reasoning_id, reasoning: render_reasoning(
|
||
reasoning_id=reasoning_id, reasoning=reasoning
|
||
),
|
||
),
|
||
rx.fragment(),
|
||
),
|
||
text_align="left",
|
||
width="100%",
|
||
margin_bottom="8px",
|
||
),
|
||
)
|
||
|
||
|
||
def render_assistant_content(run: Run) -> rx.Component:
|
||
"""
|
||
渲染回复正文
|
||
:param run: 运行实例,包含用户提示词、推理字典和回复正文
|
||
:return: Component
|
||
"""
|
||
# 若回复正文为空则不渲染,否则渲染回复正文
|
||
return rx.cond(
|
||
not run.assistant_content,
|
||
rx.fragment(),
|
||
rx.box(
|
||
rx.markdown(
|
||
run.assistant_content,
|
||
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_run(run_id: str, run: Run) -> rx.Component:
|
||
"""
|
||
渲染运行
|
||
:param run: 运行,包含用户提示词、推理字典和回复正文
|
||
:return: Component
|
||
"""
|
||
return rx.box(
|
||
# 渲染用户提示词
|
||
render_user_prompt(run=run),
|
||
# 渲染推理面板
|
||
render_reasoning_panel(run_id=run_id, run=run),
|
||
# 渲染回复正文
|
||
render_assistant_content(run=run),
|
||
width="min(100%, 50em)", # 最大宽度:父级元素最大宽度和50em中较小值
|
||
margin_x="auto", # 水平外边距:自动调整
|
||
key=run_id,
|
||
)
|
||
|
||
|
||
def render_welcome() -> rx.Component:
|
||
"""
|
||
渲染欢迎信息
|
||
:return: Component
|
||
"""
|
||
return rx.center(
|
||
rx.vstack(
|
||
rx.spacer(),
|
||
# 智能体图标和名称
|
||
rx.hstack(
|
||
rx.icon("info", size=18),
|
||
rx.text("智能体"),
|
||
),
|
||
# 预设用户提示词
|
||
rx.box(
|
||
rx.vstack(
|
||
# 标题
|
||
rx.text("猜你想问"),
|
||
rx.hstack(
|
||
rx.button("你可以帮我做什么"),
|
||
),
|
||
)
|
||
),
|
||
spacing="4",
|
||
width="min(100%, 40em)",
|
||
padding_y="40px",
|
||
),
|
||
border="none",
|
||
)
|
||
|
||
|
||
def render_conversation() -> rx.Component:
|
||
"""
|
||
渲染对话,包括若干次运行
|
||
:return: Component
|
||
"""
|
||
# 运行字典
|
||
runs = ConversationState.get_runs
|
||
|
||
# 若运行字典为空则渲染欢迎信息,否则遍历渲染运行
|
||
return rx.auto_scroll(
|
||
rx.cond(
|
||
not runs,
|
||
# 渲染欢迎信息
|
||
render_welcome(),
|
||
rx.foreach(
|
||
runs,
|
||
lambda run_id, run: render_run(run_id=run_id, run=run),
|
||
),
|
||
),
|
||
flex="1",
|
||
padding="8px",
|
||
overflow_y="auto",
|
||
)
|
||
|
||
|
||
def render_custom_input_box() -> rx.Component:
|
||
"""
|
||
渲染自定义输入框
|
||
"""
|
||
return rx.form(
|
||
rx.box(
|
||
rx.vstack(
|
||
# 输入区域
|
||
rx.input(
|
||
name="user_prompt",
|
||
placeholder="发消息...",
|
||
flex="auto",
|
||
border="none", # 外框边线
|
||
outline="none", # 高亮轮廓线
|
||
padding_bottom="8px",
|
||
),
|
||
# 操作区域,暂仅包含发送按钮
|
||
rx.hstack(
|
||
rx.spacer(), # 占位符
|
||
# 发送按钮
|
||
rx.button(
|
||
rx.icon("arrow-up", size=18),
|
||
color_scheme="blue",
|
||
radius="full",
|
||
width="36px",
|
||
height="36px",
|
||
padding="0",
|
||
type="submit",
|
||
loading=ConversationState.get_running_status,
|
||
disabled=ConversationState.get_running_status,
|
||
),
|
||
width="100%",
|
||
),
|
||
spacing="8",
|
||
padding_x="12px",
|
||
padding_y="12px",
|
||
),
|
||
border=f"1px solid {rx.color('mauve', 4)}",
|
||
radius="large",
|
||
background_color="white",
|
||
),
|
||
max_width="50em", # 最大宽度
|
||
margin="0 auto", # 水平居中
|
||
align_items="center", # 子元素垂直居中
|
||
spacing="0", # 子元素间距
|
||
)
|
||
|
||
|
||
def render_input_box() -> rx.Component:
|
||
"""
|
||
渲染输入框
|
||
"""
|
||
return rx.center(
|
||
rx.vstack(
|
||
render_custom_input_box(), # 渲染自定义输入框
|
||
# 底部文案
|
||
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")
|