# -*- coding: utf-8 -*- """ 聊天相关组件 """ import reflex as rx from application.models import Type_, Message, Dialog from application.state import ChatState def render_input_bubble(input_: str) -> rx.Component: """ 渲染输入气泡 :param content: 输入内容 :return: Component """ return rx.markdown( input_, 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_collapse_panel(dialog_id: str, message: Message) -> rx.Component: """ 渲染折叠面板 :param dialog_id: 对话唯一标识 :param message: 消息实例 :return: Component """ return rx.box( rx.cond( message.is_open, rx.hstack( rx.text( message.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", on_click=ChatState.toggle_collapse_panel(dialog_id, message.id), padding_x="1em", padding_y="0.6em", background_color=rx.color("gray", 2), border_radius="8px", ), # 折叠面板打开 rx.hstack( rx.hstack( rx.text( loading_prefix, font_size="0.90rem", bold=True, color=rx.color("gray", 10), ), loading_dot_group(message.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, message.id), padding_x="1em", padding_y="0.6em", background_color=rx.color("gray", 2), border_radius="8px", ), # 折叠面板关闭 ), rx.cond(message.is_open, detail_box, rx.fragment()), width="100%", max_width="85%", margin_bottom="10px", key=message.id, cursor="pointer", ) def render_output_container(dialog_id: str, message: Message) -> rx.Component: """ 渲染输出容器 :param dialog_id: 对话唯一标识 :param message: 消息实例 :return: Component """ return rx.cond( message.type_ == Type_.TEXT, rx.markdown( message.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=message.id, ), render_collapse_panel(dialog_id=dialog_id, message=message), ) def render_dialog_container(dialog: Dialog) -> rx.Component: """ 渲染对话容器 :param dialog: 对话实例 :return: Component """ return rx.box( rx.box( render_input_bubble(input_=dialog.input_), text_align="right", width="100%", margin_bottom="8px", ), rx.box( rx.foreach( dialog.output, lambda message: render_output_container( dialog_id=dialog.id, message=message ), ), text_align="left", width="100%", margin_bottom="8px", ), width="min(100%, 50em)", # 最大宽度:父级元素最大宽度和50em中较小值 margin_x="auto", # 水平外边距:自动调整 key=dialog.id, ) def dialog_list_component() -> rx.Component: """ 对话列表组件 :return: Component """ return rx.auto_scroll( rx.foreach(ChatState.get_current_chat_dialogs, dialog_item_component), flex="1", padding="8px", overflow_y="auto", ) def input_component() -> rx.Component: """ 输入组件 """ return rx.center( rx.vstack( rx.form( rx.hstack( rx.input( name="input", 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")