feat: 完成资源接入
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
"""动作错误的修正建议"""
|
||||
|
||||
from typing import Self
|
||||
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
from app.schemas.base import NonBlankString
|
||||
|
||||
|
||||
class ActionRepairPlan(BaseModel):
|
||||
retryable: bool = Field(description="是否可在现有需求和快照下修正")
|
||||
reason: NonBlankString = Field(description="错误原因或无法修正的原因")
|
||||
suggestions: list[NonBlankString] = Field(default_factory=list, description="重新生成动作时的具体修正建议")
|
||||
|
||||
@model_validator(mode="after")
|
||||
def require_suggestions(self) -> Self:
|
||||
if self.retryable and not self.suggestions:
|
||||
raise ValueError("可重试时必须提供修正建议")
|
||||
return self
|
||||
@@ -2,12 +2,27 @@
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, ValidationError
|
||||
|
||||
|
||||
class ActionValidationError(BaseModel):
|
||||
action_index: int | None = Field(
|
||||
default=None, ge=0, description="失败动作的下标,从 0 开始,整批错误为 null"
|
||||
)
|
||||
code: Literal["schema_error", "execution_error"]
|
||||
code: Literal["schema_error", "generation_error", "scope_error", "execution_error"]
|
||||
message: str
|
||||
|
||||
|
||||
def schema_errors(error: ValidationError) -> list[ActionValidationError]:
|
||||
errors = []
|
||||
for item in error.errors(include_url=False, include_input=False):
|
||||
location = item["loc"]
|
||||
index = location[1] if len(location) > 1 and type(location[1]) is int else None
|
||||
errors.append(
|
||||
ActionValidationError(
|
||||
action_index=index,
|
||||
code="schema_error",
|
||||
message=f"{' / '.join(map(str, location))}: {item['msg']}",
|
||||
)
|
||||
)
|
||||
return errors
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
"""前端组件目录及查询结果"""
|
||||
|
||||
from typing import Literal, Self
|
||||
|
||||
from pydantic import ConfigDict, Field, model_validator
|
||||
|
||||
from app.schemas.base import ExtensibleModel, JsonObject, NonBlankString
|
||||
from app.schemas.screen import Layout
|
||||
|
||||
|
||||
class MaterialTemplate(ExtensibleModel):
|
||||
type: NonBlankString
|
||||
name: NonBlankString
|
||||
layout: Layout
|
||||
props: JsonObject = Field(default_factory=dict)
|
||||
style: JsonObject | None = None
|
||||
events: list[JsonObject] = Field(default_factory=list)
|
||||
|
||||
|
||||
class MaterialSetter(ExtensibleModel):
|
||||
type: NonBlankString = Field(description="前端属性控件类型,不是完整的值类型约束")
|
||||
label: NonBlankString
|
||||
key: NonBlankString = Field(
|
||||
description="前端原始点路径,例如 props.option.series.0.itemStyle.color"
|
||||
)
|
||||
props: JsonObject = Field(default_factory=dict)
|
||||
|
||||
|
||||
class ComponentDefinition(ExtensibleModel):
|
||||
model_config = ConfigDict(serialize_by_alias=True)
|
||||
key: NonBlankString = Field(description="目录中的唯一编号,不是 Screen 节点 ID")
|
||||
type: NonBlankString = Field(
|
||||
description="前端实际注册的节点 type,不根据图表名称猜测"
|
||||
)
|
||||
name: NonBlankString = Field(description="组件名称")
|
||||
description: NonBlankString = Field(description="组件用途和支持的展示方式")
|
||||
tags: list[NonBlankString] = Field(
|
||||
default_factory=list, description="用于检索的分类和关键词"
|
||||
)
|
||||
renderer: NonBlankString = Field(description="渲染能力标识,当前从前端实现映射而来")
|
||||
group: NonBlankString
|
||||
icon: NonBlankString | None = None
|
||||
schema_: MaterialTemplate = Field(
|
||||
alias="schema", description="完整默认物料配置,不包含实例 ID"
|
||||
)
|
||||
setters: list[MaterialSetter] = Field(default_factory=list)
|
||||
eventOptions: list[JsonObject] = Field(default_factory=list)
|
||||
sample_data: bool = Field(
|
||||
default=False, description="默认配置是否含示例数据,不能视为真实业务数据"
|
||||
)
|
||||
props_schema: JsonObject | None = Field(
|
||||
default=None,
|
||||
description="可选的 props JSON Schema,未提供表示未知,不表示无限制",
|
||||
)
|
||||
style_schema: JsonObject | None = Field(
|
||||
default=None, description="可选的 style JSON Schema,仅保存定义,当前不执行校验"
|
||||
)
|
||||
usage_notes: list[NonBlankString] = Field(
|
||||
default_factory=list, description="配置、数据绑定及使用限制说明"
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def consistent_type(self) -> Self:
|
||||
if self.type != self.schema_.type:
|
||||
raise ValueError("目录 type 必须与 schema.type 一致")
|
||||
if "id" in self.schema_.model_extra:
|
||||
raise ValueError("默认物料配置不能包含实例 ID")
|
||||
return self
|
||||
|
||||
|
||||
class ComponentCatalog(ExtensibleModel):
|
||||
version: NonBlankString = Field(
|
||||
description="前端组件目录版本,后续用于检查是否与前端匹配"
|
||||
)
|
||||
source: NonBlankString = Field(description="真实定义来源,例如前端组件注册文件路径")
|
||||
components: list[ComponentDefinition]
|
||||
|
||||
@model_validator(mode="after")
|
||||
def unique_keys(self) -> Self:
|
||||
keys = [component.key for component in self.components]
|
||||
if len(keys) != len(set(keys)):
|
||||
raise ValueError("组件目录 key 不能重复")
|
||||
return self
|
||||
|
||||
|
||||
class ComponentQuery(ExtensibleModel):
|
||||
key: NonBlankString | None = Field(default=None, description="精确匹配目录编号")
|
||||
type: NonBlankString | None = Field(
|
||||
default=None, description="精确匹配前端节点类型,可能对应多个预设"
|
||||
)
|
||||
keyword: NonBlankString | None = Field(
|
||||
default=None, description="按名称、说明和标签查询,多个条件同时满足"
|
||||
)
|
||||
|
||||
|
||||
class ComponentResult(ExtensibleModel):
|
||||
status: Literal["resolved", "not_found", "unsupported"]
|
||||
components: list[ComponentDefinition] = Field(default_factory=list)
|
||||
catalog_version: NonBlankString | None = None
|
||||
source: NonBlankString | None = None
|
||||
message: NonBlankString
|
||||
@@ -0,0 +1,126 @@
|
||||
"""大屏规划协议,描述设计方案,不直接执行动作"""
|
||||
|
||||
from typing import Literal, Self
|
||||
|
||||
from pydantic import Field, model_validator
|
||||
|
||||
from app.schemas.base import ExtensibleModel, JsonObject, NonBlankString
|
||||
from app.schemas.screen import Layout
|
||||
|
||||
|
||||
class PlanRegion(ExtensibleModel):
|
||||
region_id: NonBlankString = Field(description="规划内的区域编号,不是前端组件 ID")
|
||||
name: NonBlankString = Field(description="区域名称,例如顶部指标区")
|
||||
purpose: NonBlankString = Field(description="区域用途和展示重点")
|
||||
layout_description: NonBlankString = Field(
|
||||
description="区域位置、排列和空间分配说明"
|
||||
)
|
||||
layout: Layout | None = Field(
|
||||
default=None, description="可选的建议位置和尺寸,画布绝对坐标,单位 px"
|
||||
)
|
||||
|
||||
|
||||
class PlanComponent(ExtensibleModel):
|
||||
plan_id: NonBlankString = Field(
|
||||
description="规划内的组件编号,用于关联资源,不作为新增组件的实际 ID"
|
||||
)
|
||||
disposition: Literal["keep", "modify", "add", "remove"] = Field(
|
||||
description="保留、调整、新增或移除,只表达规划意图,不代表执行能力已经支持"
|
||||
)
|
||||
target_id: NonBlankString | None = Field(
|
||||
default=None, description="已有组件 ID,新增时必须为空"
|
||||
)
|
||||
region_id: NonBlankString | None = Field(
|
||||
default=None, description="所属规划区域,可为空,例如移除组件或未分区"
|
||||
)
|
||||
purpose: NonBlankString = Field(description="展示目的或调整原因")
|
||||
component_type: NonBlankString | None = Field(
|
||||
default=None, description="建议组件类型,不代表已在组件库中确认可用"
|
||||
)
|
||||
metrics: list[NonBlankString] = Field(
|
||||
default_factory=list, description="展示的指标和维度描述,不是数据绑定"
|
||||
)
|
||||
design: JsonObject = Field(
|
||||
default_factory=dict, description="样式、交互及内容等设计要求,保留扩展字段"
|
||||
)
|
||||
layout: Layout | None = Field(
|
||||
default=None, description="可选的建议布局,画布绝对坐标,不是区域内相对坐标"
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_target(self) -> Self:
|
||||
if self.disposition == "add":
|
||||
if self.target_id is not None:
|
||||
raise ValueError("新增组件不能指定已有 target_id")
|
||||
elif self.target_id is None:
|
||||
raise ValueError("保留、调整或移除组件必须提供 target_id")
|
||||
return self
|
||||
|
||||
|
||||
class PlanResourceNeed(ExtensibleModel):
|
||||
resource_id: NonBlankString = Field(
|
||||
description="本次规划中的资源需求编号,不是已获取资源的 ID"
|
||||
)
|
||||
kind: NonBlankString = Field(
|
||||
description="资源类别,例如 data、component、template、asset、knowledge,允许扩展"
|
||||
)
|
||||
description: NonBlankString = Field(description="需要查询或补充的内容")
|
||||
purpose: NonBlankString = Field(description="资源用途和查询原因")
|
||||
component_ids: list[NonBlankString] = Field(
|
||||
default_factory=list, description="关联的 plan_id 列表,为空表示整个规划使用"
|
||||
)
|
||||
|
||||
|
||||
class ScreenPlan(ExtensibleModel):
|
||||
goal: NonBlankString = Field(description="规划目标,遵循已确认需求")
|
||||
design_summary: NonBlankString = Field(description="整体或局部设计思路")
|
||||
layout_description: NonBlankString = Field(
|
||||
description="区域关系、布局安排及保留内容"
|
||||
)
|
||||
canvas_changes: JsonObject = Field(
|
||||
default_factory=dict, description="建议的画布属性变化,为空表示不改画布"
|
||||
)
|
||||
regions: list[PlanRegion] = Field(default_factory=list)
|
||||
components: list[PlanComponent] = Field(default_factory=list)
|
||||
resource_needs: list[PlanResourceNeed] = Field(
|
||||
default_factory=list, description="规划细化后的待查询资源,不能声称已经获取"
|
||||
)
|
||||
constraints: list[NonBlankString] = Field(
|
||||
default_factory=list, description="沿用用户约束,不擅自放宽"
|
||||
)
|
||||
assumptions: list[NonBlankString] = Field(
|
||||
default_factory=list, description="非阻塞设计偏好的默认假设"
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_references(self) -> Self:
|
||||
region_ids = [region.region_id for region in self.regions]
|
||||
plan_ids = [component.plan_id for component in self.components]
|
||||
target_ids = [
|
||||
component.target_id
|
||||
for component in self.components
|
||||
if component.target_id is not None
|
||||
]
|
||||
resource_ids = [resource.resource_id for resource in self.resource_needs]
|
||||
for name, values in (
|
||||
("region_id", region_ids),
|
||||
("plan_id", plan_ids),
|
||||
("target_id", target_ids),
|
||||
("resource_id", resource_ids),
|
||||
):
|
||||
if len(values) != len(set(values)):
|
||||
raise ValueError(f"规划中存在重复的 {name}")
|
||||
for component in self.components:
|
||||
if (
|
||||
component.region_id is not None
|
||||
and component.region_id not in region_ids
|
||||
):
|
||||
raise ValueError(f"组件引用了不存在的区域: {component.region_id}")
|
||||
for resource in self.resource_needs:
|
||||
if len(resource.component_ids) != len(set(resource.component_ids)):
|
||||
raise ValueError(
|
||||
f"资源 {resource.resource_id} 的 component_ids 不能重复"
|
||||
)
|
||||
if unknown := set(resource.component_ids) - set(plan_ids):
|
||||
raise ValueError(f"资源引用了不存在的规划组件: {sorted(unknown)}")
|
||||
return self
|
||||
Reference in New Issue
Block a user