Python/智能体/application/components/conversation.py

385 lines
13 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 Run, Reasoning, Conversation
from application.state.conversation import ConversationState
from application.state.create_conversation_modal import CreateConversationModalState
def render_delete_conversation_component(
conversation_id: str,
):
"""
渲染删除对话组件
:param conversation_id: 对话唯一标识
:return: 删除对话组件
"""
return rx.popover.root(
rx.popover.trigger(
rx.button(
rx.icon("ellipsis", size=16),
variant="ghost",
padding="4px",
height="auto",
)
),
rx.popover.content(
rx.popover.close(
rx.button(
"删除",
color_scheme="red",
variant="ghost",
width="100%",
justify_content="center",
on_click=lambda: ConversationState.delete_conversation(
conversation_id
),
)
),
padding="4px",
),
open_delay=0,
)
def render_conversation_item_component(
conversation_id: str, conversation: Conversation
):
"""
渲染对话项组件
"""
# 对话描述
description = conversation.description
# 若当前对话唯一标识非指定对话唯一标识则正常渲染,否则高亮渲染
return rx.cond(
ConversationState.conversation_id != conversation_id,
# 正常渲染
rx.button(
rx.hstack(
rx.text(
description,
flex=1,
overflow="hidden",
text_overflow="ellipsis",
white_space="nowrap",
text_align="left",
),
render_delete_conversation_component(conversation_id),
align_items="center",
width="100%",
spacing="2",
),
width="100%",
justify_content="flex-start",
padding_x="12px",
padding_y="10px",
variant="surface",
color_scheme="blue",
on_click=lambda: ConversationState.switch_conversation(conversation_id),
),
# 高亮渲染
rx.button(
rx.hstack(
rx.text(
description,
flex=1,
overflow="hidden",
text_overflow="ellipsis",
white_space="nowrap",
text_align="left",
),
render_delete_conversation_component(conversation_id),
align_items="center",
width="100%",
spacing="2",
),
width="100%",
justify_content="flex-start",
padding_x="12px",
padding_y="10px",
variant="soft",
color_scheme="blue",
),
)
def render_reasoning_item_component(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_run_component(run_id: str, run: Run) -> rx.Component:
"""
渲染运行组件
:param run: 运行,包含用户提示词、推理字典和回复正文
:return: Component
"""
# 推理字典
reasonings = run.reasonings
# 推理状态
is_reasoning = run.is_reasoning
# 推理面板展开状态
is_reasoning_panel_open = run.is_reasoning_panel_open
return rx.box(
# 渲染用户提示词组件
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",
),
# 渲染推理面板组件
rx.box(
# 标题栏
rx.hstack(
# 若推理状态为正在推理则渲染正在推理,否则渲染推理完成
rx.cond(
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 i, _: render_reasoning_item_component(i[0], i[1]),
),
rx.fragment(),
),
text_align="left",
width="100%",
margin_bottom="8px",
),
# 渲染回复正文组件,若正在推理则不渲染,否则渲染回复正文组件
rx.cond(
is_reasoning,
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", # 圆角
),
),
),
width="min(100%, 50em)", # 最大宽度父级元素最大宽度和50em中较小值
margin_x="auto", # 水平外边距:自动调整
key=run_id,
)
def render_conversation_component() -> rx.Component:
"""
渲染对话组件,包括若干次运行
:return: Component
"""
# 运行字典
runs = ConversationState.get_runs
# 若运行字典为空则渲染空智能体图标、名称和预设用户提示词,否则遍历渲染运行组件
return rx.auto_scroll(
rx.cond(
ConversationState.is_runs_empty,
# 渲染空运行字典组件
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",
),
# 遍历渲染运行组件
rx.foreach(
runs,
lambda i, _: render_run_component(i[0], i[1]),
),
),
flex="1",
padding="8px",
overflow_y="auto",
)
def render_input_component() -> rx.Component:
"""
渲染输入和其它组件
"""
return rx.center(
rx.vstack(
# 渲染创建对话组件
rx.hstack(
rx.spacer(), # 占位符
rx.dialog.root(
# 触发事件:点击创建对话图标,支持鼠标悬停提示
rx.dialog.trigger(
rx.box(rx.tooltip(rx.icon("circle-plus"), content="创建对话"))
),
# 内容层
rx.dialog.content(
rx.form(
rx.hstack(
rx.input(
name="description",
placeholder="请输入对话描述",
flex="auto",
min_width="20ch",
),
rx.button("创建"),
spacing="2",
wrap="wrap",
width="100%",
),
on_submit=ConversationState.create_conversation,
),
background_color=rx.color("mauve", 1),
),
open=CreateConversationModalState.is_open,
on_open_change=CreateConversationModalState.toggle,
),
width="100%",
),
# 渲染自定义输入组件
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", # 子元素间距
on_submit=ConversationState.run,
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%",
)