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 b062bcd..b08185e 100644 Binary files a/agent/database.db and b/agent/database.db differ