"""前端组件目录及查询结果""" from typing import Literal, Self from pydantic import BaseModel, ConfigDict, Field, model_validator from app.schemas.base import ExtensibleModel, JsonObject, NonBlankString from app.schemas.screen import Layout, PositiveNumber class ComponentSummary(BaseModel): key: NonBlankString # 目录编号 type: NonBlankString # 前端实际组件类型 name: NonBlankString # 组件名称 description: NonBlankString # 组件用途 default_width: PositiveNumber # 默认宽度,单位 px default_height: PositiveNumber # 默认高度,单位 px 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 def to_planner_context(self) -> JsonObject: # 显式提取摘要,避免默认数据、事件和扩展字段进入规划提示词 return { "status": self.status, "catalog_version": self.catalog_version, "source": self.source, "message": self.message, "components": [ ComponentSummary( key=component.key, type=component.type, name=component.name, description=component.description, default_width=component.schema_.layout.width, default_height=component.schema_.layout.height, ).model_dump(mode="json") for component in self.components ], }