From d1aac8762a97f514b315c670404dbe8ce2f66f62 Mon Sep 17 00:00:00 2001 From: liubiren Date: Wed, 15 Jul 2026 19:59:42 +0800 Subject: [PATCH] 1 --- .../{6cd382f48060_.py => 51f049b860bb_.py} | 6 +- agent/application/application.py | 12 +- agent/application/components/typography.py | 5 + agent/application/pages/conversation.py | 163 +++++++++++------- agent/application/states/conversation.py | 4 +- agent/database.db | Bin 0 -> 73728 bytes 6 files changed, 124 insertions(+), 66 deletions(-) rename agent/alembic/versions/{6cd382f48060_.py => 51f049b860bb_.py} (96%) create mode 100644 agent/application/components/typography.py create mode 100644 agent/database.db diff --git a/agent/alembic/versions/6cd382f48060_.py b/agent/alembic/versions/51f049b860bb_.py similarity index 96% rename from agent/alembic/versions/6cd382f48060_.py rename to agent/alembic/versions/51f049b860bb_.py index 126ebd7..0c62370 100644 --- a/agent/alembic/versions/6cd382f48060_.py +++ b/agent/alembic/versions/51f049b860bb_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 6cd382f48060 +Revision ID: 51f049b860bb Revises: -Create Date: 2026-07-13 17:24:40.090382 +Create Date: 2026-07-13 22:23:49.155469 """ from typing import Sequence, Union @@ -12,7 +12,7 @@ import sqlalchemy as sa import sqlmodel # revision identifiers, used by Alembic. -revision: str = '6cd382f48060' +revision: str = '51f049b860bb' down_revision: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None diff --git a/agent/application/application.py b/agent/application/application.py index 7d9e083..8f8794f 100644 --- a/agent/application/application.py +++ b/agent/application/application.py @@ -20,7 +20,9 @@ app = rx.App( "*::before, *::after": { "box_sizing": "border-box", }, - "--prismui-background-color": "#ffffff", + "--prismui-background-color-1": "#ffffff", + "--prismui-background-color-2": "rgb(235, 235, 235)", + "--prismui-background-color-3": "#f6f6f8", "--mc-float-block-shadow": "rgba(213, 213, 213, 0.25)", "--devui-global-bg": "#f6f6f8", "--mc-global-bg": "linear-gradient(to bottom, #D0C9FF 0%, #E6D6F0 8%, #F1DBEA 12%, #C8DCFB 40%, #ABC6F6 60%, #87AEFE 90%)", @@ -32,10 +34,18 @@ app = rx.App( "--prismui-border-radius": "8px", "--devui-border-radius-full": "100px", "--font-family": "HuaweiFont,Helvetica,Arial,PingFangSC-Regular,Hiragino Sans GB,Microsoft YaHei,微软雅黑,Microsoft JhengHei", + "--prismui-font-size": "14px", + "--prismui-font-size-1": "14px", + "--prismui-font-size-2": "12px", "--prismui-font-size-sm": "12px", + "--prismui-font-weight-1": "700", "--devui-font-size": "14px", "--devui-font-size-lg": "14px", "--prismui-color-text": "#252b3a", + "--prismui-color-text-light": "#babbc0", + "--prismui-color-1": "#252b3a", + "--prismui-color-2": "#575d6c", + "--prismui-color-3": "#babbc0", "--devui-text-weak": "#575d6c", "--devui-light-text": "#ffffff", "--placeholder": "#babbc0", diff --git a/agent/application/components/typography.py b/agent/application/components/typography.py new file mode 100644 index 0000000..0a4cf1d --- /dev/null +++ b/agent/application/components/typography.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +""" +排版相关组件 +""" +import reflex as rx diff --git a/agent/application/pages/conversation.py b/agent/application/pages/conversation.py index 96a2698..95fb7f2 100644 --- a/agent/application/pages/conversation.py +++ b/agent/application/pages/conversation.py @@ -23,7 +23,7 @@ def render_conversation_history_item( """ # 会话激活状态 - is_conversation_actived = conversation_id == ConversationState.conversation_id + is_actived = conversation_id == ConversationState.conversation_id return rx.box( rx.hstack( @@ -105,10 +105,10 @@ def render_conversation_history_item( cursor="pointer", style={ "opacity": rx.cond( - is_conversation_actived, "1", "0" + is_actived, "1", "0" ), # 若当前会话已激活则不透明,否则透明 "pointer_events": rx.cond( - is_conversation_actived, "auto", "none" + is_actived, "auto", "none" ), # 若当前会话已激活则可点击,否则不可点击 "transition": "opacity 0.18s ease", }, @@ -127,13 +127,11 @@ def render_conversation_history_item( cursor="pointer", style={ "background": rx.cond( - is_conversation_actived, + is_actived, "linear-gradient(to right, #f3efff, #f3efff33, #e2f1fd33, #e2f1fd)", "var(--devui-base-bg)", ), - "box_shadow": rx.cond( - is_conversation_actived, "2px 2px 8px #e9e9e9", "none" - ), + "box_shadow": rx.cond(is_actived, "2px 2px 8px #e9e9e9", "none"), "&:hover": { "background": "linear-gradient(to right, #f3efff, #f3efff33, #e2f1fd33, #e2f1fd)", "box_shadow": "2px 2px 8px #e9e9e9", @@ -313,28 +311,33 @@ def render_greeting() -> rx.Component: ) -def render_reasoning_thought_node(thought_node_id: int, thought_node: ThoughtNode): +def render_thought_node(thought_node_id: int, thought_node: ThoughtNode): """ 渲染思考节点 :param thought_node_id: 思考节点唯一标识 :param thought_node: 思考节点实例 :return: Component """ - return rx.hstack( - rx.vstack( - rx.box( - width="8px", - height="8px", - background_color=rx.color("blue", 7), + return rx.vstack( + rx.match( + thought_node.kind, + ( + "thinking", + rx.markdown( + thought_node.content, + component_map={ + "p": lambda text: rx.text( + text, + color="var(--prismui-color-text-light)", + margin="0 0 8px", + font_size="var(--prismui-font-size-sm)", + ) + }, + ), ), - rx.box(width="2px", flex=1, background_color=rx.color("blue", 3)), - align_items="center", - spacing="0", - height="100%", + rx.fragment(), ), - rx.markdown(thought_node.content, color=rx.color("gray", 11), padding_y="4px"), align_items="flex-start", - spacing="2", width="100%", key=thought_node_id, ) @@ -343,51 +346,51 @@ def render_reasoning_thought_node(thought_node_id: int, thought_node: ThoughtNod def render_dialog_history_item(dialog_id: str, dialog: Dialog) -> rx.Component: """ 渲染对话历史条目 - :param run: 对话实例 + :param dialog_id: 对话唯一标识 + :param dialog: 对话实例 :return: Component """ # 推理状态 is_thinking = dialog.is_thinking + # 思考折叠面板展开状态 + is_collapse_expanded = dialog.is_collapse_expanded # 思考节点 thought_nodes = dialog.thought_nodes - # 推理折叠面板展开状态 - is_reasoning_panel_open = dialog.is_collapse_expanded - return rx.vstack( - # 渲染问题和再次发送按钮 + # 渲染问题和操作栏 rx.hstack( rx.spacer(), rx.vstack( # 渲染问题 - rx.markdown( + rx.text( dialog.question, max_width="600px", padding="12px 16px", - background_color="var(--devui-global-bg)", + background_color="var(--prismui-background-color-3)", border_radius="12px", word_wrap="break-word", word_break="break-all", + white_space="pre-line", color="var(--prismui-color-text)", - style={ - "& p.rt-Text": { - "padding": "0", - "margin": "0 !important", - }, - }, ), - # 再次发送按钮 + # 操作栏 rx.hstack( rx.box( rx.icon( "rotate-ccw", width="14px", height="14px", - color="var(--devui-text-weak)", + color="var(--prismui-color-2)", ), padding="4px", border_radius="4px", cursor="pointer", + style={ + "_hover": { + "background_color": "var(--prismui-background-color-2)", + } + }, ), width="100%", margin_top="8px", @@ -399,32 +402,72 @@ def render_dialog_history_item(dialog_id: str, dialog: Dialog) -> rx.Component: gap="4px", font_size="var(--devui-font-size)", ), - # 渲染智能体思考链面板和回答 + # 渲染思考折叠面板 rx.vstack( - - rx.auto_scroll( - rx.foreach( - thought_nodes, - lambda i, _: render_reasoning_thought_node(i[0], i[1]), + # 标题栏 + rx.box( + rx.hstack( + rx.text( + rx.cond(is_thinking, "思考中", "思考完成"), + color="var(--prismui-color-3)", + font_size="var(--prismui-font-size-2)", + ), + rx.icon( + "chevron-right", + width="14px", + height="14px", + color="var(--prismui-color-3)", + style={ + "transition": "transform 0.2s ease-in-out", + "transform": rx.cond( + is_collapse_expanded, "rotate(90deg)", "rotate(0deg)" + ), + }, + ), + align_items="center", + gap="4px", + margin_bottom="8px", + line_height="22px", ), - width="100%", - padding="0 12px", + cursor="pointer", + # 点击事件,展开/折叠思考折叠面板 + on_click=lambda: ConversationState.toggle_collapse(dialog_id), ), - # 渲染回答 - rx.markdown( - dialog.answer, - display="inline-block", - max_width="85%", - padding_x="1.25em", - padding_y="0.5em", - margin_left="auto", - margin_bottom="8px", - background_color=rx.color("gray", 2), - border_radius="12px", - color="var(--prismui-color-text)", + rx.cond( + is_collapse_expanded, + rx.auto_scroll( + rx.foreach( + thought_nodes, + lambda i, _: render_thought_node(i[0], i[1]), + ), + width="100%", + ), + rx.fragment(), ), text_align="left", width="100%", + margin_top="8px", + ), + # 渲染回答 + rx.markdown( + dialog.answer, + component_map={ + "p": lambda text: rx.text( + text, + word_wrap="break-word", + word_break="break-all", + white_space="pre-line", + line_height="24px", + font_size="var(--prismui-font-size-1)", + color="var(--prismui-color-1)", + ), + "strong": lambda text: rx.text( + text, + display="inline", + font_weight="var(--prismui-font-weight-1)", + ), + }, + width="100%", margin_bottom="8px", ), width="100%", @@ -470,7 +513,7 @@ def render_dialog_history() -> rx.Component: def render_create_conversation_button() -> rx.Component: """ - 渲染新建对话按钮 + 渲染新建会话按钮 :return: Component """ return rx.hstack( @@ -501,7 +544,7 @@ def render_create_conversation_button() -> rx.Component: } }, ), - content="新建对话", + content="新建会话", background_color="#ffffff", box_shadow="0 2px 12px 0 rgba(37, 43, 58, .24)", color="#252b3a", @@ -524,11 +567,11 @@ def render_create_conversation_button() -> rx.Component: rx.hstack( rx.input( name="description", - placeholder="请输入对话描述", + placeholder="请输入会话描述", flex="auto", min_width="20ch", ), - rx.button("创建"), + rx.button("新建"), spacing="2", wrap="wrap", width="100%", @@ -711,7 +754,7 @@ def render_conversation_history_collapse_button( z_index=99, width="16px", height="40px", - background="var(--prismui-background-color)", + background="var(--prismui-background-color-1)", box_shadow="2px 0 4px 0 var(--mc-float-block-shadow)", border_radius=rx.cond(is_collapse_expanded, "6px", "0 6px 6px 0"), transition="all 0.3s ease-in-out", diff --git a/agent/application/states/conversation.py b/agent/application/states/conversation.py index f38467f..62913c9 100644 --- a/agent/application/states/conversation.py +++ b/agent/application/states/conversation.py @@ -395,9 +395,9 @@ class ConversationState(rx.State): yield @rx.event - def toggle_reasoning_panel(self, dialog_id: str) -> None: + def toggle_collapse(self, dialog_id: str) -> None: """ - 展开/折叠指定运行唯一标识的推理面板 + 展开/折叠思考折叠面板 """ # 指定运行 dialog = self.conversations[self.conversation_id].dialogs[dialog_id] diff --git a/agent/database.db b/agent/database.db new file mode 100644 index 0000000000000000000000000000000000000000..3b965b3616184ce239a508a7952bb43a63e6872f GIT binary patch literal 73728 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 literal 0 HcmV?d00001