@josephyan/qingflow-app-builder-mcp 0.2.0-beta.7 → 0.2.0-beta.71
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 +5 -3
- package/docs/local-agent-install.md +21 -5
- package/npm/bin/qingflow-app-builder-mcp.mjs +1 -1
- package/npm/lib/runtime.mjs +168 -12
- package/package.json +1 -1
- package/pyproject.toml +4 -1
- package/skills/qingflow-app-builder/SKILL.md +155 -22
- package/skills/qingflow-app-builder/references/create-app.md +51 -21
- package/skills/qingflow-app-builder/references/environments.md +1 -1
- package/skills/qingflow-app-builder/references/flow-actors-and-permissions.md +123 -0
- package/skills/qingflow-app-builder/references/gotchas.md +28 -1
- package/skills/qingflow-app-builder/references/solution-playbooks.md +14 -12
- package/skills/qingflow-app-builder/references/tool-selection.md +47 -19
- package/skills/qingflow-app-builder/references/update-flow.md +112 -25
- package/skills/qingflow-app-builder/references/update-layout.md +11 -24
- package/skills/qingflow-app-builder/references/update-schema.md +1 -23
- package/skills/qingflow-app-builder/references/update-views.md +87 -21
- package/skills/qingflow-app-builder-code-integrations/SKILL.md +137 -0
- package/skills/qingflow-app-builder-code-integrations/agents/openai.yaml +4 -0
- package/skills/qingflow-app-builder-code-integrations/references/code-block.md +66 -0
- package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +77 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/backend_client.py +210 -0
- package/src/qingflow_mcp/builder_facade/models.py +1252 -3
- package/src/qingflow_mcp/builder_facade/service.py +11367 -2389
- package/src/qingflow_mcp/cli/__init__.py +1 -0
- package/src/qingflow_mcp/cli/commands/__init__.py +15 -0
- package/src/qingflow_mcp/cli/commands/app.py +40 -0
- package/src/qingflow_mcp/cli/commands/auth.py +78 -0
- package/src/qingflow_mcp/cli/commands/builder.py +515 -0
- package/src/qingflow_mcp/cli/commands/common.py +62 -0
- package/src/qingflow_mcp/cli/commands/imports.py +96 -0
- package/src/qingflow_mcp/cli/commands/record.py +304 -0
- package/src/qingflow_mcp/cli/commands/task.py +89 -0
- package/src/qingflow_mcp/cli/commands/workspace.py +33 -0
- package/src/qingflow_mcp/cli/context.py +48 -0
- package/src/qingflow_mcp/cli/formatters.py +355 -0
- package/src/qingflow_mcp/cli/json_io.py +50 -0
- package/src/qingflow_mcp/cli/main.py +149 -0
- package/src/qingflow_mcp/config.py +39 -0
- package/src/qingflow_mcp/import_store.py +121 -0
- package/src/qingflow_mcp/list_type_labels.py +24 -0
- package/src/qingflow_mcp/response_trim.py +668 -0
- package/src/qingflow_mcp/server.py +160 -18
- package/src/qingflow_mcp/server_app_builder.py +275 -68
- package/src/qingflow_mcp/server_app_user.py +219 -191
- package/src/qingflow_mcp/session_store.py +41 -1
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +43 -4
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +119 -45
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +41 -2
- package/src/qingflow_mcp/solution/executor.py +107 -11
- package/src/qingflow_mcp/solution/spec_models.py +2 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +2032 -127
- package/src/qingflow_mcp/tools/app_tools.py +419 -12
- package/src/qingflow_mcp/tools/approval_tools.py +571 -72
- package/src/qingflow_mcp/tools/auth_tools.py +398 -2
- package/src/qingflow_mcp/tools/code_block_tools.py +756 -0
- package/src/qingflow_mcp/tools/custom_button_tools.py +179 -0
- package/src/qingflow_mcp/tools/directory_tools.py +203 -31
- package/src/qingflow_mcp/tools/feedback_tools.py +230 -0
- package/src/qingflow_mcp/tools/file_tools.py +1 -0
- package/src/qingflow_mcp/tools/import_tools.py +2150 -0
- package/src/qingflow_mcp/tools/package_tools.py +18 -4
- package/src/qingflow_mcp/tools/portal_tools.py +31 -0
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +109 -7
- package/src/qingflow_mcp/tools/record_tools.py +9894 -1104
- package/src/qingflow_mcp/tools/solution_tools.py +115 -3
- package/src/qingflow_mcp/tools/task_context_tools.py +2040 -0
- package/src/qingflow_mcp/tools/task_tools.py +376 -225
- package/src/qingflow_mcp/tools/workspace_tools.py +163 -19
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from copy import deepcopy
|
|
3
4
|
import json
|
|
5
|
+
import time
|
|
4
6
|
|
|
5
7
|
from pydantic import ValidationError
|
|
6
8
|
|
|
@@ -8,36 +10,61 @@ from ..config import DEFAULT_PROFILE
|
|
|
8
10
|
from ..errors import QingflowApiError
|
|
9
11
|
from ..json_types import JSONObject
|
|
10
12
|
from ..builder_facade.models import (
|
|
13
|
+
ChartApplyRequest,
|
|
14
|
+
CustomButtonPatch,
|
|
15
|
+
FIELD_TYPE_ID_ALIASES,
|
|
11
16
|
FieldPatch,
|
|
12
17
|
FieldRemovePatch,
|
|
13
18
|
FieldUpdatePatch,
|
|
19
|
+
FlowPreset,
|
|
14
20
|
FlowNodePatch,
|
|
15
21
|
FlowPlanRequest,
|
|
16
22
|
FlowTransitionPatch,
|
|
17
23
|
LayoutApplyMode,
|
|
18
24
|
LayoutPlanRequest,
|
|
25
|
+
LayoutPreset,
|
|
19
26
|
LayoutSectionPatch,
|
|
27
|
+
PortalApplyRequest,
|
|
28
|
+
PublicButtonTriggerAction,
|
|
29
|
+
PublicFieldType,
|
|
30
|
+
PublicRelationMode,
|
|
31
|
+
PublicChartType,
|
|
32
|
+
PublicViewType,
|
|
20
33
|
SchemaPlanRequest,
|
|
34
|
+
ViewFilterOperator,
|
|
21
35
|
ViewUpsertPatch,
|
|
36
|
+
ViewsPreset,
|
|
22
37
|
ViewsPlanRequest,
|
|
23
38
|
)
|
|
24
|
-
from ..builder_facade.service import AiBuilderFacade
|
|
39
|
+
from ..builder_facade.service import AiBuilderFacade, INTEGRATION_OUTPUT_TARGET_FIELD_TYPES
|
|
25
40
|
from .app_tools import AppTools
|
|
26
41
|
from .base import ToolBase
|
|
42
|
+
from .custom_button_tools import CustomButtonTools
|
|
43
|
+
from .directory_tools import DirectoryTools
|
|
27
44
|
from .package_tools import PackageTools
|
|
45
|
+
from .portal_tools import PortalTools
|
|
46
|
+
from .qingbi_report_tools import QingbiReportTools
|
|
47
|
+
from .role_tools import RoleTools
|
|
28
48
|
from .solution_tools import SolutionTools
|
|
29
49
|
from .view_tools import ViewTools
|
|
30
50
|
from .workflow_tools import WorkflowTools
|
|
31
51
|
|
|
52
|
+
PUBLIC_STABLE_FLOW_NODE_TYPES = ["start", "approve", "fill", "copy", "webhook", "end"]
|
|
53
|
+
|
|
32
54
|
|
|
33
55
|
class AiBuilderTools(ToolBase):
|
|
34
56
|
def __init__(self, sessions, backend) -> None:
|
|
35
57
|
super().__init__(sessions, backend)
|
|
36
58
|
self._facade = AiBuilderFacade(
|
|
37
59
|
apps=AppTools(sessions, backend),
|
|
60
|
+
buttons=CustomButtonTools(sessions, backend),
|
|
38
61
|
packages=PackageTools(sessions, backend),
|
|
39
62
|
views=ViewTools(sessions, backend),
|
|
40
63
|
workflows=WorkflowTools(sessions, backend),
|
|
64
|
+
portals=PortalTools(sessions, backend),
|
|
65
|
+
charts=QingbiReportTools(sessions, backend),
|
|
66
|
+
roles=RoleTools(sessions, backend),
|
|
67
|
+
directory=DirectoryTools(sessions, backend),
|
|
41
68
|
solutions=SolutionTools(sessions, backend),
|
|
42
69
|
)
|
|
43
70
|
|
|
@@ -50,14 +77,69 @@ class AiBuilderTools(ToolBase):
|
|
|
50
77
|
def package_resolve(profile: str = DEFAULT_PROFILE, package_name: str = "") -> JSONObject:
|
|
51
78
|
return self.package_resolve(profile=profile, package_name=package_name)
|
|
52
79
|
|
|
80
|
+
@mcp.tool()
|
|
81
|
+
def builder_tool_contract(tool_name: str = "") -> JSONObject:
|
|
82
|
+
return self.builder_tool_contract(tool_name=tool_name)
|
|
83
|
+
|
|
84
|
+
@mcp.tool()
|
|
85
|
+
def package_create(
|
|
86
|
+
profile: str = DEFAULT_PROFILE,
|
|
87
|
+
package_name: str = "",
|
|
88
|
+
icon: str | None = None,
|
|
89
|
+
color: str | None = None,
|
|
90
|
+
) -> JSONObject:
|
|
91
|
+
return self.package_create(profile=profile, package_name=package_name, icon=icon, color=color)
|
|
92
|
+
|
|
93
|
+
@mcp.tool()
|
|
94
|
+
def member_search(
|
|
95
|
+
profile: str = DEFAULT_PROFILE,
|
|
96
|
+
query: str = "",
|
|
97
|
+
page_num: int = 1,
|
|
98
|
+
page_size: int = 20,
|
|
99
|
+
contain_disable: bool = False,
|
|
100
|
+
) -> JSONObject:
|
|
101
|
+
return self.member_search(
|
|
102
|
+
profile=profile,
|
|
103
|
+
query=query,
|
|
104
|
+
page_num=page_num,
|
|
105
|
+
page_size=page_size,
|
|
106
|
+
contain_disable=contain_disable,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
@mcp.tool()
|
|
110
|
+
def role_search(
|
|
111
|
+
profile: str = DEFAULT_PROFILE,
|
|
112
|
+
keyword: str = "",
|
|
113
|
+
page_num: int = 1,
|
|
114
|
+
page_size: int = 20,
|
|
115
|
+
) -> JSONObject:
|
|
116
|
+
return self.role_search(profile=profile, keyword=keyword, page_num=page_num, page_size=page_size)
|
|
117
|
+
|
|
118
|
+
@mcp.tool()
|
|
119
|
+
def role_create(
|
|
120
|
+
profile: str = DEFAULT_PROFILE,
|
|
121
|
+
role_name: str = "",
|
|
122
|
+
member_uids: list[int] | None = None,
|
|
123
|
+
member_emails: list[str] | None = None,
|
|
124
|
+
member_names: list[str] | None = None,
|
|
125
|
+
role_icon: str = "ex-user-outlined",
|
|
126
|
+
) -> JSONObject:
|
|
127
|
+
return self.role_create(
|
|
128
|
+
profile=profile,
|
|
129
|
+
role_name=role_name,
|
|
130
|
+
member_uids=member_uids or [],
|
|
131
|
+
member_emails=member_emails or [],
|
|
132
|
+
member_names=member_names or [],
|
|
133
|
+
role_icon=role_icon,
|
|
134
|
+
)
|
|
135
|
+
|
|
53
136
|
@mcp.tool()
|
|
54
137
|
def package_attach_app(
|
|
55
138
|
profile: str = DEFAULT_PROFILE,
|
|
56
139
|
tag_id: int = 0,
|
|
57
140
|
app_key: str = "",
|
|
58
|
-
app_title: str = "",
|
|
59
141
|
) -> JSONObject:
|
|
60
|
-
return self.package_attach_app(profile=profile, tag_id=tag_id, app_key=app_key
|
|
142
|
+
return self.package_attach_app(profile=profile, tag_id=tag_id, app_key=app_key)
|
|
61
143
|
|
|
62
144
|
@mcp.tool()
|
|
63
145
|
def app_release_edit_lock_if_mine(
|
|
@@ -80,8 +162,52 @@ class AiBuilderTools(ToolBase):
|
|
|
80
162
|
app_name: str = "",
|
|
81
163
|
package_tag_id: int | None = None,
|
|
82
164
|
) -> JSONObject:
|
|
165
|
+
has_app_key = bool((app_key or "").strip())
|
|
166
|
+
has_app_name = bool((app_name or "").strip())
|
|
167
|
+
has_package_tag_id = package_tag_id is not None
|
|
168
|
+
if has_app_key and (has_app_name or has_package_tag_id):
|
|
169
|
+
return _config_failure(
|
|
170
|
+
tool_name="app_resolve",
|
|
171
|
+
message="app_resolve accepts exactly one selector mode.",
|
|
172
|
+
fix_hint="Use only `app_key`, or use `app_name` together with `package_tag_id`.",
|
|
173
|
+
)
|
|
174
|
+
if not has_app_key and not (has_app_name and has_package_tag_id):
|
|
175
|
+
return _config_failure(
|
|
176
|
+
tool_name="app_resolve",
|
|
177
|
+
message="app_resolve requires either app_key, or app_name together with package_tag_id.",
|
|
178
|
+
fix_hint="For an existing known app, pass `app_key`. For package-scoped lookup, pass both `app_name` and `package_tag_id`.",
|
|
179
|
+
)
|
|
83
180
|
return self.app_resolve(profile=profile, app_key=app_key, app_name=app_name, package_tag_id=package_tag_id)
|
|
84
181
|
|
|
182
|
+
@mcp.tool()
|
|
183
|
+
def app_custom_button_list(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
184
|
+
return self.app_custom_button_list(profile=profile, app_key=app_key)
|
|
185
|
+
|
|
186
|
+
@mcp.tool()
|
|
187
|
+
def app_custom_button_get(profile: str = DEFAULT_PROFILE, app_key: str = "", button_id: int = 0) -> JSONObject:
|
|
188
|
+
return self.app_custom_button_get(profile=profile, app_key=app_key, button_id=button_id)
|
|
189
|
+
|
|
190
|
+
@mcp.tool()
|
|
191
|
+
def app_custom_button_create(
|
|
192
|
+
profile: str = DEFAULT_PROFILE,
|
|
193
|
+
app_key: str = "",
|
|
194
|
+
payload: JSONObject | None = None,
|
|
195
|
+
) -> JSONObject:
|
|
196
|
+
return self.app_custom_button_create(profile=profile, app_key=app_key, payload=payload or {})
|
|
197
|
+
|
|
198
|
+
@mcp.tool()
|
|
199
|
+
def app_custom_button_update(
|
|
200
|
+
profile: str = DEFAULT_PROFILE,
|
|
201
|
+
app_key: str = "",
|
|
202
|
+
button_id: int = 0,
|
|
203
|
+
payload: JSONObject | None = None,
|
|
204
|
+
) -> JSONObject:
|
|
205
|
+
return self.app_custom_button_update(profile=profile, app_key=app_key, button_id=button_id, payload=payload or {})
|
|
206
|
+
|
|
207
|
+
@mcp.tool()
|
|
208
|
+
def app_custom_button_delete(profile: str = DEFAULT_PROFILE, app_key: str = "", button_id: int = 0) -> JSONObject:
|
|
209
|
+
return self.app_custom_button_delete(profile=profile, app_key=app_key, button_id=button_id)
|
|
210
|
+
|
|
85
211
|
@mcp.tool()
|
|
86
212
|
def app_read_summary(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
87
213
|
return self.app_read_summary(profile=profile, app_key=app_key)
|
|
@@ -103,75 +229,51 @@ class AiBuilderTools(ToolBase):
|
|
|
103
229
|
return self.app_read_flow_summary(profile=profile, app_key=app_key)
|
|
104
230
|
|
|
105
231
|
@mcp.tool()
|
|
106
|
-
def
|
|
107
|
-
profile
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
add_fields: list[JSONObject] | None = None,
|
|
113
|
-
update_fields: list[JSONObject] | None = None,
|
|
114
|
-
remove_fields: list[JSONObject] | None = None,
|
|
115
|
-
) -> JSONObject:
|
|
116
|
-
return self.app_schema_plan(
|
|
117
|
-
profile=profile,
|
|
118
|
-
app_key=app_key,
|
|
119
|
-
package_tag_id=package_tag_id,
|
|
120
|
-
app_name=app_name,
|
|
121
|
-
create_if_missing=create_if_missing,
|
|
122
|
-
add_fields=add_fields or [],
|
|
123
|
-
update_fields=update_fields or [],
|
|
124
|
-
remove_fields=remove_fields or [],
|
|
125
|
-
)
|
|
232
|
+
def app_read_charts_summary(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
233
|
+
return self.app_read_charts_summary(profile=profile, app_key=app_key)
|
|
234
|
+
|
|
235
|
+
@mcp.tool()
|
|
236
|
+
def portal_list(profile: str = DEFAULT_PROFILE) -> JSONObject:
|
|
237
|
+
return self.portal_list(profile=profile)
|
|
126
238
|
|
|
127
239
|
@mcp.tool()
|
|
128
|
-
def
|
|
240
|
+
def portal_get(
|
|
129
241
|
profile: str = DEFAULT_PROFILE,
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
sections: list[JSONObject] | None = None,
|
|
133
|
-
preset: str | None = None,
|
|
242
|
+
dash_key: str = "",
|
|
243
|
+
being_draft: bool = True,
|
|
134
244
|
) -> JSONObject:
|
|
135
|
-
return self.
|
|
136
|
-
profile=profile,
|
|
137
|
-
app_key=app_key,
|
|
138
|
-
mode=mode,
|
|
139
|
-
sections=sections or [],
|
|
140
|
-
preset=preset,
|
|
141
|
-
)
|
|
245
|
+
return self.portal_get(profile=profile, dash_key=dash_key, being_draft=being_draft)
|
|
142
246
|
|
|
143
247
|
@mcp.tool()
|
|
144
|
-
def
|
|
248
|
+
def portal_read_summary(
|
|
145
249
|
profile: str = DEFAULT_PROFILE,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
nodes: list[JSONObject] | None = None,
|
|
149
|
-
transitions: list[JSONObject] | None = None,
|
|
150
|
-
preset: str | None = None,
|
|
250
|
+
dash_key: str = "",
|
|
251
|
+
being_draft: bool = True,
|
|
151
252
|
) -> JSONObject:
|
|
152
|
-
return self.
|
|
153
|
-
profile=profile,
|
|
154
|
-
app_key=app_key,
|
|
155
|
-
mode=mode,
|
|
156
|
-
nodes=nodes or [],
|
|
157
|
-
transitions=transitions or [],
|
|
158
|
-
preset=preset,
|
|
159
|
-
)
|
|
253
|
+
return self.portal_read_summary(profile=profile, dash_key=dash_key, being_draft=being_draft)
|
|
160
254
|
|
|
161
255
|
@mcp.tool()
|
|
162
|
-
def
|
|
256
|
+
def view_get(profile: str = DEFAULT_PROFILE, viewgraph_key: str = "") -> JSONObject:
|
|
257
|
+
return self.view_get(profile=profile, viewgraph_key=viewgraph_key)
|
|
258
|
+
|
|
259
|
+
@mcp.tool()
|
|
260
|
+
def chart_get(
|
|
163
261
|
profile: str = DEFAULT_PROFILE,
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
262
|
+
chart_id: str = "",
|
|
263
|
+
data_payload: JSONObject | None = None,
|
|
264
|
+
page_num: int | None = None,
|
|
265
|
+
page_size: int | None = None,
|
|
266
|
+
page_num_y: int | None = None,
|
|
267
|
+
page_size_y: int | None = None,
|
|
168
268
|
) -> JSONObject:
|
|
169
|
-
return self.
|
|
269
|
+
return self.chart_get(
|
|
170
270
|
profile=profile,
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
271
|
+
chart_id=chart_id,
|
|
272
|
+
data_payload=data_payload or {},
|
|
273
|
+
page_num=page_num,
|
|
274
|
+
page_size=page_size,
|
|
275
|
+
page_num_y=page_num_y,
|
|
276
|
+
page_size_y=page_size_y,
|
|
175
277
|
)
|
|
176
278
|
|
|
177
279
|
@mcp.tool()
|
|
@@ -181,18 +283,39 @@ class AiBuilderTools(ToolBase):
|
|
|
181
283
|
package_tag_id: int | None = None,
|
|
182
284
|
app_name: str = "",
|
|
183
285
|
app_title: str = "",
|
|
286
|
+
icon: str = "",
|
|
287
|
+
color: str = "",
|
|
184
288
|
create_if_missing: bool = False,
|
|
185
289
|
publish: bool = True,
|
|
186
290
|
add_fields: list[JSONObject] | None = None,
|
|
187
291
|
update_fields: list[JSONObject] | None = None,
|
|
188
292
|
remove_fields: list[JSONObject] | None = None,
|
|
189
293
|
) -> JSONObject:
|
|
294
|
+
has_app_key = bool((app_key or "").strip())
|
|
295
|
+
has_app_name = bool((app_name or "").strip())
|
|
296
|
+
has_app_title = bool((app_title or "").strip())
|
|
297
|
+
has_package_tag_id = package_tag_id is not None
|
|
298
|
+
if has_app_key:
|
|
299
|
+
if create_if_missing or has_app_name or has_package_tag_id or has_app_title:
|
|
300
|
+
return _config_failure(
|
|
301
|
+
tool_name="app_schema_apply",
|
|
302
|
+
message="app_schema_apply edit mode only accepts app_key as the resource selector.",
|
|
303
|
+
fix_hint="For existing apps, pass `app_key` only. For create mode, use `package_tag_id + app_name + create_if_missing=true`.",
|
|
304
|
+
)
|
|
305
|
+
elif not (create_if_missing and has_package_tag_id and has_app_name):
|
|
306
|
+
return _config_failure(
|
|
307
|
+
tool_name="app_schema_apply",
|
|
308
|
+
message="app_schema_apply create mode requires package_tag_id, app_name, and create_if_missing=true.",
|
|
309
|
+
fix_hint="Use `app_key` for existing apps, or pass `package_tag_id + app_name + create_if_missing=true` to create a new app.",
|
|
310
|
+
)
|
|
190
311
|
return self.app_schema_apply(
|
|
191
312
|
profile=profile,
|
|
192
313
|
app_key=app_key,
|
|
193
314
|
package_tag_id=package_tag_id,
|
|
194
315
|
app_name=app_name,
|
|
195
316
|
app_title=app_title,
|
|
317
|
+
icon=icon,
|
|
318
|
+
color=color,
|
|
196
319
|
create_if_missing=create_if_missing,
|
|
197
320
|
publish=publish,
|
|
198
321
|
add_fields=add_fields or [],
|
|
@@ -244,6 +367,67 @@ class AiBuilderTools(ToolBase):
|
|
|
244
367
|
remove_views=remove_views or [],
|
|
245
368
|
)
|
|
246
369
|
|
|
370
|
+
@mcp.tool()
|
|
371
|
+
def app_charts_apply(
|
|
372
|
+
profile: str = DEFAULT_PROFILE,
|
|
373
|
+
app_key: str = "",
|
|
374
|
+
upsert_charts: list[JSONObject] | None = None,
|
|
375
|
+
remove_chart_ids: list[str] | None = None,
|
|
376
|
+
reorder_chart_ids: list[str] | None = None,
|
|
377
|
+
) -> JSONObject:
|
|
378
|
+
return self.app_charts_apply(
|
|
379
|
+
profile=profile,
|
|
380
|
+
app_key=app_key,
|
|
381
|
+
upsert_charts=upsert_charts or [],
|
|
382
|
+
remove_chart_ids=remove_chart_ids or [],
|
|
383
|
+
reorder_chart_ids=reorder_chart_ids or [],
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
@mcp.tool()
|
|
387
|
+
def portal_apply(
|
|
388
|
+
profile: str = DEFAULT_PROFILE,
|
|
389
|
+
dash_key: str = "",
|
|
390
|
+
dash_name: str = "",
|
|
391
|
+
package_tag_id: int | None = None,
|
|
392
|
+
publish: bool = True,
|
|
393
|
+
sections: list[JSONObject] | None = None,
|
|
394
|
+
auth: JSONObject | None = None,
|
|
395
|
+
icon: str | None = None,
|
|
396
|
+
color: str | None = None,
|
|
397
|
+
hide_copyright: bool | None = None,
|
|
398
|
+
dash_global_config: JSONObject | None = None,
|
|
399
|
+
config: JSONObject | None = None,
|
|
400
|
+
) -> JSONObject:
|
|
401
|
+
has_dash_key = bool((dash_key or "").strip())
|
|
402
|
+
has_dash_name = bool((dash_name or "").strip())
|
|
403
|
+
has_package_tag_id = package_tag_id is not None
|
|
404
|
+
if has_dash_key and has_package_tag_id:
|
|
405
|
+
return _config_failure(
|
|
406
|
+
tool_name="portal_apply",
|
|
407
|
+
message="portal_apply accepts exactly one selector mode.",
|
|
408
|
+
fix_hint="Use `dash_key` to update an existing portal, or use `package_tag_id + dash_name` to create a new portal.",
|
|
409
|
+
)
|
|
410
|
+
if not has_dash_key and not (has_package_tag_id and has_dash_name):
|
|
411
|
+
return _config_failure(
|
|
412
|
+
tool_name="portal_apply",
|
|
413
|
+
message="portal_apply requires either dash_key, or package_tag_id together with dash_name.",
|
|
414
|
+
fix_hint="Use `dash_key` for an existing portal. For create mode, pass `package_tag_id + dash_name`.",
|
|
415
|
+
)
|
|
416
|
+
return self.portal_apply(
|
|
417
|
+
profile=profile,
|
|
418
|
+
dash_key=dash_key,
|
|
419
|
+
dash_name=dash_name,
|
|
420
|
+
package_tag_id=package_tag_id,
|
|
421
|
+
publish=publish,
|
|
422
|
+
sections=sections or [],
|
|
423
|
+
auth=auth,
|
|
424
|
+
icon=icon,
|
|
425
|
+
color=color,
|
|
426
|
+
hide_copyright=hide_copyright,
|
|
427
|
+
dash_global_config=dash_global_config,
|
|
428
|
+
config=config or {},
|
|
429
|
+
)
|
|
430
|
+
|
|
247
431
|
@mcp.tool()
|
|
248
432
|
def app_publish_verify(
|
|
249
433
|
profile: str = DEFAULT_PROFILE,
|
|
@@ -274,14 +458,174 @@ class AiBuilderTools(ToolBase):
|
|
|
274
458
|
suggested_next_call={"tool_name": "package_resolve", "arguments": {"profile": profile, "package_name": package_name}},
|
|
275
459
|
)
|
|
276
460
|
|
|
461
|
+
def builder_tool_contract(self, *, tool_name: str) -> JSONObject:
|
|
462
|
+
requested = str(tool_name or "").strip()
|
|
463
|
+
public_tool_names = sorted(
|
|
464
|
+
name
|
|
465
|
+
for name in _BUILDER_TOOL_CONTRACTS.keys()
|
|
466
|
+
if name not in _PRIVATE_BUILDER_TOOL_CONTRACTS and name not in _BUILDER_TOOL_CONTRACT_ALIASES
|
|
467
|
+
)
|
|
468
|
+
if requested in _PRIVATE_BUILDER_TOOL_CONTRACTS:
|
|
469
|
+
lookup_name = ""
|
|
470
|
+
elif requested in _BUILDER_TOOL_CONTRACTS:
|
|
471
|
+
lookup_name = requested
|
|
472
|
+
else:
|
|
473
|
+
lookup_name = _BUILDER_TOOL_CONTRACT_ALIASES.get(requested, requested)
|
|
474
|
+
contract = _BUILDER_TOOL_CONTRACTS.get(lookup_name)
|
|
475
|
+
if contract is None:
|
|
476
|
+
return {
|
|
477
|
+
"status": "failed",
|
|
478
|
+
"error_code": "TOOL_CONTRACT_NOT_FOUND",
|
|
479
|
+
"recoverable": True,
|
|
480
|
+
"message": "tool contract is not defined for the requested public builder tool",
|
|
481
|
+
"normalized_args": {"tool_name": requested},
|
|
482
|
+
"missing_fields": [],
|
|
483
|
+
"allowed_values": {"tool_name": public_tool_names},
|
|
484
|
+
"details": {"reason_path": "tool_name"},
|
|
485
|
+
"suggested_next_call": None,
|
|
486
|
+
"request_id": None,
|
|
487
|
+
"backend_code": None,
|
|
488
|
+
"http_status": None,
|
|
489
|
+
"noop": False,
|
|
490
|
+
"warnings": [],
|
|
491
|
+
"verification": {},
|
|
492
|
+
"verified": False,
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
"status": "success",
|
|
496
|
+
"error_code": None,
|
|
497
|
+
"recoverable": False,
|
|
498
|
+
"message": "loaded builder tool contract",
|
|
499
|
+
"normalized_args": {"tool_name": requested},
|
|
500
|
+
"missing_fields": [],
|
|
501
|
+
"allowed_values": {},
|
|
502
|
+
"details": {},
|
|
503
|
+
"suggested_next_call": None,
|
|
504
|
+
"request_id": None,
|
|
505
|
+
"backend_code": None,
|
|
506
|
+
"http_status": None,
|
|
507
|
+
"noop": False,
|
|
508
|
+
"warnings": [],
|
|
509
|
+
"verification": {},
|
|
510
|
+
"verified": True,
|
|
511
|
+
"tool_name": requested,
|
|
512
|
+
"contract": contract,
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
def package_create(
|
|
516
|
+
self,
|
|
517
|
+
*,
|
|
518
|
+
profile: str,
|
|
519
|
+
package_name: str,
|
|
520
|
+
icon: str | None = None,
|
|
521
|
+
color: str | None = None,
|
|
522
|
+
) -> JSONObject:
|
|
523
|
+
normalized_args = {
|
|
524
|
+
"package_name": package_name,
|
|
525
|
+
**({"icon": icon} if icon else {}),
|
|
526
|
+
**({"color": color} if color else {}),
|
|
527
|
+
}
|
|
528
|
+
return _safe_tool_call(
|
|
529
|
+
lambda: self._facade.package_create(profile=profile, package_name=package_name, icon=icon, color=color),
|
|
530
|
+
error_code="PACKAGE_CREATE_FAILED",
|
|
531
|
+
normalized_args=normalized_args,
|
|
532
|
+
suggested_next_call={
|
|
533
|
+
"tool_name": "package_create",
|
|
534
|
+
"arguments": {
|
|
535
|
+
"profile": profile,
|
|
536
|
+
"package_name": package_name,
|
|
537
|
+
**({"icon": icon} if icon else {}),
|
|
538
|
+
**({"color": color} if color else {}),
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
def member_search(
|
|
544
|
+
self,
|
|
545
|
+
*,
|
|
546
|
+
profile: str,
|
|
547
|
+
query: str,
|
|
548
|
+
page_num: int = 1,
|
|
549
|
+
page_size: int = 20,
|
|
550
|
+
contain_disable: bool = False,
|
|
551
|
+
) -> JSONObject:
|
|
552
|
+
normalized_args = {
|
|
553
|
+
"query": query,
|
|
554
|
+
"page_num": page_num,
|
|
555
|
+
"page_size": page_size,
|
|
556
|
+
"contain_disable": contain_disable,
|
|
557
|
+
}
|
|
558
|
+
return _safe_tool_call(
|
|
559
|
+
lambda: self._facade.member_search(
|
|
560
|
+
profile=profile,
|
|
561
|
+
query=query,
|
|
562
|
+
page_num=page_num,
|
|
563
|
+
page_size=page_size,
|
|
564
|
+
contain_disable=contain_disable,
|
|
565
|
+
),
|
|
566
|
+
error_code="MEMBER_SEARCH_FAILED",
|
|
567
|
+
normalized_args=normalized_args,
|
|
568
|
+
suggested_next_call={"tool_name": "member_search", "arguments": {"profile": profile, **normalized_args}},
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
def role_search(self, *, profile: str, keyword: str, page_num: int = 1, page_size: int = 20) -> JSONObject:
|
|
572
|
+
normalized_args = {"keyword": keyword, "page_num": page_num, "page_size": page_size}
|
|
573
|
+
return _safe_tool_call(
|
|
574
|
+
lambda: self._facade.role_search(profile=profile, keyword=keyword, page_num=page_num, page_size=page_size),
|
|
575
|
+
error_code="ROLE_SEARCH_FAILED",
|
|
576
|
+
normalized_args=normalized_args,
|
|
577
|
+
suggested_next_call={"tool_name": "role_search", "arguments": {"profile": profile, **normalized_args}},
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
def role_create(
|
|
581
|
+
self,
|
|
582
|
+
*,
|
|
583
|
+
profile: str,
|
|
584
|
+
role_name: str,
|
|
585
|
+
member_uids: list[int],
|
|
586
|
+
member_emails: list[str],
|
|
587
|
+
member_names: list[str],
|
|
588
|
+
role_icon: str = "ex-user-outlined",
|
|
589
|
+
) -> JSONObject:
|
|
590
|
+
normalized_args = {
|
|
591
|
+
"role_name": role_name,
|
|
592
|
+
"member_uids": member_uids,
|
|
593
|
+
"member_emails": member_emails,
|
|
594
|
+
"member_names": member_names,
|
|
595
|
+
"role_icon": role_icon,
|
|
596
|
+
}
|
|
597
|
+
return _safe_tool_call(
|
|
598
|
+
lambda: self._facade.role_create(
|
|
599
|
+
profile=profile,
|
|
600
|
+
role_name=role_name,
|
|
601
|
+
member_uids=member_uids,
|
|
602
|
+
member_emails=member_emails,
|
|
603
|
+
member_names=member_names,
|
|
604
|
+
role_icon=role_icon,
|
|
605
|
+
),
|
|
606
|
+
error_code="ROLE_CREATE_FAILED",
|
|
607
|
+
normalized_args=normalized_args,
|
|
608
|
+
suggested_next_call={"tool_name": "role_create", "arguments": {"profile": profile, **normalized_args}},
|
|
609
|
+
)
|
|
610
|
+
|
|
277
611
|
def package_attach_app(self, *, profile: str, tag_id: int, app_key: str, app_title: str = "") -> JSONObject:
|
|
278
612
|
normalized_args = {"tag_id": tag_id, "app_key": app_key, "app_title": app_title}
|
|
279
|
-
|
|
613
|
+
result = _safe_tool_call(
|
|
280
614
|
lambda: self._facade.package_attach_app(profile=profile, tag_id=tag_id, app_key=app_key, app_title=app_title),
|
|
281
615
|
error_code="PACKAGE_ATTACH_FAILED",
|
|
282
616
|
normalized_args=normalized_args,
|
|
283
617
|
suggested_next_call={"tool_name": "package_attach_app", "arguments": {"profile": profile, **normalized_args}},
|
|
284
618
|
)
|
|
619
|
+
return self._retry_after_self_lock_release(
|
|
620
|
+
profile=profile,
|
|
621
|
+
result=result,
|
|
622
|
+
retry_call=lambda: self._facade.package_attach_app(
|
|
623
|
+
profile=profile,
|
|
624
|
+
tag_id=tag_id,
|
|
625
|
+
app_key=app_key,
|
|
626
|
+
app_title=app_title,
|
|
627
|
+
),
|
|
628
|
+
)
|
|
285
629
|
|
|
286
630
|
def app_release_edit_lock_if_mine(
|
|
287
631
|
self,
|
|
@@ -317,6 +661,98 @@ class AiBuilderTools(ToolBase):
|
|
|
317
661
|
suggested_next_call={"tool_name": "app_resolve", "arguments": {"profile": profile, **normalized_args}},
|
|
318
662
|
)
|
|
319
663
|
|
|
664
|
+
def app_custom_button_list(self, *, profile: str, app_key: str) -> JSONObject:
|
|
665
|
+
normalized_args = {"app_key": app_key}
|
|
666
|
+
return _safe_tool_call(
|
|
667
|
+
lambda: self._facade.app_custom_button_list(profile=profile, app_key=app_key),
|
|
668
|
+
error_code="CUSTOM_BUTTON_LIST_FAILED",
|
|
669
|
+
normalized_args=normalized_args,
|
|
670
|
+
suggested_next_call={"tool_name": "app_custom_button_list", "arguments": {"profile": profile, **normalized_args}},
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
def app_custom_button_get(self, *, profile: str, app_key: str, button_id: int) -> JSONObject:
|
|
674
|
+
normalized_args = {"app_key": app_key, "button_id": button_id}
|
|
675
|
+
return _safe_tool_call(
|
|
676
|
+
lambda: self._facade.app_custom_button_get(profile=profile, app_key=app_key, button_id=button_id),
|
|
677
|
+
error_code="CUSTOM_BUTTON_GET_FAILED",
|
|
678
|
+
normalized_args=normalized_args,
|
|
679
|
+
suggested_next_call={"tool_name": "app_custom_button_get", "arguments": {"profile": profile, **normalized_args}},
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
def app_custom_button_create(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
683
|
+
try:
|
|
684
|
+
request = CustomButtonPatch.model_validate(payload)
|
|
685
|
+
except ValidationError as exc:
|
|
686
|
+
return _validation_failure(
|
|
687
|
+
str(exc),
|
|
688
|
+
tool_name="app_custom_button_create",
|
|
689
|
+
exc=exc,
|
|
690
|
+
suggested_next_call={
|
|
691
|
+
"tool_name": "app_custom_button_create",
|
|
692
|
+
"arguments": {
|
|
693
|
+
"profile": profile,
|
|
694
|
+
"app_key": app_key,
|
|
695
|
+
"payload": {
|
|
696
|
+
"button_text": "新增记录",
|
|
697
|
+
"background_color": "#FFFFFF",
|
|
698
|
+
"text_color": "#494F57",
|
|
699
|
+
"button_icon": "ex-add-outlined",
|
|
700
|
+
"trigger_action": "addData",
|
|
701
|
+
"trigger_add_data_config": {"related_app_key": "TARGET_APP_KEY", "que_relation": []},
|
|
702
|
+
},
|
|
703
|
+
},
|
|
704
|
+
},
|
|
705
|
+
)
|
|
706
|
+
normalized_args = {"app_key": app_key, "payload": request.model_dump(mode="json")}
|
|
707
|
+
return _safe_tool_call(
|
|
708
|
+
lambda: self._facade.app_custom_button_create(profile=profile, app_key=app_key, payload=request),
|
|
709
|
+
error_code="CUSTOM_BUTTON_CREATE_FAILED",
|
|
710
|
+
normalized_args=normalized_args,
|
|
711
|
+
suggested_next_call={"tool_name": "app_custom_button_create", "arguments": {"profile": profile, **normalized_args}},
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
def app_custom_button_update(self, *, profile: str, app_key: str, button_id: int, payload: JSONObject) -> JSONObject:
|
|
715
|
+
try:
|
|
716
|
+
request = CustomButtonPatch.model_validate(payload)
|
|
717
|
+
except ValidationError as exc:
|
|
718
|
+
return _validation_failure(
|
|
719
|
+
str(exc),
|
|
720
|
+
tool_name="app_custom_button_update",
|
|
721
|
+
exc=exc,
|
|
722
|
+
suggested_next_call={
|
|
723
|
+
"tool_name": "app_custom_button_update",
|
|
724
|
+
"arguments": {
|
|
725
|
+
"profile": profile,
|
|
726
|
+
"app_key": app_key,
|
|
727
|
+
"button_id": button_id,
|
|
728
|
+
"payload": {
|
|
729
|
+
"button_text": "新增记录",
|
|
730
|
+
"background_color": "#FFFFFF",
|
|
731
|
+
"text_color": "#494F57",
|
|
732
|
+
"button_icon": "ex-add-outlined",
|
|
733
|
+
"trigger_action": "link",
|
|
734
|
+
"trigger_link_url": "https://example.com",
|
|
735
|
+
},
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
)
|
|
739
|
+
normalized_args = {"app_key": app_key, "button_id": button_id, "payload": request.model_dump(mode="json")}
|
|
740
|
+
return _safe_tool_call(
|
|
741
|
+
lambda: self._facade.app_custom_button_update(profile=profile, app_key=app_key, button_id=button_id, payload=request),
|
|
742
|
+
error_code="CUSTOM_BUTTON_UPDATE_FAILED",
|
|
743
|
+
normalized_args=normalized_args,
|
|
744
|
+
suggested_next_call={"tool_name": "app_custom_button_update", "arguments": {"profile": profile, **normalized_args}},
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
def app_custom_button_delete(self, *, profile: str, app_key: str, button_id: int) -> JSONObject:
|
|
748
|
+
normalized_args = {"app_key": app_key, "button_id": button_id}
|
|
749
|
+
return _safe_tool_call(
|
|
750
|
+
lambda: self._facade.app_custom_button_delete(profile=profile, app_key=app_key, button_id=button_id),
|
|
751
|
+
error_code="CUSTOM_BUTTON_DELETE_FAILED",
|
|
752
|
+
normalized_args=normalized_args,
|
|
753
|
+
suggested_next_call={"tool_name": "app_custom_button_delete", "arguments": {"profile": profile, **normalized_args}},
|
|
754
|
+
)
|
|
755
|
+
|
|
320
756
|
def app_read_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
321
757
|
normalized_args = {"app_key": app_key}
|
|
322
758
|
return _safe_tool_call(
|
|
@@ -362,6 +798,84 @@ class AiBuilderTools(ToolBase):
|
|
|
362
798
|
suggested_next_call={"tool_name": "app_read_flow_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
363
799
|
)
|
|
364
800
|
|
|
801
|
+
def app_read_charts_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
802
|
+
normalized_args = {"app_key": app_key}
|
|
803
|
+
return _safe_tool_call(
|
|
804
|
+
lambda: self._facade.app_read_charts_summary(profile=profile, app_key=app_key),
|
|
805
|
+
error_code="CHARTS_READ_FAILED",
|
|
806
|
+
normalized_args=normalized_args,
|
|
807
|
+
suggested_next_call={"tool_name": "app_read_charts_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
def portal_list(self, *, profile: str) -> JSONObject:
|
|
811
|
+
return _safe_tool_call(
|
|
812
|
+
lambda: self._facade.portal_list(profile=profile),
|
|
813
|
+
error_code="PORTAL_LIST_FAILED",
|
|
814
|
+
normalized_args={},
|
|
815
|
+
suggested_next_call={"tool_name": "portal_list", "arguments": {"profile": profile}},
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
def portal_get(self, *, profile: str, dash_key: str, being_draft: bool = True) -> JSONObject:
|
|
819
|
+
normalized_args = {"dash_key": dash_key, "being_draft": being_draft}
|
|
820
|
+
return _safe_tool_call(
|
|
821
|
+
lambda: self._facade.portal_get(profile=profile, dash_key=dash_key, being_draft=being_draft),
|
|
822
|
+
error_code="PORTAL_GET_FAILED",
|
|
823
|
+
normalized_args=normalized_args,
|
|
824
|
+
suggested_next_call={"tool_name": "portal_get", "arguments": {"profile": profile, **normalized_args}},
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
def portal_read_summary(self, *, profile: str, dash_key: str, being_draft: bool = True) -> JSONObject:
|
|
828
|
+
normalized_args = {"dash_key": dash_key, "being_draft": being_draft}
|
|
829
|
+
return _safe_tool_call(
|
|
830
|
+
lambda: self._facade.portal_read_summary(profile=profile, dash_key=dash_key, being_draft=being_draft),
|
|
831
|
+
error_code="PORTAL_READ_FAILED",
|
|
832
|
+
normalized_args=normalized_args,
|
|
833
|
+
suggested_next_call={"tool_name": "portal_read_summary", "arguments": {"profile": profile, **normalized_args}},
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
def view_get(self, *, profile: str, viewgraph_key: str) -> JSONObject:
|
|
837
|
+
normalized_args = {"viewgraph_key": viewgraph_key}
|
|
838
|
+
return _safe_tool_call(
|
|
839
|
+
lambda: self._facade.view_get(profile=profile, viewgraph_key=viewgraph_key),
|
|
840
|
+
error_code="VIEW_GET_FAILED",
|
|
841
|
+
normalized_args=normalized_args,
|
|
842
|
+
suggested_next_call={"tool_name": "view_get", "arguments": {"profile": profile, **normalized_args}},
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
def chart_get(
|
|
846
|
+
self,
|
|
847
|
+
*,
|
|
848
|
+
profile: str,
|
|
849
|
+
chart_id: str,
|
|
850
|
+
data_payload: JSONObject | None = None,
|
|
851
|
+
page_num: int | None = None,
|
|
852
|
+
page_size: int | None = None,
|
|
853
|
+
page_num_y: int | None = None,
|
|
854
|
+
page_size_y: int | None = None,
|
|
855
|
+
) -> JSONObject:
|
|
856
|
+
normalized_args = {
|
|
857
|
+
"chart_id": chart_id,
|
|
858
|
+
"data_payload": deepcopy(data_payload) if isinstance(data_payload, dict) else {},
|
|
859
|
+
"page_num": page_num,
|
|
860
|
+
"page_size": page_size,
|
|
861
|
+
"page_num_y": page_num_y,
|
|
862
|
+
"page_size_y": page_size_y,
|
|
863
|
+
}
|
|
864
|
+
return _safe_tool_call(
|
|
865
|
+
lambda: self._facade.chart_get(
|
|
866
|
+
profile=profile,
|
|
867
|
+
chart_id=chart_id,
|
|
868
|
+
data_payload=normalized_args["data_payload"],
|
|
869
|
+
page_num=page_num,
|
|
870
|
+
page_size=page_size,
|
|
871
|
+
page_num_y=page_num_y,
|
|
872
|
+
page_size_y=page_size_y,
|
|
873
|
+
),
|
|
874
|
+
error_code="CHART_GET_FAILED",
|
|
875
|
+
normalized_args=normalized_args,
|
|
876
|
+
suggested_next_call={"tool_name": "chart_get", "arguments": {"profile": profile, "chart_id": chart_id}},
|
|
877
|
+
)
|
|
878
|
+
|
|
365
879
|
def app_schema_plan(
|
|
366
880
|
self,
|
|
367
881
|
*,
|
|
@@ -369,6 +883,8 @@ class AiBuilderTools(ToolBase):
|
|
|
369
883
|
app_key: str = "",
|
|
370
884
|
package_tag_id: int | None = None,
|
|
371
885
|
app_name: str = "",
|
|
886
|
+
icon: str = "",
|
|
887
|
+
color: str = "",
|
|
372
888
|
create_if_missing: bool = False,
|
|
373
889
|
add_fields: list[JSONObject],
|
|
374
890
|
update_fields: list[JSONObject],
|
|
@@ -380,6 +896,8 @@ class AiBuilderTools(ToolBase):
|
|
|
380
896
|
"app_key": app_key,
|
|
381
897
|
"package_tag_id": package_tag_id,
|
|
382
898
|
"app_name": app_name,
|
|
899
|
+
"icon": icon,
|
|
900
|
+
"color": color,
|
|
383
901
|
"create_if_missing": create_if_missing,
|
|
384
902
|
"add_fields": add_fields,
|
|
385
903
|
"update_fields": update_fields,
|
|
@@ -389,6 +907,8 @@ class AiBuilderTools(ToolBase):
|
|
|
389
907
|
except ValidationError as exc:
|
|
390
908
|
return _validation_failure(
|
|
391
909
|
str(exc),
|
|
910
|
+
tool_name="app_schema_plan",
|
|
911
|
+
exc=exc,
|
|
392
912
|
suggested_next_call={
|
|
393
913
|
"tool_name": "app_schema_plan",
|
|
394
914
|
"arguments": {
|
|
@@ -396,6 +916,8 @@ class AiBuilderTools(ToolBase):
|
|
|
396
916
|
"app_key": app_key,
|
|
397
917
|
"package_tag_id": package_tag_id,
|
|
398
918
|
"app_name": app_name,
|
|
919
|
+
"icon": icon,
|
|
920
|
+
"color": color,
|
|
399
921
|
"create_if_missing": create_if_missing,
|
|
400
922
|
"add_fields": [{"name": "字段名称", "type": "text"}],
|
|
401
923
|
"update_fields": [],
|
|
@@ -431,22 +953,29 @@ class AiBuilderTools(ToolBase):
|
|
|
431
953
|
except ValidationError as exc:
|
|
432
954
|
return _validation_failure(
|
|
433
955
|
str(exc),
|
|
956
|
+
tool_name="app_layout_plan",
|
|
957
|
+
exc=exc,
|
|
434
958
|
suggested_next_call={
|
|
435
959
|
"tool_name": "app_layout_plan",
|
|
436
960
|
"arguments": {
|
|
437
961
|
"profile": profile,
|
|
438
962
|
"app_key": app_key,
|
|
439
963
|
"mode": "merge",
|
|
440
|
-
"
|
|
441
|
-
|
|
964
|
+
"sections": [{
|
|
965
|
+
"type": "paragraph",
|
|
966
|
+
"paragraph_id": "basic",
|
|
967
|
+
"title": "基础信息",
|
|
968
|
+
"rows": [["字段A", "字段B", "字段C", "字段D"]],
|
|
969
|
+
}],
|
|
442
970
|
},
|
|
443
971
|
},
|
|
444
972
|
)
|
|
973
|
+
normalized_request = request.model_dump(mode="json", exclude_none=True)
|
|
445
974
|
return _safe_tool_call(
|
|
446
975
|
lambda: self._facade.app_layout_plan(profile=profile, request=request),
|
|
447
976
|
error_code="LAYOUT_PLAN_FAILED",
|
|
448
|
-
normalized_args=
|
|
449
|
-
suggested_next_call={"tool_name": "app_layout_plan", "arguments": {"profile": profile, **
|
|
977
|
+
normalized_args=normalized_request,
|
|
978
|
+
suggested_next_call={"tool_name": "app_layout_plan", "arguments": {"profile": profile, **normalized_request}},
|
|
450
979
|
)
|
|
451
980
|
|
|
452
981
|
def app_flow_plan(
|
|
@@ -472,6 +1001,8 @@ class AiBuilderTools(ToolBase):
|
|
|
472
1001
|
except ValidationError as exc:
|
|
473
1002
|
return _validation_failure(
|
|
474
1003
|
str(exc),
|
|
1004
|
+
tool_name="app_flow_plan",
|
|
1005
|
+
exc=exc,
|
|
475
1006
|
suggested_next_call={
|
|
476
1007
|
"tool_name": "app_flow_plan",
|
|
477
1008
|
"arguments": {
|
|
@@ -512,6 +1043,8 @@ class AiBuilderTools(ToolBase):
|
|
|
512
1043
|
except ValidationError as exc:
|
|
513
1044
|
return _validation_failure(
|
|
514
1045
|
str(exc),
|
|
1046
|
+
tool_name="app_views_plan",
|
|
1047
|
+
exc=exc,
|
|
515
1048
|
suggested_next_call={
|
|
516
1049
|
"tool_name": "app_views_plan",
|
|
517
1050
|
"arguments": {
|
|
@@ -538,6 +1071,57 @@ class AiBuilderTools(ToolBase):
|
|
|
538
1071
|
package_tag_id: int | None = None,
|
|
539
1072
|
app_name: str = "",
|
|
540
1073
|
app_title: str = "",
|
|
1074
|
+
icon: str = "",
|
|
1075
|
+
color: str = "",
|
|
1076
|
+
create_if_missing: bool = False,
|
|
1077
|
+
publish: bool = True,
|
|
1078
|
+
add_fields: list[JSONObject],
|
|
1079
|
+
update_fields: list[JSONObject],
|
|
1080
|
+
remove_fields: list[JSONObject],
|
|
1081
|
+
) -> JSONObject:
|
|
1082
|
+
result = self._app_schema_apply_once(
|
|
1083
|
+
profile=profile,
|
|
1084
|
+
app_key=app_key,
|
|
1085
|
+
package_tag_id=package_tag_id,
|
|
1086
|
+
app_name=app_name,
|
|
1087
|
+
app_title=app_title,
|
|
1088
|
+
icon=icon,
|
|
1089
|
+
color=color,
|
|
1090
|
+
create_if_missing=create_if_missing,
|
|
1091
|
+
publish=publish,
|
|
1092
|
+
add_fields=add_fields,
|
|
1093
|
+
update_fields=update_fields,
|
|
1094
|
+
remove_fields=remove_fields,
|
|
1095
|
+
)
|
|
1096
|
+
return self._retry_after_self_lock_release(
|
|
1097
|
+
profile=profile,
|
|
1098
|
+
result=result,
|
|
1099
|
+
retry_call=lambda: self._app_schema_apply_once(
|
|
1100
|
+
profile=profile,
|
|
1101
|
+
app_key=app_key,
|
|
1102
|
+
package_tag_id=package_tag_id,
|
|
1103
|
+
app_name=app_name,
|
|
1104
|
+
app_title=app_title,
|
|
1105
|
+
icon=icon,
|
|
1106
|
+
color=color,
|
|
1107
|
+
create_if_missing=create_if_missing,
|
|
1108
|
+
publish=publish,
|
|
1109
|
+
add_fields=add_fields,
|
|
1110
|
+
update_fields=update_fields,
|
|
1111
|
+
remove_fields=remove_fields,
|
|
1112
|
+
),
|
|
1113
|
+
)
|
|
1114
|
+
|
|
1115
|
+
def _app_schema_apply_once(
|
|
1116
|
+
self,
|
|
1117
|
+
*,
|
|
1118
|
+
profile: str,
|
|
1119
|
+
app_key: str = "",
|
|
1120
|
+
package_tag_id: int | None = None,
|
|
1121
|
+
app_name: str = "",
|
|
1122
|
+
app_title: str = "",
|
|
1123
|
+
icon: str = "",
|
|
1124
|
+
color: str = "",
|
|
541
1125
|
create_if_missing: bool = False,
|
|
542
1126
|
publish: bool = True,
|
|
543
1127
|
add_fields: list[JSONObject],
|
|
@@ -545,44 +1129,76 @@ class AiBuilderTools(ToolBase):
|
|
|
545
1129
|
remove_fields: list[JSONObject],
|
|
546
1130
|
) -> JSONObject:
|
|
547
1131
|
effective_app_name = app_name or app_title
|
|
1132
|
+
plan_result = self._rewrite_plan_result_for_apply(
|
|
1133
|
+
result=self.app_schema_plan(
|
|
1134
|
+
profile=profile,
|
|
1135
|
+
app_key=app_key,
|
|
1136
|
+
package_tag_id=package_tag_id,
|
|
1137
|
+
app_name=effective_app_name,
|
|
1138
|
+
icon=icon,
|
|
1139
|
+
color=color,
|
|
1140
|
+
create_if_missing=create_if_missing,
|
|
1141
|
+
add_fields=add_fields,
|
|
1142
|
+
update_fields=update_fields,
|
|
1143
|
+
remove_fields=remove_fields,
|
|
1144
|
+
),
|
|
1145
|
+
profile=profile,
|
|
1146
|
+
publish=publish,
|
|
1147
|
+
plan_tool_name="app_schema_plan",
|
|
1148
|
+
apply_tool_name="app_schema_apply",
|
|
1149
|
+
)
|
|
1150
|
+
if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
|
|
1151
|
+
return plan_result
|
|
1152
|
+
plan_args = plan_result.get("normalized_args")
|
|
1153
|
+
if not isinstance(plan_args, dict):
|
|
1154
|
+
plan_args = {}
|
|
548
1155
|
try:
|
|
549
|
-
parsed_add = [FieldPatch.model_validate(item) for item in add_fields]
|
|
550
|
-
parsed_update = [FieldUpdatePatch.model_validate(item) for item in update_fields]
|
|
551
|
-
parsed_remove = [FieldRemovePatch.model_validate(item) for item in remove_fields]
|
|
1156
|
+
parsed_add = [FieldPatch.model_validate(item) for item in plan_args.get("add_fields") or []]
|
|
1157
|
+
parsed_update = [FieldUpdatePatch.model_validate(item) for item in plan_args.get("update_fields") or []]
|
|
1158
|
+
parsed_remove = [FieldRemovePatch.model_validate(item) for item in plan_args.get("remove_fields") or []]
|
|
552
1159
|
except ValidationError as exc:
|
|
553
1160
|
return _validation_failure(
|
|
554
1161
|
str(exc),
|
|
1162
|
+
tool_name="app_schema_apply",
|
|
1163
|
+
exc=exc,
|
|
555
1164
|
suggested_next_call={
|
|
556
1165
|
"tool_name": "app_schema_apply",
|
|
557
1166
|
"arguments": {
|
|
558
1167
|
"profile": profile,
|
|
559
|
-
"app_key": app_key,
|
|
560
|
-
"package_tag_id": package_tag_id,
|
|
561
|
-
"app_name": effective_app_name,
|
|
562
|
-
"
|
|
563
|
-
"
|
|
564
|
-
"
|
|
565
|
-
"
|
|
1168
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
1169
|
+
"package_tag_id": plan_args.get("package_tag_id", package_tag_id),
|
|
1170
|
+
"app_name": str(plan_args.get("app_name") or effective_app_name),
|
|
1171
|
+
"icon": str(plan_args.get("icon") or icon),
|
|
1172
|
+
"color": str(plan_args.get("color") or color),
|
|
1173
|
+
"create_if_missing": bool(plan_args.get("create_if_missing", create_if_missing)),
|
|
1174
|
+
"publish": publish,
|
|
1175
|
+
"add_fields": plan_args.get("add_fields") or [{"name": "字段名称", "type": "text", "required": False}],
|
|
1176
|
+
"update_fields": plan_args.get("update_fields") or [],
|
|
1177
|
+
"remove_fields": plan_args.get("remove_fields") or [],
|
|
566
1178
|
},
|
|
567
1179
|
},
|
|
568
1180
|
)
|
|
569
1181
|
normalized_args = {
|
|
570
|
-
"app_key": app_key,
|
|
571
|
-
"package_tag_id": package_tag_id,
|
|
572
|
-
"app_name": effective_app_name,
|
|
573
|
-
"
|
|
1182
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
1183
|
+
"package_tag_id": plan_args.get("package_tag_id", package_tag_id),
|
|
1184
|
+
"app_name": str(plan_args.get("app_name") or effective_app_name),
|
|
1185
|
+
"icon": str(plan_args.get("icon") or icon or ""),
|
|
1186
|
+
"color": str(plan_args.get("color") or color or ""),
|
|
1187
|
+
"create_if_missing": bool(plan_args.get("create_if_missing", create_if_missing)),
|
|
574
1188
|
"publish": publish,
|
|
575
1189
|
"add_fields": [patch.model_dump(mode="json") for patch in parsed_add],
|
|
576
1190
|
"update_fields": [patch.model_dump(mode="json") for patch in parsed_update],
|
|
577
1191
|
"remove_fields": [patch.model_dump(mode="json") for patch in parsed_remove],
|
|
578
1192
|
}
|
|
579
|
-
|
|
1193
|
+
result = _safe_tool_call(
|
|
580
1194
|
lambda: self._facade.app_schema_apply(
|
|
581
1195
|
profile=profile,
|
|
582
|
-
app_key=app_key,
|
|
583
|
-
package_tag_id=package_tag_id,
|
|
584
|
-
app_name=effective_app_name,
|
|
585
|
-
|
|
1196
|
+
app_key=str(plan_args.get("app_key") or app_key),
|
|
1197
|
+
package_tag_id=plan_args.get("package_tag_id", package_tag_id),
|
|
1198
|
+
app_name=str(plan_args.get("app_name") or effective_app_name),
|
|
1199
|
+
icon=str(plan_args.get("icon") or icon or ""),
|
|
1200
|
+
color=str(plan_args.get("color") or color or ""),
|
|
1201
|
+
create_if_missing=bool(plan_args.get("create_if_missing", create_if_missing)),
|
|
586
1202
|
publish=publish,
|
|
587
1203
|
add_fields=parsed_add,
|
|
588
1204
|
update_fields=parsed_update,
|
|
@@ -592,48 +1208,80 @@ class AiBuilderTools(ToolBase):
|
|
|
592
1208
|
normalized_args=normalized_args,
|
|
593
1209
|
suggested_next_call={"tool_name": "app_schema_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
594
1210
|
)
|
|
1211
|
+
return result
|
|
595
1212
|
|
|
596
1213
|
def app_layout_apply(self, *, profile: str, app_key: str, mode: str = "merge", publish: bool = True, sections: list[JSONObject]) -> JSONObject:
|
|
1214
|
+
result = self._app_layout_apply_once(
|
|
1215
|
+
profile=profile,
|
|
1216
|
+
app_key=app_key,
|
|
1217
|
+
mode=mode,
|
|
1218
|
+
publish=publish,
|
|
1219
|
+
sections=sections,
|
|
1220
|
+
)
|
|
1221
|
+
return self._retry_after_self_lock_release(
|
|
1222
|
+
profile=profile,
|
|
1223
|
+
result=result,
|
|
1224
|
+
retry_call=lambda: self._app_layout_apply_once(
|
|
1225
|
+
profile=profile,
|
|
1226
|
+
app_key=app_key,
|
|
1227
|
+
mode=mode,
|
|
1228
|
+
publish=publish,
|
|
1229
|
+
sections=sections,
|
|
1230
|
+
),
|
|
1231
|
+
)
|
|
1232
|
+
|
|
1233
|
+
def _app_layout_apply_once(self, *, profile: str, app_key: str, mode: str = "merge", publish: bool = True, sections: list[JSONObject]) -> JSONObject:
|
|
1234
|
+
plan_result = self._rewrite_plan_result_for_apply(
|
|
1235
|
+
result=self.app_layout_plan(
|
|
1236
|
+
profile=profile,
|
|
1237
|
+
app_key=app_key,
|
|
1238
|
+
mode=mode,
|
|
1239
|
+
sections=sections,
|
|
1240
|
+
preset=None,
|
|
1241
|
+
),
|
|
1242
|
+
profile=profile,
|
|
1243
|
+
publish=publish,
|
|
1244
|
+
plan_tool_name="app_layout_plan",
|
|
1245
|
+
apply_tool_name="app_layout_apply",
|
|
1246
|
+
)
|
|
1247
|
+
if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
|
|
1248
|
+
return plan_result
|
|
1249
|
+
plan_args = plan_result.get("normalized_args")
|
|
1250
|
+
if not isinstance(plan_args, dict):
|
|
1251
|
+
plan_args = {}
|
|
597
1252
|
try:
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
parsed_mode = LayoutApplyMode(normalized_mode)
|
|
602
|
-
parsed_sections = [LayoutSectionPatch.model_validate(item) for item in sections]
|
|
603
|
-
except ValueError:
|
|
604
|
-
return _validation_failure(
|
|
605
|
-
"mode must be one of: merge, replace",
|
|
606
|
-
suggested_next_call={
|
|
607
|
-
"tool_name": "app_layout_apply",
|
|
608
|
-
"arguments": {
|
|
609
|
-
"profile": profile,
|
|
610
|
-
"app_key": app_key,
|
|
611
|
-
"mode": "merge",
|
|
612
|
-
"sections": [{"title": "基础信息", "rows": [["字段A", "字段B"]]}],
|
|
613
|
-
},
|
|
614
|
-
},
|
|
615
|
-
)
|
|
616
|
-
except ValidationError as exc:
|
|
1253
|
+
parsed_mode = LayoutApplyMode(str(plan_args.get("mode") or mode))
|
|
1254
|
+
parsed_sections = [LayoutSectionPatch.model_validate(item) for item in plan_args.get("sections") or []]
|
|
1255
|
+
except (ValueError, ValidationError) as exc:
|
|
617
1256
|
return _validation_failure(
|
|
618
1257
|
str(exc),
|
|
1258
|
+
tool_name="app_layout_apply",
|
|
1259
|
+
exc=exc if isinstance(exc, ValidationError) else None,
|
|
619
1260
|
suggested_next_call={
|
|
620
1261
|
"tool_name": "app_layout_apply",
|
|
621
1262
|
"arguments": {
|
|
622
1263
|
"profile": profile,
|
|
623
|
-
"app_key": app_key,
|
|
624
|
-
"mode": "merge",
|
|
625
|
-
"
|
|
1264
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
1265
|
+
"mode": str(plan_args.get("mode") or "merge"),
|
|
1266
|
+
"publish": publish,
|
|
1267
|
+
"sections": plan_args.get("sections") or [{"title": "基础信息", "rows": [["字段A", "字段B"]]}],
|
|
626
1268
|
},
|
|
627
1269
|
},
|
|
628
1270
|
)
|
|
629
1271
|
normalized_args = {
|
|
630
|
-
"app_key": app_key,
|
|
1272
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
631
1273
|
"mode": parsed_mode.value,
|
|
632
1274
|
"publish": publish,
|
|
633
|
-
"sections": [section.model_dump(mode="json") for section in parsed_sections],
|
|
1275
|
+
"sections": [section.model_dump(mode="json", exclude_none=True) for section in parsed_sections],
|
|
634
1276
|
}
|
|
635
1277
|
return _safe_tool_call(
|
|
636
|
-
lambda: self._facade.app_layout_apply(
|
|
1278
|
+
lambda: self._facade.app_layout_apply(
|
|
1279
|
+
profile=profile,
|
|
1280
|
+
app_key=str(plan_args.get("app_key") or app_key),
|
|
1281
|
+
mode=parsed_mode,
|
|
1282
|
+
publish=publish,
|
|
1283
|
+
sections=parsed_sections,
|
|
1284
|
+
),
|
|
637
1285
|
error_code="LAYOUT_APPLY_FAILED",
|
|
638
1286
|
normalized_args=normalized_args,
|
|
639
1287
|
suggested_next_call={"tool_name": "app_layout_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
@@ -649,27 +1297,80 @@ class AiBuilderTools(ToolBase):
|
|
|
649
1297
|
nodes: list[JSONObject],
|
|
650
1298
|
transitions: list[JSONObject],
|
|
651
1299
|
) -> JSONObject:
|
|
1300
|
+
result = self._app_flow_apply_once(
|
|
1301
|
+
profile=profile,
|
|
1302
|
+
app_key=app_key,
|
|
1303
|
+
mode=mode,
|
|
1304
|
+
publish=publish,
|
|
1305
|
+
nodes=nodes,
|
|
1306
|
+
transitions=transitions,
|
|
1307
|
+
)
|
|
1308
|
+
return self._retry_after_self_lock_release(
|
|
1309
|
+
profile=profile,
|
|
1310
|
+
result=result,
|
|
1311
|
+
retry_call=lambda: self._app_flow_apply_once(
|
|
1312
|
+
profile=profile,
|
|
1313
|
+
app_key=app_key,
|
|
1314
|
+
mode=mode,
|
|
1315
|
+
publish=publish,
|
|
1316
|
+
nodes=nodes,
|
|
1317
|
+
transitions=transitions,
|
|
1318
|
+
),
|
|
1319
|
+
)
|
|
1320
|
+
|
|
1321
|
+
def _app_flow_apply_once(
|
|
1322
|
+
self,
|
|
1323
|
+
*,
|
|
1324
|
+
profile: str,
|
|
1325
|
+
app_key: str,
|
|
1326
|
+
mode: str = "replace",
|
|
1327
|
+
publish: bool = True,
|
|
1328
|
+
nodes: list[JSONObject],
|
|
1329
|
+
transitions: list[JSONObject],
|
|
1330
|
+
) -> JSONObject:
|
|
1331
|
+
plan_result = self._rewrite_plan_result_for_apply(
|
|
1332
|
+
result=self.app_flow_plan(
|
|
1333
|
+
profile=profile,
|
|
1334
|
+
app_key=app_key,
|
|
1335
|
+
mode=mode,
|
|
1336
|
+
nodes=nodes,
|
|
1337
|
+
transitions=transitions,
|
|
1338
|
+
preset=None,
|
|
1339
|
+
),
|
|
1340
|
+
profile=profile,
|
|
1341
|
+
publish=publish,
|
|
1342
|
+
plan_tool_name="app_flow_plan",
|
|
1343
|
+
apply_tool_name="app_flow_apply",
|
|
1344
|
+
)
|
|
1345
|
+
if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
|
|
1346
|
+
return plan_result
|
|
1347
|
+
plan_args = plan_result.get("normalized_args")
|
|
1348
|
+
if not isinstance(plan_args, dict):
|
|
1349
|
+
plan_args = {}
|
|
652
1350
|
try:
|
|
653
1351
|
request = FlowPlanRequest.model_validate(
|
|
654
1352
|
{
|
|
655
|
-
"app_key": app_key,
|
|
656
|
-
"mode": mode,
|
|
657
|
-
"nodes": nodes,
|
|
658
|
-
"transitions": transitions,
|
|
1353
|
+
"app_key": plan_args.get("app_key") or app_key,
|
|
1354
|
+
"mode": plan_args.get("mode") or mode,
|
|
1355
|
+
"nodes": plan_args.get("nodes") or [],
|
|
1356
|
+
"transitions": plan_args.get("transitions") or [],
|
|
659
1357
|
"preset": None,
|
|
660
1358
|
}
|
|
661
1359
|
)
|
|
662
1360
|
except ValidationError as exc:
|
|
663
1361
|
return _validation_failure(
|
|
664
1362
|
str(exc),
|
|
1363
|
+
tool_name="app_flow_apply",
|
|
1364
|
+
exc=exc,
|
|
665
1365
|
suggested_next_call={
|
|
666
1366
|
"tool_name": "app_flow_apply",
|
|
667
1367
|
"arguments": {
|
|
668
1368
|
"profile": profile,
|
|
669
|
-
"app_key": app_key,
|
|
670
|
-
"mode": "replace",
|
|
671
|
-
"
|
|
672
|
-
"
|
|
1369
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
1370
|
+
"mode": str(plan_args.get("mode") or "replace"),
|
|
1371
|
+
"publish": publish,
|
|
1372
|
+
"nodes": plan_args.get("nodes") or [{"id": "start", "type": "start", "name": "发起"}],
|
|
1373
|
+
"transitions": plan_args.get("transitions") or [],
|
|
673
1374
|
},
|
|
674
1375
|
},
|
|
675
1376
|
)
|
|
@@ -703,45 +1404,363 @@ class AiBuilderTools(ToolBase):
|
|
|
703
1404
|
upsert_views: list[JSONObject],
|
|
704
1405
|
remove_views: list[str],
|
|
705
1406
|
) -> JSONObject:
|
|
1407
|
+
result = self._app_views_apply_once(
|
|
1408
|
+
profile=profile,
|
|
1409
|
+
app_key=app_key,
|
|
1410
|
+
publish=publish,
|
|
1411
|
+
upsert_views=upsert_views,
|
|
1412
|
+
remove_views=remove_views,
|
|
1413
|
+
)
|
|
1414
|
+
return self._retry_after_self_lock_release(
|
|
1415
|
+
profile=profile,
|
|
1416
|
+
result=result,
|
|
1417
|
+
retry_call=lambda: self._app_views_apply_once(
|
|
1418
|
+
profile=profile,
|
|
1419
|
+
app_key=app_key,
|
|
1420
|
+
publish=publish,
|
|
1421
|
+
remove_views=remove_views,
|
|
1422
|
+
upsert_views=upsert_views,
|
|
1423
|
+
),
|
|
1424
|
+
)
|
|
1425
|
+
|
|
1426
|
+
def _app_views_apply_once(
|
|
1427
|
+
self,
|
|
1428
|
+
*,
|
|
1429
|
+
profile: str,
|
|
1430
|
+
app_key: str,
|
|
1431
|
+
publish: bool = True,
|
|
1432
|
+
upsert_views: list[JSONObject],
|
|
1433
|
+
remove_views: list[str],
|
|
1434
|
+
) -> JSONObject:
|
|
1435
|
+
plan_result = self._rewrite_plan_result_for_apply(
|
|
1436
|
+
result=self.app_views_plan(
|
|
1437
|
+
profile=profile,
|
|
1438
|
+
app_key=app_key,
|
|
1439
|
+
upsert_views=upsert_views,
|
|
1440
|
+
remove_views=remove_views,
|
|
1441
|
+
preset=None,
|
|
1442
|
+
),
|
|
1443
|
+
profile=profile,
|
|
1444
|
+
publish=publish,
|
|
1445
|
+
plan_tool_name="app_views_plan",
|
|
1446
|
+
apply_tool_name="app_views_apply",
|
|
1447
|
+
)
|
|
1448
|
+
if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
|
|
1449
|
+
return plan_result
|
|
1450
|
+
plan_args = plan_result.get("normalized_args")
|
|
1451
|
+
if not isinstance(plan_args, dict):
|
|
1452
|
+
plan_args = {}
|
|
706
1453
|
try:
|
|
707
|
-
parsed_views = [ViewUpsertPatch.model_validate(item) for item in upsert_views]
|
|
1454
|
+
parsed_views = [ViewUpsertPatch.model_validate(item) for item in plan_args.get("upsert_views") or []]
|
|
708
1455
|
except ValidationError as exc:
|
|
709
1456
|
return _validation_failure(
|
|
710
1457
|
str(exc),
|
|
1458
|
+
tool_name="app_views_apply",
|
|
1459
|
+
exc=exc,
|
|
711
1460
|
suggested_next_call={
|
|
712
1461
|
"tool_name": "app_views_apply",
|
|
713
1462
|
"arguments": {
|
|
714
1463
|
"profile": profile,
|
|
715
|
-
"app_key": app_key,
|
|
716
|
-
"
|
|
717
|
-
"
|
|
1464
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
1465
|
+
"publish": publish,
|
|
1466
|
+
"upsert_views": plan_args.get("upsert_views") or [{"name": "全部数据", "type": "table", "columns": ["字段A"]}],
|
|
1467
|
+
"remove_views": plan_args.get("remove_views") or [],
|
|
718
1468
|
},
|
|
719
1469
|
},
|
|
720
1470
|
)
|
|
721
1471
|
normalized_args = {
|
|
722
|
-
"app_key": app_key,
|
|
1472
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
723
1473
|
"publish": publish,
|
|
724
1474
|
"upsert_views": [view.model_dump(mode="json") for view in parsed_views],
|
|
725
|
-
"remove_views": list(remove_views),
|
|
1475
|
+
"remove_views": list(plan_args.get("remove_views") or remove_views),
|
|
726
1476
|
}
|
|
727
1477
|
return _safe_tool_call(
|
|
728
|
-
lambda: self._facade.app_views_apply(
|
|
1478
|
+
lambda: self._facade.app_views_apply(
|
|
1479
|
+
profile=profile,
|
|
1480
|
+
app_key=str(plan_args.get("app_key") or app_key),
|
|
1481
|
+
publish=publish,
|
|
1482
|
+
upsert_views=parsed_views,
|
|
1483
|
+
remove_views=list(plan_args.get("remove_views") or remove_views),
|
|
1484
|
+
),
|
|
729
1485
|
error_code="VIEWS_APPLY_FAILED",
|
|
730
1486
|
normalized_args=normalized_args,
|
|
731
1487
|
suggested_next_call={"tool_name": "app_views_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
732
1488
|
)
|
|
733
1489
|
|
|
1490
|
+
def chart_apply(
|
|
1491
|
+
self,
|
|
1492
|
+
*,
|
|
1493
|
+
profile: str,
|
|
1494
|
+
app_key: str,
|
|
1495
|
+
upsert_charts: list[JSONObject],
|
|
1496
|
+
remove_chart_ids: list[str],
|
|
1497
|
+
reorder_chart_ids: list[str],
|
|
1498
|
+
) -> JSONObject:
|
|
1499
|
+
return self.app_charts_apply(
|
|
1500
|
+
profile=profile,
|
|
1501
|
+
app_key=app_key,
|
|
1502
|
+
upsert_charts=upsert_charts,
|
|
1503
|
+
remove_chart_ids=remove_chart_ids,
|
|
1504
|
+
reorder_chart_ids=reorder_chart_ids,
|
|
1505
|
+
)
|
|
1506
|
+
|
|
1507
|
+
def app_charts_apply(
|
|
1508
|
+
self,
|
|
1509
|
+
*,
|
|
1510
|
+
profile: str,
|
|
1511
|
+
app_key: str,
|
|
1512
|
+
upsert_charts: list[JSONObject],
|
|
1513
|
+
remove_chart_ids: list[str],
|
|
1514
|
+
reorder_chart_ids: list[str],
|
|
1515
|
+
) -> JSONObject:
|
|
1516
|
+
try:
|
|
1517
|
+
request = ChartApplyRequest.model_validate(
|
|
1518
|
+
{
|
|
1519
|
+
"app_key": app_key,
|
|
1520
|
+
"upsert_charts": upsert_charts or [],
|
|
1521
|
+
"remove_chart_ids": remove_chart_ids or [],
|
|
1522
|
+
"reorder_chart_ids": reorder_chart_ids or [],
|
|
1523
|
+
}
|
|
1524
|
+
)
|
|
1525
|
+
except ValidationError as exc:
|
|
1526
|
+
return _validation_failure(
|
|
1527
|
+
str(exc),
|
|
1528
|
+
tool_name="app_charts_apply",
|
|
1529
|
+
exc=exc,
|
|
1530
|
+
suggested_next_call={
|
|
1531
|
+
"tool_name": "app_charts_apply",
|
|
1532
|
+
"arguments": {
|
|
1533
|
+
"profile": profile,
|
|
1534
|
+
"app_key": app_key,
|
|
1535
|
+
"upsert_charts": [{"name": "销售总量", "chart_type": "target", "indicator_field_ids": []}],
|
|
1536
|
+
"remove_chart_ids": [],
|
|
1537
|
+
"reorder_chart_ids": [],
|
|
1538
|
+
},
|
|
1539
|
+
},
|
|
1540
|
+
)
|
|
1541
|
+
normalized_args = request.model_dump(mode="json")
|
|
1542
|
+
return _safe_tool_call(
|
|
1543
|
+
lambda: self._facade.chart_apply(profile=profile, request=request),
|
|
1544
|
+
error_code="CHART_APPLY_FAILED",
|
|
1545
|
+
normalized_args=normalized_args,
|
|
1546
|
+
suggested_next_call={"tool_name": "app_charts_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1547
|
+
)
|
|
1548
|
+
|
|
1549
|
+
def portal_apply(
|
|
1550
|
+
self,
|
|
1551
|
+
*,
|
|
1552
|
+
profile: str,
|
|
1553
|
+
dash_key: str = "",
|
|
1554
|
+
dash_name: str = "",
|
|
1555
|
+
package_tag_id: int | None = None,
|
|
1556
|
+
publish: bool = True,
|
|
1557
|
+
sections: list[JSONObject],
|
|
1558
|
+
auth: JSONObject | None = None,
|
|
1559
|
+
icon: str | None = None,
|
|
1560
|
+
color: str | None = None,
|
|
1561
|
+
hide_copyright: bool | None = None,
|
|
1562
|
+
dash_global_config: JSONObject | None = None,
|
|
1563
|
+
config: JSONObject | None = None,
|
|
1564
|
+
) -> JSONObject:
|
|
1565
|
+
try:
|
|
1566
|
+
request = PortalApplyRequest.model_validate(
|
|
1567
|
+
{
|
|
1568
|
+
"dash_key": dash_key or None,
|
|
1569
|
+
"dash_name": dash_name or None,
|
|
1570
|
+
"package_tag_id": package_tag_id,
|
|
1571
|
+
"publish": publish,
|
|
1572
|
+
"sections": sections or [],
|
|
1573
|
+
"auth": auth,
|
|
1574
|
+
"icon": icon,
|
|
1575
|
+
"color": color,
|
|
1576
|
+
"hide_copyright": hide_copyright,
|
|
1577
|
+
"dash_global_config": dash_global_config,
|
|
1578
|
+
"config": config or {},
|
|
1579
|
+
}
|
|
1580
|
+
)
|
|
1581
|
+
except ValidationError as exc:
|
|
1582
|
+
return _validation_failure(
|
|
1583
|
+
str(exc),
|
|
1584
|
+
tool_name="portal_apply",
|
|
1585
|
+
exc=exc,
|
|
1586
|
+
suggested_next_call={
|
|
1587
|
+
"tool_name": "portal_apply",
|
|
1588
|
+
"arguments": {
|
|
1589
|
+
"profile": profile,
|
|
1590
|
+
"dash_name": dash_name or "业务门户",
|
|
1591
|
+
"package_tag_id": package_tag_id or 1001,
|
|
1592
|
+
"publish": True,
|
|
1593
|
+
"sections": [
|
|
1594
|
+
{
|
|
1595
|
+
"title": "经营概览",
|
|
1596
|
+
"source_type": "text",
|
|
1597
|
+
"text": "欢迎使用业务门户",
|
|
1598
|
+
}
|
|
1599
|
+
],
|
|
1600
|
+
},
|
|
1601
|
+
},
|
|
1602
|
+
)
|
|
1603
|
+
normalized_args = request.model_dump(mode="json")
|
|
1604
|
+
return _safe_tool_call(
|
|
1605
|
+
lambda: self._facade.portal_apply(profile=profile, request=request),
|
|
1606
|
+
error_code="PORTAL_APPLY_FAILED",
|
|
1607
|
+
normalized_args=normalized_args,
|
|
1608
|
+
suggested_next_call={"tool_name": "portal_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1609
|
+
)
|
|
1610
|
+
|
|
734
1611
|
def app_publish_verify(self, *, profile: str, app_key: str, expected_package_tag_id: int | None = None) -> JSONObject:
|
|
735
1612
|
normalized_args = {"app_key": app_key, "expected_package_tag_id": expected_package_tag_id}
|
|
736
|
-
|
|
1613
|
+
result = _safe_tool_call(
|
|
737
1614
|
lambda: self._facade.app_publish_verify(profile=profile, app_key=app_key, expected_package_tag_id=expected_package_tag_id),
|
|
738
1615
|
error_code="PUBLISH_VERIFY_FAILED",
|
|
739
1616
|
normalized_args=normalized_args,
|
|
740
1617
|
suggested_next_call={"tool_name": "app_publish_verify", "arguments": {"profile": profile, **normalized_args}},
|
|
741
1618
|
)
|
|
1619
|
+
return self._retry_after_self_lock_release(
|
|
1620
|
+
profile=profile,
|
|
1621
|
+
result=result,
|
|
1622
|
+
retry_call=lambda: self._facade.app_publish_verify(
|
|
1623
|
+
profile=profile,
|
|
1624
|
+
app_key=app_key,
|
|
1625
|
+
expected_package_tag_id=expected_package_tag_id,
|
|
1626
|
+
),
|
|
1627
|
+
)
|
|
1628
|
+
|
|
1629
|
+
def _retry_after_self_lock_release(self, *, profile: str, result: JSONObject, retry_call) -> JSONObject:
|
|
1630
|
+
if not isinstance(result, dict) or result.get("status") != "failed" or result.get("error_code") != "APP_EDIT_LOCKED":
|
|
1631
|
+
return result
|
|
1632
|
+
suggested = result.get("suggested_next_call")
|
|
1633
|
+
if not isinstance(suggested, dict) or suggested.get("tool_name") != "app_release_edit_lock_if_mine":
|
|
1634
|
+
return result
|
|
1635
|
+
arguments = suggested.get("arguments")
|
|
1636
|
+
if not isinstance(arguments, dict):
|
|
1637
|
+
return result
|
|
1638
|
+
app_key = str(arguments.get("app_key") or "")
|
|
1639
|
+
lock_owner_email = str(arguments.get("lock_owner_email") or "")
|
|
1640
|
+
lock_owner_name = str(arguments.get("lock_owner_name") or "")
|
|
1641
|
+
release_attempts: list[JSONObject] = []
|
|
1642
|
+
retried: JSONObject = result
|
|
1643
|
+
for _ in range(3):
|
|
1644
|
+
release_result = self.app_release_edit_lock_if_mine(
|
|
1645
|
+
profile=profile,
|
|
1646
|
+
app_key=app_key,
|
|
1647
|
+
lock_owner_email=lock_owner_email,
|
|
1648
|
+
lock_owner_name=lock_owner_name,
|
|
1649
|
+
)
|
|
1650
|
+
release_attempts.append(release_result)
|
|
1651
|
+
if not isinstance(release_result, dict) or release_result.get("status") != "success":
|
|
1652
|
+
result.setdefault("details", {})
|
|
1653
|
+
if isinstance(result["details"], dict):
|
|
1654
|
+
result["details"]["edit_lock_release_result"] = release_result
|
|
1655
|
+
result["details"]["edit_lock_release_attempts"] = release_attempts
|
|
1656
|
+
return result
|
|
1657
|
+
retried = retry_call()
|
|
1658
|
+
if not (
|
|
1659
|
+
isinstance(retried, dict)
|
|
1660
|
+
and retried.get("status") == "failed"
|
|
1661
|
+
and retried.get("error_code") == "APP_EDIT_LOCKED"
|
|
1662
|
+
):
|
|
1663
|
+
break
|
|
1664
|
+
time.sleep(0.2)
|
|
1665
|
+
if (
|
|
1666
|
+
isinstance(retried, dict)
|
|
1667
|
+
and retried.get("status") == "failed"
|
|
1668
|
+
and retried.get("error_code") == "APP_EDIT_LOCKED"
|
|
1669
|
+
):
|
|
1670
|
+
retried = {
|
|
1671
|
+
**retried,
|
|
1672
|
+
"error_code": "PERSISTENT_SELF_LOCK",
|
|
1673
|
+
"message": "app remains locked by the current user's active editor session after repeated forced release attempts",
|
|
1674
|
+
"recoverable": True,
|
|
1675
|
+
"suggested_next_call": None,
|
|
1676
|
+
}
|
|
1677
|
+
if isinstance(retried, dict):
|
|
1678
|
+
retried.setdefault("details", {})
|
|
1679
|
+
if isinstance(retried["details"], dict):
|
|
1680
|
+
retried["details"]["edit_lock_release_result"] = release_attempts[-1] if release_attempts else None
|
|
1681
|
+
retried["details"]["edit_lock_release_attempts"] = release_attempts
|
|
1682
|
+
retried["edit_lock_released"] = bool(release_attempts)
|
|
1683
|
+
retried["retried_after_edit_lock_release"] = True
|
|
1684
|
+
return retried
|
|
1685
|
+
|
|
1686
|
+
def _rewrite_plan_result_for_apply(
|
|
1687
|
+
self,
|
|
1688
|
+
*,
|
|
1689
|
+
result: JSONObject,
|
|
1690
|
+
profile: str,
|
|
1691
|
+
publish: bool,
|
|
1692
|
+
plan_tool_name: str,
|
|
1693
|
+
apply_tool_name: str,
|
|
1694
|
+
) -> JSONObject:
|
|
1695
|
+
if not isinstance(result, dict):
|
|
1696
|
+
return result
|
|
1697
|
+
rewritten = dict(result)
|
|
1698
|
+
if rewritten.get("error_code") == "VALIDATION_ERROR":
|
|
1699
|
+
contract = _BUILDER_TOOL_CONTRACTS.get(apply_tool_name)
|
|
1700
|
+
details = rewritten.get("details")
|
|
1701
|
+
if not isinstance(details, dict):
|
|
1702
|
+
details = {}
|
|
1703
|
+
rewritten["details"] = details
|
|
1704
|
+
if isinstance(contract, dict):
|
|
1705
|
+
rewritten["allowed_values"] = deepcopy(contract.get("allowed_values", {}))
|
|
1706
|
+
details["allowed_keys"] = deepcopy(contract.get("allowed_keys", []))
|
|
1707
|
+
details["section_allowed_keys"] = deepcopy(contract.get("section_allowed_keys", []))
|
|
1708
|
+
details["section_aliases"] = deepcopy(contract.get("section_aliases", {}))
|
|
1709
|
+
details["minimal_section_example"] = deepcopy(contract.get("minimal_section_example"))
|
|
1710
|
+
suggested_next_call = rewritten.get("suggested_next_call")
|
|
1711
|
+
if isinstance(suggested_next_call, dict):
|
|
1712
|
+
if suggested_next_call.get("tool_name") == plan_tool_name:
|
|
1713
|
+
arguments = suggested_next_call.get("arguments")
|
|
1714
|
+
normalized_arguments = dict(arguments) if isinstance(arguments, dict) else {}
|
|
1715
|
+
normalized_arguments.setdefault("profile", profile)
|
|
1716
|
+
normalized_arguments["publish"] = publish
|
|
1717
|
+
rewritten["suggested_next_call"] = {
|
|
1718
|
+
**suggested_next_call,
|
|
1719
|
+
"tool_name": apply_tool_name,
|
|
1720
|
+
"arguments": normalized_arguments,
|
|
1721
|
+
}
|
|
1722
|
+
return rewritten
|
|
1723
|
+
if rewritten.get("error_code") == "VALIDATION_ERROR" and suggested_next_call.get("tool_name") == apply_tool_name:
|
|
1724
|
+
arguments = suggested_next_call.get("arguments")
|
|
1725
|
+
normalized_arguments = dict(arguments) if isinstance(arguments, dict) else {}
|
|
1726
|
+
normalized_arguments.setdefault("profile", profile)
|
|
1727
|
+
normalized_arguments["publish"] = publish
|
|
1728
|
+
if isinstance(details, dict):
|
|
1729
|
+
details["canonical_arguments"] = normalized_arguments
|
|
1730
|
+
rewritten["suggested_next_call"] = {
|
|
1731
|
+
**suggested_next_call,
|
|
1732
|
+
"arguments": normalized_arguments,
|
|
1733
|
+
}
|
|
1734
|
+
if rewritten.get("status") == "success":
|
|
1735
|
+
normalized_args = rewritten.get("normalized_args")
|
|
1736
|
+
if isinstance(normalized_args, dict):
|
|
1737
|
+
rewritten["suggested_next_call"] = {
|
|
1738
|
+
"tool_name": apply_tool_name,
|
|
1739
|
+
"arguments": {"profile": profile, **normalized_args, "publish": publish},
|
|
1740
|
+
}
|
|
1741
|
+
return rewritten
|
|
742
1742
|
|
|
743
1743
|
|
|
744
|
-
def _validation_failure(
|
|
1744
|
+
def _validation_failure(
|
|
1745
|
+
detail: str,
|
|
1746
|
+
*,
|
|
1747
|
+
tool_name: str | None = None,
|
|
1748
|
+
exc: ValidationError | None = None,
|
|
1749
|
+
suggested_next_call: JSONObject | None = None,
|
|
1750
|
+
) -> JSONObject:
|
|
1751
|
+
contract = _BUILDER_TOOL_CONTRACTS.get(tool_name or "")
|
|
1752
|
+
reason_path = None
|
|
1753
|
+
if exc is not None:
|
|
1754
|
+
errors = exc.errors()
|
|
1755
|
+
if errors:
|
|
1756
|
+
loc = errors[0].get("loc")
|
|
1757
|
+
if isinstance(loc, (tuple, list)):
|
|
1758
|
+
reason_path = ".".join(str(part) for part in loc)
|
|
1759
|
+
canonical_arguments = None
|
|
1760
|
+
if isinstance(suggested_next_call, dict):
|
|
1761
|
+
arguments = suggested_next_call.get("arguments")
|
|
1762
|
+
if isinstance(arguments, dict):
|
|
1763
|
+
canonical_arguments = arguments
|
|
745
1764
|
return {
|
|
746
1765
|
"status": "failed",
|
|
747
1766
|
"error_code": "VALIDATION_ERROR",
|
|
@@ -749,8 +1768,16 @@ def _validation_failure(detail: str, *, suggested_next_call: JSONObject | None =
|
|
|
749
1768
|
"message": detail,
|
|
750
1769
|
"normalized_args": {},
|
|
751
1770
|
"missing_fields": [],
|
|
752
|
-
"allowed_values": {},
|
|
753
|
-
"details": {
|
|
1771
|
+
"allowed_values": deepcopy(contract.get("allowed_values", {})) if isinstance(contract, dict) else {},
|
|
1772
|
+
"details": {
|
|
1773
|
+
"validation_detail": detail,
|
|
1774
|
+
"reason_path": reason_path,
|
|
1775
|
+
"allowed_keys": deepcopy(contract.get("allowed_keys", [])) if isinstance(contract, dict) else [],
|
|
1776
|
+
"canonical_arguments": canonical_arguments,
|
|
1777
|
+
"section_allowed_keys": deepcopy(contract.get("section_allowed_keys", [])) if isinstance(contract, dict) else [],
|
|
1778
|
+
"section_aliases": deepcopy(contract.get("section_aliases", {})) if isinstance(contract, dict) else {},
|
|
1779
|
+
"minimal_section_example": deepcopy(contract.get("minimal_section_example")) if isinstance(contract, dict) else None,
|
|
1780
|
+
},
|
|
754
1781
|
"suggested_next_call": suggested_next_call,
|
|
755
1782
|
"request_id": None,
|
|
756
1783
|
"backend_code": None,
|
|
@@ -760,6 +1787,29 @@ def _validation_failure(detail: str, *, suggested_next_call: JSONObject | None =
|
|
|
760
1787
|
}
|
|
761
1788
|
|
|
762
1789
|
|
|
1790
|
+
def _config_failure(*, tool_name: str, message: str, fix_hint: str) -> JSONObject:
|
|
1791
|
+
contract = _BUILDER_TOOL_CONTRACTS.get(tool_name or "")
|
|
1792
|
+
return {
|
|
1793
|
+
"status": "failed",
|
|
1794
|
+
"error_code": "CONFIG_ERROR",
|
|
1795
|
+
"recoverable": True,
|
|
1796
|
+
"message": message,
|
|
1797
|
+
"normalized_args": {},
|
|
1798
|
+
"missing_fields": [],
|
|
1799
|
+
"allowed_values": deepcopy(contract.get("allowed_values", {})) if isinstance(contract, dict) else {},
|
|
1800
|
+
"details": {
|
|
1801
|
+
"fix_hint": fix_hint,
|
|
1802
|
+
"allowed_keys": deepcopy(contract.get("allowed_keys", [])) if isinstance(contract, dict) else [],
|
|
1803
|
+
},
|
|
1804
|
+
"suggested_next_call": None,
|
|
1805
|
+
"request_id": None,
|
|
1806
|
+
"backend_code": None,
|
|
1807
|
+
"http_status": None,
|
|
1808
|
+
"noop": False,
|
|
1809
|
+
"verification": {},
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
|
|
763
1813
|
def _safe_tool_call(
|
|
764
1814
|
call,
|
|
765
1815
|
*,
|
|
@@ -827,6 +1877,11 @@ def _public_error_message(error_code: str, error: QingflowApiError) -> str:
|
|
|
827
1877
|
"PACKAGE_RESOLVE_FAILED": "package resolution is unavailable in the current route",
|
|
828
1878
|
"PACKAGE_ATTACH_FAILED": "package attachment could not be verified in the current route",
|
|
829
1879
|
"APP_RESOLVE_FAILED": "app resolution is unavailable in the current route",
|
|
1880
|
+
"CUSTOM_BUTTON_LIST_FAILED": "custom button list is unavailable in the current route",
|
|
1881
|
+
"CUSTOM_BUTTON_GET_FAILED": "custom button detail is unavailable in the current route",
|
|
1882
|
+
"CUSTOM_BUTTON_CREATE_FAILED": "custom button create could not complete because the route or readback is unavailable",
|
|
1883
|
+
"CUSTOM_BUTTON_UPDATE_FAILED": "custom button update could not complete because the route or readback is unavailable",
|
|
1884
|
+
"CUSTOM_BUTTON_DELETE_FAILED": "custom button delete could not complete because the route is unavailable",
|
|
830
1885
|
"APP_READ_FAILED": "app base or schema is unavailable in the current route",
|
|
831
1886
|
"FIELDS_READ_FAILED": "app fields are unavailable in the current route",
|
|
832
1887
|
"LAYOUT_READ_FAILED": "layout resource is unavailable for this app in the current route",
|
|
@@ -840,6 +1895,856 @@ def _public_error_message(error_code: str, error: QingflowApiError) -> str:
|
|
|
840
1895
|
"LAYOUT_APPLY_FAILED": "layout apply could not complete because the layout route or readback is unavailable",
|
|
841
1896
|
"FLOW_APPLY_FAILED": "flow apply could not complete because the workflow route or readback is unavailable",
|
|
842
1897
|
"VIEWS_APPLY_FAILED": "views apply could not complete because the views route or readback is unavailable",
|
|
1898
|
+
"CHART_APPLY_FAILED": "chart apply could not complete because the QingBI route or readback is unavailable",
|
|
1899
|
+
"PORTAL_APPLY_FAILED": "portal apply could not complete because the portal route or readback is unavailable",
|
|
843
1900
|
"PUBLISH_VERIFY_FAILED": "publish verification is unavailable in the current route",
|
|
844
1901
|
}
|
|
845
1902
|
return mapping.get(error_code, "requested builder resource is unavailable in the current route")
|
|
1903
|
+
|
|
1904
|
+
|
|
1905
|
+
_BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
1906
|
+
"package_list": {
|
|
1907
|
+
"allowed_keys": ["trial_status"],
|
|
1908
|
+
"aliases": {"trialStatus": "trial_status"},
|
|
1909
|
+
"allowed_values": {"trial_status": ["all", "trial", "formal"]},
|
|
1910
|
+
"minimal_example": {
|
|
1911
|
+
"profile": "default",
|
|
1912
|
+
"trial_status": "all",
|
|
1913
|
+
},
|
|
1914
|
+
},
|
|
1915
|
+
"package_resolve": {
|
|
1916
|
+
"allowed_keys": ["package_name"],
|
|
1917
|
+
"aliases": {"packageName": "package_name"},
|
|
1918
|
+
"allowed_values": {},
|
|
1919
|
+
"minimal_example": {
|
|
1920
|
+
"profile": "default",
|
|
1921
|
+
"package_name": "PLM(备用,施工中)",
|
|
1922
|
+
},
|
|
1923
|
+
},
|
|
1924
|
+
"package_create": {
|
|
1925
|
+
"allowed_keys": ["package_name", "icon", "color"],
|
|
1926
|
+
"aliases": {
|
|
1927
|
+
"packageName": "package_name",
|
|
1928
|
+
"iconName": "icon",
|
|
1929
|
+
"iconColor": "color",
|
|
1930
|
+
},
|
|
1931
|
+
"allowed_values": {},
|
|
1932
|
+
"minimal_example": {
|
|
1933
|
+
"profile": "default",
|
|
1934
|
+
"package_name": "项目管理",
|
|
1935
|
+
"icon": "files-folder",
|
|
1936
|
+
"color": "azure",
|
|
1937
|
+
},
|
|
1938
|
+
},
|
|
1939
|
+
"member_search": {
|
|
1940
|
+
"allowed_keys": ["query", "page_num", "page_size", "contain_disable"],
|
|
1941
|
+
"aliases": {},
|
|
1942
|
+
"allowed_values": {},
|
|
1943
|
+
"minimal_example": {
|
|
1944
|
+
"profile": "default",
|
|
1945
|
+
"query": "严琪东",
|
|
1946
|
+
"page_num": 1,
|
|
1947
|
+
"page_size": 20,
|
|
1948
|
+
"contain_disable": False,
|
|
1949
|
+
},
|
|
1950
|
+
},
|
|
1951
|
+
"role_search": {
|
|
1952
|
+
"allowed_keys": ["keyword", "page_num", "page_size"],
|
|
1953
|
+
"aliases": {},
|
|
1954
|
+
"allowed_values": {},
|
|
1955
|
+
"minimal_example": {
|
|
1956
|
+
"profile": "default",
|
|
1957
|
+
"keyword": "项目经理",
|
|
1958
|
+
"page_num": 1,
|
|
1959
|
+
"page_size": 20,
|
|
1960
|
+
},
|
|
1961
|
+
},
|
|
1962
|
+
"role_create": {
|
|
1963
|
+
"allowed_keys": ["role_name", "member_uids", "member_emails", "member_names", "role_icon"],
|
|
1964
|
+
"aliases": {},
|
|
1965
|
+
"allowed_values": {},
|
|
1966
|
+
"minimal_example": {
|
|
1967
|
+
"profile": "default",
|
|
1968
|
+
"role_name": "研发负责人",
|
|
1969
|
+
"member_names": ["严琪东"],
|
|
1970
|
+
"member_uids": [],
|
|
1971
|
+
"member_emails": [],
|
|
1972
|
+
"role_icon": "ex-user-outlined",
|
|
1973
|
+
},
|
|
1974
|
+
},
|
|
1975
|
+
"package_attach_app": {
|
|
1976
|
+
"allowed_keys": ["tag_id", "app_key"],
|
|
1977
|
+
"aliases": {},
|
|
1978
|
+
"allowed_values": {},
|
|
1979
|
+
"execution_notes": [
|
|
1980
|
+
"attach one existing app to one existing package",
|
|
1981
|
+
"app_title is no longer accepted as a public selector; resolve the app first and pass app_key",
|
|
1982
|
+
],
|
|
1983
|
+
"minimal_example": {
|
|
1984
|
+
"profile": "default",
|
|
1985
|
+
"tag_id": 1001,
|
|
1986
|
+
"app_key": "APP_KEY",
|
|
1987
|
+
},
|
|
1988
|
+
},
|
|
1989
|
+
"app_resolve": {
|
|
1990
|
+
"allowed_keys": ["app_key", "app_name", "package_tag_id"],
|
|
1991
|
+
"aliases": {},
|
|
1992
|
+
"allowed_values": {},
|
|
1993
|
+
"execution_notes": [
|
|
1994
|
+
"use exactly one selector mode",
|
|
1995
|
+
"mode 1: app_key",
|
|
1996
|
+
"mode 2: app_name + package_tag_id",
|
|
1997
|
+
],
|
|
1998
|
+
"minimal_example": {
|
|
1999
|
+
"profile": "default",
|
|
2000
|
+
"app_key": "APP_KEY",
|
|
2001
|
+
},
|
|
2002
|
+
"package_scoped_example": {
|
|
2003
|
+
"profile": "default",
|
|
2004
|
+
"app_name": "研发项目管理",
|
|
2005
|
+
"package_tag_id": 1001,
|
|
2006
|
+
},
|
|
2007
|
+
},
|
|
2008
|
+
"app_custom_button_list": {
|
|
2009
|
+
"allowed_keys": ["app_key"],
|
|
2010
|
+
"aliases": {},
|
|
2011
|
+
"allowed_values": {},
|
|
2012
|
+
"minimal_example": {
|
|
2013
|
+
"profile": "default",
|
|
2014
|
+
"app_key": "APP_KEY",
|
|
2015
|
+
},
|
|
2016
|
+
},
|
|
2017
|
+
"app_custom_button_get": {
|
|
2018
|
+
"allowed_keys": ["app_key", "button_id"],
|
|
2019
|
+
"aliases": {"buttonId": "button_id"},
|
|
2020
|
+
"allowed_values": {},
|
|
2021
|
+
"minimal_example": {
|
|
2022
|
+
"profile": "default",
|
|
2023
|
+
"app_key": "APP_KEY",
|
|
2024
|
+
"button_id": 1001,
|
|
2025
|
+
},
|
|
2026
|
+
},
|
|
2027
|
+
"app_custom_button_create": {
|
|
2028
|
+
"allowed_keys": ["app_key", "payload"],
|
|
2029
|
+
"aliases": {
|
|
2030
|
+
"payload.buttonText": "payload.button_text",
|
|
2031
|
+
"payload.backgroundColor": "payload.background_color",
|
|
2032
|
+
"payload.textColor": "payload.text_color",
|
|
2033
|
+
"payload.buttonIcon": "payload.button_icon",
|
|
2034
|
+
"payload.iconColor": "payload.icon_color",
|
|
2035
|
+
"payload.triggerAction": "payload.trigger_action",
|
|
2036
|
+
"payload.triggerLinkUrl": "payload.trigger_link_url",
|
|
2037
|
+
"payload.triggerAddDataConfig": "payload.trigger_add_data_config",
|
|
2038
|
+
"payload.externalQrobotConfig": "payload.external_qrobot_config",
|
|
2039
|
+
"payload.customButtonExternalQRobotRelationVO": "payload.external_qrobot_config",
|
|
2040
|
+
"payload.triggerWingsConfig": "payload.trigger_wings_config",
|
|
2041
|
+
},
|
|
2042
|
+
"allowed_values": {
|
|
2043
|
+
"payload.trigger_action": [member.value for member in PublicButtonTriggerAction],
|
|
2044
|
+
},
|
|
2045
|
+
"minimal_example": {
|
|
2046
|
+
"profile": "default",
|
|
2047
|
+
"app_key": "APP_KEY",
|
|
2048
|
+
"payload": {
|
|
2049
|
+
"button_text": "新增记录",
|
|
2050
|
+
"background_color": "#FFFFFF",
|
|
2051
|
+
"text_color": "#494F57",
|
|
2052
|
+
"button_icon": "ex-add-outlined",
|
|
2053
|
+
"icon_color": "#494F57",
|
|
2054
|
+
"trigger_action": "link",
|
|
2055
|
+
"trigger_link_url": "https://example.com",
|
|
2056
|
+
},
|
|
2057
|
+
},
|
|
2058
|
+
},
|
|
2059
|
+
"app_custom_button_update": {
|
|
2060
|
+
"allowed_keys": ["app_key", "button_id", "payload"],
|
|
2061
|
+
"aliases": {
|
|
2062
|
+
"buttonId": "button_id",
|
|
2063
|
+
"payload.buttonText": "payload.button_text",
|
|
2064
|
+
"payload.backgroundColor": "payload.background_color",
|
|
2065
|
+
"payload.textColor": "payload.text_color",
|
|
2066
|
+
"payload.buttonIcon": "payload.button_icon",
|
|
2067
|
+
"payload.iconColor": "payload.icon_color",
|
|
2068
|
+
"payload.triggerAction": "payload.trigger_action",
|
|
2069
|
+
"payload.triggerLinkUrl": "payload.trigger_link_url",
|
|
2070
|
+
"payload.triggerAddDataConfig": "payload.trigger_add_data_config",
|
|
2071
|
+
"payload.externalQrobotConfig": "payload.external_qrobot_config",
|
|
2072
|
+
"payload.customButtonExternalQRobotRelationVO": "payload.external_qrobot_config",
|
|
2073
|
+
"payload.triggerWingsConfig": "payload.trigger_wings_config",
|
|
2074
|
+
},
|
|
2075
|
+
"allowed_values": {
|
|
2076
|
+
"payload.trigger_action": [member.value for member in PublicButtonTriggerAction],
|
|
2077
|
+
},
|
|
2078
|
+
"minimal_example": {
|
|
2079
|
+
"profile": "default",
|
|
2080
|
+
"app_key": "APP_KEY",
|
|
2081
|
+
"button_id": 1001,
|
|
2082
|
+
"payload": {
|
|
2083
|
+
"button_text": "查看详情",
|
|
2084
|
+
"background_color": "#FFFFFF",
|
|
2085
|
+
"text_color": "#494F57",
|
|
2086
|
+
"button_icon": "ex-link-outlined",
|
|
2087
|
+
"icon_color": "#494F57",
|
|
2088
|
+
"trigger_action": "link",
|
|
2089
|
+
"trigger_link_url": "https://example.com/detail",
|
|
2090
|
+
},
|
|
2091
|
+
},
|
|
2092
|
+
},
|
|
2093
|
+
"app_custom_button_delete": {
|
|
2094
|
+
"allowed_keys": ["app_key", "button_id"],
|
|
2095
|
+
"aliases": {"buttonId": "button_id"},
|
|
2096
|
+
"allowed_values": {},
|
|
2097
|
+
"minimal_example": {
|
|
2098
|
+
"profile": "default",
|
|
2099
|
+
"app_key": "APP_KEY",
|
|
2100
|
+
"button_id": 1001,
|
|
2101
|
+
},
|
|
2102
|
+
},
|
|
2103
|
+
"app_schema_plan": {
|
|
2104
|
+
"allowed_keys": ["app_key", "package_tag_id", "app_name", "icon", "color", "create_if_missing", "add_fields", "update_fields", "remove_fields"],
|
|
2105
|
+
"aliases": {
|
|
2106
|
+
"app_title": "app_name",
|
|
2107
|
+
"title": "app_name",
|
|
2108
|
+
"field.title": "field.name",
|
|
2109
|
+
"field.label": "field.name",
|
|
2110
|
+
"field.fields": "field.subfields",
|
|
2111
|
+
"field.type_id": "field.type",
|
|
2112
|
+
"field.relationMode": "field.relation_mode",
|
|
2113
|
+
"field.selection_mode": "field.relation_mode",
|
|
2114
|
+
"field.selectionMode": "field.relation_mode",
|
|
2115
|
+
"field.multiple": "field.relation_mode",
|
|
2116
|
+
"field.allow_multiple": "field.relation_mode",
|
|
2117
|
+
"field.optional_data_num": "field.relation_mode",
|
|
2118
|
+
"field.optionalDataNum": "field.relation_mode",
|
|
2119
|
+
"field.remoteLookupConfig": "field.remote_lookup_config",
|
|
2120
|
+
"field.qLinkerBinding": "field.q_linker_binding",
|
|
2121
|
+
"field.codeBlockConfig": "field.code_block_config",
|
|
2122
|
+
"field.codeBlockBinding": "field.code_block_binding",
|
|
2123
|
+
"field.autoTrigger": "field.auto_trigger",
|
|
2124
|
+
"field.customBtnTextStatus": "field.custom_button_text_enabled",
|
|
2125
|
+
"field.customBtnText": "field.custom_button_text",
|
|
2126
|
+
},
|
|
2127
|
+
"allowed_values": {
|
|
2128
|
+
"field.type": [member.value for member in PublicFieldType],
|
|
2129
|
+
"field.relation_mode": [member.value for member in PublicRelationMode],
|
|
2130
|
+
"field_type_ids": sorted(FIELD_TYPE_ID_ALIASES.keys()),
|
|
2131
|
+
},
|
|
2132
|
+
"minimal_example": {
|
|
2133
|
+
"profile": "default",
|
|
2134
|
+
"app_name": "研发项目管理",
|
|
2135
|
+
"package_tag_id": 1001,
|
|
2136
|
+
"icon": "template",
|
|
2137
|
+
"color": "emerald",
|
|
2138
|
+
"create_if_missing": True,
|
|
2139
|
+
"add_fields": [{"name": "项目名称", "type": "text"}],
|
|
2140
|
+
"update_fields": [],
|
|
2141
|
+
"remove_fields": [],
|
|
2142
|
+
},
|
|
2143
|
+
"relation_example": {
|
|
2144
|
+
"profile": "default",
|
|
2145
|
+
"app_key": "APP_ITERATION",
|
|
2146
|
+
"add_fields": [
|
|
2147
|
+
{
|
|
2148
|
+
"name": "需求反馈引用",
|
|
2149
|
+
"type": "relation",
|
|
2150
|
+
"target_app_key": "APP_FEEDBACK",
|
|
2151
|
+
"relation_mode": "multiple",
|
|
2152
|
+
"display_field": {"name": "反馈标题"},
|
|
2153
|
+
"visible_fields": [{"name": "反馈标题"}, {"name": "优先级判断"}],
|
|
2154
|
+
}
|
|
2155
|
+
],
|
|
2156
|
+
"update_fields": [],
|
|
2157
|
+
"remove_fields": [],
|
|
2158
|
+
},
|
|
2159
|
+
},
|
|
2160
|
+
"app_schema_apply": {
|
|
2161
|
+
"allowed_keys": ["app_key", "package_tag_id", "app_name", "icon", "color", "create_if_missing", "publish", "add_fields", "update_fields", "remove_fields"],
|
|
2162
|
+
"aliases": {
|
|
2163
|
+
"app_title": "app_name",
|
|
2164
|
+
"title": "app_name",
|
|
2165
|
+
"field.title": "field.name",
|
|
2166
|
+
"field.label": "field.name",
|
|
2167
|
+
"field.fields": "field.subfields",
|
|
2168
|
+
"field.type_id": "field.type",
|
|
2169
|
+
"field.relationMode": "field.relation_mode",
|
|
2170
|
+
"field.selection_mode": "field.relation_mode",
|
|
2171
|
+
"field.selectionMode": "field.relation_mode",
|
|
2172
|
+
"field.multiple": "field.relation_mode",
|
|
2173
|
+
"field.allow_multiple": "field.relation_mode",
|
|
2174
|
+
"field.optional_data_num": "field.relation_mode",
|
|
2175
|
+
"field.optionalDataNum": "field.relation_mode",
|
|
2176
|
+
"field.remoteLookupConfig": "field.remote_lookup_config",
|
|
2177
|
+
"field.qLinkerBinding": "field.q_linker_binding",
|
|
2178
|
+
"field.codeBlockConfig": "field.code_block_config",
|
|
2179
|
+
"field.codeBlockBinding": "field.code_block_binding",
|
|
2180
|
+
"field.autoTrigger": "field.auto_trigger",
|
|
2181
|
+
"field.customBtnTextStatus": "field.custom_button_text_enabled",
|
|
2182
|
+
"field.customBtnText": "field.custom_button_text",
|
|
2183
|
+
},
|
|
2184
|
+
"allowed_values": {
|
|
2185
|
+
"field.type": [member.value for member in PublicFieldType],
|
|
2186
|
+
"field.relation_mode": [member.value for member in PublicRelationMode],
|
|
2187
|
+
"field.code_block_binding.outputs.target_field.type": list(INTEGRATION_OUTPUT_TARGET_FIELD_TYPES),
|
|
2188
|
+
"field.q_linker_binding.outputs.target_field.type": list(INTEGRATION_OUTPUT_TARGET_FIELD_TYPES),
|
|
2189
|
+
"field_type_ids": sorted(FIELD_TYPE_ID_ALIASES.keys()),
|
|
2190
|
+
},
|
|
2191
|
+
"execution_notes": [
|
|
2192
|
+
"use exactly one resource mode",
|
|
2193
|
+
"edit mode: app_key",
|
|
2194
|
+
"create mode: package_tag_id + app_name + create_if_missing=true",
|
|
2195
|
+
"multiple relation fields are backend-risky; read verification.relation_field_limit_verified and warnings before declaring the schema stable",
|
|
2196
|
+
"backend 49614 is normalized to MULTIPLE_RELATION_FIELDS_UNSUPPORTED with a workaround message",
|
|
2197
|
+
"relation_mode=multiple maps to referenceConfig.optionalDataNum=0",
|
|
2198
|
+
"if relation target metadata lookup is blocked by 40161/40002/40027, explicit display_field.name and visible_fields[].name let builder degrade verification and still continue schema write",
|
|
2199
|
+
"q_linker_binding lets you declare request config, dynamic inputs, alias parsing, and target-field bindings in one step; builder writes remoteLookupConfig plus the existing backend relation-default and questionRelations structures",
|
|
2200
|
+
"code_block_binding lets you declare inputs, code, alias parsing, and target-field bindings in one step; builder writes codeBlockConfig plus the existing backend relation-default and questionRelations structures",
|
|
2201
|
+
"builder configures code blocks only; it does not execute or trigger code blocks",
|
|
2202
|
+
"code_block_binding and q_linker_binding target fields are limited to text, long_text, number, amount, date, datetime, single_select, multi_select, and boolean",
|
|
2203
|
+
],
|
|
2204
|
+
"minimal_example": {
|
|
2205
|
+
"profile": "default",
|
|
2206
|
+
"app_name": "研发项目管理",
|
|
2207
|
+
"package_tag_id": 1001,
|
|
2208
|
+
"icon": "template",
|
|
2209
|
+
"color": "emerald",
|
|
2210
|
+
"create_if_missing": True,
|
|
2211
|
+
"publish": True,
|
|
2212
|
+
"add_fields": [{"name": "项目名称", "type": "text"}],
|
|
2213
|
+
"update_fields": [],
|
|
2214
|
+
"remove_fields": [],
|
|
2215
|
+
},
|
|
2216
|
+
"relation_example": {
|
|
2217
|
+
"profile": "default",
|
|
2218
|
+
"app_key": "APP_ITERATION",
|
|
2219
|
+
"publish": True,
|
|
2220
|
+
"add_fields": [
|
|
2221
|
+
{
|
|
2222
|
+
"name": "需求反馈引用",
|
|
2223
|
+
"type": "relation",
|
|
2224
|
+
"target_app_key": "APP_FEEDBACK",
|
|
2225
|
+
"relation_mode": "multiple",
|
|
2226
|
+
"display_field": {"name": "反馈标题"},
|
|
2227
|
+
"visible_fields": [{"name": "反馈标题"}, {"name": "优先级判断"}],
|
|
2228
|
+
}
|
|
2229
|
+
],
|
|
2230
|
+
"update_fields": [],
|
|
2231
|
+
"remove_fields": [],
|
|
2232
|
+
},
|
|
2233
|
+
"code_block_example": {
|
|
2234
|
+
"profile": "default",
|
|
2235
|
+
"app_key": "APP_SCRIPT",
|
|
2236
|
+
"publish": True,
|
|
2237
|
+
"add_fields": [
|
|
2238
|
+
{"name": "客户等级", "type": "text"},
|
|
2239
|
+
{
|
|
2240
|
+
"name": "查询代码块",
|
|
2241
|
+
"type": "code_block",
|
|
2242
|
+
"code_block_binding": {
|
|
2243
|
+
"inputs": [
|
|
2244
|
+
{"field": {"name": "客户名称"}, "var": "customerName"},
|
|
2245
|
+
{"field": {"name": "预算金额"}, "var": "budget"},
|
|
2246
|
+
],
|
|
2247
|
+
"code": "qf_output = {}; qf_output.customerLevel = budget > 100000 ? 'A' : 'B';",
|
|
2248
|
+
"auto_trigger": True,
|
|
2249
|
+
"custom_button_text_enabled": True,
|
|
2250
|
+
"custom_button_text": "评估客户",
|
|
2251
|
+
"outputs": [
|
|
2252
|
+
{"alias": "customerLevel", "path": "$.customerLevel", "target_field": {"name": "客户等级"}},
|
|
2253
|
+
],
|
|
2254
|
+
},
|
|
2255
|
+
}
|
|
2256
|
+
],
|
|
2257
|
+
"update_fields": [],
|
|
2258
|
+
"remove_fields": [],
|
|
2259
|
+
},
|
|
2260
|
+
"q_linker_example": {
|
|
2261
|
+
"profile": "default",
|
|
2262
|
+
"app_key": "APP_CUSTOMER",
|
|
2263
|
+
"publish": True,
|
|
2264
|
+
"add_fields": [
|
|
2265
|
+
{"name": "客户名称", "type": "text"},
|
|
2266
|
+
{"name": "企业名称", "type": "text"},
|
|
2267
|
+
{"name": "统一社会信用代码", "type": "text"},
|
|
2268
|
+
{
|
|
2269
|
+
"name": "企业信息查询",
|
|
2270
|
+
"type": "q_linker",
|
|
2271
|
+
"q_linker_binding": {
|
|
2272
|
+
"inputs": [
|
|
2273
|
+
{"field": {"name": "客户名称"}, "key": "keyword", "source": "query_param"},
|
|
2274
|
+
],
|
|
2275
|
+
"request": {
|
|
2276
|
+
"url": "https://example.com/company/search",
|
|
2277
|
+
"method": "GET",
|
|
2278
|
+
"headers": [],
|
|
2279
|
+
"query_params": [],
|
|
2280
|
+
"body_type": 1,
|
|
2281
|
+
"url_encoded_value": [],
|
|
2282
|
+
"result_type": 1,
|
|
2283
|
+
"auto_trigger": True,
|
|
2284
|
+
"custom_button_text_enabled": True,
|
|
2285
|
+
"custom_button_text": "查询企业信息",
|
|
2286
|
+
},
|
|
2287
|
+
"outputs": [
|
|
2288
|
+
{"alias": "company_name", "path": "$.data.name", "target_field": {"name": "企业名称"}},
|
|
2289
|
+
{"alias": "credit_code", "path": "$.data.creditCode", "target_field": {"name": "统一社会信用代码"}},
|
|
2290
|
+
],
|
|
2291
|
+
},
|
|
2292
|
+
},
|
|
2293
|
+
],
|
|
2294
|
+
"update_fields": [],
|
|
2295
|
+
"remove_fields": [],
|
|
2296
|
+
},
|
|
2297
|
+
},
|
|
2298
|
+
"app_layout_plan": {
|
|
2299
|
+
"allowed_keys": ["app_key", "mode", "sections", "preset"],
|
|
2300
|
+
"aliases": {"overwrite": "replace", "sectionId": "section_id"},
|
|
2301
|
+
"section_allowed_keys": ["type", "paragraph_id", "section_id", "title", "rows"],
|
|
2302
|
+
"section_aliases": {
|
|
2303
|
+
"name": "title",
|
|
2304
|
+
"paragraphId": "paragraph_id",
|
|
2305
|
+
"sectionId": "section_id",
|
|
2306
|
+
"fields": "rows",
|
|
2307
|
+
"field_ids": "rows",
|
|
2308
|
+
"columns": "rows_chunk_size",
|
|
2309
|
+
},
|
|
2310
|
+
"allowed_values": {"mode": [member.value for member in LayoutApplyMode], "preset": [member.value for member in LayoutPreset]},
|
|
2311
|
+
"minimal_section_example": {"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["字段A", "字段B", "字段C", "字段D"]]},
|
|
2312
|
+
"minimal_example": {
|
|
2313
|
+
"profile": "default",
|
|
2314
|
+
"app_key": "APP_KEY",
|
|
2315
|
+
"mode": "merge",
|
|
2316
|
+
"sections": [{"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["字段A", "字段B", "字段C", "字段D"]]}],
|
|
2317
|
+
},
|
|
2318
|
+
"preset_example": {"profile": "default", "app_key": "APP_KEY", "mode": "merge", "preset": "balanced", "sections": []},
|
|
2319
|
+
},
|
|
2320
|
+
"app_layout_apply": {
|
|
2321
|
+
"allowed_keys": ["app_key", "mode", "publish", "sections"],
|
|
2322
|
+
"aliases": {"overwrite": "replace", "sectionId": "section_id"},
|
|
2323
|
+
"section_allowed_keys": ["type", "paragraph_id", "section_id", "title", "rows"],
|
|
2324
|
+
"section_aliases": {
|
|
2325
|
+
"name": "title",
|
|
2326
|
+
"paragraphId": "paragraph_id",
|
|
2327
|
+
"sectionId": "section_id",
|
|
2328
|
+
"fields": "rows",
|
|
2329
|
+
"field_ids": "rows",
|
|
2330
|
+
"columns": "rows_chunk_size",
|
|
2331
|
+
},
|
|
2332
|
+
"allowed_values": {"mode": [member.value for member in LayoutApplyMode]},
|
|
2333
|
+
"execution_notes": [
|
|
2334
|
+
"layout verification is split into layout_verified and layout_summary_verified",
|
|
2335
|
+
"LAYOUT_SUMMARY_UNVERIFIED means raw form readback is stronger than the compact summary",
|
|
2336
|
+
],
|
|
2337
|
+
"minimal_section_example": {"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["字段A", "字段B", "字段C", "字段D"]]},
|
|
2338
|
+
"minimal_example": {
|
|
2339
|
+
"profile": "default",
|
|
2340
|
+
"app_key": "APP_KEY",
|
|
2341
|
+
"mode": "merge",
|
|
2342
|
+
"publish": True,
|
|
2343
|
+
"sections": [{"type": "paragraph", "paragraph_id": "basic", "title": "基础信息", "rows": [["项目名称", "项目负责人", "项目阶段", "优先级"]]}],
|
|
2344
|
+
},
|
|
2345
|
+
},
|
|
2346
|
+
"app_flow_plan": {
|
|
2347
|
+
"allowed_keys": ["app_key", "mode", "nodes", "transitions", "preset"],
|
|
2348
|
+
"aliases": {
|
|
2349
|
+
"overwrite": "replace",
|
|
2350
|
+
"base_preset": "preset",
|
|
2351
|
+
"default_approval": "basic_approval",
|
|
2352
|
+
"node.role_names": "node.assignees.role_names",
|
|
2353
|
+
"node.role_ids": "node.assignees.role_ids",
|
|
2354
|
+
"node.member_names": "node.assignees.member_names",
|
|
2355
|
+
"node.member_emails": "node.assignees.member_emails",
|
|
2356
|
+
"node.member_uids": "node.assignees.member_uids",
|
|
2357
|
+
"node.editable_fields": "node.permissions.editable_fields",
|
|
2358
|
+
"default_approval": "basic_approval",
|
|
2359
|
+
},
|
|
2360
|
+
"allowed_values": {
|
|
2361
|
+
"mode": ["replace"],
|
|
2362
|
+
"preset": [member.value for member in FlowPreset],
|
|
2363
|
+
"node.type": PUBLIC_STABLE_FLOW_NODE_TYPES,
|
|
2364
|
+
},
|
|
2365
|
+
"dependency_hints": [
|
|
2366
|
+
"approval-style workflows require an explicit status field",
|
|
2367
|
+
"approve/fill/copy nodes require at least one assignee",
|
|
2368
|
+
],
|
|
2369
|
+
"execution_notes": [
|
|
2370
|
+
"public flow building is intentionally limited to linear workflows",
|
|
2371
|
+
"branch and condition nodes are disabled because the backend workflow route is not front-end stable for these node types",
|
|
2372
|
+
],
|
|
2373
|
+
"minimal_example": {
|
|
2374
|
+
"profile": "default",
|
|
2375
|
+
"app_key": "APP_KEY",
|
|
2376
|
+
"mode": "replace",
|
|
2377
|
+
"preset": "basic_approval",
|
|
2378
|
+
"nodes": [
|
|
2379
|
+
{
|
|
2380
|
+
"id": "approve_1",
|
|
2381
|
+
"type": "approve",
|
|
2382
|
+
"name": "部门审批",
|
|
2383
|
+
"assignees": {"role_names": ["项目经理"]},
|
|
2384
|
+
"permissions": {"editable_fields": ["状态", "审批意见"]},
|
|
2385
|
+
}
|
|
2386
|
+
],
|
|
2387
|
+
"transitions": [],
|
|
2388
|
+
},
|
|
2389
|
+
},
|
|
2390
|
+
"app_flow_apply": {
|
|
2391
|
+
"allowed_keys": ["app_key", "mode", "publish", "nodes", "transitions"],
|
|
2392
|
+
"aliases": {
|
|
2393
|
+
"overwrite": "replace",
|
|
2394
|
+
"node.role_names": "node.assignees.role_names",
|
|
2395
|
+
"node.role_ids": "node.assignees.role_ids",
|
|
2396
|
+
"node.member_names": "node.assignees.member_names",
|
|
2397
|
+
"node.member_emails": "node.assignees.member_emails",
|
|
2398
|
+
"node.member_uids": "node.assignees.member_uids",
|
|
2399
|
+
"node.editable_fields": "node.permissions.editable_fields",
|
|
2400
|
+
},
|
|
2401
|
+
"allowed_values": {
|
|
2402
|
+
"mode": ["replace"],
|
|
2403
|
+
"node.type": PUBLIC_STABLE_FLOW_NODE_TYPES,
|
|
2404
|
+
},
|
|
2405
|
+
"dependency_hints": [
|
|
2406
|
+
"approval-style workflows require an explicit status field",
|
|
2407
|
+
"approve/fill/copy nodes require at least one assignee",
|
|
2408
|
+
],
|
|
2409
|
+
"execution_notes": [
|
|
2410
|
+
"public flow building is intentionally limited to linear workflows",
|
|
2411
|
+
"branch and condition nodes are disabled because the backend workflow route is not front-end stable for these node types",
|
|
2412
|
+
"workflow verification only covers linear node structure in the public tool surface",
|
|
2413
|
+
],
|
|
2414
|
+
"minimal_example": {
|
|
2415
|
+
"profile": "default",
|
|
2416
|
+
"app_key": "APP_KEY",
|
|
2417
|
+
"mode": "replace",
|
|
2418
|
+
"publish": True,
|
|
2419
|
+
"nodes": [
|
|
2420
|
+
{"id": "start", "type": "start", "name": "发起"},
|
|
2421
|
+
{
|
|
2422
|
+
"id": "approve_1",
|
|
2423
|
+
"type": "approve",
|
|
2424
|
+
"name": "部门审批",
|
|
2425
|
+
"assignees": {"role_names": ["项目经理"]},
|
|
2426
|
+
"permissions": {"editable_fields": ["状态", "审批意见"]},
|
|
2427
|
+
},
|
|
2428
|
+
{"id": "end", "type": "end", "name": "结束"},
|
|
2429
|
+
],
|
|
2430
|
+
"transitions": [{"from": "start", "to": "approve_1"}, {"from": "approve_1", "to": "end"}],
|
|
2431
|
+
},
|
|
2432
|
+
},
|
|
2433
|
+
"app_views_plan": {
|
|
2434
|
+
"allowed_keys": ["app_key", "upsert_views", "remove_views", "preset", "upsert_views[].view_key", "upsert_views[].buttons"],
|
|
2435
|
+
"aliases": {
|
|
2436
|
+
"fields": "columns",
|
|
2437
|
+
"column_names": "columns",
|
|
2438
|
+
"columnNames": "columns",
|
|
2439
|
+
"viewKey": "view_key",
|
|
2440
|
+
"tableView": "table",
|
|
2441
|
+
"cardView": "card",
|
|
2442
|
+
"kanban": "board",
|
|
2443
|
+
"filter_rules": "filters",
|
|
2444
|
+
"filterRules": "filters",
|
|
2445
|
+
"startField": "start_field",
|
|
2446
|
+
"endField": "end_field",
|
|
2447
|
+
"titleField": "title_field",
|
|
2448
|
+
"buttons[].buttonType": "buttons[].button_type",
|
|
2449
|
+
"buttons[].configType": "buttons[].config_type",
|
|
2450
|
+
"buttons[].buttonId": "buttons[].button_id",
|
|
2451
|
+
"buttons[].beingMain": "buttons[].being_main",
|
|
2452
|
+
"buttons[].buttonLimit": "buttons[].button_limit",
|
|
2453
|
+
"buttons[].buttonFormula": "buttons[].button_formula",
|
|
2454
|
+
"buttons[].buttonFormulaType": "buttons[].button_formula_type",
|
|
2455
|
+
"buttons[].printTpls": "buttons[].print_tpls",
|
|
2456
|
+
},
|
|
2457
|
+
"allowed_values": {
|
|
2458
|
+
"preset": [member.value for member in ViewsPreset],
|
|
2459
|
+
"view.type": [member.value for member in PublicViewType],
|
|
2460
|
+
"view.filter.operator": [member.value for member in ViewFilterOperator],
|
|
2461
|
+
"view.buttons.button_type": ["SYSTEM", "CUSTOM"],
|
|
2462
|
+
"view.buttons.config_type": ["TOP", "DETAIL"],
|
|
2463
|
+
},
|
|
2464
|
+
"minimal_example": {
|
|
2465
|
+
"profile": "default",
|
|
2466
|
+
"app_key": "APP_KEY",
|
|
2467
|
+
"upsert_views": [{"name": "全部数据", "type": "table", "columns": ["项目名称"]}],
|
|
2468
|
+
"remove_views": [],
|
|
2469
|
+
},
|
|
2470
|
+
"gantt_example": {
|
|
2471
|
+
"profile": "default",
|
|
2472
|
+
"app_key": "APP_KEY",
|
|
2473
|
+
"upsert_views": [
|
|
2474
|
+
{
|
|
2475
|
+
"name": "项目甘特图",
|
|
2476
|
+
"type": "gantt",
|
|
2477
|
+
"columns": ["项目名称", "开始日期", "结束日期"],
|
|
2478
|
+
"start_field": "开始日期",
|
|
2479
|
+
"end_field": "结束日期",
|
|
2480
|
+
"title_field": "项目名称",
|
|
2481
|
+
"filters": [{"field_name": "状态", "operator": "eq", "value": "进行中"}],
|
|
2482
|
+
}
|
|
2483
|
+
],
|
|
2484
|
+
"remove_views": [],
|
|
2485
|
+
},
|
|
2486
|
+
},
|
|
2487
|
+
"app_views_apply": {
|
|
2488
|
+
"allowed_keys": ["app_key", "publish", "upsert_views", "remove_views", "upsert_views[].view_key", "upsert_views[].buttons"],
|
|
2489
|
+
"aliases": {
|
|
2490
|
+
"fields": "columns",
|
|
2491
|
+
"column_names": "columns",
|
|
2492
|
+
"columnNames": "columns",
|
|
2493
|
+
"viewKey": "view_key",
|
|
2494
|
+
"tableView": "table",
|
|
2495
|
+
"cardView": "card",
|
|
2496
|
+
"kanban": "board",
|
|
2497
|
+
"filter_rules": "filters",
|
|
2498
|
+
"filterRules": "filters",
|
|
2499
|
+
"startField": "start_field",
|
|
2500
|
+
"endField": "end_field",
|
|
2501
|
+
"titleField": "title_field",
|
|
2502
|
+
"buttons[].buttonType": "buttons[].button_type",
|
|
2503
|
+
"buttons[].configType": "buttons[].config_type",
|
|
2504
|
+
"buttons[].buttonId": "buttons[].button_id",
|
|
2505
|
+
"buttons[].beingMain": "buttons[].being_main",
|
|
2506
|
+
"buttons[].buttonLimit": "buttons[].button_limit",
|
|
2507
|
+
"buttons[].buttonFormula": "buttons[].button_formula",
|
|
2508
|
+
"buttons[].buttonFormulaType": "buttons[].button_formula_type",
|
|
2509
|
+
"buttons[].printTpls": "buttons[].print_tpls",
|
|
2510
|
+
},
|
|
2511
|
+
"allowed_values": {
|
|
2512
|
+
"view.type": [member.value for member in PublicViewType],
|
|
2513
|
+
"view.filter.operator": [member.value for member in ViewFilterOperator],
|
|
2514
|
+
"view.buttons.button_type": ["SYSTEM", "CUSTOM"],
|
|
2515
|
+
"view.buttons.config_type": ["TOP", "DETAIL"],
|
|
2516
|
+
},
|
|
2517
|
+
"execution_notes": [
|
|
2518
|
+
"apply may return partial_success when some views land and others fail",
|
|
2519
|
+
"when duplicate view names exist, supply view_key to target the exact view",
|
|
2520
|
+
"read back app_read_views_summary after any failed or partial view apply",
|
|
2521
|
+
"view existence verification and saved-filter verification are separate; treat filters as unverified until verification.view_filters_verified is true",
|
|
2522
|
+
"buttons omitted preserves existing button config; buttons=[] clears all buttons; buttons=[...] replaces the full button config",
|
|
2523
|
+
],
|
|
2524
|
+
"minimal_example": {
|
|
2525
|
+
"profile": "default",
|
|
2526
|
+
"app_key": "APP_KEY",
|
|
2527
|
+
"publish": True,
|
|
2528
|
+
"upsert_views": [{"name": "全部数据", "type": "table", "columns": ["项目名称"]}],
|
|
2529
|
+
"remove_views": [],
|
|
2530
|
+
},
|
|
2531
|
+
"gantt_example": {
|
|
2532
|
+
"profile": "default",
|
|
2533
|
+
"app_key": "APP_KEY",
|
|
2534
|
+
"publish": True,
|
|
2535
|
+
"upsert_views": [
|
|
2536
|
+
{
|
|
2537
|
+
"name": "项目甘特图",
|
|
2538
|
+
"type": "gantt",
|
|
2539
|
+
"columns": ["项目名称", "开始日期", "结束日期"],
|
|
2540
|
+
"start_field": "开始日期",
|
|
2541
|
+
"end_field": "结束日期",
|
|
2542
|
+
"title_field": "项目名称",
|
|
2543
|
+
"filters": [{"field_name": "状态", "operator": "eq", "value": "进行中"}],
|
|
2544
|
+
}
|
|
2545
|
+
],
|
|
2546
|
+
"remove_views": [],
|
|
2547
|
+
},
|
|
2548
|
+
},
|
|
2549
|
+
"app_read_charts_summary": {
|
|
2550
|
+
"allowed_keys": ["app_key"],
|
|
2551
|
+
"aliases": {},
|
|
2552
|
+
"allowed_values": {},
|
|
2553
|
+
"execution_notes": [
|
|
2554
|
+
"returns a compact current chart inventory for one app",
|
|
2555
|
+
"use this before app_charts_apply when you need exact current chart_id values",
|
|
2556
|
+
"chart summaries do not include full qingbi config payloads",
|
|
2557
|
+
],
|
|
2558
|
+
"minimal_example": {
|
|
2559
|
+
"profile": "default",
|
|
2560
|
+
"app_key": "APP_KEY",
|
|
2561
|
+
},
|
|
2562
|
+
},
|
|
2563
|
+
"portal_list": {
|
|
2564
|
+
"allowed_keys": [],
|
|
2565
|
+
"aliases": {},
|
|
2566
|
+
"allowed_values": {},
|
|
2567
|
+
"execution_notes": [
|
|
2568
|
+
"returns the current user's accessible portal list",
|
|
2569
|
+
"use this as the portal discovery path before portal_get",
|
|
2570
|
+
"results are compact list items, not raw dash payloads",
|
|
2571
|
+
],
|
|
2572
|
+
"minimal_example": {
|
|
2573
|
+
"profile": "default",
|
|
2574
|
+
},
|
|
2575
|
+
},
|
|
2576
|
+
"portal_get": {
|
|
2577
|
+
"allowed_keys": ["dash_key", "being_draft"],
|
|
2578
|
+
"aliases": {"beingDraft": "being_draft"},
|
|
2579
|
+
"allowed_values": {},
|
|
2580
|
+
"execution_notes": [
|
|
2581
|
+
"returns portal-level detail plus a component inventory",
|
|
2582
|
+
"chart and view components are returned as refs only; use chart_get or view_get for more detail",
|
|
2583
|
+
"being_draft=true reads the current draft view; being_draft=false reads live",
|
|
2584
|
+
],
|
|
2585
|
+
"minimal_example": {
|
|
2586
|
+
"profile": "default",
|
|
2587
|
+
"dash_key": "DASH_KEY",
|
|
2588
|
+
"being_draft": True,
|
|
2589
|
+
},
|
|
2590
|
+
},
|
|
2591
|
+
"app_charts_apply": {
|
|
2592
|
+
"allowed_keys": ["app_key", "upsert_charts", "remove_chart_ids", "reorder_chart_ids"],
|
|
2593
|
+
"aliases": {
|
|
2594
|
+
"chart.id": "chart.chart_id",
|
|
2595
|
+
"chart.type": "chart.chart_type",
|
|
2596
|
+
"chart.dimension_fields": "chart.dimension_field_ids",
|
|
2597
|
+
"chart.indicator_fields": "chart.indicator_field_ids",
|
|
2598
|
+
"chart.metric_field_ids": "chart.indicator_field_ids",
|
|
2599
|
+
"chart.filter.op": "chart.filter.operator",
|
|
2600
|
+
},
|
|
2601
|
+
"allowed_values": {
|
|
2602
|
+
"chart.chart_type": [member.value for member in PublicChartType],
|
|
2603
|
+
"chart.filter.operator": [member.value for member in ViewFilterOperator],
|
|
2604
|
+
},
|
|
2605
|
+
"execution_notes": [
|
|
2606
|
+
"app_charts_apply is immediate-live and does not publish",
|
|
2607
|
+
"chart matching precedence is chart_id first, then exact unique chart name",
|
|
2608
|
+
"when chart names are not unique, supply chart_id instead of guessing by name",
|
|
2609
|
+
"successful create results must return a real backend chart_id",
|
|
2610
|
+
],
|
|
2611
|
+
"minimal_example": {
|
|
2612
|
+
"profile": "default",
|
|
2613
|
+
"app_key": "APP_KEY",
|
|
2614
|
+
"upsert_charts": [{"name": "数据总量", "chart_type": "target", "indicator_field_ids": []}],
|
|
2615
|
+
"remove_chart_ids": [],
|
|
2616
|
+
"reorder_chart_ids": [],
|
|
2617
|
+
},
|
|
2618
|
+
},
|
|
2619
|
+
"chart_apply": {
|
|
2620
|
+
"allowed_keys": ["app_key", "upsert_charts", "remove_chart_ids", "reorder_chart_ids"],
|
|
2621
|
+
"aliases": {
|
|
2622
|
+
"legacy_tool_name": "app_charts_apply",
|
|
2623
|
+
},
|
|
2624
|
+
"allowed_values": {
|
|
2625
|
+
"chart.chart_type": [member.value for member in PublicChartType],
|
|
2626
|
+
"chart.filter.operator": [member.value for member in ViewFilterOperator],
|
|
2627
|
+
},
|
|
2628
|
+
"execution_notes": [
|
|
2629
|
+
"legacy compatibility alias; prefer app_charts_apply in new builder flows",
|
|
2630
|
+
"behavior matches app_charts_apply exactly",
|
|
2631
|
+
],
|
|
2632
|
+
"minimal_example": {
|
|
2633
|
+
"profile": "default",
|
|
2634
|
+
"app_key": "APP_KEY",
|
|
2635
|
+
"upsert_charts": [{"name": "数据总量", "chart_type": "target", "indicator_field_ids": []}],
|
|
2636
|
+
"remove_chart_ids": [],
|
|
2637
|
+
"reorder_chart_ids": [],
|
|
2638
|
+
},
|
|
2639
|
+
},
|
|
2640
|
+
"portal_read_summary": {
|
|
2641
|
+
"allowed_keys": ["dash_key", "being_draft"],
|
|
2642
|
+
"aliases": {"beingDraft": "being_draft"},
|
|
2643
|
+
"allowed_values": {},
|
|
2644
|
+
"execution_notes": [
|
|
2645
|
+
"returns a compact portal summary instead of the raw dash payload",
|
|
2646
|
+
"being_draft=true reads the current draft view; being_draft=false reads live",
|
|
2647
|
+
"use this before portal_apply when you need the current section inventory or target dash metadata",
|
|
2648
|
+
],
|
|
2649
|
+
"minimal_example": {
|
|
2650
|
+
"profile": "default",
|
|
2651
|
+
"dash_key": "DASH_KEY",
|
|
2652
|
+
"being_draft": True,
|
|
2653
|
+
},
|
|
2654
|
+
},
|
|
2655
|
+
"view_get": {
|
|
2656
|
+
"allowed_keys": ["viewgraph_key"],
|
|
2657
|
+
"aliases": {"viewKey": "viewgraph_key"},
|
|
2658
|
+
"allowed_values": {},
|
|
2659
|
+
"execution_notes": [
|
|
2660
|
+
"returns one view's definition detail",
|
|
2661
|
+
"does not return record data; use record_list with app_key + view_id for rows",
|
|
2662
|
+
"use this after portal_get when a component references a view_ref.view_key",
|
|
2663
|
+
],
|
|
2664
|
+
"minimal_example": {
|
|
2665
|
+
"profile": "default",
|
|
2666
|
+
"viewgraph_key": "VIEW_KEY",
|
|
2667
|
+
},
|
|
2668
|
+
},
|
|
2669
|
+
"chart_get": {
|
|
2670
|
+
"allowed_keys": ["chart_id", "data_payload", "page_num", "page_size", "page_num_y", "page_size_y"],
|
|
2671
|
+
"aliases": {"payload": "data_payload"},
|
|
2672
|
+
"allowed_values": {},
|
|
2673
|
+
"execution_notes": [
|
|
2674
|
+
"returns chart base info, chart config, and chart data together",
|
|
2675
|
+
"chart_id is required; chart names are not accepted here",
|
|
2676
|
+
"data_payload defaults to {} so chart_get queries concrete chart data by default",
|
|
2677
|
+
],
|
|
2678
|
+
"minimal_example": {
|
|
2679
|
+
"profile": "default",
|
|
2680
|
+
"chart_id": "CHART_ID",
|
|
2681
|
+
"data_payload": {},
|
|
2682
|
+
},
|
|
2683
|
+
},
|
|
2684
|
+
"portal_apply": {
|
|
2685
|
+
"allowed_keys": ["dash_key", "dash_name", "package_tag_id", "publish", "sections", "auth", "icon", "color", "hide_copyright", "dash_global_config", "config"],
|
|
2686
|
+
"aliases": {
|
|
2687
|
+
"sourceType": "source_type",
|
|
2688
|
+
"chartRef": "chart_ref",
|
|
2689
|
+
"viewRef": "view_ref",
|
|
2690
|
+
"dashStyleConfigBO": "dash_style_config",
|
|
2691
|
+
},
|
|
2692
|
+
"section_allowed_keys": ["title", "source_type", "position", "dash_style_config", "config", "chart_ref", "view_ref", "text", "url"],
|
|
2693
|
+
"section_aliases": {
|
|
2694
|
+
"sourceType": "source_type",
|
|
2695
|
+
"chartRef": "chart_ref",
|
|
2696
|
+
"viewRef": "view_ref",
|
|
2697
|
+
"dashStyleConfigBO": "dash_style_config",
|
|
2698
|
+
},
|
|
2699
|
+
"allowed_values": {"section.source_type": ["chart", "view", "grid", "filter", "text", "link"]},
|
|
2700
|
+
"execution_notes": [
|
|
2701
|
+
"use exactly one resource mode",
|
|
2702
|
+
"update mode: dash_key",
|
|
2703
|
+
"create mode: package_tag_id + dash_name",
|
|
2704
|
+
"portal_apply uses replace semantics for sections",
|
|
2705
|
+
"remove a section by omitting it from the new sections list",
|
|
2706
|
+
"package_tag_id is required when creating a new portal",
|
|
2707
|
+
"publish=false only guarantees draft and base-info updates; it does not claim live has changed",
|
|
2708
|
+
"chart_ref resolves by chart_id first, then exact unique chart_name",
|
|
2709
|
+
"view_ref resolves by view_key first, then exact unique view_name",
|
|
2710
|
+
"position.pc/mobile is the canonical portal layout shape",
|
|
2711
|
+
],
|
|
2712
|
+
"minimal_example": {
|
|
2713
|
+
"profile": "default",
|
|
2714
|
+
"dash_name": "经营门户",
|
|
2715
|
+
"package_tag_id": 1001,
|
|
2716
|
+
"publish": True,
|
|
2717
|
+
"sections": [
|
|
2718
|
+
{
|
|
2719
|
+
"title": "经营概览",
|
|
2720
|
+
"source_type": "chart",
|
|
2721
|
+
"chart_ref": {"app_key": "APP_KEY", "chart_name": "数据总量"},
|
|
2722
|
+
"position": {
|
|
2723
|
+
"pc": {"x": 0, "y": 0, "cols": 12, "rows": 6},
|
|
2724
|
+
"mobile": {"x": 0, "y": 0, "cols": 6, "rows": 6},
|
|
2725
|
+
},
|
|
2726
|
+
}
|
|
2727
|
+
],
|
|
2728
|
+
},
|
|
2729
|
+
"minimal_section_example": {
|
|
2730
|
+
"title": "订单概览",
|
|
2731
|
+
"source_type": "view",
|
|
2732
|
+
"view_ref": {"app_key": "APP_KEY", "view_key": "VIEW_KEY"},
|
|
2733
|
+
"position": {
|
|
2734
|
+
"pc": {"x": 0, "y": 0, "cols": 24, "rows": 8},
|
|
2735
|
+
"mobile": {"x": 0, "y": 0, "cols": 6, "rows": 8},
|
|
2736
|
+
},
|
|
2737
|
+
},
|
|
2738
|
+
},
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
_PRIVATE_BUILDER_TOOL_CONTRACTS = {
|
|
2742
|
+
"app_schema_plan",
|
|
2743
|
+
"app_layout_plan",
|
|
2744
|
+
"app_flow_plan",
|
|
2745
|
+
"app_views_plan",
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
_BUILDER_TOOL_CONTRACT_ALIASES = {
|
|
2749
|
+
"chart_apply": "app_charts_apply",
|
|
2750
|
+
}
|