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:
renjianbo
2026-05-08 22:09:52 +08:00
parent d0b55f2b16
commit dca6020730
8 changed files with 767 additions and 3 deletions

View 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)

View File

@@ -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="定时执行时发送的消息内容")

View File

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