@josephyan/qingflow-cli 0.2.0-beta.55
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 +30 -0
- package/docs/local-agent-install.md +235 -0
- package/entry_point.py +13 -0
- package/npm/bin/qingflow.mjs +5 -0
- package/npm/lib/runtime.mjs +204 -0
- package/npm/scripts/postinstall.mjs +16 -0
- package/package.json +34 -0
- package/pyproject.toml +67 -0
- package/qingflow +15 -0
- package/src/qingflow_mcp/__init__.py +5 -0
- package/src/qingflow_mcp/__main__.py +5 -0
- package/src/qingflow_mcp/backend_client.py +547 -0
- package/src/qingflow_mcp/builder_facade/__init__.py +3 -0
- package/src/qingflow_mcp/builder_facade/models.py +985 -0
- package/src/qingflow_mcp/builder_facade/service.py +8243 -0
- 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 +184 -0
- package/src/qingflow_mcp/cli/commands/common.py +47 -0
- package/src/qingflow_mcp/cli/commands/imports.py +86 -0
- package/src/qingflow_mcp/cli/commands/record.py +202 -0
- package/src/qingflow_mcp/cli/commands/task.py +87 -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 +269 -0
- package/src/qingflow_mcp/cli/json_io.py +50 -0
- package/src/qingflow_mcp/cli/main.py +147 -0
- package/src/qingflow_mcp/config.py +221 -0
- package/src/qingflow_mcp/errors.py +66 -0
- package/src/qingflow_mcp/import_store.py +121 -0
- package/src/qingflow_mcp/json_types.py +18 -0
- package/src/qingflow_mcp/list_type_labels.py +76 -0
- package/src/qingflow_mcp/server.py +211 -0
- package/src/qingflow_mcp/server_app_builder.py +387 -0
- package/src/qingflow_mcp/server_app_user.py +317 -0
- package/src/qingflow_mcp/session_store.py +289 -0
- package/src/qingflow_mcp/solution/__init__.py +6 -0
- package/src/qingflow_mcp/solution/build_assembly_store.py +181 -0
- package/src/qingflow_mcp/solution/compiler/__init__.py +282 -0
- package/src/qingflow_mcp/solution/compiler/chart_compiler.py +96 -0
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +466 -0
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +113 -0
- package/src/qingflow_mcp/solution/compiler/navigation_compiler.py +57 -0
- package/src/qingflow_mcp/solution/compiler/package_compiler.py +19 -0
- package/src/qingflow_mcp/solution/compiler/portal_compiler.py +60 -0
- package/src/qingflow_mcp/solution/compiler/view_compiler.py +51 -0
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +173 -0
- package/src/qingflow_mcp/solution/design_session.py +222 -0
- package/src/qingflow_mcp/solution/design_store.py +100 -0
- package/src/qingflow_mcp/solution/executor.py +2339 -0
- package/src/qingflow_mcp/solution/normalizer.py +23 -0
- package/src/qingflow_mcp/solution/requirements_builder.py +536 -0
- package/src/qingflow_mcp/solution/run_store.py +244 -0
- package/src/qingflow_mcp/solution/spec_models.py +853 -0
- package/src/qingflow_mcp/tools/__init__.py +1 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +2063 -0
- package/src/qingflow_mcp/tools/app_tools.py +850 -0
- package/src/qingflow_mcp/tools/approval_tools.py +833 -0
- package/src/qingflow_mcp/tools/auth_tools.py +697 -0
- package/src/qingflow_mcp/tools/base.py +81 -0
- package/src/qingflow_mcp/tools/code_block_tools.py +679 -0
- package/src/qingflow_mcp/tools/directory_tools.py +648 -0
- package/src/qingflow_mcp/tools/feedback_tools.py +230 -0
- package/src/qingflow_mcp/tools/file_tools.py +385 -0
- package/src/qingflow_mcp/tools/import_tools.py +1971 -0
- package/src/qingflow_mcp/tools/navigation_tools.py +177 -0
- package/src/qingflow_mcp/tools/package_tools.py +240 -0
- package/src/qingflow_mcp/tools/portal_tools.py +131 -0
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +269 -0
- package/src/qingflow_mcp/tools/record_tools.py +12739 -0
- package/src/qingflow_mcp/tools/role_tools.py +94 -0
- package/src/qingflow_mcp/tools/solution_tools.py +3887 -0
- package/src/qingflow_mcp/tools/task_context_tools.py +1423 -0
- package/src/qingflow_mcp/tools/task_tools.py +843 -0
- package/src/qingflow_mcp/tools/view_tools.py +280 -0
- package/src/qingflow_mcp/tools/workflow_tools.py +312 -0
- package/src/qingflow_mcp/tools/workspace_tools.py +219 -0
|
@@ -0,0 +1,2063 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
import json
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from pydantic import ValidationError
|
|
8
|
+
|
|
9
|
+
from ..config import DEFAULT_PROFILE
|
|
10
|
+
from ..errors import QingflowApiError
|
|
11
|
+
from ..json_types import JSONObject
|
|
12
|
+
from ..builder_facade.models import (
|
|
13
|
+
ChartApplyRequest,
|
|
14
|
+
FIELD_TYPE_ID_ALIASES,
|
|
15
|
+
FieldPatch,
|
|
16
|
+
FieldRemovePatch,
|
|
17
|
+
FieldUpdatePatch,
|
|
18
|
+
FlowPreset,
|
|
19
|
+
FlowNodePatch,
|
|
20
|
+
FlowPlanRequest,
|
|
21
|
+
FlowTransitionPatch,
|
|
22
|
+
LayoutApplyMode,
|
|
23
|
+
LayoutPlanRequest,
|
|
24
|
+
LayoutPreset,
|
|
25
|
+
LayoutSectionPatch,
|
|
26
|
+
PortalApplyRequest,
|
|
27
|
+
PublicFieldType,
|
|
28
|
+
PublicRelationMode,
|
|
29
|
+
PublicChartType,
|
|
30
|
+
PublicViewType,
|
|
31
|
+
SchemaPlanRequest,
|
|
32
|
+
ViewFilterOperator,
|
|
33
|
+
ViewUpsertPatch,
|
|
34
|
+
ViewsPreset,
|
|
35
|
+
ViewsPlanRequest,
|
|
36
|
+
)
|
|
37
|
+
from ..builder_facade.service import AiBuilderFacade
|
|
38
|
+
from .app_tools import AppTools
|
|
39
|
+
from .base import ToolBase
|
|
40
|
+
from .directory_tools import DirectoryTools
|
|
41
|
+
from .package_tools import PackageTools
|
|
42
|
+
from .portal_tools import PortalTools
|
|
43
|
+
from .qingbi_report_tools import QingbiReportTools
|
|
44
|
+
from .role_tools import RoleTools
|
|
45
|
+
from .solution_tools import SolutionTools
|
|
46
|
+
from .view_tools import ViewTools
|
|
47
|
+
from .workflow_tools import WorkflowTools
|
|
48
|
+
|
|
49
|
+
PUBLIC_STABLE_FLOW_NODE_TYPES = ["start", "approve", "fill", "copy", "webhook", "end"]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class AiBuilderTools(ToolBase):
|
|
53
|
+
def __init__(self, sessions, backend) -> None:
|
|
54
|
+
super().__init__(sessions, backend)
|
|
55
|
+
self._facade = AiBuilderFacade(
|
|
56
|
+
apps=AppTools(sessions, backend),
|
|
57
|
+
packages=PackageTools(sessions, backend),
|
|
58
|
+
views=ViewTools(sessions, backend),
|
|
59
|
+
workflows=WorkflowTools(sessions, backend),
|
|
60
|
+
portals=PortalTools(sessions, backend),
|
|
61
|
+
charts=QingbiReportTools(sessions, backend),
|
|
62
|
+
roles=RoleTools(sessions, backend),
|
|
63
|
+
directory=DirectoryTools(sessions, backend),
|
|
64
|
+
solutions=SolutionTools(sessions, backend),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def register(self, mcp) -> None:
|
|
68
|
+
@mcp.tool()
|
|
69
|
+
def package_list(profile: str = DEFAULT_PROFILE, trial_status: str = "all") -> JSONObject:
|
|
70
|
+
return self.package_list(profile=profile, trial_status=trial_status)
|
|
71
|
+
|
|
72
|
+
@mcp.tool()
|
|
73
|
+
def package_resolve(profile: str = DEFAULT_PROFILE, package_name: str = "") -> JSONObject:
|
|
74
|
+
return self.package_resolve(profile=profile, package_name=package_name)
|
|
75
|
+
|
|
76
|
+
@mcp.tool()
|
|
77
|
+
def builder_tool_contract(tool_name: str = "") -> JSONObject:
|
|
78
|
+
return self.builder_tool_contract(tool_name=tool_name)
|
|
79
|
+
|
|
80
|
+
@mcp.tool()
|
|
81
|
+
def package_create(profile: str = DEFAULT_PROFILE, package_name: str = "") -> JSONObject:
|
|
82
|
+
return self.package_create(profile=profile, package_name=package_name)
|
|
83
|
+
|
|
84
|
+
@mcp.tool()
|
|
85
|
+
def member_search(
|
|
86
|
+
profile: str = DEFAULT_PROFILE,
|
|
87
|
+
query: str = "",
|
|
88
|
+
page_num: int = 1,
|
|
89
|
+
page_size: int = 20,
|
|
90
|
+
contain_disable: bool = False,
|
|
91
|
+
) -> JSONObject:
|
|
92
|
+
return self.member_search(
|
|
93
|
+
profile=profile,
|
|
94
|
+
query=query,
|
|
95
|
+
page_num=page_num,
|
|
96
|
+
page_size=page_size,
|
|
97
|
+
contain_disable=contain_disable,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@mcp.tool()
|
|
101
|
+
def role_search(
|
|
102
|
+
profile: str = DEFAULT_PROFILE,
|
|
103
|
+
keyword: str = "",
|
|
104
|
+
page_num: int = 1,
|
|
105
|
+
page_size: int = 20,
|
|
106
|
+
) -> JSONObject:
|
|
107
|
+
return self.role_search(profile=profile, keyword=keyword, page_num=page_num, page_size=page_size)
|
|
108
|
+
|
|
109
|
+
@mcp.tool()
|
|
110
|
+
def role_create(
|
|
111
|
+
profile: str = DEFAULT_PROFILE,
|
|
112
|
+
role_name: str = "",
|
|
113
|
+
member_uids: list[int] | None = None,
|
|
114
|
+
member_emails: list[str] | None = None,
|
|
115
|
+
member_names: list[str] | None = None,
|
|
116
|
+
role_icon: str = "ex-user-outlined",
|
|
117
|
+
) -> JSONObject:
|
|
118
|
+
return self.role_create(
|
|
119
|
+
profile=profile,
|
|
120
|
+
role_name=role_name,
|
|
121
|
+
member_uids=member_uids or [],
|
|
122
|
+
member_emails=member_emails or [],
|
|
123
|
+
member_names=member_names or [],
|
|
124
|
+
role_icon=role_icon,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
@mcp.tool()
|
|
128
|
+
def package_attach_app(
|
|
129
|
+
profile: str = DEFAULT_PROFILE,
|
|
130
|
+
tag_id: int = 0,
|
|
131
|
+
app_key: str = "",
|
|
132
|
+
app_title: str = "",
|
|
133
|
+
) -> JSONObject:
|
|
134
|
+
return self.package_attach_app(profile=profile, tag_id=tag_id, app_key=app_key, app_title=app_title)
|
|
135
|
+
|
|
136
|
+
@mcp.tool()
|
|
137
|
+
def app_release_edit_lock_if_mine(
|
|
138
|
+
profile: str = DEFAULT_PROFILE,
|
|
139
|
+
app_key: str = "",
|
|
140
|
+
lock_owner_email: str = "",
|
|
141
|
+
lock_owner_name: str = "",
|
|
142
|
+
) -> JSONObject:
|
|
143
|
+
return self.app_release_edit_lock_if_mine(
|
|
144
|
+
profile=profile,
|
|
145
|
+
app_key=app_key,
|
|
146
|
+
lock_owner_email=lock_owner_email,
|
|
147
|
+
lock_owner_name=lock_owner_name,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
@mcp.tool()
|
|
151
|
+
def app_resolve(
|
|
152
|
+
profile: str = DEFAULT_PROFILE,
|
|
153
|
+
app_key: str = "",
|
|
154
|
+
app_name: str = "",
|
|
155
|
+
package_tag_id: int | None = None,
|
|
156
|
+
) -> JSONObject:
|
|
157
|
+
return self.app_resolve(profile=profile, app_key=app_key, app_name=app_name, package_tag_id=package_tag_id)
|
|
158
|
+
|
|
159
|
+
@mcp.tool()
|
|
160
|
+
def app_read_summary(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
161
|
+
return self.app_read_summary(profile=profile, app_key=app_key)
|
|
162
|
+
|
|
163
|
+
@mcp.tool()
|
|
164
|
+
def app_read_fields(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
165
|
+
return self.app_read_fields(profile=profile, app_key=app_key)
|
|
166
|
+
|
|
167
|
+
@mcp.tool()
|
|
168
|
+
def app_read_layout_summary(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
169
|
+
return self.app_read_layout_summary(profile=profile, app_key=app_key)
|
|
170
|
+
|
|
171
|
+
@mcp.tool()
|
|
172
|
+
def app_read_views_summary(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
173
|
+
return self.app_read_views_summary(profile=profile, app_key=app_key)
|
|
174
|
+
|
|
175
|
+
@mcp.tool()
|
|
176
|
+
def app_read_flow_summary(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
177
|
+
return self.app_read_flow_summary(profile=profile, app_key=app_key)
|
|
178
|
+
|
|
179
|
+
@mcp.tool()
|
|
180
|
+
def app_read_charts_summary(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
|
|
181
|
+
return self.app_read_charts_summary(profile=profile, app_key=app_key)
|
|
182
|
+
|
|
183
|
+
@mcp.tool()
|
|
184
|
+
def portal_read_summary(
|
|
185
|
+
profile: str = DEFAULT_PROFILE,
|
|
186
|
+
dash_key: str = "",
|
|
187
|
+
being_draft: bool = True,
|
|
188
|
+
) -> JSONObject:
|
|
189
|
+
return self.portal_read_summary(profile=profile, dash_key=dash_key, being_draft=being_draft)
|
|
190
|
+
|
|
191
|
+
@mcp.tool()
|
|
192
|
+
def app_schema_apply(
|
|
193
|
+
profile: str = DEFAULT_PROFILE,
|
|
194
|
+
app_key: str = "",
|
|
195
|
+
package_tag_id: int | None = None,
|
|
196
|
+
app_name: str = "",
|
|
197
|
+
app_title: str = "",
|
|
198
|
+
create_if_missing: bool = False,
|
|
199
|
+
publish: bool = True,
|
|
200
|
+
add_fields: list[JSONObject] | None = None,
|
|
201
|
+
update_fields: list[JSONObject] | None = None,
|
|
202
|
+
remove_fields: list[JSONObject] | None = None,
|
|
203
|
+
) -> JSONObject:
|
|
204
|
+
return self.app_schema_apply(
|
|
205
|
+
profile=profile,
|
|
206
|
+
app_key=app_key,
|
|
207
|
+
package_tag_id=package_tag_id,
|
|
208
|
+
app_name=app_name,
|
|
209
|
+
app_title=app_title,
|
|
210
|
+
create_if_missing=create_if_missing,
|
|
211
|
+
publish=publish,
|
|
212
|
+
add_fields=add_fields or [],
|
|
213
|
+
update_fields=update_fields or [],
|
|
214
|
+
remove_fields=remove_fields or [],
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
@mcp.tool()
|
|
218
|
+
def app_layout_apply(
|
|
219
|
+
profile: str = DEFAULT_PROFILE,
|
|
220
|
+
app_key: str = "",
|
|
221
|
+
mode: str = "merge",
|
|
222
|
+
publish: bool = True,
|
|
223
|
+
sections: list[JSONObject] | None = None,
|
|
224
|
+
) -> JSONObject:
|
|
225
|
+
return self.app_layout_apply(profile=profile, app_key=app_key, mode=mode, publish=publish, sections=sections or [])
|
|
226
|
+
|
|
227
|
+
@mcp.tool()
|
|
228
|
+
def app_flow_apply(
|
|
229
|
+
profile: str = DEFAULT_PROFILE,
|
|
230
|
+
app_key: str = "",
|
|
231
|
+
mode: str = "replace",
|
|
232
|
+
publish: bool = True,
|
|
233
|
+
nodes: list[JSONObject] | None = None,
|
|
234
|
+
transitions: list[JSONObject] | None = None,
|
|
235
|
+
) -> JSONObject:
|
|
236
|
+
return self.app_flow_apply(
|
|
237
|
+
profile=profile,
|
|
238
|
+
app_key=app_key,
|
|
239
|
+
mode=mode,
|
|
240
|
+
publish=publish,
|
|
241
|
+
nodes=nodes or [],
|
|
242
|
+
transitions=transitions or [],
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
@mcp.tool()
|
|
246
|
+
def app_views_apply(
|
|
247
|
+
profile: str = DEFAULT_PROFILE,
|
|
248
|
+
app_key: str = "",
|
|
249
|
+
publish: bool = True,
|
|
250
|
+
upsert_views: list[JSONObject] | None = None,
|
|
251
|
+
remove_views: list[str] | None = None,
|
|
252
|
+
) -> JSONObject:
|
|
253
|
+
return self.app_views_apply(
|
|
254
|
+
profile=profile,
|
|
255
|
+
app_key=app_key,
|
|
256
|
+
publish=publish,
|
|
257
|
+
upsert_views=upsert_views or [],
|
|
258
|
+
remove_views=remove_views or [],
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
@mcp.tool()
|
|
262
|
+
def app_charts_apply(
|
|
263
|
+
profile: str = DEFAULT_PROFILE,
|
|
264
|
+
app_key: str = "",
|
|
265
|
+
upsert_charts: list[JSONObject] | None = None,
|
|
266
|
+
remove_chart_ids: list[str] | None = None,
|
|
267
|
+
reorder_chart_ids: list[str] | None = None,
|
|
268
|
+
) -> JSONObject:
|
|
269
|
+
return self.app_charts_apply(
|
|
270
|
+
profile=profile,
|
|
271
|
+
app_key=app_key,
|
|
272
|
+
upsert_charts=upsert_charts or [],
|
|
273
|
+
remove_chart_ids=remove_chart_ids or [],
|
|
274
|
+
reorder_chart_ids=reorder_chart_ids or [],
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
@mcp.tool()
|
|
278
|
+
def portal_apply(
|
|
279
|
+
profile: str = DEFAULT_PROFILE,
|
|
280
|
+
dash_key: str = "",
|
|
281
|
+
dash_name: str = "",
|
|
282
|
+
package_tag_id: int | None = None,
|
|
283
|
+
publish: bool = True,
|
|
284
|
+
sections: list[JSONObject] | None = None,
|
|
285
|
+
auth: JSONObject | None = None,
|
|
286
|
+
icon: str | None = None,
|
|
287
|
+
color: str | None = None,
|
|
288
|
+
hide_copyright: bool | None = None,
|
|
289
|
+
dash_global_config: JSONObject | None = None,
|
|
290
|
+
config: JSONObject | None = None,
|
|
291
|
+
) -> JSONObject:
|
|
292
|
+
return self.portal_apply(
|
|
293
|
+
profile=profile,
|
|
294
|
+
dash_key=dash_key,
|
|
295
|
+
dash_name=dash_name,
|
|
296
|
+
package_tag_id=package_tag_id,
|
|
297
|
+
publish=publish,
|
|
298
|
+
sections=sections or [],
|
|
299
|
+
auth=auth,
|
|
300
|
+
icon=icon,
|
|
301
|
+
color=color,
|
|
302
|
+
hide_copyright=hide_copyright,
|
|
303
|
+
dash_global_config=dash_global_config,
|
|
304
|
+
config=config or {},
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
@mcp.tool()
|
|
308
|
+
def app_publish_verify(
|
|
309
|
+
profile: str = DEFAULT_PROFILE,
|
|
310
|
+
app_key: str = "",
|
|
311
|
+
expected_package_tag_id: int | None = None,
|
|
312
|
+
) -> JSONObject:
|
|
313
|
+
return self.app_publish_verify(
|
|
314
|
+
profile=profile,
|
|
315
|
+
app_key=app_key,
|
|
316
|
+
expected_package_tag_id=expected_package_tag_id,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
def package_list(self, *, profile: str, trial_status: str = "all") -> JSONObject:
|
|
320
|
+
normalized_args = {"trial_status": trial_status}
|
|
321
|
+
return _safe_tool_call(
|
|
322
|
+
lambda: self._facade.package_list(profile=profile, trial_status=trial_status),
|
|
323
|
+
error_code="PACKAGE_LIST_FAILED",
|
|
324
|
+
normalized_args=normalized_args,
|
|
325
|
+
suggested_next_call={"tool_name": "package_list", "arguments": {"profile": profile, "trial_status": trial_status}},
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
def package_resolve(self, *, profile: str, package_name: str) -> JSONObject:
|
|
329
|
+
normalized_args = {"package_name": package_name}
|
|
330
|
+
return _safe_tool_call(
|
|
331
|
+
lambda: self._facade.package_resolve(profile=profile, package_name=package_name),
|
|
332
|
+
error_code="PACKAGE_RESOLVE_FAILED",
|
|
333
|
+
normalized_args=normalized_args,
|
|
334
|
+
suggested_next_call={"tool_name": "package_resolve", "arguments": {"profile": profile, "package_name": package_name}},
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
def builder_tool_contract(self, *, tool_name: str) -> JSONObject:
|
|
338
|
+
requested = str(tool_name or "").strip()
|
|
339
|
+
public_tool_names = sorted(
|
|
340
|
+
name
|
|
341
|
+
for name in _BUILDER_TOOL_CONTRACTS.keys()
|
|
342
|
+
if name not in _PRIVATE_BUILDER_TOOL_CONTRACTS and name not in _BUILDER_TOOL_CONTRACT_ALIASES
|
|
343
|
+
)
|
|
344
|
+
if requested in _PRIVATE_BUILDER_TOOL_CONTRACTS:
|
|
345
|
+
lookup_name = ""
|
|
346
|
+
elif requested in _BUILDER_TOOL_CONTRACTS:
|
|
347
|
+
lookup_name = requested
|
|
348
|
+
else:
|
|
349
|
+
lookup_name = _BUILDER_TOOL_CONTRACT_ALIASES.get(requested, requested)
|
|
350
|
+
contract = _BUILDER_TOOL_CONTRACTS.get(lookup_name)
|
|
351
|
+
if contract is None:
|
|
352
|
+
return {
|
|
353
|
+
"status": "failed",
|
|
354
|
+
"error_code": "TOOL_CONTRACT_NOT_FOUND",
|
|
355
|
+
"recoverable": True,
|
|
356
|
+
"message": "tool contract is not defined for the requested public builder tool",
|
|
357
|
+
"normalized_args": {"tool_name": requested},
|
|
358
|
+
"missing_fields": [],
|
|
359
|
+
"allowed_values": {"tool_name": public_tool_names},
|
|
360
|
+
"details": {"reason_path": "tool_name"},
|
|
361
|
+
"suggested_next_call": None,
|
|
362
|
+
"request_id": None,
|
|
363
|
+
"backend_code": None,
|
|
364
|
+
"http_status": None,
|
|
365
|
+
"noop": False,
|
|
366
|
+
"warnings": [],
|
|
367
|
+
"verification": {},
|
|
368
|
+
"verified": False,
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
"status": "success",
|
|
372
|
+
"error_code": None,
|
|
373
|
+
"recoverable": False,
|
|
374
|
+
"message": "loaded builder tool contract",
|
|
375
|
+
"normalized_args": {"tool_name": requested},
|
|
376
|
+
"missing_fields": [],
|
|
377
|
+
"allowed_values": {},
|
|
378
|
+
"details": {},
|
|
379
|
+
"suggested_next_call": None,
|
|
380
|
+
"request_id": None,
|
|
381
|
+
"backend_code": None,
|
|
382
|
+
"http_status": None,
|
|
383
|
+
"noop": False,
|
|
384
|
+
"warnings": [],
|
|
385
|
+
"verification": {},
|
|
386
|
+
"verified": True,
|
|
387
|
+
"tool_name": requested,
|
|
388
|
+
"contract": contract,
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
def package_create(self, *, profile: str, package_name: str) -> JSONObject:
|
|
392
|
+
normalized_args = {"package_name": package_name}
|
|
393
|
+
return _safe_tool_call(
|
|
394
|
+
lambda: self._facade.package_create(profile=profile, package_name=package_name),
|
|
395
|
+
error_code="PACKAGE_CREATE_FAILED",
|
|
396
|
+
normalized_args=normalized_args,
|
|
397
|
+
suggested_next_call={"tool_name": "package_create", "arguments": {"profile": profile, "package_name": package_name}},
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
def member_search(
|
|
401
|
+
self,
|
|
402
|
+
*,
|
|
403
|
+
profile: str,
|
|
404
|
+
query: str,
|
|
405
|
+
page_num: int = 1,
|
|
406
|
+
page_size: int = 20,
|
|
407
|
+
contain_disable: bool = False,
|
|
408
|
+
) -> JSONObject:
|
|
409
|
+
normalized_args = {
|
|
410
|
+
"query": query,
|
|
411
|
+
"page_num": page_num,
|
|
412
|
+
"page_size": page_size,
|
|
413
|
+
"contain_disable": contain_disable,
|
|
414
|
+
}
|
|
415
|
+
return _safe_tool_call(
|
|
416
|
+
lambda: self._facade.member_search(
|
|
417
|
+
profile=profile,
|
|
418
|
+
query=query,
|
|
419
|
+
page_num=page_num,
|
|
420
|
+
page_size=page_size,
|
|
421
|
+
contain_disable=contain_disable,
|
|
422
|
+
),
|
|
423
|
+
error_code="MEMBER_SEARCH_FAILED",
|
|
424
|
+
normalized_args=normalized_args,
|
|
425
|
+
suggested_next_call={"tool_name": "member_search", "arguments": {"profile": profile, **normalized_args}},
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
def role_search(self, *, profile: str, keyword: str, page_num: int = 1, page_size: int = 20) -> JSONObject:
|
|
429
|
+
normalized_args = {"keyword": keyword, "page_num": page_num, "page_size": page_size}
|
|
430
|
+
return _safe_tool_call(
|
|
431
|
+
lambda: self._facade.role_search(profile=profile, keyword=keyword, page_num=page_num, page_size=page_size),
|
|
432
|
+
error_code="ROLE_SEARCH_FAILED",
|
|
433
|
+
normalized_args=normalized_args,
|
|
434
|
+
suggested_next_call={"tool_name": "role_search", "arguments": {"profile": profile, **normalized_args}},
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
def role_create(
|
|
438
|
+
self,
|
|
439
|
+
*,
|
|
440
|
+
profile: str,
|
|
441
|
+
role_name: str,
|
|
442
|
+
member_uids: list[int],
|
|
443
|
+
member_emails: list[str],
|
|
444
|
+
member_names: list[str],
|
|
445
|
+
role_icon: str = "ex-user-outlined",
|
|
446
|
+
) -> JSONObject:
|
|
447
|
+
normalized_args = {
|
|
448
|
+
"role_name": role_name,
|
|
449
|
+
"member_uids": member_uids,
|
|
450
|
+
"member_emails": member_emails,
|
|
451
|
+
"member_names": member_names,
|
|
452
|
+
"role_icon": role_icon,
|
|
453
|
+
}
|
|
454
|
+
return _safe_tool_call(
|
|
455
|
+
lambda: self._facade.role_create(
|
|
456
|
+
profile=profile,
|
|
457
|
+
role_name=role_name,
|
|
458
|
+
member_uids=member_uids,
|
|
459
|
+
member_emails=member_emails,
|
|
460
|
+
member_names=member_names,
|
|
461
|
+
role_icon=role_icon,
|
|
462
|
+
),
|
|
463
|
+
error_code="ROLE_CREATE_FAILED",
|
|
464
|
+
normalized_args=normalized_args,
|
|
465
|
+
suggested_next_call={"tool_name": "role_create", "arguments": {"profile": profile, **normalized_args}},
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
def package_attach_app(self, *, profile: str, tag_id: int, app_key: str, app_title: str = "") -> JSONObject:
|
|
469
|
+
normalized_args = {"tag_id": tag_id, "app_key": app_key, "app_title": app_title}
|
|
470
|
+
result = _safe_tool_call(
|
|
471
|
+
lambda: self._facade.package_attach_app(profile=profile, tag_id=tag_id, app_key=app_key, app_title=app_title),
|
|
472
|
+
error_code="PACKAGE_ATTACH_FAILED",
|
|
473
|
+
normalized_args=normalized_args,
|
|
474
|
+
suggested_next_call={"tool_name": "package_attach_app", "arguments": {"profile": profile, **normalized_args}},
|
|
475
|
+
)
|
|
476
|
+
return self._retry_after_self_lock_release(
|
|
477
|
+
profile=profile,
|
|
478
|
+
result=result,
|
|
479
|
+
retry_call=lambda: self._facade.package_attach_app(
|
|
480
|
+
profile=profile,
|
|
481
|
+
tag_id=tag_id,
|
|
482
|
+
app_key=app_key,
|
|
483
|
+
app_title=app_title,
|
|
484
|
+
),
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
def app_release_edit_lock_if_mine(
|
|
488
|
+
self,
|
|
489
|
+
*,
|
|
490
|
+
profile: str,
|
|
491
|
+
app_key: str,
|
|
492
|
+
lock_owner_email: str = "",
|
|
493
|
+
lock_owner_name: str = "",
|
|
494
|
+
) -> JSONObject:
|
|
495
|
+
normalized_args = {
|
|
496
|
+
"app_key": app_key,
|
|
497
|
+
"lock_owner_email": lock_owner_email,
|
|
498
|
+
"lock_owner_name": lock_owner_name,
|
|
499
|
+
}
|
|
500
|
+
return _safe_tool_call(
|
|
501
|
+
lambda: self._facade.app_release_edit_lock_if_mine(
|
|
502
|
+
profile=profile,
|
|
503
|
+
app_key=app_key,
|
|
504
|
+
lock_owner_email=lock_owner_email,
|
|
505
|
+
lock_owner_name=lock_owner_name,
|
|
506
|
+
),
|
|
507
|
+
error_code="EDIT_LOCK_RELEASE_FAILED",
|
|
508
|
+
normalized_args=normalized_args,
|
|
509
|
+
suggested_next_call={"tool_name": "app_release_edit_lock_if_mine", "arguments": {"profile": profile, **normalized_args}},
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
def app_resolve(self, *, profile: str, app_key: str = "", app_name: str = "", package_tag_id: int | None = None) -> JSONObject:
|
|
513
|
+
normalized_args = {"app_key": app_key, "app_name": app_name, "package_tag_id": package_tag_id}
|
|
514
|
+
return _safe_tool_call(
|
|
515
|
+
lambda: self._facade.app_resolve(profile=profile, app_key=app_key, app_name=app_name, package_tag_id=package_tag_id),
|
|
516
|
+
error_code="APP_RESOLVE_FAILED",
|
|
517
|
+
normalized_args=normalized_args,
|
|
518
|
+
suggested_next_call={"tool_name": "app_resolve", "arguments": {"profile": profile, **normalized_args}},
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
def app_read_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
522
|
+
normalized_args = {"app_key": app_key}
|
|
523
|
+
return _safe_tool_call(
|
|
524
|
+
lambda: self._facade.app_read_summary(profile=profile, app_key=app_key),
|
|
525
|
+
error_code="APP_READ_FAILED",
|
|
526
|
+
normalized_args=normalized_args,
|
|
527
|
+
suggested_next_call={"tool_name": "app_read_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
def app_read_fields(self, *, profile: str, app_key: str) -> JSONObject:
|
|
531
|
+
normalized_args = {"app_key": app_key}
|
|
532
|
+
return _safe_tool_call(
|
|
533
|
+
lambda: self._facade.app_read_fields(profile=profile, app_key=app_key),
|
|
534
|
+
error_code="FIELDS_READ_FAILED",
|
|
535
|
+
normalized_args=normalized_args,
|
|
536
|
+
suggested_next_call={"tool_name": "app_read_fields", "arguments": {"profile": profile, "app_key": app_key}},
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
def app_read_layout_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
540
|
+
normalized_args = {"app_key": app_key}
|
|
541
|
+
return _safe_tool_call(
|
|
542
|
+
lambda: self._facade.app_read_layout_summary(profile=profile, app_key=app_key),
|
|
543
|
+
error_code="LAYOUT_READ_FAILED",
|
|
544
|
+
normalized_args=normalized_args,
|
|
545
|
+
suggested_next_call={"tool_name": "app_read_layout_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
def app_read_views_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
549
|
+
normalized_args = {"app_key": app_key}
|
|
550
|
+
return _safe_tool_call(
|
|
551
|
+
lambda: self._facade.app_read_views_summary(profile=profile, app_key=app_key),
|
|
552
|
+
error_code="VIEWS_READ_FAILED",
|
|
553
|
+
normalized_args=normalized_args,
|
|
554
|
+
suggested_next_call={"tool_name": "app_read_views_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
def app_read_flow_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
558
|
+
normalized_args = {"app_key": app_key}
|
|
559
|
+
return _safe_tool_call(
|
|
560
|
+
lambda: self._facade.app_read_flow_summary(profile=profile, app_key=app_key),
|
|
561
|
+
error_code="FLOW_READ_FAILED",
|
|
562
|
+
normalized_args=normalized_args,
|
|
563
|
+
suggested_next_call={"tool_name": "app_read_flow_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
def app_read_charts_summary(self, *, profile: str, app_key: str) -> JSONObject:
|
|
567
|
+
normalized_args = {"app_key": app_key}
|
|
568
|
+
return _safe_tool_call(
|
|
569
|
+
lambda: self._facade.app_read_charts_summary(profile=profile, app_key=app_key),
|
|
570
|
+
error_code="CHARTS_READ_FAILED",
|
|
571
|
+
normalized_args=normalized_args,
|
|
572
|
+
suggested_next_call={"tool_name": "app_read_charts_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
def portal_read_summary(self, *, profile: str, dash_key: str, being_draft: bool = True) -> JSONObject:
|
|
576
|
+
normalized_args = {"dash_key": dash_key, "being_draft": being_draft}
|
|
577
|
+
return _safe_tool_call(
|
|
578
|
+
lambda: self._facade.portal_read_summary(profile=profile, dash_key=dash_key, being_draft=being_draft),
|
|
579
|
+
error_code="PORTAL_READ_FAILED",
|
|
580
|
+
normalized_args=normalized_args,
|
|
581
|
+
suggested_next_call={"tool_name": "portal_read_summary", "arguments": {"profile": profile, **normalized_args}},
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
def app_schema_plan(
|
|
585
|
+
self,
|
|
586
|
+
*,
|
|
587
|
+
profile: str,
|
|
588
|
+
app_key: str = "",
|
|
589
|
+
package_tag_id: int | None = None,
|
|
590
|
+
app_name: str = "",
|
|
591
|
+
create_if_missing: bool = False,
|
|
592
|
+
add_fields: list[JSONObject],
|
|
593
|
+
update_fields: list[JSONObject],
|
|
594
|
+
remove_fields: list[JSONObject],
|
|
595
|
+
) -> JSONObject:
|
|
596
|
+
try:
|
|
597
|
+
request = SchemaPlanRequest.model_validate(
|
|
598
|
+
{
|
|
599
|
+
"app_key": app_key,
|
|
600
|
+
"package_tag_id": package_tag_id,
|
|
601
|
+
"app_name": app_name,
|
|
602
|
+
"create_if_missing": create_if_missing,
|
|
603
|
+
"add_fields": add_fields,
|
|
604
|
+
"update_fields": update_fields,
|
|
605
|
+
"remove_fields": remove_fields,
|
|
606
|
+
}
|
|
607
|
+
)
|
|
608
|
+
except ValidationError as exc:
|
|
609
|
+
return _validation_failure(
|
|
610
|
+
str(exc),
|
|
611
|
+
tool_name="app_schema_plan",
|
|
612
|
+
exc=exc,
|
|
613
|
+
suggested_next_call={
|
|
614
|
+
"tool_name": "app_schema_plan",
|
|
615
|
+
"arguments": {
|
|
616
|
+
"profile": profile,
|
|
617
|
+
"app_key": app_key,
|
|
618
|
+
"package_tag_id": package_tag_id,
|
|
619
|
+
"app_name": app_name,
|
|
620
|
+
"create_if_missing": create_if_missing,
|
|
621
|
+
"add_fields": [{"name": "字段名称", "type": "text"}],
|
|
622
|
+
"update_fields": [],
|
|
623
|
+
"remove_fields": [],
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
)
|
|
627
|
+
return _safe_tool_call(
|
|
628
|
+
lambda: self._facade.app_schema_plan(profile=profile, request=request),
|
|
629
|
+
error_code="SCHEMA_PLAN_FAILED",
|
|
630
|
+
normalized_args=request.model_dump(mode="json"),
|
|
631
|
+
suggested_next_call={"tool_name": "app_schema_plan", "arguments": {"profile": profile, **request.model_dump(mode="json")}},
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
def app_layout_plan(
|
|
635
|
+
self,
|
|
636
|
+
*,
|
|
637
|
+
profile: str,
|
|
638
|
+
app_key: str,
|
|
639
|
+
mode: str = "merge",
|
|
640
|
+
sections: list[JSONObject] | None = None,
|
|
641
|
+
preset: str | None = None,
|
|
642
|
+
) -> JSONObject:
|
|
643
|
+
try:
|
|
644
|
+
request = LayoutPlanRequest.model_validate(
|
|
645
|
+
{
|
|
646
|
+
"app_key": app_key,
|
|
647
|
+
"mode": mode,
|
|
648
|
+
"sections": sections or [],
|
|
649
|
+
"preset": preset,
|
|
650
|
+
}
|
|
651
|
+
)
|
|
652
|
+
except ValidationError as exc:
|
|
653
|
+
return _validation_failure(
|
|
654
|
+
str(exc),
|
|
655
|
+
tool_name="app_layout_plan",
|
|
656
|
+
exc=exc,
|
|
657
|
+
suggested_next_call={
|
|
658
|
+
"tool_name": "app_layout_plan",
|
|
659
|
+
"arguments": {
|
|
660
|
+
"profile": profile,
|
|
661
|
+
"app_key": app_key,
|
|
662
|
+
"mode": "merge",
|
|
663
|
+
"sections": [{"title": "基础信息", "rows": [["字段A", "字段B"]]}],
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
)
|
|
667
|
+
return _safe_tool_call(
|
|
668
|
+
lambda: self._facade.app_layout_plan(profile=profile, request=request),
|
|
669
|
+
error_code="LAYOUT_PLAN_FAILED",
|
|
670
|
+
normalized_args=request.model_dump(mode="json"),
|
|
671
|
+
suggested_next_call={"tool_name": "app_layout_plan", "arguments": {"profile": profile, **request.model_dump(mode="json")}},
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
def app_flow_plan(
|
|
675
|
+
self,
|
|
676
|
+
*,
|
|
677
|
+
profile: str,
|
|
678
|
+
app_key: str,
|
|
679
|
+
mode: str = "replace",
|
|
680
|
+
nodes: list[JSONObject] | None = None,
|
|
681
|
+
transitions: list[JSONObject] | None = None,
|
|
682
|
+
preset: str | None = None,
|
|
683
|
+
) -> JSONObject:
|
|
684
|
+
try:
|
|
685
|
+
request = FlowPlanRequest.model_validate(
|
|
686
|
+
{
|
|
687
|
+
"app_key": app_key,
|
|
688
|
+
"mode": mode,
|
|
689
|
+
"nodes": nodes or [],
|
|
690
|
+
"transitions": transitions or [],
|
|
691
|
+
"preset": preset,
|
|
692
|
+
}
|
|
693
|
+
)
|
|
694
|
+
except ValidationError as exc:
|
|
695
|
+
return _validation_failure(
|
|
696
|
+
str(exc),
|
|
697
|
+
tool_name="app_flow_plan",
|
|
698
|
+
exc=exc,
|
|
699
|
+
suggested_next_call={
|
|
700
|
+
"tool_name": "app_flow_plan",
|
|
701
|
+
"arguments": {
|
|
702
|
+
"profile": profile,
|
|
703
|
+
"app_key": app_key,
|
|
704
|
+
"mode": "replace",
|
|
705
|
+
"preset": "basic_approval",
|
|
706
|
+
"nodes": [],
|
|
707
|
+
"transitions": [],
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
)
|
|
711
|
+
return _safe_tool_call(
|
|
712
|
+
lambda: self._facade.app_flow_plan(profile=profile, request=request),
|
|
713
|
+
error_code="FLOW_PLAN_FAILED",
|
|
714
|
+
normalized_args=request.model_dump(mode="json"),
|
|
715
|
+
suggested_next_call={"tool_name": "app_flow_plan", "arguments": {"profile": profile, **request.model_dump(mode="json")}},
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
def app_views_plan(
|
|
719
|
+
self,
|
|
720
|
+
*,
|
|
721
|
+
profile: str,
|
|
722
|
+
app_key: str,
|
|
723
|
+
upsert_views: list[JSONObject] | None = None,
|
|
724
|
+
remove_views: list[str] | None = None,
|
|
725
|
+
preset: str | None = None,
|
|
726
|
+
) -> JSONObject:
|
|
727
|
+
try:
|
|
728
|
+
request = ViewsPlanRequest.model_validate(
|
|
729
|
+
{
|
|
730
|
+
"app_key": app_key,
|
|
731
|
+
"upsert_views": upsert_views or [],
|
|
732
|
+
"remove_views": remove_views or [],
|
|
733
|
+
"preset": preset,
|
|
734
|
+
}
|
|
735
|
+
)
|
|
736
|
+
except ValidationError as exc:
|
|
737
|
+
return _validation_failure(
|
|
738
|
+
str(exc),
|
|
739
|
+
tool_name="app_views_plan",
|
|
740
|
+
exc=exc,
|
|
741
|
+
suggested_next_call={
|
|
742
|
+
"tool_name": "app_views_plan",
|
|
743
|
+
"arguments": {
|
|
744
|
+
"profile": profile,
|
|
745
|
+
"app_key": app_key,
|
|
746
|
+
"preset": "default_table",
|
|
747
|
+
"upsert_views": [],
|
|
748
|
+
"remove_views": [],
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
)
|
|
752
|
+
return _safe_tool_call(
|
|
753
|
+
lambda: self._facade.app_views_plan(profile=profile, request=request),
|
|
754
|
+
error_code="VIEWS_PLAN_FAILED",
|
|
755
|
+
normalized_args=request.model_dump(mode="json"),
|
|
756
|
+
suggested_next_call={"tool_name": "app_views_plan", "arguments": {"profile": profile, **request.model_dump(mode="json")}},
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
def app_schema_apply(
|
|
760
|
+
self,
|
|
761
|
+
*,
|
|
762
|
+
profile: str,
|
|
763
|
+
app_key: str = "",
|
|
764
|
+
package_tag_id: int | None = None,
|
|
765
|
+
app_name: str = "",
|
|
766
|
+
app_title: str = "",
|
|
767
|
+
create_if_missing: bool = False,
|
|
768
|
+
publish: bool = True,
|
|
769
|
+
add_fields: list[JSONObject],
|
|
770
|
+
update_fields: list[JSONObject],
|
|
771
|
+
remove_fields: list[JSONObject],
|
|
772
|
+
) -> JSONObject:
|
|
773
|
+
result = self._app_schema_apply_once(
|
|
774
|
+
profile=profile,
|
|
775
|
+
app_key=app_key,
|
|
776
|
+
package_tag_id=package_tag_id,
|
|
777
|
+
app_name=app_name,
|
|
778
|
+
app_title=app_title,
|
|
779
|
+
create_if_missing=create_if_missing,
|
|
780
|
+
publish=publish,
|
|
781
|
+
add_fields=add_fields,
|
|
782
|
+
update_fields=update_fields,
|
|
783
|
+
remove_fields=remove_fields,
|
|
784
|
+
)
|
|
785
|
+
return self._retry_after_self_lock_release(
|
|
786
|
+
profile=profile,
|
|
787
|
+
result=result,
|
|
788
|
+
retry_call=lambda: self._app_schema_apply_once(
|
|
789
|
+
profile=profile,
|
|
790
|
+
app_key=app_key,
|
|
791
|
+
package_tag_id=package_tag_id,
|
|
792
|
+
app_name=app_name,
|
|
793
|
+
app_title=app_title,
|
|
794
|
+
create_if_missing=create_if_missing,
|
|
795
|
+
publish=publish,
|
|
796
|
+
add_fields=add_fields,
|
|
797
|
+
update_fields=update_fields,
|
|
798
|
+
remove_fields=remove_fields,
|
|
799
|
+
),
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
def _app_schema_apply_once(
|
|
803
|
+
self,
|
|
804
|
+
*,
|
|
805
|
+
profile: str,
|
|
806
|
+
app_key: str = "",
|
|
807
|
+
package_tag_id: int | None = None,
|
|
808
|
+
app_name: str = "",
|
|
809
|
+
app_title: str = "",
|
|
810
|
+
create_if_missing: bool = False,
|
|
811
|
+
publish: bool = True,
|
|
812
|
+
add_fields: list[JSONObject],
|
|
813
|
+
update_fields: list[JSONObject],
|
|
814
|
+
remove_fields: list[JSONObject],
|
|
815
|
+
) -> JSONObject:
|
|
816
|
+
effective_app_name = app_name or app_title
|
|
817
|
+
plan_result = self._rewrite_plan_result_for_apply(
|
|
818
|
+
result=self.app_schema_plan(
|
|
819
|
+
profile=profile,
|
|
820
|
+
app_key=app_key,
|
|
821
|
+
package_tag_id=package_tag_id,
|
|
822
|
+
app_name=effective_app_name,
|
|
823
|
+
create_if_missing=create_if_missing,
|
|
824
|
+
add_fields=add_fields,
|
|
825
|
+
update_fields=update_fields,
|
|
826
|
+
remove_fields=remove_fields,
|
|
827
|
+
),
|
|
828
|
+
profile=profile,
|
|
829
|
+
publish=publish,
|
|
830
|
+
plan_tool_name="app_schema_plan",
|
|
831
|
+
apply_tool_name="app_schema_apply",
|
|
832
|
+
)
|
|
833
|
+
if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
|
|
834
|
+
return plan_result
|
|
835
|
+
plan_args = plan_result.get("normalized_args")
|
|
836
|
+
if not isinstance(plan_args, dict):
|
|
837
|
+
plan_args = {}
|
|
838
|
+
try:
|
|
839
|
+
parsed_add = [FieldPatch.model_validate(item) for item in plan_args.get("add_fields") or []]
|
|
840
|
+
parsed_update = [FieldUpdatePatch.model_validate(item) for item in plan_args.get("update_fields") or []]
|
|
841
|
+
parsed_remove = [FieldRemovePatch.model_validate(item) for item in plan_args.get("remove_fields") or []]
|
|
842
|
+
except ValidationError as exc:
|
|
843
|
+
return _validation_failure(
|
|
844
|
+
str(exc),
|
|
845
|
+
tool_name="app_schema_apply",
|
|
846
|
+
exc=exc,
|
|
847
|
+
suggested_next_call={
|
|
848
|
+
"tool_name": "app_schema_apply",
|
|
849
|
+
"arguments": {
|
|
850
|
+
"profile": profile,
|
|
851
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
852
|
+
"package_tag_id": plan_args.get("package_tag_id", package_tag_id),
|
|
853
|
+
"app_name": str(plan_args.get("app_name") or effective_app_name),
|
|
854
|
+
"create_if_missing": bool(plan_args.get("create_if_missing", create_if_missing)),
|
|
855
|
+
"publish": publish,
|
|
856
|
+
"add_fields": plan_args.get("add_fields") or [{"name": "字段名称", "type": "text", "required": False}],
|
|
857
|
+
"update_fields": plan_args.get("update_fields") or [],
|
|
858
|
+
"remove_fields": plan_args.get("remove_fields") or [],
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
)
|
|
862
|
+
normalized_args = {
|
|
863
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
864
|
+
"package_tag_id": plan_args.get("package_tag_id", package_tag_id),
|
|
865
|
+
"app_name": str(plan_args.get("app_name") or effective_app_name),
|
|
866
|
+
"create_if_missing": bool(plan_args.get("create_if_missing", create_if_missing)),
|
|
867
|
+
"publish": publish,
|
|
868
|
+
"add_fields": [patch.model_dump(mode="json") for patch in parsed_add],
|
|
869
|
+
"update_fields": [patch.model_dump(mode="json") for patch in parsed_update],
|
|
870
|
+
"remove_fields": [patch.model_dump(mode="json") for patch in parsed_remove],
|
|
871
|
+
}
|
|
872
|
+
result = _safe_tool_call(
|
|
873
|
+
lambda: self._facade.app_schema_apply(
|
|
874
|
+
profile=profile,
|
|
875
|
+
app_key=str(plan_args.get("app_key") or app_key),
|
|
876
|
+
package_tag_id=plan_args.get("package_tag_id", package_tag_id),
|
|
877
|
+
app_name=str(plan_args.get("app_name") or effective_app_name),
|
|
878
|
+
create_if_missing=bool(plan_args.get("create_if_missing", create_if_missing)),
|
|
879
|
+
publish=publish,
|
|
880
|
+
add_fields=parsed_add,
|
|
881
|
+
update_fields=parsed_update,
|
|
882
|
+
remove_fields=parsed_remove,
|
|
883
|
+
),
|
|
884
|
+
error_code="SCHEMA_APPLY_FAILED",
|
|
885
|
+
normalized_args=normalized_args,
|
|
886
|
+
suggested_next_call={"tool_name": "app_schema_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
887
|
+
)
|
|
888
|
+
return result
|
|
889
|
+
|
|
890
|
+
def app_layout_apply(self, *, profile: str, app_key: str, mode: str = "merge", publish: bool = True, sections: list[JSONObject]) -> JSONObject:
|
|
891
|
+
result = self._app_layout_apply_once(
|
|
892
|
+
profile=profile,
|
|
893
|
+
app_key=app_key,
|
|
894
|
+
mode=mode,
|
|
895
|
+
publish=publish,
|
|
896
|
+
sections=sections,
|
|
897
|
+
)
|
|
898
|
+
return self._retry_after_self_lock_release(
|
|
899
|
+
profile=profile,
|
|
900
|
+
result=result,
|
|
901
|
+
retry_call=lambda: self._app_layout_apply_once(
|
|
902
|
+
profile=profile,
|
|
903
|
+
app_key=app_key,
|
|
904
|
+
mode=mode,
|
|
905
|
+
publish=publish,
|
|
906
|
+
sections=sections,
|
|
907
|
+
),
|
|
908
|
+
)
|
|
909
|
+
|
|
910
|
+
def _app_layout_apply_once(self, *, profile: str, app_key: str, mode: str = "merge", publish: bool = True, sections: list[JSONObject]) -> JSONObject:
|
|
911
|
+
plan_result = self._rewrite_plan_result_for_apply(
|
|
912
|
+
result=self.app_layout_plan(
|
|
913
|
+
profile=profile,
|
|
914
|
+
app_key=app_key,
|
|
915
|
+
mode=mode,
|
|
916
|
+
sections=sections,
|
|
917
|
+
preset=None,
|
|
918
|
+
),
|
|
919
|
+
profile=profile,
|
|
920
|
+
publish=publish,
|
|
921
|
+
plan_tool_name="app_layout_plan",
|
|
922
|
+
apply_tool_name="app_layout_apply",
|
|
923
|
+
)
|
|
924
|
+
if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
|
|
925
|
+
return plan_result
|
|
926
|
+
plan_args = plan_result.get("normalized_args")
|
|
927
|
+
if not isinstance(plan_args, dict):
|
|
928
|
+
plan_args = {}
|
|
929
|
+
try:
|
|
930
|
+
parsed_mode = LayoutApplyMode(str(plan_args.get("mode") or mode))
|
|
931
|
+
parsed_sections = [LayoutSectionPatch.model_validate(item) for item in plan_args.get("sections") or []]
|
|
932
|
+
except (ValueError, ValidationError) as exc:
|
|
933
|
+
return _validation_failure(
|
|
934
|
+
str(exc),
|
|
935
|
+
tool_name="app_layout_apply",
|
|
936
|
+
exc=exc if isinstance(exc, ValidationError) else None,
|
|
937
|
+
suggested_next_call={
|
|
938
|
+
"tool_name": "app_layout_apply",
|
|
939
|
+
"arguments": {
|
|
940
|
+
"profile": profile,
|
|
941
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
942
|
+
"mode": str(plan_args.get("mode") or "merge"),
|
|
943
|
+
"publish": publish,
|
|
944
|
+
"sections": plan_args.get("sections") or [{"title": "基础信息", "rows": [["字段A", "字段B"]]}],
|
|
945
|
+
},
|
|
946
|
+
},
|
|
947
|
+
)
|
|
948
|
+
normalized_args = {
|
|
949
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
950
|
+
"mode": parsed_mode.value,
|
|
951
|
+
"publish": publish,
|
|
952
|
+
"sections": [section.model_dump(mode="json") for section in parsed_sections],
|
|
953
|
+
}
|
|
954
|
+
return _safe_tool_call(
|
|
955
|
+
lambda: self._facade.app_layout_apply(
|
|
956
|
+
profile=profile,
|
|
957
|
+
app_key=str(plan_args.get("app_key") or app_key),
|
|
958
|
+
mode=parsed_mode,
|
|
959
|
+
publish=publish,
|
|
960
|
+
sections=parsed_sections,
|
|
961
|
+
),
|
|
962
|
+
error_code="LAYOUT_APPLY_FAILED",
|
|
963
|
+
normalized_args=normalized_args,
|
|
964
|
+
suggested_next_call={"tool_name": "app_layout_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
def app_flow_apply(
|
|
968
|
+
self,
|
|
969
|
+
*,
|
|
970
|
+
profile: str,
|
|
971
|
+
app_key: str,
|
|
972
|
+
mode: str = "replace",
|
|
973
|
+
publish: bool = True,
|
|
974
|
+
nodes: list[JSONObject],
|
|
975
|
+
transitions: list[JSONObject],
|
|
976
|
+
) -> JSONObject:
|
|
977
|
+
result = self._app_flow_apply_once(
|
|
978
|
+
profile=profile,
|
|
979
|
+
app_key=app_key,
|
|
980
|
+
mode=mode,
|
|
981
|
+
publish=publish,
|
|
982
|
+
nodes=nodes,
|
|
983
|
+
transitions=transitions,
|
|
984
|
+
)
|
|
985
|
+
return self._retry_after_self_lock_release(
|
|
986
|
+
profile=profile,
|
|
987
|
+
result=result,
|
|
988
|
+
retry_call=lambda: self._app_flow_apply_once(
|
|
989
|
+
profile=profile,
|
|
990
|
+
app_key=app_key,
|
|
991
|
+
mode=mode,
|
|
992
|
+
publish=publish,
|
|
993
|
+
nodes=nodes,
|
|
994
|
+
transitions=transitions,
|
|
995
|
+
),
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
def _app_flow_apply_once(
|
|
999
|
+
self,
|
|
1000
|
+
*,
|
|
1001
|
+
profile: str,
|
|
1002
|
+
app_key: str,
|
|
1003
|
+
mode: str = "replace",
|
|
1004
|
+
publish: bool = True,
|
|
1005
|
+
nodes: list[JSONObject],
|
|
1006
|
+
transitions: list[JSONObject],
|
|
1007
|
+
) -> JSONObject:
|
|
1008
|
+
plan_result = self._rewrite_plan_result_for_apply(
|
|
1009
|
+
result=self.app_flow_plan(
|
|
1010
|
+
profile=profile,
|
|
1011
|
+
app_key=app_key,
|
|
1012
|
+
mode=mode,
|
|
1013
|
+
nodes=nodes,
|
|
1014
|
+
transitions=transitions,
|
|
1015
|
+
preset=None,
|
|
1016
|
+
),
|
|
1017
|
+
profile=profile,
|
|
1018
|
+
publish=publish,
|
|
1019
|
+
plan_tool_name="app_flow_plan",
|
|
1020
|
+
apply_tool_name="app_flow_apply",
|
|
1021
|
+
)
|
|
1022
|
+
if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
|
|
1023
|
+
return plan_result
|
|
1024
|
+
plan_args = plan_result.get("normalized_args")
|
|
1025
|
+
if not isinstance(plan_args, dict):
|
|
1026
|
+
plan_args = {}
|
|
1027
|
+
try:
|
|
1028
|
+
request = FlowPlanRequest.model_validate(
|
|
1029
|
+
{
|
|
1030
|
+
"app_key": plan_args.get("app_key") or app_key,
|
|
1031
|
+
"mode": plan_args.get("mode") or mode,
|
|
1032
|
+
"nodes": plan_args.get("nodes") or [],
|
|
1033
|
+
"transitions": plan_args.get("transitions") or [],
|
|
1034
|
+
"preset": None,
|
|
1035
|
+
}
|
|
1036
|
+
)
|
|
1037
|
+
except ValidationError as exc:
|
|
1038
|
+
return _validation_failure(
|
|
1039
|
+
str(exc),
|
|
1040
|
+
tool_name="app_flow_apply",
|
|
1041
|
+
exc=exc,
|
|
1042
|
+
suggested_next_call={
|
|
1043
|
+
"tool_name": "app_flow_apply",
|
|
1044
|
+
"arguments": {
|
|
1045
|
+
"profile": profile,
|
|
1046
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
1047
|
+
"mode": str(plan_args.get("mode") or "replace"),
|
|
1048
|
+
"publish": publish,
|
|
1049
|
+
"nodes": plan_args.get("nodes") or [{"id": "start", "type": "start", "name": "发起"}],
|
|
1050
|
+
"transitions": plan_args.get("transitions") or [],
|
|
1051
|
+
},
|
|
1052
|
+
},
|
|
1053
|
+
)
|
|
1054
|
+
normalized_args = {
|
|
1055
|
+
"app_key": request.app_key,
|
|
1056
|
+
"mode": request.mode,
|
|
1057
|
+
"publish": publish,
|
|
1058
|
+
"nodes": [node.model_dump(mode="json") for node in request.nodes],
|
|
1059
|
+
"transitions": [transition.model_dump(mode="json", by_alias=True) for transition in request.transitions],
|
|
1060
|
+
}
|
|
1061
|
+
return _safe_tool_call(
|
|
1062
|
+
lambda: self._facade.app_flow_apply(
|
|
1063
|
+
profile=profile,
|
|
1064
|
+
app_key=request.app_key,
|
|
1065
|
+
mode=request.mode,
|
|
1066
|
+
publish=publish,
|
|
1067
|
+
nodes=[node.model_dump(mode="json") for node in request.nodes],
|
|
1068
|
+
transitions=[transition.model_dump(mode="json", by_alias=True) for transition in request.transitions],
|
|
1069
|
+
),
|
|
1070
|
+
error_code="FLOW_APPLY_FAILED",
|
|
1071
|
+
normalized_args=normalized_args,
|
|
1072
|
+
suggested_next_call={"tool_name": "app_flow_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
def app_views_apply(
|
|
1076
|
+
self,
|
|
1077
|
+
*,
|
|
1078
|
+
profile: str,
|
|
1079
|
+
app_key: str,
|
|
1080
|
+
publish: bool = True,
|
|
1081
|
+
upsert_views: list[JSONObject],
|
|
1082
|
+
remove_views: list[str],
|
|
1083
|
+
) -> JSONObject:
|
|
1084
|
+
result = self._app_views_apply_once(
|
|
1085
|
+
profile=profile,
|
|
1086
|
+
app_key=app_key,
|
|
1087
|
+
publish=publish,
|
|
1088
|
+
upsert_views=upsert_views,
|
|
1089
|
+
remove_views=remove_views,
|
|
1090
|
+
)
|
|
1091
|
+
return self._retry_after_self_lock_release(
|
|
1092
|
+
profile=profile,
|
|
1093
|
+
result=result,
|
|
1094
|
+
retry_call=lambda: self._app_views_apply_once(
|
|
1095
|
+
profile=profile,
|
|
1096
|
+
app_key=app_key,
|
|
1097
|
+
publish=publish,
|
|
1098
|
+
remove_views=remove_views,
|
|
1099
|
+
upsert_views=upsert_views,
|
|
1100
|
+
),
|
|
1101
|
+
)
|
|
1102
|
+
|
|
1103
|
+
def _app_views_apply_once(
|
|
1104
|
+
self,
|
|
1105
|
+
*,
|
|
1106
|
+
profile: str,
|
|
1107
|
+
app_key: str,
|
|
1108
|
+
publish: bool = True,
|
|
1109
|
+
upsert_views: list[JSONObject],
|
|
1110
|
+
remove_views: list[str],
|
|
1111
|
+
) -> JSONObject:
|
|
1112
|
+
plan_result = self._rewrite_plan_result_for_apply(
|
|
1113
|
+
result=self.app_views_plan(
|
|
1114
|
+
profile=profile,
|
|
1115
|
+
app_key=app_key,
|
|
1116
|
+
upsert_views=upsert_views,
|
|
1117
|
+
remove_views=remove_views,
|
|
1118
|
+
preset=None,
|
|
1119
|
+
),
|
|
1120
|
+
profile=profile,
|
|
1121
|
+
publish=publish,
|
|
1122
|
+
plan_tool_name="app_views_plan",
|
|
1123
|
+
apply_tool_name="app_views_apply",
|
|
1124
|
+
)
|
|
1125
|
+
if not isinstance(plan_result, dict) or plan_result.get("status") != "success":
|
|
1126
|
+
return plan_result
|
|
1127
|
+
plan_args = plan_result.get("normalized_args")
|
|
1128
|
+
if not isinstance(plan_args, dict):
|
|
1129
|
+
plan_args = {}
|
|
1130
|
+
try:
|
|
1131
|
+
parsed_views = [ViewUpsertPatch.model_validate(item) for item in plan_args.get("upsert_views") or []]
|
|
1132
|
+
except ValidationError as exc:
|
|
1133
|
+
return _validation_failure(
|
|
1134
|
+
str(exc),
|
|
1135
|
+
tool_name="app_views_apply",
|
|
1136
|
+
exc=exc,
|
|
1137
|
+
suggested_next_call={
|
|
1138
|
+
"tool_name": "app_views_apply",
|
|
1139
|
+
"arguments": {
|
|
1140
|
+
"profile": profile,
|
|
1141
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
1142
|
+
"publish": publish,
|
|
1143
|
+
"upsert_views": plan_args.get("upsert_views") or [{"name": "全部数据", "type": "table", "columns": ["字段A"]}],
|
|
1144
|
+
"remove_views": plan_args.get("remove_views") or [],
|
|
1145
|
+
},
|
|
1146
|
+
},
|
|
1147
|
+
)
|
|
1148
|
+
normalized_args = {
|
|
1149
|
+
"app_key": str(plan_args.get("app_key") or app_key),
|
|
1150
|
+
"publish": publish,
|
|
1151
|
+
"upsert_views": [view.model_dump(mode="json") for view in parsed_views],
|
|
1152
|
+
"remove_views": list(plan_args.get("remove_views") or remove_views),
|
|
1153
|
+
}
|
|
1154
|
+
return _safe_tool_call(
|
|
1155
|
+
lambda: self._facade.app_views_apply(
|
|
1156
|
+
profile=profile,
|
|
1157
|
+
app_key=str(plan_args.get("app_key") or app_key),
|
|
1158
|
+
publish=publish,
|
|
1159
|
+
upsert_views=parsed_views,
|
|
1160
|
+
remove_views=list(plan_args.get("remove_views") or remove_views),
|
|
1161
|
+
),
|
|
1162
|
+
error_code="VIEWS_APPLY_FAILED",
|
|
1163
|
+
normalized_args=normalized_args,
|
|
1164
|
+
suggested_next_call={"tool_name": "app_views_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1165
|
+
)
|
|
1166
|
+
|
|
1167
|
+
def chart_apply(
|
|
1168
|
+
self,
|
|
1169
|
+
*,
|
|
1170
|
+
profile: str,
|
|
1171
|
+
app_key: str,
|
|
1172
|
+
upsert_charts: list[JSONObject],
|
|
1173
|
+
remove_chart_ids: list[str],
|
|
1174
|
+
reorder_chart_ids: list[str],
|
|
1175
|
+
) -> JSONObject:
|
|
1176
|
+
return self.app_charts_apply(
|
|
1177
|
+
profile=profile,
|
|
1178
|
+
app_key=app_key,
|
|
1179
|
+
upsert_charts=upsert_charts,
|
|
1180
|
+
remove_chart_ids=remove_chart_ids,
|
|
1181
|
+
reorder_chart_ids=reorder_chart_ids,
|
|
1182
|
+
)
|
|
1183
|
+
|
|
1184
|
+
def app_charts_apply(
|
|
1185
|
+
self,
|
|
1186
|
+
*,
|
|
1187
|
+
profile: str,
|
|
1188
|
+
app_key: str,
|
|
1189
|
+
upsert_charts: list[JSONObject],
|
|
1190
|
+
remove_chart_ids: list[str],
|
|
1191
|
+
reorder_chart_ids: list[str],
|
|
1192
|
+
) -> JSONObject:
|
|
1193
|
+
try:
|
|
1194
|
+
request = ChartApplyRequest.model_validate(
|
|
1195
|
+
{
|
|
1196
|
+
"app_key": app_key,
|
|
1197
|
+
"upsert_charts": upsert_charts or [],
|
|
1198
|
+
"remove_chart_ids": remove_chart_ids or [],
|
|
1199
|
+
"reorder_chart_ids": reorder_chart_ids or [],
|
|
1200
|
+
}
|
|
1201
|
+
)
|
|
1202
|
+
except ValidationError as exc:
|
|
1203
|
+
return _validation_failure(
|
|
1204
|
+
str(exc),
|
|
1205
|
+
tool_name="app_charts_apply",
|
|
1206
|
+
exc=exc,
|
|
1207
|
+
suggested_next_call={
|
|
1208
|
+
"tool_name": "app_charts_apply",
|
|
1209
|
+
"arguments": {
|
|
1210
|
+
"profile": profile,
|
|
1211
|
+
"app_key": app_key,
|
|
1212
|
+
"upsert_charts": [{"name": "销售总量", "chart_type": "target", "indicator_field_ids": []}],
|
|
1213
|
+
"remove_chart_ids": [],
|
|
1214
|
+
"reorder_chart_ids": [],
|
|
1215
|
+
},
|
|
1216
|
+
},
|
|
1217
|
+
)
|
|
1218
|
+
normalized_args = request.model_dump(mode="json")
|
|
1219
|
+
return _safe_tool_call(
|
|
1220
|
+
lambda: self._facade.chart_apply(profile=profile, request=request),
|
|
1221
|
+
error_code="CHART_APPLY_FAILED",
|
|
1222
|
+
normalized_args=normalized_args,
|
|
1223
|
+
suggested_next_call={"tool_name": "app_charts_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1224
|
+
)
|
|
1225
|
+
|
|
1226
|
+
def portal_apply(
|
|
1227
|
+
self,
|
|
1228
|
+
*,
|
|
1229
|
+
profile: str,
|
|
1230
|
+
dash_key: str = "",
|
|
1231
|
+
dash_name: str = "",
|
|
1232
|
+
package_tag_id: int | None = None,
|
|
1233
|
+
publish: bool = True,
|
|
1234
|
+
sections: list[JSONObject],
|
|
1235
|
+
auth: JSONObject | None = None,
|
|
1236
|
+
icon: str | None = None,
|
|
1237
|
+
color: str | None = None,
|
|
1238
|
+
hide_copyright: bool | None = None,
|
|
1239
|
+
dash_global_config: JSONObject | None = None,
|
|
1240
|
+
config: JSONObject | None = None,
|
|
1241
|
+
) -> JSONObject:
|
|
1242
|
+
try:
|
|
1243
|
+
request = PortalApplyRequest.model_validate(
|
|
1244
|
+
{
|
|
1245
|
+
"dash_key": dash_key or None,
|
|
1246
|
+
"dash_name": dash_name or None,
|
|
1247
|
+
"package_tag_id": package_tag_id,
|
|
1248
|
+
"publish": publish,
|
|
1249
|
+
"sections": sections or [],
|
|
1250
|
+
"auth": auth,
|
|
1251
|
+
"icon": icon,
|
|
1252
|
+
"color": color,
|
|
1253
|
+
"hide_copyright": hide_copyright,
|
|
1254
|
+
"dash_global_config": dash_global_config,
|
|
1255
|
+
"config": config or {},
|
|
1256
|
+
}
|
|
1257
|
+
)
|
|
1258
|
+
except ValidationError as exc:
|
|
1259
|
+
return _validation_failure(
|
|
1260
|
+
str(exc),
|
|
1261
|
+
tool_name="portal_apply",
|
|
1262
|
+
exc=exc,
|
|
1263
|
+
suggested_next_call={
|
|
1264
|
+
"tool_name": "portal_apply",
|
|
1265
|
+
"arguments": {
|
|
1266
|
+
"profile": profile,
|
|
1267
|
+
"dash_name": dash_name or "业务门户",
|
|
1268
|
+
"package_tag_id": package_tag_id or 1001,
|
|
1269
|
+
"publish": True,
|
|
1270
|
+
"sections": [
|
|
1271
|
+
{
|
|
1272
|
+
"title": "经营概览",
|
|
1273
|
+
"source_type": "text",
|
|
1274
|
+
"text": "欢迎使用业务门户",
|
|
1275
|
+
}
|
|
1276
|
+
],
|
|
1277
|
+
},
|
|
1278
|
+
},
|
|
1279
|
+
)
|
|
1280
|
+
normalized_args = request.model_dump(mode="json")
|
|
1281
|
+
return _safe_tool_call(
|
|
1282
|
+
lambda: self._facade.portal_apply(profile=profile, request=request),
|
|
1283
|
+
error_code="PORTAL_APPLY_FAILED",
|
|
1284
|
+
normalized_args=normalized_args,
|
|
1285
|
+
suggested_next_call={"tool_name": "portal_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
1286
|
+
)
|
|
1287
|
+
|
|
1288
|
+
def app_publish_verify(self, *, profile: str, app_key: str, expected_package_tag_id: int | None = None) -> JSONObject:
|
|
1289
|
+
normalized_args = {"app_key": app_key, "expected_package_tag_id": expected_package_tag_id}
|
|
1290
|
+
result = _safe_tool_call(
|
|
1291
|
+
lambda: self._facade.app_publish_verify(profile=profile, app_key=app_key, expected_package_tag_id=expected_package_tag_id),
|
|
1292
|
+
error_code="PUBLISH_VERIFY_FAILED",
|
|
1293
|
+
normalized_args=normalized_args,
|
|
1294
|
+
suggested_next_call={"tool_name": "app_publish_verify", "arguments": {"profile": profile, **normalized_args}},
|
|
1295
|
+
)
|
|
1296
|
+
return self._retry_after_self_lock_release(
|
|
1297
|
+
profile=profile,
|
|
1298
|
+
result=result,
|
|
1299
|
+
retry_call=lambda: self._facade.app_publish_verify(
|
|
1300
|
+
profile=profile,
|
|
1301
|
+
app_key=app_key,
|
|
1302
|
+
expected_package_tag_id=expected_package_tag_id,
|
|
1303
|
+
),
|
|
1304
|
+
)
|
|
1305
|
+
|
|
1306
|
+
def _retry_after_self_lock_release(self, *, profile: str, result: JSONObject, retry_call) -> JSONObject:
|
|
1307
|
+
if not isinstance(result, dict) or result.get("status") != "failed" or result.get("error_code") != "APP_EDIT_LOCKED":
|
|
1308
|
+
return result
|
|
1309
|
+
suggested = result.get("suggested_next_call")
|
|
1310
|
+
if not isinstance(suggested, dict) or suggested.get("tool_name") != "app_release_edit_lock_if_mine":
|
|
1311
|
+
return result
|
|
1312
|
+
arguments = suggested.get("arguments")
|
|
1313
|
+
if not isinstance(arguments, dict):
|
|
1314
|
+
return result
|
|
1315
|
+
app_key = str(arguments.get("app_key") or "")
|
|
1316
|
+
lock_owner_email = str(arguments.get("lock_owner_email") or "")
|
|
1317
|
+
lock_owner_name = str(arguments.get("lock_owner_name") or "")
|
|
1318
|
+
release_attempts: list[JSONObject] = []
|
|
1319
|
+
retried: JSONObject = result
|
|
1320
|
+
for _ in range(3):
|
|
1321
|
+
release_result = self.app_release_edit_lock_if_mine(
|
|
1322
|
+
profile=profile,
|
|
1323
|
+
app_key=app_key,
|
|
1324
|
+
lock_owner_email=lock_owner_email,
|
|
1325
|
+
lock_owner_name=lock_owner_name,
|
|
1326
|
+
)
|
|
1327
|
+
release_attempts.append(release_result)
|
|
1328
|
+
if not isinstance(release_result, dict) or release_result.get("status") != "success":
|
|
1329
|
+
result.setdefault("details", {})
|
|
1330
|
+
if isinstance(result["details"], dict):
|
|
1331
|
+
result["details"]["edit_lock_release_result"] = release_result
|
|
1332
|
+
result["details"]["edit_lock_release_attempts"] = release_attempts
|
|
1333
|
+
return result
|
|
1334
|
+
retried = retry_call()
|
|
1335
|
+
if not (
|
|
1336
|
+
isinstance(retried, dict)
|
|
1337
|
+
and retried.get("status") == "failed"
|
|
1338
|
+
and retried.get("error_code") == "APP_EDIT_LOCKED"
|
|
1339
|
+
):
|
|
1340
|
+
break
|
|
1341
|
+
time.sleep(0.2)
|
|
1342
|
+
if (
|
|
1343
|
+
isinstance(retried, dict)
|
|
1344
|
+
and retried.get("status") == "failed"
|
|
1345
|
+
and retried.get("error_code") == "APP_EDIT_LOCKED"
|
|
1346
|
+
):
|
|
1347
|
+
retried = {
|
|
1348
|
+
**retried,
|
|
1349
|
+
"error_code": "PERSISTENT_SELF_LOCK",
|
|
1350
|
+
"message": "app remains locked by the current user's active editor session after repeated forced release attempts",
|
|
1351
|
+
"recoverable": True,
|
|
1352
|
+
"suggested_next_call": None,
|
|
1353
|
+
}
|
|
1354
|
+
if isinstance(retried, dict):
|
|
1355
|
+
retried.setdefault("details", {})
|
|
1356
|
+
if isinstance(retried["details"], dict):
|
|
1357
|
+
retried["details"]["edit_lock_release_result"] = release_attempts[-1] if release_attempts else None
|
|
1358
|
+
retried["details"]["edit_lock_release_attempts"] = release_attempts
|
|
1359
|
+
retried["edit_lock_released"] = bool(release_attempts)
|
|
1360
|
+
retried["retried_after_edit_lock_release"] = True
|
|
1361
|
+
return retried
|
|
1362
|
+
|
|
1363
|
+
def _rewrite_plan_result_for_apply(
|
|
1364
|
+
self,
|
|
1365
|
+
*,
|
|
1366
|
+
result: JSONObject,
|
|
1367
|
+
profile: str,
|
|
1368
|
+
publish: bool,
|
|
1369
|
+
plan_tool_name: str,
|
|
1370
|
+
apply_tool_name: str,
|
|
1371
|
+
) -> JSONObject:
|
|
1372
|
+
if not isinstance(result, dict):
|
|
1373
|
+
return result
|
|
1374
|
+
rewritten = dict(result)
|
|
1375
|
+
if rewritten.get("error_code") == "VALIDATION_ERROR":
|
|
1376
|
+
contract = _BUILDER_TOOL_CONTRACTS.get(apply_tool_name)
|
|
1377
|
+
details = rewritten.get("details")
|
|
1378
|
+
if not isinstance(details, dict):
|
|
1379
|
+
details = {}
|
|
1380
|
+
rewritten["details"] = details
|
|
1381
|
+
if isinstance(contract, dict):
|
|
1382
|
+
rewritten["allowed_values"] = deepcopy(contract.get("allowed_values", {}))
|
|
1383
|
+
details["allowed_keys"] = deepcopy(contract.get("allowed_keys", []))
|
|
1384
|
+
details["section_allowed_keys"] = deepcopy(contract.get("section_allowed_keys", []))
|
|
1385
|
+
details["section_aliases"] = deepcopy(contract.get("section_aliases", {}))
|
|
1386
|
+
details["minimal_section_example"] = deepcopy(contract.get("minimal_section_example"))
|
|
1387
|
+
suggested_next_call = rewritten.get("suggested_next_call")
|
|
1388
|
+
if isinstance(suggested_next_call, dict):
|
|
1389
|
+
if suggested_next_call.get("tool_name") == plan_tool_name:
|
|
1390
|
+
arguments = suggested_next_call.get("arguments")
|
|
1391
|
+
normalized_arguments = dict(arguments) if isinstance(arguments, dict) else {}
|
|
1392
|
+
normalized_arguments.setdefault("profile", profile)
|
|
1393
|
+
normalized_arguments["publish"] = publish
|
|
1394
|
+
rewritten["suggested_next_call"] = {
|
|
1395
|
+
**suggested_next_call,
|
|
1396
|
+
"tool_name": apply_tool_name,
|
|
1397
|
+
"arguments": normalized_arguments,
|
|
1398
|
+
}
|
|
1399
|
+
return rewritten
|
|
1400
|
+
if rewritten.get("error_code") == "VALIDATION_ERROR" and suggested_next_call.get("tool_name") == apply_tool_name:
|
|
1401
|
+
arguments = suggested_next_call.get("arguments")
|
|
1402
|
+
normalized_arguments = dict(arguments) if isinstance(arguments, dict) else {}
|
|
1403
|
+
normalized_arguments.setdefault("profile", profile)
|
|
1404
|
+
normalized_arguments["publish"] = publish
|
|
1405
|
+
if isinstance(details, dict):
|
|
1406
|
+
details["canonical_arguments"] = normalized_arguments
|
|
1407
|
+
rewritten["suggested_next_call"] = {
|
|
1408
|
+
**suggested_next_call,
|
|
1409
|
+
"arguments": normalized_arguments,
|
|
1410
|
+
}
|
|
1411
|
+
if rewritten.get("status") == "success":
|
|
1412
|
+
normalized_args = rewritten.get("normalized_args")
|
|
1413
|
+
if isinstance(normalized_args, dict):
|
|
1414
|
+
rewritten["suggested_next_call"] = {
|
|
1415
|
+
"tool_name": apply_tool_name,
|
|
1416
|
+
"arguments": {"profile": profile, **normalized_args, "publish": publish},
|
|
1417
|
+
}
|
|
1418
|
+
return rewritten
|
|
1419
|
+
|
|
1420
|
+
|
|
1421
|
+
def _validation_failure(
|
|
1422
|
+
detail: str,
|
|
1423
|
+
*,
|
|
1424
|
+
tool_name: str | None = None,
|
|
1425
|
+
exc: ValidationError | None = None,
|
|
1426
|
+
suggested_next_call: JSONObject | None = None,
|
|
1427
|
+
) -> JSONObject:
|
|
1428
|
+
contract = _BUILDER_TOOL_CONTRACTS.get(tool_name or "")
|
|
1429
|
+
reason_path = None
|
|
1430
|
+
if exc is not None:
|
|
1431
|
+
errors = exc.errors()
|
|
1432
|
+
if errors:
|
|
1433
|
+
loc = errors[0].get("loc")
|
|
1434
|
+
if isinstance(loc, (tuple, list)):
|
|
1435
|
+
reason_path = ".".join(str(part) for part in loc)
|
|
1436
|
+
canonical_arguments = None
|
|
1437
|
+
if isinstance(suggested_next_call, dict):
|
|
1438
|
+
arguments = suggested_next_call.get("arguments")
|
|
1439
|
+
if isinstance(arguments, dict):
|
|
1440
|
+
canonical_arguments = arguments
|
|
1441
|
+
return {
|
|
1442
|
+
"status": "failed",
|
|
1443
|
+
"error_code": "VALIDATION_ERROR",
|
|
1444
|
+
"recoverable": True,
|
|
1445
|
+
"message": detail,
|
|
1446
|
+
"normalized_args": {},
|
|
1447
|
+
"missing_fields": [],
|
|
1448
|
+
"allowed_values": deepcopy(contract.get("allowed_values", {})) if isinstance(contract, dict) else {},
|
|
1449
|
+
"details": {
|
|
1450
|
+
"validation_detail": detail,
|
|
1451
|
+
"reason_path": reason_path,
|
|
1452
|
+
"allowed_keys": deepcopy(contract.get("allowed_keys", [])) if isinstance(contract, dict) else [],
|
|
1453
|
+
"canonical_arguments": canonical_arguments,
|
|
1454
|
+
"section_allowed_keys": deepcopy(contract.get("section_allowed_keys", [])) if isinstance(contract, dict) else [],
|
|
1455
|
+
"section_aliases": deepcopy(contract.get("section_aliases", {})) if isinstance(contract, dict) else {},
|
|
1456
|
+
"minimal_section_example": deepcopy(contract.get("minimal_section_example")) if isinstance(contract, dict) else None,
|
|
1457
|
+
},
|
|
1458
|
+
"suggested_next_call": suggested_next_call,
|
|
1459
|
+
"request_id": None,
|
|
1460
|
+
"backend_code": None,
|
|
1461
|
+
"http_status": None,
|
|
1462
|
+
"noop": False,
|
|
1463
|
+
"verification": {},
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
|
|
1467
|
+
def _safe_tool_call(
|
|
1468
|
+
call,
|
|
1469
|
+
*,
|
|
1470
|
+
error_code: str,
|
|
1471
|
+
normalized_args: JSONObject,
|
|
1472
|
+
suggested_next_call: JSONObject | None,
|
|
1473
|
+
) -> JSONObject:
|
|
1474
|
+
try:
|
|
1475
|
+
return call()
|
|
1476
|
+
except (QingflowApiError, RuntimeError) as error:
|
|
1477
|
+
api_error = _coerce_api_error(error)
|
|
1478
|
+
public_http_status = None if api_error.http_status == 404 else api_error.http_status
|
|
1479
|
+
return {
|
|
1480
|
+
"status": "failed",
|
|
1481
|
+
"error_code": error_code,
|
|
1482
|
+
"recoverable": True,
|
|
1483
|
+
"message": _public_error_message(error_code, api_error),
|
|
1484
|
+
"normalized_args": normalized_args,
|
|
1485
|
+
"missing_fields": [],
|
|
1486
|
+
"allowed_values": {},
|
|
1487
|
+
"details": {
|
|
1488
|
+
"transport_error": {
|
|
1489
|
+
"http_status": api_error.http_status,
|
|
1490
|
+
"backend_code": api_error.backend_code,
|
|
1491
|
+
"category": api_error.category,
|
|
1492
|
+
}
|
|
1493
|
+
},
|
|
1494
|
+
"suggested_next_call": suggested_next_call,
|
|
1495
|
+
"request_id": api_error.request_id,
|
|
1496
|
+
"backend_code": api_error.backend_code,
|
|
1497
|
+
"http_status": public_http_status,
|
|
1498
|
+
"noop": False,
|
|
1499
|
+
"verification": {},
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
|
|
1503
|
+
def _coerce_api_error(error: Exception) -> QingflowApiError:
|
|
1504
|
+
if isinstance(error, QingflowApiError):
|
|
1505
|
+
return error
|
|
1506
|
+
if isinstance(error, RuntimeError):
|
|
1507
|
+
try:
|
|
1508
|
+
payload = json.loads(str(error))
|
|
1509
|
+
except json.JSONDecodeError:
|
|
1510
|
+
payload = None
|
|
1511
|
+
if isinstance(payload, dict) and payload.get("category") and payload.get("message"):
|
|
1512
|
+
details = payload.get("details")
|
|
1513
|
+
return QingflowApiError(
|
|
1514
|
+
category=str(payload.get("category")),
|
|
1515
|
+
message=str(payload.get("message")),
|
|
1516
|
+
backend_code=payload.get("backend_code"),
|
|
1517
|
+
request_id=payload.get("request_id"),
|
|
1518
|
+
http_status=payload.get("http_status"),
|
|
1519
|
+
details=details if isinstance(details, dict) else None,
|
|
1520
|
+
)
|
|
1521
|
+
return QingflowApiError(category="runtime", message=str(error))
|
|
1522
|
+
|
|
1523
|
+
|
|
1524
|
+
def _public_error_message(error_code: str, error: QingflowApiError) -> str:
|
|
1525
|
+
if error.backend_code == 40074 or error_code == "APP_EDIT_LOCKED":
|
|
1526
|
+
return "app is currently locked by another active editor session"
|
|
1527
|
+
if error.http_status != 404:
|
|
1528
|
+
return error.message
|
|
1529
|
+
mapping = {
|
|
1530
|
+
"PACKAGE_LIST_FAILED": "package list is unavailable in the current route",
|
|
1531
|
+
"PACKAGE_RESOLVE_FAILED": "package resolution is unavailable in the current route",
|
|
1532
|
+
"PACKAGE_ATTACH_FAILED": "package attachment could not be verified in the current route",
|
|
1533
|
+
"APP_RESOLVE_FAILED": "app resolution is unavailable in the current route",
|
|
1534
|
+
"APP_READ_FAILED": "app base or schema is unavailable in the current route",
|
|
1535
|
+
"FIELDS_READ_FAILED": "app fields are unavailable in the current route",
|
|
1536
|
+
"LAYOUT_READ_FAILED": "layout resource is unavailable for this app in the current route",
|
|
1537
|
+
"VIEWS_READ_FAILED": "views resource is unavailable for this app in the current route",
|
|
1538
|
+
"FLOW_READ_FAILED": "workflow resource is unavailable for this app in the current route",
|
|
1539
|
+
"SCHEMA_PLAN_FAILED": "schema planning could not load the required app state in the current route",
|
|
1540
|
+
"LAYOUT_PLAN_FAILED": "layout planning could not load the required app state in the current route",
|
|
1541
|
+
"FLOW_PLAN_FAILED": "flow planning could not load the required app state in the current route",
|
|
1542
|
+
"VIEWS_PLAN_FAILED": "views planning could not load the required app state in the current route",
|
|
1543
|
+
"SCHEMA_APPLY_FAILED": "schema apply could not complete because the app route or readback is unavailable",
|
|
1544
|
+
"LAYOUT_APPLY_FAILED": "layout apply could not complete because the layout route or readback is unavailable",
|
|
1545
|
+
"FLOW_APPLY_FAILED": "flow apply could not complete because the workflow route or readback is unavailable",
|
|
1546
|
+
"VIEWS_APPLY_FAILED": "views apply could not complete because the views route or readback is unavailable",
|
|
1547
|
+
"CHART_APPLY_FAILED": "chart apply could not complete because the QingBI route or readback is unavailable",
|
|
1548
|
+
"PORTAL_APPLY_FAILED": "portal apply could not complete because the portal route or readback is unavailable",
|
|
1549
|
+
"PUBLISH_VERIFY_FAILED": "publish verification is unavailable in the current route",
|
|
1550
|
+
}
|
|
1551
|
+
return mapping.get(error_code, "requested builder resource is unavailable in the current route")
|
|
1552
|
+
|
|
1553
|
+
|
|
1554
|
+
_BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
1555
|
+
"member_search": {
|
|
1556
|
+
"allowed_keys": ["query", "page_num", "page_size", "contain_disable"],
|
|
1557
|
+
"aliases": {},
|
|
1558
|
+
"allowed_values": {},
|
|
1559
|
+
"minimal_example": {
|
|
1560
|
+
"profile": "default",
|
|
1561
|
+
"query": "严琪东",
|
|
1562
|
+
"page_num": 1,
|
|
1563
|
+
"page_size": 20,
|
|
1564
|
+
"contain_disable": False,
|
|
1565
|
+
},
|
|
1566
|
+
},
|
|
1567
|
+
"role_search": {
|
|
1568
|
+
"allowed_keys": ["keyword", "page_num", "page_size"],
|
|
1569
|
+
"aliases": {},
|
|
1570
|
+
"allowed_values": {},
|
|
1571
|
+
"minimal_example": {
|
|
1572
|
+
"profile": "default",
|
|
1573
|
+
"keyword": "项目经理",
|
|
1574
|
+
"page_num": 1,
|
|
1575
|
+
"page_size": 20,
|
|
1576
|
+
},
|
|
1577
|
+
},
|
|
1578
|
+
"role_create": {
|
|
1579
|
+
"allowed_keys": ["role_name", "member_uids", "member_emails", "member_names", "role_icon"],
|
|
1580
|
+
"aliases": {},
|
|
1581
|
+
"allowed_values": {},
|
|
1582
|
+
"minimal_example": {
|
|
1583
|
+
"profile": "default",
|
|
1584
|
+
"role_name": "研发负责人",
|
|
1585
|
+
"member_names": ["严琪东"],
|
|
1586
|
+
"member_uids": [],
|
|
1587
|
+
"member_emails": [],
|
|
1588
|
+
"role_icon": "ex-user-outlined",
|
|
1589
|
+
},
|
|
1590
|
+
},
|
|
1591
|
+
"app_schema_plan": {
|
|
1592
|
+
"allowed_keys": ["app_key", "package_tag_id", "app_name", "create_if_missing", "add_fields", "update_fields", "remove_fields"],
|
|
1593
|
+
"aliases": {
|
|
1594
|
+
"app_title": "app_name",
|
|
1595
|
+
"title": "app_name",
|
|
1596
|
+
"field.title": "field.name",
|
|
1597
|
+
"field.label": "field.name",
|
|
1598
|
+
"field.fields": "field.subfields",
|
|
1599
|
+
"field.type_id": "field.type",
|
|
1600
|
+
"field.relationMode": "field.relation_mode",
|
|
1601
|
+
"field.selection_mode": "field.relation_mode",
|
|
1602
|
+
"field.selectionMode": "field.relation_mode",
|
|
1603
|
+
"field.multiple": "field.relation_mode",
|
|
1604
|
+
"field.allow_multiple": "field.relation_mode",
|
|
1605
|
+
"field.optional_data_num": "field.relation_mode",
|
|
1606
|
+
"field.optionalDataNum": "field.relation_mode",
|
|
1607
|
+
},
|
|
1608
|
+
"allowed_values": {
|
|
1609
|
+
"field.type": [member.value for member in PublicFieldType],
|
|
1610
|
+
"field.relation_mode": [member.value for member in PublicRelationMode],
|
|
1611
|
+
"field_type_ids": sorted(FIELD_TYPE_ID_ALIASES.keys()),
|
|
1612
|
+
},
|
|
1613
|
+
"minimal_example": {
|
|
1614
|
+
"profile": "default",
|
|
1615
|
+
"app_name": "研发项目管理",
|
|
1616
|
+
"package_tag_id": 1001,
|
|
1617
|
+
"create_if_missing": True,
|
|
1618
|
+
"add_fields": [{"name": "项目名称", "type": "text"}],
|
|
1619
|
+
"update_fields": [],
|
|
1620
|
+
"remove_fields": [],
|
|
1621
|
+
},
|
|
1622
|
+
"relation_example": {
|
|
1623
|
+
"profile": "default",
|
|
1624
|
+
"app_key": "APP_ITERATION",
|
|
1625
|
+
"add_fields": [
|
|
1626
|
+
{
|
|
1627
|
+
"name": "需求反馈引用",
|
|
1628
|
+
"type": "relation",
|
|
1629
|
+
"target_app_key": "APP_FEEDBACK",
|
|
1630
|
+
"relation_mode": "multiple",
|
|
1631
|
+
"display_field": {"name": "反馈标题"},
|
|
1632
|
+
"visible_fields": [{"name": "反馈标题"}, {"name": "优先级判断"}],
|
|
1633
|
+
}
|
|
1634
|
+
],
|
|
1635
|
+
"update_fields": [],
|
|
1636
|
+
"remove_fields": [],
|
|
1637
|
+
},
|
|
1638
|
+
},
|
|
1639
|
+
"app_schema_apply": {
|
|
1640
|
+
"allowed_keys": ["app_key", "package_tag_id", "app_name", "create_if_missing", "publish", "add_fields", "update_fields", "remove_fields"],
|
|
1641
|
+
"aliases": {
|
|
1642
|
+
"app_title": "app_name",
|
|
1643
|
+
"title": "app_name",
|
|
1644
|
+
"field.title": "field.name",
|
|
1645
|
+
"field.label": "field.name",
|
|
1646
|
+
"field.fields": "field.subfields",
|
|
1647
|
+
"field.type_id": "field.type",
|
|
1648
|
+
"field.relationMode": "field.relation_mode",
|
|
1649
|
+
"field.selection_mode": "field.relation_mode",
|
|
1650
|
+
"field.selectionMode": "field.relation_mode",
|
|
1651
|
+
"field.multiple": "field.relation_mode",
|
|
1652
|
+
"field.allow_multiple": "field.relation_mode",
|
|
1653
|
+
"field.optional_data_num": "field.relation_mode",
|
|
1654
|
+
"field.optionalDataNum": "field.relation_mode",
|
|
1655
|
+
},
|
|
1656
|
+
"allowed_values": {
|
|
1657
|
+
"field.type": [member.value for member in PublicFieldType],
|
|
1658
|
+
"field.relation_mode": [member.value for member in PublicRelationMode],
|
|
1659
|
+
"field_type_ids": sorted(FIELD_TYPE_ID_ALIASES.keys()),
|
|
1660
|
+
},
|
|
1661
|
+
"execution_notes": [
|
|
1662
|
+
"multiple relation fields are backend-risky; read verification.relation_field_limit_verified and warnings before declaring the schema stable",
|
|
1663
|
+
"backend 49614 is normalized to MULTIPLE_RELATION_FIELDS_UNSUPPORTED with a workaround message",
|
|
1664
|
+
"relation_mode=multiple maps to referenceConfig.optionalDataNum=0",
|
|
1665
|
+
],
|
|
1666
|
+
"minimal_example": {
|
|
1667
|
+
"profile": "default",
|
|
1668
|
+
"app_name": "研发项目管理",
|
|
1669
|
+
"package_tag_id": 1001,
|
|
1670
|
+
"create_if_missing": True,
|
|
1671
|
+
"publish": True,
|
|
1672
|
+
"add_fields": [{"name": "项目名称", "type": "text"}],
|
|
1673
|
+
"update_fields": [],
|
|
1674
|
+
"remove_fields": [],
|
|
1675
|
+
},
|
|
1676
|
+
"relation_example": {
|
|
1677
|
+
"profile": "default",
|
|
1678
|
+
"app_key": "APP_ITERATION",
|
|
1679
|
+
"publish": True,
|
|
1680
|
+
"add_fields": [
|
|
1681
|
+
{
|
|
1682
|
+
"name": "需求反馈引用",
|
|
1683
|
+
"type": "relation",
|
|
1684
|
+
"target_app_key": "APP_FEEDBACK",
|
|
1685
|
+
"relation_mode": "multiple",
|
|
1686
|
+
"display_field": {"name": "反馈标题"},
|
|
1687
|
+
"visible_fields": [{"name": "反馈标题"}, {"name": "优先级判断"}],
|
|
1688
|
+
}
|
|
1689
|
+
],
|
|
1690
|
+
"update_fields": [],
|
|
1691
|
+
"remove_fields": [],
|
|
1692
|
+
},
|
|
1693
|
+
},
|
|
1694
|
+
"app_layout_plan": {
|
|
1695
|
+
"allowed_keys": ["app_key", "mode", "sections", "preset"],
|
|
1696
|
+
"aliases": {"overwrite": "replace", "sectionId": "section_id"},
|
|
1697
|
+
"section_allowed_keys": ["section_id", "title", "rows"],
|
|
1698
|
+
"section_aliases": {
|
|
1699
|
+
"name": "title",
|
|
1700
|
+
"sectionId": "section_id",
|
|
1701
|
+
"fields": "rows",
|
|
1702
|
+
"field_ids": "rows",
|
|
1703
|
+
"columns": "rows_chunk_size",
|
|
1704
|
+
},
|
|
1705
|
+
"allowed_values": {"mode": [member.value for member in LayoutApplyMode], "preset": [member.value for member in LayoutPreset]},
|
|
1706
|
+
"minimal_section_example": {"title": "基础信息", "rows": [["字段A", "字段B"]]},
|
|
1707
|
+
"minimal_example": {
|
|
1708
|
+
"profile": "default",
|
|
1709
|
+
"app_key": "APP_KEY",
|
|
1710
|
+
"mode": "merge",
|
|
1711
|
+
"sections": [{"title": "基础信息", "rows": [["字段A", "字段B"]]}],
|
|
1712
|
+
},
|
|
1713
|
+
"preset_example": {"profile": "default", "app_key": "APP_KEY", "mode": "merge", "preset": "balanced", "sections": []},
|
|
1714
|
+
},
|
|
1715
|
+
"app_layout_apply": {
|
|
1716
|
+
"allowed_keys": ["app_key", "mode", "publish", "sections"],
|
|
1717
|
+
"aliases": {"overwrite": "replace", "sectionId": "section_id"},
|
|
1718
|
+
"section_allowed_keys": ["section_id", "title", "rows"],
|
|
1719
|
+
"section_aliases": {
|
|
1720
|
+
"name": "title",
|
|
1721
|
+
"sectionId": "section_id",
|
|
1722
|
+
"fields": "rows",
|
|
1723
|
+
"field_ids": "rows",
|
|
1724
|
+
"columns": "rows_chunk_size",
|
|
1725
|
+
},
|
|
1726
|
+
"allowed_values": {"mode": [member.value for member in LayoutApplyMode]},
|
|
1727
|
+
"execution_notes": [
|
|
1728
|
+
"layout verification is split into layout_verified and layout_summary_verified",
|
|
1729
|
+
"LAYOUT_SUMMARY_UNVERIFIED means raw form readback is stronger than the compact summary",
|
|
1730
|
+
],
|
|
1731
|
+
"minimal_section_example": {"title": "基础信息", "rows": [["字段A", "字段B"]]},
|
|
1732
|
+
"minimal_example": {
|
|
1733
|
+
"profile": "default",
|
|
1734
|
+
"app_key": "APP_KEY",
|
|
1735
|
+
"mode": "merge",
|
|
1736
|
+
"publish": True,
|
|
1737
|
+
"sections": [{"title": "基础信息", "rows": [["项目名称", "项目负责人"]]}],
|
|
1738
|
+
},
|
|
1739
|
+
},
|
|
1740
|
+
"app_flow_plan": {
|
|
1741
|
+
"allowed_keys": ["app_key", "mode", "nodes", "transitions", "preset"],
|
|
1742
|
+
"aliases": {
|
|
1743
|
+
"overwrite": "replace",
|
|
1744
|
+
"base_preset": "preset",
|
|
1745
|
+
"default_approval": "basic_approval",
|
|
1746
|
+
"node.role_names": "node.assignees.role_names",
|
|
1747
|
+
"node.role_ids": "node.assignees.role_ids",
|
|
1748
|
+
"node.member_names": "node.assignees.member_names",
|
|
1749
|
+
"node.member_emails": "node.assignees.member_emails",
|
|
1750
|
+
"node.member_uids": "node.assignees.member_uids",
|
|
1751
|
+
"node.editable_fields": "node.permissions.editable_fields",
|
|
1752
|
+
"default_approval": "basic_approval",
|
|
1753
|
+
},
|
|
1754
|
+
"allowed_values": {
|
|
1755
|
+
"mode": ["replace"],
|
|
1756
|
+
"preset": [member.value for member in FlowPreset],
|
|
1757
|
+
"node.type": PUBLIC_STABLE_FLOW_NODE_TYPES,
|
|
1758
|
+
},
|
|
1759
|
+
"dependency_hints": [
|
|
1760
|
+
"approval-style workflows require an explicit status field",
|
|
1761
|
+
"approve/fill/copy nodes require at least one assignee",
|
|
1762
|
+
],
|
|
1763
|
+
"execution_notes": [
|
|
1764
|
+
"public flow building is intentionally limited to linear workflows",
|
|
1765
|
+
"branch and condition nodes are disabled because the backend workflow route is not front-end stable for these node types",
|
|
1766
|
+
],
|
|
1767
|
+
"minimal_example": {
|
|
1768
|
+
"profile": "default",
|
|
1769
|
+
"app_key": "APP_KEY",
|
|
1770
|
+
"mode": "replace",
|
|
1771
|
+
"preset": "basic_approval",
|
|
1772
|
+
"nodes": [
|
|
1773
|
+
{
|
|
1774
|
+
"id": "approve_1",
|
|
1775
|
+
"type": "approve",
|
|
1776
|
+
"name": "部门审批",
|
|
1777
|
+
"assignees": {"role_names": ["项目经理"]},
|
|
1778
|
+
"permissions": {"editable_fields": ["状态", "审批意见"]},
|
|
1779
|
+
}
|
|
1780
|
+
],
|
|
1781
|
+
"transitions": [],
|
|
1782
|
+
},
|
|
1783
|
+
},
|
|
1784
|
+
"app_flow_apply": {
|
|
1785
|
+
"allowed_keys": ["app_key", "mode", "publish", "nodes", "transitions"],
|
|
1786
|
+
"aliases": {
|
|
1787
|
+
"overwrite": "replace",
|
|
1788
|
+
"node.role_names": "node.assignees.role_names",
|
|
1789
|
+
"node.role_ids": "node.assignees.role_ids",
|
|
1790
|
+
"node.member_names": "node.assignees.member_names",
|
|
1791
|
+
"node.member_emails": "node.assignees.member_emails",
|
|
1792
|
+
"node.member_uids": "node.assignees.member_uids",
|
|
1793
|
+
"node.editable_fields": "node.permissions.editable_fields",
|
|
1794
|
+
},
|
|
1795
|
+
"allowed_values": {
|
|
1796
|
+
"mode": ["replace"],
|
|
1797
|
+
"node.type": PUBLIC_STABLE_FLOW_NODE_TYPES,
|
|
1798
|
+
},
|
|
1799
|
+
"dependency_hints": [
|
|
1800
|
+
"approval-style workflows require an explicit status field",
|
|
1801
|
+
"approve/fill/copy nodes require at least one assignee",
|
|
1802
|
+
],
|
|
1803
|
+
"execution_notes": [
|
|
1804
|
+
"public flow building is intentionally limited to linear workflows",
|
|
1805
|
+
"branch and condition nodes are disabled because the backend workflow route is not front-end stable for these node types",
|
|
1806
|
+
"workflow verification only covers linear node structure in the public tool surface",
|
|
1807
|
+
],
|
|
1808
|
+
"minimal_example": {
|
|
1809
|
+
"profile": "default",
|
|
1810
|
+
"app_key": "APP_KEY",
|
|
1811
|
+
"mode": "replace",
|
|
1812
|
+
"publish": True,
|
|
1813
|
+
"nodes": [
|
|
1814
|
+
{"id": "start", "type": "start", "name": "发起"},
|
|
1815
|
+
{
|
|
1816
|
+
"id": "approve_1",
|
|
1817
|
+
"type": "approve",
|
|
1818
|
+
"name": "部门审批",
|
|
1819
|
+
"assignees": {"role_names": ["项目经理"]},
|
|
1820
|
+
"permissions": {"editable_fields": ["状态", "审批意见"]},
|
|
1821
|
+
},
|
|
1822
|
+
{"id": "end", "type": "end", "name": "结束"},
|
|
1823
|
+
],
|
|
1824
|
+
"transitions": [{"from": "start", "to": "approve_1"}, {"from": "approve_1", "to": "end"}],
|
|
1825
|
+
},
|
|
1826
|
+
},
|
|
1827
|
+
"app_views_plan": {
|
|
1828
|
+
"allowed_keys": ["app_key", "upsert_views", "remove_views", "preset", "upsert_views[].view_key"],
|
|
1829
|
+
"aliases": {
|
|
1830
|
+
"fields": "columns",
|
|
1831
|
+
"column_names": "columns",
|
|
1832
|
+
"columnNames": "columns",
|
|
1833
|
+
"viewKey": "view_key",
|
|
1834
|
+
"tableView": "table",
|
|
1835
|
+
"cardView": "card",
|
|
1836
|
+
"kanban": "board",
|
|
1837
|
+
"filter_rules": "filters",
|
|
1838
|
+
"filterRules": "filters",
|
|
1839
|
+
"startField": "start_field",
|
|
1840
|
+
"endField": "end_field",
|
|
1841
|
+
"titleField": "title_field",
|
|
1842
|
+
},
|
|
1843
|
+
"allowed_values": {
|
|
1844
|
+
"preset": [member.value for member in ViewsPreset],
|
|
1845
|
+
"view.type": [member.value for member in PublicViewType],
|
|
1846
|
+
"view.filter.operator": [member.value for member in ViewFilterOperator],
|
|
1847
|
+
},
|
|
1848
|
+
"minimal_example": {
|
|
1849
|
+
"profile": "default",
|
|
1850
|
+
"app_key": "APP_KEY",
|
|
1851
|
+
"upsert_views": [{"name": "全部数据", "type": "table", "columns": ["项目名称"]}],
|
|
1852
|
+
"remove_views": [],
|
|
1853
|
+
},
|
|
1854
|
+
"gantt_example": {
|
|
1855
|
+
"profile": "default",
|
|
1856
|
+
"app_key": "APP_KEY",
|
|
1857
|
+
"upsert_views": [
|
|
1858
|
+
{
|
|
1859
|
+
"name": "项目甘特图",
|
|
1860
|
+
"type": "gantt",
|
|
1861
|
+
"columns": ["项目名称", "开始日期", "结束日期"],
|
|
1862
|
+
"start_field": "开始日期",
|
|
1863
|
+
"end_field": "结束日期",
|
|
1864
|
+
"title_field": "项目名称",
|
|
1865
|
+
"filters": [{"field_name": "状态", "operator": "eq", "value": "进行中"}],
|
|
1866
|
+
}
|
|
1867
|
+
],
|
|
1868
|
+
"remove_views": [],
|
|
1869
|
+
},
|
|
1870
|
+
},
|
|
1871
|
+
"app_views_apply": {
|
|
1872
|
+
"allowed_keys": ["app_key", "publish", "upsert_views", "remove_views", "upsert_views[].view_key"],
|
|
1873
|
+
"aliases": {
|
|
1874
|
+
"fields": "columns",
|
|
1875
|
+
"column_names": "columns",
|
|
1876
|
+
"columnNames": "columns",
|
|
1877
|
+
"viewKey": "view_key",
|
|
1878
|
+
"tableView": "table",
|
|
1879
|
+
"cardView": "card",
|
|
1880
|
+
"kanban": "board",
|
|
1881
|
+
"filter_rules": "filters",
|
|
1882
|
+
"filterRules": "filters",
|
|
1883
|
+
"startField": "start_field",
|
|
1884
|
+
"endField": "end_field",
|
|
1885
|
+
"titleField": "title_field",
|
|
1886
|
+
},
|
|
1887
|
+
"allowed_values": {
|
|
1888
|
+
"view.type": [member.value for member in PublicViewType],
|
|
1889
|
+
"view.filter.operator": [member.value for member in ViewFilterOperator],
|
|
1890
|
+
},
|
|
1891
|
+
"execution_notes": [
|
|
1892
|
+
"apply may return partial_success when some views land and others fail",
|
|
1893
|
+
"when duplicate view names exist, supply view_key to target the exact view",
|
|
1894
|
+
"read back app_read_views_summary after any failed or partial view apply",
|
|
1895
|
+
"view existence verification and saved-filter verification are separate; treat filters as unverified until verification.view_filters_verified is true",
|
|
1896
|
+
],
|
|
1897
|
+
"minimal_example": {
|
|
1898
|
+
"profile": "default",
|
|
1899
|
+
"app_key": "APP_KEY",
|
|
1900
|
+
"publish": True,
|
|
1901
|
+
"upsert_views": [{"name": "全部数据", "type": "table", "columns": ["项目名称"]}],
|
|
1902
|
+
"remove_views": [],
|
|
1903
|
+
},
|
|
1904
|
+
"gantt_example": {
|
|
1905
|
+
"profile": "default",
|
|
1906
|
+
"app_key": "APP_KEY",
|
|
1907
|
+
"publish": True,
|
|
1908
|
+
"upsert_views": [
|
|
1909
|
+
{
|
|
1910
|
+
"name": "项目甘特图",
|
|
1911
|
+
"type": "gantt",
|
|
1912
|
+
"columns": ["项目名称", "开始日期", "结束日期"],
|
|
1913
|
+
"start_field": "开始日期",
|
|
1914
|
+
"end_field": "结束日期",
|
|
1915
|
+
"title_field": "项目名称",
|
|
1916
|
+
"filters": [{"field_name": "状态", "operator": "eq", "value": "进行中"}],
|
|
1917
|
+
}
|
|
1918
|
+
],
|
|
1919
|
+
"remove_views": [],
|
|
1920
|
+
},
|
|
1921
|
+
},
|
|
1922
|
+
"app_read_charts_summary": {
|
|
1923
|
+
"allowed_keys": ["app_key"],
|
|
1924
|
+
"aliases": {},
|
|
1925
|
+
"allowed_values": {},
|
|
1926
|
+
"execution_notes": [
|
|
1927
|
+
"returns a compact current chart inventory for one app",
|
|
1928
|
+
"use this before app_charts_apply when you need exact current chart_id values",
|
|
1929
|
+
"chart summaries do not include full qingbi config payloads",
|
|
1930
|
+
],
|
|
1931
|
+
"minimal_example": {
|
|
1932
|
+
"profile": "default",
|
|
1933
|
+
"app_key": "APP_KEY",
|
|
1934
|
+
},
|
|
1935
|
+
},
|
|
1936
|
+
"app_charts_apply": {
|
|
1937
|
+
"allowed_keys": ["app_key", "upsert_charts", "remove_chart_ids", "reorder_chart_ids"],
|
|
1938
|
+
"aliases": {
|
|
1939
|
+
"chart.id": "chart.chart_id",
|
|
1940
|
+
"chart.type": "chart.chart_type",
|
|
1941
|
+
"chart.dimension_fields": "chart.dimension_field_ids",
|
|
1942
|
+
"chart.indicator_fields": "chart.indicator_field_ids",
|
|
1943
|
+
"chart.metric_field_ids": "chart.indicator_field_ids",
|
|
1944
|
+
"chart.filter.op": "chart.filter.operator",
|
|
1945
|
+
},
|
|
1946
|
+
"allowed_values": {
|
|
1947
|
+
"chart.chart_type": [member.value for member in PublicChartType],
|
|
1948
|
+
"chart.filter.operator": [member.value for member in ViewFilterOperator],
|
|
1949
|
+
},
|
|
1950
|
+
"execution_notes": [
|
|
1951
|
+
"app_charts_apply is immediate-live and does not publish",
|
|
1952
|
+
"chart matching precedence is chart_id first, then exact unique chart name",
|
|
1953
|
+
"when chart names are not unique, supply chart_id instead of guessing by name",
|
|
1954
|
+
"successful create results must return a real backend chart_id",
|
|
1955
|
+
],
|
|
1956
|
+
"minimal_example": {
|
|
1957
|
+
"profile": "default",
|
|
1958
|
+
"app_key": "APP_KEY",
|
|
1959
|
+
"upsert_charts": [{"name": "数据总量", "chart_type": "target", "indicator_field_ids": []}],
|
|
1960
|
+
"remove_chart_ids": [],
|
|
1961
|
+
"reorder_chart_ids": [],
|
|
1962
|
+
},
|
|
1963
|
+
},
|
|
1964
|
+
"chart_apply": {
|
|
1965
|
+
"allowed_keys": ["app_key", "upsert_charts", "remove_chart_ids", "reorder_chart_ids"],
|
|
1966
|
+
"aliases": {
|
|
1967
|
+
"legacy_tool_name": "app_charts_apply",
|
|
1968
|
+
},
|
|
1969
|
+
"allowed_values": {
|
|
1970
|
+
"chart.chart_type": [member.value for member in PublicChartType],
|
|
1971
|
+
"chart.filter.operator": [member.value for member in ViewFilterOperator],
|
|
1972
|
+
},
|
|
1973
|
+
"execution_notes": [
|
|
1974
|
+
"legacy compatibility alias; prefer app_charts_apply in new builder flows",
|
|
1975
|
+
"behavior matches app_charts_apply exactly",
|
|
1976
|
+
],
|
|
1977
|
+
"minimal_example": {
|
|
1978
|
+
"profile": "default",
|
|
1979
|
+
"app_key": "APP_KEY",
|
|
1980
|
+
"upsert_charts": [{"name": "数据总量", "chart_type": "target", "indicator_field_ids": []}],
|
|
1981
|
+
"remove_chart_ids": [],
|
|
1982
|
+
"reorder_chart_ids": [],
|
|
1983
|
+
},
|
|
1984
|
+
},
|
|
1985
|
+
"portal_read_summary": {
|
|
1986
|
+
"allowed_keys": ["dash_key", "being_draft"],
|
|
1987
|
+
"aliases": {"beingDraft": "being_draft"},
|
|
1988
|
+
"allowed_values": {},
|
|
1989
|
+
"execution_notes": [
|
|
1990
|
+
"returns a compact portal summary instead of the raw dash payload",
|
|
1991
|
+
"being_draft=true reads the current draft view; being_draft=false reads live",
|
|
1992
|
+
"use this before portal_apply when you need the current section inventory or target dash metadata",
|
|
1993
|
+
],
|
|
1994
|
+
"minimal_example": {
|
|
1995
|
+
"profile": "default",
|
|
1996
|
+
"dash_key": "DASH_KEY",
|
|
1997
|
+
"being_draft": True,
|
|
1998
|
+
},
|
|
1999
|
+
},
|
|
2000
|
+
"portal_apply": {
|
|
2001
|
+
"allowed_keys": ["dash_key", "dash_name", "package_tag_id", "publish", "sections", "auth", "icon", "color", "hide_copyright", "dash_global_config", "config"],
|
|
2002
|
+
"aliases": {
|
|
2003
|
+
"sourceType": "source_type",
|
|
2004
|
+
"chartRef": "chart_ref",
|
|
2005
|
+
"viewRef": "view_ref",
|
|
2006
|
+
"dashStyleConfigBO": "dash_style_config",
|
|
2007
|
+
},
|
|
2008
|
+
"section_allowed_keys": ["title", "source_type", "position", "dash_style_config", "config", "chart_ref", "view_ref", "text", "url"],
|
|
2009
|
+
"section_aliases": {
|
|
2010
|
+
"sourceType": "source_type",
|
|
2011
|
+
"chartRef": "chart_ref",
|
|
2012
|
+
"viewRef": "view_ref",
|
|
2013
|
+
"dashStyleConfigBO": "dash_style_config",
|
|
2014
|
+
},
|
|
2015
|
+
"allowed_values": {"section.source_type": ["chart", "view", "grid", "filter", "text", "link"]},
|
|
2016
|
+
"execution_notes": [
|
|
2017
|
+
"portal_apply uses replace semantics for sections",
|
|
2018
|
+
"remove a section by omitting it from the new sections list",
|
|
2019
|
+
"package_tag_id is required when creating a new portal",
|
|
2020
|
+
"publish=false only guarantees draft and base-info updates; it does not claim live has changed",
|
|
2021
|
+
"chart_ref resolves by chart_id first, then exact unique chart_name",
|
|
2022
|
+
"view_ref resolves by view_key first, then exact unique view_name",
|
|
2023
|
+
"position.pc/mobile is the canonical portal layout shape",
|
|
2024
|
+
],
|
|
2025
|
+
"minimal_example": {
|
|
2026
|
+
"profile": "default",
|
|
2027
|
+
"dash_name": "经营门户",
|
|
2028
|
+
"package_tag_id": 1001,
|
|
2029
|
+
"publish": True,
|
|
2030
|
+
"sections": [
|
|
2031
|
+
{
|
|
2032
|
+
"title": "经营概览",
|
|
2033
|
+
"source_type": "chart",
|
|
2034
|
+
"chart_ref": {"app_key": "APP_KEY", "chart_name": "数据总量"},
|
|
2035
|
+
"position": {
|
|
2036
|
+
"pc": {"x": 0, "y": 0, "cols": 12, "rows": 6},
|
|
2037
|
+
"mobile": {"x": 0, "y": 0, "cols": 6, "rows": 6},
|
|
2038
|
+
},
|
|
2039
|
+
}
|
|
2040
|
+
],
|
|
2041
|
+
},
|
|
2042
|
+
"minimal_section_example": {
|
|
2043
|
+
"title": "订单概览",
|
|
2044
|
+
"source_type": "view",
|
|
2045
|
+
"view_ref": {"app_key": "APP_KEY", "view_key": "VIEW_KEY"},
|
|
2046
|
+
"position": {
|
|
2047
|
+
"pc": {"x": 0, "y": 0, "cols": 24, "rows": 8},
|
|
2048
|
+
"mobile": {"x": 0, "y": 0, "cols": 6, "rows": 8},
|
|
2049
|
+
},
|
|
2050
|
+
},
|
|
2051
|
+
},
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
_PRIVATE_BUILDER_TOOL_CONTRACTS = {
|
|
2055
|
+
"app_schema_plan",
|
|
2056
|
+
"app_layout_plan",
|
|
2057
|
+
"app_flow_plan",
|
|
2058
|
+
"app_views_plan",
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
_BUILDER_TOOL_CONTRACT_ALIASES = {
|
|
2062
|
+
"chart_apply": "app_charts_apply",
|
|
2063
|
+
}
|