diff --git a/agent/application/application.py b/agent/application/application.py index 7421924..ed75d9a 100644 --- a/agent/application/application.py +++ b/agent/application/application.py @@ -23,7 +23,7 @@ app = rx.App( } }) """ - ) + ) # 点击链接在新标签页打开 ], stylesheets=["/styles.css"], style={ diff --git a/agent/application/pages/login.py b/agent/application/pages/login.py index 6b918d7..2b889fd 100644 --- a/agent/application/pages/login.py +++ b/agent/application/pages/login.py @@ -61,8 +61,8 @@ def login_page() -> rx.Component: rx.button( rx.text( rx.cond( - AuthState.resend_captcha_code_countdown > 0, - f"{AuthState.resend_captcha_code_countdown} s", + AuthState.countdown > 0, + f"{AuthState.countdown} s", "获取验证码", ), line_height="16px", diff --git a/agent/application/pages/task.py b/agent/application/pages/task.py index b87bc70..6550b3a 100644 --- a/agent/application/pages/task.py +++ b/agent/application/pages/task.py @@ -587,8 +587,8 @@ def message_history() -> rx.Component: height="28px", width="100%", cursor="pointer", - # 点击事件:显示当前任务删除确认 - on_select=TaskState.toggle_task_delete_confirm( + # 点击事件:显示任务删除确认 + on_click=TaskState.toggle_task_delete_confirm_open( True ).stop_propagation, style={ @@ -652,9 +652,9 @@ def message_history() -> rx.Component: width="240px", padding="16px", ), - # 显示绑定:显示当前任务删除确认 - open=TaskState.is_task_delete_confirm_shown, - on_open_change=TaskState.toggle_task_delete_confirm, + # 显示绑定:显示任务删除确认 + open=TaskState.is_task_delete_confirm_open, + on_open_change=TaskState.toggle_task_delete_confirm_open, ), align_items="center", gap="4px", @@ -856,6 +856,4 @@ def task_page() -> rx.Component: # 渲染... width="100%", height="100%", - # 挂载事件:恢复任务状态 - on_mount=AuthState.resume_task_state, ) diff --git a/agent/application/pages/workbench.py b/agent/application/pages/workbench.py index 665f824..90fd0fc 100644 --- a/agent/application/pages/workbench.py +++ b/agent/application/pages/workbench.py @@ -159,13 +159,64 @@ def sidebar() -> rx.Component: rx.alert_dialog.root( rx.alert_dialog.trigger(rx.box(height="0", width="100%")), rx.alert_dialog.content( - rx.alert_dialog.title("确认退出登录?"), - rx.alert_dialog.description( - "退出登录不会丢失任何数据,你仍可以登录此账号。" + rx.vstack( + rx.text( + "确认退出登录?", + line_height="24px", + font_size="var(--prismui-font-size-3)", + font_weight="var(--prismui-font-weight-3)", + color="var(--prismui-color-1)", + margin_bottom="16px", + ), + rx.text( + "退出登录不会丢失任何数据,你仍可以登录此账号。", + line_height="20px", + color="var(--prismui-color-2)", + margin_bottom="24px", + ), + rx.hstack( + rx.spacer(), + rx.hstack( + rx.alert_dialog.cancel( + rx.button( + "取消", + style={ + "_hover": { + "background": "var(--prismui-background-color-9)" + } + }, + ) + ), + rx.alert_dialog.action( + rx.button( + "退出登录", + color="var(--prismui-color-9)", + width="80px", + background_color="var(--prismui-background-color-6)", + style={ + "_hover": { + "background": "var(--prismui-background-color-7)" + } + }, + ), + # 点击事件:退出登录 + on_click=AuthState.logout, + ), + ), + justify_content="space-between", + width="100%", + ), + gap="0", + width="100%", ), - rx.alert_dialog.cancel("取消"), - rx.alert_dialog.action("退出登录"), + width="480px", + padding="24px", + border_radius="var(--prismui-border-radius-7)", ), + # 打开绑定退出登录确认打开状态 + open=SidebarState.is_logout_confirm_open, + # 当打开状态变动时绑定切换退出登录确认打开状态 + on_open_change=SidebarState.toggle_logout_confirm_open, ), # 渲染系统图标和名称 rx.hstack( @@ -221,6 +272,7 @@ def sidebar() -> rx.Component: width="100%", background_color="var(--prismui-background-color-3)" ), rx.hstack( + # 渲染用户中心 rx.popover.root( rx.popover.trigger( rx.hstack( @@ -274,6 +326,7 @@ def sidebar() -> rx.Component: border_radius="var(--prismui-border-radius-3)", ), rx.vstack( + # 渲染退出登录 rx.popover.close( rx.hstack( rx.icon( @@ -290,12 +343,17 @@ def sidebar() -> rx.Component: padding="5px 8px", border_radius="var(--prismui-border-radius-3)", cursor="pointer", + # 点击事件:切换退出登录确认显示 + on_click=SidebarState.toggle_logout_confirm_open( + True + ).stop_propagation, style={ "_hover": { - "background": "var(--prismui-background-color-2)", + "background": "var(--prismui-background-color-9)", }, }, ), + width="100%", ), width="100%", padding="8px", @@ -308,6 +366,7 @@ def sidebar() -> rx.Component: background_color="var(--prismui-background-color-9)", ), ), + # 渲染消息中心 rx.tooltip( rx.box( rx.icon( @@ -376,4 +435,6 @@ def workbench() -> rx.Component: width="100%", height="100vh", ), + # 挂载事件:恢复认证状态 + on_mount=AuthState.resume, ) diff --git a/agent/application/states/auth.py b/agent/application/states/auth.py index b36ba17..e4212a1 100644 --- a/agent/application/states/auth.py +++ b/agent/application/states/auth.py @@ -6,14 +6,14 @@ from asyncio import sleep from email.header import Header from email.mime.text import MIMEText import re -from typing import AsyncGenerator, Optional +from typing import AsyncGenerator from aiosmtplib import SMTP, SMTPException import reflex as rx from reflex.event import EventCallback -from application.states.task import TaskState from application.states.database import DatabaseState +from application.states.task import TaskState # 验证码HTML模板 CAPTCHA_HTML_TEMPLATE = """ @@ -35,8 +35,8 @@ class AuthState(rx.State): captcha_code: str = "" # 验证码已发送 is_captcha_code_sent: bool = False - # 再次发送验证码倒计时 - resend_captcha_code_countdown: int = 0 + # 倒计时 + countdown: int = 0 # 协议同意状态 is_policies_agreed: bool = True # 错误信息 @@ -44,22 +44,6 @@ class AuthState(rx.State): # 登录中状态 is_logging_in: bool = False - # 设置悬停卡片打开状态 - is_settings_hover_card_open: bool = False - - # 数据库状态 - _db_state: Optional[DatabaseState] = None - - async def get_db_state(self) -> DatabaseState: - """ - 获取当前数据库状态 - :return: 当前数据库状态 - """ - # 若数据库状态为空则获取数据库状态,否则直接返回当前数据库状态 - if not self._db_state: - self._db_state = await self.get_state(DatabaseState) - return self._db_state - @rx.event def reset_error(self) -> None: """ @@ -112,34 +96,34 @@ class AuthState(rx.State): 邮箱为空或再次发送验证码倒计时大于0时禁用 :return: bool """ - return not self.email or self.resend_captcha_code_countdown > 0 + return not self.email or self.countdown > 0 @rx.event(background=True) - async def countdown_background(self) -> None: + async def background_countdown(self) -> None: """ - 倒计时(后台任务) + 后台运行倒计时 :return: None """ async with self: - self.resend_captcha_code_countdown = 59 + self.countdown = 59 - while self.resend_captcha_code_countdown > 0: + while self.countdown > 0: await sleep(1) async with self: - self.resend_captcha_code_countdown -= 1 + self.countdown -= 1 @rx.event(background=True) - async def send_captcha_code_background(self) -> None: + async def background_send_captcha_code(self) -> None: """ - 发送验证码(后台任务) + 后台运行发送验证码 :return: None """ async with self: - # 获取当前数据库状态 - db_state = await self.get_db_state() + # 获取数据库状态 + database_state = await self.get_state(DatabaseState) # 创建验证记录 - captcha_code = await db_state.create_captcha_record(email=self.email) + captcha_code = await database_state.create_captcha_record(email=self.email) # 构建验证码邮件 message = MIMEText( @@ -174,15 +158,16 @@ class AuthState(rx.State): if not self.validate_email(): return - if self.resend_captcha_code_countdown > 0: + # 若倒计时未结束则不允许再次发送验证码 + if self.countdown > 0: return # 标记验证码已发送 self.is_captcha_code_sent = True - # 同时触发发送验证码和倒计时后台事件 - yield type(self).send_captcha_code_background() - yield type(self).countdown_background() + # 并行处理:后台运行倒计时与后台运行发送验证码 + yield type(self).background_countdown() + yield type(self).background_send_captcha_code() @rx.event def set_captcha_code(self, captcha_code: str) -> None: @@ -202,14 +187,14 @@ class AuthState(rx.State): self.is_policies_agreed = not self.is_policies_agreed @rx.event - async def resume_task_state(self): + async def resume(self): """ - 恢复任务状态 + 恢复认证状态 """ # 获取任务状态 task_state = await self.get_state(TaskState) # 恢复任务状态 - await task_state.resume_task_state(user_id=self.user_id) + await task_state.resume(user_id=self.user_id) @rx.event async def login(self) -> None: @@ -238,11 +223,11 @@ class AuthState(rx.State): self.is_logging_in = True - # 获取当前数据库状态 - db_state = await self.get_db_state() + # 获取数据库状态 + database_state = await self.get_state(DatabaseState) # 核验验证码 - if not await db_state.verify_captcha_code( + if not await database_state.verify_captcha_code( email=self.email, captcha_code=self.captcha_code ): self.is_logging_in = False @@ -250,41 +235,32 @@ class AuthState(rx.State): return # 创建用户记录 - user_id = await db_state.create_user_record(email=self.email) + user_id = await database_state.create_user_record(email=self.email) # 获取任务状态 task_state = await self.get_state(TaskState) - # 恢复当前用户的任务状态 - await task_state.resume_task_state(user_id=user_id) + # 恢复任务状态 + await task_state.resume(user_id=user_id) self.user_id = user_id self.email = "" self.captcha_code = "" self.is_captcha_code_sent = False - self.resend_captcha_code_countdown = 0 + self.countdown = 0 self.is_policies_agreed = True self.error = "" self.is_logging_in = False - @rx.event - def set_settings_hover_card_open(self, is_settings_hover_card_open: bool) -> None: - """ - 设置设置悬停卡片打开 - :param is_settings_hover_card_open: 设置悬停卡片打开状态,True 表示打开,False 表示关闭 - :return: None - """ - self.is_settings_hover_card_open = is_settings_hover_card_open - @rx.event async def logout(self) -> None: """ 退出登录 :return: None """ - self.is_settings_hover_card_open = False # 设置设置悬停卡片关闭 - task_state = await self.get_state(TaskState) - task_state.user_id = "" - task_state.tasks = {} - task_state.task_id = "" self.user_id = "" + + # 获取任务状态 + task_state = await self.get_state(TaskState) + # 重置任务状态 + task_state.reset() diff --git a/agent/application/states/database.py b/agent/application/states/database.py index ab2f741..951f1dd 100644 --- a/agent/application/states/database.py +++ b/agent/application/states/database.py @@ -167,7 +167,7 @@ class DatabaseState(rx.State): """ 核验验证码 :param email: 邮箱 - :param code: 验证码 + :param captcha_code: 验证码 :return: 核验是否成功,True 表示核验成功,False 表示核验失败 """ async with rx.asession() as session: diff --git a/agent/application/states/sidebar.py b/agent/application/states/sidebar.py index 131fc23..c2df10e 100644 --- a/agent/application/states/sidebar.py +++ b/agent/application/states/sidebar.py @@ -15,6 +15,9 @@ class SidebarState(rx.State): # 选中的导航按钮,默认为创建任务 nav_button: str = "create_task" + # 退出登录确认打开状态 + is_logout_confirm_open: bool = False + @rx.event async def set_nav_button(self, nav_button: str): """ @@ -32,7 +35,7 @@ class SidebarState(rx.State): @rx.event async def set_task_id(self, task_id: str): """ - 将指定任务唯一标识设置为当前选中任务 + 将指定任务的唯一标识设置为当前选中任务的唯一标识 """ self.nav_button = "" @@ -40,3 +43,12 @@ class SidebarState(rx.State): task_state = await self.get_state(TaskState) # 将指定任务唯一标识设置为当前任务唯一标识 task_state.set_task_id(task_id) + + @rx.event + def toggle_logout_confirm_open(self, is_open: bool) -> None: + """ + 切换退出登录确认打开状态 + :param is_open: 退出登录确认打开状态 + :return: None + """ + self.is_logout_confirm_open = is_open diff --git a/agent/application/states/task.py b/agent/application/states/task.py index f062645..538d61c 100644 --- a/agent/application/states/task.py +++ b/agent/application/states/task.py @@ -51,26 +51,12 @@ class TaskState(rx.State): # 临时用户提示词 temporary_user_prompt: str = "" - # 当前任务删除确认显示 - is_task_delete_confirm_shown: bool = False - # 上一次任务删除确认显示日期时间 - last_task_delete_confirm_show_at: float = 0 + # 任务删除确认打开状态 + is_task_delete_confirm_open: bool = False + # 任务删除确认打开日期时间 + task_delete_confirm_open_at: float = 0 - # 当前数据库状态(私有变量) - # 私有变量:reflex 约定以 _ 开头的变量为私有变量,后端不序列化,前端不可使用 - _db_state: DatabaseState | None = None - - async def get_db_state(self) -> DatabaseState: - """ - 获取当前数据库状态 - :return: 当前数据库状态 - """ - # 若数据库状态为 None 则获取数据库状态,否则直接返回当前数据库状态 - if not self._db_state: - self._db_state = await self.get_state(DatabaseState) - return self._db_state - - async def resume_task_state(self, user_id: str) -> None: + async def resume(self, user_id: str) -> None: """ 恢复任务状态 :param user_id: 用户唯一标识 @@ -80,12 +66,11 @@ class TaskState(rx.State): if not self.user_id: return - # 获取当前数据库状态 - db_state = await self.get_db_state() + # 获取数据库状态 + database_state = await self.get_state(DatabaseState) # 查询当前用户的任务字典 - self.tasks = await db_state.read_tasks(self.user_id) + self.tasks = await database_state.read_tasks(self.user_id) - # 遍历所有任务若最后一条消息未审批则将该任务和该消息正在运行设置为是 for task in self.tasks.values(): # 最后一个消息 message = next(iter(reversed(task.messages.values())), None) @@ -157,20 +142,20 @@ class TaskState(rx.State): return f"更新于 {task.last_run_at.strftime("%Y-%m-%d %H:%M:%S")}" @rx.event - async def toggle_task_delete_confirm(self, is_shown: bool) -> None: + async def toggle_task_delete_confirm_open(self, is_open: bool) -> None: """ - 切换当前任务删除确认 - :param is_shown: 显示当前任务删除确认 + 切换任务删除确认打开状态 + :param is_open: 任务删除确认打开状态 :return: None """ # 当前日期时间 - now = datetime.now().timestamp() - # 根据当前时间与上一次任务删除确认显示日期时间间隔防抖 - if now - self.last_task_delete_confirm_show_at < 0.2: + current = datetime.now().timestamp() + # 根据当前时间与任务删除确认打开日期时间间隔防抖 + if current - self.task_delete_confirm_open_at < 0.2: return - self.is_task_delete_confirm_shown = is_shown - self.last_task_delete_confirm_show_at = now + self.is_task_delete_confirm_open = is_open + self.task_delete_confirm_open_at = current @rx.event async def delete_task(self) -> EventSpec | None: @@ -185,20 +170,20 @@ class TaskState(rx.State): if not task: return - # 获取当前数据库状态 - db_state = await self.get_db_state() + # 获取数据库状态 + database_state = await self.get_state(DatabaseState) # 更新任务记录 - await db_state.update_task_record(task.id, is_deleted=True) + await database_state.update_task_record(task.id, is_deleted=True) del self.tasks[task.id] - # 获取当前侧边栏状态 + # 获取侧边栏状态 sidebar_state = await self.get_state(SidebarState) # 将创建任务设置为导航按钮 sidebar_state.nav_button = "" # 将空设置为当前任务唯一标识 self.task_id = "" # 将任务删除确认显示设置为否 - self.is_task_delete_confirm_shown = False + self.is_task_delete_confirm_open = False return rx.toast.success("删除成功") @rx.event @@ -289,19 +274,18 @@ class TaskState(rx.State): """ from application.states import SidebarState - # 获取当前数据库状态 - db_state = await self.get_db_state() - - # 若当前任务唯一标识为空则创建任务 + # 获取数据库状态 + database_state = await self.get_state(DatabaseState) + # 若当前任务的唯一标识为空则创建任务 if not self.task_id: - # 获取当前侧边栏状态 + # 获取侧边栏状态 sidebar_state = await self.get_state(SidebarState) # 将空设置为导航按钮 sidebar_state.nav_button = "" # 创建任务记录再添加任务 self.tasks.update( - await db_state.create_task_record( + await database_state.create_task_record( self.user_id, self.temporary_user_prompt ) ) @@ -354,7 +338,7 @@ class TaskState(rx.State): task.awaiting_stream = True # 先创建消息记录再添加消息 task.messages.update( - await db_state.create_message_record( + await database_state.create_message_record( task_id=task.id, message=Message( type=MessageType.USER_PROMPT, @@ -368,7 +352,9 @@ class TaskState(rx.State): index_map_to_message_id: dict[int, str] = {} # 查询消息历史列表 - message_history = await db_state.read_message_history(task_id=self.task_id) + message_history = await database_state.read_message_history( + task_id=self.task_id + ) # 使用量 usage = task.usage @@ -644,7 +630,7 @@ class TaskState(rx.State): # 更新使用量 task.usage = result.usage # 更新任务记录 - await db_state.update_task_record( + await database_state.update_task_record( id=task.id, last_run_at=task.last_run_at, usage=task.usage, @@ -652,13 +638,13 @@ class TaskState(rx.State): ) # 创建运行记录 - await db_state.create_run_record( + await database_state.create_run_record( task_id=task.id, new_messages=result.new_messages(), ) # 批量创建消息记录 - await db_state.create_message_records( + await database_state.create_message_records( task_id=task.id, messages=messages, ) @@ -780,9 +766,9 @@ class TaskState(rx.State): # 设置工作流 task.workflow = workflow # 获取数据库状态 - db_state = await self.get_db_state() + database_state = await self.get_state(DatabaseState) # 更新任务记录 - await db_state.update_task_record( + await database_state.update_task_record( id=task.id, workflow=task.workflow, ) diff --git a/agent/application/workshop/web_retrieve.py b/agent/application/workshop/web_retrieve.py index ea0d3ed..b38dc88 100644 --- a/agent/application/workshop/web_retrieve.py +++ b/agent/application/workshop/web_retrieve.py @@ -178,7 +178,7 @@ instruction = """ 无需联网:基础常识、数学计算、代码编写与解释、用户已提供完整材料的分析任务、无时间依赖的静态问题。 【检索硬约束】 -1. 最多**2轮联网检索**,严禁发起第3轮。一轮定义:单次思考决策发起1条检索Query;**单次思考步骤,最多只能生成1条检索query,禁止同一思考周期并行发起多条联网检索调用**。必须串行执行:执行1次检索,拿到结果评估之后,才允许在后续思考中决定是否发起下一轮检索。不允许一步同时调用多次web_search。 +1. 最多**2轮联网检索**,严禁发起第3轮。一轮定义:单次思考决策发起1条检索Query;**单次思考步骤,最多只能生成1条检索query,禁止同一思考周期并行发起多条联网检索调用**。必须串行执行:执行1次检索,拿到结果评估之后,才允许在后续思考中决定是否发起下一轮检索。不允许一步同时调用多次web_retrieve。 2. 发起新一轮检索前,必须评估已获取的检索摘要,现有信息足以回答问题则立即停止调用工具。 3. 禁止仅改写同义词、做语义几乎等价的重复检索;若存在局部信息缺失,优先从已有结果中提取可用内容,不要直接发起新检索。 4. 默认只执行1次真实联网检索;拿到检索结果后评估证据,满足以下任意条件,必须停止检索: @@ -187,7 +187,7 @@ instruction = """ ✅仅在以下4种场景,才允许执行第2轮检索: ①第一轮无有效检索结果;②回答问题必需的关键事实完全缺失;③权威信源之间事实存在明显冲突;④第一轮检索结果严重偏题。 第二轮检索必须瞄准明确的信息缺口,禁止全盘复刻上一轮查询。 -> 重要:第二轮检索执行完成后,无论信息是否补齐,都必须终止检索,不再发起任何web_search调用。 +> 重要:第二轮检索执行完成后,无论信息是否补齐,都必须终止检索,不再发起任何web_retrieve调用。 ## 二、时间与Query规则 时效判断严格以运行时传入的当前日期、年份、时区为准,禁止依靠模型内置记忆猜测时间。 @@ -217,7 +217,7 @@ instruction = """ ## 六、引用规范 引用编号区分检索轮次:S1‑1、S1‑2 代表第1轮检索结果;S2‑1、S2‑2 代表第2轮检索结果。 只引用回答中实际用到的资料。 -引用格式:`[参考 S1‑1] https://xxx`,链接必须来源于 **web_search** 工具返回结果,禁止编造链接。 +引用格式:`[参考 S1‑1] https://xxx`,链接必须来源于 **web_retrieve** 工具返回结果,禁止编造链接。 ## 七、输出规则 简单事实类问题:优先给出结论,附带关键证据与来源,回答简洁,避免冗余。 diff --git a/agent/assets/styles.css b/agent/assets/styles.css index 603fc1b..fd0a28e 100644 --- a/agent/assets/styles.css +++ b/agent/assets/styles.css @@ -131,6 +131,12 @@ pre, pre code { color: var(--prismui-color-7) !important; } + +.rt-AlertDialogContent { + font-family: var(--prismui-font-family) !important; + font-size: var(--prismui-font-size-2) !important; +} + /* DropdownMenuItem */ .rt-DropdownMenuItem .rt-Text { font-family: var(--prismui-font-family); @@ -150,6 +156,7 @@ pre, pre code { font-family: var(--prismui-font-family); font-size: var(--prismui-font-size-2); color: var(--prismui-color-1); + white-space: nowrap; height: 32px; width: 68px; background-color: var(--prismui-background-color-1); diff --git a/agent/database.db b/agent/database.db index 5c99655..c8ea0d3 100644 Binary files a/agent/database.db and b/agent/database.db differ