feat:移除了弹窗,服务器添加sls

This commit is contained in:
2026-09-08 22:39:45 +08:00
commit 6a295f9a7a
4082 changed files with 1322534 additions and 0 deletions

View File

@ -0,0 +1,362 @@
#!/usr/bin/env python3
# Copyright (c) 2026 Lark Technologies Pte. Ltd.
# SPDX-License-Identifier: MIT
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
from typing import Any
SKILL_ROOT = Path(__file__).resolve().parent.parent
REFERENCES_DIR = SKILL_ROOT / "references"
DEFAULT_INDEX_PATH = REFERENCES_DIR / "iconpark-index.json"
DEFAULT_LIMIT = 8
CURATED_ICON_BOOSTS = {
"设置": {"iconpark/Base/setting.svg"},
"配置": {"iconpark/Base/setting.svg", "iconpark/Base/config.svg"},
"目标": {"iconpark/Base/aiming.svg", "iconpark/Sports/target-one.svg"},
"增长": {"iconpark/Charts/positive-dynamics.svg"},
"趋势": {"iconpark/Charts/chart-line.svg", "iconpark/Charts/positive-dynamics.svg"},
"占比": {"iconpark/Charts/chart-proportion.svg"},
"数据": {"iconpark/Charts/data-screen.svg"},
"看板": {"iconpark/Charts/data-screen.svg"},
"成功": {"iconpark/Character/check-one.svg"},
"完成": {"iconpark/Character/check-one.svg"},
"失败": {"iconpark/Character/close-one.svg"},
"风险": {"iconpark/Character/close-one.svg"},
"团队": {"iconpark/Peoples/peoples.svg"},
"用户": {"iconpark/Peoples/peoples.svg", "iconpark/Peoples/user.svg"},
"安全": {"iconpark/Safe/protect.svg"},
"防护": {"iconpark/Safe/protect.svg"},
"全球": {"iconpark/Travel/world.svg"},
"市场": {"iconpark/Travel/world.svg"},
"邮件": {"iconpark/Office/envelope-one.svg"},
"联系": {"iconpark/Office/envelope-one.svg"},
"会议": {"iconpark/Office/schedule.svg"},
"日程": {"iconpark/Office/schedule.svg"},
"飞书": {"iconpark/Brand/bydesign.svg"},
}
CURATED_BOOST_SCORE = 40
class IconParkToolError(Exception):
pass
def fail(message: str) -> None:
raise IconParkToolError(message)
def normalize_whitespace(value: str) -> str:
return re.sub(r"\s+", " ", value).strip()
def normalize_token(value: str) -> str:
return normalize_whitespace(value.lower().replace("_", "-"))
def append_unique(target: list[str], token: str) -> None:
normalized = normalize_token(token)
if normalized and normalized not in target:
target.append(normalized)
def tokenize_query(value: str) -> list[str]:
normalized = normalize_token(value)
if not normalized:
return []
tokens: list[str] = []
for item in re.split(r"[\s,/|,。;;:()【】\[\]《》<>]+", normalized):
append_unique(tokens, item)
for phrase in re.findall(r"[\u3400-\u9fff]+", normalized):
if len(phrase) < 2:
continue
max_size = min(6, len(phrase))
for size in range(max_size, 1, -1):
for start in range(0, len(phrase) - size + 1):
append_unique(tokens, phrase[start : start + size])
synonym_tokens = {
"目标": ["aim", "target", "goal"],
"聚焦": ["focus", "target"],
"增长": ["growth", "trend", "positive"],
"趋势": ["trend", "chart", "line"],
"数据": ["data", "analytics", "chart"],
"指标": ["metric", "data"],
"看板": ["dashboard", "screen", "data"],
"成功": ["success", "check", "done"],
"完成": ["done", "success", "check"],
"失败": ["fail", "close", "risk"],
"风险": ["risk", "fail", "protect"],
"安全": ["safe", "security", "protect"],
"配置": ["config", "setting", "system"],
"设置": ["setting", "config"],
"团队": ["team", "people", "users"],
"用户": ["user", "people"],
"全球": ["global", "world", "earth"],
"市场": ["market", "world", "business"],
"邮件": ["mail", "message"],
"mail": ["message", "envelope", "envelope-one"],
"计划": ["plan", "schedule"],
"时间": ["time", "schedule"],
"学习": ["learning", "education", "book"],
"培训": ["training", "education"],
"自动化": ["automation", "ai"],
"ai": ["ai", "automation", "magic"],
}
for token in list(tokens):
for keyword, aliases in synonym_tokens.items():
if is_ascii_token(keyword):
matches = token == keyword
else:
matches = keyword in token
if matches:
for alias in aliases:
append_unique(tokens, alias)
return tokens
def is_ascii_token(value: str) -> bool:
return bool(re.fullmatch(r"[a-z0-9-]+", value))
def allows_substring_match(value: str) -> bool:
return not is_ascii_token(value) or len(value) >= 3
def field_tokens(*values: str) -> set[str]:
tokens: set[str] = set()
for value in values:
normalized = normalize_token(value)
if not normalized:
continue
tokens.add(normalized)
for part in re.split(r"[-\s]+", normalized):
if part:
tokens.add(part)
return tokens
def load_index(path: str | Path = DEFAULT_INDEX_PATH) -> dict[str, Any]:
index_path = Path(path)
if not index_path.exists():
fail(f"iconpark index not found: {index_path}")
try:
index_data = json.loads(index_path.read_text(encoding="utf-8"))
except json.JSONDecodeError as error:
fail(f"invalid iconpark index JSON: {error}")
if not isinstance(index_data.get("icons"), list):
fail("iconpark index must contain an icons array")
return index_data
def icon_search_text(entry: dict[str, Any]) -> str:
parts = [
entry.get("iconType", ""),
entry.get("category", ""),
entry.get("name", ""),
" ".join(entry.get("tags") or []),
]
return normalize_token(" ".join(parts))
def score_icon(entry: dict[str, Any], query: str, tokens: list[str]) -> int:
raw_icon_type = entry.get("iconType", "")
icon_type = normalize_token(raw_icon_type)
category = normalize_token(entry.get("category", ""))
name = normalize_token(entry.get("name", ""))
tags = [normalize_token(tag) for tag in entry.get("tags") or []]
name_tokens = field_tokens(name)
category_tokens = field_tokens(category)
tag_tokens = field_tokens(*tags)
icon_type_tokens = field_tokens(icon_type)
search_text = icon_search_text(entry)
normalized_query = normalize_token(query)
score = 0
boosted_keywords: set[str] = set()
if normalized_query:
if normalized_query == icon_type or normalized_query == name:
score += 200
elif normalized_query in tag_tokens:
score += 120
elif normalized_query in icon_type_tokens:
score += 60
elif allows_substring_match(normalized_query) and normalized_query in search_text:
score += 30
for token in tokens:
for keyword, boosted_icon_types in CURATED_ICON_BOOSTS.items():
if keyword in boosted_keywords:
continue
if keyword in token and raw_icon_type in boosted_icon_types:
score += CURATED_BOOST_SCORE
boosted_keywords.add(keyword)
if token == name:
score += 80
elif token in name_tokens:
score += 55
elif allows_substring_match(token) and token in name:
score += 45
if token == category:
score += 35
elif token in category_tokens:
score += 25
elif allows_substring_match(token) and token in category:
score += 15
for tag in tags:
if token == tag:
score += 60
elif token in field_tokens(tag):
score += 45
elif allows_substring_match(token) and token in tag:
score += 20
if token in icon_type_tokens:
score += 20
elif allows_substring_match(token) and token in icon_type:
score += 15
return score
def parse_limit(value: Any) -> int:
if value is None or value is False:
return DEFAULT_LIMIT
if value is True:
fail("limit requires an integer value")
try:
return int(value)
except (TypeError, ValueError):
fail(f"limit must be an integer: {value}")
def public_icon(entry: dict[str, Any], score: int | None = None) -> dict[str, Any]:
result = {
"iconType": entry["iconType"],
"category": entry["category"],
"name": entry["name"],
"tags": entry.get("tags") or [],
}
if score is not None:
result["score"] = score
return result
def search_icons(index_data: dict[str, Any], options: dict[str, Any]) -> list[dict[str, Any]]:
query = str(options.get("query") or "")
if not normalize_whitespace(query):
fail("query is required")
limit = parse_limit(options.get("limit"))
category_filter = normalize_token(str(options.get("category") or ""))
tokens = tokenize_query(query)
ranked: list[dict[str, Any]] = []
for entry in index_data["icons"]:
if category_filter and normalize_token(entry.get("category", "")) != category_filter:
continue
score = score_icon(entry, query, tokens)
if query and score == 0:
continue
ranked.append(public_icon(entry, score))
ranked.sort(key=lambda item: (-int(item["score"]), item["category"], item["name"]))
return ranked[: max(limit, 0)]
def resolve_icon(index_data: dict[str, Any], name_or_type: str | None) -> dict[str, Any]:
if not name_or_type:
fail("name is required")
target = normalize_token(name_or_type)
matches = []
for entry in index_data["icons"]:
candidates = {
normalize_token(entry["iconType"]),
normalize_token(entry["name"]),
normalize_token(f'{entry["category"]}/{entry["name"]}.svg'),
}
if target in candidates:
matches.append(entry)
if not matches:
fail(f"icon not found: {name_or_type}")
if len(matches) > 1:
names = ", ".join(entry["iconType"] for entry in matches)
fail(f"ambiguous icon name: {name_or_type}; matches: {names}")
return public_icon(matches[0])
def list_categories(index_data: dict[str, Any]) -> list[dict[str, Any]]:
counts: dict[str, int] = {}
for entry in index_data["icons"]:
counts[entry["category"]] = counts.get(entry["category"], 0) + 1
return [{"category": category, "count": counts[category]} for category in sorted(counts)]
def parse_cli_args(argv: list[str]) -> tuple[str | None, dict[str, Any]]:
if not argv:
return None, {}
command, *rest = argv
options: dict[str, Any] = {}
index = 0
while index < len(rest):
token = rest[index]
if not token.startswith("--"):
fail(f"unexpected argument: {token}")
key = token[2:]
next_token = rest[index + 1] if index + 1 < len(rest) else None
if next_token is None or next_token.startswith("--"):
options[key] = True
index += 1
continue
options[key] = next_token
index += 2
return command, options
def print_usage() -> None:
usage = [
"Usage:",
" python3 iconpark_tool.py search --query <text> [--category <Category>] [--limit 8]",
" python3 iconpark_tool.py resolve --name <name|iconType>",
" python3 iconpark_tool.py list-categories",
]
print("\n".join(usage), file=sys.stderr)
def write_json(value: Any) -> None:
print(json.dumps(value, ensure_ascii=False, indent=2))
def run_cli(argv: list[str] | None = None) -> None:
command, options = parse_cli_args(argv or sys.argv[1:])
if not command or command in {"--help", "help"}:
print_usage()
raise SystemExit(0)
index_data = load_index()
if command == "search":
write_json(search_icons(index_data, options))
return
if command == "resolve":
write_json(resolve_icon(index_data, options.get("name")))
return
if command == "list-categories":
write_json(list_categories(index_data))
return
print_usage()
fail(f"unknown command: {command}")
if __name__ == "__main__":
try:
run_cli()
except IconParkToolError as error:
print(f"iconpark-tool error: {error}", file=sys.stderr)
raise SystemExit(1) from error