diff --git a/agent/alembic/versions/a7e21164c990_.py b/agent/alembic/versions/54a15eecb10a_.py similarity index 94% rename from agent/alembic/versions/a7e21164c990_.py rename to agent/alembic/versions/54a15eecb10a_.py index 4607a15..ecc0390 100644 --- a/agent/alembic/versions/a7e21164c990_.py +++ b/agent/alembic/versions/54a15eecb10a_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: a7e21164c990 +Revision ID: 54a15eecb10a Revises: -Create Date: 2026-07-07 15:00:23.572644 +Create Date: 2026-07-10 14:17:15.523102 """ from typing import Sequence, Union @@ -12,7 +12,7 @@ import sqlalchemy as sa import sqlmodel # revision identifiers, used by Alembic. -revision: str = 'a7e21164c990' +revision: str = '54a15eecb10a' 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 7667e6e..f414748 100644 --- a/agent/application/application.py +++ b/agent/application/application.py @@ -21,6 +21,7 @@ app = rx.App( "box_sizing": "border-box", }, "--devui-base-bg": "#ffffff", + "--devui-global-bg": "#f6f6f8", "--mc-global-bg": "linear-gradient(to bottom, #D0C9FF 0%, #E6D6F0 8%, #F1DBEA 12%, #C8DCFB 40%, #ABC6F6 60%, #87AEFE 90%)", "--mc-box-shadow": "rgba(25, 25, 25, .06)", "--devui-shadow-length-connected-overlay": "0px 0px 12px 0px rgba(0, 0, 0, 0.1)", @@ -40,7 +41,8 @@ app = rx.App( "--devui-primary": "#5e7ce0", "--devui-brand": "#5e7ce0", "--devui-brand-hover": "#7693f5", - } # 自定义主题颜色 + "--devui-dividing-line": "#f2f2f3", + }, # 自定义主题颜色 ) # 此处变量名需使用 app ,具体原因目前尚不清楚 # 注册首页路由 app.add_page(component=render_index_page, route="/") diff --git a/agent/application/components/conversation.py b/agent/application/components/conversation.py index aa8fa1a..dd8a873 100644 --- a/agent/application/components/conversation.py +++ b/agent/application/components/conversation.py @@ -19,7 +19,7 @@ def render_conversation_history_item( :return: Component """ - def render_more_button( + def more_button( conversation_id: str, ) -> rx.Component: """ @@ -105,7 +105,7 @@ def render_conversation_history_item( ), # 渲染更多按钮 rx.box( - render_more_button(conversation_id=conversation_id), + more_button(conversation_id=conversation_id), min_width="14px", cursor="pointer", # 鼠标光标显示为手指 style={ @@ -180,169 +180,262 @@ def render_reasoning_item_component(reasoning_id: int, reasoning: Reasoning): ) -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_box() -> rx.Component: """ 渲染对话框 :return: Component """ + + def item(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.vstack( + rx.hstack( + rx.spacer(), + rx.vstack( + # 用户问题 + rx.markdown( + run.user_prompt, + max_width="600px", + padding="12px 16px", + background_color="var(--devui-global-bg)", + border_radius="12px", + word_wrap="break-word", + word_break="break-all", + 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)", + ), + padding="4px", + border_radius="4px", + cursor="pointer", + ), + width="100%", + margin_top="8px", + ), + ), + # 用户身份标识 + rx.avatar( + src="/user_avatar.svg", + align_self="flex-start", + width="32px", + height="32px", + border_radius="100%", + border="none", + ), + width="100%", + margin_top="8px", + align_items="flex-start", + gap="4px", + font_size="var(--devui-font-size)", + ), + # 智能体回答 + rx.hstack( + # 智能体身份标识 + rx.avatar( + src="/logo.svg", + align_self="flex-start", + width="32px", + height="32px", + border_radius="100%", + border="none", + ), + rx.box( + rx.vstack( + # 标题栏 + rx.cond( + is_reasoning, + rx.hstack( + rx.icon("loader"), + rx.text("推理中..."), + rx.icon("chevron-down"), + ), + rx.hstack( + rx.icon("check"), + rx.text("已推理"), + rx.icon("chevron-up"), + ), + ), + + ) + ), + # 标题栏 + 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="100%", + key=run_id, + ) + # 运行字典 runs = ConversationState.get_runs - # 若运行字典为空则渲染品牌图标和名称、预设用户提示词,否则遍历渲染运行项 - return rx.vstack( - rx.cond( - runs.length() == 0, + # 若运行字典为空则渲染品牌图标、名称和介绍、预设用户提示词等,否则遍历渲染运行项 + return rx.cond( + runs.length() == 0, + rx.vstack( rx.vstack( - # 智能体图标和名称 - rx.hstack( - rx.image( - src="/logo.png", - width="64px", - height="64px", - object_fit="contain", # 等比缩放 + # 品牌图标、名称和介绍 + rx.vstack( + rx.hstack( + rx.image( + src="/logo.png", + width="64px", + height="64px", + object_fit="contain", + ), + rx.box( + "棱镜球", + font_size="32px", + font_weight="700", + letter_spacing="1px", + ), + display="flex", + align_items="center", + gap="8px", ), - rx.box( - "Gluballu", - font_size="32px", - font_weight="700", - letter_spacing="1px", + rx.vstack( + rx.text( + "产品汪的棱镜球,精准捕捉各类业务需求", + width="100%", + text_align="center", + ), + rx.text( + "可帮助检索业务资料,协助生成产品方案、输出 PRD 、流程图和原型图等", + width="100%", + text_align="center", + ), + width="100%", + line_height="1.5", + font_size="var(--devui-font-size)", ), display="flex", - margin_bottom="12px", + flex_direction="column", + width="100%", align_items="center", - gap="8px", + gap="12px", + color="var(--devui-text)", ), - rx.box( - "Gluballu 可辅助产品检索业务资料、开展产品设计与撰写产品方案等。", - line_height="1.5", - ), - # 预设用户提示词 - rx.box( - rx.vstack( - # 标题 - rx.text("猜你想问"), - rx.hstack( - rx.button("你可以帮我做什么"), + # 猜你想问 + rx.vstack( + rx.hstack( + rx.text( + "猜你想问", + line_height="24px", + font_size="16px", + font_weight="700", ), - ) + display="flex", + width="100%", + justify_content="space-between", + align_items="center", + margin_bottom="16px", + ), + rx.hstack( + rx.box( + "需求梳理", + padding="10px 16px", + background_color="var(--devui-dividing-line)", + border_radius="var(--devui-border-radius-full)", + font_size="var(--devui-font-size)", + color="var(--devui-aide-text)", + cursor="pointer", + ), + display="flex", + flex_wrap="wrap", + align_items="center", + gap="12px", + ), + width="100%", + padding="24px", + background_color="var(--devui-base-bg)", + border_radius="24px", ), + display="flex", + flex_direction="column", + width="100%", + min_height="0", + margin="auto 0", + align_items="center", + gap="24px", color="var(--devui-text)", ), - # 遍历渲染运行项 + display="flex", + flex="1", + flex_direction="column", + width="100%", + max_width="1200px", + padding="0 12px", + justify_content="flex-start", + gap="24px", + overflow="auto", + style={ + "&::-webkit-scrollbar": { + "display": "none", + }, + }, + ), + # 遍历渲染对话项 + rx.vstack( rx.auto_scroll( rx.foreach( runs, - lambda i, _: render_run_component(i[0], i[1]), + lambda i, _: item(i[0], i[1]), ), + width="100%", + padding="0 12px", ), + flex="1", + width="100%", + max_width="1200px", + margin_x="auto", + padding_top="20px", + justify_content="flex-start", + overflow_x="hidden", + overflow_y="auto", ), - display="flex", - flex="1", - flex_direction="column", - width="100%", - max_height="1200px", - padding="0 12px", - justify_content="flex-start", - gap="24px", - overflow="auto", - style={ - "&::-webkit-scrollbar": { - "display": "none", - }, - }, ) @@ -352,6 +445,17 @@ def render_create_conversation_button() -> rx.Component: :return: Component """ return rx.hstack( + rx.el.style( + """ + .rt-TooltipArrow polygon { + fill: #ffffff !important; + } + .rt-TooltipText { + color: var(--devui-text) !important; + opacity: 1 !important; + } + """ + ), rx.spacer(), # 占位符 rx.dialog.root( # 触发事件:点击图标 @@ -369,8 +473,9 @@ def render_create_conversation_button() -> rx.Component: }, ), content="新建对话", - background_color="var(--devui-base-bg)", + background_color="#ffffff", box_shadow="0 2px 12px 0 rgba(37, 43, 58, .24)", + color="#252b3a", side="top", side_offset=9, ), @@ -430,7 +535,7 @@ def render_input_box() -> rx.Component: rx.hstack( rx.text_area( name="user_prompt", - placeholder="请输入您的问题,并按Enter发送,按Shift+Enter换行", + placeholder="请输入您的问题,按Enter换行", vertical_align="middle", width="100%", height="64px", diff --git a/agent/application/components/frame.py b/agent/application/components/frame.py index 5ae4da6..f678ead 100644 --- a/agent/application/components/frame.py +++ b/agent/application/components/frame.py @@ -79,7 +79,7 @@ def render_sidebar() -> rx.Component: object_fit="contain", # 等比缩放 ), rx.text( - "Gluballu", + "棱镜球", line_height="20px", # 行高 font_size="11px", font_weight="700", diff --git a/agent/application/pages/index.py b/agent/application/pages/index.py index a6d1ec8..3668edb 100644 --- a/agent/application/pages/index.py +++ b/agent/application/pages/index.py @@ -15,6 +15,7 @@ def render_index_page() -> rx.Component: """ 渲染根页面 """ + return rx.hstack( # 渲染侧边栏 render_sidebar(), diff --git a/agent/assets/logo.png b/agent/assets/logo.png index c9b75a7..35822f6 100644 Binary files a/agent/assets/logo.png and b/agent/assets/logo.png differ diff --git a/agent/assets/user_avatar.svg b/agent/assets/user_avatar.svg new file mode 100644 index 0000000..f6ce925 --- /dev/null +++ b/agent/assets/user_avatar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/agent/reflex.lock/bun.lock b/agent/reflex.lock/bun.lock index e858491..6b697d4 100644 --- a/agent/reflex.lock/bun.lock +++ b/agent/reflex.lock/bun.lock @@ -430,7 +430,7 @@ "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "https://registry.npmmirror.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], - "@types/hast": ["@types/hast@3.0.4", "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + "@types/hast": ["@types/hast@3.0.5", "https://registry.npmmirror.com/@types/hast/-/hast-3.0.5.tgz", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="], "@types/katex": ["@types/katex@0.16.8", "https://registry.npmmirror.com/@types/katex/-/katex-0.16.8.tgz", {}, "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg=="], @@ -446,7 +446,7 @@ "@types/unist": ["@types/unist@3.0.3", "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - "@ungap/structured-clone": ["@ungap/structured-clone@1.3.1", "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", {}, "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ=="], + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.2", "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", {}, "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA=="], "arg": ["arg@5.0.2", "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],