From bcd2616d115b8ff1a1d84aab334e11f6ec885872 Mon Sep 17 00:00:00 2001 From: liubiren Date: Mon, 12 Oct 2026 16:51:15 +0800 Subject: [PATCH] 1 --- agent/application/application.py | 6 +- agent/application/pages/__init__.py | 5 +- .../application/pages/{index.py => login.py} | 246 +--------- .../pages/{conversation.py => task.py} | 186 ++++++-- agent/application/pages/workbench.py | 427 ++++++++++++++++++ agent/application/states/__init__.py | 4 +- agent/application/states/auth.py | 32 +- agent/application/states/database.py | 132 +++--- agent/application/states/models.py | 22 +- .../states/{conversation.py => task.py} | 279 ++++++------ agent/assets/create_task.svg | 1 + agent/assets/styles.css | 8 +- agent/database.db | Bin 479232 -> 479232 bytes 13 files changed, 815 insertions(+), 533 deletions(-) rename agent/application/pages/{index.py => login.py} (54%) rename agent/application/pages/{conversation.py => task.py} (87%) create mode 100644 agent/application/pages/workbench.py rename agent/application/states/{conversation.py => task.py} (74%) create mode 100644 agent/assets/create_task.svg diff --git a/agent/application/application.py b/agent/application/application.py index 8bf5150..7421924 100644 --- a/agent/application/application.py +++ b/agent/application/application.py @@ -4,7 +4,7 @@ """ import reflex as rx -from application.pages.index import index +from application.pages.workbench import workbench from application.workshop.database import connection_pool_init @@ -55,7 +55,7 @@ app = rx.App( }, ) # 此处变量名需使用 app ,具体原因目前尚不清楚 -# 添加页面:首页 -app.add_page(component=index(), route="/") +# 添加页面:工作台 +app.add_page(component=workbench(), route="/") # 注册声明周期任务:连接池初始化 app.register_lifespan_task(connection_pool_init) diff --git a/agent/application/pages/__init__.py b/agent/application/pages/__init__.py index 2c3cd2c..22230e4 100644 --- a/agent/application/pages/__init__.py +++ b/agent/application/pages/__init__.py @@ -1,6 +1,7 @@ # -*- 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.login import login_page -__all__ = ["conversation_page", "knowledge_base_page"] +__all__ = ["create_task_page", "knowledge_base_page", "login_page"] diff --git a/agent/application/pages/index.py b/agent/application/pages/login.py similarity index 54% rename from agent/application/pages/index.py rename to agent/application/pages/login.py index 927c9da..c120339 100644 --- a/agent/application/pages/index.py +++ b/agent/application/pages/login.py @@ -1,218 +1,14 @@ # -*- coding: utf-8 -*- """ -首页 +登录页面 """ import reflex as rx -from application.pages import conversation_page, knowledge_base_page from application.states import AuthState - -# 导航按钮字典 -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: +def login_page() -> 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 """ @@ -225,7 +21,7 @@ def login_popup() -> rx.Component: margin_bottom="24px", line_height="32px", font_size="var(--prismui-font-size-5)", - font_weight="var(--prismui-font-weight-3)", + font_weight="var(--prismui-font-weight-4)", ), # 邮箱输入框 rx.input( @@ -422,36 +218,4 @@ def login_popup() -> rx.Component: width="100vw", height="100vh", 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(), - ), - ) + ) \ No newline at end of file diff --git a/agent/application/pages/conversation.py b/agent/application/pages/task.py similarity index 87% rename from agent/application/pages/conversation.py rename to agent/application/pages/task.py index 3242d37..b819d28 100644 --- a/agent/application/pages/conversation.py +++ b/agent/application/pages/task.py @@ -4,28 +4,28 @@ """ import reflex as rx -from application.states import AuthState, ConversationState +from application.states import AuthState, TaskState from application.states.models import ( - ConversationHistoryItem, + TaskHistoryItem, MessageHistoryItem, MessageType, WorkflowType, ) -def conversation_history_item( - item: ConversationHistoryItem, +def task_history_item( + item: TaskHistoryItem, ) -> rx.Component: """ - 会话历史项 - :param item: 会话历史项 + 任务历史项 + :param item: 任务历史项 :return: Component """ # 高亮 is_highlight = ( - item.id == ConversationState.popover_shown_conversation_history_item_id - ) | (item.id == ConversationState.conversation_id) + item.id == TaskState.popover_shown_task_history_item_id + ) | (item.id == TaskState.task_id) return rx.list.item( rx.vstack( @@ -62,8 +62,8 @@ def conversation_history_item( height="20px", margin_bottom="8px", ), - # 点击事件:设置激活会话 - on_click=lambda: ConversationState.set_conversation(item.id), + # 点击事件:设置激活任务 + on_click=lambda: TaskState.set_task(item.id), width="100%", ), # 会话历史项的更多按钮 @@ -94,8 +94,8 @@ def conversation_history_item( rx.popover.close( rx.box( "删除", - # 点击事件:删除对话 - on_click=lambda: ConversationState.delete_conversation( + # 点击事件:删除任务 + on_click=lambda: TaskState.delete_task( item.id ), display="flex", @@ -128,8 +128,8 @@ def conversation_history_item( box_shadow="var(--prismui-box-shadow-6)", 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 ), 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( is_conversation_history_shown: bool, ) -> rx.Component: @@ -195,8 +248,8 @@ def conversation_history( ), rx.auto_scroll( rx.foreach( - ConversationState.conversation_history, - conversation_history_item, + TaskState.task_history, + task_history_item, ), flex="1", align_items="stretch", @@ -235,7 +288,7 @@ def workflow_button(workflow_type: WorkflowType): return rx.box( workflow_type.value, # 点击事件:设置工作流 - on_click=lambda: ConversationState.set_workflow(workflow_type), + on_click=lambda: TaskState.set_workflow(workflow_type), padding="10px 16px", background_color="var(--prismui-background-color-6)", border_radius="var(--prismui-border-radius-9)", @@ -399,7 +452,7 @@ def thinking(item: MessageHistoryItem) -> rx.Component: line_height="22px", ), # 点击事件,展示 / 隐藏消息历史项 - on_click=lambda: ConversationState.toggle_message_history_item_shown( + on_click=lambda: TaskState.toggle_message_history_item_shown( item.id ), cursor="pointer", @@ -462,7 +515,7 @@ def tool_call(item: MessageHistoryItem) -> rx.Component: line_height="22px", ), # 点击事件,展示 / 隐藏消息历史项 - on_click=lambda: ConversationState.toggle_message_history_item_shown( + on_click=lambda: TaskState.toggle_message_history_item_shown( item.id ), cursor="pointer", @@ -496,7 +549,7 @@ def tool_call(item: MessageHistoryItem) -> rx.Component: # 值绑定审批内容 value=item.approval_content, # 值变化事件:设置审批内容 - on_change=lambda content: ConversationState.set_approval_content( + on_change=lambda content: TaskState.set_approval_content( item.id, content ), placeholder="请输入", @@ -523,7 +576,7 @@ def tool_call(item: MessageHistoryItem) -> rx.Component: rx.button( "提交", # 点击事件:审批 - on_click=lambda: ConversationState.run( + on_click=lambda: TaskState.run( True, item.id ), padding="0 12px", @@ -711,17 +764,17 @@ def message_history() -> rx.Component: return rx.box( # 若消息历史为空则显示使用指引,否则渲染消息历史项 rx.cond( - ConversationState.message_history.length() == 0, + TaskState.message_history.length() == 0, use_guidance(), rx.vstack( rx.auto_scroll( rx.vstack( rx.foreach( - ConversationState.message_history, message_history_item + TaskState.message_history, message_history_item ), # 若等待流式输出则显示加载动效 rx.cond( - ConversationState.awaiting_stream, + TaskState.awaiting_stream, rx.box( rx.box( class_name="awaiting_stream", @@ -789,7 +842,7 @@ def conversation_creating() -> rx.Component: rx.tooltip( rx.icon( "plus", - on_click=ConversationState.create_conversation, + on_click=TaskState.create_task, width="14px", height="14px", style={ @@ -798,7 +851,7 @@ def conversation_creating() -> rx.Component: } }, ), - content="创建会话", + content="创建任务", side="top", side_offset=9, background_color="var(--prismui-background-color-1)", @@ -838,9 +891,9 @@ def user_prompt_sending() -> rx.Component: rx.vstack( 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换行", vertical_align="middle", width="100%", @@ -878,19 +931,19 @@ def user_prompt_sending() -> rx.Component: line_height="21px", 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", position="relative", justify_content="center", align_items="center", padding="0 12px", 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-5)", ), @@ -900,7 +953,7 @@ def user_prompt_sending() -> rx.Component: font_size="var(--prismui-font-size-2)", white_space="nowrap", cursor=rx.cond( - ConversationState.is_user_prompt_sending_button_disabled, + TaskState.is_user_prompt_sending_button_disabled, "not-allowed", "pointer", ), @@ -957,13 +1010,8 @@ def conversation_history_toggle_button( height="16px", color="var(--prismui-color-3)", ), - # 点击事件:切换会话历史折叠面板展开状态 - on_click=ConversationState.toggle_conversation_history_shown, position="absolute", top="calc(50% - 20px)", - left=rx.cond( - is_conversation_history_shown, "calc(clamp(240px, 25%, 380px) - 8px)", "0" - ), z_index="99", width="16px", 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( rx.hstack( - # 会话历史 - conversation_history(ConversationState.is_conversation_history_shown), + # 任务历史 # 工作区 rx.box( # 对话历史 @@ -1015,15 +1062,56 @@ def conversation_page() -> rx.Component: overflow="hidden", transition="all 0.18s ease-in-out", ), - # 会话历史显示 / 隐藏按钮 - conversation_history_toggle_button( - ConversationState.is_conversation_history_shown - ), - # 挂载事件:恢复会话状态 - on_mount=AuthState.resume_conversation_state, + # 挂载事件:恢复任务状态 + on_mount=AuthState.resume_task_state, position="relative", width="100%", height="100%", 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", + width="100%", + height="100%", + border_radius="var(--prismui-border-radius-4)", + overflow="hidden", + ) \ No newline at end of file diff --git a/agent/application/pages/workbench.py b/agent/application/pages/workbench.py new file mode 100644 index 0000000..9797736 --- /dev/null +++ b/agent/application/pages/workbench.py @@ -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", + ), + ) diff --git a/agent/application/states/__init__.py b/agent/application/states/__init__.py index cb0d944..7f2c655 100644 --- a/agent/application/states/__init__.py +++ b/agent/application/states/__init__.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- from application.states.auth import AuthState -from application.states.conversation import ConversationState +from application.states.task import TaskState from application.states.database import DatabaseState __all__ = [ "AuthState", - "ConversationState", + "TaskState", "DatabaseState", ] diff --git a/agent/application/states/auth.py b/agent/application/states/auth.py index cbe4de1..6ab209e 100644 --- a/agent/application/states/auth.py +++ b/agent/application/states/auth.py @@ -12,7 +12,7 @@ from aiosmtplib import SMTP, SMTPException import reflex as rx from reflex.event import EventCallback -from application.states.conversation import ConversationState +from application.states.task import TaskState from application.states.database import DatabaseState # 验证码HTML模板 @@ -204,14 +204,14 @@ class AuthState(rx.State): self.is_policies_agreed = not self.is_policies_agreed @rx.event - async def resume_conversation_state(self): + async def resume_task_state(self): """ - 恢复会话状态 + 恢复任务状态 """ - # 获取会话状态 - conversation_state = await self.get_state(ConversationState) - # 恢复会话状态 - await conversation_state.resume_conversation_state(user_id=self.user_id) + # 获取任务状态 + task_state = await self.get_state(TaskState) + # 恢复任务状态 + await task_state.resume_task_state(user_id=self.user_id) @rx.event async def login(self) -> None: @@ -254,10 +254,10 @@ class AuthState(rx.State): # 创建用户记录 user_id = await db_state.create_user_record(email=self.email) - # 获取会话状态 - conversation_state = await self.get_state(ConversationState) - # 恢复当前用户的会话状态 - await conversation_state.resume_conversation_state(user_id=user_id) + # 获取任务状态 + task_state = await self.get_state(TaskState) + # 恢复当前用户的任务状态 + await task_state.resume_task_state(user_id=user_id) self.user_id = user_id @@ -292,9 +292,9 @@ class AuthState(rx.State): :return: None """ self.is_settings_hover_card_open = False # 设置设置悬停卡片关闭 - conversation_state = await self.get_state(ConversationState) - conversation_state.user_id = "" - conversation_state.conversations = {} - conversation_state.actived_conversation_id = "" - conversation_state.is_conversation_history_shown = False + task_state = await self.get_state(TaskState) + task_state.user_id = "" + task_state.tasks = {} + task_state.actived_task_id = "" + task_state.is_task_history_shown = False self.user_id = "" diff --git a/agent/application/states/database.py b/agent/application/states/database.py index 745f217..164575c 100644 --- a/agent/application/states/database.py +++ b/agent/application/states/database.py @@ -13,7 +13,7 @@ from sqlmodel import Field, SQLModel, select, update from application.states.models import ( Approval, - Conversation, + Task, Message, MessageType, Workflow, @@ -67,30 +67,30 @@ class UserRecord(SQLModel, table=True): email: str = Field(..., index=True, description="邮箱") -class ConversationRecord(SQLModel, table=True): +class TaskRecord(SQLModel, table=True): """ - 会话记录 + 任务记录 """ id: str = Field( default_factory=lambda: str(uuid7()), primary_key=True, - description="会话唯一标识", + description="任务唯一标识", ) user_id: str = Field(..., index=True, description="用户唯一标识") - description: str = Field(default="新会话", description="会话描述") + description: str = Field(default="新任务", description="任务描述") created_at: datetime = Field( - default_factory=datetime.now, description="会话创建时间" + default_factory=datetime.now, description="任务创建时间" ) is_deleted: bool = Field( default=False, index=True, - description="会话已删除:True 表示已删除,False 表示未删除", + description="任务已删除:True 表示已删除,False 表示未删除", ) - usage: str = Field(default="", description="会话使用量") + usage: str = Field(default="", description="任务使用量") workflow: str = Field( default="", - description="会话工作流", + description="任务工作流", ) # 原则上嵌套列表或字典若不涉及查询则以字符串储存 @@ -104,7 +104,7 @@ class RunRecord(SQLModel, table=True): primary_key=True, description="运行唯一标识", ) - conversation_id: str = Field(..., index=True, description="会话唯一标识") + task_id: str = Field(..., index=True, description="任务唯一标识") new_messages: str = Field(..., description="新增消息") @@ -118,7 +118,7 @@ class MessageRecord(SQLModel, table=True): primary_key=True, description="消息唯一标识", ) - conversation_id: str = Field(..., index=True, description="会话唯一标识") + task_id: str = Field(..., index=True, description="任务唯一标识") type: MessageType = Field(..., description="消息类型") title: str = Field(default="", description="消息标题") content: str = Field(default="", description="消息内容") @@ -204,66 +204,66 @@ class DatabaseState(rx.State): await session.refresh(record) 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: 用户唯一标识 - :return: 会话实例 + :return: 任务实例 """ async with rx.asession() as session: - record = ConversationRecord(user_id=user_id) + record = TaskRecord(user_id=user_id) session.add(record) await session.commit() await session.refresh(record) - return { - record.id: Conversation( - id=record.id, - description=record.description, - usage=usage_validate_json( - record.usage - ), # 将 record.usage 由 JSON 字符串反序列化为 RunUsage - created_at=record.created_at, - ) - } + return { + record.id: Task( + id=record.id, + description=record.description, + usage=usage_validate_json( + record.usage + ), # 将 record.usage 由 JSON 字符串反序列化为 RunUsage + 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: 用户唯一标识 - :return: 当前用户的会话字典 + :return: 当前用户的任务字典 """ - conversations: dict[str, Conversation] = {} + tasks: dict[str, Task] = {} async with rx.asession() as session: result = await session.exec( - select(ConversationRecord, MessageRecord) - .outerjoin(MessageRecord, MessageRecord.conversation_id == ConversationRecord.id) # type: ignore + select(TaskRecord, MessageRecord) + .outerjoin(MessageRecord, MessageRecord.task_id == TaskRecord.id) # type: ignore .where( - ConversationRecord.user_id == user_id, - ConversationRecord.is_deleted == False, + TaskRecord.user_id == user_id, + TaskRecord.is_deleted == False, ) - .order_by(ConversationRecord.id, MessageRecord.id) + .order_by(TaskRecord.id, MessageRecord.id) ) for ( - conversation_record, + task_record, message_record, ) in result.all(): - conversation = conversations.setdefault( - conversation_record.id, - Conversation( - id=conversation_record.id, - description=conversation_record.description, + task = tasks.setdefault( + task_record.id, + Task( + id=task_record.id, + description=task_record.description, usage=usage_validate_json( - conversation_record.usage - ), # 将 conversation_record.usage 由 JSON 字符串反序列化为 RunUsage - created_at=conversation_record.created_at, + task_record.usage + ), # 将 task_record.usage 由 JSON 字符串反序列化为 RunUsage + created_at=task_record.created_at, workflow=workflow_validate_json( - conversation_record.workflow - ), # 将 conversation_record.workflow 由 JSON 字符串反序列化为 Workflow, + task_record.workflow + ), # 将 task_record.workflow 由 JSON 字符串反序列化为 Workflow, ), ) if not message_record: continue - conversation.messages.setdefault( + task.messages.setdefault( message_record.id, Message( id=message_record.id, @@ -273,27 +273,27 @@ class DatabaseState(rx.State): approval=approval_validate_json(message_record.approval), ), ) - return conversations + return tasks - async def update_conversation_record( + async def update_task_record( self, - conversation_id: str, + task_id: str, description: str | None = None, is_deleted: bool | None = None, usage: RunUsage | None = None, workflow: Workflow | None = None, ) -> None: """ - 更新会话记录 - :param conversation_id: 指定会话唯一标识 - :param description: 会话描述 - :param is_deleted: 会话已删除 + 更新任务记录 + :param task_id: 指定任务唯一标识 + :param description: 任务描述 + :param is_deleted: 任务已删除 :param usage: 使用量 :param workflow: 工作流 :return: None """ async with rx.asession() as session: - record = await session.get(ConversationRecord, conversation_id) + record = await session.get(TaskRecord, task_id) if not record: return if isinstance(description, str): @@ -312,19 +312,19 @@ class DatabaseState(rx.State): async def create_message_record( self, - conversation_id: str, + task_id: str, message: Message, ) -> dict[str, Message]: """ 创建消息记录 - :param conversation_id: 会话唯一标识 + :param task_id: 任务唯一标识 :param message: 消息实例 :return: 消息实例 """ async with rx.asession() as session: record = MessageRecord( id=message.id, - conversation_id=conversation_id, + task_id=task_id, type=message.type, title=message.title, content=message.content, @@ -337,12 +337,12 @@ class DatabaseState(rx.State): async def create_message_records( self, - conversation_id: str, + task_id: str, messages: list[Message], ) -> None: """ 创建消息记录 - :param conversation_id: 会话唯一标识 + :param task_id: 任务唯一标识 :param messages: 消息实例列表 :return: None """ @@ -360,7 +360,7 @@ class DatabaseState(rx.State): session.add( MessageRecord( id=message.id, - conversation_id=conversation_id, + task_id=task_id, type=message.type, title=message.title, content=message.content, @@ -388,17 +388,17 @@ class DatabaseState(rx.State): record.approval = approval_dump_json(approval) 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: 消息历史 """ message_history: list[ModelMessage] = [] async with rx.asession() as session: result = await session.exec( select(RunRecord) - .where(RunRecord.conversation_id == conversation_id) + .where(RunRecord.task_id == task_id) .order_by(RunRecord.id) ) for run in result.all(): @@ -411,19 +411,19 @@ class DatabaseState(rx.State): async def create_run_record( self, - conversation_id: str, + task_id: str, new_messages: list[ModelMessage], ) -> None: """ 创建运行记录 - :param conversation_id: 会话唯一标识 + :param task_id: 任务唯一标识 :param new_messages: 新增消息 :return: None """ async with rx.asession() as session: session.add( RunRecord( - conversation_id=conversation_id, + task_id=task_id, new_messages=ModelMessagesTypeAdapter.dump_json( new_messages ).decode("utf-8"), diff --git a/agent/application/states/models.py b/agent/application/states/models.py index c28be92..a2581e9 100644 --- a/agent/application/states/models.py +++ b/agent/application/states/models.py @@ -77,15 +77,15 @@ class Workflow(BaseModel): ) -class Conversation(BaseModel): +class Task(BaseModel): """ - 会话类 + 任务类 """ - id: str = Field(..., description="会话唯一标识") - description: str = Field(..., description="会话描述") - created_at: datetime = Field(..., description="会话创建日期时间") - usage: RunUsage = Field(..., description="会话使用量") + id: str = Field(..., description="任务唯一标识") + description: str = Field(..., description="任务描述") + created_at: datetime = Field(..., description="任务创建日期时间") + usage: RunUsage = Field(..., description="任务使用量") messages: dict[str, Message] = Field(default_factory=dict, description="消息字典") workflow: Workflow = Field(default_factory=Workflow, description="工作,默认为对话") user_prompt: str = Field(default="", description="用户提示词") @@ -98,14 +98,14 @@ class Conversation(BaseModel): ) -class ConversationHistoryItem(BaseModel): +class TaskHistoryItem(BaseModel): """ - 会话历史项类 + 任务历史项类 """ - id: str = Field(..., description="会话唯一标识") - description: str = Field(..., description="会话描述") - created_at: str = Field(..., description="会话创建日期时间") + id: str = Field(..., description="任务唯一标识") + description: str = Field(..., description="任务描述") + created_at: str = Field(..., description="任务创建日期时间") class MessageHistoryItem(Message): diff --git a/agent/application/states/conversation.py b/agent/application/states/task.py similarity index 74% rename from agent/application/states/conversation.py rename to agent/application/states/task.py index 7d1021d..9cd88bc 100644 --- a/agent/application/states/conversation.py +++ b/agent/application/states/task.py @@ -24,8 +24,8 @@ import reflex as rx from application.states.database import DatabaseState from application.states.models import ( Approval, - Conversation, - ConversationHistoryItem, + Task, + TaskHistoryItem, Message, MessageHistoryItem, MessageType, @@ -51,24 +51,21 @@ def format_conversation_created_at(created_at: datetime) -> str: return formatted_created_at -class ConversationState(rx.State): +class TaskState(rx.State): """ - 会话状态 + 任务状态 """ # 当前用户唯一标识 user_id: str = "" - # 当前用户的会话字典(私有变量) - # 当前用户的会话字典 - conversations: dict[str, Conversation] = {} # 键为会话唯一标识,值为会话 - # 当前会话唯一标识 - conversation_id: str = "" + # 当前用户的任务字典 + tasks: dict[str, Task] = {} # 键为任务唯一标识,值为任务实例 + # 当前任务唯一标识 + task_id: str = "" - # 显示会话历史,True 表示显示,False 表示隐藏 - is_conversation_history_shown: bool = False - # 悬停气泡显示的会话历史项唯一标识(一个会话只允许存在一个悬停气泡会话历史项) - popover_shown_conversation_history_item_id: str = "" + # 悬停气泡显示的任务历史项唯一标识(一个任务只允许存在一个悬停气泡任务历史项) + popover_shown_task_history_item_id: str = "" # 当前数据库状态(私有变量) # 私有变量:reflex 约定以 _ 开头的变量为私有变量,后端不序列化,前端不可使用 @@ -84,9 +81,9 @@ class ConversationState(rx.State): self._db_state = await self.get_state(DatabaseState) 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: 用户唯一标识 :return: None """ @@ -96,54 +93,54 @@ class ConversationState(rx.State): # 获取当前数据库状态 db_state = await self.get_db_state() - # 查询当前用户的会话字典 - self.conversations = await db_state.read_conversations(self.user_id) - # 若当前用户的会话字典为空则先创建会话记录再添加会话 - if not self.conversations: - self.conversations.update( - await db_state.create_conversation_record(self.user_id) + # 查询当前用户的任务字典 + self.tasks = await db_state.read_tasks(self.user_id) + # 若当前用户的任务字典为空则先创建任务记录再添加任务 + if not self.tasks: + self.tasks.update( + 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) - # 若最后一个消息包含审批且审批结果为 None 则将会话正在运行设置为是、消息正在运行设置为是 + message = next(iter(reversed(task.messages.values())), None) + # 若最后一个消息包含审批且审批结果为 None 则任务正在运行设置为是、消息正在运行设置为是 if ( message is not None and isinstance(approval := message.approval, Approval) and approval.result is None ): - conversation.is_running = True + task.is_running = True message.is_running = True @rx.event - def toggle_conversation_history_shown(self) -> None: + def toggle_task_history_shown(self) -> 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 - def conversation_history(self) -> list[ConversationHistoryItem]: + def task_history(self) -> list[TaskHistoryItem]: """ 获取会话历史项列表(按照会话创建日期时间降序排序) - :return: 会话历史项列表 + :return: 任务历史项列表 """ return [ - ConversationHistoryItem( - id=conversation.id, - description=conversation.description, - created_at=format_conversation_created_at(conversation.created_at), + TaskHistoryItem( + id=task.id, + description=task.description, + 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 - def set_conversation_history_item_popover_shown( + def set_task_history_item_popover_shown( self, item_id: str, is_shown: bool ) -> None: """ @@ -153,36 +150,36 @@ class ConversationState(rx.State): self.popover_shown_conversation_history_item_id = item_id if is_shown else "" @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 """ # 获取当前数据库状态 db_state = await self.get_db_state() - # 更新会话记录 - await db_state.update_conversation_record(conversation_id, is_deleted=True) - del self.conversations[conversation_id] + # 更新任务记录 + await db_state.update_task_record(task_id, is_deleted=True) + del self.tasks[task_id] - # 删除后,若当前用户的会话字典为空则创建会话 - if not self.conversations: - self.conversations.update( - await db_state.create_conversation_record(self.user_id) + # 删除后,若当前用户的任务字典为空则创建任务 + if not self.tasks: + self.tasks.update( + await db_state.create_task_record(self.user_id) ) - # 删除后,若当前会话唯一标识不存在则将最后一个会话的唯一标识作为当前会话唯一标识 - if self.conversation_id not in self.conversations: - self.conversation_id = next(reversed(self.conversations.keys())) + # 删除后,若当前任务唯一标识不存在则将最后一个任务的唯一标识作为当前任务唯一标识 + if self.task_id not in self.tasks: + self.task_id = next(reversed(self.tasks.keys())) @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 """ - self.conversation_id = conversation_id + self.task_id = task_id @rx.var def message_history(self) -> list[MessageHistoryItem]: @@ -190,12 +187,12 @@ class ConversationState(rx.State): 获取当前会话的消息历史 :return: 当前会话的消息历史 """ - # 当前会话 - conversation = self.conversations.get(self.conversation_id) - if not conversation: + # 当前任务 + task = self.tasks.get(self.task_id) + if not task: return [] message_history: list[MessageHistoryItem] = [] - for message in conversation.messages.values(): + for message in task.messages.values(): message_history.append( MessageHistoryItem( **message.model_dump(), @@ -207,19 +204,19 @@ class ConversationState(rx.State): return message_history @rx.event - async def create_conversation(self) -> None: + async def create_task(self) -> None: """ - 创建会话 + 创建任务 :return: None """ # 获取当前数据库状态 db_state = await self.get_db_state() - # 创建会话记录再添加会话 - self.conversations.update( - await db_state.create_conversation_record(self.user_id) + # 创建任务记录再添加任务 + self.tasks.update( + 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 def user_prompt(self) -> str: @@ -227,11 +224,11 @@ class ConversationState(rx.State): 用户提示词 :return: 用户提示词 """ - # 当前会话 - conversation = self.conversations.get(self.conversation_id) - if not conversation: + # 当前任务 + task = self.tasks.get(self.task_id) + if not task: return "" - return conversation.user_prompt + return task.user_prompt @rx.event def set_user_prompt(self, user_prompt: str) -> None: @@ -240,15 +237,15 @@ class ConversationState(rx.State): :param user_prompt: 用户提示词 :return: None """ - # 当前会话 - conversation = self.conversations.get(self.conversation_id) - if not conversation: + # 当前任务 + task = self.tasks.get(self.task_id) + if not task: return if not user_prompt.strip(): return - conversation.user_prompt = user_prompt + task.user_prompt = user_prompt @rx.var def is_user_prompt_sending_button_disabled(self) -> bool: @@ -256,11 +253,11 @@ class ConversationState(rx.State): 用户提示词发送按钮不可点击状态 :return: bool """ - # 当前会话 - conversation = self.conversations.get(self.conversation_id) - if not conversation: + # 当前任务 + task = self.tasks.get(self.task_id) + if not task: return True - return conversation.is_running or not conversation.user_prompt + return task.is_running or not task.user_prompt @rx.event async def run( @@ -274,9 +271,9 @@ class ConversationState(rx.State): :param message_id: 消息唯一标识 :return: AsyncGenerator """ - # 当前会话 - conversation = self.conversations.get(self.conversation_id) - if not conversation: + # 当前任务 + task = self.tasks.get(self.task_id) + if not task: return # 获取当前数据库状态 @@ -290,7 +287,7 @@ class ConversationState(rx.State): return # 指定消息唯一标识的消息 - message = conversation.messages.get(message_id) + message = task.messages.get(message_id) if ( not message or not isinstance(approval := message.approval, Approval) @@ -299,7 +296,7 @@ class ConversationState(rx.State): return # 将等待流式输出设置为是 - conversation.awaiting_stream = True + task.awaiting_stream = True # 更新消息内容 message.content = f"您输入的是:\n\n{approval.content}\n\n" # 更新审批结果 @@ -314,19 +311,19 @@ class ConversationState(rx.State): else: # 若用户提示词为空则直接返回 - if not (user_prompt := conversation.user_prompt): + if not (user_prompt := task.user_prompt): 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( - conversation_id=conversation.id, + task_id=task.id, message=Message( type=MessageType.USER_PROMPT, content=user_prompt, @@ -340,13 +337,13 @@ class ConversationState(rx.State): # 查询消息历史列表 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: from application.workshop.talk import ( run_stream_events, @@ -367,7 +364,7 @@ class ConversationState(rx.State): # 运行并流式输出事件 stream_events = run_stream_events( - deps=conversation.workflow.deps, + deps=task.workflow.deps, user_prompt=user_prompt, message_history=message_history, usage=usage, @@ -388,7 +385,7 @@ class ConversationState(rx.State): # 运行并流式输出事件 stream_events = run_stream_events( - deps=conversation.workflow.deps, + deps=task.workflow.deps, user_prompt=user_prompt, message_history=message_history, usage=usage, @@ -402,7 +399,7 @@ class ConversationState(rx.State): # 运行并流式输出事件 stream_events = run_stream_events( - deps=conversation.workflow.deps, + deps=task.workflow.deps, user_prompt=user_prompt, message_history=message_history, usage=usage, @@ -416,7 +413,7 @@ class ConversationState(rx.State): # 运行并流式输出事件 stream_events = run_stream_events( - deps=conversation.workflow.deps, + deps=task.workflow.deps, user_prompt=user_prompt, message_history=message_history, usage=usage, @@ -427,8 +424,8 @@ class ConversationState(rx.State): # 获取运行流式输出事件 async for event in stream_events: # 若等待流式输出则将等待流式输出设置为否 - if conversation.awaiting_stream: - conversation.awaiting_stream = False + if task.awaiting_stream: + task.awaiting_stream = False match event: # ========== 开始事件 ========== case PartStartEvent( @@ -446,7 +443,7 @@ class ConversationState(rx.State): is_running=True, ) # 添加至消息字典 - conversation.messages[message.id] = message + task.messages[message.id] = message # 将消息唯一标识与片段索引映射 index_map_to_message_id[index] = message.id yield # 通知前端更新渲染 @@ -457,7 +454,7 @@ class ConversationState(rx.State): ): if ( 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( @@ -466,9 +463,9 @@ class ConversationState(rx.State): 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 ] = message.id yield # 通知前端更新渲染 @@ -478,7 +475,7 @@ class ConversationState(rx.State): # 构建消息 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 yield # 通知前端更新渲染 @@ -492,7 +489,7 @@ class ConversationState(rx.State): content_delta=content_delta, provider_details=provider_details, ): - message = conversation.messages[ + message = task.messages[ index_map_to_message_id[index] ] # 若内容增量非空则将内容追加至消息内容,否则若供应商详情可调用更新消息内容 @@ -521,7 +518,7 @@ class ConversationState(rx.State): case TextPartDelta( content_delta=content_delta, ): - message = conversation.messages[ + message = task.messages[ index_map_to_message_id[index] ] # 增量更新 @@ -537,7 +534,7 @@ class ConversationState(rx.State): # 思考分片 case ThinkingPart(): # 获取消息 - message = conversation.messages[ + message = task.messages[ index_map_to_message_id[index] ] message.is_running = False @@ -548,7 +545,7 @@ class ConversationState(rx.State): # 文本分片 case TextPart(): # 获取消息 - message = conversation.messages[ + message = task.messages[ index_map_to_message_id[index] ] messages.append(message) @@ -563,8 +560,8 @@ class ConversationState(rx.State): content=content, ): # 获取消息 - message = conversation.messages[ - conversation.workflow.tool_call_id_map_to_message_id[ + message = task.messages[ + task.workflow.tool_call_id_map_to_message_id[ tool_call_id ] ] @@ -580,14 +577,14 @@ class ConversationState(rx.State): # 若工具调用唯一标识为空则创建消息并添加至消息字典,否则若消息内容非空则根据工具调用唯一标识获取消息并更新消息内容 if not tool_call_id: message = Message(type=MessageType.TEXT, content=content) - conversation.messages[message.id] = message + task.messages[message.id] = message yield # 通知前端更新渲染 messages.append(message) else: if content: - message = conversation.messages[ - conversation.workflow.tool_call_id_map_to_message_id[ + message = task.messages[ + task.workflow.tool_call_id_map_to_message_id[ tool_call_id ] ] @@ -599,8 +596,8 @@ class ConversationState(rx.State): if isinstance(output := result.output, DeferredToolRequests): for appr in output.approvals: # 获取消息 - message = conversation.messages[ - conversation.workflow.tool_call_id_map_to_message_id[ + message = task.messages[ + task.workflow.tool_call_id_map_to_message_id[ tool_call_id := appr.tool_call_id ] ] @@ -614,28 +611,28 @@ class ConversationState(rx.State): else: # 若工作流类型为预定航班则设置为对话 - if conversation.workflow.type == WorkflowType.BOOK_FLIGHT: - conversation.workflow = Workflow() + if task.workflow.type == WorkflowType.BOOK_FLIGHT: + task.workflow = Workflow() # 将正在运行设置为否 - conversation.is_running = False + task.is_running = False yield # 通知前端更新渲染 # 更新会话记录 - await db_state.update_conversation_record( - conversation.id, + await db_state.update_task_record( + task.id, usage=result.usage, - workflow=conversation.workflow, + workflow=task.workflow, ) # 创建运行记录 await db_state.create_run_record( - conversation_id=conversation.id, + task_id=task.id, new_messages=result.new_messages(), ) # 批量创建消息记录 await db_state.create_message_records( - conversation_id=conversation.id, + task_id=task.id, messages=messages, ) @@ -646,10 +643,10 @@ class ConversationState(rx.State): :return: 当前会话正在运行(True 表示正在运行,False 表示运行完成) """ # 当前会话 - conversation = self.conversations.get(self.conversation_id) - if not conversation: + task = self.tasks.get(self.task_id) + if not task: return False - return conversation.is_running + return task.is_running @rx.var def awaiting_stream(self) -> bool: @@ -658,10 +655,10 @@ class ConversationState(rx.State): :return: 当前会话正在等待流式输出(True 表示等待流式输出,False 表示已开始流式输出或已完成) """ # 当前会话 - conversation = self.conversations.get(self.conversation_id) - if not conversation: + task = self.tasks.get(self.task_id) + if not task: return False - return conversation.awaiting_stream + return task.awaiting_stream @rx.event 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) - if not conversation: + task = self.tasks.get(self.task_id) + if not task: return - conversation.messages[item_id].is_shown ^= True + task.messages[item_id].is_shown ^= True @rx.event def set_approval_content(self, item_id: str, content: str) -> None: @@ -682,12 +679,12 @@ class ConversationState(rx.State): :param content: 内容 """ # 当前会话 - conversation = self.conversations.get(self.conversation_id) - if not conversation: + task = self.tasks.get(self.task_id) + if not task: return # 获取消息 - message = conversation.messages.get(item_id) + message = task.messages.get(item_id) if not message or not isinstance(approval := message.approval, Approval): return @@ -702,8 +699,8 @@ class ConversationState(rx.State): 设置工作流 """ # 当前会话 - conversation = self.conversations.get(self.conversation_id) - if not conversation: + task = self.tasks.get(self.task_id) + if not task: return match type: @@ -754,11 +751,11 @@ class ConversationState(rx.State): workflow = Workflow() # 设置工作流 - conversation.workflow = workflow + task.workflow = workflow # 获取数据库状态 db_state = await self.get_db_state() # 更新会话记录 - await db_state.update_conversation_record( - conversation.id, - workflow=conversation.workflow, + await db_state.update_task_record( + task.id, + workflow=task.workflow, ) diff --git a/agent/assets/create_task.svg b/agent/assets/create_task.svg new file mode 100644 index 0000000..d859859 --- /dev/null +++ b/agent/assets/create_task.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/agent/assets/styles.css b/agent/assets/styles.css index b542093..bc87770 100644 --- a/agent/assets/styles.css +++ b/agent/assets/styles.css @@ -1,13 +1,16 @@ :root { --prismui-background-color-1: #ffffff; --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-5: #ffffff33; --prismui-background-color-6: #f2f2f3; --prismui-background-color-7: #5e7ce0; --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: rgb(242, 242, 242); --prismui-background-2: linear-gradient(180deg, #fffffff2, #f8fafff2 99%); --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; @@ -18,7 +21,8 @@ --prismui-font-size-5: 20px; --prismui-font-weight-1: normal; --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-2: #575d6c; --prismui-color-3: #babbc0; diff --git a/agent/database.db b/agent/database.db index 5feab4906854f637768ef08f9e1be78ed084ee49..2604709dea95685b2e905dc5c1e1a6b47b103dd3 100644 GIT binary patch delta 697 zcmZozAltA&c7n9v0R{$!QXqx_fr&cCx(68adVlh=t1t+&@iOr9^6%um%y)@5hUX4< zHP3Rc?_9N<0$ltYo$Sjvyx0vk_RL@t<)~%gag}Fml+DabDG^{72eAbw>#`?{D3v4@ zXUAuzC}}n-^0AA{%QH3!PF}_C$qAR3T)-nbS)3zHjLVCGN8DGPu~9cOuaSk99n4mo z+|E&qY)TC`ySTbKW3%~W5zbIFQ+he~%J58QFcx<=WNcIfx+S$Dvm(Bcfr}k1B0Bjl zmpEqyh^NTd2;xak&gWuh0XcDU6PKwh*iA*L$@xVoN(vgK#i>O=muogMasu6=I606@ zjxVz~J|#6LwInqKtcj1?A`Dd%LV;$JBs;sfq9S8+ZAoGhP!G@+h}j4hhjWmtV~DFl zh@+E_s{*n~ll?fQCtv11BE#X{WCRdq#2bRxo3HZdCyTK$ zu`#pn;AUrHW7Xj;VA;-?%9O&CGFea{aC3ISL1rd~-pP6;ow~)`9HF7|jvTza28M}- zMybZhx|YVtDZ1uH$*H=QsVU~VhN%WgX{P4MCg!P=7u1>w8X74W8d{keS{a$>8Jn3I z8gJGvy~)MQ#?QT(y@8uwiI5?d+|%Va88s%y^sfMFdc2u^!aaUw4o2?DFXqcYOj)4A i59Cf*r_mgtIF*6Xe05R+KnDuN+761SZ4#;Bw delta 657 zcmZozAltA&c7n9vUIqq+QXqx_-ibQKx_cS)>LPjhe=rEM@iOr9^6%um%y)@5hUX4< zD$jJT%UoWZTwMGdwd@Nxyx3JX_RL`8s}f)rca>*sl+DabNv)V%$SyNEirq;nIX|x~ zwWv6;Br`uRJ~KtLQJs%nTwb2BNp|uEc29mZ>B$8=qLalr(zqJAc-h5$)fpRgCr{=m z#xSLciJM(qU7fMnd9oB|C{|N?Irp+RvT?DCyBkja$0a9RTAW%0bO5r2lh<&Wsv!#$ zr6%VWr6_1X)oM2Ka{^sq2yz7@w<-^^(#cxf7CFcQFuj?@@hPb}sU@i?noaub?Ba%o zj4hlciAgy?vk^f7QH?3W;T+`Z7~-lB;^^e#s(>s%*^g6k@@4KLY|b2P;^Bsq12`lm zALbI~HAGgl`6`covKT898#DV3ZgwU%Rvq2~u1SokOest$6Bh<<&Mr8}%*4nvS+As% z+1S`{@{Kw-{@Pm3R0gfkPd@xHJfABx5un!1Y#y2W(HywAZFbjvz~3q0sz@^&yN5A