This commit is contained in:
liubiren 2026-10-12 17:48:57 +08:00
parent bcd2616d11
commit 09f3ff2cc2
7 changed files with 151 additions and 111 deletions

View File

@ -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 ###

View File

@ -23,48 +23,30 @@ def task_history_item(
""" """
# 高亮 # 高亮
is_highlight = ( is_highlight = (item.id == TaskState.popover_shown_task_history_item_id) | (
item.id == TaskState.popover_shown_task_history_item_id item.id == TaskState.task_id
) | (item.id == TaskState.task_id) )
return rx.list.item( return rx.list.item(
rx.vstack( rx.hstack(
rx.hstack( # 会话历史项的会话描述
# 会话历史项的会话描述 rx.text(
rx.text( item.description,
item.description, flex=1,
flex=1, height="22px",
height="22px", line_height="22px",
line_height="22px", padding_right="18px",
padding_right="18px", color="var(--prismui-color-2)",
color="var(--prismui-color-2)", text_overflow="ellipsis",
text_overflow="ellipsis", white_space="nowrap",
white_space="nowrap", overflow="hidden",
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",
), ),
display="flex",
align_items="center",
width="100%",
margin_bottom="8px",
# 点击事件:设置激活任务 # 点击事件:设置激活任务
on_click=lambda: TaskState.set_task(item.id), on_click=lambda: TaskState.set_task(item.id),
width="100%",
), ),
# 会话历史项的更多按钮 # 会话历史项的更多按钮
rx.box( rx.box(
@ -95,9 +77,7 @@ def task_history_item(
rx.box( rx.box(
"删除", "删除",
# 点击事件:删除任务 # 点击事件:删除任务
on_click=lambda: TaskState.delete_task( on_click=lambda: TaskState.delete_task(item.id),
item.id
),
display="flex", display="flex",
align_items="center", align_items="center",
justify_content="center", justify_content="center",
@ -225,6 +205,7 @@ def task_history(
overflow="hidden", overflow="hidden",
) )
def conversation_history( def conversation_history(
is_conversation_history_shown: bool, is_conversation_history_shown: bool,
) -> rx.Component: ) -> rx.Component:
@ -452,9 +433,7 @@ def thinking(item: MessageHistoryItem) -> rx.Component:
line_height="22px", line_height="22px",
), ),
# 点击事件,展示 / 隐藏消息历史项 # 点击事件,展示 / 隐藏消息历史项
on_click=lambda: TaskState.toggle_message_history_item_shown( on_click=lambda: TaskState.toggle_message_history_item_shown(item.id),
item.id
),
cursor="pointer", cursor="pointer",
), ),
# 思考的内容 # 思考的内容
@ -515,9 +494,7 @@ def tool_call(item: MessageHistoryItem) -> rx.Component:
line_height="22px", line_height="22px",
), ),
# 点击事件,展示 / 隐藏消息历史项 # 点击事件,展示 / 隐藏消息历史项
on_click=lambda: TaskState.toggle_message_history_item_shown( on_click=lambda: TaskState.toggle_message_history_item_shown(item.id),
item.id
),
cursor="pointer", cursor="pointer",
), ),
rx.box( rx.box(
@ -769,9 +746,7 @@ def message_history() -> rx.Component:
rx.vstack( rx.vstack(
rx.auto_scroll( rx.auto_scroll(
rx.vstack( rx.vstack(
rx.foreach( rx.foreach(TaskState.message_history, message_history_item),
TaskState.message_history, message_history_item
),
# 若等待流式输出则显示加载动效 # 若等待流式输出则显示加载动效
rx.cond( rx.cond(
TaskState.awaiting_stream, TaskState.awaiting_stream,
@ -1072,7 +1047,6 @@ def create_task_page() -> rx.Component:
) )
def conversation_page() -> rx.Component: def conversation_page() -> rx.Component:
""" """
会话页面:布局参考 MetaChat,从左到右分别为会话历史和工作区。其中,工作区从上到下分别为消息历史和用户提示词输入框。 会话页面:布局参考 MetaChat,从左到右分别为会话历史和工作区。其中,工作区从上到下分别为消息历史和用户提示词输入框。
@ -1114,4 +1088,4 @@ def conversation_page() -> rx.Component:
height="100%", height="100%",
border_radius="var(--prismui-border-radius-4)", border_radius="var(--prismui-border-radius-4)",
overflow="hidden", overflow="hidden",
) )

View File

@ -76,45 +76,35 @@ def task_history_item(
) )
return rx.list.item( return rx.list.item(
rx.vstack( rx.hstack(
rx.hstack( # 渲染任务正在运行图标
# 会话历史项的会话描述 rx.cond(
rx.text( item.is_running,
item.description, rx.icon(
flex=1, "play", width="14px", height="14px", color="var(--prismui-color-2)"
height="22px",
line_height="22px",
padding_right="18px",
color="var(--prismui-color-2)",
text_overflow="ellipsis",
white_space="nowrap",
overflow="hidden",
), ),
display="flex", rx.fragment(),
align_items="center",
width="100%",
margin_bottom="8px",
), ),
rx.hstack( # 渲染任务描述
rx.spacer(), rx.text(
# 会话历史项的会话创建日期时间 item.description,
rx.text( flex=1,
item.created_at, height="22px",
line_height="20px", line_height="22px",
font_size="var(--prismui-font-size-1)", padding_right="18px",
color="var(--prismui-color-8)", color="var(--prismui-color-2)",
), text_overflow="ellipsis",
justify_content="space-between", white_space="nowrap",
align_items="center", overflow="hidden",
width="100%",
height="20px",
margin_bottom="8px",
), ),
display="flex",
align_items="center",
width="100%",
margin_bottom="8px",
# 点击事件:设置激活任务 # 点击事件:设置激活任务
on_click=lambda: TaskState.set_task(item.id), on_click=lambda: TaskState.set_task(item.id),
width="100%",
), ),
# 会话历史项的更多按钮 # 任务更多按钮
rx.box( rx.box(
rx.popover.root( rx.popover.root(
rx.popover.trigger( rx.popover.trigger(
@ -371,6 +361,7 @@ def sidebar() -> rx.Component:
font_size="var(--prismui-font-size-2)", font_size="var(--prismui-font-size-2)",
padding="0 0 0 10px", padding="0 0 0 10px",
), ),
task_history(),
padding="0 12px", padding="0 12px",
), ),
align_items="flex-start", align_items="flex-start",

View File

@ -1,6 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
数据库状态 数据库状态
更新数据库方法:
1. reflex db makemigrations
2. reflex db migrate
""" """
from dataclasses import asdict from dataclasses import asdict
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -79,8 +83,8 @@ class TaskRecord(SQLModel, table=True):
) )
user_id: str = Field(..., index=True, description="用户唯一标识") user_id: str = Field(..., index=True, description="用户唯一标识")
description: str = Field(default="新任务", description="任务描述") description: str = Field(default="新任务", description="任务描述")
created_at: datetime = Field( last_run_at: datetime = Field(
default_factory=datetime.now, description="任务创建时间" default_factory=datetime.now, description="上一次运行日期时间"
) )
is_deleted: bool = Field( is_deleted: bool = Field(
default=False, default=False,
@ -222,7 +226,7 @@ class DatabaseState(rx.State):
usage=usage_validate_json( usage=usage_validate_json(
record.usage record.usage
), # 将 record.usage 由 JSON 字符串反序列化为 RunUsage ), # 将 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( Task(
id=task_record.id, id=task_record.id,
description=task_record.description, description=task_record.description,
last_run_at=task_record.last_run_at,
usage=usage_validate_json( usage=usage_validate_json(
task_record.usage task_record.usage
), # 将 task_record.usage 由 JSON 字符串反序列化为 RunUsage ), # 将 task_record.usage 由 JSON 字符串反序列化为 RunUsage
created_at=task_record.created_at,
workflow=workflow_validate_json( workflow=workflow_validate_json(
task_record.workflow task_record.workflow
), # 将 task_record.workflow 由 JSON 字符串反序列化为 Workflow, ), # 将 task_record.workflow 由 JSON 字符串反序列化为 Workflow,
@ -277,27 +281,31 @@ class DatabaseState(rx.State):
async def update_task_record( async def update_task_record(
self, self,
task_id: str, id: str,
description: str | None = None, description: str | None = None,
last_run_at: datetime | None = None,
is_deleted: bool | None = None, is_deleted: bool | None = None,
usage: RunUsage | None = None, usage: RunUsage | None = None,
workflow: Workflow | None = None, workflow: Workflow | None = None,
) -> None: ) -> None:
""" """
更新任务记录 更新任务记录
:param task_id: 指定任务唯一标识 :param id: 任务唯一标识
:param description: 任务描述 :param description: 任务描述
:param last_run_at: 上一次运行日期时间
:param is_deleted: 任务已删除 :param is_deleted: 任务已删除
:param usage: 使用量 :param usage: 使用量
:param workflow: 工作流 :param workflow: 工作流
:return: None :return: None
""" """
async with rx.asession() as session: async with rx.asession() as session:
record = await session.get(TaskRecord, task_id) record = await session.get(TaskRecord, id)
if not record: if not record:
return return
if isinstance(description, str): if isinstance(description, str):
record.description = description record.description = description
if isinstance(last_run_at, datetime):
record.last_run_at = last_run_at
if isinstance(is_deleted, bool): if isinstance(is_deleted, bool):
record.is_deleted = is_deleted record.is_deleted = is_deleted
if isinstance(usage, RunUsage): if isinstance(usage, RunUsage):

View File

@ -84,7 +84,7 @@ class Task(BaseModel):
id: str = Field(..., description="任务唯一标识") id: str = Field(..., description="任务唯一标识")
description: str = Field(..., description="任务描述") description: str = Field(..., description="任务描述")
created_at: datetime = Field(..., description="任务创建日期时间") last_run_at: datetime = Field(..., description="上一次运行日期时间")
usage: RunUsage = Field(..., description="任务使用量") usage: RunUsage = Field(..., description="任务使用量")
messages: dict[str, Message] = Field(default_factory=dict, description="消息字典") messages: dict[str, Message] = Field(default_factory=dict, description="消息字典")
workflow: Workflow = Field(default_factory=Workflow, description="工作,默认为对话") workflow: Workflow = Field(default_factory=Workflow, description="工作,默认为对话")
@ -105,7 +105,10 @@ class TaskHistoryItem(BaseModel):
id: str = Field(..., description="任务唯一标识") id: str = Field(..., description="任务唯一标识")
description: 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): class MessageHistoryItem(Message):

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
会话状态 任务状态
""" """
from datetime import date as Date, datetime from datetime import date as Date, datetime
from typing import AsyncGenerator from typing import AsyncGenerator
@ -35,20 +35,20 @@ from application.states.models import (
from application.workshop.events import AgentRuntimeEvent from application.workshop.events import AgentRuntimeEvent
def format_conversation_created_at(created_at: datetime) -> str: def format_datetime(datetime: datetime) -> str:
""" """
格式化会话创建日期时间 格式化日期时间
:param created_at: 创建日期时间 :param datetime: 日期时间
:return: 格式化后的日期时间字符串 :return: 格式化后日期时间字符串
""" """
match (datetime.now().date() - created_at.date()).days: match (datetime.now().date() - datetime.date()).days:
case 0: case 0:
formatted_created_at = f"{created_at.strftime('%H:%M')}" formatted_datetime = f"{datetime.strftime('%H:%M')}"
case 1: case 1:
formatted_created_at = f"昨天 {created_at.strftime('%H:%M')}" formatted_datetime = f"昨天 {datetime.strftime('%H:%M')}"
case _: case _:
formatted_created_at = created_at.strftime("%Y-%m-%d %H:%M") formatted_datetime = datetime.strftime("%Y-%m-%d %H:%M")
return formatted_created_at return formatted_datetime
class TaskState(rx.State): class TaskState(rx.State):
@ -127,14 +127,15 @@ class TaskState(rx.State):
@rx.var @rx.var
def task_history(self) -> list[TaskHistoryItem]: def task_history(self) -> list[TaskHistoryItem]:
""" """
获取会话历史项列表(按照会话创建日期时间降序排序) 获取任务历史项列表(按照任务创建日期时间降序排序)
:return: 任务历史项列表 :return: 任务历史项列表
""" """
return [ return [
TaskHistoryItem( TaskHistoryItem(
id=task.id, id=task.id,
description=task.description, 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()) for task in reversed(self.tasks.values())
] ]
@ -613,14 +614,19 @@ class TaskState(rx.State):
# 若工作流类型为预定航班则设置为对话 # 若工作流类型为预定航班则设置为对话
if task.workflow.type == WorkflowType.BOOK_FLIGHT: if task.workflow.type == WorkflowType.BOOK_FLIGHT:
task.workflow = Workflow() task.workflow = Workflow()
# 将正在运行设置为否 # 更新上一次运行日期时间
task.last_run_at = datetime.now()
# 更新正在运行状态
task.is_running = False task.is_running = False
yield # 通知前端更新渲染 yield # 通知前端更新渲染
# 更新会话记录 # 更新使用量
task.usage = result.usage
# 更新任务记录
await db_state.update_task_record( await db_state.update_task_record(
task.id, id=task.id,
usage=result.usage, last_run_at=task.last_run_at,
usage=task.usage,
workflow=task.workflow, workflow=task.workflow,
) )
@ -754,8 +760,8 @@ class TaskState(rx.State):
task.workflow = workflow task.workflow = workflow
# 获取数据库状态 # 获取数据库状态
db_state = await self.get_db_state() db_state = await self.get_db_state()
# 更新会话记录 # 更新任务记录
await db_state.update_task_record( await db_state.update_task_record(
task.id, id=task.id,
workflow=task.workflow, workflow=task.workflow,
) )

Binary file not shown.