This commit is contained in:
liubiren 2026-07-10 20:15:09 +08:00
parent 9bd365a44e
commit 524290c2b7
8 changed files with 259 additions and 150 deletions

View File

@ -1,8 +1,8 @@
"""empty message """empty message
Revision ID: a7e21164c990 Revision ID: 54a15eecb10a
Revises: Revises:
Create Date: 2026-07-07 15:00:23.572644 Create Date: 2026-07-10 14:17:15.523102
""" """
from typing import Sequence, Union from typing import Sequence, Union
@ -12,7 +12,7 @@ import sqlalchemy as sa
import sqlmodel import sqlmodel
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = 'a7e21164c990' revision: str = '54a15eecb10a'
down_revision: Union[str, Sequence[str], None] = None down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None

View File

@ -21,6 +21,7 @@ app = rx.App(
"box_sizing": "border-box", "box_sizing": "border-box",
}, },
"--devui-base-bg": "#ffffff", "--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-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)", "--mc-box-shadow": "rgba(25, 25, 25, .06)",
"--devui-shadow-length-connected-overlay": "0px 0px 12px 0px rgba(0, 0, 0, 0.1)", "--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-primary": "#5e7ce0",
"--devui-brand": "#5e7ce0", "--devui-brand": "#5e7ce0",
"--devui-brand-hover": "#7693f5", "--devui-brand-hover": "#7693f5",
} # 自定义主题颜色 "--devui-dividing-line": "#f2f2f3",
}, # 自定义主题颜色
) # 此处变量名需使用 app ,具体原因目前尚不清楚 ) # 此处变量名需使用 app ,具体原因目前尚不清楚
# 注册首页路由 # 注册首页路由
app.add_page(component=render_index_page, route="/") app.add_page(component=render_index_page, route="/")

View File

@ -19,7 +19,7 @@ def render_conversation_history_item(
:return: Component :return: Component
""" """
def render_more_button( def more_button(
conversation_id: str, conversation_id: str,
) -> rx.Component: ) -> rx.Component:
""" """
@ -105,7 +105,7 @@ def render_conversation_history_item(
), ),
# 渲染更多按钮 # 渲染更多按钮
rx.box( rx.box(
render_more_button(conversation_id=conversation_id), more_button(conversation_id=conversation_id),
min_width="14px", min_width="14px",
cursor="pointer", # 鼠标光标显示为手指 cursor="pointer", # 鼠标光标显示为手指
style={ style={
@ -180,10 +180,16 @@ def render_reasoning_item_component(reasoning_id: int, reasoning: Reasoning):
) )
def render_run_component(run_id: str, run: Run) -> rx.Component: def render_conversation_box() -> rx.Component:
""" """
渲染运行组件 渲染对话框
:param run: 运行包含用户提示词推理字典和回复正文 :return: Component
"""
def item(run_id: str, run: Run) -> rx.Component:
"""
对话项
:param run: 对话实例
:return: Component :return: Component
""" """
# 推理字典 # 推理字典
@ -193,57 +199,89 @@ def render_run_component(run_id: str, run: Run) -> rx.Component:
# 推理面板展开状态 # 推理面板展开状态
is_reasoning_panel_open = run.is_reasoning_panel_open is_reasoning_panel_open = run.is_reasoning_panel_open
return rx.box( return rx.vstack(
# 渲染用户提示词组件 rx.hstack(
rx.box( rx.spacer(),
rx.vstack(
# 用户问题
rx.markdown( rx.markdown(
run.user_prompt, run.user_prompt,
color=rx.color("gray", 12), # 文字颜色 max_width="600px",
background_color=rx.color("gray", 2), # 背景颜色 padding="12px 16px",
display="inline-block", # 布局模式:自适应文本宽度 background_color="var(--devui-global-bg)",
max_width="85%", # 最大宽度 border_radius="12px",
padding_x="1.25em", # 水平内边距 word_wrap="break-word",
padding_y="0.5em", # 垂直内边距 word_break="break-all",
margin_left="auto", # 左侧外边距自动调整 style={
margin_bottom="8px", # 底部外边距 "& p.rt-Text": {
border_radius="12px", # 圆角 "padding": "0",
"margin": "0 !important",
},
},
), ),
text_align="right", # 再次发送按钮
width="100%",
margin_bottom="8px",
),
# 渲染推理面板组件
rx.box(
# 标题栏
rx.hstack( 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( rx.cond(
is_reasoning, is_reasoning,
rx.badge("正在推理"), rx.hstack(
rx.badge("推理完成"), rx.icon("loader"),
rx.text("推理中..."),
rx.icon("chevron-down"),
), ),
rx.spacer(), rx.hstack(
# 若推理面板展开则渲染上箭头,否则渲染下箭头 rx.icon("check"),
rx.icon( rx.text("已推理"),
rx.cond(is_reasoning_panel_open, "chevron_up", "chevron_down"), rx.icon("chevron-up"),
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", text_align="left",
width="100%", width="100%",
margin_bottom="8px", margin_bottom="8px",
@ -267,73 +305,108 @@ def render_run_component(run_id: str, run: Run) -> rx.Component:
), ),
), ),
), ),
width="min(100%, 50em)", # 最大宽度父级元素最大宽度和50em中较小值 width="100%",
margin_x="auto", # 水平外边距:自动调整
key=run_id, key=run_id,
) )
def render_conversation_box() -> rx.Component:
"""
渲染对话框
:return: Component
"""
# 运行字典 # 运行字典
runs = ConversationState.get_runs runs = ConversationState.get_runs
# 若运行字典为空则渲染品牌图标和名称、预设用户提示词,否则遍历渲染运行项 # 若运行字典为空则渲染品牌图标、名称和介绍、预设用户提示词等,否则遍历渲染运行项
return rx.vstack( return rx.cond(
rx.cond(
runs.length() == 0, runs.length() == 0,
rx.vstack( rx.vstack(
# 智能体图标和名称 rx.vstack(
# 品牌图标、名称和介绍
rx.vstack(
rx.hstack( rx.hstack(
rx.image( rx.image(
src="/logo.png", src="/logo.png",
width="64px", width="64px",
height="64px", height="64px",
object_fit="contain", # 等比缩放 object_fit="contain",
), ),
rx.box( rx.box(
"Gluballu", "棱镜球",
font_size="32px", font_size="32px",
font_weight="700", font_weight="700",
letter_spacing="1px", letter_spacing="1px",
), ),
display="flex", display="flex",
margin_bottom="12px",
align_items="center", align_items="center",
gap="8px", gap="8px",
), ),
rx.box(
"Gluballu 可辅助产品检索业务资料、开展产品设计与撰写产品方案等。",
line_height="1.5",
),
# 预设用户提示词
rx.box(
rx.vstack( rx.vstack(
# 标题 rx.text(
rx.text("猜你想问"), "产品汪的棱镜球,精准捕捉各类业务需求",
rx.hstack( width="100%",
rx.button("你可以帮我做什么"), 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",
flex_direction="column",
width="100%",
align_items="center",
gap="12px",
color="var(--devui-text)", color="var(--devui-text)",
), ),
# 遍历渲染运行项 # 猜你想问
rx.auto_scroll( rx.vstack(
rx.foreach( rx.hstack(
runs, rx.text(
lambda i, _: render_run_component(i[0], i[1]), "猜你想问",
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", display="flex",
flex="1", flex="1",
flex_direction="column", flex_direction="column",
width="100%", width="100%",
max_height="1200px", max_width="1200px",
padding="0 12px", padding="0 12px",
justify_content="flex-start", justify_content="flex-start",
gap="24px", gap="24px",
@ -343,6 +416,26 @@ def render_conversation_box() -> rx.Component:
"display": "none", "display": "none",
}, },
}, },
),
# 遍历渲染对话项
rx.vstack(
rx.auto_scroll(
rx.foreach(
runs,
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",
),
) )
@ -352,6 +445,17 @@ def render_create_conversation_button() -> rx.Component:
:return: Component :return: Component
""" """
return rx.hstack( 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.spacer(), # 占位符
rx.dialog.root( rx.dialog.root(
# 触发事件:点击图标 # 触发事件:点击图标
@ -369,8 +473,9 @@ def render_create_conversation_button() -> rx.Component:
}, },
), ),
content="新建对话", content="新建对话",
background_color="var(--devui-base-bg)", background_color="#ffffff",
box_shadow="0 2px 12px 0 rgba(37, 43, 58, .24)", box_shadow="0 2px 12px 0 rgba(37, 43, 58, .24)",
color="#252b3a",
side="top", side="top",
side_offset=9, side_offset=9,
), ),
@ -430,7 +535,7 @@ def render_input_box() -> rx.Component:
rx.hstack( rx.hstack(
rx.text_area( rx.text_area(
name="user_prompt", name="user_prompt",
placeholder="请输入您的问题,Enter发送按Shift+Enter换行", placeholder="请输入您的问题,按Enter换行",
vertical_align="middle", vertical_align="middle",
width="100%", width="100%",
height="64px", height="64px",

View File

@ -79,7 +79,7 @@ def render_sidebar() -> rx.Component:
object_fit="contain", # 等比缩放 object_fit="contain", # 等比缩放
), ),
rx.text( rx.text(
"Gluballu", "棱镜球",
line_height="20px", # 行高 line_height="20px", # 行高
font_size="11px", font_size="11px",
font_weight="700", font_weight="700",

View File

@ -15,6 +15,7 @@ def render_index_page() -> rx.Component:
""" """
渲染根页面 渲染根页面
""" """
return rx.hstack( return rx.hstack(
# 渲染侧边栏 # 渲染侧边栏
render_sidebar(), render_sidebar(),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 70 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -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/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=="], "@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=="], "@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=="], "arg": ["arg@5.0.2", "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],