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 = (
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,从左到右分别为会话历史和工作区。其中,工作区从上到下分别为消息历史和用户提示词输入框。

View File

@ -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",

View File

@ -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):

View File

@ -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):

View File

@ -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,
)

Binary file not shown.