feat:优化执行步骤

This commit is contained in:
2026-10-08 00:28:29 -07:00
parent 62a5b2a8f4
commit 05c050d2ba
20 changed files with 625 additions and 69 deletions
+22
View File
@@ -7,6 +7,7 @@ from pydantic import Field
from .add import AddAction
from .base import ActionModel, BaseAction
from .delete import DeleteAction
from .layout import AlignAction, ArrangeAction, DistributeAction, SetLayoutAction
from .move import MoveAction
from .resize import ResizeAction
from .update import UpdateAction
@@ -23,8 +24,29 @@ Action = Annotated[
Field(discriminator="type"),
]
# 模型可以提交的动作:前端协议 + 布局意图
# 布局意图由后端展开成 move 和 resize,不会返回给前端
AgentAction = Annotated[
AddAction
| DeleteAction
| MoveAction
| ResizeAction
| UpdateAction
| UpdateCanvasAction
| ArrangeAction
| AlignAction
| DistributeAction
| SetLayoutAction,
Field(discriminator="type"),
]
__all__ = [
"Action",
"AgentAction",
"AlignAction",
"ArrangeAction",
"DistributeAction",
"SetLayoutAction",
"ActionModel",
"AddAction",
"BaseAction",
+118
View File
@@ -0,0 +1,118 @@
"""布局意图:模型只说“怎么排”,坐标由后端算,展开成 move 和 resize 后才交给前端
这些动作只存在于模型提交的这一步,不会出现在返回给前端的动作里,前端协议不变
"""
from typing import Annotated, Literal, Self
from pydantic import Field, model_validator
from app.schemas.screen import Number, PositiveNumber
from .base import ActionModel, BaseAction
NonNegativeNumber = Annotated[Number, Field(ge=0)]
TargetIds = Annotated[
list[str],
Field(min_length=1, description="要排列的组件 ID,不能重复"),
]
class Area(ActionModel):
"""画布上的一块矩形区域,坐标单位 px"""
x: Number = Field(description="左上角横坐标")
y: Number = Field(description="左上角纵坐标")
width: PositiveNumber
height: PositiveNumber
class _Group(ActionModel):
target_ids: TargetIds
@model_validator(mode="after")
def unique_ids(self) -> Self:
if len(set(self.target_ids)) != len(self.target_ids):
raise ValueError("target_ids 不能重复")
return self
class ArrangeAction(_Group):
"""把一组组件平分一块区域:每个组件的位置和尺寸都由这里决定,彼此不重叠、不超出区域
用于“平分空间”“排成一行/一列”“两行两列铺满”等;按 target_ids 的顺序从左到右、从上到下放置
"""
type: Literal["arrange"] = "arrange"
mode: Literal["row", "column", "grid"] = Field(
description="row:排成一行;column:排成一列;grid:按 columns 列排成网格"
)
columns: int | None = Field(
default=None, ge=1, description="mode=grid 时每行几个,其他模式不填"
)
area: Area | None = Field(
default=None, description="要铺满的区域,省略表示整个画布"
)
gap: NonNegativeNumber = Field(default=20, description="组件之间的间距,单位 px")
padding: NonNegativeNumber = Field(
default=0, description="区域四周留出的边距,单位 px"
)
@model_validator(mode="after")
def columns_match_mode(self) -> Self:
if (self.mode == "grid") != (self.columns is not None):
raise ValueError("mode=grid 时必须填写 columns,其他模式不填")
return self
class AlignAction(_Group):
"""按基准对齐,不改变尺寸;居中、靠左、靠右、顶部对齐等"""
type: Literal["align"] = "align"
to: Literal["canvas", "selection"] = Field(
default="canvas",
description="canvas:对齐到画布;selection:对齐到这些组件合起来的外框",
)
horizontal: Literal["left", "center", "right"] | None = Field(
default=None, description="水平方向的对齐方式,省略表示不调整水平位置"
)
vertical: Literal["top", "middle", "bottom"] | None = Field(
default=None, description="垂直方向的对齐方式,省略表示不调整垂直位置"
)
@model_validator(mode="after")
def require_direction(self) -> Self:
if self.horizontal is None and self.vertical is None:
raise ValueError("align 至少提供 horizontal 或 vertical")
return self
class DistributeAction(_Group):
"""沿一个方向等间距排开,不改变尺寸;按当前位置的先后顺序排列"""
type: Literal["distribute"] = "distribute"
axis: Literal["horizontal", "vertical"]
gap: NonNegativeNumber | None = Field(
default=None,
description="固定间距,单位 px,从区域起点(没有 area 时是最前一个组件的位置)依次排开;"
"省略表示把间距摊匀,首尾两个组件的外沿不动",
)
area: Area | None = Field(
default=None, description="在这块区域内排开,省略表示用这些组件合起来的外框"
)
class SetLayoutAction(BaseAction):
"""直接设置组件的绝对位置和尺寸,只写要改的项,其余保持不变;不需要自己换算位移"""
type: Literal["set_layout"] = "set_layout"
x: Number | None = Field(default=None, description="目标左上角横坐标")
y: Number | None = Field(default=None, description="目标左上角纵坐标")
width: PositiveNumber | None = Field(default=None, description="目标宽度")
height: PositiveNumber | None = Field(default=None, description="目标高度")
@model_validator(mode="after")
def require_field(self) -> Self:
if all(v is None for v in (self.x, self.y, self.width, self.height)):
raise ValueError("set_layout 至少提供 x、y、width、height 中的一项")
return self
+1 -1
View File
@@ -10,7 +10,7 @@ from .base import BaseAction
class MoveAction(BaseAction):
"""按相对位移移动组件。目标是绝对位置时,用目标坐标减去当前坐标,例如当前 x=100、目标 x=150 时 delta_x=50"""
"""按相对位移移动组件,适合“往右挪一点”这类相对说法;目标位置是确定的用 set_layout,多个组件排列用 arrange、align、distribute,不要自己换算坐标"""
type: Literal["move"] = "move"
delta_x: Number = Field(