ui-ux-pro-max-cli 2.8.8
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 +99 -0
- package/assets/data/_sync_all.py +414 -0
- package/assets/data/app-interface.csv +31 -0
- package/assets/data/charts.csv +26 -0
- package/assets/data/colors.csv +162 -0
- package/assets/data/design.csv +1776 -0
- package/assets/data/draft.csv +1779 -0
- package/assets/data/google-fonts.csv +1924 -0
- package/assets/data/icons.csv +106 -0
- package/assets/data/landing.csv +35 -0
- package/assets/data/products.csv +162 -0
- package/assets/data/react-performance.csv +45 -0
- package/assets/data/stacks/angular.csv +51 -0
- package/assets/data/stacks/astro.csv +54 -0
- package/assets/data/stacks/flutter.csv +53 -0
- package/assets/data/stacks/html-tailwind.csv +56 -0
- package/assets/data/stacks/javafx.csv +76 -0
- package/assets/data/stacks/jetpack-compose.csv +53 -0
- package/assets/data/stacks/laravel.csv +51 -0
- package/assets/data/stacks/nextjs.csv +53 -0
- package/assets/data/stacks/nuxt-ui.csv +71 -0
- package/assets/data/stacks/nuxtjs.csv +59 -0
- package/assets/data/stacks/react-native.csv +52 -0
- package/assets/data/stacks/react.csv +54 -0
- package/assets/data/stacks/shadcn.csv +61 -0
- package/assets/data/stacks/svelte.csv +54 -0
- package/assets/data/stacks/swiftui.csv +51 -0
- package/assets/data/stacks/threejs.csv +54 -0
- package/assets/data/stacks/vue.csv +50 -0
- package/assets/data/styles.csv +85 -0
- package/assets/data/typography.csv +74 -0
- package/assets/data/ui-reasoning.csv +162 -0
- package/assets/data/ux-guidelines.csv +100 -0
- package/assets/scripts/core.py +263 -0
- package/assets/scripts/design_system.py +1157 -0
- package/assets/scripts/search.py +114 -0
- package/assets/skills/banner-design/SKILL.md +196 -0
- package/assets/skills/banner-design/references/banner-sizes-and-styles.md +118 -0
- package/assets/skills/brand/SKILL.md +97 -0
- package/assets/skills/brand/references/approval-checklist.md +169 -0
- package/assets/skills/brand/references/asset-organization.md +157 -0
- package/assets/skills/brand/references/brand-guideline-template.md +140 -0
- package/assets/skills/brand/references/color-palette-management.md +186 -0
- package/assets/skills/brand/references/consistency-checklist.md +94 -0
- package/assets/skills/brand/references/logo-usage-rules.md +185 -0
- package/assets/skills/brand/references/messaging-framework.md +85 -0
- package/assets/skills/brand/references/typography-specifications.md +214 -0
- package/assets/skills/brand/references/update.md +118 -0
- package/assets/skills/brand/references/visual-identity.md +96 -0
- package/assets/skills/brand/references/voice-framework.md +88 -0
- package/assets/skills/brand/scripts/extract-colors.cjs +341 -0
- package/assets/skills/brand/scripts/inject-brand-context.cjs +349 -0
- package/assets/skills/brand/scripts/sync-brand-to-tokens.cjs +266 -0
- package/assets/skills/brand/scripts/validate-asset.cjs +387 -0
- package/assets/skills/brand/templates/brand-guidelines-starter.md +275 -0
- package/assets/skills/design/SKILL.md +313 -0
- package/assets/skills/design/data/cip/deliverables.csv +51 -0
- package/assets/skills/design/data/cip/industries.csv +21 -0
- package/assets/skills/design/data/cip/mockup-contexts.csv +21 -0
- package/assets/skills/design/data/cip/styles.csv +21 -0
- package/assets/skills/design/data/icon/styles.csv +16 -0
- package/assets/skills/design/data/logo/colors.csv +56 -0
- package/assets/skills/design/data/logo/industries.csv +56 -0
- package/assets/skills/design/data/logo/styles.csv +56 -0
- package/assets/skills/design/references/banner-sizes-and-styles.md +118 -0
- package/assets/skills/design/references/cip-deliverable-guide.md +95 -0
- package/assets/skills/design/references/cip-design.md +121 -0
- package/assets/skills/design/references/cip-prompt-engineering.md +84 -0
- package/assets/skills/design/references/cip-style-guide.md +68 -0
- package/assets/skills/design/references/design-routing.md +207 -0
- package/assets/skills/design/references/icon-design.md +122 -0
- package/assets/skills/design/references/logo-color-psychology.md +101 -0
- package/assets/skills/design/references/logo-design.md +92 -0
- package/assets/skills/design/references/logo-prompt-engineering.md +158 -0
- package/assets/skills/design/references/logo-style-guide.md +109 -0
- package/assets/skills/design/references/slides-copywriting-formulas.md +84 -0
- package/assets/skills/design/references/slides-create.md +4 -0
- package/assets/skills/design/references/slides-html-template.md +295 -0
- package/assets/skills/design/references/slides-layout-patterns.md +137 -0
- package/assets/skills/design/references/slides-strategies.md +94 -0
- package/assets/skills/design/references/slides.md +42 -0
- package/assets/skills/design/references/social-photos-design.md +329 -0
- package/assets/skills/design/scripts/cip/core.py +215 -0
- package/assets/skills/design/scripts/cip/generate.py +484 -0
- package/assets/skills/design/scripts/cip/render-html.py +424 -0
- package/assets/skills/design/scripts/cip/search.py +127 -0
- package/assets/skills/design/scripts/icon/generate.py +487 -0
- package/assets/skills/design/scripts/logo/core.py +175 -0
- package/assets/skills/design/scripts/logo/generate.py +362 -0
- package/assets/skills/design/scripts/logo/search.py +114 -0
- package/assets/skills/design-system/SKILL.md +244 -0
- package/assets/skills/design-system/data/slide-backgrounds.csv +11 -0
- package/assets/skills/design-system/data/slide-charts.csv +26 -0
- package/assets/skills/design-system/data/slide-color-logic.csv +14 -0
- package/assets/skills/design-system/data/slide-copy.csv +26 -0
- package/assets/skills/design-system/data/slide-layout-logic.csv +16 -0
- package/assets/skills/design-system/data/slide-layouts.csv +26 -0
- package/assets/skills/design-system/data/slide-strategies.csv +16 -0
- package/assets/skills/design-system/data/slide-typography.csv +15 -0
- package/assets/skills/design-system/references/component-specs.md +236 -0
- package/assets/skills/design-system/references/component-tokens.md +214 -0
- package/assets/skills/design-system/references/primitive-tokens.md +203 -0
- package/assets/skills/design-system/references/semantic-tokens.md +215 -0
- package/assets/skills/design-system/references/states-and-variants.md +241 -0
- package/assets/skills/design-system/references/tailwind-integration.md +251 -0
- package/assets/skills/design-system/references/token-architecture.md +224 -0
- package/assets/skills/design-system/scripts/embed-tokens.cjs +99 -0
- package/assets/skills/design-system/scripts/fetch-background.py +317 -0
- package/assets/skills/design-system/scripts/generate-slide.py +770 -0
- package/assets/skills/design-system/scripts/generate-tokens.cjs +205 -0
- package/assets/skills/design-system/scripts/html-token-validator.py +327 -0
- package/assets/skills/design-system/scripts/search-slides.py +218 -0
- package/assets/skills/design-system/scripts/slide-token-validator.py +35 -0
- package/assets/skills/design-system/scripts/slide_search_core.py +453 -0
- package/assets/skills/design-system/scripts/validate-tokens.cjs +251 -0
- package/assets/skills/design-system/templates/design-tokens-starter.json +143 -0
- package/assets/skills/slides/SKILL.md +40 -0
- package/assets/skills/slides/references/copywriting-formulas.md +84 -0
- package/assets/skills/slides/references/create.md +4 -0
- package/assets/skills/slides/references/html-template.md +295 -0
- package/assets/skills/slides/references/layout-patterns.md +137 -0
- package/assets/skills/slides/references/slide-strategies.md +94 -0
- package/assets/skills/ui-styling/LICENSE.txt +202 -0
- package/assets/skills/ui-styling/SKILL.md +324 -0
- package/assets/skills/ui-styling/references/canvas-design-system.md +320 -0
- package/assets/skills/ui-styling/references/shadcn-accessibility.md +471 -0
- package/assets/skills/ui-styling/references/shadcn-components.md +424 -0
- package/assets/skills/ui-styling/references/shadcn-theming.md +373 -0
- package/assets/skills/ui-styling/references/tailwind-customization.md +483 -0
- package/assets/skills/ui-styling/references/tailwind-responsive.md +382 -0
- package/assets/skills/ui-styling/references/tailwind-utilities.md +455 -0
- package/assets/skills/ui-styling/scripts/requirements.txt +17 -0
- package/assets/skills/ui-styling/scripts/shadcn_add.py +308 -0
- package/assets/skills/ui-styling/scripts/tailwind_config_gen.py +473 -0
- package/assets/skills/ui-styling/scripts/tests/coverage-ui.json +1 -0
- package/assets/skills/ui-styling/scripts/tests/requirements.txt +3 -0
- package/assets/skills/ui-styling/scripts/tests/test_shadcn_add.py +266 -0
- package/assets/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py +336 -0
- package/assets/templates/base/quick-reference.md +297 -0
- package/assets/templates/base/skill-content.md +368 -0
- package/assets/templates/platforms/agent.json +21 -0
- package/assets/templates/platforms/augment.json +18 -0
- package/assets/templates/platforms/claude.json +21 -0
- package/assets/templates/platforms/codebuddy.json +21 -0
- package/assets/templates/platforms/codex.json +21 -0
- package/assets/templates/platforms/continue.json +21 -0
- package/assets/templates/platforms/copilot.json +21 -0
- package/assets/templates/platforms/cursor.json +21 -0
- package/assets/templates/platforms/droid.json +21 -0
- package/assets/templates/platforms/gemini.json +21 -0
- package/assets/templates/platforms/kilocode.json +21 -0
- package/assets/templates/platforms/kiro.json +21 -0
- package/assets/templates/platforms/opencode.json +21 -0
- package/assets/templates/platforms/qoder.json +21 -0
- package/assets/templates/platforms/roocode.json +21 -0
- package/assets/templates/platforms/trae.json +21 -0
- package/assets/templates/platforms/warp.json +18 -0
- package/assets/templates/platforms/windsurf.json +21 -0
- package/dist/index.js +10630 -0
- package/package.json +51 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Icon Generation Script using Gemini 3.1 Pro Preview API
|
|
5
|
+
Generates SVG icons via text generation (SVG is XML text format)
|
|
6
|
+
|
|
7
|
+
Model: gemini-3.1-pro-preview - best thinking, token efficiency, factual consistency
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python generate.py --prompt "settings gear icon" --style outlined
|
|
11
|
+
python generate.py --prompt "shopping cart" --style filled --color "#6366F1"
|
|
12
|
+
python generate.py --name "dashboard" --category navigation --style duotone
|
|
13
|
+
python generate.py --prompt "cloud upload" --batch 4 --output-dir ./icons
|
|
14
|
+
python generate.py --prompt "user profile" --sizes "16,24,32,48"
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import sys
|
|
22
|
+
import time
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from datetime import datetime
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def load_env():
|
|
28
|
+
"""Load .env files in priority order"""
|
|
29
|
+
env_paths = [
|
|
30
|
+
Path(__file__).parent.parent.parent / ".env",
|
|
31
|
+
Path.home() / ".claude" / "skills" / ".env",
|
|
32
|
+
Path.home() / ".claude" / ".env"
|
|
33
|
+
]
|
|
34
|
+
for env_path in env_paths:
|
|
35
|
+
if env_path.exists():
|
|
36
|
+
with open(env_path) as f:
|
|
37
|
+
for line in f:
|
|
38
|
+
line = line.strip()
|
|
39
|
+
if line and not line.startswith('#') and '=' in line:
|
|
40
|
+
key, value = line.split('=', 1)
|
|
41
|
+
if key not in os.environ:
|
|
42
|
+
os.environ[key] = value.strip('"\'')
|
|
43
|
+
|
|
44
|
+
load_env()
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
from google import genai
|
|
48
|
+
from google.genai import types
|
|
49
|
+
except ImportError:
|
|
50
|
+
print("Error: google-genai package not installed.")
|
|
51
|
+
print("Install with: pip install google-genai")
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ============ CONFIGURATION ============
|
|
56
|
+
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
|
|
57
|
+
MODEL = "gemini-3.1-pro-preview"
|
|
58
|
+
|
|
59
|
+
# Icon styles with SVG-specific instructions
|
|
60
|
+
ICON_STYLES = {
|
|
61
|
+
"outlined": "outlined stroke icons, 2px stroke width, no fill, clean open paths",
|
|
62
|
+
"filled": "solid filled icons, no stroke, flat color fills, bold shapes",
|
|
63
|
+
"duotone": "duotone style with primary color at full opacity and secondary color at 30% opacity, layered shapes",
|
|
64
|
+
"thin": "thin line icons, 1px or 1.5px stroke width, delicate minimalist lines",
|
|
65
|
+
"bold": "bold thick line icons, 3px stroke width, heavy weight, impactful",
|
|
66
|
+
"rounded": "rounded icons with round line caps and joins, soft corners, friendly feel",
|
|
67
|
+
"sharp": "sharp angular icons, square line caps and mitered joins, precise edges",
|
|
68
|
+
"flat": "flat design icons, solid fills, no gradients or shadows, geometric simplicity",
|
|
69
|
+
"gradient": "linear or radial gradient fills, modern vibrant color transitions",
|
|
70
|
+
"glassmorphism": "glassmorphism style with semi-transparent fills, blur backdrop effect simulation, frosted glass",
|
|
71
|
+
"pixel": "pixel art style icons on a grid, retro 8-bit aesthetic, crisp edges",
|
|
72
|
+
"hand-drawn": "hand-drawn sketch style, slightly irregular strokes, organic feel, imperfect lines",
|
|
73
|
+
"isometric": "isometric 3D projection, 30-degree angles, dimensional depth",
|
|
74
|
+
"glyph": "simple glyph style, single solid shape, minimal detail, pictogram",
|
|
75
|
+
"animated-ready": "animated-ready SVG with named groups and IDs for CSS/JS animation targets",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
ICON_CATEGORIES = {
|
|
79
|
+
"navigation": "arrows, menus, hamburger, chevrons, home, back, forward, breadcrumb",
|
|
80
|
+
"action": "edit, delete, save, download, upload, share, copy, paste, print, search",
|
|
81
|
+
"communication": "email, chat, phone, video call, notification, bell, message bubble",
|
|
82
|
+
"media": "play, pause, stop, skip, volume, microphone, camera, image, gallery",
|
|
83
|
+
"file": "document, folder, archive, attachment, cloud, database, storage",
|
|
84
|
+
"user": "person, group, avatar, profile, settings, lock, key, shield",
|
|
85
|
+
"commerce": "cart, bag, wallet, credit card, receipt, tag, gift, store",
|
|
86
|
+
"data": "chart, graph, analytics, dashboard, table, filter, sort, calendar",
|
|
87
|
+
"development": "code, terminal, bug, git, API, server, database, deploy",
|
|
88
|
+
"social": "heart, star, thumbs up, bookmark, flag, trophy, badge, crown",
|
|
89
|
+
"weather": "sun, moon, cloud, rain, snow, wind, thunder, temperature",
|
|
90
|
+
"map": "pin, location, compass, globe, route, directions, map marker",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# SVG generation prompt template
|
|
94
|
+
SVG_PROMPT_TEMPLATE = """Generate a clean, production-ready SVG icon.
|
|
95
|
+
|
|
96
|
+
Requirements:
|
|
97
|
+
- Output ONLY valid SVG code, nothing else
|
|
98
|
+
- ViewBox: "0 0 {viewbox} {viewbox}"
|
|
99
|
+
- Use currentColor for strokes/fills (inherits CSS color)
|
|
100
|
+
- No embedded fonts or text elements unless specifically requested
|
|
101
|
+
- No raster images or external references
|
|
102
|
+
- Optimized paths with minimal nodes
|
|
103
|
+
- Accessible: include <title> element with icon description
|
|
104
|
+
{style_instructions}
|
|
105
|
+
{color_instructions}
|
|
106
|
+
{size_instructions}
|
|
107
|
+
|
|
108
|
+
Icon to generate: {prompt}
|
|
109
|
+
|
|
110
|
+
Output the SVG code only, wrapped in ```svg``` code block."""
|
|
111
|
+
|
|
112
|
+
SVG_BATCH_PROMPT_TEMPLATE = """Generate {count} distinct SVG icon variations for: {prompt}
|
|
113
|
+
|
|
114
|
+
Requirements for EACH icon:
|
|
115
|
+
- Output ONLY valid SVG code
|
|
116
|
+
- ViewBox: "0 0 {viewbox} {viewbox}"
|
|
117
|
+
- Use currentColor for strokes/fills (inherits CSS color)
|
|
118
|
+
- No embedded fonts, raster images, or external references
|
|
119
|
+
- Optimized paths with minimal nodes
|
|
120
|
+
- Include <title> element with icon description
|
|
121
|
+
{style_instructions}
|
|
122
|
+
{color_instructions}
|
|
123
|
+
|
|
124
|
+
Generate {count} different visual interpretations. Output each SVG in a separate ```svg``` code block.
|
|
125
|
+
Label each variation (e.g., "Variation 1: [brief description]")."""
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def extract_svgs(text):
|
|
129
|
+
"""Extract SVG code blocks from model response"""
|
|
130
|
+
svgs = []
|
|
131
|
+
|
|
132
|
+
# Try ```svg code blocks first
|
|
133
|
+
pattern = r'```svg\s*\n(.*?)```'
|
|
134
|
+
matches = re.findall(pattern, text, re.DOTALL)
|
|
135
|
+
if matches:
|
|
136
|
+
svgs.extend(matches)
|
|
137
|
+
|
|
138
|
+
# Fallback: try ```xml code blocks
|
|
139
|
+
if not svgs:
|
|
140
|
+
pattern = r'```xml\s*\n(.*?)```'
|
|
141
|
+
matches = re.findall(pattern, text, re.DOTALL)
|
|
142
|
+
svgs.extend(matches)
|
|
143
|
+
|
|
144
|
+
# Fallback: try bare <svg> tags
|
|
145
|
+
if not svgs:
|
|
146
|
+
pattern = r'(<svg[^>]*>.*?</svg>)'
|
|
147
|
+
matches = re.findall(pattern, text, re.DOTALL)
|
|
148
|
+
svgs.extend(matches)
|
|
149
|
+
|
|
150
|
+
# Clean up extracted SVGs
|
|
151
|
+
cleaned = []
|
|
152
|
+
for svg in svgs:
|
|
153
|
+
svg = svg.strip()
|
|
154
|
+
if not svg.startswith('<svg'):
|
|
155
|
+
# Try to find <svg> within the extracted text
|
|
156
|
+
match = re.search(r'(<svg[^>]*>.*?</svg>)', svg, re.DOTALL)
|
|
157
|
+
if match:
|
|
158
|
+
svg = match.group(1)
|
|
159
|
+
else:
|
|
160
|
+
continue
|
|
161
|
+
cleaned.append(svg)
|
|
162
|
+
|
|
163
|
+
return cleaned
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def apply_color(svg_code, color):
|
|
167
|
+
"""Replace currentColor with specific color if provided"""
|
|
168
|
+
if color:
|
|
169
|
+
# Replace currentColor with the specified color
|
|
170
|
+
svg_code = svg_code.replace('currentColor', color)
|
|
171
|
+
# If no currentColor was present, add fill/stroke color
|
|
172
|
+
if color not in svg_code:
|
|
173
|
+
svg_code = svg_code.replace('<svg', f'<svg color="{color}"', 1)
|
|
174
|
+
return svg_code
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def apply_viewbox_size(svg_code, size):
|
|
178
|
+
"""Adjust SVG viewBox to target size"""
|
|
179
|
+
if size:
|
|
180
|
+
# Update width/height attributes if present
|
|
181
|
+
svg_code = re.sub(r'width="[^"]*"', f'width="{size}"', svg_code)
|
|
182
|
+
svg_code = re.sub(r'height="[^"]*"', f'height="{size}"', svg_code)
|
|
183
|
+
# Add width/height if not present
|
|
184
|
+
if 'width=' not in svg_code:
|
|
185
|
+
svg_code = svg_code.replace('<svg', f'<svg width="{size}" height="{size}"', 1)
|
|
186
|
+
return svg_code
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def generate_icon(prompt, style=None, category=None, name=None,
|
|
190
|
+
color=None, size=24, output_path=None, viewbox=24):
|
|
191
|
+
"""Generate a single SVG icon using Gemini 3.1 Pro Preview"""
|
|
192
|
+
|
|
193
|
+
if not GEMINI_API_KEY:
|
|
194
|
+
print("Error: GEMINI_API_KEY not set")
|
|
195
|
+
print("Set it with: export GEMINI_API_KEY='your-key'")
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
client = genai.Client(api_key=GEMINI_API_KEY)
|
|
199
|
+
|
|
200
|
+
# Build style instructions
|
|
201
|
+
style_instructions = ""
|
|
202
|
+
if style and style in ICON_STYLES:
|
|
203
|
+
style_instructions = f"- Style: {ICON_STYLES[style]}"
|
|
204
|
+
|
|
205
|
+
# Build color instructions
|
|
206
|
+
color_instructions = "- Use currentColor for all strokes and fills"
|
|
207
|
+
if color:
|
|
208
|
+
color_instructions = f"- Use color: {color} for primary elements, currentColor for secondary"
|
|
209
|
+
|
|
210
|
+
# Build size instructions
|
|
211
|
+
size_instructions = f"- Design for {size}px display size, optimize detail level accordingly"
|
|
212
|
+
|
|
213
|
+
# Build final prompt
|
|
214
|
+
icon_prompt = prompt
|
|
215
|
+
if category and category in ICON_CATEGORIES:
|
|
216
|
+
icon_prompt = f"{prompt} (category: {ICON_CATEGORIES[category]})"
|
|
217
|
+
if name:
|
|
218
|
+
icon_prompt = f"'{name}' icon: {icon_prompt}"
|
|
219
|
+
|
|
220
|
+
full_prompt = SVG_PROMPT_TEMPLATE.format(
|
|
221
|
+
prompt=icon_prompt,
|
|
222
|
+
viewbox=viewbox,
|
|
223
|
+
style_instructions=style_instructions,
|
|
224
|
+
color_instructions=color_instructions,
|
|
225
|
+
size_instructions=size_instructions
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
print(f"Generating icon with {MODEL}...")
|
|
229
|
+
print(f"Prompt: {prompt}")
|
|
230
|
+
if style:
|
|
231
|
+
print(f"Style: {style}")
|
|
232
|
+
print()
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
response = client.models.generate_content(
|
|
236
|
+
model=MODEL,
|
|
237
|
+
contents=full_prompt,
|
|
238
|
+
config=types.GenerateContentConfig(
|
|
239
|
+
temperature=0.7,
|
|
240
|
+
max_output_tokens=4096,
|
|
241
|
+
)
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Extract SVG from response
|
|
245
|
+
response_text = response.text if hasattr(response, 'text') else ""
|
|
246
|
+
if not response_text:
|
|
247
|
+
for part in response.candidates[0].content.parts:
|
|
248
|
+
if hasattr(part, 'text') and part.text:
|
|
249
|
+
response_text += part.text
|
|
250
|
+
|
|
251
|
+
svgs = extract_svgs(response_text)
|
|
252
|
+
|
|
253
|
+
if not svgs:
|
|
254
|
+
print("No valid SVG generated. Model response:")
|
|
255
|
+
print(response_text[:500])
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
svg_code = svgs[0]
|
|
259
|
+
|
|
260
|
+
# Apply color if specified
|
|
261
|
+
svg_code = apply_color(svg_code, color)
|
|
262
|
+
|
|
263
|
+
# Apply size
|
|
264
|
+
svg_code = apply_viewbox_size(svg_code, size)
|
|
265
|
+
|
|
266
|
+
# Determine output path
|
|
267
|
+
if output_path is None:
|
|
268
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
269
|
+
slug = name or prompt.split()[0] if prompt else "icon"
|
|
270
|
+
slug = re.sub(r'[^a-zA-Z0-9_-]', '_', slug.lower())
|
|
271
|
+
style_suffix = f"_{style}" if style else ""
|
|
272
|
+
output_path = f"{slug}{style_suffix}_{timestamp}.svg"
|
|
273
|
+
|
|
274
|
+
# Save SVG
|
|
275
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
276
|
+
f.write(svg_code)
|
|
277
|
+
|
|
278
|
+
print(f"Icon saved to: {output_path}")
|
|
279
|
+
return output_path
|
|
280
|
+
|
|
281
|
+
except Exception as e:
|
|
282
|
+
print(f"Error generating icon: {e}")
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def generate_batch(prompt, count, output_dir, style=None, color=None,
|
|
287
|
+
viewbox=24, name=None):
|
|
288
|
+
"""Generate multiple icon variations"""
|
|
289
|
+
|
|
290
|
+
if not GEMINI_API_KEY:
|
|
291
|
+
print("Error: GEMINI_API_KEY not set")
|
|
292
|
+
return []
|
|
293
|
+
|
|
294
|
+
client = genai.Client(api_key=GEMINI_API_KEY)
|
|
295
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
296
|
+
|
|
297
|
+
# Build instructions
|
|
298
|
+
style_instructions = ""
|
|
299
|
+
if style and style in ICON_STYLES:
|
|
300
|
+
style_instructions = f"- Style: {ICON_STYLES[style]}"
|
|
301
|
+
|
|
302
|
+
color_instructions = "- Use currentColor for all strokes and fills"
|
|
303
|
+
if color:
|
|
304
|
+
color_instructions = f"- Use color: {color} for primary elements"
|
|
305
|
+
|
|
306
|
+
full_prompt = SVG_BATCH_PROMPT_TEMPLATE.format(
|
|
307
|
+
prompt=prompt,
|
|
308
|
+
count=count,
|
|
309
|
+
viewbox=viewbox,
|
|
310
|
+
style_instructions=style_instructions,
|
|
311
|
+
color_instructions=color_instructions
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
print(f"\n{'='*60}")
|
|
315
|
+
print(f" BATCH ICON GENERATION")
|
|
316
|
+
print(f" Model: {MODEL}")
|
|
317
|
+
print(f" Prompt: {prompt}")
|
|
318
|
+
print(f" Variants: {count}")
|
|
319
|
+
print(f" Output: {output_dir}")
|
|
320
|
+
print(f"{'='*60}\n")
|
|
321
|
+
|
|
322
|
+
try:
|
|
323
|
+
response = client.models.generate_content(
|
|
324
|
+
model=MODEL,
|
|
325
|
+
contents=full_prompt,
|
|
326
|
+
config=types.GenerateContentConfig(
|
|
327
|
+
temperature=0.9,
|
|
328
|
+
max_output_tokens=16384,
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
response_text = response.text if hasattr(response, 'text') else ""
|
|
333
|
+
if not response_text:
|
|
334
|
+
for part in response.candidates[0].content.parts:
|
|
335
|
+
if hasattr(part, 'text') and part.text:
|
|
336
|
+
response_text += part.text
|
|
337
|
+
|
|
338
|
+
svgs = extract_svgs(response_text)
|
|
339
|
+
|
|
340
|
+
if not svgs:
|
|
341
|
+
print("No valid SVGs generated.")
|
|
342
|
+
print(response_text[:500])
|
|
343
|
+
return []
|
|
344
|
+
|
|
345
|
+
results = []
|
|
346
|
+
slug = name or re.sub(r'[^a-zA-Z0-9_-]', '_', prompt.split()[0].lower())
|
|
347
|
+
style_suffix = f"_{style}" if style else ""
|
|
348
|
+
|
|
349
|
+
for i, svg_code in enumerate(svgs[:count]):
|
|
350
|
+
svg_code = apply_color(svg_code, color)
|
|
351
|
+
filename = f"{slug}{style_suffix}_{i+1:02d}.svg"
|
|
352
|
+
filepath = os.path.join(output_dir, filename)
|
|
353
|
+
|
|
354
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
355
|
+
f.write(svg_code)
|
|
356
|
+
|
|
357
|
+
results.append(filepath)
|
|
358
|
+
print(f" [{i+1}/{len(svgs[:count])}] Saved: {filename}")
|
|
359
|
+
|
|
360
|
+
print(f"\n{'='*60}")
|
|
361
|
+
print(f" BATCH COMPLETE: {len(results)}/{count} icons generated")
|
|
362
|
+
print(f"{'='*60}\n")
|
|
363
|
+
|
|
364
|
+
return results
|
|
365
|
+
|
|
366
|
+
except Exception as e:
|
|
367
|
+
print(f"Error generating icons: {e}")
|
|
368
|
+
return []
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def generate_sizes(prompt, sizes, style=None, color=None, output_dir=None, name=None):
|
|
372
|
+
"""Generate same icon at multiple sizes"""
|
|
373
|
+
if output_dir is None:
|
|
374
|
+
output_dir = "."
|
|
375
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
376
|
+
|
|
377
|
+
results = []
|
|
378
|
+
slug = name or re.sub(r'[^a-zA-Z0-9_-]', '_', prompt.split()[0].lower())
|
|
379
|
+
style_suffix = f"_{style}" if style else ""
|
|
380
|
+
|
|
381
|
+
for size in sizes:
|
|
382
|
+
print(f"Generating {size}px variant...")
|
|
383
|
+
filename = f"{slug}{style_suffix}_{size}px.svg"
|
|
384
|
+
filepath = os.path.join(output_dir, filename)
|
|
385
|
+
|
|
386
|
+
result = generate_icon(
|
|
387
|
+
prompt=prompt,
|
|
388
|
+
style=style,
|
|
389
|
+
color=color,
|
|
390
|
+
size=size,
|
|
391
|
+
output_path=filepath,
|
|
392
|
+
viewbox=size
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
if result:
|
|
396
|
+
results.append(result)
|
|
397
|
+
|
|
398
|
+
time.sleep(1)
|
|
399
|
+
|
|
400
|
+
return results
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def main():
|
|
404
|
+
parser = argparse.ArgumentParser(
|
|
405
|
+
description="Generate SVG icons using Gemini 3.1 Pro Preview"
|
|
406
|
+
)
|
|
407
|
+
parser.add_argument("--prompt", "-p", type=str, help="Icon description")
|
|
408
|
+
parser.add_argument("--name", "-n", type=str, help="Icon name (for filename)")
|
|
409
|
+
parser.add_argument("--style", "-s", choices=list(ICON_STYLES.keys()),
|
|
410
|
+
help="Icon style")
|
|
411
|
+
parser.add_argument("--category", "-c", choices=list(ICON_CATEGORIES.keys()),
|
|
412
|
+
help="Icon category for context")
|
|
413
|
+
parser.add_argument("--color", type=str,
|
|
414
|
+
help="Primary color (hex, e.g. #6366F1). Default: currentColor")
|
|
415
|
+
parser.add_argument("--size", type=int, default=24,
|
|
416
|
+
help="Icon size in px (default: 24)")
|
|
417
|
+
parser.add_argument("--viewbox", type=int, default=24,
|
|
418
|
+
help="SVG viewBox size (default: 24)")
|
|
419
|
+
parser.add_argument("--output", "-o", type=str, help="Output file path")
|
|
420
|
+
parser.add_argument("--output-dir", type=str, help="Output directory for batch")
|
|
421
|
+
parser.add_argument("--batch", type=int,
|
|
422
|
+
help="Number of icon variants to generate")
|
|
423
|
+
parser.add_argument("--sizes", type=str,
|
|
424
|
+
help="Comma-separated sizes (e.g. '16,24,32,48')")
|
|
425
|
+
parser.add_argument("--list-styles", action="store_true",
|
|
426
|
+
help="List available icon styles")
|
|
427
|
+
parser.add_argument("--list-categories", action="store_true",
|
|
428
|
+
help="List available icon categories")
|
|
429
|
+
|
|
430
|
+
args = parser.parse_args()
|
|
431
|
+
|
|
432
|
+
if args.list_styles:
|
|
433
|
+
print("Available icon styles:")
|
|
434
|
+
for style, desc in ICON_STYLES.items():
|
|
435
|
+
print(f" {style}: {desc[:70]}...")
|
|
436
|
+
return
|
|
437
|
+
|
|
438
|
+
if args.list_categories:
|
|
439
|
+
print("Available icon categories:")
|
|
440
|
+
for cat, desc in ICON_CATEGORIES.items():
|
|
441
|
+
print(f" {cat}: {desc}")
|
|
442
|
+
return
|
|
443
|
+
|
|
444
|
+
if not args.prompt and not args.name:
|
|
445
|
+
parser.error("Either --prompt or --name is required")
|
|
446
|
+
|
|
447
|
+
prompt = args.prompt or args.name
|
|
448
|
+
|
|
449
|
+
# Multi-size mode
|
|
450
|
+
if args.sizes:
|
|
451
|
+
sizes = [int(s.strip()) for s in args.sizes.split(",")]
|
|
452
|
+
generate_sizes(
|
|
453
|
+
prompt=prompt,
|
|
454
|
+
sizes=sizes,
|
|
455
|
+
style=args.style,
|
|
456
|
+
color=args.color,
|
|
457
|
+
output_dir=args.output_dir or "./icons",
|
|
458
|
+
name=args.name
|
|
459
|
+
)
|
|
460
|
+
# Batch mode
|
|
461
|
+
elif args.batch:
|
|
462
|
+
output_dir = args.output_dir or "./icons"
|
|
463
|
+
generate_batch(
|
|
464
|
+
prompt=prompt,
|
|
465
|
+
count=args.batch,
|
|
466
|
+
output_dir=output_dir,
|
|
467
|
+
style=args.style,
|
|
468
|
+
color=args.color,
|
|
469
|
+
viewbox=args.viewbox,
|
|
470
|
+
name=args.name
|
|
471
|
+
)
|
|
472
|
+
# Single icon
|
|
473
|
+
else:
|
|
474
|
+
generate_icon(
|
|
475
|
+
prompt=prompt,
|
|
476
|
+
style=args.style,
|
|
477
|
+
category=args.category,
|
|
478
|
+
name=args.name,
|
|
479
|
+
color=args.color,
|
|
480
|
+
size=args.size,
|
|
481
|
+
output_path=args.output,
|
|
482
|
+
viewbox=args.viewbox
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
if __name__ == "__main__":
|
|
487
|
+
main()
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Logo Design Core - BM25 search engine for logo design guidelines
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import csv
|
|
8
|
+
import re
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from math import log
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
|
|
13
|
+
# ============ CONFIGURATION ============
|
|
14
|
+
DATA_DIR = Path(__file__).parent.parent.parent / "data" / "logo"
|
|
15
|
+
MAX_RESULTS = 3
|
|
16
|
+
|
|
17
|
+
CSV_CONFIG = {
|
|
18
|
+
"style": {
|
|
19
|
+
"file": "styles.csv",
|
|
20
|
+
"search_cols": ["Style Name", "Category", "Keywords", "Best For"],
|
|
21
|
+
"output_cols": ["Style Name", "Category", "Keywords", "Primary Colors", "Secondary Colors", "Typography", "Effects", "Best For", "Avoid For", "Complexity", "Era"]
|
|
22
|
+
},
|
|
23
|
+
"color": {
|
|
24
|
+
"file": "colors.csv",
|
|
25
|
+
"search_cols": ["Palette Name", "Category", "Keywords", "Psychology", "Best For"],
|
|
26
|
+
"output_cols": ["Palette Name", "Category", "Keywords", "Primary Hex", "Secondary Hex", "Accent Hex", "Background Hex", "Text Hex", "Psychology", "Best For", "Avoid For"]
|
|
27
|
+
},
|
|
28
|
+
"industry": {
|
|
29
|
+
"file": "industries.csv",
|
|
30
|
+
"search_cols": ["Industry", "Keywords", "Recommended Styles", "Mood"],
|
|
31
|
+
"output_cols": ["Industry", "Keywords", "Recommended Styles", "Primary Colors", "Typography", "Common Symbols", "Mood", "Best Practices", "Avoid"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ============ BM25 IMPLEMENTATION ============
|
|
37
|
+
class BM25:
|
|
38
|
+
"""BM25 ranking algorithm for text search"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, k1=1.5, b=0.75):
|
|
41
|
+
self.k1 = k1
|
|
42
|
+
self.b = b
|
|
43
|
+
self.corpus = []
|
|
44
|
+
self.doc_lengths = []
|
|
45
|
+
self.avgdl = 0
|
|
46
|
+
self.idf = {}
|
|
47
|
+
self.doc_freqs = defaultdict(int)
|
|
48
|
+
self.N = 0
|
|
49
|
+
|
|
50
|
+
def tokenize(self, text):
|
|
51
|
+
"""Lowercase, split, remove punctuation, filter short words"""
|
|
52
|
+
text = re.sub(r'[^\w\s]', ' ', str(text).lower())
|
|
53
|
+
return [w for w in text.split() if len(w) > 2]
|
|
54
|
+
|
|
55
|
+
def fit(self, documents):
|
|
56
|
+
"""Build BM25 index from documents"""
|
|
57
|
+
self.corpus = [self.tokenize(doc) for doc in documents]
|
|
58
|
+
self.N = len(self.corpus)
|
|
59
|
+
if self.N == 0:
|
|
60
|
+
return
|
|
61
|
+
self.doc_lengths = [len(doc) for doc in self.corpus]
|
|
62
|
+
self.avgdl = sum(self.doc_lengths) / self.N
|
|
63
|
+
|
|
64
|
+
for doc in self.corpus:
|
|
65
|
+
seen = set()
|
|
66
|
+
for word in doc:
|
|
67
|
+
if word not in seen:
|
|
68
|
+
self.doc_freqs[word] += 1
|
|
69
|
+
seen.add(word)
|
|
70
|
+
|
|
71
|
+
for word, freq in self.doc_freqs.items():
|
|
72
|
+
self.idf[word] = log((self.N - freq + 0.5) / (freq + 0.5) + 1)
|
|
73
|
+
|
|
74
|
+
def score(self, query):
|
|
75
|
+
"""Score all documents against query"""
|
|
76
|
+
query_tokens = self.tokenize(query)
|
|
77
|
+
scores = []
|
|
78
|
+
|
|
79
|
+
for idx, doc in enumerate(self.corpus):
|
|
80
|
+
score = 0
|
|
81
|
+
doc_len = self.doc_lengths[idx]
|
|
82
|
+
term_freqs = defaultdict(int)
|
|
83
|
+
for word in doc:
|
|
84
|
+
term_freqs[word] += 1
|
|
85
|
+
|
|
86
|
+
for token in query_tokens:
|
|
87
|
+
if token in self.idf:
|
|
88
|
+
tf = term_freqs[token]
|
|
89
|
+
idf = self.idf[token]
|
|
90
|
+
numerator = tf * (self.k1 + 1)
|
|
91
|
+
denominator = tf + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)
|
|
92
|
+
score += idf * numerator / denominator
|
|
93
|
+
|
|
94
|
+
scores.append((idx, score))
|
|
95
|
+
|
|
96
|
+
return sorted(scores, key=lambda x: x[1], reverse=True)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ============ SEARCH FUNCTIONS ============
|
|
100
|
+
def _load_csv(filepath):
|
|
101
|
+
"""Load CSV and return list of dicts"""
|
|
102
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
103
|
+
return list(csv.DictReader(f))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _search_csv(filepath, search_cols, output_cols, query, max_results):
|
|
107
|
+
"""Core search function using BM25"""
|
|
108
|
+
if not filepath.exists():
|
|
109
|
+
return []
|
|
110
|
+
|
|
111
|
+
data = _load_csv(filepath)
|
|
112
|
+
|
|
113
|
+
# Build documents from search columns
|
|
114
|
+
documents = [" ".join(str(row.get(col, "")) for col in search_cols) for row in data]
|
|
115
|
+
|
|
116
|
+
# BM25 search
|
|
117
|
+
bm25 = BM25()
|
|
118
|
+
bm25.fit(documents)
|
|
119
|
+
ranked = bm25.score(query)
|
|
120
|
+
|
|
121
|
+
# Get top results with score > 0
|
|
122
|
+
results = []
|
|
123
|
+
for idx, score in ranked[:max_results]:
|
|
124
|
+
if score > 0:
|
|
125
|
+
row = data[idx]
|
|
126
|
+
results.append({col: row.get(col, "") for col in output_cols if col in row})
|
|
127
|
+
|
|
128
|
+
return results
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def detect_domain(query):
|
|
132
|
+
"""Auto-detect the most relevant domain from query"""
|
|
133
|
+
query_lower = query.lower()
|
|
134
|
+
|
|
135
|
+
domain_keywords = {
|
|
136
|
+
"style": ["style", "minimalist", "vintage", "modern", "retro", "geometric", "abstract", "emblem", "badge", "wordmark", "mascot", "luxury", "playful", "corporate"],
|
|
137
|
+
"color": ["color", "palette", "hex", "#", "rgb", "blue", "red", "green", "gold", "warm", "cool", "vibrant", "pastel"],
|
|
138
|
+
"industry": ["tech", "healthcare", "finance", "legal", "restaurant", "food", "fashion", "beauty", "education", "sports", "fitness", "real estate", "crypto", "gaming"]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
scores = {domain: sum(1 for kw in keywords if kw in query_lower) for domain, keywords in domain_keywords.items()}
|
|
142
|
+
best = max(scores, key=scores.get)
|
|
143
|
+
return best if scores[best] > 0 else "style"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def search(query, domain=None, max_results=MAX_RESULTS):
|
|
147
|
+
"""Main search function with auto-domain detection"""
|
|
148
|
+
if domain is None:
|
|
149
|
+
domain = detect_domain(query)
|
|
150
|
+
|
|
151
|
+
config = CSV_CONFIG.get(domain, CSV_CONFIG["style"])
|
|
152
|
+
filepath = DATA_DIR / config["file"]
|
|
153
|
+
|
|
154
|
+
if not filepath.exists():
|
|
155
|
+
return {"error": f"File not found: {filepath}", "domain": domain}
|
|
156
|
+
|
|
157
|
+
results = _search_csv(filepath, config["search_cols"], config["output_cols"], query, max_results)
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
"domain": domain,
|
|
161
|
+
"query": query,
|
|
162
|
+
"file": config["file"],
|
|
163
|
+
"count": len(results),
|
|
164
|
+
"results": results
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def search_all(query, max_results=2):
|
|
169
|
+
"""Search across all domains and combine results"""
|
|
170
|
+
all_results = {}
|
|
171
|
+
for domain in CSV_CONFIG.keys():
|
|
172
|
+
result = search(query, domain, max_results)
|
|
173
|
+
if result.get("results"):
|
|
174
|
+
all_results[domain] = result["results"]
|
|
175
|
+
return all_results
|