This commit is contained in:
liubiren 2026-10-12 16:51:15 +08:00
parent 0f7709eb8e
commit bcd2616d11
13 changed files with 815 additions and 533 deletions

View File

@ -4,7 +4,7 @@
""" """
import reflex as rx import reflex as rx
from application.pages.index import index from application.pages.workbench import workbench
from application.workshop.database import connection_pool_init from application.workshop.database import connection_pool_init
@ -55,7 +55,7 @@ app = rx.App(
}, },
) # 此处变量名需使用 app ,具体原因目前尚不清楚 ) # 此处变量名需使用 app ,具体原因目前尚不清楚
# 添加页面:首页 # 添加页面:工作台
app.add_page(component=index(), route="/") app.add_page(component=workbench(), route="/")
# 注册声明周期任务:连接池初始化 # 注册声明周期任务:连接池初始化
app.register_lifespan_task(connection_pool_init) app.register_lifespan_task(connection_pool_init)

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from application.pages.conversation import conversation_page from application.pages.task import create_task_page
from application.pages.knowledge_base import knowledge_base_page from application.pages.knowledge_base import knowledge_base_page
from application.pages.login import login_page
__all__ = ["conversation_page", "knowledge_base_page"] __all__ = ["create_task_page", "knowledge_base_page", "login_page"]

View File

@ -1,218 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
首页 登录页面
""" """
import reflex as rx import reflex as rx
from application.pages import conversation_page, knowledge_base_page
from application.states import AuthState from application.states import AuthState
def login_page() -> rx.Component:
# 导航按钮字典
nav_buttons: dict = {
"conversation": {"src": "/conversation.svg", "text": "会话"},
"knowledge_base": {"src": "/knowledge_base.svg", "text": "知识库"},
}
"""
约定:
1 若组件仅用于当前页面则无需抽象为通用组件(application.components.*)
"""
def nav_button(
nav_button: str,
) -> rx.Component:
""" """
导航按钮 登录页面
:param nav_button: 指定的导航按钮
:return : Component
"""
# 导航按钮激活状态
is_actived = nav_button == AuthState.activated_nav_button
return rx.vstack(
# 导航按钮的图标
rx.image(
nav_buttons[nav_button]["src"],
width="36px",
height="36px",
object_fit="contain",
padding="6px",
background_color=rx.cond(
is_actived,
"var(--prismui-background-color-1)",
"transparent",
),
border_radius="var(--prismui-border-radius-3)",
box_shadow=rx.cond(is_actived, "var(--prismui-box-shadow-2)", "none"),
),
# 导航按钮的文本
rx.text(
nav_buttons[nav_button]["text"],
line_height="20px",
font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-1)",
),
align_items="center",
gap="4px",
cursor="pointer",
# 点击事件:设置激活导航按钮
on_click=AuthState.set_activated_nav_button(nav_button),
)
def settings_button():
"""
设置按钮
:return: Component
"""
return rx.box(
rx.hover_card.root(
# 触发事件:点击自定义按钮
rx.hover_card.trigger(
rx.box(
rx.icon(
"settings",
stroke_width=1.5,
width="20px",
height="20px",
color="var(--prismui-color-1)",
),
display="flex",
flex_flow="column",
justify_content="center",
align_items="center",
width="36px",
height="36px",
border_radius="var(--prismui-border-radius-3)",
style={
"&:hover": {
"background": "var(--prismui-background-color-5)",
}, # 鼠标悬停背景颜色
},
cursor="pointer",
)
),
# 悬停卡片
rx.hover_card.content(
rx.box(
rx.box(
transform="translateX(-50%) rotate(45deg)",
position="absolute",
top="calc(50% - 4px)",
left="0",
width="8px",
height="8px",
background_color="var(--prismui-background-color-1)",
),
rx.box(
"退出登录",
display="flex",
justify_content="center",
align_items="center",
width="100%",
height="24px",
line_height="16px",
border_radius="var(--prismui-border-radius-1)",
style={
"_hover": {
"background_color": "var(--prismui-background-color-6)",
},
},
# 点击事件:退出登录
on_click=lambda: AuthState.logout(),
),
cursor="pointer",
),
side="left",
side_offset=8,
position="relative",
align="center",
padding="4px 12px",
border_radius="var(--prismui-border-radius-1)",
box_shadow="var(--prismui-box-shadow-3)",
font_family="var(--prismui-font-family)",
overflow="visible",
style={
"font-size": "var(--prismui-font-size-1) !important",
"color": "var(--prismui-color-1) !important",
},
),
open=AuthState.is_settings_hover_card_open,
# 悬停卡片打开状态变化事件:设置设置悬停卡片打开状态
on_open_change=AuthState.set_settings_hover_card_open,
),
min_width="14px",
cursor="pointer",
)
def sidebar() -> rx.Component:
"""
侧边栏
:return: Component
"""
return rx.vstack(
rx.vstack(
rx.vstack(
# 品牌图标
rx.image(
src="/logo.png",
width="32px",
height="32px",
object_fit="contain",
),
# 品牌名称
rx.text(
"棱镜球",
line_height="20px",
font_size="calc(var(--prismui-font-size-1) - 1px)", # 较导航按钮标签弱化
font_weight="var(--prismui-font-weight-2)",
),
align_items="center",
gap="4px",
),
# 分隔线
rx.divider(
width="32px",
height="1px",
margin="16px 0",
background_color="var(--prismui-background-color-4)",
),
rx.vstack(
# 会话导航按钮
nav_button("conversation"),
# 知识库导航按钮
nav_button("knowledge_base"),
gap="8px",
),
align_items="center",
width="100%",
margin_top="12px",
),
rx.vstack(
rx.vstack(
# 设置按钮
settings_button(),
),
padding_bottom="16px",
gap="8px",
),
flex_shrink="0",
justify_content="space-between",
align_items="center",
width="60px",
min_width="60px",
max_width="60px",
height="100%",
)
def login_popup() -> rx.Component:
"""
登录弹窗
:return: Component :return: Component
""" """
@ -225,7 +21,7 @@ def login_popup() -> rx.Component:
margin_bottom="24px", margin_bottom="24px",
line_height="32px", line_height="32px",
font_size="var(--prismui-font-size-5)", font_size="var(--prismui-font-size-5)",
font_weight="var(--prismui-font-weight-3)", font_weight="var(--prismui-font-weight-4)",
), ),
# 邮箱输入框 # 邮箱输入框
rx.input( rx.input(
@ -423,35 +219,3 @@ def login_popup() -> rx.Component:
height="100vh", height="100vh",
backdrop_filter="blur(4px)", backdrop_filter="blur(4px)",
) )
def index() -> rx.Component:
"""
首页(默认显示会话页面)
"""
return rx.fragment(
rx.hstack(
# 侧边栏
sidebar(),
# 根据侧边栏状态中激活的导航按钮相应页面
rx.match(
AuthState.activated_nav_button,
# 知识库页面
("knowledge_base", knowledge_base_page()),
# 会话页面
conversation_page(),
),
width="100%",
height="100vh",
overflow="auto",
padding="8px 8px 8px 0",
background="var(--prismui-background-1)",
),
# 若用户唯一标识为空则登录窗
rx.cond(
AuthState.user_id,
rx.fragment(),
login_popup(),
),
)

View File

@ -4,28 +4,28 @@
""" """
import reflex as rx import reflex as rx
from application.states import AuthState, ConversationState from application.states import AuthState, TaskState
from application.states.models import ( from application.states.models import (
ConversationHistoryItem, TaskHistoryItem,
MessageHistoryItem, MessageHistoryItem,
MessageType, MessageType,
WorkflowType, WorkflowType,
) )
def conversation_history_item( def task_history_item(
item: ConversationHistoryItem, item: TaskHistoryItem,
) -> rx.Component: ) -> rx.Component:
""" """
会话历史项 任务历史项
:param item: 会话历史项 :param item: 任务历史项
:return: Component :return: Component
""" """
# 高亮 # 高亮
is_highlight = ( is_highlight = (
item.id == ConversationState.popover_shown_conversation_history_item_id item.id == TaskState.popover_shown_task_history_item_id
) | (item.id == ConversationState.conversation_id) ) | (item.id == TaskState.task_id)
return rx.list.item( return rx.list.item(
rx.vstack( rx.vstack(
@ -62,8 +62,8 @@ def conversation_history_item(
height="20px", height="20px",
margin_bottom="8px", margin_bottom="8px",
), ),
# 点击事件:设置激活会话 # 点击事件:设置激活任务
on_click=lambda: ConversationState.set_conversation(item.id), on_click=lambda: TaskState.set_task(item.id),
width="100%", width="100%",
), ),
# 会话历史项的更多按钮 # 会话历史项的更多按钮
@ -94,8 +94,8 @@ def conversation_history_item(
rx.popover.close( rx.popover.close(
rx.box( rx.box(
"删除", "删除",
# 点击事件:删除对话 # 点击事件:删除任务
on_click=lambda: ConversationState.delete_conversation( on_click=lambda: TaskState.delete_task(
item.id item.id
), ),
display="flex", display="flex",
@ -128,8 +128,8 @@ def conversation_history_item(
box_shadow="var(--prismui-box-shadow-6)", box_shadow="var(--prismui-box-shadow-6)",
overflow="visible", overflow="visible",
), ),
# 悬停气泡显示 / 隐藏变化事件:设置会话历史项悬停气泡显示 / 隐藏 # 悬停气泡显示 / 隐藏变化事件:设置任务历史项悬停气泡显示 / 隐藏
on_open_change=lambda is_shown: ConversationState.set_conversation_history_item_popover_shown( on_open_change=lambda is_shown: TaskState.set_task_history_item_popover_shown(
item.id, is_shown item.id, is_shown
), ),
open_delay=0, open_delay=0,
@ -172,6 +172,59 @@ def conversation_history_item(
) )
def task_history(
is_conversation_history_shown: bool,
) -> rx.Component:
"""
任务历史
:param is_conversation_history_shown: 展示 / 隐藏
:return: Component
"""
return rx.box(
rx.vstack(
# 会话历史的标题
rx.hstack(
rx.text(
"会话历史",
margin_bottom="8px",
font_weight="var(--prismui-font-weight-3)",
white_space="nowrap",
),
align_items="center",
justify_content="space-between",
),
rx.auto_scroll(
rx.foreach(
TaskState.task_history,
task_history_item,
),
flex="1",
align_items="stretch",
width="100%",
margin_top="8px",
style={
"&::-webkit-scrollbar": {
"display": "none",
},
},
overflow="auto",
),
display="flex",
flex_direction="column",
height="100%",
padding="12px",
background_color="#f9f9f9cc",
backdrop_filter="blur(50px)",
gap="12px",
),
width=rx.cond(is_conversation_history_shown, "25%", "0px"),
min_width=rx.cond(is_conversation_history_shown, "240px", "0px"),
max_width=rx.cond(is_conversation_history_shown, "380px", "0px"),
height="100%",
transition="all 0.18s ease-in-out",
overflow="hidden",
)
def conversation_history( def conversation_history(
is_conversation_history_shown: bool, is_conversation_history_shown: bool,
) -> rx.Component: ) -> rx.Component:
@ -195,8 +248,8 @@ def conversation_history(
), ),
rx.auto_scroll( rx.auto_scroll(
rx.foreach( rx.foreach(
ConversationState.conversation_history, TaskState.task_history,
conversation_history_item, task_history_item,
), ),
flex="1", flex="1",
align_items="stretch", align_items="stretch",
@ -235,7 +288,7 @@ def workflow_button(workflow_type: WorkflowType):
return rx.box( return rx.box(
workflow_type.value, workflow_type.value,
# 点击事件:设置工作流 # 点击事件:设置工作流
on_click=lambda: ConversationState.set_workflow(workflow_type), on_click=lambda: TaskState.set_workflow(workflow_type),
padding="10px 16px", padding="10px 16px",
background_color="var(--prismui-background-color-6)", background_color="var(--prismui-background-color-6)",
border_radius="var(--prismui-border-radius-9)", border_radius="var(--prismui-border-radius-9)",
@ -399,7 +452,7 @@ def thinking(item: MessageHistoryItem) -> rx.Component:
line_height="22px", line_height="22px",
), ),
# 点击事件,展示 / 隐藏消息历史项 # 点击事件,展示 / 隐藏消息历史项
on_click=lambda: ConversationState.toggle_message_history_item_shown( on_click=lambda: TaskState.toggle_message_history_item_shown(
item.id item.id
), ),
cursor="pointer", cursor="pointer",
@ -462,7 +515,7 @@ def tool_call(item: MessageHistoryItem) -> rx.Component:
line_height="22px", line_height="22px",
), ),
# 点击事件,展示 / 隐藏消息历史项 # 点击事件,展示 / 隐藏消息历史项
on_click=lambda: ConversationState.toggle_message_history_item_shown( on_click=lambda: TaskState.toggle_message_history_item_shown(
item.id item.id
), ),
cursor="pointer", cursor="pointer",
@ -496,7 +549,7 @@ def tool_call(item: MessageHistoryItem) -> rx.Component:
# 值绑定审批内容 # 值绑定审批内容
value=item.approval_content, value=item.approval_content,
# 值变化事件:设置审批内容 # 值变化事件:设置审批内容
on_change=lambda content: ConversationState.set_approval_content( on_change=lambda content: TaskState.set_approval_content(
item.id, content item.id, content
), ),
placeholder="请输入", placeholder="请输入",
@ -523,7 +576,7 @@ def tool_call(item: MessageHistoryItem) -> rx.Component:
rx.button( rx.button(
"提交", "提交",
# 点击事件:审批 # 点击事件:审批
on_click=lambda: ConversationState.run( on_click=lambda: TaskState.run(
True, item.id True, item.id
), ),
padding="0 12px", padding="0 12px",
@ -711,17 +764,17 @@ def message_history() -> rx.Component:
return rx.box( return rx.box(
# 若消息历史为空则显示使用指引,否则渲染消息历史项 # 若消息历史为空则显示使用指引,否则渲染消息历史项
rx.cond( rx.cond(
ConversationState.message_history.length() == 0, TaskState.message_history.length() == 0,
use_guidance(), use_guidance(),
rx.vstack( rx.vstack(
rx.auto_scroll( rx.auto_scroll(
rx.vstack( rx.vstack(
rx.foreach( rx.foreach(
ConversationState.message_history, message_history_item TaskState.message_history, message_history_item
), ),
# 若等待流式输出则显示加载动效 # 若等待流式输出则显示加载动效
rx.cond( rx.cond(
ConversationState.awaiting_stream, TaskState.awaiting_stream,
rx.box( rx.box(
rx.box( rx.box(
class_name="awaiting_stream", class_name="awaiting_stream",
@ -789,7 +842,7 @@ def conversation_creating() -> rx.Component:
rx.tooltip( rx.tooltip(
rx.icon( rx.icon(
"plus", "plus",
on_click=ConversationState.create_conversation, on_click=TaskState.create_task,
width="14px", width="14px",
height="14px", height="14px",
style={ style={
@ -798,7 +851,7 @@ def conversation_creating() -> rx.Component:
} }
}, },
), ),
content="创建会话", content="创建任务",
side="top", side="top",
side_offset=9, side_offset=9,
background_color="var(--prismui-background-color-1)", background_color="var(--prismui-background-color-1)",
@ -838,9 +891,9 @@ def user_prompt_sending() -> rx.Component:
rx.vstack( rx.vstack(
rx.text_area( rx.text_area(
# 值绑定:用户提示词 # 值绑定:用户提示词
value=ConversationState.user_prompt, value=TaskState.user_prompt,
# 输入事件:设置用户提示词 # 输入事件:设置用户提示词
on_change=ConversationState.set_user_prompt, on_change=TaskState.set_user_prompt,
placeholder="请输入您的问题,按Enter换行", placeholder="请输入您的问题,按Enter换行",
vertical_align="middle", vertical_align="middle",
width="100%", width="100%",
@ -878,19 +931,19 @@ def user_prompt_sending() -> rx.Component:
line_height="21px", line_height="21px",
color="var(--prismui-color-9)", color="var(--prismui-color-9)",
), ),
# 加载状态:绑定当前会话的运行状态 # 加载状态:绑定当前任务的运行状态
loading=ConversationState.is_running, loading=TaskState.is_running,
# 不可点击状态:绑定用户提示词发送按钮不可点击状态 # 不可点击状态:绑定用户提示词发送按钮不可点击状态
disabled=ConversationState.is_user_prompt_sending_button_disabled, disabled=TaskState.is_user_prompt_sending_button_disabled,
# 点击事件:运行 # 点击事件:运行
on_click=lambda: ConversationState.run(), on_click=lambda: TaskState.run(),
display="inline-flex", display="inline-flex",
position="relative", position="relative",
justify_content="center", justify_content="center",
align_items="center", align_items="center",
padding="0 12px", padding="0 12px",
background_color=rx.cond( background_color=rx.cond(
ConversationState.is_user_prompt_sending_button_disabled, TaskState.is_user_prompt_sending_button_disabled,
"var(--prismui-color-11)", "var(--prismui-color-11)",
"var(--prismui-color-5)", "var(--prismui-color-5)",
), ),
@ -900,7 +953,7 @@ def user_prompt_sending() -> rx.Component:
font_size="var(--prismui-font-size-2)", font_size="var(--prismui-font-size-2)",
white_space="nowrap", white_space="nowrap",
cursor=rx.cond( cursor=rx.cond(
ConversationState.is_user_prompt_sending_button_disabled, TaskState.is_user_prompt_sending_button_disabled,
"not-allowed", "not-allowed",
"pointer", "pointer",
), ),
@ -957,13 +1010,8 @@ def conversation_history_toggle_button(
height="16px", height="16px",
color="var(--prismui-color-3)", color="var(--prismui-color-3)",
), ),
# 点击事件:切换会话历史折叠面板展开状态
on_click=ConversationState.toggle_conversation_history_shown,
position="absolute", position="absolute",
top="calc(50% - 20px)", top="calc(50% - 20px)",
left=rx.cond(
is_conversation_history_shown, "calc(clamp(240px, 25%, 380px) - 8px)", "0"
),
z_index="99", z_index="99",
width="16px", width="16px",
height="40px", height="40px",
@ -979,14 +1027,13 @@ def conversation_history_toggle_button(
) )
def conversation_page() -> rx.Component: def create_task_page() -> rx.Component:
""" """
会话页面:布局参考 MetaChat,从左到右分别为会话历史和工作区。其中,工作区从上到下分别为消息历史和用户提示词输入框。 创建任务页面
""" """
return rx.box( return rx.box(
rx.hstack( rx.hstack(
# 会话历史 # 任务历史
conversation_history(ConversationState.is_conversation_history_shown),
# 工作区 # 工作区
rx.box( rx.box(
# 对话历史 # 对话历史
@ -1015,12 +1062,53 @@ def conversation_page() -> rx.Component:
overflow="hidden", overflow="hidden",
transition="all 0.18s ease-in-out", transition="all 0.18s ease-in-out",
), ),
# 会话历史显示 / 隐藏按钮 # 挂载事件:恢复任务状态
conversation_history_toggle_button( on_mount=AuthState.resume_task_state,
ConversationState.is_conversation_history_shown position="relative",
), width="100%",
# 挂载事件:恢复会话状态 height="100%",
on_mount=AuthState.resume_conversation_state, border_radius="var(--prismui-border-radius-4)",
overflow="hidden",
)
def conversation_page() -> rx.Component:
"""
会话页面:布局参考 MetaChat,从左到右分别为会话历史和工作区。其中,工作区从上到下分别为消息历史和用户提示词输入框。
"""
return rx.box(
rx.hstack(
# 工作区
rx.box(
# 对话历史
rx.vstack(
message_history(),
width="100%",
height="100%",
gap="8px",
align_items="center",
),
# 用户提示词发送
user_prompt_sending(),
position="relative",
display="flex",
flex="1",
width="0",
height="100%",
padding_bottom="120px",
background="var(--prismui-background-2)",
),
position="relative",
display="flex",
flex="1",
height="100%",
min_height="0",
overflow="hidden",
transition="all 0.18s ease-in-out",
),
# 挂载事件:恢复任务状态
on_mount=AuthState.resume_task_state,
position="relative", position="relative",
width="100%", width="100%",
height="100%", height="100%",

View File

@ -0,0 +1,427 @@
# -*- coding: utf-8 -*-
"""
工作台
"""
import reflex as rx
from application.pages import create_task_page, knowledge_base_page, login_page
from application.states import AuthState, TaskState
from application.states.models import TaskHistoryItem
# 导航按钮字典
NAV_BUTTONS: dict = {
"create_task": {"src": "/create_task.svg", "text": "创建任务"},
}
"""
约定:
1 若组件仅用于当前页面则无需抽象为通用组件(application.components.*)
"""
def nav_button(
nav_button: str,
) -> rx.Component:
"""
导航按钮
:param nav_button: 导航按钮
:return : Component
"""
# 导航按钮激活状态
is_actived = nav_button == AuthState.activated_nav_button
return rx.hstack(
# 导航按钮的图标
rx.image(
NAV_BUTTONS[nav_button]["src"],
width="16px",
height="16px",
object_fit="contain",
),
# 导航按钮的文本
rx.text(
NAV_BUTTONS[nav_button]["text"],
line_height="20px",
font_size="var(--prismui-font-size-2)",
font_weight=rx.cond(
is_actived, "--prismui-font-weight-3", None
), # 通过加粗字体区分是否激活
color="var(--prismui-color-1)",
),
align_items="center",
gap="8px",
padding="5px 12px",
background_color=rx.cond(
is_actived, "var(--prismui-background-color-2)", "transparent"
),
cursor="pointer",
# 点击事件:设置激活导航按钮
on_click=AuthState.set_activated_nav_button(nav_button),
)
def task_history_item(
item: TaskHistoryItem,
) -> rx.Component:
"""
任务历史项
:param item: 任务历史项
:return: Component
"""
# 高亮
is_highlight = (item.id == TaskState.popover_shown_task_history_item_id) | (
item.id == TaskState.task_id
)
return rx.list.item(
rx.vstack(
rx.hstack(
# 会话历史项的会话描述
rx.text(
item.description,
flex=1,
height="22px",
line_height="22px",
padding_right="18px",
color="var(--prismui-color-2)",
text_overflow="ellipsis",
white_space="nowrap",
overflow="hidden",
),
display="flex",
align_items="center",
width="100%",
margin_bottom="8px",
),
rx.hstack(
rx.spacer(),
# 会话历史项的会话创建日期时间
rx.text(
item.created_at,
line_height="20px",
font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-8)",
),
justify_content="space-between",
align_items="center",
width="100%",
height="20px",
margin_bottom="8px",
),
# 点击事件:设置激活任务
on_click=lambda: TaskState.set_task(item.id),
width="100%",
),
# 会话历史项的更多按钮
rx.box(
rx.popover.root(
rx.popover.trigger(
rx.box(
rx.icon(
"ellipsis",
width="14px",
height="14px",
color="var(--prismui-color-2)",
),
cursor="pointer",
)
),
rx.popover.content(
rx.vstack(
rx.box(
position="absolute",
top="-6px",
left="50%",
transform="translateX(-50%) rotate(45deg)",
width="8px",
height="8px",
background_color="var(--prismui-background-color-1)",
),
rx.popover.close(
rx.box(
"删除",
# 点击事件:删除任务
on_click=lambda: TaskState.delete_task(item.id),
display="flex",
align_items="center",
justify_content="center",
width="100%",
height="24px",
border_radius="var(--prismui-border-radius-1)",
line_height="16px",
font_family="var(--prismui-font-family)",
font_size="var(--prismui-font-size-1)",
color="var(--prismui-color-1)",
style={
"_hover": {
"background_color": "var(--prismui-background-color-6)",
}, # 鼠标悬停显示背景颜色
},
cursor="pointer",
),
),
position="relative",
width="100%",
),
side="bottom",
side_offset=8,
position="relative",
align="center",
padding="4px 8px",
border_radius="var(--prismui-border-radius-1)",
box_shadow="var(--prismui-box-shadow-6)",
overflow="visible",
),
# 悬停气泡显示 / 隐藏变化事件:设置任务历史项悬停气泡显示 / 隐藏
on_open_change=lambda is_shown: TaskState.set_task_history_item_popover_shown(
item.id, is_shown
),
open_delay=0,
),
position="absolute",
top="27px",
right="16px",
transform="translateY(-50%)",
z_index="99",
min_width="14px",
cursor="pointer",
opacity=rx.cond(is_highlight, "1", "0"),
transition="opacity 0.18s ease-in-out",
pointer_events=rx.cond(is_highlight, "auto", "none"),
),
display="flex",
position="relative",
align_items="flex-start",
width="100%",
padding="16px",
margin_bottom="8px",
background=rx.cond(
is_highlight,
"var(--prismui-background-3)",
"var(--prismui-background-color-1)",
),
border_radius="var(--prismui-border-radius-3)",
box_shadow=rx.cond(is_highlight, "var(--prismui-box-shadow-7)", "none"),
cursor="pointer",
style={
"&:hover": {
"background": "var(--prismui-background-3)",
"box_shadow": "var(--prismui-box-shadow-7)",
}, # 鼠标悬停背景颜色和阴影
"&:hover > div:last-child": {
"opacity": "1",
"pointer_events": "auto",
}, # 鼠标悬停显示更多按钮
},
)
def task_history() -> rx.Component:
"""
任务历史
:return: rx.Component
"""
return rx.auto_scroll(
rx.foreach(
TaskState.task_history,
task_history_item,
),
flex="1",
align_items="stretch",
width="100%",
margin_top="8px",
style={
"&::-webkit-scrollbar": {
"display": "none",
},
},
overflow="auto",
)
def settings_button():
"""
设置按钮
:return: Component
"""
return rx.box(
rx.hover_card.root(
# 触发事件:点击自定义按钮
rx.hover_card.trigger(
rx.box(
rx.icon(
"settings",
stroke_width=1.5,
width="20px",
height="20px",
color="var(--prismui-color-1)",
),
display="flex",
flex_flow="column",
justify_content="center",
align_items="center",
width="36px",
height="36px",
border_radius="var(--prismui-border-radius-3)",
style={
"&:hover": {
"background": "var(--prismui-background-color-5)",
}, # 鼠标悬停背景颜色
},
cursor="pointer",
)
),
# 悬停卡片
rx.hover_card.content(
rx.box(
rx.box(
transform="translateX(-50%) rotate(45deg)",
position="absolute",
top="calc(50% - 4px)",
left="0",
width="8px",
height="8px",
background_color="var(--prismui-background-color-1)",
),
rx.box(
"退出登录",
display="flex",
justify_content="center",
align_items="center",
width="100%",
height="24px",
line_height="16px",
border_radius="var(--prismui-border-radius-1)",
style={
"_hover": {
"background_color": "var(--prismui-background-color-6)",
},
},
# 点击事件:退出登录
on_click=lambda: AuthState.logout(),
),
cursor="pointer",
),
side="left",
side_offset=8,
position="relative",
align="center",
padding="4px 12px",
border_radius="var(--prismui-border-radius-1)",
box_shadow="var(--prismui-box-shadow-3)",
font_family="var(--prismui-font-family)",
overflow="visible",
style={
"font-size": "var(--prismui-font-size-1) !important",
"color": "var(--prismui-color-1) !important",
},
),
open=AuthState.is_settings_hover_card_open,
# 悬停卡片打开状态变化事件:设置设置悬停卡片打开状态
on_open_change=AuthState.set_settings_hover_card_open,
),
min_width="14px",
cursor="pointer",
)
def sidebar() -> rx.Component:
"""
侧边栏
:return: rx.Component
"""
return rx.vstack(
rx.vstack(
# 渲染系统图标和名称
rx.hstack(
# 系统图标
rx.image(
src="/logo.png",
width="20px",
height="20px",
object_fit="contain",
),
# 系统名称
rx.text(
"棱镜球",
line_height="20px",
font_size="var(--prismui-font-size-2)",
font_weight="var(--prismui-font-weight-4)",
),
align_items="center",
gap="4px",
height="56px",
padding="0 0 12px 12px",
),
# 渲染导航按钮
rx.vstack(
# 渲染导航按钮:创建任务
nav_button("create_task"),
gap="2px",
padding="0 0 16px 0",
),
# 渲染最近任务
rx.vstack(
rx.text(
"最近",
line_height="20px",
font_size="var(--prismui-font-size-2)",
padding="0 0 0 10px",
),
padding="0 12px",
),
align_items="flex-start",
gap="2px",
width="100%",
),
rx.vstack(
rx.vstack(
# 设置按钮
settings_button(),
),
padding_bottom="16px",
gap="8px",
),
flex_shrink="0",
justify_content="space-between",
align_items="flex-start",
width="264px",
height="100%",
padding="0 12px",
background="var(--prismui-background-1)",
)
def workbench() -> rx.Component:
"""
工作台
"""
return rx.fragment(
# 若用户未登录则渲染登录页面
rx.cond(
AuthState.user_id,
rx.fragment(),
login_page(),
),
rx.hstack(
# 侧边栏
sidebar(),
# 默认新建任务页面
rx.match(
AuthState.activated_nav_button,
# 知识库页面
("knowledge_base", knowledge_base_page()),
# 创建任务页面
("create_task", create_task_page()),
# 会话页面
create_task_page(),
),
width="100%",
height="100vh",
overflow="auto",
),
)

View File

@ -1,11 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from application.states.auth import AuthState from application.states.auth import AuthState
from application.states.conversation import ConversationState from application.states.task import TaskState
from application.states.database import DatabaseState from application.states.database import DatabaseState
__all__ = [ __all__ = [
"AuthState", "AuthState",
"ConversationState", "TaskState",
"DatabaseState", "DatabaseState",
] ]

View File

@ -12,7 +12,7 @@ from aiosmtplib import SMTP, SMTPException
import reflex as rx import reflex as rx
from reflex.event import EventCallback from reflex.event import EventCallback
from application.states.conversation import ConversationState from application.states.task import TaskState
from application.states.database import DatabaseState from application.states.database import DatabaseState
# 验证码HTML模板 # 验证码HTML模板
@ -204,14 +204,14 @@ class AuthState(rx.State):
self.is_policies_agreed = not self.is_policies_agreed self.is_policies_agreed = not self.is_policies_agreed
@rx.event @rx.event
async def resume_conversation_state(self): async def resume_task_state(self):
""" """
恢复会话状态 恢复任务状态
""" """
# 获取会话状态 # 获取任务状态
conversation_state = await self.get_state(ConversationState) task_state = await self.get_state(TaskState)
# 恢复会话状态 # 恢复任务状态
await conversation_state.resume_conversation_state(user_id=self.user_id) await task_state.resume_task_state(user_id=self.user_id)
@rx.event @rx.event
async def login(self) -> None: async def login(self) -> None:
@ -254,10 +254,10 @@ class AuthState(rx.State):
# 创建用户记录 # 创建用户记录
user_id = await db_state.create_user_record(email=self.email) user_id = await db_state.create_user_record(email=self.email)
# 获取会话状态 # 获取任务状态
conversation_state = await self.get_state(ConversationState) task_state = await self.get_state(TaskState)
# 恢复当前用户的会话状态 # 恢复当前用户的任务状态
await conversation_state.resume_conversation_state(user_id=user_id) await task_state.resume_task_state(user_id=user_id)
self.user_id = user_id self.user_id = user_id
@ -292,9 +292,9 @@ class AuthState(rx.State):
:return: None :return: None
""" """
self.is_settings_hover_card_open = False # 设置设置悬停卡片关闭 self.is_settings_hover_card_open = False # 设置设置悬停卡片关闭
conversation_state = await self.get_state(ConversationState) task_state = await self.get_state(TaskState)
conversation_state.user_id = "" task_state.user_id = ""
conversation_state.conversations = {} task_state.tasks = {}
conversation_state.actived_conversation_id = "" task_state.actived_task_id = ""
conversation_state.is_conversation_history_shown = False task_state.is_task_history_shown = False
self.user_id = "" self.user_id = ""

View File

@ -13,7 +13,7 @@ from sqlmodel import Field, SQLModel, select, update
from application.states.models import ( from application.states.models import (
Approval, Approval,
Conversation, Task,
Message, Message,
MessageType, MessageType,
Workflow, Workflow,
@ -67,30 +67,30 @@ class UserRecord(SQLModel, table=True):
email: str = Field(..., index=True, description="邮箱") email: str = Field(..., index=True, description="邮箱")
class ConversationRecord(SQLModel, table=True): class TaskRecord(SQLModel, table=True):
""" """
会话记录 任务记录
""" """
id: str = Field( id: str = Field(
default_factory=lambda: str(uuid7()), default_factory=lambda: str(uuid7()),
primary_key=True, primary_key=True,
description="会话唯一标识", description="任务唯一标识",
) )
user_id: str = Field(..., index=True, description="用户唯一标识") user_id: str = Field(..., index=True, description="用户唯一标识")
description: str = Field(default="新会话", description="会话描述") description: str = Field(default="新任务", description="任务描述")
created_at: datetime = Field( created_at: datetime = Field(
default_factory=datetime.now, description="会话创建时间" default_factory=datetime.now, description="任务创建时间"
) )
is_deleted: bool = Field( is_deleted: bool = Field(
default=False, default=False,
index=True, index=True,
description="会话已删除:True 表示已删除,False 表示未删除", description="任务已删除:True 表示已删除,False 表示未删除",
) )
usage: str = Field(default="", description="会话使用量") usage: str = Field(default="", description="任务使用量")
workflow: str = Field( workflow: str = Field(
default="", default="",
description="会话工作流", description="任务工作流",
) # 原则上嵌套列表或字典若不涉及查询则以字符串储存 ) # 原则上嵌套列表或字典若不涉及查询则以字符串储存
@ -104,7 +104,7 @@ class RunRecord(SQLModel, table=True):
primary_key=True, primary_key=True,
description="运行唯一标识", description="运行唯一标识",
) )
conversation_id: str = Field(..., index=True, description="会话唯一标识") task_id: str = Field(..., index=True, description="任务唯一标识")
new_messages: str = Field(..., description="新增消息") new_messages: str = Field(..., description="新增消息")
@ -118,7 +118,7 @@ class MessageRecord(SQLModel, table=True):
primary_key=True, primary_key=True,
description="消息唯一标识", description="消息唯一标识",
) )
conversation_id: str = Field(..., index=True, description="会话唯一标识") task_id: str = Field(..., index=True, description="任务唯一标识")
type: MessageType = Field(..., description="消息类型") type: MessageType = Field(..., description="消息类型")
title: str = Field(default="", description="消息标题") title: str = Field(default="", description="消息标题")
content: str = Field(default="", description="消息内容") content: str = Field(default="", description="消息内容")
@ -204,66 +204,66 @@ class DatabaseState(rx.State):
await session.refresh(record) await session.refresh(record)
return record.id return record.id
async def create_conversation_record(self, user_id: str) -> dict[str, Conversation]: async def create_task_record(self, user_id: str) -> dict[str, Task]:
""" """
创建会话记录 创建任务记录
:param user_id: 用户唯一标识 :param user_id: 用户唯一标识
:return: 会话实例 :return: 任务实例
""" """
async with rx.asession() as session: async with rx.asession() as session:
record = ConversationRecord(user_id=user_id) record = TaskRecord(user_id=user_id)
session.add(record) session.add(record)
await session.commit() await session.commit()
await session.refresh(record) await session.refresh(record)
return { return {
record.id: Conversation( record.id: Task(
id=record.id, id=record.id,
description=record.description, description=record.description,
usage=usage_validate_json( usage=usage_validate_json(
record.usage record.usage
), # 将 record.usage 由 JSON 字符串反序列化为 RunUsage ), # 将 record.usage 由 JSON 字符串反序列化为 RunUsage
created_at=record.created_at, created_at=record.created_at,
) )
} }
async def read_conversations(self, user_id: str) -> dict[str, Conversation]: async def read_tasks(self, user_id: str) -> dict[str, Task]:
""" """
查询当前用户的会话字典 查询当前用户的任务字典
:param user_id: 用户唯一标识 :param user_id: 用户唯一标识
:return: 当前用户的会话字典 :return: 当前用户的任务字典
""" """
conversations: dict[str, Conversation] = {} tasks: dict[str, Task] = {}
async with rx.asession() as session: async with rx.asession() as session:
result = await session.exec( result = await session.exec(
select(ConversationRecord, MessageRecord) select(TaskRecord, MessageRecord)
.outerjoin(MessageRecord, MessageRecord.conversation_id == ConversationRecord.id) # type: ignore .outerjoin(MessageRecord, MessageRecord.task_id == TaskRecord.id) # type: ignore
.where( .where(
ConversationRecord.user_id == user_id, TaskRecord.user_id == user_id,
ConversationRecord.is_deleted == False, TaskRecord.is_deleted == False,
) )
.order_by(ConversationRecord.id, MessageRecord.id) .order_by(TaskRecord.id, MessageRecord.id)
) )
for ( for (
conversation_record, task_record,
message_record, message_record,
) in result.all(): ) in result.all():
conversation = conversations.setdefault( task = tasks.setdefault(
conversation_record.id, task_record.id,
Conversation( Task(
id=conversation_record.id, id=task_record.id,
description=conversation_record.description, description=task_record.description,
usage=usage_validate_json( usage=usage_validate_json(
conversation_record.usage task_record.usage
), # 将 conversation_record.usage 由 JSON 字符串反序列化为 RunUsage ), # 将 task_record.usage 由 JSON 字符串反序列化为 RunUsage
created_at=conversation_record.created_at, created_at=task_record.created_at,
workflow=workflow_validate_json( workflow=workflow_validate_json(
conversation_record.workflow task_record.workflow
), # 将 conversation_record.workflow 由 JSON 字符串反序列化为 Workflow, ), # 将 task_record.workflow 由 JSON 字符串反序列化为 Workflow,
), ),
) )
if not message_record: if not message_record:
continue continue
conversation.messages.setdefault( task.messages.setdefault(
message_record.id, message_record.id,
Message( Message(
id=message_record.id, id=message_record.id,
@ -273,27 +273,27 @@ class DatabaseState(rx.State):
approval=approval_validate_json(message_record.approval), approval=approval_validate_json(message_record.approval),
), ),
) )
return conversations return tasks
async def update_conversation_record( async def update_task_record(
self, self,
conversation_id: str, task_id: str,
description: str | None = None, description: str | None = None,
is_deleted: bool | None = None, is_deleted: bool | None = None,
usage: RunUsage | None = None, usage: RunUsage | None = None,
workflow: Workflow | None = None, workflow: Workflow | None = None,
) -> None: ) -> None:
""" """
更新会话记录 更新任务记录
:param conversation_id: 指定会话唯一标识 :param task_id: 指定任务唯一标识
:param description: 会话描述 :param description: 任务描述
:param is_deleted: 会话已删除 :param is_deleted: 任务已删除
:param usage: 使用量 :param usage: 使用量
:param workflow: 工作流 :param workflow: 工作流
:return: None :return: None
""" """
async with rx.asession() as session: async with rx.asession() as session:
record = await session.get(ConversationRecord, conversation_id) record = await session.get(TaskRecord, task_id)
if not record: if not record:
return return
if isinstance(description, str): if isinstance(description, str):
@ -312,19 +312,19 @@ class DatabaseState(rx.State):
async def create_message_record( async def create_message_record(
self, self,
conversation_id: str, task_id: str,
message: Message, message: Message,
) -> dict[str, Message]: ) -> dict[str, Message]:
""" """
创建消息记录 创建消息记录
:param conversation_id: 会话唯一标识 :param task_id: 任务唯一标识
:param message: 消息实例 :param message: 消息实例
:return: 消息实例 :return: 消息实例
""" """
async with rx.asession() as session: async with rx.asession() as session:
record = MessageRecord( record = MessageRecord(
id=message.id, id=message.id,
conversation_id=conversation_id, task_id=task_id,
type=message.type, type=message.type,
title=message.title, title=message.title,
content=message.content, content=message.content,
@ -337,12 +337,12 @@ class DatabaseState(rx.State):
async def create_message_records( async def create_message_records(
self, self,
conversation_id: str, task_id: str,
messages: list[Message], messages: list[Message],
) -> None: ) -> None:
""" """
创建消息记录 创建消息记录
:param conversation_id: 会话唯一标识 :param task_id: 任务唯一标识
:param messages: 消息实例列表 :param messages: 消息实例列表
:return: None :return: None
""" """
@ -360,7 +360,7 @@ class DatabaseState(rx.State):
session.add( session.add(
MessageRecord( MessageRecord(
id=message.id, id=message.id,
conversation_id=conversation_id, task_id=task_id,
type=message.type, type=message.type,
title=message.title, title=message.title,
content=message.content, content=message.content,
@ -388,17 +388,17 @@ class DatabaseState(rx.State):
record.approval = approval_dump_json(approval) record.approval = approval_dump_json(approval)
await session.commit() await session.commit()
async def read_message_history(self, conversation_id: str) -> list[ModelMessage]: async def read_message_history(self, task_id: str) -> list[ModelMessage]:
""" """
查询消息历史列表 查询消息历史列表
:param conversation_id: 会话唯一标识 :param task_id: 任务唯一标识
:return: 消息历史 :return: 消息历史
""" """
message_history: list[ModelMessage] = [] message_history: list[ModelMessage] = []
async with rx.asession() as session: async with rx.asession() as session:
result = await session.exec( result = await session.exec(
select(RunRecord) select(RunRecord)
.where(RunRecord.conversation_id == conversation_id) .where(RunRecord.task_id == task_id)
.order_by(RunRecord.id) .order_by(RunRecord.id)
) )
for run in result.all(): for run in result.all():
@ -411,19 +411,19 @@ class DatabaseState(rx.State):
async def create_run_record( async def create_run_record(
self, self,
conversation_id: str, task_id: str,
new_messages: list[ModelMessage], new_messages: list[ModelMessage],
) -> None: ) -> None:
""" """
创建运行记录 创建运行记录
:param conversation_id: 会话唯一标识 :param task_id: 任务唯一标识
:param new_messages: 新增消息 :param new_messages: 新增消息
:return: None :return: None
""" """
async with rx.asession() as session: async with rx.asession() as session:
session.add( session.add(
RunRecord( RunRecord(
conversation_id=conversation_id, task_id=task_id,
new_messages=ModelMessagesTypeAdapter.dump_json( new_messages=ModelMessagesTypeAdapter.dump_json(
new_messages new_messages
).decode("utf-8"), ).decode("utf-8"),

View File

@ -77,15 +77,15 @@ class Workflow(BaseModel):
) )
class Conversation(BaseModel): class Task(BaseModel):
""" """
会话类 任务类
""" """
id: str = Field(..., description="会话唯一标识") id: str = Field(..., description="任务唯一标识")
description: str = Field(..., description="会话描述") description: str = Field(..., description="任务描述")
created_at: datetime = Field(..., description="会话创建日期时间") created_at: datetime = Field(..., description="任务创建日期时间")
usage: RunUsage = Field(..., description="会话使用量") usage: RunUsage = Field(..., description="任务使用量")
messages: dict[str, Message] = Field(default_factory=dict, description="消息字典") messages: dict[str, Message] = Field(default_factory=dict, description="消息字典")
workflow: Workflow = Field(default_factory=Workflow, description="工作,默认为对话") workflow: Workflow = Field(default_factory=Workflow, description="工作,默认为对话")
user_prompt: str = Field(default="", description="用户提示词") user_prompt: str = Field(default="", description="用户提示词")
@ -98,14 +98,14 @@ class Conversation(BaseModel):
) )
class ConversationHistoryItem(BaseModel): class TaskHistoryItem(BaseModel):
""" """
会话历史项类 任务历史项类
""" """
id: str = Field(..., description="会话唯一标识") id: str = Field(..., description="任务唯一标识")
description: str = Field(..., description="会话描述") description: str = Field(..., description="任务描述")
created_at: str = Field(..., description="会话创建日期时间") created_at: str = Field(..., description="任务创建日期时间")
class MessageHistoryItem(Message): class MessageHistoryItem(Message):

View File

@ -24,8 +24,8 @@ import reflex as rx
from application.states.database import DatabaseState from application.states.database import DatabaseState
from application.states.models import ( from application.states.models import (
Approval, Approval,
Conversation, Task,
ConversationHistoryItem, TaskHistoryItem,
Message, Message,
MessageHistoryItem, MessageHistoryItem,
MessageType, MessageType,
@ -51,24 +51,21 @@ def format_conversation_created_at(created_at: datetime) -> str:
return formatted_created_at return formatted_created_at
class ConversationState(rx.State): class TaskState(rx.State):
""" """
会话状态 任务状态
""" """
# 当前用户唯一标识 # 当前用户唯一标识
user_id: str = "" user_id: str = ""
# 当前用户的会话字典(私有变量)
# 当前用户的会话字典 # 当前用户的任务字典
conversations: dict[str, Conversation] = {} # 键为会话唯一标识,值为会话 tasks: dict[str, Task] = {} # 键为任务唯一标识,值为任务实例
# 当前会话唯一标识 # 当前任务唯一标识
conversation_id: str = "" task_id: str = ""
# 显示会话历史,True 表示显示,False 表示隐藏 # 悬停气泡显示的任务历史项唯一标识(一个任务只允许存在一个悬停气泡任务历史项)
is_conversation_history_shown: bool = False popover_shown_task_history_item_id: str = ""
# 悬停气泡显示的会话历史项唯一标识(一个会话只允许存在一个悬停气泡会话历史项)
popover_shown_conversation_history_item_id: str = ""
# 当前数据库状态(私有变量) # 当前数据库状态(私有变量)
# 私有变量:reflex 约定以 _ 开头的变量为私有变量,后端不序列化,前端不可使用 # 私有变量:reflex 约定以 _ 开头的变量为私有变量,后端不序列化,前端不可使用
@ -84,9 +81,9 @@ class ConversationState(rx.State):
self._db_state = await self.get_state(DatabaseState) self._db_state = await self.get_state(DatabaseState)
return self._db_state return self._db_state
async def resume_conversation_state(self, user_id: str) -> None: async def resume_task_state(self, user_id: str) -> None:
""" """
恢复当前用户的会话状态 恢复任务状态
:param user_id: 用户唯一标识 :param user_id: 用户唯一标识
:return: None :return: None
""" """
@ -96,54 +93,54 @@ class ConversationState(rx.State):
# 获取当前数据库状态 # 获取当前数据库状态
db_state = await self.get_db_state() db_state = await self.get_db_state()
# 查询当前用户的会话字典 # 查询当前用户的任务字典
self.conversations = await db_state.read_conversations(self.user_id) self.tasks = await db_state.read_tasks(self.user_id)
# 若当前用户的会话字典为空则先创建会话记录再添加会话 # 若当前用户的任务字典为空则先创建任务记录再添加任务
if not self.conversations: if not self.tasks:
self.conversations.update( self.tasks.update(
await db_state.create_conversation_record(self.user_id) await db_state.create_task_record(self.user_id)
) )
# 将最后一个会话的唯一标识作为当前会话唯一标识 # 将最后一个任务的唯一标识作为当前任务唯一标识
self.conversation_id = next(reversed(self.conversations.keys())) self.task_id = next(reversed(self.tasks.keys()))
# 当前会话 # 当前任务
conversation = self.conversations[self.conversation_id] task = self.tasks[self.task_id]
# 最后一个消息 # 最后一个消息
message = next(iter(reversed(conversation.messages.values())), None) message = next(iter(reversed(task.messages.values())), None)
# 若最后一个消息包含审批且审批结果为 None 则将会话正在运行设置为是、消息正在运行设置为是 # 若最后一个消息包含审批且审批结果为 None 则任务正在运行设置为是、消息正在运行设置为是
if ( if (
message is not None message is not None
and isinstance(approval := message.approval, Approval) and isinstance(approval := message.approval, Approval)
and approval.result is None and approval.result is None
): ):
conversation.is_running = True task.is_running = True
message.is_running = True message.is_running = True
@rx.event @rx.event
def toggle_conversation_history_shown(self) -> None: def toggle_task_history_shown(self) -> None:
""" """
显示 / 隐藏会话历史 显示 / 隐藏任务历史
:return: None :return: None
""" """
self.is_conversation_history_shown = not self.is_conversation_history_shown self.is_task_history_shown = not self.is_task_history_shown
@rx.var @rx.var
def conversation_history(self) -> list[ConversationHistoryItem]: def task_history(self) -> list[TaskHistoryItem]:
""" """
获取会话历史项列表(按照会话创建日期时间降序排序) 获取会话历史项列表(按照会话创建日期时间降序排序)
:return: 会话历史项列表 :return: 任务历史项列表
""" """
return [ return [
ConversationHistoryItem( TaskHistoryItem(
id=conversation.id, id=task.id,
description=conversation.description, description=task.description,
created_at=format_conversation_created_at(conversation.created_at), created_at=format_conversation_created_at(task.created_at),
) )
for conversation in reversed(self.conversations.values()) for task in reversed(self.tasks.values())
] ]
@rx.event @rx.event
def set_conversation_history_item_popover_shown( def set_task_history_item_popover_shown(
self, item_id: str, is_shown: bool self, item_id: str, is_shown: bool
) -> None: ) -> None:
""" """
@ -153,36 +150,36 @@ class ConversationState(rx.State):
self.popover_shown_conversation_history_item_id = item_id if is_shown else "" self.popover_shown_conversation_history_item_id = item_id if is_shown else ""
@rx.event @rx.event
async def delete_conversation(self, conversation_id: str) -> None: async def delete_task(self, task_id: str) -> None:
""" """
删除会话 删除任务
:param conversation_id: 需删除的会话唯一标识 :param task_id: 需删除的任务唯一标识
:return: None :return: None
""" """
# 获取当前数据库状态 # 获取当前数据库状态
db_state = await self.get_db_state() db_state = await self.get_db_state()
# 更新会话记录 # 更新任务记录
await db_state.update_conversation_record(conversation_id, is_deleted=True) await db_state.update_task_record(task_id, is_deleted=True)
del self.conversations[conversation_id] del self.tasks[task_id]
# 删除后,若当前用户的会话字典为空则创建会话 # 删除后,若当前用户的任务字典为空则创建任务
if not self.conversations: if not self.tasks:
self.conversations.update( self.tasks.update(
await db_state.create_conversation_record(self.user_id) await db_state.create_task_record(self.user_id)
) )
# 删除后,若当前会话唯一标识不存在则将最后一个会话的唯一标识作为当前会话唯一标识 # 删除后,若当前任务唯一标识不存在则将最后一个任务的唯一标识作为当前任务唯一标识
if self.conversation_id not in self.conversations: if self.task_id not in self.tasks:
self.conversation_id = next(reversed(self.conversations.keys())) self.task_id = next(reversed(self.tasks.keys()))
@rx.event @rx.event
def set_conversation(self, conversation_id: str) -> None: def set_task(self, task_id: str) -> None:
""" """
设置当前会话 将指定任务设置为当前任务
:param conversation_id: 会话唯一标识 :param task_id: 任务唯一标识
:return: None :return: None
""" """
self.conversation_id = conversation_id self.task_id = task_id
@rx.var @rx.var
def message_history(self) -> list[MessageHistoryItem]: def message_history(self) -> list[MessageHistoryItem]:
@ -190,12 +187,12 @@ class ConversationState(rx.State):
获取当前会话的消息历史 获取当前会话的消息历史
:return: 当前会话的消息历史 :return: 当前会话的消息历史
""" """
# 当前会话 # 当前任务
conversation = self.conversations.get(self.conversation_id) task = self.tasks.get(self.task_id)
if not conversation: if not task:
return [] return []
message_history: list[MessageHistoryItem] = [] message_history: list[MessageHistoryItem] = []
for message in conversation.messages.values(): for message in task.messages.values():
message_history.append( message_history.append(
MessageHistoryItem( MessageHistoryItem(
**message.model_dump(), **message.model_dump(),
@ -207,19 +204,19 @@ class ConversationState(rx.State):
return message_history return message_history
@rx.event @rx.event
async def create_conversation(self) -> None: async def create_task(self) -> None:
""" """
创建会话 创建任务
:return: None :return: None
""" """
# 获取当前数据库状态 # 获取当前数据库状态
db_state = await self.get_db_state() db_state = await self.get_db_state()
# 创建会话记录再添加会话 # 创建任务记录再添加任务
self.conversations.update( self.tasks.update(
await db_state.create_conversation_record(self.user_id) await db_state.create_task_record(self.user_id)
) )
# 将最后一个会话的唯一标识作为当前会话唯一标识 # 将最后一个任务的唯一标识作为当前任务唯一标识
self.conversation_id = next(reversed(self.conversations.keys())) self.task_id = next(reversed(self.tasks.keys()))
@rx.var @rx.var
def user_prompt(self) -> str: def user_prompt(self) -> str:
@ -227,11 +224,11 @@ class ConversationState(rx.State):
用户提示词 用户提示词
:return: 用户提示词 :return: 用户提示词
""" """
# 当前会话 # 当前任务
conversation = self.conversations.get(self.conversation_id) task = self.tasks.get(self.task_id)
if not conversation: if not task:
return "" return ""
return conversation.user_prompt return task.user_prompt
@rx.event @rx.event
def set_user_prompt(self, user_prompt: str) -> None: def set_user_prompt(self, user_prompt: str) -> None:
@ -240,15 +237,15 @@ class ConversationState(rx.State):
:param user_prompt: 用户提示词 :param user_prompt: 用户提示词
:return: None :return: None
""" """
# 当前会话 # 当前任务
conversation = self.conversations.get(self.conversation_id) task = self.tasks.get(self.task_id)
if not conversation: if not task:
return return
if not user_prompt.strip(): if not user_prompt.strip():
return return
conversation.user_prompt = user_prompt task.user_prompt = user_prompt
@rx.var @rx.var
def is_user_prompt_sending_button_disabled(self) -> bool: def is_user_prompt_sending_button_disabled(self) -> bool:
@ -256,11 +253,11 @@ class ConversationState(rx.State):
用户提示词发送按钮不可点击状态 用户提示词发送按钮不可点击状态
:return: bool :return: bool
""" """
# 当前会话 # 当前任务
conversation = self.conversations.get(self.conversation_id) task = self.tasks.get(self.task_id)
if not conversation: if not task:
return True return True
return conversation.is_running or not conversation.user_prompt return task.is_running or not task.user_prompt
@rx.event @rx.event
async def run( async def run(
@ -274,9 +271,9 @@ class ConversationState(rx.State):
:param message_id: 消息唯一标识 :param message_id: 消息唯一标识
:return: AsyncGenerator :return: AsyncGenerator
""" """
# 当前会话 # 当前任务
conversation = self.conversations.get(self.conversation_id) task = self.tasks.get(self.task_id)
if not conversation: if not task:
return return
# 获取当前数据库状态 # 获取当前数据库状态
@ -290,7 +287,7 @@ class ConversationState(rx.State):
return return
# 指定消息唯一标识的消息 # 指定消息唯一标识的消息
message = conversation.messages.get(message_id) message = task.messages.get(message_id)
if ( if (
not message not message
or not isinstance(approval := message.approval, Approval) or not isinstance(approval := message.approval, Approval)
@ -299,7 +296,7 @@ class ConversationState(rx.State):
return return
# 将等待流式输出设置为是 # 将等待流式输出设置为是
conversation.awaiting_stream = True task.awaiting_stream = True
# 更新消息内容 # 更新消息内容
message.content = f"您输入的是:\n\n{approval.content}\n\n" message.content = f"您输入的是:\n\n{approval.content}\n\n"
# 更新审批结果 # 更新审批结果
@ -314,19 +311,19 @@ class ConversationState(rx.State):
else: else:
# 若用户提示词为空则直接返回 # 若用户提示词为空则直接返回
if not (user_prompt := conversation.user_prompt): if not (user_prompt := task.user_prompt):
return return
# 清空用户提示词 # 清空用户提示词
conversation.user_prompt = "" task.user_prompt = ""
# 将正在运行设置为是 # 将正在运行设置为是
conversation.is_running = True task.is_running = True
# 将等待流式输出设置为是 # 将等待流式输出设置为是
conversation.awaiting_stream = True task.awaiting_stream = True
# 先创建消息记录再添加消息 # 先创建消息记录再添加消息
conversation.messages.update( task.messages.update(
await db_state.create_message_record( await db_state.create_message_record(
conversation_id=conversation.id, task_id=task.id,
message=Message( message=Message(
type=MessageType.USER_PROMPT, type=MessageType.USER_PROMPT,
content=user_prompt, content=user_prompt,
@ -340,13 +337,13 @@ class ConversationState(rx.State):
# 查询消息历史列表 # 查询消息历史列表
message_history = await db_state.read_message_history( message_history = await db_state.read_message_history(
conversation_id=self.conversation_id task_id=self.task_id
) )
# 使用量 # 使用量
usage = conversation.usage usage = task.usage
# 初始化工作流 # 初始化工作流
match conversation.workflow.type: match task.workflow.type:
case WorkflowType.TALK: case WorkflowType.TALK:
from application.workshop.talk import ( from application.workshop.talk import (
run_stream_events, run_stream_events,
@ -367,7 +364,7 @@ class ConversationState(rx.State):
# 运行并流式输出事件 # 运行并流式输出事件
stream_events = run_stream_events( stream_events = run_stream_events(
deps=conversation.workflow.deps, deps=task.workflow.deps,
user_prompt=user_prompt, user_prompt=user_prompt,
message_history=message_history, message_history=message_history,
usage=usage, usage=usage,
@ -388,7 +385,7 @@ class ConversationState(rx.State):
# 运行并流式输出事件 # 运行并流式输出事件
stream_events = run_stream_events( stream_events = run_stream_events(
deps=conversation.workflow.deps, deps=task.workflow.deps,
user_prompt=user_prompt, user_prompt=user_prompt,
message_history=message_history, message_history=message_history,
usage=usage, usage=usage,
@ -402,7 +399,7 @@ class ConversationState(rx.State):
# 运行并流式输出事件 # 运行并流式输出事件
stream_events = run_stream_events( stream_events = run_stream_events(
deps=conversation.workflow.deps, deps=task.workflow.deps,
user_prompt=user_prompt, user_prompt=user_prompt,
message_history=message_history, message_history=message_history,
usage=usage, usage=usage,
@ -416,7 +413,7 @@ class ConversationState(rx.State):
# 运行并流式输出事件 # 运行并流式输出事件
stream_events = run_stream_events( stream_events = run_stream_events(
deps=conversation.workflow.deps, deps=task.workflow.deps,
user_prompt=user_prompt, user_prompt=user_prompt,
message_history=message_history, message_history=message_history,
usage=usage, usage=usage,
@ -427,8 +424,8 @@ class ConversationState(rx.State):
# 获取运行流式输出事件 # 获取运行流式输出事件
async for event in stream_events: async for event in stream_events:
# 若等待流式输出则将等待流式输出设置为否 # 若等待流式输出则将等待流式输出设置为否
if conversation.awaiting_stream: if task.awaiting_stream:
conversation.awaiting_stream = False task.awaiting_stream = False
match event: match event:
# ========== 开始事件 ========== # ========== 开始事件 ==========
case PartStartEvent( case PartStartEvent(
@ -446,7 +443,7 @@ class ConversationState(rx.State):
is_running=True, is_running=True,
) )
# 添加至消息字典 # 添加至消息字典
conversation.messages[message.id] = message task.messages[message.id] = message
# 将消息唯一标识与片段索引映射 # 将消息唯一标识与片段索引映射
index_map_to_message_id[index] = message.id index_map_to_message_id[index] = message.id
yield # 通知前端更新渲染 yield # 通知前端更新渲染
@ -457,7 +454,7 @@ class ConversationState(rx.State):
): ):
if ( if (
tool_call_id tool_call_id
not in conversation.workflow.tool_call_id_map_to_message_id not in task.workflow.tool_call_id_map_to_message_id
): ):
# 构建消息 # 构建消息
message = Message( message = Message(
@ -466,9 +463,9 @@ class ConversationState(rx.State):
is_running=True, is_running=True,
) )
# 添加至消息字典 # 添加至消息字典
conversation.messages[message.id] = message task.messages[message.id] = message
# 将消息唯一标识与工具调用唯一标识映射 # 将消息唯一标识与工具调用唯一标识映射
conversation.workflow.tool_call_id_map_to_message_id[ task.workflow.tool_call_id_map_to_message_id[
tool_call_id tool_call_id
] = message.id ] = message.id
yield # 通知前端更新渲染 yield # 通知前端更新渲染
@ -478,7 +475,7 @@ class ConversationState(rx.State):
# 构建消息 # 构建消息
message = Message(type=MessageType.TEXT, content=content) message = Message(type=MessageType.TEXT, content=content)
# 添加至消息字典 # 添加至消息字典
conversation.messages[message.id] = message task.messages[message.id] = message
# 将消息唯一标识与片段索引映射 # 将消息唯一标识与片段索引映射
index_map_to_message_id[index] = message.id index_map_to_message_id[index] = message.id
yield # 通知前端更新渲染 yield # 通知前端更新渲染
@ -492,7 +489,7 @@ class ConversationState(rx.State):
content_delta=content_delta, content_delta=content_delta,
provider_details=provider_details, provider_details=provider_details,
): ):
message = conversation.messages[ message = task.messages[
index_map_to_message_id[index] index_map_to_message_id[index]
] ]
# 若内容增量非空则将内容追加至消息内容,否则若供应商详情可调用更新消息内容 # 若内容增量非空则将内容追加至消息内容,否则若供应商详情可调用更新消息内容
@ -521,7 +518,7 @@ class ConversationState(rx.State):
case TextPartDelta( case TextPartDelta(
content_delta=content_delta, content_delta=content_delta,
): ):
message = conversation.messages[ message = task.messages[
index_map_to_message_id[index] index_map_to_message_id[index]
] ]
# 增量更新 # 增量更新
@ -537,7 +534,7 @@ class ConversationState(rx.State):
# 思考分片 # 思考分片
case ThinkingPart(): case ThinkingPart():
# 获取消息 # 获取消息
message = conversation.messages[ message = task.messages[
index_map_to_message_id[index] index_map_to_message_id[index]
] ]
message.is_running = False message.is_running = False
@ -548,7 +545,7 @@ class ConversationState(rx.State):
# 文本分片 # 文本分片
case TextPart(): case TextPart():
# 获取消息 # 获取消息
message = conversation.messages[ message = task.messages[
index_map_to_message_id[index] index_map_to_message_id[index]
] ]
messages.append(message) messages.append(message)
@ -563,8 +560,8 @@ class ConversationState(rx.State):
content=content, content=content,
): ):
# 获取消息 # 获取消息
message = conversation.messages[ message = task.messages[
conversation.workflow.tool_call_id_map_to_message_id[ task.workflow.tool_call_id_map_to_message_id[
tool_call_id tool_call_id
] ]
] ]
@ -580,14 +577,14 @@ class ConversationState(rx.State):
# 若工具调用唯一标识为空则创建消息并添加至消息字典,否则若消息内容非空则根据工具调用唯一标识获取消息并更新消息内容 # 若工具调用唯一标识为空则创建消息并添加至消息字典,否则若消息内容非空则根据工具调用唯一标识获取消息并更新消息内容
if not tool_call_id: if not tool_call_id:
message = Message(type=MessageType.TEXT, content=content) message = Message(type=MessageType.TEXT, content=content)
conversation.messages[message.id] = message task.messages[message.id] = message
yield # 通知前端更新渲染 yield # 通知前端更新渲染
messages.append(message) messages.append(message)
else: else:
if content: if content:
message = conversation.messages[ message = task.messages[
conversation.workflow.tool_call_id_map_to_message_id[ task.workflow.tool_call_id_map_to_message_id[
tool_call_id tool_call_id
] ]
] ]
@ -599,8 +596,8 @@ class ConversationState(rx.State):
if isinstance(output := result.output, DeferredToolRequests): if isinstance(output := result.output, DeferredToolRequests):
for appr in output.approvals: for appr in output.approvals:
# 获取消息 # 获取消息
message = conversation.messages[ message = task.messages[
conversation.workflow.tool_call_id_map_to_message_id[ task.workflow.tool_call_id_map_to_message_id[
tool_call_id := appr.tool_call_id tool_call_id := appr.tool_call_id
] ]
] ]
@ -614,28 +611,28 @@ class ConversationState(rx.State):
else: else:
# 若工作流类型为预定航班则设置为对话 # 若工作流类型为预定航班则设置为对话
if conversation.workflow.type == WorkflowType.BOOK_FLIGHT: if task.workflow.type == WorkflowType.BOOK_FLIGHT:
conversation.workflow = Workflow() task.workflow = Workflow()
# 将正在运行设置为否 # 将正在运行设置为否
conversation.is_running = False task.is_running = False
yield # 通知前端更新渲染 yield # 通知前端更新渲染
# 更新会话记录 # 更新会话记录
await db_state.update_conversation_record( await db_state.update_task_record(
conversation.id, task.id,
usage=result.usage, usage=result.usage,
workflow=conversation.workflow, workflow=task.workflow,
) )
# 创建运行记录 # 创建运行记录
await db_state.create_run_record( await db_state.create_run_record(
conversation_id=conversation.id, task_id=task.id,
new_messages=result.new_messages(), new_messages=result.new_messages(),
) )
# 批量创建消息记录 # 批量创建消息记录
await db_state.create_message_records( await db_state.create_message_records(
conversation_id=conversation.id, task_id=task.id,
messages=messages, messages=messages,
) )
@ -646,10 +643,10 @@ class ConversationState(rx.State):
:return: 当前会话正在运行(True 表示正在运行,False 表示运行完成) :return: 当前会话正在运行(True 表示正在运行,False 表示运行完成)
""" """
# 当前会话 # 当前会话
conversation = self.conversations.get(self.conversation_id) task = self.tasks.get(self.task_id)
if not conversation: if not task:
return False return False
return conversation.is_running return task.is_running
@rx.var @rx.var
def awaiting_stream(self) -> bool: def awaiting_stream(self) -> bool:
@ -658,10 +655,10 @@ class ConversationState(rx.State):
:return: 当前会话正在等待流式输出(True 表示等待流式输出,False 表示已开始流式输出或已完成) :return: 当前会话正在等待流式输出(True 表示等待流式输出,False 表示已开始流式输出或已完成)
""" """
# 当前会话 # 当前会话
conversation = self.conversations.get(self.conversation_id) task = self.tasks.get(self.task_id)
if not conversation: if not task:
return False return False
return conversation.awaiting_stream return task.awaiting_stream
@rx.event @rx.event
def toggle_message_history_item_shown(self, item_id: str) -> None: def toggle_message_history_item_shown(self, item_id: str) -> None:
@ -669,10 +666,10 @@ class ConversationState(rx.State):
展示 / 隐藏消息历史项 展示 / 隐藏消息历史项
""" """
# 当前会话 # 当前会话
conversation = self.conversations.get(self.conversation_id) task = self.tasks.get(self.task_id)
if not conversation: if not task:
return return
conversation.messages[item_id].is_shown ^= True task.messages[item_id].is_shown ^= True
@rx.event @rx.event
def set_approval_content(self, item_id: str, content: str) -> None: def set_approval_content(self, item_id: str, content: str) -> None:
@ -682,12 +679,12 @@ class ConversationState(rx.State):
:param content: 内容 :param content: 内容
""" """
# 当前会话 # 当前会话
conversation = self.conversations.get(self.conversation_id) task = self.tasks.get(self.task_id)
if not conversation: if not task:
return return
# 获取消息 # 获取消息
message = conversation.messages.get(item_id) message = task.messages.get(item_id)
if not message or not isinstance(approval := message.approval, Approval): if not message or not isinstance(approval := message.approval, Approval):
return return
@ -702,8 +699,8 @@ class ConversationState(rx.State):
设置工作流 设置工作流
""" """
# 当前会话 # 当前会话
conversation = self.conversations.get(self.conversation_id) task = self.tasks.get(self.task_id)
if not conversation: if not task:
return return
match type: match type:
@ -754,11 +751,11 @@ class ConversationState(rx.State):
workflow = Workflow() workflow = Workflow()
# 设置工作流 # 设置工作流
conversation.workflow = workflow task.workflow = workflow
# 获取数据库状态 # 获取数据库状态
db_state = await self.get_db_state() db_state = await self.get_db_state()
# 更新会话记录 # 更新会话记录
await db_state.update_conversation_record( await db_state.update_task_record(
conversation.id, task.id,
workflow=conversation.workflow, workflow=task.workflow,
) )

View File

@ -0,0 +1 @@
<svg viewBox="0 0 14.4531 14.6597" fill="currentColor" xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="wb-icon" aria-hidden="true"><path transform="matrix(1 0 0 1 0 0.0102059)" d="M7.2266 0.0001C7.9263 0.0001 8.9357 -0.0101 9.9521 0.17C10.9684 0.3502 12.061 0.7315 12.8926 1.5636C14.2855 2.9577 14.4531 4.8385 14.4531 6.2804C14.4531 7.7222 14.2853 9.6021 12.8926 10.9962C12.094 11.7954 11.0936 12.1824 10.1406 12.3721C9.1916 12.5611 8.2453 12.5626 7.5312 12.5606C7.2983 12.56 6.9785 12.7013 6.5684 13.0948C6.17 13.4771 5.7651 14.0227 5.3662 14.6495L4.3535 14.005C4.7714 13.3484 5.2367 12.709 5.7373 12.2286C6.2261 11.7597 6.8348 11.3587 7.5342 11.3604C8.2533 11.3624 9.0891 11.3581 9.9062 11.1954C10.7192 11.0335 11.4695 10.7233 12.0439 10.1485C13.073 9.1185 13.2539 7.6783 13.2539 6.2804C13.2539 4.8825 13.0729 3.4423 12.0439 2.4122C11.4564 1.8243 10.6378 1.5103 9.7432 1.3517C8.8486 1.1931 7.9467 1.1993 7.2266 1.1993C6.5064 1.1993 5.6045 1.1931 4.71 1.3517C3.8153 1.5103 2.9967 1.8242 2.4092 2.4122C1.3801 3.4423 1.1992 4.8825 1.1992 6.2804C1.1993 7.6783 1.3802 9.1185 2.4092 10.1485C3.0494 10.7892 3.9083 11.1001 4.8252 11.2442L4.7324 11.837L4.6387 12.4298C3.5897 12.2648 2.45 11.8863 1.5605 10.9962C0.1678 9.6021 0 7.7222 0 6.2804C0 4.8385 0.1676 2.9577 1.5605 1.5636C2.392 0.7316 3.4838 0.3502 4.5 0.17C5.5166 -0.0102 6.5267 0.0001 7.2266 0.0001Z"></path><path transform="matrix(1 0 0 1 4.52539 3.5943)" d="M3.2959 2.0967L5.3994 2.0967L5.3994 3.2959L3.2959 3.2959L3.2959 5.4023L2.0967 5.4023L2.0967 3.2959L0 3.2959L0 2.0967L2.0967 2.0967L2.0967 0L3.2959 0L3.2959 2.0967Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,13 +1,16 @@
:root { :root {
--prismui-background-color-1: #ffffff; --prismui-background-color-1: #ffffff;
--prismui-background-color-2: #ebebeb; --prismui-background-color-2: #ebebeb;
--prismui-background-color-3: #f6f6f8; --prismui-background-color-3: rgb(230, 230, 230);
--prismui-background-color-4: #babbc0; --prismui-background-color-4: #babbc0;
--prismui-background-color-5: #ffffff33; --prismui-background-color-5: #ffffff33;
--prismui-background-color-6: #f2f2f3; --prismui-background-color-6: #f2f2f3;
--prismui-background-color-7: #5e7ce0; --prismui-background-color-7: #5e7ce0;
--prismui-background-color-8: #beccfa; --prismui-background-color-8: #beccfa;
/*
--prismui-background-1: linear-gradient(to bottom, #D0C9FF 0%, #E6D6F0 8%, #F1DBEA 12%, #C8DCFB 40%, #ABC6F6 60%, #87AEFE 90%); --prismui-background-1: linear-gradient(to bottom, #D0C9FF 0%, #E6D6F0 8%, #F1DBEA 12%, #C8DCFB 40%, #ABC6F6 60%, #87AEFE 90%);
*/
--prismui-background-1: rgb(242, 242, 242);
--prismui-background-2: linear-gradient(180deg, #fffffff2, #f8fafff2 99%); --prismui-background-2: linear-gradient(180deg, #fffffff2, #f8fafff2 99%);
--prismui-background-3: linear-gradient(to right, #f3efff, #f3efff33, #e2f1fd33, #e2f1fd); --prismui-background-3: linear-gradient(to right, #f3efff, #f3efff33, #e2f1fd33, #e2f1fd);
--prismui-font-family: HuaweiFont,Helvetica,Arial,PingFangSC-Regular,Hiragino Sans GB,Microsoft YaHei,微软雅黑,Microsoft JhengHei; --prismui-font-family: HuaweiFont,Helvetica,Arial,PingFangSC-Regular,Hiragino Sans GB,Microsoft YaHei,微软雅黑,Microsoft JhengHei;
@ -18,7 +21,8 @@
--prismui-font-size-5: 20px; --prismui-font-size-5: 20px;
--prismui-font-weight-1: normal; --prismui-font-weight-1: normal;
--prismui-font-weight-2: 500; --prismui-font-weight-2: 500;
--prismui-font-weight-3: 700; --prismui-font-weight-3: 600;
--prismui-font-weight-4: 700;
--prismui-color-1: #252b3a; --prismui-color-1: #252b3a;
--prismui-color-2: #575d6c; --prismui-color-2: #575d6c;
--prismui-color-3: #babbc0; --prismui-color-3: #babbc0;

Binary file not shown.