Initial commit
This commit is contained in:
521
skills/xlsx/templates/palettes.py
Executable file
521
skills/xlsx/templates/palettes.py
Executable file
@@ -0,0 +1,521 @@
|
||||
"""
|
||||
xlsx skill — Palette System (Style-First Theme Engine)
|
||||
=======================================================
|
||||
12 visual styles × scene-based fallback. No domain-color binding.
|
||||
|
||||
Themes (12):
|
||||
professional, warm, elegant, creative,
|
||||
muji, aesop, kinfolk, celine, bottega, chanel, bloomberg, original_blue
|
||||
|
||||
Matching priority:
|
||||
1. Explicit style keywords in prompt → direct match
|
||||
2. Scene/content keywords → infer style
|
||||
3. No match → professional (safe default)
|
||||
|
||||
Usage:
|
||||
from templates.palettes import resolve_palette, get_palette
|
||||
|
||||
# Auto-detect from user prompt
|
||||
palette = resolve_palette("帮我做一个温暖的销售月报") # Chinese prompt example
|
||||
# → warm palette
|
||||
|
||||
# Manual selection
|
||||
palette = get_palette("bottega")
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import Dict, Optional, Tuple
|
||||
|
||||
# ============================================================
|
||||
# §1 Palette Data Structure
|
||||
# ============================================================
|
||||
|
||||
_Palette = Dict[str, str | list]
|
||||
|
||||
|
||||
def _make_palette(
|
||||
*,
|
||||
primary: str,
|
||||
primary_light: str,
|
||||
accent_positive: str = "1B7D46",
|
||||
accent_negative: str = "C0392B",
|
||||
accent_warning: str = "D4820A",
|
||||
neutral_900: str = "37352F",
|
||||
neutral_600: str = "8C8A84",
|
||||
neutral_200: str = "E9E9E8",
|
||||
neutral_100: str = "F7F7F5",
|
||||
neutral_50: str = "FAFAF9",
|
||||
neutral_0: str = "FFFFFF",
|
||||
header_text: str = "FFFFFF",
|
||||
cf_positive_bg: str = "E8F5E9",
|
||||
cf_negative_bg: str = "FDEDEC",
|
||||
cf_warning_bg: str = "FEF9E7",
|
||||
) -> _Palette:
|
||||
return {
|
||||
"PRIMARY": primary,
|
||||
"PRIMARY_LIGHT": primary_light,
|
||||
"SECONDARY": primary_light,
|
||||
"ACCENT_POSITIVE": accent_positive,
|
||||
"ACCENT_NEGATIVE": accent_negative,
|
||||
"ACCENT_WARNING": accent_warning,
|
||||
"NEUTRAL_900": neutral_900,
|
||||
"NEUTRAL_600": neutral_600,
|
||||
"NEUTRAL_200": neutral_200,
|
||||
"NEUTRAL_100": neutral_100,
|
||||
"NEUTRAL_50": neutral_50,
|
||||
"NEUTRAL_0": neutral_0,
|
||||
"HEADER_TEXT": header_text,
|
||||
"CF_POSITIVE_BG": cf_positive_bg,
|
||||
"CF_NEGATIVE_BG": cf_negative_bg,
|
||||
"CF_WARNING_BG": cf_warning_bg,
|
||||
"CHART_COLORS": [primary, accent_positive, accent_warning, accent_negative, neutral_600],
|
||||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# §2 Legacy Palettes (6 original styles)
|
||||
# ============================================================
|
||||
|
||||
# -- Professional: formal business, universal default --
|
||||
PROFESSIONAL = _make_palette(
|
||||
primary="1B2A4A",
|
||||
primary_light="D6E4F0",
|
||||
)
|
||||
|
||||
# -- Warm: warm and vibrant, high impact --
|
||||
WARM = _make_palette(
|
||||
primary="B85C1E",
|
||||
primary_light="F5E6D5",
|
||||
accent_positive="2E7D32",
|
||||
accent_negative="C62828",
|
||||
accent_warning="E65100",
|
||||
neutral_900="3E2F1F",
|
||||
neutral_600="9C8B78",
|
||||
neutral_200="EAE0D5",
|
||||
neutral_100="F7F2EC",
|
||||
neutral_50="FBF8F5",
|
||||
)
|
||||
|
||||
# -- Fresh: natural freshness, friendly and light --
|
||||
FRESH = _make_palette(
|
||||
primary="0E7C6B",
|
||||
primary_light="D4F0EB",
|
||||
accent_positive="2E9E5A",
|
||||
accent_negative="D94F4F",
|
||||
accent_warning="E6A023",
|
||||
neutral_900="2F3735",
|
||||
neutral_600="7A8C87",
|
||||
neutral_200="DEE9E6",
|
||||
neutral_100="F2F8F6",
|
||||
neutral_50="F8FBFA",
|
||||
)
|
||||
|
||||
# -- Elegant: premium restraint, minimalist black-white --
|
||||
ELEGANT = _make_palette(
|
||||
primary="2C2C2C",
|
||||
primary_light="E5E5E5",
|
||||
accent_positive="4A4A4A",
|
||||
accent_negative="8B0000",
|
||||
accent_warning="6B6B6B",
|
||||
neutral_900="1A1A1A",
|
||||
neutral_600="808080",
|
||||
neutral_200="D4D4D4",
|
||||
neutral_100="F0F0F0",
|
||||
neutral_50="F8F8F8",
|
||||
)
|
||||
|
||||
# -- Creative: artistic personality, distinctive --
|
||||
CREATIVE = _make_palette(
|
||||
primary="6C5B7B",
|
||||
primary_light="E4DDE8",
|
||||
accent_positive="6B9E78",
|
||||
accent_negative="C06C7E",
|
||||
accent_warning="C4A46A",
|
||||
neutral_900="3E3A42",
|
||||
neutral_600="9590A0",
|
||||
neutral_200="E0DCE4",
|
||||
neutral_100="F3F1F5",
|
||||
neutral_50="F9F8FA",
|
||||
)
|
||||
|
||||
# -- Vibrant: high-saturation multi-color, data display --
|
||||
VIBRANT = _make_palette(
|
||||
primary="2563EB",
|
||||
primary_light="DBEAFE",
|
||||
accent_positive="16A34A",
|
||||
accent_negative="DC2626",
|
||||
accent_warning="EA580C",
|
||||
neutral_900="1E293B",
|
||||
neutral_600="64748B",
|
||||
neutral_200="E2E8F0",
|
||||
neutral_100="F1F5F9",
|
||||
neutral_50="F8FAFC",
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# §3 Premium Palettes (8 curated themes, "high-end feel" series)
|
||||
# ============================================================
|
||||
|
||||
# -- A · MUJI breathing feel: restrained minimalism, pencil on paper --
|
||||
MUJI = _make_palette(
|
||||
primary="2C2C2C",
|
||||
primary_light="F2F1EE",
|
||||
accent_positive="5B8C5A",
|
||||
accent_negative="C25450",
|
||||
accent_warning="C9A84C",
|
||||
neutral_900="2C2C2C",
|
||||
neutral_600="999999",
|
||||
neutral_200="E8E6E1",
|
||||
neutral_100="F9F9F7",
|
||||
neutral_50="FCFCFB",
|
||||
header_text="FFFFFF",
|
||||
)
|
||||
|
||||
# -- B · Aesop sandstone: earth tones, premium skincare packaging --
|
||||
AESOP = _make_palette(
|
||||
primary="3D3229",
|
||||
primary_light="EDE8E0",
|
||||
accent_positive="6B8F71",
|
||||
accent_negative="B85C4A",
|
||||
accent_warning="C4975A",
|
||||
neutral_900="4A4038",
|
||||
neutral_600="8C7B6B",
|
||||
neutral_200="DDD5C9",
|
||||
neutral_100="FAF8F5",
|
||||
neutral_50="FDFCFA",
|
||||
header_text="FFFFFF",
|
||||
)
|
||||
|
||||
# -- C · Dieter Rams Industrial: Less but better --
|
||||
DIETER_RAMS = _make_palette(
|
||||
primary="1A1A1A",
|
||||
primary_light="F7F7F7",
|
||||
accent_positive="2D8C6F",
|
||||
accent_negative="D44D3C",
|
||||
accent_warning="D4920A",
|
||||
neutral_900="1A1A1A",
|
||||
neutral_600="787878",
|
||||
neutral_200="E5E5E5",
|
||||
neutral_100="F7F7F7",
|
||||
neutral_50="FAFAFA",
|
||||
header_text="FFFFFF",
|
||||
)
|
||||
|
||||
# -- D · Kinfolk cream publication: independent magazine typography, slow-life aesthetic --
|
||||
KINFOLK = _make_palette(
|
||||
primary="5C524C",
|
||||
primary_light="F0ECE7",
|
||||
accent_positive="8DAA7F",
|
||||
accent_negative="C9776A",
|
||||
accent_warning="C9A96A",
|
||||
neutral_900="5C524C",
|
||||
neutral_600="BEB5AD",
|
||||
neutral_200="EAE5DF",
|
||||
neutral_100="FDFCFA",
|
||||
neutral_50="FEFDFB",
|
||||
header_text="FFFFFF",
|
||||
)
|
||||
|
||||
# -- E · Céline pure black-white: monochrome, fashion house coldness --
|
||||
CELINE = _make_palette(
|
||||
primary="000000",
|
||||
primary_light="FAFAFA",
|
||||
accent_positive="4A7C59",
|
||||
accent_negative="A63D2F",
|
||||
accent_warning="8C7A3C",
|
||||
neutral_900="000000",
|
||||
neutral_600="ADADAD",
|
||||
neutral_200="E0E0E0",
|
||||
neutral_100="FAFAFA",
|
||||
neutral_50="FDFDFD",
|
||||
header_text="FFFFFF",
|
||||
)
|
||||
|
||||
# -- F · Bottega dark green: Italian luxury, deep forest green --
|
||||
BOTTEGA = _make_palette(
|
||||
primary="2D4A3E",
|
||||
primary_light="E8F0EB",
|
||||
accent_positive="5FA67A",
|
||||
accent_negative="C2694B",
|
||||
accent_warning="B89B4A",
|
||||
neutral_900="3B5249",
|
||||
neutral_600="7A9B8C",
|
||||
neutral_200="D4E3DB",
|
||||
neutral_100="F6FAF8",
|
||||
neutral_50="F9FCFA",
|
||||
header_text="FFFFFF",
|
||||
)
|
||||
|
||||
# -- G · Chanel champagne gold: Chanel elegance, beige + golden brown --
|
||||
CHANEL = _make_palette(
|
||||
primary="1C1917",
|
||||
primary_light="E7DFD4",
|
||||
accent_positive="A3845B",
|
||||
accent_negative="B0413E",
|
||||
accent_warning="C4975A",
|
||||
neutral_900="1C1917",
|
||||
neutral_600="A39888",
|
||||
neutral_200="E7E0D5",
|
||||
neutral_100="FDFBF7",
|
||||
neutral_50="FEFDFB",
|
||||
header_text="FFFFFF",
|
||||
)
|
||||
|
||||
# -- H · Bloomberg deep blue: financial terminal, high-density data aesthetic --
|
||||
BLOOMBERG = _make_palette(
|
||||
primary="0D1B2A",
|
||||
primary_light="D6E0EB",
|
||||
accent_positive="10B981",
|
||||
accent_negative="EF4444",
|
||||
accent_warning="F59E0B",
|
||||
neutral_900="0D1B2A",
|
||||
neutral_600="708DA8",
|
||||
neutral_200="D6E0EB",
|
||||
neutral_100="F4F7FA",
|
||||
neutral_50="F8FAFB",
|
||||
header_text="FFFFFF",
|
||||
)
|
||||
|
||||
# -- Original Blue/Black: original blue-black color scheme (Round 1 #1/#6 style) --
|
||||
ORIGINAL_BLUE = _make_palette(
|
||||
primary="1B2A4A",
|
||||
primary_light="D6E4F0",
|
||||
accent_positive="2E8B57",
|
||||
accent_negative="EB5757",
|
||||
accent_warning="F2994A",
|
||||
neutral_900="333333",
|
||||
neutral_600="666666",
|
||||
neutral_200="E0E0E0",
|
||||
neutral_100="F5F5F5",
|
||||
neutral_50="FAFAFA",
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# §4 Registry
|
||||
# ============================================================
|
||||
|
||||
PALETTE_REGISTRY: Dict[str, _Palette] = {
|
||||
# Legacy (removed: fresh, vibrant)
|
||||
"professional": PROFESSIONAL,
|
||||
"warm": WARM,
|
||||
"elegant": ELEGANT,
|
||||
"creative": CREATIVE,
|
||||
# Premium (high-end feel)
|
||||
"muji": MUJI,
|
||||
"aesop": AESOP,
|
||||
# dieter_rams removed — header too dark, poor readability
|
||||
"kinfolk": KINFOLK,
|
||||
"celine": CELINE,
|
||||
"bottega": BOTTEGA,
|
||||
"chanel": CHANEL,
|
||||
"bloomberg": BLOOMBERG,
|
||||
"original_blue": ORIGINAL_BLUE,
|
||||
}
|
||||
|
||||
# Aliases for convenience
|
||||
PALETTE_REGISTRY["muji_breathing"] = MUJI
|
||||
PALETTE_REGISTRY["sandstone"] = AESOP
|
||||
PALETTE_REGISTRY["industrial"] = BLOOMBERG # was dieter_rams, redirected
|
||||
PALETTE_REGISTRY["cream"] = KINFOLK
|
||||
PALETTE_REGISTRY["monochrome"] = CELINE
|
||||
PALETTE_REGISTRY["forest_green"] = BOTTEGA
|
||||
PALETTE_REGISTRY["champagne"] = CHANEL
|
||||
PALETTE_REGISTRY["terminal"] = BLOOMBERG
|
||||
PALETTE_REGISTRY["classic_blue"] = ORIGINAL_BLUE
|
||||
|
||||
|
||||
# ============================================================
|
||||
# §5 Keyword Matching (three-step)
|
||||
# ============================================================
|
||||
|
||||
# Step 1: Explicit style keywords (highest priority)
|
||||
_STYLE_KEYWORDS: Dict[str, list[str]] = {
|
||||
"professional": [
|
||||
"正式", "商务", "专业", "沉稳", "稳重", "professional", "formal",
|
||||
"corporate", "business",
|
||||
],
|
||||
"warm": [
|
||||
"温暖", "活力", "热情", "热烈", "暖色", "温馨", "warm", "energetic",
|
||||
"活跃", "热力",
|
||||
],
|
||||
"elegant": [
|
||||
"极简", "简约", "elegant", "minimal",
|
||||
"清新", "自然", "清爽", "淡雅", "浅色", "明亮", "fresh",
|
||||
"natural", "clean", "light", "素雅",
|
||||
"多彩", "丰富", "鲜艳", "vivid", "colorful", "明快",
|
||||
"高饱和", "鲜明", "亮色",
|
||||
],
|
||||
"creative": [
|
||||
"文艺", "个性", "紫色", "莫兰迪", "creative", "artistic",
|
||||
"柔和", "雅致",
|
||||
],
|
||||
# Premium themes
|
||||
"muji": [
|
||||
"muji", "无印", "呼吸感", "白纸", "铅笔", "素净", "无印良品",
|
||||
],
|
||||
"aesop": [
|
||||
"aesop", "沙岩", "大地色", "护肤", "泥土", "陶", "terracotta",
|
||||
],
|
||||
"bloomberg": [
|
||||
"bloomberg", "终端", "深蓝", "terminal", "金融终端", "数据终端",
|
||||
"rams", "dieter", "工业", "德系", "包豪斯", "bauhaus", "less but better",
|
||||
"工业风",
|
||||
],
|
||||
"kinfolk": [
|
||||
"kinfolk", "奶油", "刊物", "杂志", "慢生活", "latte", "拿铁",
|
||||
],
|
||||
"celine": [
|
||||
"celine", "黑白", "时装", "冷冽", "mono", "纯黑", "monochrome",
|
||||
],
|
||||
"bottega": [
|
||||
"bottega", "墨绿", "深绿", "森林", "橄榄", "绿色", "forest",
|
||||
"贵气", "奢牌",
|
||||
],
|
||||
"chanel": [
|
||||
"chanel", "米金", "金棕", "香奈儿", "champagne", "米色", "奶茶",
|
||||
],
|
||||
"original_blue": [
|
||||
"原始", "经典蓝", "classic blue", "original", "传统蓝",
|
||||
],
|
||||
}
|
||||
|
||||
# Step 2: Scene keywords → infer style (lower priority)
|
||||
_SCENE_TO_STYLE: Dict[str, str] = {
|
||||
# Sales / Marketing / Ops → warm
|
||||
"销售": "warm", "营销": "warm", "运营": "warm", "客户": "warm",
|
||||
"业绩": "warm", "KPI": "warm", "GMV": "warm", "转化": "warm",
|
||||
"漏斗": "warm", "签约": "warm", "提成": "warm", "电商": "warm",
|
||||
"sales": "warm", "marketing": "warm", "campaign": "warm",
|
||||
# Education / Medical → muji (was fresh, now removed)
|
||||
"成绩": "muji", "考试": "muji", "学生": "muji", "课程": "muji",
|
||||
"教育": "muji", "GPA": "muji", "学校": "muji", "班级": "muji",
|
||||
"医疗": "muji", "健康": "muji", "患者": "muji", "体检": "muji",
|
||||
"医院": "muji", "科室": "muji", "护理": "muji",
|
||||
"环保": "muji",
|
||||
"education": "muji", "medical": "muji", "health": "muji",
|
||||
# Design / Brand → creative
|
||||
"设计": "creative", "创意": "creative", "品牌": "creative",
|
||||
"UI": "creative", "UX": "creative", "作品": "creative",
|
||||
"视觉": "creative", "素材": "creative",
|
||||
"design": "creative", "brand": "creative", "portfolio": "creative",
|
||||
# Formal / Reporting → professional
|
||||
"汇报": "professional", "提案": "professional", "会议": "professional",
|
||||
"述职": "professional", "总结": "professional", "报告": "professional",
|
||||
"年报": "professional", "季报": "professional", "月报": "professional",
|
||||
"财务": "professional", "财报": "professional", "预算": "professional",
|
||||
"审计": "professional", "咨询": "professional", "战略": "professional",
|
||||
"finance": "professional", "budget": "professional", "report": "professional",
|
||||
# Minimal / Premium → elegant
|
||||
"premium": "elegant", "luxury": "elegant",
|
||||
# Finance data → bloomberg
|
||||
"股票": "bloomberg", "基金": "bloomberg", "投资": "bloomberg",
|
||||
"交易": "bloomberg", "行情": "bloomberg", "K线": "bloomberg",
|
||||
"stock": "bloomberg", "trading": "bloomberg", "portfolio_fin": "bloomberg",
|
||||
# High-end / Luxury brand → chanel
|
||||
"奢侈": "chanel", "高端": "chanel", "高级": "chanel",
|
||||
}
|
||||
|
||||
|
||||
def _match_style_keywords(text: str) -> Optional[str]:
|
||||
"""Step 1: Match explicit style keywords. Returns style name or None."""
|
||||
text_lower = text.lower()
|
||||
best_match = None
|
||||
best_score = 0
|
||||
for style, keywords in _STYLE_KEYWORDS.items():
|
||||
score = sum(1 for kw in keywords if kw.lower() in text_lower)
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_match = style
|
||||
return best_match if best_score > 0 else None
|
||||
|
||||
|
||||
def _infer_from_scene(text: str) -> Optional[str]:
|
||||
"""Step 2: Infer style from scene/content keywords. Returns style name or None."""
|
||||
text_lower = text.lower()
|
||||
votes: Dict[str, int] = {}
|
||||
for keyword, style in _SCENE_TO_STYLE.items():
|
||||
if keyword.lower() in text_lower:
|
||||
votes[style] = votes.get(style, 0) + 1
|
||||
if not votes:
|
||||
return None
|
||||
return max(votes, key=votes.get)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# §6 Public API
|
||||
# ============================================================
|
||||
|
||||
def get_palette(style: str = "professional") -> _Palette:
|
||||
"""Get a palette by style name. Falls back to professional."""
|
||||
return PALETTE_REGISTRY.get(style, PROFESSIONAL)
|
||||
|
||||
|
||||
def resolve_palette(prompt: str) -> _Palette:
|
||||
"""
|
||||
Auto-detect style from user prompt (three-step):
|
||||
1. Explicit style keywords → direct match
|
||||
2. Scene/content keywords → infer style
|
||||
3. No match → professional (safe default)
|
||||
"""
|
||||
style = detect_style(prompt)
|
||||
return get_palette(style)
|
||||
|
||||
|
||||
def resolve_palette_with_info(prompt: str) -> Tuple[_Palette, str]:
|
||||
"""Same as resolve_palette but also returns the detected style name."""
|
||||
style = detect_style(prompt)
|
||||
return get_palette(style), style
|
||||
|
||||
|
||||
def detect_style(prompt: str) -> str:
|
||||
"""
|
||||
Detect style from prompt. Three-step priority:
|
||||
1. Explicit style keywords
|
||||
2. Scene keywords → infer style
|
||||
3. Default: professional
|
||||
"""
|
||||
style = _match_style_keywords(prompt)
|
||||
if style:
|
||||
return style
|
||||
style = _infer_from_scene(prompt)
|
||||
if style:
|
||||
return style
|
||||
return "professional"
|
||||
|
||||
|
||||
def list_available() -> list[str]:
|
||||
"""Return list of available style names (no aliases)."""
|
||||
# Return only canonical names, not aliases
|
||||
canonical = [
|
||||
"professional", "warm", "elegant", "creative",
|
||||
"muji", "aesop", "kinfolk", "celine", "bottega",
|
||||
"chanel", "bloomberg", "original_blue",
|
||||
]
|
||||
return canonical
|
||||
|
||||
|
||||
def apply_palette(palette: _Palette, module_globals: dict):
|
||||
"""
|
||||
Inject palette tokens into a module's global namespace.
|
||||
Designed to be called from base.py to override its color constants.
|
||||
"""
|
||||
key_map = {
|
||||
"PRIMARY": "PRIMARY",
|
||||
"PRIMARY_LIGHT": "PRIMARY_LIGHT",
|
||||
"SECONDARY": "SECONDARY",
|
||||
"ACCENT_POSITIVE": "ACCENT_POSITIVE",
|
||||
"ACCENT_NEGATIVE": "ACCENT_NEGATIVE",
|
||||
"ACCENT_WARNING": "ACCENT_WARNING",
|
||||
"NEUTRAL_900": "NEUTRAL_900",
|
||||
"NEUTRAL_600": "NEUTRAL_600",
|
||||
"NEUTRAL_200": "NEUTRAL_200",
|
||||
"NEUTRAL_100": "NEUTRAL_100",
|
||||
"NEUTRAL_50": "NEUTRAL_50",
|
||||
"NEUTRAL_0": "NEUTRAL_0",
|
||||
"CHART_COLORS": "CHART_COLORS",
|
||||
}
|
||||
for palette_key, global_key in key_map.items():
|
||||
if palette_key in palette:
|
||||
module_globals[global_key] = palette[palette_key]
|
||||
Reference in New Issue
Block a user