@josephyan/qingflow-app-user-mcp 0.2.0-beta.93 → 0.2.0-beta.95
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
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.
|
|
6
|
+
npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.95
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.
|
|
12
|
+
npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.95 qingflow-app-user-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import base64
|
|
3
4
|
from copy import deepcopy
|
|
4
5
|
from dataclasses import dataclass, field
|
|
5
6
|
import json
|
|
6
7
|
import os
|
|
8
|
+
import random
|
|
7
9
|
import re
|
|
10
|
+
import string
|
|
8
11
|
import tempfile
|
|
9
12
|
from typing import Any, cast
|
|
13
|
+
from urllib.parse import quote_plus, unquote_plus
|
|
10
14
|
from uuid import uuid4
|
|
11
15
|
|
|
12
16
|
from ..backend_client import BackendRequestContext
|
|
@@ -143,6 +147,7 @@ JUDGE_EQUAL_ANY = 9
|
|
|
143
147
|
JUDGE_FUZZY_MATCH = 19
|
|
144
148
|
JUDGE_INCLUDE_ANY = 20
|
|
145
149
|
DEFAULT_TYPE_RELATION = 2
|
|
150
|
+
DEFAULT_TYPE_FORMULA = 3
|
|
146
151
|
RELATION_TYPE_Q_LINKER = 2
|
|
147
152
|
RELATION_TYPE_CODE_BLOCK = 3
|
|
148
153
|
|
|
@@ -12839,6 +12844,275 @@ _QUESTION_RELATION_SAVE_KEYS = (
|
|
|
12839
12844
|
"sortConfig",
|
|
12840
12845
|
)
|
|
12841
12846
|
|
|
12847
|
+
_RELATION_QUESTION_SAVE_KEYS = (
|
|
12848
|
+
"queId",
|
|
12849
|
+
"queTempId",
|
|
12850
|
+
"queType",
|
|
12851
|
+
"queOriginType",
|
|
12852
|
+
"queTitle",
|
|
12853
|
+
"queWidth",
|
|
12854
|
+
"scanType",
|
|
12855
|
+
"status",
|
|
12856
|
+
"required",
|
|
12857
|
+
"queHint",
|
|
12858
|
+
"linkedQuestions",
|
|
12859
|
+
"logicalShow",
|
|
12860
|
+
"queDefaultType",
|
|
12861
|
+
"queDefaultValue",
|
|
12862
|
+
"queDefaultValues",
|
|
12863
|
+
"subQueWidth",
|
|
12864
|
+
"innerQuestions",
|
|
12865
|
+
"minOpts",
|
|
12866
|
+
"maxOpts",
|
|
12867
|
+
"beingHide",
|
|
12868
|
+
"beingDesensitized",
|
|
12869
|
+
"relationDisplayMode",
|
|
12870
|
+
"customRenderConfig",
|
|
12871
|
+
)
|
|
12872
|
+
|
|
12873
|
+
_REFERENCE_CONFIG_SAVE_KEYS = (
|
|
12874
|
+
"referAppKey",
|
|
12875
|
+
"referQueId",
|
|
12876
|
+
"customButtonText",
|
|
12877
|
+
"beingTableSource",
|
|
12878
|
+
"referMatchRules",
|
|
12879
|
+
"canAddData",
|
|
12880
|
+
"dataAdditionButtonText",
|
|
12881
|
+
"canViewProcessLog",
|
|
12882
|
+
"optionalDataNum",
|
|
12883
|
+
"beingDataLogVisible",
|
|
12884
|
+
"beingDefaultFormulaAutoFillEnabled",
|
|
12885
|
+
"defaultValueMatchRules",
|
|
12886
|
+
"configShowForm",
|
|
12887
|
+
"configSortFieldId",
|
|
12888
|
+
"configAsc",
|
|
12889
|
+
"dataShowForm",
|
|
12890
|
+
"defaultRow",
|
|
12891
|
+
"fieldNameShow",
|
|
12892
|
+
"dataSortFieldId",
|
|
12893
|
+
"dataSortAsc",
|
|
12894
|
+
)
|
|
12895
|
+
|
|
12896
|
+
_REFERENCE_QUESTION_SAVE_KEYS = (
|
|
12897
|
+
"queId",
|
|
12898
|
+
"queTitle",
|
|
12899
|
+
"queType",
|
|
12900
|
+
"queAuth",
|
|
12901
|
+
"ordinal",
|
|
12902
|
+
)
|
|
12903
|
+
|
|
12904
|
+
_REFERENCE_FILL_RULE_SAVE_KEYS = (
|
|
12905
|
+
"queId",
|
|
12906
|
+
"relatedQueId",
|
|
12907
|
+
"queTitle",
|
|
12908
|
+
"relatedQueTitle",
|
|
12909
|
+
)
|
|
12910
|
+
|
|
12911
|
+
_REFERENCE_AUTH_QUESTION_SAVE_KEYS = (
|
|
12912
|
+
"queId",
|
|
12913
|
+
"queAuth",
|
|
12914
|
+
)
|
|
12915
|
+
|
|
12916
|
+
|
|
12917
|
+
def _copy_present_keys(
|
|
12918
|
+
source: dict[str, Any],
|
|
12919
|
+
keys: tuple[str, ...],
|
|
12920
|
+
*,
|
|
12921
|
+
keep_none_keys: tuple[str, ...] = (),
|
|
12922
|
+
) -> dict[str, Any]:
|
|
12923
|
+
payload: dict[str, Any] = {}
|
|
12924
|
+
keep_none = set(keep_none_keys)
|
|
12925
|
+
for key in keys:
|
|
12926
|
+
if key not in source:
|
|
12927
|
+
continue
|
|
12928
|
+
value = source.get(key)
|
|
12929
|
+
if value is None and key not in keep_none:
|
|
12930
|
+
continue
|
|
12931
|
+
payload[key] = deepcopy(value)
|
|
12932
|
+
return payload
|
|
12933
|
+
|
|
12934
|
+
|
|
12935
|
+
def _looks_like_backend_encoded_formula(value: str) -> bool:
|
|
12936
|
+
if len(value) <= 32:
|
|
12937
|
+
return False
|
|
12938
|
+
encoded = value[16:-16]
|
|
12939
|
+
if not encoded:
|
|
12940
|
+
return False
|
|
12941
|
+
try:
|
|
12942
|
+
decoded = base64.b64decode(encoded, validate=True).decode("utf-8")
|
|
12943
|
+
unquote_plus(decoded)
|
|
12944
|
+
except Exception:
|
|
12945
|
+
return False
|
|
12946
|
+
return True
|
|
12947
|
+
|
|
12948
|
+
|
|
12949
|
+
def _encode_formula_for_backend_save(value: Any) -> Any:
|
|
12950
|
+
if not isinstance(value, str) or not value:
|
|
12951
|
+
return value
|
|
12952
|
+
if _looks_like_backend_encoded_formula(value):
|
|
12953
|
+
return value
|
|
12954
|
+
encoded = quote_plus(value, encoding="utf-8")
|
|
12955
|
+
b64_value = base64.b64encode(encoded.encode("utf-8")).decode("ascii")
|
|
12956
|
+
alphabet = string.ascii_letters + string.digits
|
|
12957
|
+
prefix = "".join(random.choice(alphabet) for _ in range(16))
|
|
12958
|
+
suffix = "".join(random.choice(alphabet) for _ in range(16))
|
|
12959
|
+
return f"{prefix}{b64_value}{suffix}"
|
|
12960
|
+
|
|
12961
|
+
|
|
12962
|
+
def _normalize_formula_defaults_for_save(value: Any) -> None:
|
|
12963
|
+
if isinstance(value, list):
|
|
12964
|
+
for item in value:
|
|
12965
|
+
_normalize_formula_defaults_for_save(item)
|
|
12966
|
+
return
|
|
12967
|
+
if not isinstance(value, dict):
|
|
12968
|
+
return
|
|
12969
|
+
if _coerce_any_int(value.get("queDefaultType")) == DEFAULT_TYPE_FORMULA and value.get("queDefaultValue"):
|
|
12970
|
+
value["queDefaultValue"] = _encode_formula_for_backend_save(value.get("queDefaultValue"))
|
|
12971
|
+
for key in ("subQuestions", "innerQuestions"):
|
|
12972
|
+
nested = value.get(key)
|
|
12973
|
+
if isinstance(nested, (list, dict)):
|
|
12974
|
+
_normalize_formula_defaults_for_save(nested)
|
|
12975
|
+
|
|
12976
|
+
|
|
12977
|
+
def _normalize_reference_question_for_save(value: Any, *, ordinal: int) -> dict[str, Any] | None:
|
|
12978
|
+
if not isinstance(value, dict):
|
|
12979
|
+
return None
|
|
12980
|
+
payload = _copy_present_keys(value, _REFERENCE_QUESTION_SAVE_KEYS)
|
|
12981
|
+
que_id = _coerce_any_int(value.get("queId"))
|
|
12982
|
+
if que_id is not None:
|
|
12983
|
+
payload["queId"] = que_id
|
|
12984
|
+
if "ordinal" not in payload:
|
|
12985
|
+
payload["ordinal"] = _coerce_nonnegative_int(value.get("ordinal"))
|
|
12986
|
+
if payload.get("ordinal") is None:
|
|
12987
|
+
payload["ordinal"] = ordinal
|
|
12988
|
+
if not any(key in payload for key in ("queId", "queTitle", "queType")):
|
|
12989
|
+
return None
|
|
12990
|
+
return payload
|
|
12991
|
+
|
|
12992
|
+
|
|
12993
|
+
def _normalize_reference_fill_rule_for_save(value: Any) -> dict[str, Any] | None:
|
|
12994
|
+
if not isinstance(value, dict):
|
|
12995
|
+
return None
|
|
12996
|
+
payload = _copy_present_keys(value, _REFERENCE_FILL_RULE_SAVE_KEYS)
|
|
12997
|
+
que_id = _coerce_nonnegative_int(value.get("queId"))
|
|
12998
|
+
related_que_id = _coerce_nonnegative_int(value.get("relatedQueId", value.get("referQueId")))
|
|
12999
|
+
if que_id is not None:
|
|
13000
|
+
payload["queId"] = que_id
|
|
13001
|
+
if related_que_id is not None:
|
|
13002
|
+
payload["relatedQueId"] = related_que_id
|
|
13003
|
+
if "relatedQueTitle" not in payload and value.get("referQueTitle") is not None:
|
|
13004
|
+
payload["relatedQueTitle"] = str(value.get("referQueTitle") or "")
|
|
13005
|
+
if "queId" not in payload or "relatedQueId" not in payload:
|
|
13006
|
+
return None
|
|
13007
|
+
return payload
|
|
13008
|
+
|
|
13009
|
+
|
|
13010
|
+
def _normalize_reference_auth_question_for_save(value: Any) -> dict[str, Any] | None:
|
|
13011
|
+
if not isinstance(value, dict):
|
|
13012
|
+
return None
|
|
13013
|
+
payload = _copy_present_keys(value, _REFERENCE_AUTH_QUESTION_SAVE_KEYS)
|
|
13014
|
+
que_id = _coerce_any_int(value.get("queId"))
|
|
13015
|
+
if que_id is not None:
|
|
13016
|
+
payload["queId"] = que_id
|
|
13017
|
+
que_auth = _coerce_nonnegative_int(value.get("queAuth"))
|
|
13018
|
+
if que_auth is not None:
|
|
13019
|
+
payload["queAuth"] = que_auth
|
|
13020
|
+
sub_ques = [
|
|
13021
|
+
item
|
|
13022
|
+
for item in (
|
|
13023
|
+
_normalize_reference_auth_question_for_save(raw_item)
|
|
13024
|
+
for raw_item in cast(list[Any], value.get("subQues") or [])
|
|
13025
|
+
)
|
|
13026
|
+
if item is not None
|
|
13027
|
+
]
|
|
13028
|
+
inner_ques = [
|
|
13029
|
+
item
|
|
13030
|
+
for item in (
|
|
13031
|
+
_normalize_reference_auth_question_for_save(raw_item)
|
|
13032
|
+
for raw_item in cast(list[Any], value.get("innerQues") or [])
|
|
13033
|
+
)
|
|
13034
|
+
if item is not None
|
|
13035
|
+
]
|
|
13036
|
+
if sub_ques or "subQues" in value:
|
|
13037
|
+
payload["subQues"] = sub_ques
|
|
13038
|
+
if inner_ques or "innerQues" in value:
|
|
13039
|
+
payload["innerQues"] = inner_ques
|
|
13040
|
+
if "queId" not in payload or "queAuth" not in payload:
|
|
13041
|
+
return None
|
|
13042
|
+
return payload
|
|
13043
|
+
|
|
13044
|
+
|
|
13045
|
+
def _normalize_reference_config_for_save(
|
|
13046
|
+
reference: Any,
|
|
13047
|
+
*,
|
|
13048
|
+
field: dict[str, Any],
|
|
13049
|
+
) -> dict[str, Any]:
|
|
13050
|
+
source = reference if isinstance(reference, dict) else {}
|
|
13051
|
+
payload = _copy_present_keys(source, _REFERENCE_CONFIG_SAVE_KEYS)
|
|
13052
|
+
if str(field.get("target_app_key") or "").strip():
|
|
13053
|
+
payload["referAppKey"] = str(field.get("target_app_key") or "").strip()
|
|
13054
|
+
if field.get("target_field_que_id") is not None:
|
|
13055
|
+
payload["referQueId"] = _coerce_nonnegative_int(field.get("target_field_que_id"))
|
|
13056
|
+
if field.get("field_name_show") is not None:
|
|
13057
|
+
payload["fieldNameShow"] = bool(field.get("field_name_show"))
|
|
13058
|
+
|
|
13059
|
+
refer_questions: list[dict[str, Any]] = []
|
|
13060
|
+
for index, raw_item in enumerate(cast(list[Any], source.get("referQuestions") or []), start=1):
|
|
13061
|
+
normalized_item = _normalize_reference_question_for_save(raw_item, ordinal=index)
|
|
13062
|
+
if normalized_item is not None:
|
|
13063
|
+
refer_questions.append(normalized_item)
|
|
13064
|
+
if refer_questions or "referQuestions" in source:
|
|
13065
|
+
payload["referQuestions"] = refer_questions
|
|
13066
|
+
|
|
13067
|
+
refer_fill_rules = [
|
|
13068
|
+
item
|
|
13069
|
+
for item in (
|
|
13070
|
+
_normalize_reference_fill_rule_for_save(raw_item)
|
|
13071
|
+
for raw_item in cast(list[Any], source.get("referFillRules") or [])
|
|
13072
|
+
)
|
|
13073
|
+
if item is not None
|
|
13074
|
+
]
|
|
13075
|
+
if refer_fill_rules or "referFillRules" in source:
|
|
13076
|
+
payload["referFillRules"] = refer_fill_rules
|
|
13077
|
+
|
|
13078
|
+
refer_auth_ques = [
|
|
13079
|
+
item
|
|
13080
|
+
for item in (
|
|
13081
|
+
_normalize_reference_auth_question_for_save(raw_item)
|
|
13082
|
+
for raw_item in cast(list[Any], source.get("referAuthQues") or [])
|
|
13083
|
+
)
|
|
13084
|
+
if item is not None
|
|
13085
|
+
]
|
|
13086
|
+
if refer_auth_ques or "referAuthQues" in source:
|
|
13087
|
+
payload["referAuthQues"] = refer_auth_ques
|
|
13088
|
+
|
|
13089
|
+
return payload
|
|
13090
|
+
|
|
13091
|
+
|
|
13092
|
+
def _normalize_relation_question_for_save(question: dict[str, Any], *, field: dict[str, Any]) -> dict[str, Any]:
|
|
13093
|
+
payload = _copy_present_keys(question, _RELATION_QUESTION_SAVE_KEYS)
|
|
13094
|
+
overlay_keys = _field_question_overlay_keys(field)
|
|
13095
|
+
que_id = _coerce_nonnegative_int(question.get("queId"))
|
|
13096
|
+
if que_id is not None:
|
|
13097
|
+
payload["queId"] = que_id
|
|
13098
|
+
que_temp_id = _coerce_nonnegative_int(question.get("queTempId"))
|
|
13099
|
+
if que_temp_id is not None and "queId" not in payload:
|
|
13100
|
+
payload["queTempId"] = que_temp_id
|
|
13101
|
+
payload["queType"] = _coerce_positive_int(question.get("queType")) or 25
|
|
13102
|
+
payload["queTitle"] = str(field.get("name") or question.get("queTitle") or "")
|
|
13103
|
+
if "required" in overlay_keys or "required" in question or field.get("required") is not None:
|
|
13104
|
+
payload["required"] = bool(field.get("required", question.get("required", False)))
|
|
13105
|
+
if "description" in overlay_keys:
|
|
13106
|
+
payload["queHint"] = "" if field.get("description") is None else str(field.get("description"))
|
|
13107
|
+
elif "queHint" in question and question.get("queHint") is not None:
|
|
13108
|
+
payload["queHint"] = str(question.get("queHint") or "")
|
|
13109
|
+
if field.get("default_type") is not None:
|
|
13110
|
+
payload["queDefaultType"] = _coerce_positive_int(field.get("default_type")) or 1
|
|
13111
|
+
if "default_value" in field:
|
|
13112
|
+
payload["queDefaultValue"] = field.get("default_value")
|
|
13113
|
+
payload["referenceConfig"] = _normalize_reference_config_for_save(question.get("referenceConfig"), field=field)
|
|
13114
|
+
return payload
|
|
13115
|
+
|
|
12842
13116
|
|
|
12843
13117
|
def _build_form_save_base_payload(current_schema: dict[str, Any], title: str) -> dict[str, Any]:
|
|
12844
13118
|
payload: dict[str, Any] = {"formTitle": title}
|
|
@@ -12863,6 +13137,8 @@ def _normalize_question_relations_for_save(question_relations: list[dict[str, An
|
|
|
12863
13137
|
value = relation.get(key)
|
|
12864
13138
|
if value is None:
|
|
12865
13139
|
continue
|
|
13140
|
+
if key == "matchRuleFormula":
|
|
13141
|
+
value = _encode_formula_for_backend_save(value)
|
|
12866
13142
|
item[key] = deepcopy(value)
|
|
12867
13143
|
if item:
|
|
12868
13144
|
normalized.append(item)
|
|
@@ -12899,7 +13175,7 @@ def _sync_question_title_references(value: Any, *, by_que_id: dict[int, str], by
|
|
|
12899
13175
|
if not isinstance(value, dict):
|
|
12900
13176
|
return
|
|
12901
13177
|
|
|
12902
|
-
title_keys = ("queTitle", "
|
|
13178
|
+
title_keys = ("queTitle", "_field_id")
|
|
12903
13179
|
que_id = _coerce_nonnegative_int(value.get("queId"))
|
|
12904
13180
|
replacement = None
|
|
12905
13181
|
if que_id is not None and que_id in by_que_id:
|
|
@@ -12936,6 +13212,8 @@ def _materialize_preserved_question(field: dict[str, Any]) -> dict[str, Any] | N
|
|
|
12936
13212
|
if "description" in overlay_keys:
|
|
12937
13213
|
description = field.get("description")
|
|
12938
13214
|
template["queHint"] = "" if description is None else str(description)
|
|
13215
|
+
if str(field.get("type") or "") == FieldType.relation.value:
|
|
13216
|
+
return _normalize_relation_question_for_save(template, field=field)
|
|
12939
13217
|
return template
|
|
12940
13218
|
|
|
12941
13219
|
|
|
@@ -13043,6 +13321,7 @@ def _field_to_question(field: dict[str, Any], *, temp_id: int) -> dict[str, Any]
|
|
|
13043
13321
|
if field.get("target_field_que_id") is not None:
|
|
13044
13322
|
reference["referQueId"] = field.get("target_field_que_id")
|
|
13045
13323
|
question["referenceConfig"] = reference
|
|
13324
|
+
question = _normalize_relation_question_for_save(question, field=field)
|
|
13046
13325
|
if field.get("type") == FieldType.department.value:
|
|
13047
13326
|
scope_type, scope_payload = _serialize_department_scope_for_question(field.get("department_scope"))
|
|
13048
13327
|
question["deptSelectScopeType"] = scope_type
|
|
@@ -13182,6 +13461,7 @@ def _build_form_payload_from_fields(
|
|
|
13182
13461
|
for row in form_rows:
|
|
13183
13462
|
_apply_row_widths(row)
|
|
13184
13463
|
payload = default_form_payload(title, form_rows)
|
|
13464
|
+
_normalize_formula_defaults_for_save(payload.get("formQues"))
|
|
13185
13465
|
payload["editVersionNo"] = int(current_schema.get("editVersionNo") or 1)
|
|
13186
13466
|
payload["questionRelations"] = _normalize_question_relations_for_save(
|
|
13187
13467
|
question_relations if question_relations is not None else (current_schema.get("questionRelations") or [])
|
|
@@ -13299,6 +13579,7 @@ def _build_form_payload_for_edit_fields(
|
|
|
13299
13579
|
|
|
13300
13580
|
payload = _build_form_save_base_payload(current_schema, title)
|
|
13301
13581
|
payload["formQues"] = form_rows
|
|
13582
|
+
_normalize_formula_defaults_for_save(payload.get("formQues"))
|
|
13302
13583
|
payload["questionRelations"] = normalized_relations
|
|
13303
13584
|
payload["editVersionNo"] = int(current_schema.get("editVersionNo") or 1)
|
|
13304
13585
|
return payload
|
|
@@ -14715,6 +14996,7 @@ def _build_form_payload_from_existing_schema(
|
|
|
14715
14996
|
|
|
14716
14997
|
payload = _build_form_save_base_payload(current_schema, str(current_schema.get("formTitle") or "未命名应用"))
|
|
14717
14998
|
payload["formQues"] = form_rows
|
|
14999
|
+
_normalize_formula_defaults_for_save(payload.get("formQues"))
|
|
14718
15000
|
payload["questionRelations"] = _normalize_question_relations_for_save(current_schema.get("questionRelations") or [])
|
|
14719
15001
|
payload["editVersionNo"] = int(current_schema.get("editVersionNo") or 1)
|
|
14720
15002
|
payload.setdefault("formTitle", current_schema.get("formTitle") or "未命名应用")
|