@amaster.ai/pi-lark 0.1.2-beta.42 → 0.1.2-beta.44
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/package.json +3 -3
- package/skills/lark-approval/references/lark-approval-initiate.md +2 -5
- package/skills/lark-approval/references/lark-approval-instances-initiated.md +6 -0
- package/skills/lark-approval/references/lark-approval-tasks-query.md +9 -0
- package/skills/lark-approval/references/lark-approval-tasks-rollback.md +8 -2
- package/skills/lark-apps/SKILL.md +16 -10
- package/skills/lark-apps/references/lark-apps-access-scope-set.md +1 -1
- package/skills/lark-apps/references/lark-apps-db-execute.md +185 -1
- package/skills/lark-apps/references/lark-apps-db.md +1 -1
- package/skills/lark-apps/references/lark-apps-role.md +133 -0
- package/skills/lark-base/SKILL.md +6 -2
- package/skills/lark-base/references/dashboard-block-data-config.md +28 -2
- package/skills/lark-base/references/lark-base-cell-value.md +9 -4
- package/skills/lark-base/references/lark-base-dashboard.md +11 -2
- package/skills/lark-base/references/lark-base-data-query.md +9 -7
- package/skills/lark-base/references/lark-base-field-create.md +4 -2
- package/skills/lark-base/references/lark-base-field-json.md +52 -15
- package/skills/lark-base/references/lark-base-field-update.md +4 -2
- package/skills/lark-base/references/lark-base-view-set-filter.md +3 -1
- package/skills/lark-drive/SKILL.md +4 -2
- package/skills/lark-drive/references/lark-drive-delete.md +23 -11
- package/skills/lark-drive/references/lark-drive-move.md +5 -3
- package/skills/lark-drive/references/lark-drive-task-result.md +58 -5
- package/skills/lark-event/SKILL.md +2 -1
- package/skills/lark-event/references/lark-event-approval.md +170 -0
- package/skills/lark-slides/references/slides_xml_schema_definition.xml +7 -2
- package/skills/lark-slides/references/xml-format-guide.md +15 -0
- package/skills/lark-slides/scripts/xml_text_overlap_lint.py +264 -6
- package/skills/lark-slides/scripts/xml_text_overlap_lint_test.py +348 -6
- package/skills/lark-vc/SKILL.md +6 -3
- package/skills/lark-vc/references/vc-domain-boundaries.md +9 -1
- package/skills/lark-vc-agent/SKILL.md +1 -1
- package/skills/lark-wiki/SKILL.md +4 -2
- package/skills/lark-wiki/references/lark-wiki-move-to-drive.md +122 -0
- package/skills/lark-wiki/references/lark-wiki-move.md +5 -3
|
@@ -38,6 +38,8 @@ SXSD_ATTR_ALIASES = {
|
|
|
38
38
|
"fontColor": "color",
|
|
39
39
|
}
|
|
40
40
|
SERVER_FILLED_SXSD_ATTRS = {"id"}
|
|
41
|
+
DEFAULT_TABLE_COLUMN_WIDTH = 110
|
|
42
|
+
DEFAULT_TABLE_ROW_HEIGHT = 37
|
|
41
43
|
_SXSD_TAG_ATTRIBUTES_CACHE: dict[str, set[str]] | None = None
|
|
42
44
|
_ICONPARK_ICON_TYPES_CACHE: set[str] | None = None
|
|
43
45
|
|
|
@@ -88,6 +90,84 @@ def extract_numeric_attribute(tag_source: str, name: str) -> int | float | None:
|
|
|
88
90
|
return int(value) if value.is_integer() else value
|
|
89
91
|
|
|
90
92
|
|
|
93
|
+
def sum_sizes(sizes: list[int | float]) -> int | float:
|
|
94
|
+
return sum(sizes)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def is_filled_size(size: int | float | None) -> bool:
|
|
98
|
+
return isinstance(size, (int, float)) and math.isfinite(size) and size > 0
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def fill_last_size_gap(sizes: list[int | float], target_size: int | float) -> list[int | float]:
|
|
102
|
+
if not sizes:
|
|
103
|
+
return sizes
|
|
104
|
+
final_sizes = [
|
|
105
|
+
size if index == len(sizes) - 1 else max(1, math.floor(size + 0.5))
|
|
106
|
+
for index, size in enumerate(sizes)
|
|
107
|
+
]
|
|
108
|
+
remaining_size = target_size - sum_sizes(final_sizes[:-1])
|
|
109
|
+
if remaining_size >= 1:
|
|
110
|
+
final_sizes[-1] = remaining_size
|
|
111
|
+
return final_sizes
|
|
112
|
+
|
|
113
|
+
size_to_redistribute = 1 - remaining_size
|
|
114
|
+
for index in range(len(final_sizes) - 2, -1, -1):
|
|
115
|
+
reduction = min(final_sizes[index] - 1, size_to_redistribute)
|
|
116
|
+
final_sizes[index] -= reduction
|
|
117
|
+
size_to_redistribute -= reduction
|
|
118
|
+
if size_to_redistribute == 0:
|
|
119
|
+
final_sizes[-1] = 1
|
|
120
|
+
return final_sizes
|
|
121
|
+
|
|
122
|
+
final_sizes[-1] = 1
|
|
123
|
+
return final_sizes
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def solve_weighted_min_layout(
|
|
127
|
+
input_sizes: list[int | float | None], default_size: int | float, target_min_size: int | float | None
|
|
128
|
+
) -> dict[str, Any]:
|
|
129
|
+
filled_indexes: list[int] = []
|
|
130
|
+
empty_indexes: list[int] = []
|
|
131
|
+
base_sizes: list[int | float] = []
|
|
132
|
+
for index, size in enumerate(input_sizes):
|
|
133
|
+
if is_filled_size(size):
|
|
134
|
+
filled_indexes.append(index)
|
|
135
|
+
base_sizes.append(size)
|
|
136
|
+
else:
|
|
137
|
+
empty_indexes.append(index)
|
|
138
|
+
base_sizes.append(0)
|
|
139
|
+
filled_sum = sum_sizes(base_sizes)
|
|
140
|
+
|
|
141
|
+
if target_min_size is None:
|
|
142
|
+
final_sizes = [default_size if index in empty_indexes else size for index, size in enumerate(base_sizes)]
|
|
143
|
+
return {"final_sizes": final_sizes, "actual_size": sum_sizes(final_sizes), "ratio": 1}
|
|
144
|
+
|
|
145
|
+
if not filled_indexes:
|
|
146
|
+
average_size = target_min_size / len(input_sizes)
|
|
147
|
+
final_sizes = fill_last_size_gap([average_size] * len(input_sizes), target_min_size)
|
|
148
|
+
return {"final_sizes": final_sizes, "actual_size": sum_sizes(final_sizes), "ratio": 1}
|
|
149
|
+
|
|
150
|
+
if empty_indexes:
|
|
151
|
+
remaining_size = target_min_size - filled_sum
|
|
152
|
+
final_sizes = [*base_sizes]
|
|
153
|
+
if remaining_size > 0:
|
|
154
|
+
average_size = remaining_size / len(empty_indexes)
|
|
155
|
+
empty_sizes = fill_last_size_gap([average_size] * len(empty_indexes), remaining_size)
|
|
156
|
+
for index, empty_size in zip(empty_indexes, empty_sizes):
|
|
157
|
+
final_sizes[index] = empty_size
|
|
158
|
+
else:
|
|
159
|
+
for index in empty_indexes:
|
|
160
|
+
final_sizes[index] = default_size
|
|
161
|
+
return {"final_sizes": final_sizes, "actual_size": sum_sizes(final_sizes), "ratio": 1}
|
|
162
|
+
|
|
163
|
+
ratio = max(1, target_min_size / filled_sum)
|
|
164
|
+
actual_size = max(target_min_size, filled_sum)
|
|
165
|
+
if ratio == 1:
|
|
166
|
+
return {"final_sizes": [*base_sizes], "actual_size": actual_size, "ratio": ratio}
|
|
167
|
+
final_sizes = fill_last_size_gap([size * ratio for size in base_sizes], actual_size)
|
|
168
|
+
return {"final_sizes": final_sizes, "actual_size": sum_sizes(final_sizes), "ratio": ratio}
|
|
169
|
+
|
|
170
|
+
|
|
91
171
|
def strip_xml(value: str) -> str:
|
|
92
172
|
stripped = re.sub(r"<!\[CDATA\[([\s\S]*?)\]\]>", r"\1", value)
|
|
93
173
|
stripped = re.sub(r"<[^>]+>", " ", stripped)
|
|
@@ -515,8 +595,8 @@ def extract_elements(slide_xml: str) -> list[dict[str, Any]]:
|
|
|
515
595
|
for match in re.finditer(r"<(shape|img|table|chart|whiteboard)\b([^>]*)>", slide_xml):
|
|
516
596
|
kind, attrs = match.group(1), match.group(2)
|
|
517
597
|
content = ""
|
|
518
|
-
if kind
|
|
519
|
-
close_index = slide_xml.find("</
|
|
598
|
+
if kind in {"shape", "table"}:
|
|
599
|
+
close_index = slide_xml.find(f"</{kind}>", match.end())
|
|
520
600
|
if close_index != -1:
|
|
521
601
|
content = slide_xml[match.end() : close_index]
|
|
522
602
|
|
|
@@ -525,6 +605,15 @@ def extract_elements(slide_xml: str) -> list[dict[str, Any]]:
|
|
|
525
605
|
y = extract_numeric_attribute(attrs, "topLeftY")
|
|
526
606
|
width = extract_numeric_attribute(attrs, "width")
|
|
527
607
|
height = extract_numeric_attribute(attrs, "height")
|
|
608
|
+
rotation = extract_numeric_attribute(attrs, "rotation") or 0
|
|
609
|
+
table_layouts: dict[str, dict[str, Any] | None] = {}
|
|
610
|
+
if kind == "table":
|
|
611
|
+
width, table_layouts["width"] = resolve_table_dimension(
|
|
612
|
+
content, width, extract_table_column_sizes, DEFAULT_TABLE_COLUMN_WIDTH
|
|
613
|
+
)
|
|
614
|
+
height, table_layouts["height"] = resolve_table_dimension(
|
|
615
|
+
content, height, extract_table_row_sizes, DEFAULT_TABLE_ROW_HEIGHT
|
|
616
|
+
)
|
|
528
617
|
if all(value is not None for value in [x, y, width, height]):
|
|
529
618
|
element = {
|
|
530
619
|
"id": element_id,
|
|
@@ -534,8 +623,17 @@ def extract_elements(slide_xml: str) -> list[dict[str, Any]]:
|
|
|
534
623
|
"y": y,
|
|
535
624
|
"width": width,
|
|
536
625
|
"height": height,
|
|
626
|
+
"rotation": rotation,
|
|
537
627
|
"order": len(elements),
|
|
538
628
|
}
|
|
629
|
+
if kind == "table":
|
|
630
|
+
element.update(
|
|
631
|
+
{
|
|
632
|
+
"declared_width": extract_numeric_attribute(attrs, "width"),
|
|
633
|
+
"declared_height": extract_numeric_attribute(attrs, "height"),
|
|
634
|
+
"table_layouts": table_layouts,
|
|
635
|
+
}
|
|
636
|
+
)
|
|
539
637
|
if kind == "shape":
|
|
540
638
|
element.update(
|
|
541
639
|
{
|
|
@@ -867,11 +965,158 @@ def detect_whiteboard_external_overlaps(
|
|
|
867
965
|
return issues
|
|
868
966
|
|
|
869
967
|
|
|
968
|
+
def element_canvas_bbox(element: dict[str, Any]) -> dict[str, int | float]:
|
|
969
|
+
bbox = {key: element[key] for key in ("x", "y", "width", "height")}
|
|
970
|
+
if element["kind"] != "chart" and not (element["kind"] == "shape" and element["type"] == "text"):
|
|
971
|
+
return bbox
|
|
972
|
+
|
|
973
|
+
rotation = element["rotation"]
|
|
974
|
+
if not isinstance(rotation, (int, float)) or not math.isfinite(rotation):
|
|
975
|
+
rotation = 0
|
|
976
|
+
rotation %= 360
|
|
977
|
+
if math.isclose(rotation, 0, abs_tol=1e-9):
|
|
978
|
+
return bbox
|
|
979
|
+
radians = math.radians(rotation)
|
|
980
|
+
sine = abs(math.sin(radians))
|
|
981
|
+
cosine = abs(math.cos(radians))
|
|
982
|
+
sine = 0 if math.isclose(sine, 0, abs_tol=1e-12) else sine
|
|
983
|
+
cosine = 0 if math.isclose(cosine, 0, abs_tol=1e-12) else cosine
|
|
984
|
+
rotated_width = element["width"] * cosine + element["height"] * sine
|
|
985
|
+
rotated_height = element["width"] * sine + element["height"] * cosine
|
|
986
|
+
return {
|
|
987
|
+
"x": element["x"] - (rotated_width - element["width"]) / 2,
|
|
988
|
+
"y": element["y"] - (rotated_height - element["height"]) / 2,
|
|
989
|
+
"width": rotated_width,
|
|
990
|
+
"height": rotated_height,
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
def detect_elements_out_of_canvas(
|
|
995
|
+
elements: list[dict[str, Any]], slide_width: int | float, slide_height: int | float
|
|
996
|
+
) -> list[dict[str, Any]]:
|
|
997
|
+
issues: list[dict[str, Any]] = []
|
|
998
|
+
for element in (
|
|
999
|
+
element
|
|
1000
|
+
for element in elements
|
|
1001
|
+
if element["kind"] in {"table", "chart"}
|
|
1002
|
+
or (element["kind"] == "shape" and element["type"] == "text")
|
|
1003
|
+
):
|
|
1004
|
+
bbox = element_canvas_bbox(element)
|
|
1005
|
+
overflow = {
|
|
1006
|
+
"left": max(-bbox["x"], 0),
|
|
1007
|
+
"top": max(-bbox["y"], 0),
|
|
1008
|
+
"right": max(bbox["x"] + bbox["width"] - slide_width, 0),
|
|
1009
|
+
"bottom": max(bbox["y"] + bbox["height"] - slide_height, 0),
|
|
1010
|
+
}
|
|
1011
|
+
overflow_details = [
|
|
1012
|
+
f"{side} by {amount:g}px" for side, amount in overflow.items() if amount > 0
|
|
1013
|
+
]
|
|
1014
|
+
if not overflow_details:
|
|
1015
|
+
continue
|
|
1016
|
+
issues.append(
|
|
1017
|
+
{
|
|
1018
|
+
"level": "error",
|
|
1019
|
+
"code": f'{element["kind"]}_out_of_canvas',
|
|
1020
|
+
"elements": [element["id"]],
|
|
1021
|
+
"canvas": {"width": slide_width, "height": slide_height},
|
|
1022
|
+
"bbox": bbox,
|
|
1023
|
+
"overflow": overflow,
|
|
1024
|
+
"message": (
|
|
1025
|
+
f'{element["kind"]} {element["id"]} exceeds the {slide_width:g}x{slide_height:g} canvas '
|
|
1026
|
+
f'({", ".join(overflow_details)})'
|
|
1027
|
+
),
|
|
1028
|
+
"hint": (
|
|
1029
|
+
"Move the table inside the canvas, reduce table.width/table.height, or split the table across "
|
|
1030
|
+
"slides."
|
|
1031
|
+
if element["kind"] == "table"
|
|
1032
|
+
else f'Move the {element["kind"]} inside the canvas or reduce its width/height.'
|
|
1033
|
+
),
|
|
1034
|
+
}
|
|
1035
|
+
)
|
|
1036
|
+
return issues
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
def extract_table_column_sizes(table_xml: str) -> list[int | float | None]:
|
|
1040
|
+
sizes: list[int | float | None] = []
|
|
1041
|
+
for match in re.finditer(r"<col\b([^>]*)/?>", table_xml):
|
|
1042
|
+
attrs = match.group(1)
|
|
1043
|
+
span = extract_numeric_attribute(attrs, "span") or 1
|
|
1044
|
+
span_count = int(span) if math.isfinite(span) and span > 0 and float(span).is_integer() else 1
|
|
1045
|
+
sizes.extend([extract_numeric_attribute(attrs, "width")] * span_count)
|
|
1046
|
+
return sizes
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
def extract_table_row_sizes(table_xml: str) -> list[int | float | None]:
|
|
1050
|
+
return [extract_numeric_attribute(match.group(1), "height") for match in re.finditer(r"<tr\b([^>]*)>", table_xml)]
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
def resolve_table_dimension(
|
|
1054
|
+
table_xml: str,
|
|
1055
|
+
declared_size: int | float | None,
|
|
1056
|
+
extract_sizes: Any,
|
|
1057
|
+
default_size: int | float,
|
|
1058
|
+
) -> tuple[int | float | None, dict[str, Any] | None]:
|
|
1059
|
+
input_sizes = extract_sizes(table_xml)
|
|
1060
|
+
if not input_sizes:
|
|
1061
|
+
return declared_size, None
|
|
1062
|
+
layout = solve_weighted_min_layout(
|
|
1063
|
+
input_sizes, default_size, declared_size if is_filled_size(declared_size) else None
|
|
1064
|
+
)
|
|
1065
|
+
return layout["actual_size"], layout
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
def format_size(size: int | float) -> str:
|
|
1069
|
+
return f"{size:g}"
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
def detect_table_layout_size_mismatches(elements: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
1073
|
+
issues: list[dict[str, Any]] = []
|
|
1074
|
+
dimensions = {
|
|
1075
|
+
"width": ("col", "column widths"),
|
|
1076
|
+
"height": ("tr", "row heights"),
|
|
1077
|
+
}
|
|
1078
|
+
for table in (element for element in elements if element["kind"] == "table"):
|
|
1079
|
+
for dimension, (child_tag, child_description) in dimensions.items():
|
|
1080
|
+
target_size = table[f"declared_{dimension}"]
|
|
1081
|
+
if not is_filled_size(target_size):
|
|
1082
|
+
continue
|
|
1083
|
+
layout = table["table_layouts"][dimension]
|
|
1084
|
+
if layout is None:
|
|
1085
|
+
continue
|
|
1086
|
+
actual_size = layout["actual_size"]
|
|
1087
|
+
if math.isclose(actual_size, target_size, rel_tol=1e-9, abs_tol=1e-9):
|
|
1088
|
+
continue
|
|
1089
|
+
issues.append(
|
|
1090
|
+
{
|
|
1091
|
+
"level": "info",
|
|
1092
|
+
"code": "table_resolved_size_mismatch",
|
|
1093
|
+
"elements": [table["id"]],
|
|
1094
|
+
"dimension": dimension,
|
|
1095
|
+
"declared_size": target_size,
|
|
1096
|
+
"resolved_size": actual_size,
|
|
1097
|
+
"resolved_sizes": layout["final_sizes"],
|
|
1098
|
+
"message": (
|
|
1099
|
+
f'table {table["id"]} declares {dimension}={format_size(target_size)}px, but its '
|
|
1100
|
+
f"{child_description} resolve to {format_size(actual_size)}px"
|
|
1101
|
+
),
|
|
1102
|
+
"hint": (
|
|
1103
|
+
f"Set table.{dimension} to {format_size(actual_size)}px, or adjust <{child_tag}> sizes "
|
|
1104
|
+
f"so their resolved total matches {format_size(target_size)}px."
|
|
1105
|
+
),
|
|
1106
|
+
}
|
|
1107
|
+
)
|
|
1108
|
+
return issues
|
|
1109
|
+
|
|
1110
|
+
|
|
870
1111
|
def lint_slide(
|
|
871
1112
|
slide_xml: str, slide_number: int, slide_width: int | float = 960, slide_height: int | float = 540
|
|
872
1113
|
) -> dict[str, Any]:
|
|
873
1114
|
elements = extract_elements(slide_xml)
|
|
874
|
-
issues: list[dict[str, Any]] =
|
|
1115
|
+
issues: list[dict[str, Any]] = [
|
|
1116
|
+
*detect_whiteboard_external_overlaps(elements, slide_width, slide_height),
|
|
1117
|
+
*detect_elements_out_of_canvas(elements, slide_width, slide_height),
|
|
1118
|
+
*detect_table_layout_size_mismatches(elements),
|
|
1119
|
+
]
|
|
875
1120
|
|
|
876
1121
|
for index, left in enumerate(elements):
|
|
877
1122
|
for right in elements[index + 1 :]:
|
|
@@ -896,7 +1141,7 @@ def lint_xml(xml: str, source_path: str | None = None) -> dict[str, Any]:
|
|
|
896
1141
|
return {
|
|
897
1142
|
"file": source_path,
|
|
898
1143
|
"slide_size": {"width": 960, "height": 540},
|
|
899
|
-
"summary": {"slide_count": 0, "error_count": 1, "warning_count": 0},
|
|
1144
|
+
"summary": {"slide_count": 0, "error_count": 1, "warning_count": 0, "info_count": 0},
|
|
900
1145
|
"issues": [xml_error],
|
|
901
1146
|
"slides": [],
|
|
902
1147
|
}
|
|
@@ -908,10 +1153,16 @@ def lint_xml(xml: str, source_path: str | None = None) -> dict[str, Any]:
|
|
|
908
1153
|
if namespace_issues:
|
|
909
1154
|
error_count = sum(1 for issue in top_level_issues if issue["level"] == "error")
|
|
910
1155
|
warning_count = sum(1 for issue in top_level_issues if issue["level"] == "warning")
|
|
1156
|
+
info_count = sum(1 for issue in top_level_issues if issue["level"] == "info")
|
|
911
1157
|
return {
|
|
912
1158
|
"file": source_path,
|
|
913
1159
|
"slide_size": {"width": 960, "height": 540},
|
|
914
|
-
"summary": {
|
|
1160
|
+
"summary": {
|
|
1161
|
+
"slide_count": 0,
|
|
1162
|
+
"error_count": error_count,
|
|
1163
|
+
"warning_count": warning_count,
|
|
1164
|
+
"info_count": info_count,
|
|
1165
|
+
},
|
|
915
1166
|
"issues": top_level_issues,
|
|
916
1167
|
"slides": [],
|
|
917
1168
|
}
|
|
@@ -924,10 +1175,17 @@ def lint_xml(xml: str, source_path: str | None = None) -> dict[str, Any]:
|
|
|
924
1175
|
error_count += sum(1 for slide in slides for issue in slide["issues"] if issue["level"] == "error")
|
|
925
1176
|
warning_count = sum(1 for issue in top_level_issues if issue["level"] == "warning")
|
|
926
1177
|
warning_count += sum(1 for slide in slides for issue in slide["issues"] if issue["level"] == "warning")
|
|
1178
|
+
info_count = sum(1 for issue in top_level_issues if issue["level"] == "info")
|
|
1179
|
+
info_count += sum(1 for slide in slides for issue in slide["issues"] if issue["level"] == "info")
|
|
927
1180
|
result = {
|
|
928
1181
|
"file": source_path,
|
|
929
1182
|
"slide_size": {"width": presentation["width"], "height": presentation["height"]},
|
|
930
|
-
"summary": {
|
|
1183
|
+
"summary": {
|
|
1184
|
+
"slide_count": len(slides),
|
|
1185
|
+
"error_count": error_count,
|
|
1186
|
+
"warning_count": warning_count,
|
|
1187
|
+
"info_count": info_count,
|
|
1188
|
+
},
|
|
931
1189
|
"slides": slides,
|
|
932
1190
|
}
|
|
933
1191
|
if top_level_issues:
|