From 998a0fc57231b14142c586015c096106a29f5612 Mon Sep 17 00:00:00 2001 From: liubiren Date: Mon, 27 Jul 2026 20:13:11 +0800 Subject: [PATCH] 1 --- .../{a7968da75b72_.py => 4269426de639_.py} | 6 +- agent/application/application.py | 4 +- agent/application/domain_models.py | 8 +- agent/application/pages/conversation.py | 513 +++++++++--------- agent/application/pages/index.py | 12 +- agent/application/states/conversation.py | 82 ++- agent/application/states/database.py | 14 +- agent/assets/styles.css | 16 +- agent/database.db | Bin 77824 -> 90112 bytes 9 files changed, 359 insertions(+), 296 deletions(-) rename agent/alembic/versions/{a7968da75b72_.py => 4269426de639_.py} (97%) diff --git a/agent/alembic/versions/a7968da75b72_.py b/agent/alembic/versions/4269426de639_.py similarity index 97% rename from agent/alembic/versions/a7968da75b72_.py rename to agent/alembic/versions/4269426de639_.py index d079021..e383932 100644 --- a/agent/alembic/versions/a7968da75b72_.py +++ b/agent/alembic/versions/4269426de639_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: a7968da75b72 +Revision ID: 4269426de639 Revises: -Create Date: 2026-07-26 23:23:19.558164 +Create Date: 2026-07-27 10:10:56.902466 """ from typing import Sequence, Union @@ -12,7 +12,7 @@ import sqlalchemy as sa import sqlmodel # revision identifiers, used by Alembic. -revision: str = 'a7968da75b72' +revision: str = '4269426de639' down_revision: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None diff --git a/agent/application/application.py b/agent/application/application.py index af2e2fa..8175713 100644 --- a/agent/application/application.py +++ b/agent/application/application.py @@ -28,7 +28,9 @@ app = rx.App( "outline": "none", "box-shadow": "none", }, - "*::placeholder": { + ".rt-TextAreaInput::placeholder, .rt-TextFieldInput::placeholder": { + "font-family": "var(--font-family)", + "font-size": "var(--prismui-font-size-2)", "color": "var(--prismui-color-3)", "opacity": 1, }, # 覆盖占位符样式 diff --git a/agent/application/domain_models.py b/agent/application/domain_models.py index 2ceae48..6d101cc 100644 --- a/agent/application/domain_models.py +++ b/agent/application/domain_models.py @@ -3,10 +3,10 @@ 领域模型 """ from datetime import datetime -from typing import Dict, List -from pydantic_ai._uuid import uuid7 +from typing import Dict + from pydantic import BaseModel, Field -from dataclasses import dataclass +from pydantic_ai._uuid import uuid7 class Thought(BaseModel): @@ -25,7 +25,7 @@ class Dialog(BaseModel): id: str = Field(default_factory=lambda: str(uuid7()), description="对话唯一标识") user_prompt: str = Field(..., description="用户提示词") - thoughts: List[Thought] = Field(default_factory=list, description="思考列表") + thoughts: Dict[int, Thought] = Field(default_factory=dict, description="思考列表") result_output: str = Field(default="", description="结果输出") is_thinking: bool = Field( default=False, description="正在思考,True 表示正在思考,False 表示未正在思考" diff --git a/agent/application/pages/conversation.py b/agent/application/pages/conversation.py index 904627e..0d6d7c5 100644 --- a/agent/application/pages/conversation.py +++ b/agent/application/pages/conversation.py @@ -3,6 +3,7 @@ 会话页面 """ import reflex as rx +from typing import Dict from application.domain_models import ( Conversation, @@ -14,143 +15,160 @@ from application.states import ConversationState, AuthState def conversation_history_item( - item: ConversationHistoryItem, + conversation: ConversationHistoryItem, ) -> rx.Component: """ 会话历史项 - :param item: 会话历史项 + :param conversation: 会话历史项 :return: Component """ - # 会话激活状态 - is_actived: bool = item.id == ConversationState.conversation_id + # 高亮:若为当前会话或显示更多则高亮 + highlight: bool = ( + conversation.id == ConversationState.shown_more_conversation_id + ) | (conversation.id == ConversationState.conversation_id) return rx.list.item( rx.vstack( rx.hstack( # 对话描述 rx.text( - item.description, + conversation.description, flex=1, height="22px", line_height="22px", - padding_right="4px", + padding_right="18px", color="var(--prismui-color-2)", - overflow="hidden", text_overflow="ellipsis", white_space="nowrap", - ), - # 更多按钮 - 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="-12px", - left="50%", - transform="translateX(-50%) rotate(45deg)", - width="8px", - height="8px", - background_color="var(--prismui-background-color-1)", - ), - rx.popover.close( - rx.box( - "删除", - width="100%", - height="24px", - padding="4px", - border_radius="var(--prismui-border-radius-1)", - line_height="16px", - font_size="var(--prismui-font-size-1)", - color="var(--prismui-color-1)", - style={ - "_hover": { - "background_color": "var(--prismui-background-color-6)", - } - }, - # 点击事件:删除对话 - on_click=lambda: ConversationState.delete_conversation( - item.id - ), - ), - ), - position="relative", - width="100%", - ), - side="bottom", - side_offset=8, - position="relative", - align="center", - padding="8px", - border_radius="var(--prismui-border-radius-1)", - box_shadow="0 2px 12px rgba(0,0,0,0.1)", - font_family="var(--prismui-font-family)", - overflow="visible", - ), - open_delay=0, - ), - min_width="14px", - cursor="pointer", - style={ - "opacity": rx.cond( - is_actived, "1", "0" - ), # 若当前会话已激活则不透明,否则透明 - "transition": "opacity 0.18s ease", - "pointer_events": rx.cond(is_actived, "auto", "none"), - }, + overflow="hidden", ), display="flex", align_items="center", width="100%", margin_bottom="8px", - cursor="pointer", ), rx.hstack( rx.spacer(), # 创建时间 rx.text( - "1", - margin_bottom="8px", + conversation.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: ConversationState.switch_conversation(conversation.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: ConversationState.delete_conversation( + conversation.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: ConversationState.set_shown_more_conversation_id( + conversation.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(highlight, "1", "0"), + transition="opacity 0.18s ease", + pointer_events=rx.cond(highlight, "auto", "none"), + ), + display="flex", + position="relative", + align_items="flex-start", width="100%", padding="16px", margin_bottom="8px", background=rx.cond( - is_actived, - "linear-gradient(to right, #f3efff, #f3efff33, #e2f1fd33, #e2f1fd)", + highlight, + "var(--prismui-background-3)", "var(--prismui-background-color-1)", ), border_radius="var(--prismui-border-radius-3)", - box_shadow=rx.cond(is_actived, "2px 2px 8px #e9e9e9", "none"), + box_shadow=rx.cond(highlight, "var(--prismui-box-shadow-7)", "none"), + cursor="pointer", style={ "&:hover": { - "background": "linear-gradient(to right, #f3efff, #f3efff33, #e2f1fd33, #e2f1fd)", - "box_shadow": "2px 2px 8px #e9e9e9", + "background": "var(--prismui-background-3)", + "box_shadow": "var(--prismui-box-shadow-7)", }, # 鼠标悬停背景颜色和阴影 - "&:hover > div > div:last-child": { - "opacity": "1 !important", - "pointer_events": "auto !important", - }, # 鼠标悬停强制显示右侧三点按钮 + "&:hover > div:last-child": { + "opacity": "1", + "pointer_events": "auto", + }, # 鼠标悬停显示更多按钮 }, - cursor="pointer", - # 点击事件:切换会话 - on_click=ConversationState.switch_conversation(item.id), ) @@ -223,12 +241,11 @@ def greeting() -> rx.Component: src="/logo.png", width="64px", height="64px", - object_fit="contain", ), rx.box( "棱镜球", - font_size="32px", - font_weight="700", + font_size="calc(var(--prismui-font-size-3) * 2)", + font_weight="var(--prismui-font-weight-3)", letter_spacing="1px", ), display="flex", @@ -248,14 +265,12 @@ def greeting() -> rx.Component: ), width="100%", line_height="1.5", - font_size="var(--devui-font-size)", ), display="flex", flex_direction="column", width="100%", align_items="center", gap="12px", - color="var(--prismui-color-text)", ), # 演示案例 rx.vstack( @@ -263,23 +278,22 @@ def greeting() -> rx.Component: rx.text( "演示案例", line_height="24px", - font_size="16px", - font_weight="700", + font_size="var(--prismui-font-size-3)", + font_weight="var(--prismui-font-weight-3)", ), display="flex", - width="100%", justify_content="space-between", align_items="center", + width="100%", margin_bottom="16px", ), rx.hstack( rx.box( "智能客服", padding="10px 16px", - background_color="var(--devui-dividing-line)", - border_radius="var(--devui-border-radius-full)", - font_size="var(--devui-font-size)", - color="var(--devui-aide-text)", + background_color="var(--prismui-background-color-6)", + border_radius="var(--prismui-border-radius-9)", + color="var(--prismui-color-10)", cursor="pointer", ), display="flex", @@ -289,26 +303,26 @@ def greeting() -> rx.Component: ), width="100%", padding="24px", - background_color="var(--devui-base-bg)", - border_radius="24px", + background_color="var(--prismui-background-color-1)", + border_radius="var(--prismui-border-radius-7)", ), display="flex", flex_direction="column", + align_items="center", + gap="24px", width="100%", min_height="0", margin="auto 0", - align_items="center", - gap="24px", color="var(--prismui-color-text)", ), display="flex", flex="1", flex_direction="column", + justify_content="flex-start", + gap="24px", width="100%", max_width="1200px", padding="0 12px", - justify_content="flex-start", - gap="24px", overflow="auto", style={ "&::-webkit-scrollbar": { @@ -318,20 +332,20 @@ def greeting() -> rx.Component: ) -def thought(thought: Thought): +def thought(thought): """ - 思考节点 + 思考卡片 - :param thought: 思考节点实例 + :param thought: 思考实例 :return: Component """ return rx.vstack( rx.match( - thought.type, + thought[1].type, ( "thinking", rx.text( - thought.content, + thought[1].content, line_height="22px", font_size="var(--prismui-font-size-2)", color="var(--prismui-color-3)", @@ -354,28 +368,26 @@ def dialog(dialog: Dialog) -> rx.Component: is_thinking = dialog.is_thinking # 思考折叠面板展开状态 is_expanded = dialog.is_expanded - # 思考节点 - thoughts = dialog.thoughts return rx.vstack( - # 问题和操作栏 rx.hstack( rx.spacer(), rx.vstack( - # 问题 + # 用户提示词 rx.text( dialog.user_prompt, max_width="600px", padding="12px 16px", background_color="var(--prismui-background-color-3)", border_radius="var(--prismui-border-radius-3)", + line_height="1.5", + color="var(--prismui-color-text)", word_wrap="break-word", word_break="break-all", white_space="pre-line", - color="var(--prismui-color-text)", ), - # 操作栏 rx.hstack( + # 再次发送 rx.box( rx.icon( "rotate-ccw", @@ -396,11 +408,10 @@ def dialog(dialog: Dialog) -> rx.Component: margin_top="8px", ), ), - width="100%", - margin_top="8px", align_items="flex-start", gap="4px", - font_size="var(--devui-font-size)", + width="100%", + margin_top="8px", ), # 思考折叠面板 rx.vstack( @@ -437,7 +448,7 @@ def dialog(dialog: Dialog) -> rx.Component: rx.box( rx.auto_scroll( rx.foreach( - thoughts, + dialog.thoughts, thought, ), width="100%", @@ -464,35 +475,43 @@ def dialog(dialog: Dialog) -> rx.Component: "p": lambda text: rx.text( text, margin_bottom="8px", + line_height="1.5", + word_wrap="break-word", + word_break="break-all", + white_space="pre-line", + ), + "h2": lambda text: rx.text( + text, + margin_bottom="8px", + line_height="1.5", + font_weight="var(--prismui-font-weight-3)", word_wrap="break-word", word_break="break-all", white_space="pre-line", - line_height="24px", - font_size="var(--prismui-font-size-1)", - color="var(--prismui-color-1)", ), "strong": lambda text: rx.text( text, display="inline", - font_weight="var(--prismui-font-weight-1)", + font_weight="var(--prismui-font-weight-3)", ), "ul": lambda children: rx.vstack( children, margin_bottom="8px", gap="4px" ), "li": lambda text: rx.text( text, + display="list-item", + list_style_type="disc", + list_style_position="outside", + margin="0 0 4px 16px", + line_height="1.5", + font_size="var(--prismui-font-size-1)", + color="var(--prismui-color-1)", word_wrap="break-word", word_break="break-all", white_space="pre-line", - line_height="24px", - font_size="var(--prismui-font-size-1)", - color="var(--prismui-color-1)", - style={ - "display": "list-item", - "list_style_type": "disc", - "list_style_position": "inside", - }, + style={}, ), + "hr": lambda _: rx.divider(margin="8px 0"), }, width="100%", margin_top="8px", @@ -503,21 +522,19 @@ def dialog(dialog: Dialog) -> rx.Component: ) -def dialogs() -> rx.Component: +def dialogs_showing() -> rx.Component: """ - 对话列表 + 对话列表展示 :return: Component """ - # 获取当前会话的对话历史 + # 当前会话的对话列表 dialogs = ConversationState.dialogs - - # 若运行字典为空则品牌图标、名称和介绍、预设用户提示词等,否则遍历运行项 + # 若对话列表为空则显示欢迎,否则显示对话列表 return rx.cond( dialogs.length() == 0, # 欢迎 greeting(), - # 遍历对话项 rx.vstack( rx.auto_scroll( rx.foreach( @@ -539,54 +556,53 @@ def dialogs() -> rx.Component: ) -def create_conversation_button() -> rx.Component: +def create_conversation() -> rx.Component: """ - 新建会话按钮 + 创建会话 :return: Component """ return rx.hstack( rx.el.style( """ .rt-TooltipArrow polygon { - fill: #ffffff !important; + fill: var(--prismui-background-color-1) !important; } .rt-TooltipText { - color: var(--prismui-color-text) !important; + font-family: var(--prismui-font-family) !important; + color: var(--prismui-color-1) !important; opacity: 1 !important; } """ ), rx.spacer(), rx.dialog.root( - # 触发事件:点击图标 rx.dialog.trigger( rx.box( rx.tooltip( rx.icon( "plus", - size=14, - color="var(--prismui-color-text)", + width="14px", + height="14px", style={ "_hover": { - "color": "var(--devui-brand)", + "color": "var(--prismui-color-5)", } }, ), content="新建会话", - background_color="#ffffff", - box_shadow="0 2px 12px 0 rgba(37, 43, 58, .24)", - color="#252b3a", side="top", side_offset=9, + background_color="var(--prismui-background-color-1)", + box_shadow="var(--prismui-box-shadow-3)", ), display="flex", - width="24px", - height="24px", - background_color="var(--devui-base-bg)", - box_shadow="0 1px 8px #1919190f", - border_radius="var(--devui-border-radius-full)", justify_content="center", align_items="center", + width="24px", + height="24px", + background_color="var(--prismui-background-color-1)", + box_shadow="var(--prismui-box-shadow-8)", + border_radius="var(--prismui-border-radius-9)", cursor="pointer", ) ), @@ -623,103 +639,104 @@ def create_conversation_button() -> rx.Component: ) -def operation() -> rx.Component: +def user_prompt_sending() -> rx.Component: """ - 操作区 + 用户提示词发送 :return: Component """ return rx.vstack( - # 自定义输入组件 - rx.form( - rx.box( - rx.vstack( - rx.hstack( - rx.text_area( - name="question", - placeholder="请输入您的问题,按Enter换行", - vertical_align="middle", - width="100%", - height="64px", - padding="4px 0", - background_color="var(--devui-base-bg)", - font_size="var(--devui-font-size)", - color="var(--prismui-color-text)", - style={ - "border": "none !important", - "outline": "none !important", - "boxShadow": "none !important", - }, - ), - width="100%", - padding="0 16px", - ), - rx.hstack( - rx.spacer(), - rx.button( - rx.icon( - "send", - margin_right="4px", - width="12px", - height="12px", - ), - rx.text("发送"), - position="relative", - display="inline-flex", - padding="0 12px", - background_color="var(--devui-primary)", - border="none", - border_radius="20pxvar(--devui-border-radius-4)", - align_items="center", - justify_content="center", - white_space="nowrap", - inline_height="1.5", - font_size="var(--devui-font-size)", - color="var(--devui-light-text)", - overflow="hidden", - cursor="pointer", - type="submit", - loading=ConversationState.running_status, - disabled=ConversationState.running_status, - ), - width="100%", - justify_content="flex-end", - align_items="center", - height="32px", - padding="0 16px", - ), - ), + rx.vstack( + rx.text_area( + # 值绑定用户提示词 + value=ConversationState.user_prompt, + # 输入事件:设置用户提示词 + on_change=ConversationState.set_user_prompt, + placeholder="请输入您的问题,按Enter换行", + vertical_align="middle", + width="100%", + height="64px", + padding="4px 0", + background_color="var(--prismui-background-color-1)", + border="none !important", + outline="none !important", + box_shadow="none !important", style={ - "* textarea::placeholder": { - "fontFamily": "var(--font-family)", - "fontSize": "var(--devui-font-size)", - "color": "var(--placeholder)", - "opacity": 1, + ".rt-TextAreaInput": { + "border": "none !important", + "outline": "none !important", + "box-shadow": "none !important", }, }, ), - display="flex", - flex_direction="column", + rx.hstack( + rx.spacer(), + rx.button( + rx.icon( + "send", + margin_right="4px", + width="15px", + height="15px", + color="var(--prismui-color-9)", + ), + rx.text( + "发送", + line_height="21px", + color="var(--prismui-color-9)", + ), + loading=ConversationState.running_status, + # 是否禁用绑定用户提示词发送禁用状态 + disabled=ConversationState.is_user_prompt_sending_disabled, + # 点击事件:发送用户提示词 + on_click=ConversationState.handle_user_prompt, + display="inline-flex", + position="relative", + justify_content="center", + align_items="center", + padding="0 12px", + background_color=rx.cond( + ConversationState.is_user_prompt_sending_disabled, + "var(--prismui-color-11)", + "var(--prismui-color-5)", + ), + border="none", + border_radius="var(--prismui-border-radius-5)", + inline_height="1.5", + font_size="var(--prismui-font-size-2)", + white_space="nowrap", + overflow="hidden", + cursor=rx.cond( + ConversationState.is_user_prompt_sending_disabled, + "not-allowed", + "pointer", + ), + ), + width="100%", + justify_content="flex-end", + align_items="center", + height="32px", + ), width="100%", - padding="12px 0", - background_color="var(--devui-base-bg)", - border="none", - border_radius="16px", - box_shadow="0 1px 8px 0 var(--mc-box-shadow)", - on_submit=ConversationState.run, - reset_on_submit=True, + padding="12px 16px", + background_color="var(--prismui-background-color-1)", + border_radius="var(--prismui-border-radius-6)", + box_shadow="var(--prismui-box-shadow-9)", ), - # 底部文案 + # 免责声明 rx.text( "内容由大模型生成,无法确保准确性和完整性,仅供参考", - margin_top="8px", + width="100%", text_align="center", - font_size="12px", - color="var(--devui-aide-text)", + margin_top="8px", + line_height="18px", + font_size="var(--prismui-font-size-1)", + color="var(--prismui-color-10)", ), + display="flex", + flex_direction="column", width="100%", max_width="1200px", padding="0 12px 12px", - align_items="center", + border="none", ) @@ -774,12 +791,12 @@ def conversation() -> rx.Component: conversation_history(is_conversation_history_shown), rx.box( rx.vstack( - # 对话历史 - dialogs(), - # 新建对话按钮 - create_conversation_button(), - # 操作区 - operation(), + # 对话列表展示 + dialogs_showing(), + # 创建对话 + create_conversation(), + # 用户提示词发送 + user_prompt_sending(), display="flex", flex_flow="column", width="100%", diff --git a/agent/application/pages/index.py b/agent/application/pages/index.py index 24c590a..9703301 100644 --- a/agent/application/pages/index.py +++ b/agent/application/pages/index.py @@ -224,7 +224,7 @@ def login_popup() -> rx.Component: "登录解锁更多功能", margin_bottom="24px", line_height="32px", - font_size="var(--prismui-font-size-3)", + font_size="var(--prismui-font-size-4)", font_weight="var(--prismui-font-weight-3)", ), # 邮箱输入框 @@ -281,7 +281,11 @@ def login_popup() -> rx.Component: "var(--prismui-color-4)", "var(--prismui-color-5)", ), - cursor="pointer", + cursor=rx.cond( + AuthState.is_captcha_sending_disabled, + "not-allowed", + "pointer", + ), ), width="100%", height="45px", @@ -390,7 +394,7 @@ def login_popup() -> rx.Component: padding="20px", background_color="var(--prismui-background-color-1)", border="var(--prismui-border-2)", - border_radius="var(--prismui-border-radius-5)", + border_radius="var(--prismui-border-radius-6)", ), rx.text( "加微信好友", @@ -407,7 +411,7 @@ def login_popup() -> rx.Component: ), background="var(--prismui-background-2)", border="1px solid #2222220f", - border_radius="var(--prismui-border-radius-5)", + border_radius="var(--prismui-border-radius-6)", box_shadow="0 4px 64px 0 #0000001a", overflow="hidden", ), diff --git a/agent/application/states/conversation.py b/agent/application/states/conversation.py index 6af05dd..0887554 100644 --- a/agent/application/states/conversation.py +++ b/agent/application/states/conversation.py @@ -77,10 +77,15 @@ class ConversationState(rx.State): # 会话历史展示状态,True表示展示,False表示隐藏 is_conversation_history_shown: bool = False + # 显示更多的会话唯一标识 + shown_more_conversation_id: str = "" # 会话创建状态,True表示正在创建,False表示未正在创建 is_conversation_creating: bool = False + # 用户提示词 + user_prompt: str = "" + async def resume(self, user_id: str) -> None: """ 恢复当前用户会话状态 @@ -112,22 +117,6 @@ class ConversationState(rx.State): """ self.is_conversation_history_shown = not self.is_conversation_history_shown - @staticmethod - def format_conversation_created_at(created_at: datetime) -> str: - """ - 格式化会话创建时间 - :param created_at: 会话创建时间 - :return: 格式化后的会话创建时间 - """ - match (datetime.now().date() - created_at.date()).days: - case 0: - formated_created_at = f"今天 {created_at.strftime('%H:%M')}" - case 1: - formated_created_at = f"昨天 {created_at.strftime('%H:%M')}" - case _: - formated_created_at = created_at.strftime("%Y-%m-%d %H:%M") - return formated_created_at - @rx.var def conversation_history_items(self) -> List[ConversationHistoryItem]: """ @@ -138,17 +127,34 @@ class ConversationState(rx.State): for conversation in reversed( self.conversations.values() ): # 按照会话唯一标识倒序排序 + # 格式化会话创建时间 + match (datetime.now().date() - conversation.created_at.date()).days: + case 0: + created_at = f"{conversation.created_at.strftime('%H:%M')}" + case 1: + created_at = f"昨天 {conversation.created_at.strftime('%H:%M')}" + case _: + created_at = conversation.created_at.strftime("%Y-%m-%d %H:%M") + items.append( ConversationHistoryItem( id=conversation.id, description=conversation.description, - created_at=self.format_conversation_created_at( - created_at=conversation.created_at - ), + created_at=created_at, ) ) return items + @rx.event + def set_shown_more_conversation_id( + self, conversation_id: str, is_shown: bool + ) -> None: + """ + 设置显示更多的会话唯一标识 + :return: None + """ + self.shown_more_conversation_id = conversation_id if is_shown else "" + @rx.event async def delete_conversation(self, conversation_id: str) -> None: """ @@ -212,6 +218,27 @@ class ConversationState(rx.State): """ self.is_conversation_creating = not self.is_conversation_creating + @rx.event + def set_user_prompt(self, user_prompt: str) -> None: + """ + 设置用户提示词 + :param user_prompt: 用户提示词 + :return: None + """ + self.user_prompt = user_prompt.strip() + + @rx.var + def is_user_prompt_sending_disabled(self) -> bool: + """ + 用户提示词发送禁用状态 + :return: bool + """ + # 当前会话 + conversation = self.conversations.get(self.conversation_id) + if not conversation: + return True + return conversation.is_running or not self.user_prompt + @rx.var def running_status(self) -> bool: """ @@ -227,7 +254,7 @@ class ConversationState(rx.State): @rx.var def dialogs(self) -> List[Dialog]: """ - 获取当前会话的对话列表 + 当前会话的对话列表 :return: 当前会话的对话列表 """ # 当前会话 @@ -237,17 +264,18 @@ class ConversationState(rx.State): return list(conversation.dialogs.values()) @rx.event - async def run(self, form_data: dict[str, Any]) -> AsyncGenerator[None]: + async def handle_user_prompt(self) -> AsyncGenerator[None]: """ - 运行 - :param form_data: 表单数据 - :return: AsyncGenerator + 处理用户提示词 + :return: AsyncGenerator[None] """ - # 解析用户提示词 - user_prompt = form_data["user_prompt"].strip() - if not user_prompt: + if not self.user_prompt: return + # 清空前端用户提示词 + user_prompt = self.user_prompt + self.user_prompt = "" + # 当前会话 conversation = self.conversations[self.conversation_id] # 将当前会话的运行状态设置为正在运行 diff --git a/agent/application/states/database.py b/agent/application/states/database.py index 5cf22dc..32d63cd 100644 --- a/agent/application/states/database.py +++ b/agent/application/states/database.py @@ -4,7 +4,7 @@ """ from datetime import datetime, timedelta from random import choices -from typing import Dict, List, Tuple +from typing import Dict, List from pydantic import TypeAdapter from pydantic_ai import ModelMessage, ModelMessagesTypeAdapter @@ -106,8 +106,8 @@ class DialogRecord(SQLModel, table=True): ) conversation_id: str = Field(..., index=True, description="会话唯一标识") user_prompt: str = Field(..., description="用户提示词") - thoughts: List[ThoughtRecord] = Field( - default_factory=List, sa_type=JSON, description="思考列表" + thoughts: Dict[int, ThoughtRecord] = Field( + default_factory=dict, sa_type=JSON, description="思考列表" ) result_output: str = Field(default="", description="结果输出") @@ -123,7 +123,7 @@ class ResultRecord(SQLModel, table=True): # 思考领域模型类型适配器 -ThoughtTypeAdapter = TypeAdapter(List[Thought]) +ThoughtTypeAdapter = TypeAdapter(Dict[int, Thought]) class DatabaseState(rx.State): @@ -282,7 +282,7 @@ class DatabaseState(rx.State): async def create_dialog_record( self, conversation_id: str, user_prompt: str - ) -> Dialog: + ) -> Dict[str, Dialog]: """ 创建对话记录 :param conversation_id: 会话唯一标识 @@ -297,12 +297,12 @@ class DatabaseState(rx.State): session.add(record) await session.commit() await session.refresh(record) - return Dialog(id=record.id, user_prompt=user_prompt) + return {record.id: Dialog(id=record.id, user_prompt=user_prompt)} async def complete_dialog_record( self, id: str, - thoughts: List[Thought], + thoughts: Dict[int, Thought], result_output: str, ) -> None: """ diff --git a/agent/assets/styles.css b/agent/assets/styles.css index 3679826..ca7d6e8 100644 --- a/agent/assets/styles.css +++ b/agent/assets/styles.css @@ -7,10 +7,12 @@ --prismui-background-color-6: #f2f2f3; --prismui-background-1: linear-gradient(to bottom, #D0C9FF 0%, #E6D6F0 8%, #F1DBEA 12%, #C8DCFB 40%, #ABC6F6 60%, #87AEFE 90%); --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; --prismui-font-size-1: 12px; --prismui-font-size-2: 14px; - --prismui-font-size-3: 20px; + --prismui-font-size-3: 16px; + --prismui-font-size-4: 20px; --prismui-font-weight-1: normal; --prismui-font-weight-2: 500; --prismui-font-weight-3: 700; @@ -21,17 +23,27 @@ --prismui-color-5: #5e7ce0; --prismui-color-6: #f66f6a; --prismui-color-7: #f2f2f3; + --prismui-color-8: #aeaeae; --prismui-color-9: #ffffff; + --prismui-color-10: #71757f; + --prismui-color-11: #beccfa; --prismui-border-1: 1px solid #060a260f; --prismui-border-radius-1: 4px; --prismui-border-radius-2: 6px; --prismui-border-radius-3: 8px; --prismui-border-radius-4: 12px; - --prismui-border-radius-5: 20px; + --prismui-border-radius-5: 16px; + --prismui-border-radius-6: 20px; + --prismui-border-radius-7: 24px; + --prismui-border-radius-9: 100px; --prismui-box-shadow-1: 0 4px 64px 0 #0000001a; --prismui-box-shadow-2: 0 4px 12px #0000001a; --prismui-box-shadow-3: 0 2px 12px 0 #252b3a3d; --prismui-box-shadow-4: 2px 0 4px 0 #d5d5d540; --prismui-box-shadow-5: -2px -2px 4px #0000000d; + --prismui-box-shadow-6: 0 2px 12px #00000019; + --prismui-box-shadow-7: 2px 2px 8px #e9e9e9; + --prismui-box-shadow-8:0 1px 8px #1919190f; + --prismui-box-shadow-9: 0 1px 8px 0 #1919190f; background-color: var(--prismui-background-color-1); } \ No newline at end of file diff --git a/agent/database.db b/agent/database.db index b062bcd75a1696a1d1911440b7f57accbff29a55..b08185e0f24567d3c7ae349644ea54adb1fb697a 100644 GIT binary patch literal 90112 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 VPc!!$JiPevukETD7xe2T@b6LS_>ceq delta 417 zcmZoTz}oPDWr8#-9|Hq}@I(cBAVaT9WMj$%d7eabOEZgL#U`rRt_yB%38#nwXgz86+7#o3`P}o>?!}&oweIGSdYrHZoH%GPW`@u`)8( z1Dar9Y`IN<@d3XUA(KE(X5|0F!2bv83qd|+W)4n>@NWkG-%w!zZWd;KPD7GBj9)Q+ IS=68e0Kn{LhyVZp