@josephyan/qingflow-app-builder-mcp 1.1.16 → 1.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +56 -11
- package/skills/qingflow-app-builder/references/complete-system-development-guide.md +54 -5
- package/skills/qingflow-app-builder/references/create-app.md +10 -1
- package/skills/qingflow-app-builder/references/gotchas.md +17 -3
- package/skills/qingflow-app-builder/references/single-app-development-guide.md +33 -4
- package/skills/qingflow-app-builder/references/tool-selection.md +20 -4
- package/skills/qingflow-app-builder/references/update-schema.md +11 -0
- package/skills/qingflow-app-builder/references/update-views.md +49 -0
- package/src/qingflow_mcp/builder_facade/models.py +202 -2
- package/src/qingflow_mcp/builder_facade/service.py +622 -29
- package/src/qingflow_mcp/cli/commands/builder.py +6 -3
- package/src/qingflow_mcp/server_app_builder.py +6 -3
- package/src/qingflow_mcp/tools/ai_builder_tools.py +548 -30
|
@@ -1105,6 +1105,91 @@ class ViewAssociatedResourcesPatch(StrictModel):
|
|
|
1105
1105
|
)
|
|
1106
1106
|
|
|
1107
1107
|
|
|
1108
|
+
class ViewActionButtonPatch(StrictModel):
|
|
1109
|
+
button_id: int | None = Field(default=None, validation_alias=AliasChoices("button_id", "buttonId", "id"))
|
|
1110
|
+
client_key: str | None = Field(default=None, validation_alias=AliasChoices("client_key", "clientKey"))
|
|
1111
|
+
text: str = Field(validation_alias=AliasChoices("text", "button_text", "buttonText", "name"))
|
|
1112
|
+
action: PublicButtonTriggerAction = Field(validation_alias=AliasChoices("action", "trigger_action", "triggerAction"))
|
|
1113
|
+
url: str | None = Field(default=None, validation_alias=AliasChoices("url", "link_url", "linkUrl", "trigger_link_url", "triggerLinkUrl"))
|
|
1114
|
+
target_app_key: str | None = Field(default=None, validation_alias=AliasChoices("target_app_key", "targetAppKey", "related_app_key", "relatedAppKey"))
|
|
1115
|
+
target_app_name: str | None = Field(default=None, validation_alias=AliasChoices("target_app_name", "targetAppName", "related_app_name", "relatedAppName"))
|
|
1116
|
+
field_mappings: list[dict[str, Any]] = Field(default_factory=list, validation_alias=AliasChoices("field_mappings", "fieldMappings", "mappings"))
|
|
1117
|
+
default_values: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("default_values", "defaultValues", "defaults"))
|
|
1118
|
+
placement: PublicButtonPlacement = Field(default=PublicButtonPlacement.detail, validation_alias=AliasChoices("placement", "position"))
|
|
1119
|
+
primary: bool = Field(default=False, validation_alias=AliasChoices("primary", "being_main", "beingMain"))
|
|
1120
|
+
visible_when: list[list[ViewFilterRulePatch]] = Field(
|
|
1121
|
+
default_factory=list,
|
|
1122
|
+
validation_alias=AliasChoices("visible_when", "visibleWhen", "button_limit", "buttonLimit"),
|
|
1123
|
+
)
|
|
1124
|
+
button_formula: str | None = Field(default=None, validation_alias=AliasChoices("button_formula", "buttonFormula"))
|
|
1125
|
+
button_formula_type: int = Field(default=1, validation_alias=AliasChoices("button_formula_type", "buttonFormulaType"))
|
|
1126
|
+
print_tpls: list[Any] = Field(default_factory=list, validation_alias=AliasChoices("print_tpls", "printTpls"))
|
|
1127
|
+
style_preset: str | None = Field(default=None, validation_alias=AliasChoices("style_preset", "stylePreset"))
|
|
1128
|
+
background_color: str | None = Field(default=None, validation_alias=AliasChoices("background_color", "backgroundColor"))
|
|
1129
|
+
text_color: str | None = Field(default=None, validation_alias=AliasChoices("text_color", "textColor"))
|
|
1130
|
+
button_icon: str | None = Field(default=None, validation_alias=AliasChoices("button_icon", "buttonIcon"))
|
|
1131
|
+
external_qrobot_config: dict[str, Any] | None = Field(
|
|
1132
|
+
default=None,
|
|
1133
|
+
validation_alias=AliasChoices(
|
|
1134
|
+
"external_qrobot_config",
|
|
1135
|
+
"externalQrobotConfig",
|
|
1136
|
+
"externalQRobotConfig",
|
|
1137
|
+
"custom_button_external_qrobot_relation_vo",
|
|
1138
|
+
"customButtonExternalQRobotRelationVO",
|
|
1139
|
+
),
|
|
1140
|
+
)
|
|
1141
|
+
trigger_wings_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("trigger_wings_config", "triggerWingsConfig"))
|
|
1142
|
+
|
|
1143
|
+
@model_validator(mode="before")
|
|
1144
|
+
@classmethod
|
|
1145
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
1146
|
+
if not isinstance(value, dict):
|
|
1147
|
+
return value
|
|
1148
|
+
payload = dict(value)
|
|
1149
|
+
raw_action = payload.get("action", payload.get("trigger_action", payload.get("triggerAction")))
|
|
1150
|
+
if isinstance(raw_action, str):
|
|
1151
|
+
normalized = raw_action.strip()
|
|
1152
|
+
aliases = {
|
|
1153
|
+
"add_data": PublicButtonTriggerAction.add_data.value,
|
|
1154
|
+
"add-data": PublicButtonTriggerAction.add_data.value,
|
|
1155
|
+
"adddata": PublicButtonTriggerAction.add_data.value,
|
|
1156
|
+
"addData": PublicButtonTriggerAction.add_data.value,
|
|
1157
|
+
"link": PublicButtonTriggerAction.link.value,
|
|
1158
|
+
"url": PublicButtonTriggerAction.link.value,
|
|
1159
|
+
"qrobot": PublicButtonTriggerAction.qrobot.value,
|
|
1160
|
+
"qRobot": PublicButtonTriggerAction.qrobot.value,
|
|
1161
|
+
"wings": PublicButtonTriggerAction.wings.value,
|
|
1162
|
+
}
|
|
1163
|
+
payload["action"] = aliases.get(normalized, aliases.get(normalized.lower(), normalized))
|
|
1164
|
+
raw_placement = payload.get("placement", payload.get("position", payload.get("config_type", payload.get("configType"))))
|
|
1165
|
+
if isinstance(raw_placement, str):
|
|
1166
|
+
normalized = raw_placement.strip().lower()
|
|
1167
|
+
if normalized in {"top", "header"}:
|
|
1168
|
+
payload["placement"] = PublicButtonPlacement.header.value
|
|
1169
|
+
elif normalized in {"detail", "data_detail"}:
|
|
1170
|
+
payload["placement"] = PublicButtonPlacement.detail.value
|
|
1171
|
+
elif normalized in {"list", "row", "row_action", "inside"}:
|
|
1172
|
+
payload["placement"] = PublicButtonPlacement.list.value
|
|
1173
|
+
raw_limits = payload.get("visible_when", payload.get("visibleWhen", payload.get("button_limit", payload.get("buttonLimit"))))
|
|
1174
|
+
if isinstance(raw_limits, list) and raw_limits and all(isinstance(item, dict) for item in raw_limits):
|
|
1175
|
+
payload["visible_when"] = [raw_limits]
|
|
1176
|
+
return payload
|
|
1177
|
+
|
|
1178
|
+
@model_validator(mode="after")
|
|
1179
|
+
def validate_shape(self) -> "ViewActionButtonPatch":
|
|
1180
|
+
if not str(self.text or "").strip():
|
|
1181
|
+
raise ValueError("action_buttons[].text is required")
|
|
1182
|
+
if self.action == PublicButtonTriggerAction.link and not str(self.url or "").strip():
|
|
1183
|
+
raise ValueError("link action_buttons require url")
|
|
1184
|
+
if self.action == PublicButtonTriggerAction.add_data and not str(self.target_app_key or "").strip():
|
|
1185
|
+
raise ValueError("add_data action_buttons require target_app_key")
|
|
1186
|
+
if self.action == PublicButtonTriggerAction.qrobot and self.external_qrobot_config is None:
|
|
1187
|
+
raise ValueError("qRobot action_buttons require external_qrobot_config")
|
|
1188
|
+
if self.action == PublicButtonTriggerAction.wings and self.trigger_wings_config is None:
|
|
1189
|
+
raise ValueError("wings action_buttons require trigger_wings_config")
|
|
1190
|
+
return self
|
|
1191
|
+
|
|
1192
|
+
|
|
1108
1193
|
class ViewUpsertPatch(StrictModel):
|
|
1109
1194
|
name: str
|
|
1110
1195
|
view_key: str | None = Field(default=None, validation_alias=AliasChoices("view_key", "viewKey"))
|
|
@@ -1116,6 +1201,8 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1116
1201
|
end_field: str | None = Field(default=None, validation_alias=AliasChoices("end_field", "endField"))
|
|
1117
1202
|
title_field: str | None = Field(default=None, validation_alias=AliasChoices("title_field", "titleField"))
|
|
1118
1203
|
buttons: list["ViewButtonBindingPatch"] | None = None
|
|
1204
|
+
action_buttons: list[ViewActionButtonPatch] | None = Field(default=None, validation_alias=AliasChoices("action_buttons", "actionButtons"))
|
|
1205
|
+
action_buttons_mode: str = Field(default="merge", validation_alias=AliasChoices("action_buttons_mode", "actionButtonsMode", "button_mode", "buttonMode"))
|
|
1119
1206
|
visibility: VisibilityPatch | None = None
|
|
1120
1207
|
query_conditions: ViewQueryConditionsPatch | None = Field(default=None, validation_alias=AliasChoices("query_conditions", "queryConditions", "query_condition", "queryCondition"))
|
|
1121
1208
|
associated_resources: ViewAssociatedResourcesPatch | None = Field(
|
|
@@ -1165,6 +1252,12 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1165
1252
|
payload["associated_resources"] = payload.pop("associatedReports")
|
|
1166
1253
|
if "asosChartConfig" in payload and "associated_resources" not in payload:
|
|
1167
1254
|
payload["associated_resources"] = payload.pop("asosChartConfig")
|
|
1255
|
+
if "actionButtons" in payload and "action_buttons" not in payload:
|
|
1256
|
+
payload["action_buttons"] = payload.pop("actionButtons")
|
|
1257
|
+
if "actionButtonsMode" in payload and "action_buttons_mode" not in payload:
|
|
1258
|
+
payload["action_buttons_mode"] = payload.pop("actionButtonsMode")
|
|
1259
|
+
if "buttonMode" in payload and "action_buttons_mode" not in payload:
|
|
1260
|
+
payload["action_buttons_mode"] = payload.pop("buttonMode")
|
|
1168
1261
|
raw_type = payload.get("type")
|
|
1169
1262
|
if isinstance(raw_type, str):
|
|
1170
1263
|
normalized = raw_type.strip().lower()
|
|
@@ -1186,6 +1279,10 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1186
1279
|
raise ValueError("board view requires group_by")
|
|
1187
1280
|
if self.type == PublicViewType.gantt and not (self.start_field and self.end_field):
|
|
1188
1281
|
raise ValueError("gantt view requires start_field and end_field")
|
|
1282
|
+
normalized_mode = str(self.action_buttons_mode or "merge").strip().lower()
|
|
1283
|
+
if normalized_mode not in {"merge", "replace"}:
|
|
1284
|
+
raise ValueError("action_buttons_mode must be merge or replace")
|
|
1285
|
+
self.action_buttons_mode = normalized_mode
|
|
1189
1286
|
return self
|
|
1190
1287
|
|
|
1191
1288
|
|
|
@@ -1211,6 +1308,76 @@ class ViewPartialPatch(StrictModel):
|
|
|
1211
1308
|
return self
|
|
1212
1309
|
|
|
1213
1310
|
|
|
1311
|
+
def public_button_trigger_action_value(value: Any) -> str:
|
|
1312
|
+
raw = value.value if isinstance(value, PublicButtonTriggerAction) else str(value or "").strip()
|
|
1313
|
+
normalized = raw.lower()
|
|
1314
|
+
if raw == PublicButtonTriggerAction.add_data.value or normalized in {"add_data", "add-data", "adddata"}:
|
|
1315
|
+
return "add_data"
|
|
1316
|
+
if normalized in {PublicButtonTriggerAction.link.value, "url"}:
|
|
1317
|
+
return "link"
|
|
1318
|
+
if raw == PublicButtonTriggerAction.qrobot.value or normalized == "qrobot":
|
|
1319
|
+
return "qRobot"
|
|
1320
|
+
if normalized == PublicButtonTriggerAction.wings.value:
|
|
1321
|
+
return "wings"
|
|
1322
|
+
return raw
|
|
1323
|
+
|
|
1324
|
+
|
|
1325
|
+
def _drop_empty_public_payload_values(payload: dict[str, Any]) -> dict[str, Any]:
|
|
1326
|
+
compact: dict[str, Any] = {}
|
|
1327
|
+
for key, item in payload.items():
|
|
1328
|
+
if item is None or item == "" or item == [] or item == {}:
|
|
1329
|
+
continue
|
|
1330
|
+
if key == "primary" and item is False:
|
|
1331
|
+
continue
|
|
1332
|
+
compact[key] = item
|
|
1333
|
+
return compact
|
|
1334
|
+
|
|
1335
|
+
|
|
1336
|
+
def public_view_action_button_payload(button: Any, *, compact: bool = False) -> dict[str, Any]:
|
|
1337
|
+
if isinstance(button, ViewActionButtonPatch):
|
|
1338
|
+
payload = button.model_dump(mode="json")
|
|
1339
|
+
elif isinstance(button, dict):
|
|
1340
|
+
payload = dict(button)
|
|
1341
|
+
else:
|
|
1342
|
+
return {}
|
|
1343
|
+
raw_action = payload.get("action", payload.get("trigger_action", payload.get("triggerAction")))
|
|
1344
|
+
if raw_action is not None:
|
|
1345
|
+
payload["action"] = public_button_trigger_action_value(raw_action)
|
|
1346
|
+
payload.pop("trigger_action", None)
|
|
1347
|
+
payload.pop("triggerAction", None)
|
|
1348
|
+
if compact:
|
|
1349
|
+
return _drop_empty_public_payload_values(payload)
|
|
1350
|
+
return payload
|
|
1351
|
+
|
|
1352
|
+
|
|
1353
|
+
def public_view_upsert_payload(view: ViewUpsertPatch) -> dict[str, Any]:
|
|
1354
|
+
payload = view.model_dump(mode="json")
|
|
1355
|
+
if view.action_buttons is not None:
|
|
1356
|
+
payload["action_buttons"] = [
|
|
1357
|
+
public_view_action_button_payload(button, compact=True)
|
|
1358
|
+
for button in view.action_buttons
|
|
1359
|
+
]
|
|
1360
|
+
return payload
|
|
1361
|
+
|
|
1362
|
+
|
|
1363
|
+
def public_view_partial_payload(patch: ViewPartialPatch) -> dict[str, Any]:
|
|
1364
|
+
payload = patch.model_dump(mode="json")
|
|
1365
|
+
patch_set = payload.get("set")
|
|
1366
|
+
if isinstance(patch_set, dict):
|
|
1367
|
+
raw_buttons = patch_set.get("action_buttons")
|
|
1368
|
+
if raw_buttons is None:
|
|
1369
|
+
raw_buttons = patch_set.get("actionButtons")
|
|
1370
|
+
if isinstance(raw_buttons, list):
|
|
1371
|
+
patch_set = dict(patch_set)
|
|
1372
|
+
patch_set.pop("actionButtons", None)
|
|
1373
|
+
patch_set["action_buttons"] = [
|
|
1374
|
+
public_view_action_button_payload(button, compact=True)
|
|
1375
|
+
for button in raw_buttons
|
|
1376
|
+
]
|
|
1377
|
+
payload["set"] = patch_set
|
|
1378
|
+
return payload
|
|
1379
|
+
|
|
1380
|
+
|
|
1214
1381
|
class CustomButtonJudgeValuePatch(StrictModel):
|
|
1215
1382
|
id: int | str | None = None
|
|
1216
1383
|
value: Any | None = None
|
|
@@ -2093,6 +2260,22 @@ class ChartApplyRequest(StrictModel):
|
|
|
2093
2260
|
return self
|
|
2094
2261
|
|
|
2095
2262
|
|
|
2263
|
+
class PortalInlineChartPatch(ChartUpsertPatch):
|
|
2264
|
+
app_key: str = Field(validation_alias=AliasChoices("app_key", "appKey"))
|
|
2265
|
+
|
|
2266
|
+
@model_validator(mode="before")
|
|
2267
|
+
@classmethod
|
|
2268
|
+
def normalize_inline_aliases(cls, value: Any) -> Any:
|
|
2269
|
+
if not isinstance(value, dict):
|
|
2270
|
+
return value
|
|
2271
|
+
payload = dict(value)
|
|
2272
|
+
if "chart_name" in payload and "name" not in payload:
|
|
2273
|
+
payload["name"] = payload.pop("chart_name")
|
|
2274
|
+
if "chartName" in payload and "name" not in payload:
|
|
2275
|
+
payload["name"] = payload.pop("chartName")
|
|
2276
|
+
return payload
|
|
2277
|
+
|
|
2278
|
+
|
|
2096
2279
|
class PortalComponentPositionPatch(StrictModel):
|
|
2097
2280
|
pc_x: int = Field(default=0, validation_alias=AliasChoices("pc_x", "pcX", "x"))
|
|
2098
2281
|
pc_y: int = Field(default=0, validation_alias=AliasChoices("pc_y", "pcY", "y"))
|
|
@@ -2177,6 +2360,7 @@ class PortalSectionPatch(StrictModel):
|
|
|
2177
2360
|
dash_style_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("dash_style_config", "dashStyleConfigBO"))
|
|
2178
2361
|
config: dict[str, Any] = Field(default_factory=dict)
|
|
2179
2362
|
chart_ref: PortalChartRefPatch | None = None
|
|
2363
|
+
chart: PortalInlineChartPatch | None = Field(default=None, validation_alias=AliasChoices("chart", "inline_chart", "inlineChart", "create_chart", "createChart"))
|
|
2180
2364
|
view_ref: PortalViewRefPatch | None = None
|
|
2181
2365
|
text: str | None = None
|
|
2182
2366
|
url: str | None = None
|
|
@@ -2195,6 +2379,14 @@ class PortalSectionPatch(StrictModel):
|
|
|
2195
2379
|
payload["role"] = raw_role.strip().lower()
|
|
2196
2380
|
if "chartRef" in payload and "chart_ref" not in payload:
|
|
2197
2381
|
payload["chart_ref"] = payload.pop("chartRef")
|
|
2382
|
+
if "inlineChart" in payload and "chart" not in payload:
|
|
2383
|
+
payload["chart"] = payload.pop("inlineChart")
|
|
2384
|
+
if "inline_chart" in payload and "chart" not in payload:
|
|
2385
|
+
payload["chart"] = payload.pop("inline_chart")
|
|
2386
|
+
if "createChart" in payload and "chart" not in payload:
|
|
2387
|
+
payload["chart"] = payload.pop("createChart")
|
|
2388
|
+
if "create_chart" in payload and "chart" not in payload:
|
|
2389
|
+
payload["chart"] = payload.pop("create_chart")
|
|
2198
2390
|
if "viewRef" in payload and "view_ref" not in payload:
|
|
2199
2391
|
payload["view_ref"] = payload.pop("viewRef")
|
|
2200
2392
|
if "dashStyleConfigBO" in payload and "dash_style_config" not in payload:
|
|
@@ -2206,8 +2398,8 @@ class PortalSectionPatch(StrictModel):
|
|
|
2206
2398
|
supported = {"chart", "view", "grid", "filter", "text", "link"}
|
|
2207
2399
|
if self.source_type not in supported:
|
|
2208
2400
|
raise ValueError(f"unsupported portal source_type '{self.source_type}'")
|
|
2209
|
-
if self.source_type == "chart" and self.chart_ref is None:
|
|
2210
|
-
raise ValueError("chart section requires chart_ref")
|
|
2401
|
+
if self.source_type == "chart" and self.chart_ref is None and self.chart is None:
|
|
2402
|
+
raise ValueError("chart section requires chart_ref or chart")
|
|
2211
2403
|
if self.source_type == "view" and self.view_ref is None:
|
|
2212
2404
|
raise ValueError("view section requires view_ref")
|
|
2213
2405
|
if self.source_type == "text" and self.text is None:
|
|
@@ -2495,6 +2687,14 @@ def _normalize_field_payload(value: Any) -> Any:
|
|
|
2495
2687
|
if not isinstance(value, dict):
|
|
2496
2688
|
return value
|
|
2497
2689
|
payload = dict(value)
|
|
2690
|
+
if "data_title" in payload and "as_data_title" not in payload and "asDataTitle" not in payload:
|
|
2691
|
+
payload["as_data_title"] = payload.pop("data_title")
|
|
2692
|
+
if "dataTitle" in payload and "as_data_title" not in payload and "asDataTitle" not in payload:
|
|
2693
|
+
payload["as_data_title"] = payload.pop("dataTitle")
|
|
2694
|
+
if "data_cover" in payload and "as_data_cover" not in payload and "asDataCover" not in payload:
|
|
2695
|
+
payload["as_data_cover"] = payload.pop("data_cover")
|
|
2696
|
+
if "dataCover" in payload and "as_data_cover" not in payload and "asDataCover" not in payload:
|
|
2697
|
+
payload["as_data_cover"] = payload.pop("dataCover")
|
|
2498
2698
|
if "fields" in payload and "subfields" not in payload:
|
|
2499
2699
|
payload["subfields"] = payload.pop("fields")
|
|
2500
2700
|
if "options" in payload:
|