feat: add Digital Employee Factory frontend, Goal scheduling, and Phase 4 UI
- New pages: DigitalEmployeeFactory.vue (goal grid + create dialog), GoalDetail.vue (task tree + progress) - New store: goal.ts (Pinia store with full Goal/Task API bindings) - Router: add /digital-employees and /goals/:id routes - MainLayout: add "数字员工工厂" nav menu item with UserFilled icon - Schedule model: add schedule_type, goal_id, goal_config for Goal-type cron scheduling - Schedule service: add _create_execution_for_goal_schedule for periodic Goal creation - Migration 014: add schedule_type/goal_id/goal_config columns to agent_schedules Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
30
backend/alembic/versions/014_add_schedule_goal_fields.py
Normal file
30
backend/alembic/versions/014_add_schedule_goal_fields.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""add schedule_type, goal_id, goal_config to agent_schedules
|
||||
|
||||
Revision ID: 014_add_schedule_goal_fields
|
||||
Revises: 013_add_goals_tasks
|
||||
Create Date: 2026-05-08
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects.mysql import CHAR
|
||||
|
||||
revision = "014_add_schedule_goal_fields"
|
||||
down_revision = "013_add_goals_tasks"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("agent_schedules", sa.Column("schedule_type", sa.String(20), default="agent", comment="调度类型: agent / goal"))
|
||||
op.add_column("agent_schedules", sa.Column("goal_id", CHAR(36), sa.ForeignKey("goals.id"), nullable=True, comment="关联 Goal ID"))
|
||||
op.add_column("agent_schedules", sa.Column("goal_config", sa.JSON, nullable=True, comment="Goal 调度配置"))
|
||||
op.create_index("ix_agent_schedules_goal_id", "agent_schedules", ["goal_id"])
|
||||
op.alter_column("agent_schedules", "agent_id", existing_type=CHAR(36), nullable=True)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_agent_schedules_goal_id", table_name="agent_schedules")
|
||||
op.drop_column("agent_schedules", "goal_config")
|
||||
op.drop_column("agent_schedules", "goal_id")
|
||||
op.drop_column("agent_schedules", "schedule_type")
|
||||
op.alter_column("agent_schedules", "agent_id", existing_type=CHAR(36), nullable=False)
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Agent 定时任务表:按 cron 表达式周期执行 Agent"""
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, String, Text, Integer, DateTime, ForeignKey, Boolean
|
||||
from sqlalchemy import Column, String, Text, Integer, DateTime, ForeignKey, Boolean, JSON
|
||||
from sqlalchemy.dialects.mysql import CHAR
|
||||
from app.core.database import Base
|
||||
|
||||
@@ -11,7 +11,10 @@ class AgentSchedule(Base):
|
||||
__tablename__ = "agent_schedules"
|
||||
|
||||
id = Column(CHAR(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
agent_id = Column(CHAR(36), ForeignKey("agents.id"), nullable=False, index=True, comment="关联 Agent ID")
|
||||
agent_id = Column(CHAR(36), ForeignKey("agents.id"), nullable=True, index=True, comment="关联 Agent ID(schedule_type=agent 时必填)")
|
||||
schedule_type = Column(String(20), default="agent", comment="调度类型: agent / goal")
|
||||
goal_id = Column(CHAR(36), ForeignKey("goals.id"), nullable=True, index=True, comment="关联 Goal ID(schedule_type=goal 时必填)")
|
||||
goal_config = Column(JSON, nullable=True, comment="Goal 调度配置: {title, description, priority}")
|
||||
name = Column(String(100), nullable=False, comment="任务名称")
|
||||
cron_expression = Column(String(100), nullable=False, comment="cron 表达式,如 0 9 * * *")
|
||||
input_message = Column(Text, nullable=False, comment="定时执行时发送的消息内容")
|
||||
|
||||
@@ -47,6 +47,46 @@ def compute_next_run(cron_expression: str, after: Optional[datetime] = None, tz:
|
||||
return next_utc.replace(tzinfo=None)
|
||||
|
||||
|
||||
def _create_execution_for_goal_schedule(db: Session, schedule) -> Optional[str]:
|
||||
"""为 Goal 类型定时任务创建 Goal 并投递执行。
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
schedule: AgentSchedule ORM 对象 (schedule_type == "goal")
|
||||
|
||||
Returns:
|
||||
创建的 goal_id,失败返回 None
|
||||
"""
|
||||
from app.services.goal_service import create_goal, update_goal
|
||||
from app.tasks.goal_tasks import execute_goal_task
|
||||
|
||||
gc = schedule.goal_config or {}
|
||||
title = gc.get("title", schedule.input_message or schedule.name)
|
||||
description = gc.get("description", "")
|
||||
priority = gc.get("priority", 5)
|
||||
main_agent_id = schedule.agent_id or gc.get("main_agent_id")
|
||||
|
||||
try:
|
||||
goal = create_goal(
|
||||
db=db,
|
||||
creator_id=schedule.user_id,
|
||||
title=title,
|
||||
description=description,
|
||||
priority=priority,
|
||||
main_agent_id=main_agent_id,
|
||||
)
|
||||
task = execute_goal_task.delay(str(goal.id))
|
||||
update_goal(db, str(goal.id), status="active")
|
||||
logger.info(
|
||||
"Goal 定时任务 %s 已投递: goal_id=%s celery_task=%s",
|
||||
schedule.id, goal.id, task.id,
|
||||
)
|
||||
return str(goal.id)
|
||||
except Exception as e:
|
||||
logger.error("Goal 定时任务 %s 投递失败: %s", schedule.id, e)
|
||||
return None
|
||||
|
||||
|
||||
def create_execution_for_schedule(db: Session, schedule) -> Optional[str]:
|
||||
"""为定时任务创建 Execution 记录并投递 Celery 任务。
|
||||
|
||||
@@ -60,6 +100,10 @@ def create_execution_for_schedule(db: Session, schedule) -> Optional[str]:
|
||||
from app.models.execution import Execution
|
||||
from app.models.agent import Agent
|
||||
|
||||
# Goal 类型调度:创建 Goal 并投递执行
|
||||
if getattr(schedule, "schedule_type", "agent") == "goal" and schedule.goal_id:
|
||||
return _create_execution_for_goal_schedule(db, schedule)
|
||||
|
||||
agent = db.query(Agent).filter(Agent.id == schedule.agent_id).first()
|
||||
if not agent:
|
||||
logger.warning("定时任务 %s 关联的 Agent %s 不存在", schedule.id, schedule.agent_id)
|
||||
|
||||
Reference in New Issue
Block a user