From 09f3ff2cc236ca8d0c508f09b30c46cd586704da Mon Sep 17 00:00:00 2001 From: liubiren Date: Mon, 12 Oct 2026 17:48:57 +0800 Subject: [PATCH] 1 --- agent/alembic/versions/e41b99574371_.py | 58 ++++++++++++++++++ agent/application/pages/task.py | 76 ++++++++---------------- agent/application/pages/workbench.py | 57 ++++++++---------- agent/application/states/database.py | 22 ++++--- agent/application/states/models.py | 7 ++- agent/application/states/task.py | 42 +++++++------ agent/database.db | Bin 479232 -> 479232 bytes 7 files changed, 151 insertions(+), 111 deletions(-) create mode 100644 agent/alembic/versions/e41b99574371_.py diff --git a/agent/alembic/versions/e41b99574371_.py b/agent/alembic/versions/e41b99574371_.py new file mode 100644 index 0000000..76790d9 --- /dev/null +++ b/agent/alembic/versions/e41b99574371_.py @@ -0,0 +1,58 @@ +"""empty message + +Revision ID: e41b99574371 +Revises: 3e4ccabe44c3 +Create Date: 2026-10-12 16:52:21.920854 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'e41b99574371' +down_revision: Union[str, Sequence[str], None] = '3e4ccabe44c3' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('messagerecord', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_messagerecord_conversation_id')) + batch_op.create_index(batch_op.f('ix_messagerecord_task_id'), ['task_id'], unique=False) + + with op.batch_alter_table('runrecord', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_runrecord_conversation_id')) + batch_op.create_index(batch_op.f('ix_runrecord_task_id'), ['task_id'], unique=False) + + with op.batch_alter_table('taskrecord', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_conversationrecord_is_deleted')) + batch_op.drop_index(batch_op.f('ix_conversationrecord_user_id')) + batch_op.create_index(batch_op.f('ix_taskrecord_is_deleted'), ['is_deleted'], unique=False) + batch_op.create_index(batch_op.f('ix_taskrecord_user_id'), ['user_id'], unique=False) + + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('taskrecord', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_taskrecord_user_id')) + batch_op.drop_index(batch_op.f('ix_taskrecord_is_deleted')) + batch_op.create_index(batch_op.f('ix_conversationrecord_user_id'), ['user_id'], unique=False) + batch_op.create_index(batch_op.f('ix_conversationrecord_is_deleted'), ['is_deleted'], unique=False) + + with op.batch_alter_table('runrecord', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_runrecord_task_id')) + batch_op.create_index(batch_op.f('ix_runrecord_conversation_id'), ['task_id'], unique=False) + + with op.batch_alter_table('messagerecord', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_messagerecord_task_id')) + batch_op.create_index(batch_op.f('ix_messagerecord_conversation_id'), ['task_id'], unique=False) + + # ### end Alembic commands ### diff --git a/agent/application/pages/task.py b/agent/application/pages/task.py index b819d28..ffc673d 100644 --- a/agent/application/pages/task.py +++ b/agent/application/pages/task.py @@ -23,48 +23,30 @@ def task_history_item( """ # 高亮 - is_highlight = ( - item.id == TaskState.popover_shown_task_history_item_id - ) | (item.id == TaskState.task_id) + 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", + 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", # 点击事件:设置激活任务 on_click=lambda: TaskState.set_task(item.id), - width="100%", ), # 会话历史项的更多按钮 rx.box( @@ -95,9 +77,7 @@ def task_history_item( rx.box( "删除", # 点击事件:删除任务 - on_click=lambda: TaskState.delete_task( - item.id - ), + on_click=lambda: TaskState.delete_task(item.id), display="flex", align_items="center", justify_content="center", @@ -225,6 +205,7 @@ def task_history( overflow="hidden", ) + def conversation_history( is_conversation_history_shown: bool, ) -> rx.Component: @@ -452,9 +433,7 @@ def thinking(item: MessageHistoryItem) -> rx.Component: line_height="22px", ), # 点击事件,展示 / 隐藏消息历史项 - on_click=lambda: TaskState.toggle_message_history_item_shown( - item.id - ), + on_click=lambda: TaskState.toggle_message_history_item_shown(item.id), cursor="pointer", ), # 思考的内容 @@ -515,9 +494,7 @@ def tool_call(item: MessageHistoryItem) -> rx.Component: line_height="22px", ), # 点击事件,展示 / 隐藏消息历史项 - on_click=lambda: TaskState.toggle_message_history_item_shown( - item.id - ), + on_click=lambda: TaskState.toggle_message_history_item_shown(item.id), cursor="pointer", ), rx.box( @@ -769,9 +746,7 @@ def message_history() -> rx.Component: rx.vstack( rx.auto_scroll( rx.vstack( - rx.foreach( - TaskState.message_history, message_history_item - ), + rx.foreach(TaskState.message_history, message_history_item), # 若等待流式输出则显示加载动效 rx.cond( TaskState.awaiting_stream, @@ -1072,7 +1047,6 @@ def create_task_page() -> rx.Component: ) - def conversation_page() -> rx.Component: """ 会话页面:布局参考 MetaChat,从左到右分别为会话历史和工作区。其中,工作区从上到下分别为消息历史和用户提示词输入框。 @@ -1114,4 +1088,4 @@ def conversation_page() -> rx.Component: 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 index 9797736..9443f4c 100644 --- a/agent/application/pages/workbench.py +++ b/agent/application/pages/workbench.py @@ -76,45 +76,35 @@ def task_history_item( ) 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", + rx.hstack( + # 渲染任务正在运行图标 + rx.cond( + item.is_running, + rx.icon( + "play", width="14px", height="14px", color="var(--prismui-color-2)" ), - display="flex", - align_items="center", - width="100%", - margin_bottom="8px", + rx.fragment(), ), - 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", + # 渲染任务描述 + 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", # 点击事件:设置激活任务 on_click=lambda: TaskState.set_task(item.id), - width="100%", ), - # 会话历史项的更多按钮 + # 任务更多按钮 rx.box( rx.popover.root( rx.popover.trigger( @@ -371,6 +361,7 @@ def sidebar() -> rx.Component: font_size="var(--prismui-font-size-2)", padding="0 0 0 10px", ), + task_history(), padding="0 12px", ), align_items="flex-start", diff --git a/agent/application/states/database.py b/agent/application/states/database.py index 164575c..f483134 100644 --- a/agent/application/states/database.py +++ b/agent/application/states/database.py @@ -1,6 +1,10 @@ # -*- coding: utf-8 -*- """ 数据库状态 + +更新数据库方法: +1. reflex db makemigrations +2. reflex db migrate """ from dataclasses import asdict from datetime import datetime, timedelta @@ -79,8 +83,8 @@ class TaskRecord(SQLModel, table=True): ) user_id: str = Field(..., index=True, description="用户唯一标识") description: str = Field(default="新任务", description="任务描述") - created_at: datetime = Field( - default_factory=datetime.now, description="任务创建时间" + last_run_at: datetime = Field( + default_factory=datetime.now, description="上一次运行日期时间" ) is_deleted: bool = Field( default=False, @@ -222,7 +226,7 @@ class DatabaseState(rx.State): usage=usage_validate_json( record.usage ), # 将 record.usage 由 JSON 字符串反序列化为 RunUsage - created_at=record.created_at, + last_run_at=record.last_run_at, ) } @@ -252,10 +256,10 @@ class DatabaseState(rx.State): Task( id=task_record.id, description=task_record.description, + last_run_at=task_record.last_run_at, usage=usage_validate_json( task_record.usage ), # 将 task_record.usage 由 JSON 字符串反序列化为 RunUsage - created_at=task_record.created_at, workflow=workflow_validate_json( task_record.workflow ), # 将 task_record.workflow 由 JSON 字符串反序列化为 Workflow, @@ -277,27 +281,31 @@ class DatabaseState(rx.State): async def update_task_record( self, - task_id: str, + id: str, description: str | None = None, + last_run_at: datetime | None = None, is_deleted: bool | None = None, usage: RunUsage | None = None, workflow: Workflow | None = None, ) -> None: """ 更新任务记录 - :param task_id: 指定任务唯一标识 + :param id: 任务唯一标识 :param description: 任务描述 + :param last_run_at: 上一次运行日期时间 :param is_deleted: 任务已删除 :param usage: 使用量 :param workflow: 工作流 :return: None """ async with rx.asession() as session: - record = await session.get(TaskRecord, task_id) + record = await session.get(TaskRecord, id) if not record: return if isinstance(description, str): record.description = description + if isinstance(last_run_at, datetime): + record.last_run_at = last_run_at if isinstance(is_deleted, bool): record.is_deleted = is_deleted if isinstance(usage, RunUsage): diff --git a/agent/application/states/models.py b/agent/application/states/models.py index a2581e9..5c6cb31 100644 --- a/agent/application/states/models.py +++ b/agent/application/states/models.py @@ -84,7 +84,7 @@ class Task(BaseModel): id: str = Field(..., description="任务唯一标识") description: str = Field(..., description="任务描述") - created_at: datetime = Field(..., description="任务创建日期时间") + last_run_at: datetime = Field(..., description="上一次运行日期时间") usage: RunUsage = Field(..., description="任务使用量") messages: dict[str, Message] = Field(default_factory=dict, description="消息字典") workflow: Workflow = Field(default_factory=Workflow, description="工作,默认为对话") @@ -105,7 +105,10 @@ class TaskHistoryItem(BaseModel): id: str = Field(..., description="任务唯一标识") description: str = Field(..., description="任务描述") - created_at: str = Field(..., description="任务创建日期时间") + last_run_at: str = Field(..., description="上一次运行日期时间") + is_running: bool = Field( + default=False, description="正在运行,True 表示正在运行, False 表示运行结束" + ) class MessageHistoryItem(Message): diff --git a/agent/application/states/task.py b/agent/application/states/task.py index 9cd88bc..9878405 100644 --- a/agent/application/states/task.py +++ b/agent/application/states/task.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -会话状态 +任务状态 """ from datetime import date as Date, datetime from typing import AsyncGenerator @@ -35,20 +35,20 @@ from application.states.models import ( from application.workshop.events import AgentRuntimeEvent -def format_conversation_created_at(created_at: datetime) -> str: +def format_datetime(datetime: datetime) -> str: """ - 格式化会话创建日期时间 - :param created_at: 创建日期时间 - :return: 格式化后的日期时间字符串 + 格式化日期时间 + :param datetime: 日期时间 + :return: 格式化后日期时间字符串 """ - match (datetime.now().date() - created_at.date()).days: + match (datetime.now().date() - datetime.date()).days: case 0: - formatted_created_at = f"{created_at.strftime('%H:%M')}" + formatted_datetime = f"{datetime.strftime('%H:%M')}" case 1: - formatted_created_at = f"昨天 {created_at.strftime('%H:%M')}" + formatted_datetime = f"昨天 {datetime.strftime('%H:%M')}" case _: - formatted_created_at = created_at.strftime("%Y-%m-%d %H:%M") - return formatted_created_at + formatted_datetime = datetime.strftime("%Y-%m-%d %H:%M") + return formatted_datetime class TaskState(rx.State): @@ -127,14 +127,15 @@ class TaskState(rx.State): @rx.var def task_history(self) -> list[TaskHistoryItem]: """ - 获取会话历史项列表(按照会话创建日期时间降序排序) + 获取任务历史项列表(按照任务创建日期时间降序排序) :return: 任务历史项列表 """ return [ TaskHistoryItem( id=task.id, description=task.description, - created_at=format_conversation_created_at(task.created_at), + last_run_at=format_datetime(task.last_run_at), + is_running=task.is_running, ) for task in reversed(self.tasks.values()) ] @@ -613,14 +614,19 @@ class TaskState(rx.State): # 若工作流类型为预定航班则设置为对话 if task.workflow.type == WorkflowType.BOOK_FLIGHT: task.workflow = Workflow() - # 将正在运行设置为否 + # 更新上一次运行日期时间 + task.last_run_at = datetime.now() + # 更新正在运行状态 task.is_running = False yield # 通知前端更新渲染 - # 更新会话记录 + # 更新使用量 + task.usage = result.usage + # 更新任务记录 await db_state.update_task_record( - task.id, - usage=result.usage, + id=task.id, + last_run_at=task.last_run_at, + usage=task.usage, workflow=task.workflow, ) @@ -754,8 +760,8 @@ class TaskState(rx.State): task.workflow = workflow # 获取数据库状态 db_state = await self.get_db_state() - # 更新会话记录 + # 更新任务记录 await db_state.update_task_record( - task.id, + id=task.id, workflow=task.workflow, ) diff --git a/agent/database.db b/agent/database.db index 2604709dea95685b2e905dc5c1e1a6b47b103dd3..c84b9186a38a52dd2277d87af1c6b4289cb321d0 100644 GIT binary patch delta 1542 zcmdr~Urbw781K3F+&{hd+OtA8E_7{q=6b(P-0au2*ii+Mu=dCsg3m=G2@e%%K zUJ>6D8e~a2o6fT(xwOI;gKbW)6AUBoYfh z7mbAiiD3MB@~lwqFr7e8NZaevV!hd@=E|bvePJFr$$+?VbF4tH*Or3;Wv$i=;dnfF zFucCrMR5yUDg2ALLCBU!W9xPR<)o#Z_@sSDyX}I#x&#b>wK8T}wnlF}9HUZlh}7Ws z*6s6Z&Avu&yGA?8g#y`RQ+E#4Tv=F;ZlH20&p&ckla8)5-xCjn!iU0%@H#H}Ke%%F z|E9}Dbo{$Tv6qy9*ZUH|1IIQAg84Yk(g$T)YmyN8HKp`WFrJ{l6A1(prKAWQVLT$a z%Wq15hPLFRaHab|O-)sGrKj3Gp)KPFfeOB>K(^o;cpRs2C$7XYUc@tahAzZil?;u! zh-W-Wq;Z|&%GqbEFgnR=v=-@)f|KXRXN?{HDBiF3eZ_&GcaUx9mJ z8T$wO6?>lTW7}ArxzEfpqs(z;KjUTuumIBFEf4@zK(^c*u5%bu4Nx~SUf5~e*bPgJ z*4=QLw9JecFV<7MstF!3qV=%Dc)1>y8tEo@mAI!}ndLnYu_QC=CNu%@uUKDa({l*j2g8n@Ec1eQoEQWmlu-h$;Dy-=};P1$idXacR+iZzBfzV z5B$r+&+4KNI3=UZ3ht52pk}lp;0I;2EtheQx+MCwpJe9z#;6STuV~B)_LEn?(Tx)r z=thAGlB=qh7W{_Cga%dGL&vxJ8)F#I%h1ba#xQKt+Nk(WI?(k?_3+~VHy@pu+zFe( zb~>D5`H}_C;t%mL+>Rf|P@PjRs42BW-KvVpqVkdQw$i0Mp&)b%O`?-1gtj4@{JnfB hv!ep`%SK%@)H9cN!B(*9-l>OYm{bc)wQ#8x{%@Xk>*xRg delta 1411 zcma))ZEO@p7{_<+=63gXw>MXNaBZ)Z?RnbWQfbNcliI4GFDXJPM~N>qxgOUAFT&N{ zwOZMhv?BE-5y45A#6$~8t$r%lT95bzd`tMCv3Pw6fmSNf@BtK!f-y#%*)8Up^n*K@ zXMcH~ng7i5%rkhRbnry!(Lnw=j^ldiy!&~NRffU2V;x0Brt*PwTN-eCB_zHsjES3t zYCg&T!skuqdrjA59$yTqm4R%)tgKi@vc;7G~Shb!APZJ zJ&CR)jtS-ExsGU}Q{C30{#x8UDe28PP+ zCY`H-*U5oWX;Y-(@wNuFxuw40DV3IG#fok6n)_BtC(_YQW1rDubm}h`34*_@3>+H3 zZ_97E;wdB6gO7Rw_)+EAd(fYp_sNZbvkp#o?+c6gSH5KOE2XMH2^%%>cx<;(QxgwQ zsK29?j%vQcca=kbqu~4v3ipbPs1bq8j74rnE@mXB zQ*=)Z9{1p>k)VrhxP?y+gz)%?Zq4wB30(Nph~^T@ojctuHgTF)#VqTx29zb9R=6`0 ze+=Gm9#?>t>8pn+fam5l{N|jA|C!&Fxz-40ApUl?m^_~cO#JobU4*(+P)M#cLJj!s z-&*|HtcIHwOkBHY;?Eb%%$YhE0Fa(i&qAPi;tEhPxT*W(d3jtuEbWnM#XlsUGOCyg zhwE;ZOg#e%Byx5$48pu_?iN}4$r*BOp!kiA4^kYpv67-`<9dn_8!IU476RS6&YepU z@Bm8-+>cMarIA-;5FvM{!3Qo!SUS3^*z*J3{!tSQfg(GTLfpNuoz!}ON!k$=+Ot@} zve4~C3KJ$_7HI!oCq56*UmZt)P6wUN%s7HOR9+e7s)acsw8C~)+o$E+`t4wHrY-kB zir5VN!Xd87(a52BG>#6Vb`(aEXUTJhC>^kn)KTM;j!1^|h*Ti|B~Qy^a=#prHL|+{ zJ`|L-)rPas@w4MgaGnj?Z(cyo$6y}0D1rdbjnMnGk!cZxcod? z#J>dwGtsT^qysNa595p3GOT3};?T@Alrmtg{T%&L#J)e&;0lV+z!|y655;nPziC}