From 4d4093a1994783d48256d901d559bafdc6cb7cf6 Mon Sep 17 00:00:00 2001 From: liubiren Date: Wed, 14 Oct 2026 20:54:23 +0800 Subject: [PATCH] 1 --- agent/application/pages/task.py | 76 ------------------------ agent/application/pages/workbench.py | 68 +++++++++++++--------- agent/application/states/task.py | 84 ++++++++++----------------- agent/database.db | Bin 479232 -> 487424 bytes 4 files changed, 72 insertions(+), 156 deletions(-) diff --git a/agent/application/pages/task.py b/agent/application/pages/task.py index db32e66..b7476c7 100644 --- a/agent/application/pages/task.py +++ b/agent/application/pages/task.py @@ -13,82 +13,6 @@ from application.states.models import ( ) -def task_history_item( - item: TaskHistoryItem, -) -> rx.Component: - """ - 任务历史项 - :param item: 任务历史项 - :return: rx.Component - """ - - # 高亮 - is_highlight = (item.id == TaskState.popover_shown_task_history_item_id) | ( - item.id == TaskState.task_id - ) - - return rx.hstack( - # 渲染任务历史项的任务描述 - rx.text( - item.description, - line_height="20px", - padding_right="18px", - color="var(--prismui-color-2)", - text_overflow="ellipsis", - white_space="nowrap", - cursor="inherit", - ), - align_items="center", - width="100%", - cursor="pointer", - on_click=lambda: TaskState.set_task_id(item.id), - ) - - -def task_history() -> rx.Component: - """ - 任务历史 - :return: rx.Component - """ - return 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", - ) - - def workflow_button(workflow_type: WorkflowType): """ 工作流按钮 diff --git a/agent/application/pages/workbench.py b/agent/application/pages/workbench.py index e98d6e4..110002a 100644 --- a/agent/application/pages/workbench.py +++ b/agent/application/pages/workbench.py @@ -73,39 +73,55 @@ def task_history_item( :return: Component """ - # 高亮 - is_highlight = (item.id == TaskState.popover_shown_task_history_item_id) | ( - item.id == TaskState.task_id - ) + # 悬停状态 + is_hovered = item.id == TaskState.hovered_task_id + # 若悬停状态为是则渲染任务描述和更多图标,否则:若运行状态为是则渲染正在运行图标,否则不渲染;同时渲染任务描述和上一次运行日期时间 return rx.list.item( - rx.hstack( - # 渲染任务正在运行图标 - rx.cond( - item.is_running, - rx.icon( - "play", width="14px", height="14px", color="var(--prismui-color-2)" + rx.cond( + is_hovered, + rx.hstack( + rx.text( + item.description, + line_height="20px", + color="var(--prismui-color-2)", + text_overflow="ellipsis", + white_space="nowrap", ), - rx.fragment(), + rx.icon("ellipsis"), + width="100%", + padding="5px 12px", ), - # 渲染任务描述 - rx.text( - item.description, - flex=1, - line_height="20px", - color="var(--prismui-color-2)", - text_overflow="ellipsis", - white_space="nowrap", - overflow="hidden", + rx.hstack( + rx.cond(item.is_running, rx.image("/is_running.svg"), rx.fragment()), + # 渲染任务描述 + rx.text( + item.description, + flex=1, + min_width="0", # 自适应宽度 + line_height="20px", + color="var(--prismui-color-2)", + white_space="nowrap", # 禁止换行 + overflow="hidden", + mask_image="linear-gradient(to right, white calc(100% - 24px), transparent 100%)", # 添加蒙版 + ), + # 渲染上一次运行日期时间 + rx.text( + item.last_run_at, + line_height="20px", + font_size="calc(var(--prismui-font-size-1) - 1px)", + color="var(--prismui-color-2)", + flex_shrink=0, # 禁止缩放 + ), + gap="8px", + width="100%", + padding="5px 12px", ), - display="flex", - align_items="center", - width="100%", - padding="5px 0", - # 点击事件:将指定任务唯一标识设置为当前任务唯一标识 - on_click=lambda: TaskState.set_task_id(item.id), ), width="100%", + cursor="pointer", + # 点击事件:将指定任务唯一标识设置为当前任务唯一标识 + on_click=lambda: TaskState.set_task_id(item.id), ) diff --git a/agent/application/states/task.py b/agent/application/states/task.py index 89ae316..ee8db0b 100644 --- a/agent/application/states/task.py +++ b/agent/application/states/task.py @@ -35,22 +35,6 @@ from application.states.models import ( from application.workshop.events import AgentRuntimeEvent -def format_datetime(datetime: datetime) -> str: - """ - 格式化日期时间 - :param datetime: 日期时间 - :return: 格式化后日期时间字符串 - """ - match (datetime.now().date() - datetime.date()).days: - case 0: - formatted_datetime = f"{datetime.strftime('%H:%M')}" - case 1: - formatted_datetime = f"昨天 {datetime.strftime('%H:%M')}" - case _: - formatted_datetime = datetime.strftime("%Y-%m-%d %H:%M") - return formatted_datetime - - class TaskState(rx.State): """ 任务状态 @@ -64,8 +48,8 @@ class TaskState(rx.State): # 当前任务唯一标识 task_id: str = "" - # 悬停气泡显示的任务历史项唯一标识(一个任务只允许存在一个悬停气泡任务历史项) - popover_shown_task_history_item_id: str = "" + # 鼠标悬停的任务唯一标识 + hovered_task_id: str = "" # 当前数据库状态(私有变量) # 私有变量:reflex 约定以 _ 开头的变量为私有变量,后端不序列化,前端不可使用 @@ -97,9 +81,7 @@ class TaskState(rx.State): 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.tasks.update(await db_state.create_task_record(self.user_id)) # 将最后一个任务的唯一标识作为当前任务唯一标识 self.task_id = next(reversed(self.tasks.keys())) @@ -116,34 +98,42 @@ class TaskState(rx.State): task.is_running = True message.is_running = True - @rx.event - def toggle_task_history_shown(self) -> None: - """ - 显示 / 隐藏任务历史 - :return: None - """ - self.is_task_history_shown = not self.is_task_history_shown - @rx.var def task_history(self) -> list[TaskHistoryItem]: """ 获取任务历史项列表(按照任务创建日期时间降序排序) :return: 任务历史项列表 """ + + def formatter(datetime: datetime) -> str: + """ + 格式器 + :param datetime: 日期时间 + :return: 格式化后日期时间字符串 + """ + # 时间间隔 + delta = (datetime.now().date() - datetime.date()).total_seconds() + if delta < 60: + return f"刚刚" + elif delta < 60 * 60: + return f"{round(delta / 60)}分钟前" + elif delta < 60 * 60 * 24: + return f"{round(delta / 60 / 60)}小时前" + else: + return f"{round(delta / 60 / 60 / 24)}天前" + return [ TaskHistoryItem( id=task.id, description=task.description, - last_run_at=format_datetime(task.last_run_at), + last_run_at=formatter(task.last_run_at), is_running=task.is_running, ) for task in reversed(self.tasks.values()) ] @rx.event - def set_task_history_item_popover_shown( - self, item_id: str, is_shown: bool - ) -> None: + def set_task_history_item_popover_shown(self, item_id: str, is_shown: bool) -> None: """ 设置会话历史项悬停气泡显示 / 隐藏 :return: None @@ -165,9 +155,7 @@ class TaskState(rx.State): # 删除后,若当前用户的任务字典为空则创建任务 if not self.tasks: - self.tasks.update( - await db_state.create_task_record(self.user_id) - ) + self.tasks.update(await db_state.create_task_record(self.user_id)) # 删除后,若当前任务唯一标识不存在则将最后一个任务的唯一标识作为当前任务唯一标识 if self.task_id not in self.tasks: @@ -213,9 +201,7 @@ class TaskState(rx.State): # 获取当前数据库状态 db_state = await self.get_db_state() # 创建任务记录再添加任务 - self.tasks.update( - await db_state.create_task_record(self.user_id) - ) + self.tasks.update(await db_state.create_task_record(self.user_id)) # 将最后一个任务的唯一标识作为当前任务唯一标识 self.task_id = next(reversed(self.tasks.keys())) @@ -337,9 +323,7 @@ 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 db_state.read_message_history(task_id=self.task_id) # 使用量 usage = task.usage @@ -490,9 +474,7 @@ class TaskState(rx.State): content_delta=content_delta, provider_details=provider_details, ): - message = task.messages[ - index_map_to_message_id[index] - ] + message = task.messages[index_map_to_message_id[index]] # 若内容增量非空则将内容追加至消息内容,否则若供应商详情可调用更新消息内容 if content_delta: message.content += content_delta @@ -519,9 +501,7 @@ class TaskState(rx.State): case TextPartDelta( content_delta=content_delta, ): - message = task.messages[ - index_map_to_message_id[index] - ] + message = task.messages[index_map_to_message_id[index]] # 增量更新 message.content += content_delta yield # 通知前端更新渲染 @@ -535,9 +515,7 @@ class TaskState(rx.State): # 思考分片 case ThinkingPart(): # 获取消息 - message = task.messages[ - index_map_to_message_id[index] - ] + message = task.messages[index_map_to_message_id[index]] message.is_running = False message.title = "思考已完成" yield # 通知前端更新渲染 @@ -546,9 +524,7 @@ class TaskState(rx.State): # 文本分片 case TextPart(): # 获取消息 - message = task.messages[ - index_map_to_message_id[index] - ] + message = task.messages[index_map_to_message_id[index]] messages.append(message) # ========== 工具调用结果事件 ========== diff --git a/agent/database.db b/agent/database.db index c84b9186a38a52dd2277d87af1c6b4289cb321d0..f9b430fcc2b74837b1f196cd4a12a5c645dc11d4 100644 GIT binary patch delta 4030 zcmeHJeQXow8TXw(a|r|}l(7y0U6{b-GM|0^ipC1w53y^Ep)38 z_?(c~&WAs6$OYq&?~ehAlQ_gCc9N;mI&B&nEYh}$W}|TLef%+n|0b>bW6${_C|aqe zp@}~>DHq?p@AJIR?|I+n_j_KZcQbQqbL=VY?^G()Pvcvkg_p`xKbZ8UkLbOa5BK8U z#M(9A%e-~;12tBas61vf1ooAdxLkp~iMeC0YQA%VD(OW5J zmDy2cvQ(OFX0w$(@${SZM~@#ncdoNLTYd7Z0ym)Ic-De%x)+;~6N(ro*jS|?7V1&Tn z!QoM7BZTI??Zh{APu%xOeOdJh{s(*vKaC&2DO`^&VHqrp9mK3yX?*YI#Er$B+M6ns z){t;VE3y_!b7OV&H*{82X`=LS>E6;W5zEAfL^pAac!}6n^0$&}B?Bezm+UXuZur!2 z!!Tkvsk0hhH9TX$^&jce`X>DWJ*C&{mUJ0iSa*;$?9tF!W{>8IR*r)$Wj?w70*-xL z{J1Iq&+J(I0B6^{tl61GFKM={$G*dA4{2DP;fP_M;i+u@A(b;V`@a^m?A7W3k``fWHzYuE{M+StB zQ9hIvlBXqhTWI!YJAzC1b)!0MH0iVJ_E=VsS8*Qhr7p4xsUVail>G8H#iH%izZ4hZG#kh2%Ya zV&A9`nSdm~Q;97xNtDY8`E%UtET8*1IwOY{1`9IW_6c#a4=lO4W|SHgN6v`hDK0m* zf~n9UK-$3>Ki?oH2k@otei^}3A1ooJ1AO~9#N)dneCjf+4yG{6UF}i8@zEIMB+SKy ze2UApC`rrqjj+}8XrASALt;at5?*Q;fc=-wrqJNDd>ln6^5zc*?!3q}z>hG_@Y4a= zdZC+d3!rE#8k&Q6b%6tVm+Pymw%;$UhnFi${TAEK?y11n>Hsa2P4>h-eG@^f0;R&# zTU6>TP_Y}-yUzW%TI+I6ef?gwet|r(*mK&wYFLh?eu-~fUzFunDXPkBud>=I9af9e z#JU&N=6{``EoL?z*RIdTuHiqy*y3- z9UG0nG8AnWnKm?-6#FlU>C;?3%jIT`MnysHFab~yajFeoWGP@`Xra+)1UA;x*VL2# ztwy6142t6mTrPrQA#N@O%Spo_dF3R^4#9w|7x6CeM;FgT)tBgZgFB*Vp_maOizQ@igS51Fd^id#q^jAdp?9F{BdyvBpV!t#93KS zQ8X?DE*0UCfDNQvIN!og=TXZjAXL_=cMTFoAO0Enq7hu7tG;o2N?_Dm0c>p{+ z47Q|^IOv=D?7wgY+{oC~zLB0Q;a6os0FrV$|=ENW%5)E@`gmSLao zg=6x6RzBqfhM{q^NasQjm&*^{VOmiPG`j~@md2ty+bhiX0bsBZB4O#`s1z6i8(2|3 z33dj}O+w}})XB5ZTu^WpHD^&KFXtF+Ly@r1kraa&p|zI}&4UZ)FLnyZ8TtwcJDR+} z%|@WuK>G_FN_ELyNbVUc&6w>)O_`PQ;lm8HHt;qYL@oRjt>f{9zet85^xC1g-vZJFoPyKKHFyGV$M#nfBpydrkb`}6Y+OQwr31mr#3q* z@|9Rkne6OecdK_4U$%W0vu=1m$(@xHMO*DwHa4znH&C>q@ST5A+bZZfx5a0H>#N5D zF0_TVlTMqBCLKs4p|$AD&WfPLF~5 ZGV}fY{|WU8;?aq(@czlqAwFo!KLNH`(tQ8` delta 614 zcmYMxUuaTM90%}w?mg#f*Q?jj*sx}k&5=fm|I#5FTcxX(!S)~xBJx4on&F?RHIaoN zHHc=5L!7p!6+IQP#bw=>VEu!rr(vL@y%_yl5hNpm3R>5Q9X!~cRQTjxsEfjL?(SI9gjw0sSdB(>9LXIF<`;T z$yUZ?^Wgm##-Y<=x8uH)j&^v&=4{QaY!M1{y%pw{_KHo~cR*^J0HbQLuu{+}OY(&B z3nfvr`dk1${AP6zkAx7lO9<2_G0azcK|ma=>I4O6m2Y$wJpOeabYfIHLi1*EsCIw@ z-!cCA0;-dgnRqrCBQl$qARsFx#wgzVbsTb?+-XW!6BdO_ShL!HeF$*NBS6go8@b8> z({zyoa%ee>ROH~fF76~y{d*u6dnVw8SBO;lpat$wJ)}sWAG%1-5ZtXdDYg`4R$_~6 z1U*JKP#g^)J^aS@u-nmRDc=v55nd{f;NO)KWY$O<(BZ$$t{7<}I@I7mgZ>76{|OF? z!)v`XF0bdXWBoQRzP?Pf0ce)qABFABV342?BrMphAL&*IzGZ39LNp&3Be^>Y6J+Zn zqe5BU1ip}o4S7G4GM|!-I?v0TCwD$EZPRmOP!5vj#@J{@NuwOaKR`M8%i>7N`4tzr EKN)$!egFUf