@josephyan/qingflow-app-builder-mcp 0.2.0-beta.37 → 0.2.0-beta.39

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.
@@ -4,12 +4,11 @@ Use this when the task is only about table, card, board, or gantt views.
4
4
 
5
5
  ## Minimal sequence
6
6
 
7
- 1. `builder_tool_contract(tool_name="app_views_plan")`
7
+ 1. `builder_tool_contract(tool_name="app_views_apply")`
8
8
  2. `app_read_fields`
9
9
  3. `app_read_views_summary`
10
- 4. `app_views_plan`
11
- 5. `app_views_apply`
12
- 6. `app_read_views_summary` again whenever apply returns `failed` or `partial_success`
10
+ 4. `app_views_apply`
11
+ 5. `app_read_views_summary` again whenever apply returns `failed` or `partial_success`
13
12
 
14
13
  If you are unsure about keys or view types, call `builder_tool_contract(tool_name="app_views_apply")` before guessing.
15
14
 
@@ -30,28 +29,7 @@ Canonical rules before any example:
30
29
  - For gantt, use `start_field`, `end_field`, and optionally `title_field`
31
30
  - If `app_read_views_summary` shows duplicate view names, include `view_key` in `upsert_views[]` and update that exact target
32
31
 
33
- Plan a default table view:
34
-
35
- ```json
36
- {
37
- "tool_name": "app_views_plan",
38
- "arguments": {
39
- "profile": "default",
40
- "app_key": "APP_123",
41
- "upsert_views": [
42
- {
43
- "name": "全部订单",
44
- "view_key": "VIEW_KEY_IF_DUPLICATE_NAMES_EXIST",
45
- "type": "table",
46
- "columns": ["订单编号", "客户名称", "订单金额", "状态"]
47
- }
48
- ],
49
- "remove_views": []
50
- }
51
- }
52
- ```
53
-
54
- Apply it:
32
+ Apply a default table view:
55
33
 
56
34
  ```json
57
35
  {
@@ -63,6 +41,7 @@ Apply it:
63
41
  "upsert_views": [
64
42
  {
65
43
  "name": "全部订单",
44
+ "view_key": "VIEW_KEY_IF_DUPLICATE_NAMES_EXIST",
66
45
  "type": "table",
67
46
  "columns": ["订单编号", "客户名称", "订单金额", "状态"]
68
47
  }
@@ -71,8 +50,7 @@ Apply it:
71
50
  }
72
51
  }
73
52
  ```
74
-
75
- After `app_views_plan` succeeds, prefer reusing its `suggested_next_call.arguments` directly. Do not rewrite aliases back into non-canonical keys such as `column_names`.
53
+ After `app_views_apply` returns canonical arguments or blocking issues, prefer reusing its `suggested_next_call.arguments` directly. Do not rewrite aliases back into non-canonical keys such as `column_names`.
76
54
 
77
55
  Board example:
78
56
 
@@ -2,4 +2,4 @@ from __future__ import annotations
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.2.0b37"
5
+ __version__ = "0.2.0b39"
@@ -69,6 +69,14 @@ class PublicViewType(str, Enum):
69
69
  gantt = "gantt"
70
70
 
71
71
 
72
+ class PublicChartType(str, Enum):
73
+ target = "target"
74
+ pie = "pie"
75
+ bar = "bar"
76
+ line = "line"
77
+ table = "table"
78
+
79
+
72
80
  class LayoutApplyMode(str, Enum):
73
81
  merge = "merge"
74
82
  replace = "replace"
@@ -491,6 +499,261 @@ class ViewUpsertPatch(StrictModel):
491
499
  return self
492
500
 
493
501
 
502
+ class ChartFilterRulePatch(StrictModel):
503
+ field_name: str = Field(validation_alias=AliasChoices("field_name", "fieldName", "field", "name"))
504
+ operator: ViewFilterOperator = Field(validation_alias=AliasChoices("operator", "op"))
505
+ values: list[Any] = Field(default_factory=list)
506
+
507
+ @model_validator(mode="before")
508
+ @classmethod
509
+ def normalize_aliases(cls, value: Any) -> Any:
510
+ if not isinstance(value, dict):
511
+ return value
512
+ payload = dict(value)
513
+ if "value" in payload and "values" not in payload:
514
+ payload["values"] = [payload.pop("value")]
515
+ raw_operator = payload.get("operator", payload.get("op"))
516
+ if isinstance(raw_operator, str):
517
+ normalized = raw_operator.strip().lower()
518
+ operator_aliases = {
519
+ "equals": ViewFilterOperator.eq.value,
520
+ "equal": ViewFilterOperator.eq.value,
521
+ "=": ViewFilterOperator.eq.value,
522
+ "not_equals": ViewFilterOperator.neq.value,
523
+ "not_equal": ViewFilterOperator.neq.value,
524
+ "!=": ViewFilterOperator.neq.value,
525
+ ">=": ViewFilterOperator.gte.value,
526
+ "<=": ViewFilterOperator.lte.value,
527
+ "any_of": ViewFilterOperator.in_.value,
528
+ "one_of": ViewFilterOperator.in_.value,
529
+ "between_any": ViewFilterOperator.in_.value,
530
+ "empty": ViewFilterOperator.is_empty.value,
531
+ "is blank": ViewFilterOperator.is_empty.value,
532
+ "blank": ViewFilterOperator.is_empty.value,
533
+ "not_empty": ViewFilterOperator.not_empty.value,
534
+ "not blank": ViewFilterOperator.not_empty.value,
535
+ }
536
+ if normalized in operator_aliases:
537
+ payload["operator"] = operator_aliases[normalized]
538
+ elif "operator" not in payload:
539
+ payload["operator"] = normalized
540
+ payload.pop("op", None)
541
+ return payload
542
+
543
+ @model_validator(mode="after")
544
+ def validate_shape(self) -> "ChartFilterRulePatch":
545
+ if self.operator in {ViewFilterOperator.is_empty, ViewFilterOperator.not_empty}:
546
+ self.values = []
547
+ return self
548
+ if not self.values:
549
+ raise ValueError("chart filter rule requires values")
550
+ return self
551
+
552
+
553
+ class ChartUpsertPatch(StrictModel):
554
+ chart_id: str | None = None
555
+ name: str
556
+ chart_type: PublicChartType
557
+ dimension_field_ids: list[str] = Field(default_factory=list)
558
+ indicator_field_ids: list[str] = Field(default_factory=list)
559
+ filters: list[ChartFilterRulePatch] = Field(default_factory=list)
560
+ question_config: list[dict[str, Any]] = Field(default_factory=list)
561
+ user_config: list[dict[str, Any]] = Field(default_factory=list)
562
+ config: dict[str, Any] = Field(default_factory=dict)
563
+
564
+ @model_validator(mode="before")
565
+ @classmethod
566
+ def normalize_aliases(cls, value: Any) -> Any:
567
+ if not isinstance(value, dict):
568
+ return value
569
+ payload = dict(value)
570
+ if "id" in payload and "chart_id" not in payload:
571
+ payload["chart_id"] = payload.pop("id")
572
+ if "type" in payload and "chart_type" not in payload:
573
+ payload["chart_type"] = payload.pop("type")
574
+ if "dimension_fields" in payload and "dimension_field_ids" not in payload:
575
+ payload["dimension_field_ids"] = payload.pop("dimension_fields")
576
+ if "indicator_fields" in payload and "indicator_field_ids" not in payload:
577
+ payload["indicator_field_ids"] = payload.pop("indicator_fields")
578
+ if "metric_field_ids" in payload and "indicator_field_ids" not in payload:
579
+ payload["indicator_field_ids"] = payload.pop("metric_field_ids")
580
+ raw_type = payload.get("chart_type")
581
+ if isinstance(raw_type, str):
582
+ normalized = raw_type.strip().lower()
583
+ aliases = {
584
+ "targetchart": PublicChartType.target.value,
585
+ "piechart": PublicChartType.pie.value,
586
+ "barchart": PublicChartType.bar.value,
587
+ "linechart": PublicChartType.line.value,
588
+ "tablechart": PublicChartType.table.value,
589
+ }
590
+ if normalized in aliases:
591
+ payload["chart_type"] = aliases[normalized]
592
+ if isinstance(payload.get("chart_id"), int):
593
+ payload["chart_id"] = str(payload["chart_id"])
594
+ if isinstance(payload.get("dimension_field_ids"), list):
595
+ payload["dimension_field_ids"] = [str(item) for item in payload["dimension_field_ids"] if item is not None and str(item).strip()]
596
+ if isinstance(payload.get("indicator_field_ids"), list):
597
+ payload["indicator_field_ids"] = [str(item) for item in payload["indicator_field_ids"] if item is not None and str(item).strip()]
598
+ return payload
599
+
600
+
601
+ class ChartApplyRequest(StrictModel):
602
+ app_key: str
603
+ upsert_charts: list[ChartUpsertPatch] = Field(default_factory=list)
604
+ remove_chart_ids: list[str] = Field(default_factory=list)
605
+ reorder_chart_ids: list[str] = Field(default_factory=list)
606
+
607
+ @model_validator(mode="before")
608
+ @classmethod
609
+ def normalize_ids(cls, value: Any) -> Any:
610
+ if not isinstance(value, dict):
611
+ return value
612
+ payload = dict(value)
613
+ for key in ("remove_chart_ids", "reorder_chart_ids"):
614
+ raw = payload.get(key)
615
+ if isinstance(raw, list):
616
+ payload[key] = [str(item) for item in raw if item is not None and str(item).strip()]
617
+ return payload
618
+
619
+ @model_validator(mode="after")
620
+ def validate_shape(self) -> "ChartApplyRequest":
621
+ if not self.upsert_charts and not self.remove_chart_ids and not self.reorder_chart_ids:
622
+ raise ValueError("chart apply requires at least one upsert, remove, or reorder operation")
623
+ return self
624
+
625
+
626
+ class PortalComponentPositionPatch(StrictModel):
627
+ pc_x: int = Field(default=0, validation_alias=AliasChoices("pc_x", "pcX", "x"))
628
+ pc_y: int = Field(default=0, validation_alias=AliasChoices("pc_y", "pcY", "y"))
629
+ pc_w: int = Field(default=12, validation_alias=AliasChoices("pc_w", "pcW", "w"))
630
+ pc_h: int = Field(default=8, validation_alias=AliasChoices("pc_h", "pcH", "h"))
631
+ mobile_x: int = Field(default=0, validation_alias=AliasChoices("mobile_x", "mobileX"))
632
+ mobile_y: int = Field(default=0, validation_alias=AliasChoices("mobile_y", "mobileY"))
633
+ mobile_w: int = Field(default=12, validation_alias=AliasChoices("mobile_w", "mobileW"))
634
+ mobile_h: int = Field(default=8, validation_alias=AliasChoices("mobile_h", "mobileH"))
635
+
636
+ @model_validator(mode="before")
637
+ @classmethod
638
+ def normalize_nested_layout(cls, value: Any) -> Any:
639
+ if not isinstance(value, dict):
640
+ return value
641
+ payload = dict(value)
642
+ pc = payload.pop("pc", None)
643
+ mobile = payload.pop("mobile", None)
644
+ if isinstance(pc, dict):
645
+ if "pc_x" not in payload and "x" in pc:
646
+ payload["pc_x"] = pc.get("x")
647
+ if "pc_y" not in payload and "y" in pc:
648
+ payload["pc_y"] = pc.get("y")
649
+ if "pc_w" not in payload and "cols" in pc:
650
+ payload["pc_w"] = pc.get("cols")
651
+ if "pc_h" not in payload and "rows" in pc:
652
+ payload["pc_h"] = pc.get("rows")
653
+ if isinstance(mobile, dict):
654
+ if "mobile_x" not in payload and "x" in mobile:
655
+ payload["mobile_x"] = mobile.get("x")
656
+ if "mobile_y" not in payload and "y" in mobile:
657
+ payload["mobile_y"] = mobile.get("y")
658
+ if "mobile_w" not in payload and "cols" in mobile:
659
+ payload["mobile_w"] = mobile.get("cols")
660
+ if "mobile_h" not in payload and "rows" in mobile:
661
+ payload["mobile_h"] = mobile.get("rows")
662
+ return payload
663
+
664
+
665
+ class PortalChartRefPatch(StrictModel):
666
+ app_key: str
667
+ chart_id: str | None = None
668
+ chart_name: str | None = None
669
+
670
+ @model_validator(mode="after")
671
+ def validate_target(self) -> "PortalChartRefPatch":
672
+ if not (self.chart_id or self.chart_name):
673
+ raise ValueError("chart_ref requires chart_id or chart_name")
674
+ return self
675
+
676
+
677
+ class PortalViewRefPatch(StrictModel):
678
+ app_key: str
679
+ view_key: str | None = None
680
+ view_name: str | None = None
681
+
682
+ @model_validator(mode="after")
683
+ def validate_target(self) -> "PortalViewRefPatch":
684
+ if not (self.view_key or self.view_name):
685
+ raise ValueError("view_ref requires view_key or view_name")
686
+ return self
687
+
688
+
689
+ class PortalSectionPatch(StrictModel):
690
+ title: str
691
+ source_type: str = Field(validation_alias=AliasChoices("source_type", "sourceType"))
692
+ position: PortalComponentPositionPatch | None = None
693
+ dash_style_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("dash_style_config", "dashStyleConfigBO"))
694
+ config: dict[str, Any] = Field(default_factory=dict)
695
+ chart_ref: PortalChartRefPatch | None = None
696
+ view_ref: PortalViewRefPatch | None = None
697
+ text: str | None = None
698
+ url: str | None = None
699
+
700
+ @model_validator(mode="before")
701
+ @classmethod
702
+ def normalize_aliases(cls, value: Any) -> Any:
703
+ if not isinstance(value, dict):
704
+ return value
705
+ payload = dict(value)
706
+ raw_type = payload.get("source_type", payload.get("sourceType"))
707
+ if isinstance(raw_type, str):
708
+ payload["source_type"] = raw_type.strip().lower()
709
+ if "chartRef" in payload and "chart_ref" not in payload:
710
+ payload["chart_ref"] = payload.pop("chartRef")
711
+ if "viewRef" in payload and "view_ref" not in payload:
712
+ payload["view_ref"] = payload.pop("viewRef")
713
+ if "dashStyleConfigBO" in payload and "dash_style_config" not in payload:
714
+ payload["dash_style_config"] = payload.pop("dashStyleConfigBO")
715
+ return payload
716
+
717
+ @model_validator(mode="after")
718
+ def validate_shape(self) -> "PortalSectionPatch":
719
+ supported = {"chart", "view", "grid", "filter", "text", "link"}
720
+ if self.source_type not in supported:
721
+ raise ValueError(f"unsupported portal source_type '{self.source_type}'")
722
+ if self.source_type == "chart" and self.chart_ref is None:
723
+ raise ValueError("chart section requires chart_ref")
724
+ if self.source_type == "view" and self.view_ref is None:
725
+ raise ValueError("view section requires view_ref")
726
+ if self.source_type == "text" and self.text is None:
727
+ raise ValueError("text section requires text")
728
+ if self.source_type == "link" and self.url is None:
729
+ raise ValueError("link section requires url")
730
+ return self
731
+
732
+
733
+ class PortalApplyRequest(StrictModel):
734
+ dash_key: str | None = None
735
+ dash_name: str | None = None
736
+ package_tag_id: int | None = None
737
+ publish: bool = True
738
+ sections: list[PortalSectionPatch] = Field(default_factory=list)
739
+ auth: dict[str, Any] | None = None
740
+ icon: str | None = None
741
+ color: str | None = None
742
+ hide_copyright: bool | None = Field(default=None, validation_alias=AliasChoices("hide_copyright", "hideCopyright"))
743
+ dash_global_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("dash_global_config", "dashGlobalConfig"))
744
+ config: dict[str, Any] = Field(default_factory=dict)
745
+
746
+ @model_validator(mode="after")
747
+ def validate_shape(self) -> "PortalApplyRequest":
748
+ if not self.dash_key and not self.package_tag_id:
749
+ raise ValueError("package_tag_id is required when dash_key is empty")
750
+ if not self.dash_key and not self.dash_name:
751
+ raise ValueError("dash_name is required when creating a portal")
752
+ if not self.sections:
753
+ raise ValueError("portal apply requires a non-empty sections list")
754
+ return self
755
+
756
+
494
757
  FieldPatch.model_rebuild()
495
758
 
496
759