# -*- coding: utf-8 -*- """ Platform definitions and resolution. Central authoritative registry for all platforms (logistics, LLM, content, etc.). Import and use PLATFORMS dict, resolve_platform_key(), and seed_platforms(). """ import json import time from typing import Any, Optional from util.logging_config import get_skill_logger # --------------------------------------------------------------------------- # Platform specification fields: # display_name – Human-readable name (e.g. "Maersk") # domain – Business domain: logistics / llm / content / ecommerce / generic # provider_code – Supplier code (logistics); can be same as key # default_url – Default entry URL (empty string if none) # aliases – List of name aliases (Chinese/English, for CLI resolution) # capabilities – Dict of capability flags: # {"web": bool, "api_key": bool, "rpa": bool} # --------------------------------------------------------------------------- PLATFORMS: dict[str, dict[str, Any]] = { # ========================================================================= # Logistics platforms # ========================================================================= "maersk": { "display_name": "Maersk", "domain": "logistics", "provider_code": "maersk", "default_url": "https://www.maersk.com", "aliases": ["马士基", "maersk", "Maersk"], "capabilities": {"web": True, "api_key": False, "rpa": True}, }, "cosco": { "display_name": "COSCO", "domain": "logistics", "provider_code": "cosco", "default_url": "", "aliases": ["中远海运", "COSCO", "cosco"], "capabilities": {"web": True, "api_key": False, "rpa": True}, }, "msc": { "display_name": "MSC", "domain": "logistics", "provider_code": "msc", "default_url": "", "aliases": ["MSC", "地中海航运"], "capabilities": {"web": True, "api_key": False, "rpa": True}, }, "cma_cgm": { "display_name": "CMA CGM", "domain": "logistics", "provider_code": "cma_cgm", "default_url": "", "aliases": ["达飞", "CMA CGM", "cma cgm"], "capabilities": {"web": True, "api_key": False, "rpa": True}, }, "evergreen": { "display_name": "Evergreen", "domain": "logistics", "provider_code": "evergreen", "default_url": "", "aliases": ["长荣", "Evergreen", "evergreen"], "capabilities": {"web": True, "api_key": False, "rpa": True}, }, "cargo_wise": { "display_name": "CargoWise", "domain": "logistics", "provider_code": "cargo_wise", "default_url": "", "aliases": ["CargoWise", "Cargo Wise", "cargo wise"], "capabilities": {"web": True, "api_key": False, "rpa": True}, }, "freightos": { "display_name": "Freightos", "domain": "logistics", "provider_code": "freightos", "default_url": "", "aliases": ["Freightos", "freightos"], "capabilities": {"web": True, "api_key": True, "rpa": False}, }, "webcargo": { "display_name": "WebCargo", "domain": "logistics", "provider_code": "webcargo", "default_url": "", "aliases": ["WebCargo", "web cargo"], "capabilities": {"web": True, "api_key": True, "rpa": False}, }, "sinotrans": { "display_name": "Sinotrans", "domain": "logistics", "provider_code": "sinotrans", "default_url": "", "aliases": ["中外运", "Sinotrans", "sinotrans"], "capabilities": {"web": True, "api_key": False, "rpa": True}, }, # ========================================================================= # LLM / AI platforms # ========================================================================= "deepseek": { "display_name": "DeepSeek", "domain": "llm", "provider_code": "deepseek", "default_url": "https://chat.deepseek.com", "aliases": ["DeepSeek", "deepseek"], "capabilities": {"web": True, "api_key": True, "rpa": False}, }, "doubao": { "display_name": "Doubao", "domain": "llm", "provider_code": "doubao", "default_url": "https://www.doubao.com", "aliases": ["豆包", "Doubao"], "capabilities": {"web": True, "api_key": True, "rpa": False}, }, "kimi": { "display_name": "Kimi", "domain": "llm", "provider_code": "kimi", "default_url": "https://kimi.moonshot.cn", "aliases": ["Kimi", "月之暗面"], "capabilities": {"web": True, "api_key": True, "rpa": False}, }, "qianwen": { "display_name": "Qianwen", "domain": "llm", "provider_code": "qianwen", "default_url": "https://tongyi.aliyun.com", "aliases": ["通义千问", "通义", "千问", "Qianwen"], "capabilities": {"web": True, "api_key": True, "rpa": False}, }, "yiyan": { "display_name": "YiYan", "domain": "llm", "provider_code": "yiyan", "default_url": "https://yiyan.baidu.com", "aliases": ["文心一言", "文心", "一言", "YiYan"], "capabilities": {"web": True, "api_key": True, "rpa": False}, }, "yuanbao": { "display_name": "Yuanbao", "domain": "llm", "provider_code": "yuanbao", "default_url": "https://yuanbao.tencent.com", "aliases": ["腾讯元宝", "元宝", "Yuanbao"], "capabilities": {"web": True, "api_key": True, "rpa": False}, }, "gemini": { "display_name": "Gemini", "domain": "llm", "provider_code": "gemini", "default_url": "https://gemini.google.com", "aliases": ["Gemini", "谷歌Gemini", "Google Gemini", "Bard"], "capabilities": {"web": True, "api_key": True, "rpa": False}, }, "minimax": { "display_name": "MiniMax", "domain": "llm", "provider_code": "minimax", "default_url": "", "aliases": ["MiniMax", "minimax"], "capabilities": {"web": True, "api_key": True, "rpa": False}, }, # ========================================================================= # Content platforms (carried forward from v1) # ========================================================================= "sohu": { "display_name": "搜狐号", "domain": "content", "provider_code": "sohu", "default_url": "https://mp.sohu.com", "aliases": ["搜狐", "搜狐号"], "capabilities": {"web": True, "api_key": False, "rpa": False}, }, "toutiao": { "display_name": "头条号", "domain": "content", "provider_code": "toutiao", "default_url": "https://mp.toutiao.com/", "aliases": ["头条", "头条号"], "capabilities": {"web": True, "api_key": False, "rpa": False}, }, "zhihu": { "display_name": "知乎", "domain": "content", "provider_code": "zhihu", "default_url": "https://www.zhihu.com", "aliases": ["知乎", "知乎号"], "capabilities": {"web": True, "api_key": False, "rpa": False}, }, "wechat": { "display_name": "微信公众号", "domain": "content", "provider_code": "wechat", "default_url": "https://mp.weixin.qq.com", "aliases": ["公众号", "微信", "微信公众号"], "capabilities": {"web": True, "api_key": False, "rpa": False}, }, } # --------------------------------------------------------------------------- # Derived alias map # --------------------------------------------------------------------------- def _build_alias_map() -> dict[str, str]: """Build a case-insensitive flat alias -> key map.""" m: dict[str, str] = {} for key, spec in PLATFORMS.items(): # Register the key itself (lowercase) m[key.lower()] = key # Register all aliases (original + lowercase) for alias in [spec["display_name"]] + spec.get("aliases", []): a = str(alias).strip() if a: m[a] = key m[a.lower()] = key return m _PLATFORM_ALIAS_MAP = _build_alias_map() def resolve_platform_key(name: str) -> Optional[str]: """ Resolve user input to a canonical platform key. Supports: key, display_name, any alias. Case-insensitive. Returns None if unrecognized. """ if not name: return None s = str(name).strip() if not s: return None sl = s.lower() # Direct key match if sl in PLATFORMS: return sl # Alias map if sl in _PLATFORM_ALIAS_MAP: return _PLATFORM_ALIAS_MAP[sl] if s in _PLATFORM_ALIAS_MAP: return _PLATFORM_ALIAS_MAP[s] return None def get_platform(key: str) -> Optional[dict[str, Any]]: """Return the full platform spec dict for a validated key.""" return PLATFORMS.get(key) def list_platforms(domain: Optional[str] = None) -> list[dict[str, Any]]: """List all platforms, optionally filtered by domain. Returns a list of dicts with metadata.""" result = [] for key, spec in PLATFORMS.items(): if domain and spec["domain"] != domain: continue result.append({ "platform_key": key, "display_name": spec["display_name"], "domain": spec["domain"], "provider_code": spec.get("provider_code"), "default_url": spec.get("default_url", ""), "aliases": spec.get("aliases", []), "capabilities": spec.get("capabilities", {}), "enabled": True, }) return result def seed_platforms(conn) -> None: """ Upsert all platform definitions into the 'platforms' table. Must be called after init_db() in the same connection lifecycle. """ log = get_skill_logger() log.info("platform_seed_start") cur = conn.cursor() now = int(time.time()) upsert_count = 0 for key, spec in PLATFORMS.items(): aliases_json = json.dumps(spec.get("aliases", []), ensure_ascii=False) capabilities_json = json.dumps(spec.get("capabilities", {}), ensure_ascii=False) cur.execute( """ INSERT INTO platforms (platform_key, display_name, domain, provider_code, default_url, aliases_json, capabilities_json, enabled, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?) ON CONFLICT(platform_key) DO UPDATE SET display_name = excluded.display_name, domain = excluded.domain, provider_code = excluded.provider_code, default_url = excluded.default_url, aliases_json = excluded.aliases_json, capabilities_json = excluded.capabilities_json, enabled = 1, updated_at = excluded.updated_at """, ( key, spec["display_name"], spec["domain"], spec.get("provider_code"), spec.get("default_url", ""), aliases_json, capabilities_json, now, now, ), ) upsert_count += 1 conn.commit() log.info("platform_seed_done count=%s", upsert_count) # --------------------------------------------------------------------------- # Legacy compatibility helpers (for old account_service v1 code references) # --------------------------------------------------------------------------- # Kept for backward compatibility so old imports don't break immediately. # New code should use PLATFORMS dict directly and resolve_platform_key(). _PLATFORM_PRIMARY_CN: dict[str, str] = { k: spec["display_name"] for k, spec in PLATFORMS.items() } PLATFORM_URLS: dict[str, str] = { k: (spec.get("default_url") or "") for k, spec in PLATFORMS.items() } def _platform_list_cn_for_help() -> str: """帮助文案:一行展示名,供 CLI 用法提示。""" return "、".join( spec["display_name"] for _k, spec in sorted( PLATFORMS.items(), key=lambda x: x[1]["display_name"] ) )