@yuhan1124/draw-prompt 0.4.0 → 0.4.2
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 +81 -27
- package/SKILL.md +54 -24
- package/agents/openai.yaml +18 -12
- package/package.json +9 -2
- package/references/conversion-skill-plan.md +74 -16
- package/scripts/prompt_cli.py +2911 -128
- package/references/golden-cases.jsonl +0 -5
- package/references/visual-cases.jsonl +0 -8
package/scripts/prompt_cli.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# /// script
|
|
3
|
-
# requires-python = ">=3.
|
|
3
|
+
# requires-python = ">=3.9"
|
|
4
4
|
# dependencies = ["pyyaml>=6.0", "pillow>=10.0"]
|
|
5
5
|
# ///
|
|
6
6
|
"""draw-prompt CLI — 生图需求转化 + 偏好持久化 + Codex 交付块生成。
|
|
@@ -14,8 +14,10 @@ codex exec)。本 CLI 的 `handoff` 子命令负责把 prompt 包装成一段
|
|
|
14
14
|
粘贴/转交给 Codex 的现成指令,但不会自己去执行它。
|
|
15
15
|
|
|
16
16
|
CLI 只干确定性的脏活:
|
|
17
|
+
install-skill 从 npx/npm 包安装到 Codex/Claude skills 目录
|
|
17
18
|
convert 自然语言画图需求 -> Prompt / handoff
|
|
18
19
|
compose 长输入/文档 -> 多张配套图的视觉计划 + Prompt
|
|
20
|
+
variants 同一需求 -> 多风格 Prompt 组
|
|
19
21
|
series 多张同风格系列图 -> 稳定一致的 Prompt 组
|
|
20
22
|
edit 参考图/改图需求 -> 保留项、修改项、编辑 Prompt
|
|
21
23
|
brand 品牌风格档案 -> 可复用品牌 Prompt 块
|
|
@@ -33,6 +35,7 @@ CLI 只干确定性的脏活:
|
|
|
33
35
|
feedback 对样本回填采纳 / 弃用
|
|
34
36
|
judge 存储 agent 给出的评分(CLI 不评分、不看图、不调 Codex)
|
|
35
37
|
handoff 生成交给 Codex 的现成指令块(仅打印,不执行)
|
|
38
|
+
styles 列出内置风格预设
|
|
36
39
|
status 数据与下游通道健康检查
|
|
37
40
|
|
|
38
41
|
所有运行时数据写到数据目录(默认 ~/.local/share/draw-prompt/,可用
|
|
@@ -47,6 +50,7 @@ import hashlib
|
|
|
47
50
|
import json
|
|
48
51
|
import os
|
|
49
52
|
import re
|
|
53
|
+
import shutil
|
|
50
54
|
import sys
|
|
51
55
|
from datetime import datetime, timezone
|
|
52
56
|
from pathlib import Path
|
|
@@ -131,7 +135,27 @@ def ensure_home() -> None:
|
|
|
131
135
|
|
|
132
136
|
|
|
133
137
|
SCHEMA_VERSION = 1
|
|
134
|
-
COMPILER_VERSION = "0.4.
|
|
138
|
+
COMPILER_VERSION = "0.4.2"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
PACKAGED_SKILL_FILES = [
|
|
142
|
+
"SKILL.md",
|
|
143
|
+
"README.md",
|
|
144
|
+
"LICENSE",
|
|
145
|
+
"package.json",
|
|
146
|
+
"agents/openai.yaml",
|
|
147
|
+
"bin/draw-prompt.js",
|
|
148
|
+
"scripts/prompt_cli.py",
|
|
149
|
+
"references/codex-handoff.md",
|
|
150
|
+
"references/compile.md",
|
|
151
|
+
"references/conversion-skill-plan.md",
|
|
152
|
+
"references/gallery.md",
|
|
153
|
+
"references/harness.md",
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def package_root() -> Path:
|
|
158
|
+
return Path(__file__).resolve().parent.parent
|
|
135
159
|
|
|
136
160
|
|
|
137
161
|
# --------------------------------------------------------------------------- #
|
|
@@ -140,6 +164,7 @@ COMPILER_VERSION = "0.4.0"
|
|
|
140
164
|
PROFILE_TEMPLATE_FM = {
|
|
141
165
|
"default_aspect": "未设置",
|
|
142
166
|
"default_quality": "high",
|
|
167
|
+
"default_style_preset": "auto",
|
|
143
168
|
"favored_styles": [],
|
|
144
169
|
"avoided_elements": [],
|
|
145
170
|
"text_language": "zh",
|
|
@@ -280,7 +305,7 @@ ASSET_ROUTES = {
|
|
|
280
305
|
"ui": {
|
|
281
306
|
"label": "ui",
|
|
282
307
|
"title": "UI / 产品界面",
|
|
283
|
-
"keywords": ["ui", "界面", "app", "dashboard", "仪表盘", "网页", "web", "小程序", "mockup"],
|
|
308
|
+
"keywords": ["ui", "界面", "app", "dashboard", "仪表盘", "看板", "saas", "后台", "控制台", "网页", "web", "小程序", "mockup"],
|
|
284
309
|
"aspect": "9:16",
|
|
285
310
|
"size": "portrait",
|
|
286
311
|
"quality": "high",
|
|
@@ -295,6 +320,29 @@ ASSET_ROUTES = {
|
|
|
295
320
|
"quality": "high",
|
|
296
321
|
"tags": ["infographic"],
|
|
297
322
|
},
|
|
323
|
+
"slide": {
|
|
324
|
+
"label": "slide",
|
|
325
|
+
"title": "PPT / 汇报单页",
|
|
326
|
+
"keywords": [
|
|
327
|
+
"ppt",
|
|
328
|
+
"powerpoint",
|
|
329
|
+
"slide",
|
|
330
|
+
"slides",
|
|
331
|
+
"presentation",
|
|
332
|
+
"deck",
|
|
333
|
+
"widescreen",
|
|
334
|
+
"幻灯片",
|
|
335
|
+
"演示文稿",
|
|
336
|
+
"汇报页",
|
|
337
|
+
"汇报单页",
|
|
338
|
+
"企业汇报",
|
|
339
|
+
"PPT",
|
|
340
|
+
],
|
|
341
|
+
"aspect": "16:9",
|
|
342
|
+
"size": "landscape",
|
|
343
|
+
"quality": "high",
|
|
344
|
+
"tags": ["slide", "presentation"],
|
|
345
|
+
},
|
|
298
346
|
"diagram": {
|
|
299
347
|
"label": "diagram",
|
|
300
348
|
"title": "学术 / 系统架构图",
|
|
@@ -343,7 +391,7 @@ ASSET_ROUTES = {
|
|
|
343
391
|
"logo": {
|
|
344
392
|
"label": "logo",
|
|
345
393
|
"title": "Logo / 品牌系统板",
|
|
346
|
-
"keywords": ["logo", "标识", "
|
|
394
|
+
"keywords": ["logo", "标识", "品牌标识", "字标", "brand identity", "visual identity", "vi"],
|
|
347
395
|
"aspect": "1:1",
|
|
348
396
|
"size": "square",
|
|
349
397
|
"quality": "high",
|
|
@@ -362,8 +410,1695 @@ STYLE_HINTS = [
|
|
|
362
410
|
(["写实", "真实"], "realistic, natural, unprocessed visual language"),
|
|
363
411
|
]
|
|
364
412
|
|
|
413
|
+
STYLE_PRESETS = {
|
|
414
|
+
"auto": [],
|
|
415
|
+
"corporate": [
|
|
416
|
+
"quiet corporate communication style",
|
|
417
|
+
"clean grid, restrained contrast, executive presentation polish",
|
|
418
|
+
],
|
|
419
|
+
"premium": [
|
|
420
|
+
"premium editorial finish",
|
|
421
|
+
"controlled palette, tactile materials, generous negative space",
|
|
422
|
+
],
|
|
423
|
+
"minimal": [
|
|
424
|
+
"minimal clean visual system",
|
|
425
|
+
"precise spacing, low clutter, strong hierarchy",
|
|
426
|
+
],
|
|
427
|
+
"flat-vector": [
|
|
428
|
+
"modern flat vector language",
|
|
429
|
+
"crisp geometric forms, clean fills, consistent stroke weights",
|
|
430
|
+
],
|
|
431
|
+
"photoreal": [
|
|
432
|
+
"photorealistic rendering",
|
|
433
|
+
"natural material response, believable lighting, camera-real detail",
|
|
434
|
+
],
|
|
435
|
+
"clean-ui": [
|
|
436
|
+
"production-grade UI visual language",
|
|
437
|
+
"consistent components, dense but readable layout, precise spacing",
|
|
438
|
+
],
|
|
439
|
+
"editorial": [
|
|
440
|
+
"magazine editorial art direction",
|
|
441
|
+
"strong typographic hierarchy, refined cropping, deliberate negative space",
|
|
442
|
+
],
|
|
443
|
+
"cinematic": [
|
|
444
|
+
"cinematic visual language",
|
|
445
|
+
"dramatic but controlled lighting, lens depth, atmospheric composition",
|
|
446
|
+
],
|
|
447
|
+
"isometric": [
|
|
448
|
+
"clean isometric illustration style",
|
|
449
|
+
"precise geometric perspective, modular geometry, balanced depth",
|
|
450
|
+
],
|
|
451
|
+
"technical-blueprint": [
|
|
452
|
+
"technical blueprint visual language",
|
|
453
|
+
"thin precise lines, annotation discipline, structured spatial logic",
|
|
454
|
+
],
|
|
455
|
+
"hand-drawn": [
|
|
456
|
+
"hand-drawn illustration style",
|
|
457
|
+
"visible linework, tactile paper texture, warm organic imperfections",
|
|
458
|
+
],
|
|
459
|
+
"watercolor": [
|
|
460
|
+
"soft watercolor illustration style",
|
|
461
|
+
"transparent washes, gentle edges, paper grain texture",
|
|
462
|
+
],
|
|
463
|
+
"retro-print": [
|
|
464
|
+
"tasteful retro print design",
|
|
465
|
+
"halftone texture, restrained vintage palette, contemporary composition",
|
|
466
|
+
],
|
|
467
|
+
"bold-typographic": [
|
|
468
|
+
"bold typographic poster language",
|
|
469
|
+
"large type hierarchy, strong contrast, disciplined graphic blocks",
|
|
470
|
+
],
|
|
471
|
+
"data-journalism": [
|
|
472
|
+
"data journalism visual style",
|
|
473
|
+
"clear annotation system, evidence-first layout, sober color coding",
|
|
474
|
+
],
|
|
475
|
+
"luxury-product": [
|
|
476
|
+
"luxury product advertising style",
|
|
477
|
+
"premium material emphasis, controlled reflections, elegant restraint",
|
|
478
|
+
],
|
|
479
|
+
"soft-3d": [
|
|
480
|
+
"soft 3D illustration style",
|
|
481
|
+
"rounded forms, clay-like material, gentle studio lighting",
|
|
482
|
+
],
|
|
483
|
+
"monochrome-ink": [
|
|
484
|
+
"monochrome ink visual language",
|
|
485
|
+
"high-contrast black and white, expressive line weight, clean whitespace",
|
|
486
|
+
],
|
|
487
|
+
"executive-consulting": [
|
|
488
|
+
"executive consulting deck polish",
|
|
489
|
+
"precise hierarchy, restrained accents, boardroom clarity",
|
|
490
|
+
],
|
|
491
|
+
"annual-report": [
|
|
492
|
+
"annual report editorial design",
|
|
493
|
+
"measured typography, structured spreads, credible institutional tone",
|
|
494
|
+
],
|
|
495
|
+
"keynote": [
|
|
496
|
+
"premium keynote presentation style",
|
|
497
|
+
"large confident type, cinematic pacing, sparse supporting detail",
|
|
498
|
+
],
|
|
499
|
+
"swiss": [
|
|
500
|
+
"Swiss International Typographic style",
|
|
501
|
+
"strict grid discipline, asymmetric balance, clean sans-serif rhythm",
|
|
502
|
+
],
|
|
503
|
+
"bauhaus": [
|
|
504
|
+
"Bauhaus graphic design language",
|
|
505
|
+
"primary geometry, functional composition, disciplined color blocking",
|
|
506
|
+
],
|
|
507
|
+
"brutalist": [
|
|
508
|
+
"brutalist graphic design",
|
|
509
|
+
"raw hierarchy, stark contrast, utilitarian spacing",
|
|
510
|
+
],
|
|
511
|
+
"memphis": [
|
|
512
|
+
"Memphis-inspired postmodern design",
|
|
513
|
+
"playful geometry, patterned accents, controlled visual energy",
|
|
514
|
+
],
|
|
515
|
+
"art-deco": [
|
|
516
|
+
"Art Deco visual language",
|
|
517
|
+
"symmetry, stepped geometry, metallic elegance, refined ornament",
|
|
518
|
+
],
|
|
519
|
+
"art-nouveau": [
|
|
520
|
+
"Art Nouveau inspired ornament",
|
|
521
|
+
"flowing linework, botanical curves, elegant decorative rhythm",
|
|
522
|
+
],
|
|
523
|
+
"constructivist": [
|
|
524
|
+
"constructivist poster language",
|
|
525
|
+
"diagonal structure, bold geometry, urgent graphic rhythm",
|
|
526
|
+
],
|
|
527
|
+
"de-stijl": [
|
|
528
|
+
"De Stijl inspired composition",
|
|
529
|
+
"orthogonal geometry, primary color accents, balanced whitespace",
|
|
530
|
+
],
|
|
531
|
+
"suprematist": [
|
|
532
|
+
"suprematist abstract design language",
|
|
533
|
+
"floating geometry, sparse field, controlled asymmetry",
|
|
534
|
+
],
|
|
535
|
+
"mid-century": [
|
|
536
|
+
"mid-century modern visual style",
|
|
537
|
+
"warm geometry, simplified shapes, optimistic color restraint",
|
|
538
|
+
],
|
|
539
|
+
"scandinavian": [
|
|
540
|
+
"Scandinavian design restraint",
|
|
541
|
+
"quiet neutrals, natural material feel, airy functional spacing",
|
|
542
|
+
],
|
|
543
|
+
"wabi-sabi": [
|
|
544
|
+
"wabi-sabi visual restraint",
|
|
545
|
+
"imperfect texture, muted natural tones, calm asymmetry",
|
|
546
|
+
],
|
|
547
|
+
"new-chinese": [
|
|
548
|
+
"New Chinese visual style",
|
|
549
|
+
"restrained oriental composition, ink-like balance, modern elegance",
|
|
550
|
+
],
|
|
551
|
+
"zen-minimal": [
|
|
552
|
+
"zen minimal visual language",
|
|
553
|
+
"quiet emptiness, calm proportions, soft tonal contrast",
|
|
554
|
+
],
|
|
555
|
+
"ink-wash": [
|
|
556
|
+
"ink wash illustration style",
|
|
557
|
+
"soft tonal gradients, expressive brush texture, quiet negative space",
|
|
558
|
+
],
|
|
559
|
+
"ukiyo-e": [
|
|
560
|
+
"ukiyo-e inspired print language",
|
|
561
|
+
"flat color fields, elegant linework, woodblock texture",
|
|
562
|
+
],
|
|
563
|
+
"woodblock": [
|
|
564
|
+
"woodblock print texture",
|
|
565
|
+
"carved line edges, ink pressure variation, handmade print grain",
|
|
566
|
+
],
|
|
567
|
+
"linocut": [
|
|
568
|
+
"linocut print style",
|
|
569
|
+
"bold carved contours, limited palette, tactile ink coverage",
|
|
570
|
+
],
|
|
571
|
+
"risograph": [
|
|
572
|
+
"risograph print style",
|
|
573
|
+
"spot-color layering, slight registration offsets, paper grain",
|
|
574
|
+
],
|
|
575
|
+
"screenprint": [
|
|
576
|
+
"screenprint poster style",
|
|
577
|
+
"flat ink layers, crisp separations, tactile print texture",
|
|
578
|
+
],
|
|
579
|
+
"collage": [
|
|
580
|
+
"editorial collage style",
|
|
581
|
+
"cut-paper layering, mixed texture, disciplined visual contrast",
|
|
582
|
+
],
|
|
583
|
+
"paper-cut": [
|
|
584
|
+
"paper-cut illustration style",
|
|
585
|
+
"layered paper edges, soft shadow depth, crafted tactile surfaces",
|
|
586
|
+
],
|
|
587
|
+
"origami": [
|
|
588
|
+
"origami-inspired folded paper style",
|
|
589
|
+
"faceted paper planes, crisp folds, clean geometric shadows",
|
|
590
|
+
],
|
|
591
|
+
"gouache": [
|
|
592
|
+
"gouache illustration style",
|
|
593
|
+
"opaque pigment texture, soft matte surfaces, painterly edges",
|
|
594
|
+
],
|
|
595
|
+
"airbrush": [
|
|
596
|
+
"airbrush illustration finish",
|
|
597
|
+
"smooth gradients, soft specular bloom, polished retro futurism",
|
|
598
|
+
],
|
|
599
|
+
"oil-paint": [
|
|
600
|
+
"oil painting inspired finish",
|
|
601
|
+
"visible brush texture, layered pigment depth, rich tonal blending",
|
|
602
|
+
],
|
|
603
|
+
"acrylic": [
|
|
604
|
+
"acrylic paint inspired finish",
|
|
605
|
+
"opaque color blocks, expressive edges, matte surface texture",
|
|
606
|
+
],
|
|
607
|
+
"pastel-pencil": [
|
|
608
|
+
"pastel pencil illustration style",
|
|
609
|
+
"powdery color texture, soft edges, tactile drawing surface",
|
|
610
|
+
],
|
|
611
|
+
"charcoal": [
|
|
612
|
+
"charcoal drawing style",
|
|
613
|
+
"rich smudged blacks, expressive tonal range, textured paper",
|
|
614
|
+
],
|
|
615
|
+
"graphite": [
|
|
616
|
+
"graphite sketch style",
|
|
617
|
+
"fine tonal shading, precise line pressure, paper tooth texture",
|
|
618
|
+
],
|
|
619
|
+
"line-art": [
|
|
620
|
+
"clean line art style",
|
|
621
|
+
"confident contours, minimal fill, precise negative space",
|
|
622
|
+
],
|
|
623
|
+
"duotone": [
|
|
624
|
+
"duotone graphic style",
|
|
625
|
+
"two-color discipline, strong value contrast, simplified hierarchy",
|
|
626
|
+
],
|
|
627
|
+
"pastel-pop": [
|
|
628
|
+
"pastel pop visual style",
|
|
629
|
+
"soft saturated palette, friendly contrast, clean rounded forms",
|
|
630
|
+
],
|
|
631
|
+
"earth-tone": [
|
|
632
|
+
"earth-tone visual palette",
|
|
633
|
+
"warm natural colors, grounded contrast, tactile material feel",
|
|
634
|
+
],
|
|
635
|
+
"high-contrast": [
|
|
636
|
+
"high-contrast graphic language",
|
|
637
|
+
"bold value separation, sharp hierarchy, assertive clarity",
|
|
638
|
+
],
|
|
639
|
+
"muted-luxury": [
|
|
640
|
+
"muted luxury visual style",
|
|
641
|
+
"quiet neutrals, refined accents, premium restraint",
|
|
642
|
+
],
|
|
643
|
+
"pixel-art": [
|
|
644
|
+
"pixel art visual style",
|
|
645
|
+
"limited resolution grid, crisp pixel edges, controlled palette",
|
|
646
|
+
],
|
|
647
|
+
"voxel": [
|
|
648
|
+
"voxel 3D style",
|
|
649
|
+
"blocky volumetric geometry, crisp lighting, playful depth",
|
|
650
|
+
],
|
|
651
|
+
"low-poly": [
|
|
652
|
+
"low-poly 3D style",
|
|
653
|
+
"faceted geometry, simplified planes, clean directional lighting",
|
|
654
|
+
],
|
|
655
|
+
"clay-render": [
|
|
656
|
+
"clay render 3D style",
|
|
657
|
+
"matte sculpted surfaces, soft shadows, simplified form language",
|
|
658
|
+
],
|
|
659
|
+
"hard-surface-3d": [
|
|
660
|
+
"hard-surface 3D rendering",
|
|
661
|
+
"precise bevels, clean industrial surfaces, controlled reflections",
|
|
662
|
+
],
|
|
663
|
+
"chrome": [
|
|
664
|
+
"chrome material visual style",
|
|
665
|
+
"mirror-like reflections, sharp highlights, futuristic polish",
|
|
666
|
+
],
|
|
667
|
+
"glassmorphism": [
|
|
668
|
+
"glassmorphism visual language",
|
|
669
|
+
"translucent layers, soft blur, luminous edge highlights",
|
|
670
|
+
],
|
|
671
|
+
"holographic": [
|
|
672
|
+
"holographic visual finish",
|
|
673
|
+
"iridescent gradients, luminous refraction, futuristic material shimmer",
|
|
674
|
+
],
|
|
675
|
+
"iridescent": [
|
|
676
|
+
"iridescent material palette",
|
|
677
|
+
"shifting spectral highlights, pearlescent sheen, refined glow",
|
|
678
|
+
],
|
|
679
|
+
"neon-noir": [
|
|
680
|
+
"neon noir visual style",
|
|
681
|
+
"deep shadows, controlled neon accents, cinematic contrast",
|
|
682
|
+
],
|
|
683
|
+
"cyberpunk": [
|
|
684
|
+
"cyberpunk visual style",
|
|
685
|
+
"neon tech palette, dense luminous detail, dark futuristic atmosphere",
|
|
686
|
+
],
|
|
687
|
+
"synthwave": [
|
|
688
|
+
"synthwave visual style",
|
|
689
|
+
"magenta-cyan palette, retro-futurist glow, graphic horizon rhythm",
|
|
690
|
+
],
|
|
691
|
+
"vaporwave": [
|
|
692
|
+
"vaporwave visual style",
|
|
693
|
+
"soft surreal gradients, retro digital polish, pastel neon balance",
|
|
694
|
+
],
|
|
695
|
+
"y2k": [
|
|
696
|
+
"Y2K digital visual style",
|
|
697
|
+
"glossy surfaces, chrome accents, early-digital optimism",
|
|
698
|
+
],
|
|
699
|
+
"cyber-minimal": [
|
|
700
|
+
"cyber minimal visual language",
|
|
701
|
+
"dark clean surfaces, precise luminous accents, high-tech restraint",
|
|
702
|
+
],
|
|
703
|
+
"gradient-mesh": [
|
|
704
|
+
"gradient mesh visual style",
|
|
705
|
+
"smooth color fields, subtle depth, contemporary digital polish",
|
|
706
|
+
],
|
|
707
|
+
"high-key-photo": [
|
|
708
|
+
"high-key photographic style",
|
|
709
|
+
"bright tonal range, soft shadows, clean airy polish",
|
|
710
|
+
],
|
|
711
|
+
"low-key-photo": [
|
|
712
|
+
"low-key photographic style",
|
|
713
|
+
"deep controlled shadows, selective highlights, dramatic restraint",
|
|
714
|
+
],
|
|
715
|
+
"analog-film": [
|
|
716
|
+
"analog film photographic look",
|
|
717
|
+
"natural grain, soft highlight rolloff, tactile color response",
|
|
718
|
+
],
|
|
719
|
+
"polaroid": [
|
|
720
|
+
"instant film photographic look",
|
|
721
|
+
"soft contrast, nostalgic color shift, tactile print feel",
|
|
722
|
+
],
|
|
723
|
+
"macro-product": [
|
|
724
|
+
"macro product photography style",
|
|
725
|
+
"close material detail, shallow depth, crisp tactile focus",
|
|
726
|
+
],
|
|
727
|
+
"studio-product": [
|
|
728
|
+
"studio product photography style",
|
|
729
|
+
"controlled light shaping, clean reflections, commercial precision",
|
|
730
|
+
],
|
|
731
|
+
"natural-light": [
|
|
732
|
+
"natural light photographic style",
|
|
733
|
+
"soft believable illumination, gentle contrast, unforced realism",
|
|
734
|
+
],
|
|
735
|
+
"film-noir": [
|
|
736
|
+
"film noir visual language",
|
|
737
|
+
"stark shadows, dramatic light cuts, monochrome tension",
|
|
738
|
+
],
|
|
739
|
+
"sepia-photo": [
|
|
740
|
+
"sepia photographic finish",
|
|
741
|
+
"warm brown tonal range, aged print texture, soft contrast",
|
|
742
|
+
],
|
|
743
|
+
"architectural-photo": [
|
|
744
|
+
"architectural photography discipline",
|
|
745
|
+
"precise perspective, clean planes, controlled spatial rhythm",
|
|
746
|
+
],
|
|
747
|
+
"scientific-visual": [
|
|
748
|
+
"scientific visual communication style",
|
|
749
|
+
"clear annotation hierarchy, disciplined color coding, evidence-first clarity",
|
|
750
|
+
],
|
|
751
|
+
"patent-drawing": [
|
|
752
|
+
"patent drawing visual style",
|
|
753
|
+
"thin precise linework, numbered annotation discipline, white field clarity",
|
|
754
|
+
],
|
|
755
|
+
"schematic": [
|
|
756
|
+
"schematic visual language",
|
|
757
|
+
"clean line systems, measured spacing, unambiguous visual logic",
|
|
758
|
+
],
|
|
759
|
+
"whitepaper": [
|
|
760
|
+
"whitepaper figure style",
|
|
761
|
+
"restrained academic polish, readable labels, sober layout structure",
|
|
762
|
+
],
|
|
763
|
+
"museum-catalog": [
|
|
764
|
+
"museum catalog editorial style",
|
|
765
|
+
"archival restraint, spacious layout, refined caption hierarchy",
|
|
766
|
+
],
|
|
767
|
+
"newspaper": [
|
|
768
|
+
"newspaper editorial design",
|
|
769
|
+
"dense readable typography, monochrome structure, disciplined hierarchy",
|
|
770
|
+
],
|
|
771
|
+
"zine": [
|
|
772
|
+
"zine editorial style",
|
|
773
|
+
"raw print texture, handmade composition energy, independent publishing feel",
|
|
774
|
+
],
|
|
775
|
+
"renaissance-painting": [
|
|
776
|
+
"Renaissance painting inspired finish",
|
|
777
|
+
"balanced proportions, soft chiaroscuro, classical tonal depth",
|
|
778
|
+
],
|
|
779
|
+
"baroque": [
|
|
780
|
+
"Baroque visual drama",
|
|
781
|
+
"rich chiaroscuro, dynamic diagonals, ornate depth",
|
|
782
|
+
],
|
|
783
|
+
"rococo": [
|
|
784
|
+
"Rococo decorative style",
|
|
785
|
+
"soft pastel palette, delicate ornament, graceful asymmetry",
|
|
786
|
+
],
|
|
787
|
+
"neoclassical": [
|
|
788
|
+
"neoclassical visual restraint",
|
|
789
|
+
"orderly proportions, marble-like tones, classical calm",
|
|
790
|
+
],
|
|
791
|
+
"romanticism": [
|
|
792
|
+
"Romanticist visual language",
|
|
793
|
+
"dramatic atmosphere, emotional contrast, painterly depth",
|
|
794
|
+
],
|
|
795
|
+
"impressionist": [
|
|
796
|
+
"Impressionist painting style",
|
|
797
|
+
"broken color brushwork, luminous softness, optical texture",
|
|
798
|
+
],
|
|
799
|
+
"post-impressionist": [
|
|
800
|
+
"Post-Impressionist painting style",
|
|
801
|
+
"expressive color structure, visible brush rhythm, simplified forms",
|
|
802
|
+
],
|
|
803
|
+
"pointillist": [
|
|
804
|
+
"pointillist painting technique",
|
|
805
|
+
"small color marks, optical mixing, disciplined surface texture",
|
|
806
|
+
],
|
|
807
|
+
"fauvist": [
|
|
808
|
+
"Fauvist color language",
|
|
809
|
+
"bold nonnatural color, expressive contrast, simplified shapes",
|
|
810
|
+
],
|
|
811
|
+
"cubist": [
|
|
812
|
+
"Cubist visual structure",
|
|
813
|
+
"fragmented planes, angular geometry, multiple-view abstraction",
|
|
814
|
+
],
|
|
815
|
+
"futurist": [
|
|
816
|
+
"Futurist graphic energy",
|
|
817
|
+
"dynamic motion rhythm, diagonal force, modern mechanical tension",
|
|
818
|
+
],
|
|
819
|
+
"surrealist": [
|
|
820
|
+
"Surrealist visual logic",
|
|
821
|
+
"dreamlike juxtapositions, precise rendering, uncanny calm",
|
|
822
|
+
],
|
|
823
|
+
"expressionist": [
|
|
824
|
+
"Expressionist visual language",
|
|
825
|
+
"distorted energy, heightened color, emotional brushwork",
|
|
826
|
+
],
|
|
827
|
+
"abstract-expressionist": [
|
|
828
|
+
"Abstract Expressionist finish",
|
|
829
|
+
"gestural marks, layered pigment energy, large-field tension",
|
|
830
|
+
],
|
|
831
|
+
"color-field": [
|
|
832
|
+
"color field painting style",
|
|
833
|
+
"large tonal expanses, soft boundaries, meditative color presence",
|
|
834
|
+
],
|
|
835
|
+
"hard-edge": [
|
|
836
|
+
"hard-edge abstract style",
|
|
837
|
+
"clean color boundaries, geometric clarity, flat precision",
|
|
838
|
+
],
|
|
839
|
+
"geometric-abstract": [
|
|
840
|
+
"geometric abstraction",
|
|
841
|
+
"pure shapes, balanced color fields, disciplined spatial rhythm",
|
|
842
|
+
],
|
|
843
|
+
"lyrical-abstract": [
|
|
844
|
+
"lyrical abstraction",
|
|
845
|
+
"fluid marks, soft color movement, airy expressive balance",
|
|
846
|
+
],
|
|
847
|
+
"op-art": [
|
|
848
|
+
"Op Art visual style",
|
|
849
|
+
"optical vibration, precise pattern rhythm, high contrast geometry",
|
|
850
|
+
],
|
|
851
|
+
"pop-art": [
|
|
852
|
+
"Pop Art graphic style",
|
|
853
|
+
"bold flat color, comic-print texture, mass-media polish",
|
|
854
|
+
],
|
|
855
|
+
"psychedelic": [
|
|
856
|
+
"psychedelic visual style",
|
|
857
|
+
"warped curves, saturated gradients, rhythmic color flow",
|
|
858
|
+
],
|
|
859
|
+
"minimalist-art": [
|
|
860
|
+
"minimalist art visual language",
|
|
861
|
+
"reduced forms, exact spacing, quiet material presence",
|
|
862
|
+
],
|
|
863
|
+
"folk-art": [
|
|
864
|
+
"folk art visual style",
|
|
865
|
+
"decorative simplicity, handmade pattern rhythm, warm color restraint",
|
|
866
|
+
],
|
|
867
|
+
"naive-art": [
|
|
868
|
+
"naive art visual style",
|
|
869
|
+
"direct simplified forms, flat perspective, candid color charm",
|
|
870
|
+
],
|
|
871
|
+
"outsider-art": [
|
|
872
|
+
"outsider art inspired mark-making",
|
|
873
|
+
"raw pattern density, intuitive composition, handmade intensity",
|
|
874
|
+
],
|
|
875
|
+
"stained-glass": [
|
|
876
|
+
"stained-glass visual language",
|
|
877
|
+
"lead-like outlines, luminous color panels, jewel-toned contrast",
|
|
878
|
+
],
|
|
879
|
+
"mosaic": [
|
|
880
|
+
"mosaic surface style",
|
|
881
|
+
"small tiled color pieces, tactile seams, structured pattern field",
|
|
882
|
+
],
|
|
883
|
+
"fresco": [
|
|
884
|
+
"fresco painting texture",
|
|
885
|
+
"matte plaster surface, aged pigment softness, mineral tonal range",
|
|
886
|
+
],
|
|
887
|
+
"tapestry": [
|
|
888
|
+
"tapestry textile style",
|
|
889
|
+
"woven texture, muted threads, ornamental pattern depth",
|
|
890
|
+
],
|
|
891
|
+
"embroidery": [
|
|
892
|
+
"embroidery textile style",
|
|
893
|
+
"stitched linework, thread texture, tactile raised detail",
|
|
894
|
+
],
|
|
895
|
+
"batik": [
|
|
896
|
+
"batik textile visual style",
|
|
897
|
+
"wax-resist edges, organic dye textures, patterned color fields",
|
|
898
|
+
],
|
|
899
|
+
"marbling": [
|
|
900
|
+
"paper marbling style",
|
|
901
|
+
"fluid veined color, organic swirls, delicate surface pattern",
|
|
902
|
+
],
|
|
903
|
+
"terrazzo": [
|
|
904
|
+
"terrazzo material style",
|
|
905
|
+
"speckled stone chips, polished surface rhythm, playful aggregate texture",
|
|
906
|
+
],
|
|
907
|
+
"kintsugi": [
|
|
908
|
+
"kintsugi-inspired material accent",
|
|
909
|
+
"fine metallic fracture lines, repaired elegance, restrained imperfection",
|
|
910
|
+
],
|
|
911
|
+
"raku-ceramic": [
|
|
912
|
+
"raku ceramic finish",
|
|
913
|
+
"smoky glaze variation, crackle texture, earthy fired surface",
|
|
914
|
+
],
|
|
915
|
+
"porcelain": [
|
|
916
|
+
"porcelain material finish",
|
|
917
|
+
"smooth white surface, delicate glaze, refined translucent softness",
|
|
918
|
+
],
|
|
919
|
+
"enamel": [
|
|
920
|
+
"enamel material finish",
|
|
921
|
+
"glossy saturated color, smooth hard surface, crisp highlights",
|
|
922
|
+
],
|
|
923
|
+
"lacquerware": [
|
|
924
|
+
"lacquerware visual finish",
|
|
925
|
+
"deep glossy surface, subtle layered shine, refined dark richness",
|
|
926
|
+
],
|
|
927
|
+
"brushed-metal": [
|
|
928
|
+
"brushed metal material style",
|
|
929
|
+
"linear grain, cool highlights, industrial precision",
|
|
930
|
+
],
|
|
931
|
+
"carbon-fiber": [
|
|
932
|
+
"carbon fiber material style",
|
|
933
|
+
"woven dark texture, subtle reflectivity, high-performance polish",
|
|
934
|
+
],
|
|
935
|
+
"concrete": [
|
|
936
|
+
"architectural concrete material style",
|
|
937
|
+
"matte mineral texture, subtle pores, neutral gray restraint",
|
|
938
|
+
],
|
|
939
|
+
"matte-black": [
|
|
940
|
+
"matte black visual finish",
|
|
941
|
+
"low-reflection surface, deep tonal control, premium minimal contrast",
|
|
942
|
+
],
|
|
943
|
+
"felt-texture": [
|
|
944
|
+
"felt material texture",
|
|
945
|
+
"soft fiber surface, muted color absorption, tactile warmth",
|
|
946
|
+
],
|
|
947
|
+
"linen-texture": [
|
|
948
|
+
"linen texture visual style",
|
|
949
|
+
"woven fibers, natural irregularity, soft matte tactility",
|
|
950
|
+
],
|
|
951
|
+
"denim-texture": [
|
|
952
|
+
"denim textile texture",
|
|
953
|
+
"twill weave, indigo tonal variation, sturdy fabric grain",
|
|
954
|
+
],
|
|
955
|
+
"grainy-paper": [
|
|
956
|
+
"grainy paper visual texture",
|
|
957
|
+
"visible paper tooth, subtle fiber noise, tactile matte surface",
|
|
958
|
+
],
|
|
959
|
+
"newsprint": [
|
|
960
|
+
"newsprint print style",
|
|
961
|
+
"coarse paper grain, ink spread, economical monochrome texture",
|
|
962
|
+
],
|
|
963
|
+
"letterpress": [
|
|
964
|
+
"letterpress print style",
|
|
965
|
+
"pressed ink texture, debossed edges, tactile paper impression",
|
|
966
|
+
],
|
|
967
|
+
"engraving": [
|
|
968
|
+
"engraving illustration style",
|
|
969
|
+
"fine hatch lines, precise tonal modeling, antique print discipline",
|
|
970
|
+
],
|
|
971
|
+
"etching": [
|
|
972
|
+
"etching print style",
|
|
973
|
+
"delicate scratched lines, crosshatching, plate-mark texture",
|
|
974
|
+
],
|
|
975
|
+
"copperplate": [
|
|
976
|
+
"copperplate engraving style",
|
|
977
|
+
"elegant line weight, refined hatching, historical print polish",
|
|
978
|
+
],
|
|
979
|
+
"mezzotint": [
|
|
980
|
+
"mezzotint print style",
|
|
981
|
+
"velvety tonal gradients, deep blacks, soft print transitions",
|
|
982
|
+
],
|
|
983
|
+
"cyanotype": [
|
|
984
|
+
"cyanotype print style",
|
|
985
|
+
"Prussian blue tonal range, sun-print softness, archival texture",
|
|
986
|
+
],
|
|
987
|
+
"rubber-stamp": [
|
|
988
|
+
"rubber stamp print style",
|
|
989
|
+
"uneven ink edges, pressed texture, handmade registration variation",
|
|
990
|
+
],
|
|
991
|
+
"sticker-sheet": [
|
|
992
|
+
"sticker sheet graphic style",
|
|
993
|
+
"die-cut edges, glossy flat color, playful spacing rhythm",
|
|
994
|
+
],
|
|
995
|
+
"badge-emblem": [
|
|
996
|
+
"badge emblem graphic style",
|
|
997
|
+
"compact circular hierarchy, bold border rhythm, crisp lettering logic",
|
|
998
|
+
],
|
|
999
|
+
"monoline": [
|
|
1000
|
+
"monoline drawing style",
|
|
1001
|
+
"single-weight contours, clean curves, consistent stroke rhythm",
|
|
1002
|
+
],
|
|
1003
|
+
"flat-illustration": [
|
|
1004
|
+
"flat illustration style",
|
|
1005
|
+
"simplified shapes, solid color fields, clean graphic hierarchy",
|
|
1006
|
+
],
|
|
1007
|
+
"editorial-illustration": [
|
|
1008
|
+
"editorial illustration style",
|
|
1009
|
+
"conceptual clarity, textured shapes, publication-grade composition",
|
|
1010
|
+
],
|
|
1011
|
+
"storybook": [
|
|
1012
|
+
"storybook illustration style",
|
|
1013
|
+
"warm hand-rendered texture, gentle color, narrative softness",
|
|
1014
|
+
],
|
|
1015
|
+
"picture-book": [
|
|
1016
|
+
"picture-book illustration style",
|
|
1017
|
+
"friendly shapes, tactile color, clear readable composition",
|
|
1018
|
+
],
|
|
1019
|
+
"comic-book": [
|
|
1020
|
+
"comic book print style",
|
|
1021
|
+
"inked contours, halftone shading, bold panel-like energy",
|
|
1022
|
+
],
|
|
1023
|
+
"manga-tone": [
|
|
1024
|
+
"manga screentone style",
|
|
1025
|
+
"black ink linework, tone patterns, crisp monochrome contrast",
|
|
1026
|
+
],
|
|
1027
|
+
"anime-cel": [
|
|
1028
|
+
"anime cel finish",
|
|
1029
|
+
"flat color fills, clean outlines, crisp shadow shapes",
|
|
1030
|
+
],
|
|
1031
|
+
"cel-shaded": [
|
|
1032
|
+
"cel-shaded rendering",
|
|
1033
|
+
"flat shade bands, clean contours, graphic lighting simplification",
|
|
1034
|
+
],
|
|
1035
|
+
"toon-shaded": [
|
|
1036
|
+
"toon-shaded 3D style",
|
|
1037
|
+
"simplified lighting bands, clean outlines, stylized material response",
|
|
1038
|
+
],
|
|
1039
|
+
"cartoon-modern": [
|
|
1040
|
+
"modern cartoon graphic style",
|
|
1041
|
+
"rounded simplified forms, playful proportion, clean color blocks",
|
|
1042
|
+
],
|
|
1043
|
+
"chibi": [
|
|
1044
|
+
"chibi-inspired proportion style",
|
|
1045
|
+
"compact rounded forms, soft expression cues, playful simplicity",
|
|
1046
|
+
],
|
|
1047
|
+
"kawaii": [
|
|
1048
|
+
"kawaii visual style",
|
|
1049
|
+
"soft rounded forms, pastel palette, gentle playful polish",
|
|
1050
|
+
],
|
|
1051
|
+
"doodle": [
|
|
1052
|
+
"doodle drawing style",
|
|
1053
|
+
"casual linework, spontaneous marks, playful looseness",
|
|
1054
|
+
],
|
|
1055
|
+
"sketchnote": [
|
|
1056
|
+
"sketchnote visual style",
|
|
1057
|
+
"handwritten rhythm, simple line structure, organized roughness",
|
|
1058
|
+
],
|
|
1059
|
+
"whiteboard-sketch": [
|
|
1060
|
+
"whiteboard sketch style",
|
|
1061
|
+
"marker-like lines, clean white field, quick explanatory clarity",
|
|
1062
|
+
],
|
|
1063
|
+
"marker-render": [
|
|
1064
|
+
"marker rendering style",
|
|
1065
|
+
"broad strokes, layered translucent color, industrial design sketch finish",
|
|
1066
|
+
],
|
|
1067
|
+
"concept-art": [
|
|
1068
|
+
"concept art rendering style",
|
|
1069
|
+
"high-impact composition, atmospheric value control, polished ideation finish",
|
|
1070
|
+
],
|
|
1071
|
+
"matte-painting": [
|
|
1072
|
+
"matte painting finish",
|
|
1073
|
+
"seamless painterly depth, cinematic scale, controlled atmosphere",
|
|
1074
|
+
],
|
|
1075
|
+
"fantasy-illustration": [
|
|
1076
|
+
"fantasy illustration finish",
|
|
1077
|
+
"ornate detail, rich color depth, dramatic painterly atmosphere",
|
|
1078
|
+
],
|
|
1079
|
+
"sci-fi-illustration": [
|
|
1080
|
+
"science-fiction illustration style",
|
|
1081
|
+
"sleek futuristic surfaces, luminous accents, precise speculative polish",
|
|
1082
|
+
],
|
|
1083
|
+
"dark-fantasy": [
|
|
1084
|
+
"dark fantasy visual style",
|
|
1085
|
+
"shadowed painterly depth, muted drama, ornate texture",
|
|
1086
|
+
],
|
|
1087
|
+
"solarpunk": [
|
|
1088
|
+
"solarpunk visual style",
|
|
1089
|
+
"optimistic clean technology mood, warm natural palette, luminous clarity",
|
|
1090
|
+
],
|
|
1091
|
+
"cassette-futurism": [
|
|
1092
|
+
"cassette futurism visual style",
|
|
1093
|
+
"retro analog controls, chunky geometry, warm technical nostalgia",
|
|
1094
|
+
],
|
|
1095
|
+
"atompunk": [
|
|
1096
|
+
"atompunk retro-futurist style",
|
|
1097
|
+
"mid-century futurism, atomic geometry, glossy optimism",
|
|
1098
|
+
],
|
|
1099
|
+
"dieselpunk": [
|
|
1100
|
+
"dieselpunk visual style",
|
|
1101
|
+
"industrial grit, brass-dark palette, mechanical art-deco tension",
|
|
1102
|
+
],
|
|
1103
|
+
"steampunk": [
|
|
1104
|
+
"steampunk visual style",
|
|
1105
|
+
"brass mechanical texture, aged leather tones, Victorian industrial polish",
|
|
1106
|
+
],
|
|
1107
|
+
"biopunk": [
|
|
1108
|
+
"biopunk visual style",
|
|
1109
|
+
"organic technical textures, translucent material cues, speculative polish",
|
|
1110
|
+
],
|
|
1111
|
+
"afrofuturist": [
|
|
1112
|
+
"Afrofuturist visual language",
|
|
1113
|
+
"bold geometry, metallic accents, rhythmic futuristic pattern",
|
|
1114
|
+
],
|
|
1115
|
+
"retrofuturism": [
|
|
1116
|
+
"retrofuturist visual style",
|
|
1117
|
+
"past-era future optimism, rounded technical shapes, polished nostalgia",
|
|
1118
|
+
],
|
|
1119
|
+
"neo-futurism": [
|
|
1120
|
+
"neo-futurist visual style",
|
|
1121
|
+
"fluid geometry, sleek minimal surfaces, luminous structural clarity",
|
|
1122
|
+
],
|
|
1123
|
+
"minimal-tech": [
|
|
1124
|
+
"minimal technology visual style",
|
|
1125
|
+
"precise dark-light balance, sparse luminous accents, clean engineered feel",
|
|
1126
|
+
],
|
|
1127
|
+
"terminal-aesthetic": [
|
|
1128
|
+
"terminal aesthetic visual style",
|
|
1129
|
+
"monospace rhythm, dark field, phosphor-like glow",
|
|
1130
|
+
],
|
|
1131
|
+
"crt-screen": [
|
|
1132
|
+
"CRT screen visual finish",
|
|
1133
|
+
"scanlines, soft phosphor bloom, slight analog distortion",
|
|
1134
|
+
],
|
|
1135
|
+
"glitch-art": [
|
|
1136
|
+
"glitch art visual style",
|
|
1137
|
+
"digital fragmentation, chromatic offsets, controlled signal noise",
|
|
1138
|
+
],
|
|
1139
|
+
"datamosh": [
|
|
1140
|
+
"datamosh digital style",
|
|
1141
|
+
"compression smears, pixel drift, controlled digital breakdown",
|
|
1142
|
+
],
|
|
1143
|
+
"ascii-art": [
|
|
1144
|
+
"ASCII art visual style",
|
|
1145
|
+
"monospace glyph texture, grid-like tonal construction, retro computing feel",
|
|
1146
|
+
],
|
|
1147
|
+
"generative-art": [
|
|
1148
|
+
"generative art visual style",
|
|
1149
|
+
"algorithmic pattern logic, parametric rhythm, computational elegance",
|
|
1150
|
+
],
|
|
1151
|
+
"fractal": [
|
|
1152
|
+
"fractal visual style",
|
|
1153
|
+
"recursive pattern depth, mathematical symmetry, intricate scale variation",
|
|
1154
|
+
],
|
|
1155
|
+
"topographic": [
|
|
1156
|
+
"topographic line visual style",
|
|
1157
|
+
"contour-line rhythm, layered elevation feel, precise spatial abstraction",
|
|
1158
|
+
],
|
|
1159
|
+
"cartographic": [
|
|
1160
|
+
"cartographic visual style",
|
|
1161
|
+
"map-like line discipline, measured labeling rhythm, muted technical palette",
|
|
1162
|
+
],
|
|
1163
|
+
"infographic-clean": [
|
|
1164
|
+
"clean infographic visual style",
|
|
1165
|
+
"simple annotation hierarchy, restrained color coding, high readability",
|
|
1166
|
+
],
|
|
1167
|
+
"dashboard-dark": [
|
|
1168
|
+
"dark dashboard visual language",
|
|
1169
|
+
"deep surfaces, luminous data accents, dense but readable spacing",
|
|
1170
|
+
],
|
|
1171
|
+
"trading-terminal": [
|
|
1172
|
+
"trading terminal visual style",
|
|
1173
|
+
"dense numeric rhythm, dark grid, high-contrast signal colors",
|
|
1174
|
+
],
|
|
1175
|
+
"medical-illustration": [
|
|
1176
|
+
"medical illustration style",
|
|
1177
|
+
"clinical clarity, clean shading, precise explanatory linework",
|
|
1178
|
+
],
|
|
1179
|
+
"botanical-illustration": [
|
|
1180
|
+
"botanical illustration style",
|
|
1181
|
+
"delicate natural linework, subtle watercolor tones, scientific precision",
|
|
1182
|
+
],
|
|
1183
|
+
"anatomical-plate": [
|
|
1184
|
+
"anatomical plate illustration style",
|
|
1185
|
+
"fine explanatory linework, muted academic palette, archival precision",
|
|
1186
|
+
],
|
|
1187
|
+
"astronomical-plate": [
|
|
1188
|
+
"astronomical plate visual style",
|
|
1189
|
+
"deep field contrast, precise luminous points, archival scientific calm",
|
|
1190
|
+
],
|
|
1191
|
+
"field-guide": [
|
|
1192
|
+
"field guide illustration style",
|
|
1193
|
+
"clear specimen-like rendering, neutral spacing, concise annotation rhythm",
|
|
1194
|
+
],
|
|
1195
|
+
"catalog-product": [
|
|
1196
|
+
"catalog product visual style",
|
|
1197
|
+
"clean isolated presentation, consistent lighting, commercial comparability",
|
|
1198
|
+
],
|
|
1199
|
+
"packaging-render": [
|
|
1200
|
+
"packaging render style",
|
|
1201
|
+
"crisp dieline-like edges, material clarity, retail-grade polish",
|
|
1202
|
+
],
|
|
1203
|
+
"ecommerce-clean": [
|
|
1204
|
+
"e-commerce clean visual style",
|
|
1205
|
+
"white field clarity, accurate material detail, conversion-focused polish",
|
|
1206
|
+
],
|
|
1207
|
+
"luxury-editorial": [
|
|
1208
|
+
"luxury editorial visual style",
|
|
1209
|
+
"quiet opulence, elegant spacing, refined material emphasis",
|
|
1210
|
+
],
|
|
1211
|
+
"minimal-magazine": [
|
|
1212
|
+
"minimal magazine layout style",
|
|
1213
|
+
"large whitespace, refined typography, disciplined editorial pacing",
|
|
1214
|
+
],
|
|
1215
|
+
"photobook": [
|
|
1216
|
+
"photobook editorial style",
|
|
1217
|
+
"image-forward pacing, quiet captions, archival sequencing feel",
|
|
1218
|
+
],
|
|
1219
|
+
"scrapbook": [
|
|
1220
|
+
"scrapbook visual style",
|
|
1221
|
+
"paper layering, tape-like accents, handmade archival texture",
|
|
1222
|
+
],
|
|
1223
|
+
"grid-system": [
|
|
1224
|
+
"grid system visual style",
|
|
1225
|
+
"visible alignment discipline, consistent rhythm, rational spacing",
|
|
1226
|
+
],
|
|
1227
|
+
"asymmetric-layout": [
|
|
1228
|
+
"asymmetric layout style",
|
|
1229
|
+
"off-center balance, dynamic whitespace, controlled visual tension",
|
|
1230
|
+
],
|
|
1231
|
+
"centered-minimal": [
|
|
1232
|
+
"centered minimal composition",
|
|
1233
|
+
"single-axis calm, balanced margins, quiet focal strength",
|
|
1234
|
+
],
|
|
1235
|
+
"dense-editorial": [
|
|
1236
|
+
"dense editorial layout style",
|
|
1237
|
+
"compact hierarchy, precise spacing, information-rich polish",
|
|
1238
|
+
],
|
|
1239
|
+
"sparse-editorial": [
|
|
1240
|
+
"sparse editorial layout style",
|
|
1241
|
+
"generous whitespace, slow visual pacing, refined restraint",
|
|
1242
|
+
],
|
|
1243
|
+
"posterized": [
|
|
1244
|
+
"posterized graphic style",
|
|
1245
|
+
"reduced tonal steps, flat color separation, bold silhouette clarity",
|
|
1246
|
+
],
|
|
1247
|
+
"halftone": [
|
|
1248
|
+
"halftone print style",
|
|
1249
|
+
"dot-screen shading, print texture, graphic tonal compression",
|
|
1250
|
+
],
|
|
1251
|
+
"grainy-gradient": [
|
|
1252
|
+
"grainy gradient style",
|
|
1253
|
+
"soft gradient fields, fine noise texture, contemporary digital warmth",
|
|
1254
|
+
],
|
|
1255
|
+
"noir-comic": [
|
|
1256
|
+
"noir comic visual style",
|
|
1257
|
+
"heavy ink shadows, dramatic contrast, graphic suspense",
|
|
1258
|
+
],
|
|
1259
|
+
"pulp-cover": [
|
|
1260
|
+
"pulp cover illustration style",
|
|
1261
|
+
"bold painted drama, aged print texture, high-contrast title-area rhythm",
|
|
1262
|
+
],
|
|
1263
|
+
"retro-computer": [
|
|
1264
|
+
"retro computer visual style",
|
|
1265
|
+
"early digital geometry, phosphor glow, limited color palette",
|
|
1266
|
+
],
|
|
1267
|
+
"eight-bit": [
|
|
1268
|
+
"8-bit visual style",
|
|
1269
|
+
"chunky pixel grid, limited palette, crisp low-resolution charm",
|
|
1270
|
+
],
|
|
1271
|
+
"sixteen-bit": [
|
|
1272
|
+
"16-bit visual style",
|
|
1273
|
+
"richer pixel shading, crisp sprite-like edges, nostalgic color depth",
|
|
1274
|
+
],
|
|
1275
|
+
"lo-fi-digital": [
|
|
1276
|
+
"lo-fi digital visual style",
|
|
1277
|
+
"compressed texture, imperfect pixels, casual screen-era warmth",
|
|
1278
|
+
],
|
|
1279
|
+
"vhs": [
|
|
1280
|
+
"VHS visual finish",
|
|
1281
|
+
"analog color bleed, scan noise, retro tape softness",
|
|
1282
|
+
],
|
|
1283
|
+
"scanline": [
|
|
1284
|
+
"scanline visual finish",
|
|
1285
|
+
"horizontal line texture, subtle flicker feel, retro display rhythm",
|
|
1286
|
+
],
|
|
1287
|
+
"infrared-photo": [
|
|
1288
|
+
"infrared photographic look",
|
|
1289
|
+
"false-color tonal shift, glowing highlights, otherworldly contrast",
|
|
1290
|
+
],
|
|
1291
|
+
"thermal-imaging": [
|
|
1292
|
+
"thermal imaging palette",
|
|
1293
|
+
"heat-map color gradients, high contrast, technical false-color clarity",
|
|
1294
|
+
],
|
|
1295
|
+
"xray-render": [
|
|
1296
|
+
"X-ray inspired rendering",
|
|
1297
|
+
"translucent layered forms, cool monochrome tones, internal contour clarity",
|
|
1298
|
+
],
|
|
1299
|
+
"cross-section": [
|
|
1300
|
+
"cross-section visual style",
|
|
1301
|
+
"clean sliced planes, layered material clarity, explanatory depth",
|
|
1302
|
+
],
|
|
1303
|
+
"exploded-view": [
|
|
1304
|
+
"exploded-view rendering style",
|
|
1305
|
+
"separated layers, precise spacing, technical clarity",
|
|
1306
|
+
],
|
|
1307
|
+
"orthographic": [
|
|
1308
|
+
"orthographic rendering style",
|
|
1309
|
+
"flat projection, measured edges, exact technical presentation",
|
|
1310
|
+
],
|
|
1311
|
+
"axonometric": [
|
|
1312
|
+
"axonometric visual style",
|
|
1313
|
+
"parallel projection, clean depth, measured geometric consistency",
|
|
1314
|
+
],
|
|
1315
|
+
"blueprint-white": [
|
|
1316
|
+
"white blueprint variant",
|
|
1317
|
+
"blue linework on clean white field, precise technical spacing",
|
|
1318
|
+
],
|
|
1319
|
+
"blackpaper-gold": [
|
|
1320
|
+
"black paper gold-ink visual style",
|
|
1321
|
+
"deep matte field, metallic line accents, elegant contrast",
|
|
1322
|
+
],
|
|
1323
|
+
"silverpoint": [
|
|
1324
|
+
"silverpoint drawing style",
|
|
1325
|
+
"delicate metallic gray linework, restrained shading, archival subtlety",
|
|
1326
|
+
],
|
|
1327
|
+
"sumi-e": [
|
|
1328
|
+
"sumi-e ink painting style",
|
|
1329
|
+
"economical brush strokes, tonal ink wash, quiet asymmetry",
|
|
1330
|
+
],
|
|
1331
|
+
"gongbi": [
|
|
1332
|
+
"gongbi fine-line painting style",
|
|
1333
|
+
"meticulous contours, delicate color layers, refined precision",
|
|
1334
|
+
],
|
|
1335
|
+
"minhwa": [
|
|
1336
|
+
"minhwa folk painting style",
|
|
1337
|
+
"flat decorative color, symbolic pattern rhythm, handmade warmth",
|
|
1338
|
+
],
|
|
1339
|
+
"mandala": [
|
|
1340
|
+
"mandala geometric style",
|
|
1341
|
+
"radial symmetry, intricate pattern balance, meditative structure",
|
|
1342
|
+
],
|
|
1343
|
+
"arabesque": [
|
|
1344
|
+
"arabesque ornamental style",
|
|
1345
|
+
"flowing geometric ornament, interlaced curves, refined repetition",
|
|
1346
|
+
],
|
|
1347
|
+
"azulejo": [
|
|
1348
|
+
"azulejo tile visual style",
|
|
1349
|
+
"glazed blue-white patterning, ceramic shine, modular repetition",
|
|
1350
|
+
],
|
|
1351
|
+
"mudcloth": [
|
|
1352
|
+
"mudcloth textile visual style",
|
|
1353
|
+
"earthy geometric pattern, hand-dyed texture, rhythmic contrast",
|
|
1354
|
+
],
|
|
1355
|
+
"ikat": [
|
|
1356
|
+
"ikat textile visual style",
|
|
1357
|
+
"blurred woven edges, patterned dye rhythm, tactile fabric depth",
|
|
1358
|
+
],
|
|
1359
|
+
"tartan": [
|
|
1360
|
+
"tartan pattern style",
|
|
1361
|
+
"woven plaid structure, intersecting color bands, textile precision",
|
|
1362
|
+
],
|
|
1363
|
+
"quilted": [
|
|
1364
|
+
"quilted textile style",
|
|
1365
|
+
"stitched patch rhythm, padded texture, warm crafted geometry",
|
|
1366
|
+
],
|
|
1367
|
+
"macrame": [
|
|
1368
|
+
"macrame fiber style",
|
|
1369
|
+
"knotted cord texture, natural fiber warmth, handmade pattern rhythm",
|
|
1370
|
+
],
|
|
1371
|
+
"beadwork": [
|
|
1372
|
+
"beadwork surface style",
|
|
1373
|
+
"small reflective units, tactile pattern density, crafted shimmer",
|
|
1374
|
+
],
|
|
1375
|
+
"neon-sign": [
|
|
1376
|
+
"neon sign visual style",
|
|
1377
|
+
"glowing tube strokes, dark field contrast, luminous edge bloom",
|
|
1378
|
+
],
|
|
1379
|
+
"laser-cut": [
|
|
1380
|
+
"laser-cut material style",
|
|
1381
|
+
"precise cut edges, layered depth, clean manufactured detail",
|
|
1382
|
+
],
|
|
1383
|
+
"paper-engineering": [
|
|
1384
|
+
"paper engineering visual style",
|
|
1385
|
+
"folded layers, crisp tabs, dimensional paper construction",
|
|
1386
|
+
],
|
|
1387
|
+
"blue-hour": [
|
|
1388
|
+
"blue-hour photographic mood",
|
|
1389
|
+
"cool twilight tones, soft ambient glow, low contrast calm",
|
|
1390
|
+
],
|
|
1391
|
+
"golden-hour": [
|
|
1392
|
+
"golden-hour photographic mood",
|
|
1393
|
+
"warm directional glow, soft shadows, gentle luminous atmosphere",
|
|
1394
|
+
],
|
|
1395
|
+
"overcast-soft": [
|
|
1396
|
+
"overcast soft-light style",
|
|
1397
|
+
"diffuse illumination, muted contrast, natural color restraint",
|
|
1398
|
+
],
|
|
1399
|
+
"flash-photo": [
|
|
1400
|
+
"direct flash photographic style",
|
|
1401
|
+
"hard frontal light, crisp shadows, candid glossy energy",
|
|
1402
|
+
],
|
|
1403
|
+
"editorial-flash": [
|
|
1404
|
+
"editorial flash photography style",
|
|
1405
|
+
"controlled direct light, high-fashion contrast, polished immediacy",
|
|
1406
|
+
],
|
|
1407
|
+
"long-exposure": [
|
|
1408
|
+
"long-exposure photographic style",
|
|
1409
|
+
"motion trails, smooth light flow, temporal softness",
|
|
1410
|
+
],
|
|
1411
|
+
"tilt-shift": [
|
|
1412
|
+
"tilt-shift photographic look",
|
|
1413
|
+
"miniature-like focus falloff, selective sharpness, playful scale feel",
|
|
1414
|
+
],
|
|
1415
|
+
"fisheye": [
|
|
1416
|
+
"fisheye lens visual style",
|
|
1417
|
+
"wide curved perspective, strong spatial distortion, energetic framing",
|
|
1418
|
+
],
|
|
1419
|
+
"macro-texture": [
|
|
1420
|
+
"macro texture visual style",
|
|
1421
|
+
"extreme close detail, shallow depth, tactile surface emphasis",
|
|
1422
|
+
],
|
|
1423
|
+
"editorial-still-life": [
|
|
1424
|
+
"editorial still-life visual style",
|
|
1425
|
+
"arranged form balance, refined surfaces, publication-grade lighting",
|
|
1426
|
+
],
|
|
1427
|
+
"premium-packshot": [
|
|
1428
|
+
"premium packshot style",
|
|
1429
|
+
"accurate silhouette, clean reflections, commercial studio clarity",
|
|
1430
|
+
],
|
|
1431
|
+
"specular-luxury": [
|
|
1432
|
+
"specular luxury rendering",
|
|
1433
|
+
"controlled highlights, glossy material depth, elegant dark contrast",
|
|
1434
|
+
],
|
|
1435
|
+
"matte-studio": [
|
|
1436
|
+
"matte studio rendering",
|
|
1437
|
+
"soft non-reflective surfaces, clean shadow grounding, quiet polish",
|
|
1438
|
+
],
|
|
1439
|
+
"translucent-resin": [
|
|
1440
|
+
"translucent resin material style",
|
|
1441
|
+
"milky depth, soft internal glow, smooth polished surface",
|
|
1442
|
+
],
|
|
1443
|
+
"liquid-metal": [
|
|
1444
|
+
"liquid metal material style",
|
|
1445
|
+
"fluid reflective surface, smooth highlights, futuristic sheen",
|
|
1446
|
+
],
|
|
1447
|
+
"prismatic": [
|
|
1448
|
+
"prismatic visual finish",
|
|
1449
|
+
"split spectral highlights, crystalline refraction, clean luminous edges",
|
|
1450
|
+
],
|
|
1451
|
+
"crystal": [
|
|
1452
|
+
"crystal material style",
|
|
1453
|
+
"faceted transparency, sharp refraction, cool luminous clarity",
|
|
1454
|
+
],
|
|
1455
|
+
"paper-grain-quiet": [
|
|
1456
|
+
"quiet paper grain style",
|
|
1457
|
+
"subtle fibers, matte softness, understated print tactility",
|
|
1458
|
+
],
|
|
1459
|
+
"soft-shadow-ui": [
|
|
1460
|
+
"soft shadow UI visual style",
|
|
1461
|
+
"gentle elevation, rounded surfaces, restrained depth cues",
|
|
1462
|
+
],
|
|
1463
|
+
"flat-ui": [
|
|
1464
|
+
"flat UI visual style",
|
|
1465
|
+
"solid fills, simple hierarchy, crisp component spacing",
|
|
1466
|
+
],
|
|
1467
|
+
"skeuomorphic": [
|
|
1468
|
+
"skeuomorphic visual style",
|
|
1469
|
+
"realistic material cues, tactile controls, dimensional polish",
|
|
1470
|
+
],
|
|
1471
|
+
"neumorphic": [
|
|
1472
|
+
"neumorphic visual style",
|
|
1473
|
+
"soft extruded surfaces, subtle inner shadows, monochrome depth",
|
|
1474
|
+
],
|
|
1475
|
+
"liquid-glass": [
|
|
1476
|
+
"liquid glass visual style",
|
|
1477
|
+
"fluid translucent surfaces, refractive highlights, clean layered depth",
|
|
1478
|
+
],
|
|
1479
|
+
"branded-system": [
|
|
1480
|
+
"cohesive visual system style",
|
|
1481
|
+
"consistent spacing, controlled palette, repeatable identity logic",
|
|
1482
|
+
],
|
|
1483
|
+
"premium-saas": [
|
|
1484
|
+
"premium SaaS product visual style",
|
|
1485
|
+
"quiet interface polish, dense readable surfaces, restrained accent color",
|
|
1486
|
+
],
|
|
1487
|
+
"enterprise-dashboard": [
|
|
1488
|
+
"enterprise dashboard visual style",
|
|
1489
|
+
"organized density, neutral surfaces, precise status color logic",
|
|
1490
|
+
],
|
|
1491
|
+
"fintech": [
|
|
1492
|
+
"fintech visual style",
|
|
1493
|
+
"trustworthy polish, cool neutral palette, precise numeric hierarchy",
|
|
1494
|
+
],
|
|
1495
|
+
"health-tech": [
|
|
1496
|
+
"health-tech visual style",
|
|
1497
|
+
"clean clinical palette, calm hierarchy, accessible visual clarity",
|
|
1498
|
+
],
|
|
1499
|
+
"education-tech": [
|
|
1500
|
+
"education technology visual style",
|
|
1501
|
+
"friendly structure, clear learning hierarchy, warm restrained color",
|
|
1502
|
+
],
|
|
1503
|
+
"creator-economy": [
|
|
1504
|
+
"creator economy visual style",
|
|
1505
|
+
"vibrant gradients, social-native polish, energetic hierarchy",
|
|
1506
|
+
],
|
|
1507
|
+
"developer-tool": [
|
|
1508
|
+
"developer tool visual style",
|
|
1509
|
+
"monospace accents, dark-light contrast, precise technical density",
|
|
1510
|
+
],
|
|
1511
|
+
"open-source-docs": [
|
|
1512
|
+
"open-source documentation visual style",
|
|
1513
|
+
"plain clarity, code-like rhythm, accessible technical hierarchy",
|
|
1514
|
+
],
|
|
1515
|
+
"academic-conference": [
|
|
1516
|
+
"academic conference visual style",
|
|
1517
|
+
"poster-session clarity, structured sections, sober typography",
|
|
1518
|
+
],
|
|
1519
|
+
"premium-workshop": [
|
|
1520
|
+
"premium workshop visual style",
|
|
1521
|
+
"crafted instructional polish, warm neutral palette, practical hierarchy",
|
|
1522
|
+
],
|
|
1523
|
+
"event-poster": [
|
|
1524
|
+
"event poster visual style",
|
|
1525
|
+
"bold focal hierarchy, energetic typography, clean promotional rhythm",
|
|
1526
|
+
],
|
|
1527
|
+
"festival-poster": [
|
|
1528
|
+
"festival poster visual style",
|
|
1529
|
+
"vivid color rhythm, layered print texture, celebratory graphic energy",
|
|
1530
|
+
],
|
|
1531
|
+
"gallery-poster": [
|
|
1532
|
+
"gallery poster visual style",
|
|
1533
|
+
"minimal exhibition typography, generous whitespace, refined cultural tone",
|
|
1534
|
+
],
|
|
1535
|
+
"book-cover": [
|
|
1536
|
+
"book cover design style",
|
|
1537
|
+
"strong title-area hierarchy, symbolic composition, shelf-ready polish",
|
|
1538
|
+
],
|
|
1539
|
+
"album-cover": [
|
|
1540
|
+
"album cover visual style",
|
|
1541
|
+
"square-format impact, atmospheric abstraction, strong graphic identity",
|
|
1542
|
+
],
|
|
1543
|
+
"editorial-cover": [
|
|
1544
|
+
"editorial cover design style",
|
|
1545
|
+
"publication-grade hierarchy, striking crop logic, confident typography",
|
|
1546
|
+
],
|
|
1547
|
+
"poster-minimal": [
|
|
1548
|
+
"minimal poster design style",
|
|
1549
|
+
"single focal idea, strong whitespace, restrained graphic impact",
|
|
1550
|
+
],
|
|
1551
|
+
"poster-maximal": [
|
|
1552
|
+
"maximal poster design style",
|
|
1553
|
+
"layered detail density, energetic typography, controlled overload",
|
|
1554
|
+
],
|
|
1555
|
+
"neo-brutalist-web": [
|
|
1556
|
+
"neo-brutalist web visual style",
|
|
1557
|
+
"bold borders, raw spacing, stark digital contrast",
|
|
1558
|
+
],
|
|
1559
|
+
"anti-design": [
|
|
1560
|
+
"anti-design visual style",
|
|
1561
|
+
"deliberate imbalance, rough digital tension, expressive rule-breaking",
|
|
1562
|
+
],
|
|
1563
|
+
"clean-startup": [
|
|
1564
|
+
"clean startup visual style",
|
|
1565
|
+
"friendly whitespace, smooth gradients, approachable product polish",
|
|
1566
|
+
],
|
|
1567
|
+
"premium-minimal-product": [
|
|
1568
|
+
"premium minimal product style",
|
|
1569
|
+
"single-object clarity, quiet reflections, exact material emphasis",
|
|
1570
|
+
],
|
|
1571
|
+
"editorial-tech": [
|
|
1572
|
+
"editorial technology visual style",
|
|
1573
|
+
"thoughtful grid, subtle technical motifs, magazine-grade clarity",
|
|
1574
|
+
],
|
|
1575
|
+
"science-fiction-minimal": [
|
|
1576
|
+
"minimal science-fiction visual style",
|
|
1577
|
+
"sparse futuristic surfaces, cool luminous accents, clean speculative mood",
|
|
1578
|
+
],
|
|
1579
|
+
"space-opera": [
|
|
1580
|
+
"space opera visual style",
|
|
1581
|
+
"grand luminous scale, rich atmospheric contrast, polished dramatic depth",
|
|
1582
|
+
],
|
|
1583
|
+
"retro-space-age": [
|
|
1584
|
+
"retro space-age visual style",
|
|
1585
|
+
"rounded futuristic geometry, optimistic color, mid-century technical charm",
|
|
1586
|
+
],
|
|
1587
|
+
"industrial-design-sketch": [
|
|
1588
|
+
"industrial design sketch style",
|
|
1589
|
+
"marker shading, precise perspective lines, product ideation polish",
|
|
1590
|
+
],
|
|
1591
|
+
"automotive-render": [
|
|
1592
|
+
"automotive rendering style",
|
|
1593
|
+
"sleek reflections, precise surfacing, dynamic studio lighting",
|
|
1594
|
+
],
|
|
1595
|
+
"watch-render": [
|
|
1596
|
+
"watch rendering style",
|
|
1597
|
+
"macro metallic detail, polished bevels, luxury precision",
|
|
1598
|
+
],
|
|
1599
|
+
"jewelry-render": [
|
|
1600
|
+
"jewelry rendering style",
|
|
1601
|
+
"small-scale specular highlights, gemstone clarity, luxury surface control",
|
|
1602
|
+
],
|
|
1603
|
+
"cosmetic-campaign": [
|
|
1604
|
+
"cosmetic campaign visual style",
|
|
1605
|
+
"smooth material sheen, clean beauty lighting, refined color harmony",
|
|
1606
|
+
],
|
|
1607
|
+
"beverage-campaign": [
|
|
1608
|
+
"beverage campaign visual style",
|
|
1609
|
+
"fresh condensation detail, bright commercial clarity, appetizing color",
|
|
1610
|
+
],
|
|
1611
|
+
"outdoor-campaign": [
|
|
1612
|
+
"outdoor campaign visual style",
|
|
1613
|
+
"rugged material contrast, wide spatial feeling, crisp brand-ready energy",
|
|
1614
|
+
],
|
|
1615
|
+
"eco-campaign": [
|
|
1616
|
+
"eco campaign visual style",
|
|
1617
|
+
"natural color palette, recycled texture cues, clean optimistic restraint",
|
|
1618
|
+
],
|
|
1619
|
+
"nonprofit-campaign": [
|
|
1620
|
+
"nonprofit campaign visual style",
|
|
1621
|
+
"honest typography, warm restrained palette, credible emotional clarity",
|
|
1622
|
+
],
|
|
1623
|
+
"public-service": [
|
|
1624
|
+
"public service visual style",
|
|
1625
|
+
"clear warning hierarchy, accessible contrast, official communication tone",
|
|
1626
|
+
],
|
|
1627
|
+
"wayfinding": [
|
|
1628
|
+
"wayfinding visual style",
|
|
1629
|
+
"unambiguous arrows, high legibility, systematic spacing",
|
|
1630
|
+
],
|
|
1631
|
+
"signage-system": [
|
|
1632
|
+
"signage system visual style",
|
|
1633
|
+
"bold legible forms, consistent spacing, practical contrast",
|
|
1634
|
+
],
|
|
1635
|
+
"safety-manual": [
|
|
1636
|
+
"safety manual illustration style",
|
|
1637
|
+
"clear procedural layout, simple line forms, high-contrast caution palette",
|
|
1638
|
+
],
|
|
1639
|
+
"instruction-manual": [
|
|
1640
|
+
"instruction manual visual style",
|
|
1641
|
+
"stepwise clarity, precise linework, neutral explanatory tone",
|
|
1642
|
+
],
|
|
1643
|
+
"assembly-guide": [
|
|
1644
|
+
"assembly guide visual style",
|
|
1645
|
+
"exploded clarity, numbered rhythm, crisp technical linework",
|
|
1646
|
+
],
|
|
1647
|
+
"technical-manual": [
|
|
1648
|
+
"technical manual visual style",
|
|
1649
|
+
"measured diagrams, exact spacing, sober annotation hierarchy",
|
|
1650
|
+
],
|
|
1651
|
+
"luxury-brochure": [
|
|
1652
|
+
"luxury brochure editorial style",
|
|
1653
|
+
"generous margins, refined serif-sans contrast, premium print tactility",
|
|
1654
|
+
],
|
|
1655
|
+
"travel-editorial": [
|
|
1656
|
+
"travel editorial visual style",
|
|
1657
|
+
"warm photographic pacing, refined captions, aspirational color harmony",
|
|
1658
|
+
],
|
|
1659
|
+
"culinary-editorial": [
|
|
1660
|
+
"culinary editorial visual style",
|
|
1661
|
+
"appetizing texture detail, warm light, refined magazine composition",
|
|
1662
|
+
],
|
|
1663
|
+
"wellness-editorial": [
|
|
1664
|
+
"wellness editorial visual style",
|
|
1665
|
+
"soft neutral palette, calm spacing, gentle natural texture",
|
|
1666
|
+
],
|
|
1667
|
+
"sports-editorial": [
|
|
1668
|
+
"sports editorial visual style",
|
|
1669
|
+
"bold motion rhythm, high-contrast type, energetic graphic pacing",
|
|
1670
|
+
],
|
|
1671
|
+
"music-editorial": [
|
|
1672
|
+
"music editorial visual style",
|
|
1673
|
+
"rhythmic typography, atmospheric texture, expressive color contrast",
|
|
1674
|
+
],
|
|
1675
|
+
"culture-magazine": [
|
|
1676
|
+
"culture magazine visual style",
|
|
1677
|
+
"intelligent editorial hierarchy, refined image-text rhythm, contemporary polish",
|
|
1678
|
+
],
|
|
1679
|
+
"luxury-watch-ad": [
|
|
1680
|
+
"luxury watch advertising style",
|
|
1681
|
+
"precise metallic detail, deep shadows, refined highlight control",
|
|
1682
|
+
],
|
|
1683
|
+
"beauty-ad": [
|
|
1684
|
+
"beauty advertising visual style",
|
|
1685
|
+
"soft flawless gradients, glossy surfaces, elegant tonal control",
|
|
1686
|
+
],
|
|
1687
|
+
"tech-ad": [
|
|
1688
|
+
"technology advertising visual style",
|
|
1689
|
+
"sleek surfaces, luminous edges, minimal future polish",
|
|
1690
|
+
],
|
|
1691
|
+
"fashion-ad": [
|
|
1692
|
+
"fashion advertising visual style",
|
|
1693
|
+
"confident composition, strong negative space, editorial drama",
|
|
1694
|
+
],
|
|
1695
|
+
"toy-packaging": [
|
|
1696
|
+
"toy packaging visual style",
|
|
1697
|
+
"bright playful color, rounded typography, shelf-impact clarity",
|
|
1698
|
+
],
|
|
1699
|
+
"craft-packaging": [
|
|
1700
|
+
"craft packaging visual style",
|
|
1701
|
+
"handmade texture, warm paper tones, small-batch authenticity",
|
|
1702
|
+
],
|
|
1703
|
+
"apothecary-label": [
|
|
1704
|
+
"apothecary label visual style",
|
|
1705
|
+
"vintage typography, ornate borders, muted botanical color",
|
|
1706
|
+
],
|
|
1707
|
+
"minimal-label": [
|
|
1708
|
+
"minimal label design style",
|
|
1709
|
+
"small precise type, large whitespace, quiet product confidence",
|
|
1710
|
+
],
|
|
1711
|
+
"maximal-label": [
|
|
1712
|
+
"maximal label design style",
|
|
1713
|
+
"dense ornament, layered type, rich shelf presence",
|
|
1714
|
+
],
|
|
1715
|
+
"street-poster": [
|
|
1716
|
+
"street poster visual style",
|
|
1717
|
+
"paste-up texture, torn-paper edges, urban graphic immediacy",
|
|
1718
|
+
],
|
|
1719
|
+
"graffiti-inspired": [
|
|
1720
|
+
"graffiti-inspired graphic style",
|
|
1721
|
+
"spray texture, bold letter energy, high-contrast color rhythm",
|
|
1722
|
+
],
|
|
1723
|
+
"stencil": [
|
|
1724
|
+
"stencil graphic style",
|
|
1725
|
+
"cut-out shapes, hard edges, raw spray texture",
|
|
1726
|
+
],
|
|
1727
|
+
"chalkboard": [
|
|
1728
|
+
"chalkboard visual style",
|
|
1729
|
+
"chalk dust texture, hand-drawn lettering feel, dark matte surface",
|
|
1730
|
+
],
|
|
1731
|
+
"blueprint-dark": [
|
|
1732
|
+
"dark blueprint visual style",
|
|
1733
|
+
"cyan linework, dark field, measured technical clarity",
|
|
1734
|
+
],
|
|
1735
|
+
"circuit-board": [
|
|
1736
|
+
"circuit-board visual style",
|
|
1737
|
+
"fine conductive traces, technical grid rhythm, luminous electronic accents",
|
|
1738
|
+
],
|
|
1739
|
+
"microchip": [
|
|
1740
|
+
"microchip visual style",
|
|
1741
|
+
"dense etched pathways, metallic grid logic, high-tech precision",
|
|
1742
|
+
],
|
|
1743
|
+
"quantum-glow": [
|
|
1744
|
+
"quantum glow visual style",
|
|
1745
|
+
"fine particle-like light, dark scientific palette, subtle luminous fields",
|
|
1746
|
+
],
|
|
1747
|
+
"neural-network": [
|
|
1748
|
+
"neural network visual style",
|
|
1749
|
+
"connected point rhythm, luminous line structure, abstract technical clarity",
|
|
1750
|
+
],
|
|
1751
|
+
"wireframe-3d": [
|
|
1752
|
+
"wireframe 3D visual style",
|
|
1753
|
+
"transparent mesh lines, geometric construction, technical depth",
|
|
1754
|
+
],
|
|
1755
|
+
"point-cloud": [
|
|
1756
|
+
"point cloud visual style",
|
|
1757
|
+
"scattered luminous samples, volumetric depth, computational precision",
|
|
1758
|
+
],
|
|
1759
|
+
"parametric": [
|
|
1760
|
+
"parametric design style",
|
|
1761
|
+
"algorithmic curves, repeated structural rhythm, precise computational form",
|
|
1762
|
+
],
|
|
1763
|
+
"mesh-gradient": [
|
|
1764
|
+
"mesh gradient visual style",
|
|
1765
|
+
"smooth interpolated color, soft abstract depth, modern digital surface",
|
|
1766
|
+
],
|
|
1767
|
+
"aurora-gradient": [
|
|
1768
|
+
"aurora gradient visual style",
|
|
1769
|
+
"flowing luminous color bands, soft spectral transitions, airy depth",
|
|
1770
|
+
],
|
|
1771
|
+
"liquid-gradient": [
|
|
1772
|
+
"liquid gradient style",
|
|
1773
|
+
"fluid color mixing, smooth morphing surfaces, glossy digital polish",
|
|
1774
|
+
],
|
|
1775
|
+
"noise-texture": [
|
|
1776
|
+
"noise texture visual style",
|
|
1777
|
+
"fine grain overlay, tactile digital surface, softened flat fields",
|
|
1778
|
+
],
|
|
1779
|
+
"subtle-gradient": [
|
|
1780
|
+
"subtle gradient visual style",
|
|
1781
|
+
"barely-there tonal transitions, quiet polish, modern restraint",
|
|
1782
|
+
],
|
|
1783
|
+
"bold-gradient": [
|
|
1784
|
+
"bold gradient visual style",
|
|
1785
|
+
"high-saturation color transitions, strong focal energy, clean digital finish",
|
|
1786
|
+
],
|
|
1787
|
+
"monochrome-editorial": [
|
|
1788
|
+
"monochrome editorial style",
|
|
1789
|
+
"single-color discipline, strong tonal hierarchy, refined restraint",
|
|
1790
|
+
],
|
|
1791
|
+
"warm-neutral": [
|
|
1792
|
+
"warm neutral visual style",
|
|
1793
|
+
"cream-gray balance, soft contrast, calm material warmth",
|
|
1794
|
+
],
|
|
1795
|
+
"cool-neutral": [
|
|
1796
|
+
"cool neutral visual style",
|
|
1797
|
+
"silver-gray balance, precise contrast, modern calm",
|
|
1798
|
+
],
|
|
1799
|
+
"jewel-tone": [
|
|
1800
|
+
"jewel-tone visual style",
|
|
1801
|
+
"deep saturated palette, rich contrast, refined luminous color",
|
|
1802
|
+
],
|
|
1803
|
+
"monochrome-red": [
|
|
1804
|
+
"monochrome red visual style",
|
|
1805
|
+
"single hue dominance, strong value hierarchy, graphic intensity",
|
|
1806
|
+
],
|
|
1807
|
+
"monochrome-blue": [
|
|
1808
|
+
"monochrome blue visual style",
|
|
1809
|
+
"single hue discipline, cool depth, calm technical precision",
|
|
1810
|
+
],
|
|
1811
|
+
"black-white-red": [
|
|
1812
|
+
"black-white-red graphic style",
|
|
1813
|
+
"tri-color contrast, urgent hierarchy, bold poster energy",
|
|
1814
|
+
],
|
|
1815
|
+
"pastel-minimal": [
|
|
1816
|
+
"pastel minimal visual style",
|
|
1817
|
+
"soft low-saturation palette, quiet spacing, gentle hierarchy",
|
|
1818
|
+
],
|
|
1819
|
+
"acid-graphics": [
|
|
1820
|
+
"acid graphic style",
|
|
1821
|
+
"sharp saturated color, abrasive contrast, experimental digital energy",
|
|
1822
|
+
],
|
|
1823
|
+
"new-wave": [
|
|
1824
|
+
"new wave graphic style",
|
|
1825
|
+
"diagonal typography, bright contrast, experimental grid rhythm",
|
|
1826
|
+
],
|
|
1827
|
+
"postmodern-grid": [
|
|
1828
|
+
"postmodern grid style",
|
|
1829
|
+
"playful alignment shifts, expressive geometry, controlled disorder",
|
|
1830
|
+
],
|
|
1831
|
+
"neo-memphis": [
|
|
1832
|
+
"neo-Memphis visual style",
|
|
1833
|
+
"playful shapes, modern pastel-bright balance, clean postmodern rhythm",
|
|
1834
|
+
],
|
|
1835
|
+
"corporate-memphis": [
|
|
1836
|
+
"corporate Memphis visual style",
|
|
1837
|
+
"friendly geometric accents, clean business polish, restrained playfulness",
|
|
1838
|
+
],
|
|
1839
|
+
"friendly-saas": [
|
|
1840
|
+
"friendly SaaS visual style",
|
|
1841
|
+
"approachable rounded surfaces, clear hierarchy, warm accent color",
|
|
1842
|
+
],
|
|
1843
|
+
"serious-saas": [
|
|
1844
|
+
"serious SaaS visual style",
|
|
1845
|
+
"neutral density, exact alignment, enterprise-grade restraint",
|
|
1846
|
+
],
|
|
1847
|
+
"ai-product": [
|
|
1848
|
+
"AI product visual style",
|
|
1849
|
+
"abstract luminous gradients, technical clarity, polished futuristic restraint",
|
|
1850
|
+
],
|
|
1851
|
+
"robotics-lab": [
|
|
1852
|
+
"robotics lab visual style",
|
|
1853
|
+
"precision surfaces, cool technical lighting, mechanical clarity",
|
|
1854
|
+
],
|
|
1855
|
+
"space-tech": [
|
|
1856
|
+
"space technology visual style",
|
|
1857
|
+
"dark precision, luminous orbital lines, aerospace-grade polish",
|
|
1858
|
+
],
|
|
1859
|
+
"climate-tech": [
|
|
1860
|
+
"climate technology visual style",
|
|
1861
|
+
"natural green-blue palette, scientific clarity, optimistic restraint",
|
|
1862
|
+
],
|
|
1863
|
+
"biotech": [
|
|
1864
|
+
"biotech visual style",
|
|
1865
|
+
"soft scientific gradients, translucent material cues, clinical precision",
|
|
1866
|
+
],
|
|
1867
|
+
"legal-tech": [
|
|
1868
|
+
"legal technology visual style",
|
|
1869
|
+
"formal typography, sober palette, trust-first hierarchy",
|
|
1870
|
+
],
|
|
1871
|
+
"gov-tech": [
|
|
1872
|
+
"government technology visual style",
|
|
1873
|
+
"accessible clarity, official restraint, high legibility",
|
|
1874
|
+
],
|
|
1875
|
+
"security-tech": [
|
|
1876
|
+
"security technology visual style",
|
|
1877
|
+
"dark protective palette, sharp geometric accents, precise signal hierarchy",
|
|
1878
|
+
],
|
|
1879
|
+
"privacy-tech": [
|
|
1880
|
+
"privacy technology visual style",
|
|
1881
|
+
"calm protective tones, minimal lock-like geometry, trustworthy restraint",
|
|
1882
|
+
],
|
|
1883
|
+
"knowledge-base": [
|
|
1884
|
+
"knowledge base visual style",
|
|
1885
|
+
"organized documentation rhythm, calm typography, clear information grouping",
|
|
1886
|
+
],
|
|
1887
|
+
"research-lab": [
|
|
1888
|
+
"research lab visual style",
|
|
1889
|
+
"whitepaper clarity, subtle scientific texture, disciplined spacing",
|
|
1890
|
+
],
|
|
1891
|
+
"ops-dashboard": [
|
|
1892
|
+
"operations dashboard visual style",
|
|
1893
|
+
"dense status hierarchy, clear severity color, pragmatic interface polish",
|
|
1894
|
+
],
|
|
1895
|
+
"command-center": [
|
|
1896
|
+
"command center visual style",
|
|
1897
|
+
"dark operational density, luminous status accents, high-readability structure",
|
|
1898
|
+
],
|
|
1899
|
+
"map-visual": [
|
|
1900
|
+
"map visual style",
|
|
1901
|
+
"thin route lines, muted geography-like tones, precise labeling rhythm",
|
|
1902
|
+
],
|
|
1903
|
+
"timeline-editorial": [
|
|
1904
|
+
"timeline editorial style",
|
|
1905
|
+
"linear narrative rhythm, clear milestones, refined spacing",
|
|
1906
|
+
],
|
|
1907
|
+
"process-diagram": [
|
|
1908
|
+
"process diagram visual style",
|
|
1909
|
+
"sequential clarity, directional rhythm, clean explanatory structure",
|
|
1910
|
+
],
|
|
1911
|
+
"flowchart-clean": [
|
|
1912
|
+
"clean flowchart visual style",
|
|
1913
|
+
"simple node geometry, clear connector rhythm, high legibility",
|
|
1914
|
+
],
|
|
1915
|
+
"mind-map": [
|
|
1916
|
+
"mind-map visual style",
|
|
1917
|
+
"radial branching rhythm, organized idea clusters, clean linework",
|
|
1918
|
+
],
|
|
1919
|
+
"systems-map": [
|
|
1920
|
+
"systems map visual style",
|
|
1921
|
+
"relationship-first layout, precise grouping, sober annotation hierarchy",
|
|
1922
|
+
],
|
|
1923
|
+
"matrix-layout": [
|
|
1924
|
+
"matrix layout visual style",
|
|
1925
|
+
"two-axis comparison clarity, balanced cells, dense readability",
|
|
1926
|
+
],
|
|
1927
|
+
"scorecard": [
|
|
1928
|
+
"scorecard visual style",
|
|
1929
|
+
"compact metric hierarchy, strong label clarity, business-grade polish",
|
|
1930
|
+
],
|
|
1931
|
+
"kanban-board": [
|
|
1932
|
+
"kanban board visual style",
|
|
1933
|
+
"lane rhythm, compact task surfaces, clean operational hierarchy",
|
|
1934
|
+
],
|
|
1935
|
+
"roadmap": [
|
|
1936
|
+
"roadmap visual style",
|
|
1937
|
+
"phased progression, timeline clarity, strategic planning polish",
|
|
1938
|
+
],
|
|
1939
|
+
"okr-dashboard": [
|
|
1940
|
+
"OKR dashboard visual style",
|
|
1941
|
+
"goal hierarchy, progress emphasis, clean business structure",
|
|
1942
|
+
],
|
|
1943
|
+
"risk-matrix": [
|
|
1944
|
+
"risk matrix visual style",
|
|
1945
|
+
"severity-probability grid, alert color discipline, executive clarity",
|
|
1946
|
+
],
|
|
1947
|
+
"audit-report": [
|
|
1948
|
+
"audit report visual style",
|
|
1949
|
+
"formal table-like clarity, restrained palette, evidence-first structure",
|
|
1950
|
+
],
|
|
1951
|
+
"incident-report": [
|
|
1952
|
+
"incident report visual style",
|
|
1953
|
+
"clear severity hierarchy, timeline emphasis, operational readability",
|
|
1954
|
+
],
|
|
1955
|
+
"postmortem": [
|
|
1956
|
+
"postmortem report visual style",
|
|
1957
|
+
"root-cause clarity, sober tone, structured evidence hierarchy",
|
|
1958
|
+
],
|
|
1959
|
+
"qa-report": [
|
|
1960
|
+
"QA report visual style",
|
|
1961
|
+
"defect-status clarity, compact grids, pragmatic color coding",
|
|
1962
|
+
],
|
|
1963
|
+
"release-notes": [
|
|
1964
|
+
"release notes visual style",
|
|
1965
|
+
"versioned hierarchy, clean changelog rhythm, product communication polish",
|
|
1966
|
+
],
|
|
1967
|
+
"pitch-deck": [
|
|
1968
|
+
"pitch deck visual style",
|
|
1969
|
+
"bold narrative hierarchy, investor-grade polish, confident whitespace",
|
|
1970
|
+
],
|
|
1971
|
+
"strategy-deck": [
|
|
1972
|
+
"strategy deck visual style",
|
|
1973
|
+
"executive synthesis, restrained visuals, strong storyline hierarchy",
|
|
1974
|
+
],
|
|
1975
|
+
"board-deck": [
|
|
1976
|
+
"board deck visual style",
|
|
1977
|
+
"formal executive clarity, sparse high-signal layout, sober color accents",
|
|
1978
|
+
],
|
|
1979
|
+
"sales-deck": [
|
|
1980
|
+
"sales deck visual style",
|
|
1981
|
+
"benefit-led hierarchy, clean proof points, commercial polish",
|
|
1982
|
+
],
|
|
1983
|
+
"training-slide": [
|
|
1984
|
+
"training slide visual style",
|
|
1985
|
+
"instructional clarity, stepwise structure, readable learner pacing",
|
|
1986
|
+
],
|
|
1987
|
+
"lecture-slide": [
|
|
1988
|
+
"lecture slide visual style",
|
|
1989
|
+
"academic hierarchy, clear diagrams, minimal distraction",
|
|
1990
|
+
],
|
|
1991
|
+
"workshop-canvas": [
|
|
1992
|
+
"workshop canvas visual style",
|
|
1993
|
+
"sectioned workspace rhythm, writable zones, facilitation clarity",
|
|
1994
|
+
],
|
|
1995
|
+
"design-system": [
|
|
1996
|
+
"design system visual style",
|
|
1997
|
+
"component consistency, token-like spacing, systematic documentation clarity",
|
|
1998
|
+
],
|
|
1999
|
+
"style-tile": [
|
|
2000
|
+
"style tile visual format",
|
|
2001
|
+
"palette-type-texture grouping, compact design direction clarity",
|
|
2002
|
+
],
|
|
2003
|
+
"ui-kit": [
|
|
2004
|
+
"UI kit visual style",
|
|
2005
|
+
"component samples, consistent states, production-grade spacing",
|
|
2006
|
+
],
|
|
2007
|
+
"wireframe": [
|
|
2008
|
+
"wireframe visual style",
|
|
2009
|
+
"low-fidelity boxes, neutral lines, structure-first clarity",
|
|
2010
|
+
],
|
|
2011
|
+
"lo-fi-wireframe": [
|
|
2012
|
+
"low-fidelity wireframe style",
|
|
2013
|
+
"rough gray boxes, minimal decoration, quick structure communication",
|
|
2014
|
+
],
|
|
2015
|
+
"hi-fi-mockup": [
|
|
2016
|
+
"high-fidelity mockup style",
|
|
2017
|
+
"polished components, realistic spacing, production-ready surfaces",
|
|
2018
|
+
],
|
|
2019
|
+
"mobile-app-polish": [
|
|
2020
|
+
"mobile app polish",
|
|
2021
|
+
"compact hierarchy, thumb-friendly spacing, crisp interface surfaces",
|
|
2022
|
+
],
|
|
2023
|
+
"desktop-app-polish": [
|
|
2024
|
+
"desktop app polish",
|
|
2025
|
+
"dense controls, clear panes, precise productivity layout",
|
|
2026
|
+
],
|
|
2027
|
+
"web-landing-polish": [
|
|
2028
|
+
"web landing page polish",
|
|
2029
|
+
"strong hero hierarchy, clean conversion path, modern responsive feel",
|
|
2030
|
+
],
|
|
2031
|
+
"poster-grid": [
|
|
2032
|
+
"poster grid design style",
|
|
2033
|
+
"strong modular alignment, type-image rhythm, print-ready hierarchy",
|
|
2034
|
+
],
|
|
2035
|
+
"typographic-only": [
|
|
2036
|
+
"typographic-only visual style",
|
|
2037
|
+
"type as the primary graphic, strong spacing, expressive hierarchy",
|
|
2038
|
+
],
|
|
2039
|
+
"kinetic-type": [
|
|
2040
|
+
"kinetic typography visual style",
|
|
2041
|
+
"motion-implied type rhythm, dynamic spacing, energetic letterforms",
|
|
2042
|
+
],
|
|
2043
|
+
"calligraphic": [
|
|
2044
|
+
"calligraphic visual style",
|
|
2045
|
+
"flowing stroke contrast, ink rhythm, refined hand-drawn energy",
|
|
2046
|
+
],
|
|
2047
|
+
"brush-lettering": [
|
|
2048
|
+
"brush lettering style",
|
|
2049
|
+
"expressive stroke pressure, handmade curves, ink-like texture",
|
|
2050
|
+
],
|
|
2051
|
+
"blackletter": [
|
|
2052
|
+
"blackletter inspired typography",
|
|
2053
|
+
"dense angular letterforms, historical drama, high-contrast strokes",
|
|
2054
|
+
],
|
|
2055
|
+
"serif-editorial": [
|
|
2056
|
+
"serif editorial typography",
|
|
2057
|
+
"refined letter contrast, magazine-grade spacing, literary polish",
|
|
2058
|
+
],
|
|
2059
|
+
"grotesk-modern": [
|
|
2060
|
+
"grotesk modern typography",
|
|
2061
|
+
"neutral sans rhythm, exact spacing, contemporary clarity",
|
|
2062
|
+
],
|
|
2063
|
+
"mono-technical": [
|
|
2064
|
+
"monospace technical typography",
|
|
2065
|
+
"code-like rhythm, exact alignment, analytical clarity",
|
|
2066
|
+
],
|
|
2067
|
+
"variable-type": [
|
|
2068
|
+
"variable typography style",
|
|
2069
|
+
"dynamic weight contrast, flexible letter rhythm, modern type expression",
|
|
2070
|
+
],
|
|
2071
|
+
"ornamental-type": [
|
|
2072
|
+
"ornamental typography style",
|
|
2073
|
+
"decorative letter detail, display hierarchy, crafted type presence",
|
|
2074
|
+
],
|
|
2075
|
+
"handwritten": [
|
|
2076
|
+
"handwritten visual style",
|
|
2077
|
+
"natural stroke variation, informal rhythm, tactile personal feel",
|
|
2078
|
+
],
|
|
2079
|
+
"chalk-lettering": [
|
|
2080
|
+
"chalk lettering style",
|
|
2081
|
+
"powdery strokes, dark matte field, handmade signage texture",
|
|
2082
|
+
],
|
|
2083
|
+
"neon-lettering": [
|
|
2084
|
+
"neon lettering style",
|
|
2085
|
+
"tube-like strokes, luminous glow, dark-field contrast",
|
|
2086
|
+
],
|
|
2087
|
+
"metallic-type": [
|
|
2088
|
+
"metallic typography style",
|
|
2089
|
+
"reflective letter surfaces, bevel highlights, premium dimensionality",
|
|
2090
|
+
],
|
|
2091
|
+
"embossed-type": [
|
|
2092
|
+
"embossed typography style",
|
|
2093
|
+
"raised paper texture, soft shadows, tactile print depth",
|
|
2094
|
+
],
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
DEFAULT_VARIANT_PRESETS = ["corporate", "premium", "minimal", "flat-vector", "photoreal", "clean-ui"]
|
|
2098
|
+
|
|
365
2099
|
ASPECT_SIZE = {
|
|
366
2100
|
"3:4": "portrait",
|
|
2101
|
+
"4:5": "portrait",
|
|
367
2102
|
"4:3": "landscape",
|
|
368
2103
|
"16:9": "landscape",
|
|
369
2104
|
"9:16": "portrait",
|
|
@@ -373,11 +2108,17 @@ ASPECT_SIZE = {
|
|
|
373
2108
|
BUZZWORDS = ["stunning", "beautiful", "professional", "high quality", "nice", "modern", "高级感"]
|
|
374
2109
|
|
|
375
2110
|
TEMPLATE_DEFS = {
|
|
2111
|
+
"poster_general": {
|
|
2112
|
+
"asset_type": "poster",
|
|
2113
|
+
"label": "通用视觉海报",
|
|
2114
|
+
"layout": "strong title area, relevant main visual, supporting content blocks only when requested, quiet footer if needed",
|
|
2115
|
+
"keywords": [],
|
|
2116
|
+
},
|
|
376
2117
|
"poster_zh_promo": {
|
|
377
2118
|
"asset_type": "poster",
|
|
378
2119
|
"label": "中文促销海报",
|
|
379
2120
|
"layout": "large headline zone, hero product zone, price/offer block, quiet footer",
|
|
380
|
-
"keywords": ["促销", "新品", "价格", "优惠", "茶饮", "冷泡"
|
|
2121
|
+
"keywords": ["促销", "新品", "价格", "优惠", "茶饮", "冷泡"],
|
|
381
2122
|
},
|
|
382
2123
|
"poster_brand_kv": {
|
|
383
2124
|
"asset_type": "poster",
|
|
@@ -389,7 +2130,7 @@ TEMPLATE_DEFS = {
|
|
|
389
2130
|
"asset_type": "poster",
|
|
390
2131
|
"label": "活动海报",
|
|
391
2132
|
"layout": "title, date/venue, speaker or theme block, organizer footer",
|
|
392
|
-
"keywords": ["活动", "会议", "展览", "event", "workshop", "讲座"],
|
|
2133
|
+
"keywords": ["活动", "会议", "发布会", "展览", "event", "workshop", "讲座"],
|
|
393
2134
|
},
|
|
394
2135
|
"poster_info_dense": {
|
|
395
2136
|
"asset_type": "poster",
|
|
@@ -397,18 +2138,36 @@ TEMPLATE_DEFS = {
|
|
|
397
2138
|
"layout": "modular grid with clear title, sections, callouts, and footer rules",
|
|
398
2139
|
"keywords": ["日程", "规则", "清单", "信息", "流程", "说明"],
|
|
399
2140
|
},
|
|
2141
|
+
"poster_social_cover": {
|
|
2142
|
+
"asset_type": "poster",
|
|
2143
|
+
"label": "社媒封面 / 方图",
|
|
2144
|
+
"layout": "large readable title, compact card grid or content blocks from the brief, strong mobile feed thumbnail readability, no price or product-offer area unless requested",
|
|
2145
|
+
"keywords": ["小红书", "社媒", "方图", "封面", "cover", "social"],
|
|
2146
|
+
},
|
|
400
2147
|
"ui_mobile_home": {
|
|
401
2148
|
"asset_type": "ui",
|
|
402
2149
|
"label": "移动 App 首页",
|
|
403
2150
|
"layout": "phone status bar, app header, content cards, primary action, bottom navigation",
|
|
404
|
-
"keywords": ["
|
|
2151
|
+
"keywords": ["首页", "home", "home screen", "app 首页", "手机首页", "移动首页", "小程序首页"],
|
|
2152
|
+
},
|
|
2153
|
+
"ui_requested_screen": {
|
|
2154
|
+
"asset_type": "ui",
|
|
2155
|
+
"label": "指定界面",
|
|
2156
|
+
"layout": "preserve the requested screen type, named sections, controls, and reading order; include only UI elements directly requested or implied by the screen type",
|
|
2157
|
+
"keywords": ["设置", "详情", "列表", "表单", "profile", "settings", "detail", "list", "form"],
|
|
405
2158
|
},
|
|
406
2159
|
"ui_dashboard": {
|
|
407
2160
|
"asset_type": "ui",
|
|
408
2161
|
"label": "Web / SaaS Dashboard",
|
|
409
|
-
"layout": "
|
|
2162
|
+
"layout": "dashboard workspace that preserves the requested sections; use KPI cards only for named metrics, and add charts, tables, or lists only when the brief requests or strongly implies them",
|
|
410
2163
|
"keywords": ["dashboard", "仪表盘", "后台", "saas", "web"],
|
|
411
2164
|
},
|
|
2165
|
+
"slide_corporate_report": {
|
|
2166
|
+
"asset_type": "slide",
|
|
2167
|
+
"label": "企业汇报单页",
|
|
2168
|
+
"layout": "widescreen 16:9 corporate presentation slide; preserve every explicit region, section structure, visual element placement, and spacing requirement from the user brief",
|
|
2169
|
+
"keywords": ["ppt", "powerpoint", "slide", "presentation", "deck", "widescreen", "汇报", "报告", "分析", "风险", "幻灯片", "演示文稿"],
|
|
2170
|
+
},
|
|
412
2171
|
"diagram_rag": {
|
|
413
2172
|
"asset_type": "diagram",
|
|
414
2173
|
"label": "RAG 架构图",
|
|
@@ -424,7 +2183,7 @@ TEMPLATE_DEFS = {
|
|
|
424
2183
|
"product_hero": {
|
|
425
2184
|
"asset_type": "product",
|
|
426
2185
|
"label": "产品英雄图",
|
|
427
|
-
"layout": "single product hero,
|
|
2186
|
+
"layout": "single requested product hero, no props unless requested, clear material close-up, editorial finish",
|
|
428
2187
|
"keywords": ["产品", "商品", "渲染", "电商", "新品"],
|
|
429
2188
|
},
|
|
430
2189
|
"illustration_scene": {
|
|
@@ -502,10 +2261,59 @@ def split_csv(value: str | None) -> list[str]:
|
|
|
502
2261
|
return [v.strip() for v in re.split(r"[,,]", value) if v.strip()]
|
|
503
2262
|
|
|
504
2263
|
|
|
2264
|
+
def available_style_presets(include_auto: bool = False) -> list[str]:
|
|
2265
|
+
names = sorted(STYLE_PRESETS.keys())
|
|
2266
|
+
return names if include_auto else [name for name in names if name != "auto"]
|
|
2267
|
+
|
|
2268
|
+
|
|
2269
|
+
def parse_style_preset_list(value: str | None, default: list[str] | None = None) -> list[str]:
|
|
2270
|
+
if not value:
|
|
2271
|
+
return list(default or [])
|
|
2272
|
+
raw = [item.strip() for item in split_csv(value)]
|
|
2273
|
+
if any(item.lower() == "all" for item in raw):
|
|
2274
|
+
raw = available_style_presets()
|
|
2275
|
+
seen: set[str] = set()
|
|
2276
|
+
presets: list[str] = []
|
|
2277
|
+
invalid: list[str] = []
|
|
2278
|
+
for item in raw:
|
|
2279
|
+
if not item:
|
|
2280
|
+
continue
|
|
2281
|
+
if item not in STYLE_PRESETS:
|
|
2282
|
+
invalid.append(item)
|
|
2283
|
+
continue
|
|
2284
|
+
if item not in seen:
|
|
2285
|
+
seen.add(item)
|
|
2286
|
+
presets.append(item)
|
|
2287
|
+
if invalid:
|
|
2288
|
+
allowed = ", ".join(available_style_presets(include_auto=True)) + ", all"
|
|
2289
|
+
raise ValueError(f"未知风格预设:{', '.join(invalid)}。可选:{allowed}")
|
|
2290
|
+
return presets
|
|
2291
|
+
|
|
2292
|
+
|
|
505
2293
|
def normalize_ws(text: str) -> str:
|
|
506
2294
|
return " ".join(text.split())
|
|
507
2295
|
|
|
508
2296
|
|
|
2297
|
+
def strip_nonvisual_request_meta(text: str) -> str:
|
|
2298
|
+
"""移除尾部对 agent/skill 的交互指令,避免污染生图 prompt。"""
|
|
2299
|
+
lines = text.splitlines()
|
|
2300
|
+
while lines:
|
|
2301
|
+
line = lines[-1].strip()
|
|
2302
|
+
compact = re.sub(r"\s+", "", line.lower())
|
|
2303
|
+
if not line:
|
|
2304
|
+
lines.pop()
|
|
2305
|
+
continue
|
|
2306
|
+
has_tool_ref = any(k in compact for k in ["skill", "技能", "模型", "agent"])
|
|
2307
|
+
has_action_ref = any(k in compact for k in ["画图", "出图", "生成图片", "看看效果", "看效果", "试试", "测试"])
|
|
2308
|
+
is_request_meta = any(k in compact for k in ["这是我的一个需求", "这是我的需求", "这个需求"])
|
|
2309
|
+
if (has_tool_ref and has_action_ref) or (is_request_meta and has_action_ref):
|
|
2310
|
+
lines.pop()
|
|
2311
|
+
continue
|
|
2312
|
+
break
|
|
2313
|
+
cleaned = "\n".join(lines).strip()
|
|
2314
|
+
return cleaned or text.strip()
|
|
2315
|
+
|
|
2316
|
+
|
|
509
2317
|
def has_cjk(text: str) -> bool:
|
|
510
2318
|
return bool(re.search(r"[\u4e00-\u9fff]", text))
|
|
511
2319
|
|
|
@@ -540,15 +2348,49 @@ def safety_avoid_list(notes: list[str]) -> list[str]:
|
|
|
540
2348
|
]
|
|
541
2349
|
|
|
542
2350
|
|
|
2351
|
+
def keyword_in_text(keyword: str, text_lower: str) -> bool:
|
|
2352
|
+
kw = keyword.lower().strip()
|
|
2353
|
+
if not kw:
|
|
2354
|
+
return False
|
|
2355
|
+
positions: list[int] = []
|
|
2356
|
+
if kw.isascii() and re.search(r"[a-z0-9]", kw):
|
|
2357
|
+
positions = [m.start() for m in re.finditer(rf"(?<![a-z0-9]){re.escape(kw)}(?![a-z0-9])", text_lower)]
|
|
2358
|
+
else:
|
|
2359
|
+
start = 0
|
|
2360
|
+
while True:
|
|
2361
|
+
pos = text_lower.find(kw, start)
|
|
2362
|
+
if pos < 0:
|
|
2363
|
+
break
|
|
2364
|
+
positions.append(pos)
|
|
2365
|
+
start = pos + max(1, len(kw))
|
|
2366
|
+
for pos in positions:
|
|
2367
|
+
context = text_lower[max(0, pos - 14) : pos]
|
|
2368
|
+
if any(marker in context for marker in ["不要", "不需要", "不能", "不得", "无", "没有", "no ", "not ", "without "]):
|
|
2369
|
+
continue
|
|
2370
|
+
return True
|
|
2371
|
+
return False
|
|
2372
|
+
|
|
2373
|
+
|
|
543
2374
|
def route_asset_type(request: str, override: str | None = None) -> str:
|
|
544
2375
|
if override:
|
|
545
2376
|
return override
|
|
546
2377
|
lower = request.lower()
|
|
2378
|
+
explicit_routes = [
|
|
2379
|
+
("slide", ["powerpoint", "slide", "presentation", "ppt", "幻灯片", "演示文稿", "汇报单页"]),
|
|
2380
|
+
("ui", ["ui", "界面", "app", "dashboard", "仪表盘", "看板", "saas", "后台", "控制台", "网页", "mockup"]),
|
|
2381
|
+
("diagram", ["架构图", "系统图", "流程架构", "architecture diagram", "system diagram"]),
|
|
2382
|
+
("infographic", ["信息图", "infographic", "图解", "流程图", "时间线"]),
|
|
2383
|
+
("poster", ["海报", "poster", "banner", "主视觉", "kv", "封面"]),
|
|
2384
|
+
("logo", ["logo", "品牌标识", "字标", "visual identity"]),
|
|
2385
|
+
]
|
|
2386
|
+
for asset_type, keywords in explicit_routes:
|
|
2387
|
+
if any(keyword_in_text(kw, lower) for kw in keywords):
|
|
2388
|
+
return asset_type
|
|
547
2389
|
best = ("poster", 0)
|
|
548
2390
|
for asset_type, meta in ASSET_ROUTES.items():
|
|
549
2391
|
score = 0
|
|
550
2392
|
for kw in meta["keywords"]:
|
|
551
|
-
if kw
|
|
2393
|
+
if keyword_in_text(str(kw), lower):
|
|
552
2394
|
score += 1
|
|
553
2395
|
if score > best[1]:
|
|
554
2396
|
best = (asset_type, score)
|
|
@@ -584,10 +2426,10 @@ def infer_quality(request: str, asset_type: str, texts: list[str], override: str
|
|
|
584
2426
|
if override:
|
|
585
2427
|
return override
|
|
586
2428
|
lower = request.lower()
|
|
2429
|
+
if texts or asset_type in {"poster", "ui", "infographic", "slide", "diagram", "logo"}:
|
|
2430
|
+
return "high"
|
|
587
2431
|
if any(k in lower for k in ["草稿", "draft", "探索"]):
|
|
588
2432
|
return "medium"
|
|
589
|
-
if texts or asset_type in {"poster", "ui", "infographic", "diagram", "logo"}:
|
|
590
|
-
return "high"
|
|
591
2433
|
prof = str(profile.get("default_quality") or "").strip()
|
|
592
2434
|
if prof:
|
|
593
2435
|
return prof
|
|
@@ -596,19 +2438,45 @@ def infer_quality(request: str, asset_type: str, texts: list[str], override: str
|
|
|
596
2438
|
|
|
597
2439
|
def extract_required_texts(request: str, explicit_texts: list[str]) -> list[str]:
|
|
598
2440
|
seen: set[str] = set()
|
|
599
|
-
|
|
2441
|
+
candidates: list[tuple[int, str]] = []
|
|
2442
|
+
|
|
2443
|
+
def clean_text(text: str) -> str:
|
|
2444
|
+
text = text.strip(" \t\n\r,,、。;;::.!?!?")
|
|
2445
|
+
text = re.sub(r"^(?:[^::]{0,12}(?:指标卡|卡片|列表|标题|模块|节点|步骤|栏目|分支))[::]", "", text)
|
|
2446
|
+
text = re.sub(r"^(?:顶部|底部|中间|左侧|右侧|上方|下方|首页|页面)?(?:主标题|副标题|标题)\s+", "", text)
|
|
2447
|
+
text = re.sub(r"^(?:问候语|核心卡片|主要按钮|主按钮|按钮)\s+", "", text)
|
|
2448
|
+
text = re.sub(r"^(?:是|为|叫)\s+", "", text)
|
|
2449
|
+
text = re.sub(r"^(?:一个|一枚|一项)?(?:明显的|醒目的|主要的|primary\s+)?(.{1,16})按钮$", r"\1", text, flags=re.IGNORECASE)
|
|
2450
|
+
text = re.sub(r"(?:网格|列表|区域|模块)$", "", text)
|
|
2451
|
+
text = re.sub(r"(?:要)?(?:渲染)?(?:清晰|清楚|可读|明显)$", "", text)
|
|
2452
|
+
text = re.sub(r"(?:这些)?元素$", "", text)
|
|
2453
|
+
text = re.sub(r"(?:[一二三四五六七八九十\d]+个)?步骤$", "", text)
|
|
2454
|
+
if re.search(r"箭头.*关系|关系.*箭头|展示输入输出关系", text):
|
|
2455
|
+
return ""
|
|
2456
|
+
if re.fullmatch(r"[A-Za-z]{1,2}", text) and text.upper() not in {"AI", "UI"}:
|
|
2457
|
+
return ""
|
|
2458
|
+
if text in {"顶部导航", "底部导航", "左侧导航", "右侧导航", "导航栏", "顶部栏", "状态栏", "箭头关系", "箭头", "关系"}:
|
|
2459
|
+
return ""
|
|
2460
|
+
return text.strip(" \t\n\r,,、。;;::.!?!?")
|
|
2461
|
+
|
|
2462
|
+
def add_candidate(start: int, text: str) -> None:
|
|
2463
|
+
text = clean_text(text)
|
|
2464
|
+
key = re.sub(r"\s+", "", text)
|
|
2465
|
+
if 0 < len(text) <= 40 and key not in seen:
|
|
2466
|
+
seen.add(key)
|
|
2467
|
+
candidates.append((start, text))
|
|
600
2468
|
|
|
601
2469
|
def add(text: str) -> None:
|
|
602
|
-
text = text
|
|
2470
|
+
text = clean_text(text)
|
|
603
2471
|
key = re.sub(r"\s+", "", text)
|
|
604
2472
|
if 0 < len(text) <= 40 and key not in seen:
|
|
605
2473
|
seen.add(key)
|
|
606
|
-
|
|
2474
|
+
candidates.append((len(candidates), text))
|
|
607
2475
|
|
|
608
2476
|
for item in explicit_texts:
|
|
609
2477
|
add(item)
|
|
610
2478
|
if explicit_texts:
|
|
611
|
-
return
|
|
2479
|
+
return [text for _, text in candidates]
|
|
612
2480
|
patterns = [
|
|
613
2481
|
r'"([^"\n]{1,40})"',
|
|
614
2482
|
r"'([^'\n]{1,40})'",
|
|
@@ -617,24 +2485,138 @@ def extract_required_texts(request: str, explicit_texts: list[str]) -> list[str]
|
|
|
617
2485
|
r"『([^』\n]{1,40})』",
|
|
618
2486
|
]
|
|
619
2487
|
for pat in patterns:
|
|
620
|
-
for match in re.
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
2488
|
+
for match in re.finditer(pat, request):
|
|
2489
|
+
add_candidate(match.start(1), match.group(1))
|
|
2490
|
+
labeled_single_patterns = [
|
|
2491
|
+
r"(?:主标题|标题|副标题|主题|时间|地点)\s*(?:写上|写|显示|为|是|叫|[::])\s*([^,,。;;\n]{2,40})",
|
|
2492
|
+
r"(?:时间|地点)\s+([^,,。;;\n]{2,40})",
|
|
2493
|
+
r"(?:核心卡片|主要按钮|主按钮|按钮)\s*(?:写上|写|显示|为|是|叫|[::])\s*([^,,。;;\n]{2,30})",
|
|
2494
|
+
r"(?:需要)?包含\s*([A-Za-z][A-Za-z0-9_-]{2,30})\s*字样",
|
|
2495
|
+
r"(?:名为|叫做|名称是|名字叫)\s*([A-Za-z][A-Za-z0-9_-]{2,30})\b",
|
|
2496
|
+
]
|
|
2497
|
+
for pat in labeled_single_patterns:
|
|
2498
|
+
for match in re.finditer(pat, request, flags=re.IGNORECASE):
|
|
2499
|
+
add_candidate(match.start(1), match.group(1))
|
|
2500
|
+
for match in re.finditer(
|
|
2501
|
+
r"问候语\s*(?:写上|写|显示|为|是|叫|[::])?\s*(.+?)(?=,(?:核心|下方|上方|主要|页面|风格)|[。;;\n]|$)",
|
|
2502
|
+
request,
|
|
2503
|
+
flags=re.IGNORECASE,
|
|
2504
|
+
):
|
|
2505
|
+
add_candidate(match.start(1), match.group(1))
|
|
2506
|
+
for match in re.finditer(
|
|
2507
|
+
r"(?:需要)?(?:出现|展示|包含|包括)?(?:文案|文字)\s*(?:写上|写|显示|为|是|[::])\s*([^。;;\n]{2,100})",
|
|
2508
|
+
request,
|
|
2509
|
+
flags=re.IGNORECASE,
|
|
2510
|
+
):
|
|
2511
|
+
value = match.group(1)
|
|
2512
|
+
for part_match in re.finditer(r"[^、;;/|]+", value):
|
|
2513
|
+
add_candidate(match.start(1) + part_match.start(), part_match.group(0))
|
|
2514
|
+
title_patterns = [
|
|
2515
|
+
r"\btitle\s*[::]\s*([^,,。.;;\n]{1,40})",
|
|
2516
|
+
r"(?:Chinese\s+title|title\s+in\s+Chinese)\s*[::]\s*(.+?)(?=\s+(?:Bullet\s+points?|Icon|Column\s+\d+|Bottom\s+area|High\s+contrast)\b|[,,。.;;\n]|$)",
|
|
2517
|
+
r"(?:中文标题|标题)\s*[::]\s*(.+?)(?=\s+(?:要点|图标|栏目|底部)\b|[,,。.;;\n]|$)",
|
|
2518
|
+
]
|
|
2519
|
+
for pat in title_patterns:
|
|
2520
|
+
for match in re.finditer(pat, request, flags=re.IGNORECASE):
|
|
2521
|
+
add_candidate(match.start(1), match.group(1))
|
|
2522
|
+
bullet_patterns = [
|
|
2523
|
+
r"(?:Bullet\s+points?|bullets?)\s*[::]\s*(.+?)(?=\s+(?:Icon|Column\s+\d+|Bottom\s+area|High\s+contrast)\b|[。\n]|$)",
|
|
2524
|
+
r"(?:项目符号|要点|Bullet点)\s*[::]\s*(.+?)(?=\s+(?:图标|栏目|底部)\b|[。\n]|$)",
|
|
2525
|
+
]
|
|
2526
|
+
for pat in bullet_patterns:
|
|
2527
|
+
for match in re.finditer(pat, request, flags=re.IGNORECASE):
|
|
2528
|
+
value = match.group(1)
|
|
2529
|
+
for part_match in re.finditer(r"[^,,、;;]+", value):
|
|
2530
|
+
add_candidate(match.start(1) + part_match.start(), part_match.group(0))
|
|
2531
|
+
text_hint = r"(?:写上|写|显示|品牌名|wordmark|(?<!副)标题)"
|
|
2532
|
+
for match in re.finditer(rf"{text_hint}[::\s]*(?:写上|写|显示|为|是|叫)?[::\s]*([^,,、。;;,.]{{2,30}})", request, flags=re.IGNORECASE):
|
|
2533
|
+
add_candidate(match.start(1), match.group(1))
|
|
2534
|
+
for match in re.finditer(r"(?:文字|文案)\s*(?:写上|写|显示|为|是|[::])\s*([^,,、。;;,.]{2,24})", request, flags=re.IGNORECASE):
|
|
2535
|
+
add_candidate(match.start(1), match.group(1))
|
|
2536
|
+
for match in re.finditer(r"(?:文字|文案)\s*([^,,、。;;,.]{2,24}?)(?:要)?(?:清晰|清楚|可读)", request, flags=re.IGNORECASE):
|
|
2537
|
+
add_candidate(match.start(1), match.group(1))
|
|
2538
|
+
for match in re.finditer(r"\d+(?:\s*/\s*\d+)?\s*元", request):
|
|
2539
|
+
add_candidate(match.start(), match.group(0))
|
|
2540
|
+
candidates.sort(key=lambda item: item[0])
|
|
2541
|
+
return [text for _, text in candidates]
|
|
2542
|
+
|
|
2543
|
+
|
|
2544
|
+
def merge_texts(primary: list[str], extra: list[str]) -> list[str]:
|
|
2545
|
+
merged: list[str] = []
|
|
2546
|
+
seen: set[str] = set()
|
|
2547
|
+
for item in primary + extra:
|
|
2548
|
+
text = item.strip(" \t\n\r,,、。;;::.!?!?")
|
|
2549
|
+
text = re.sub(r"^(?:[^::]{0,12}(?:指标卡|卡片|列表|标题|模块|节点|步骤|栏目|分支))[::]", "", text)
|
|
2550
|
+
text = re.sub(r"^(?:顶部|底部|中间|左侧|右侧|上方|下方|首页|页面)?(?:主标题|副标题|标题)\s+", "", text)
|
|
2551
|
+
text = re.sub(r"^(?:问候语|核心卡片|主要按钮|主按钮|按钮)\s+", "", text)
|
|
2552
|
+
text = re.sub(r"^(?:是|为|叫)\s+", "", text)
|
|
2553
|
+
text = re.sub(r"^(?:一个|一枚|一项)?(?:明显的|醒目的|主要的|primary\s+)?(.{1,16})按钮$", r"\1", text, flags=re.IGNORECASE)
|
|
2554
|
+
text = re.sub(r"(?:网格|列表|区域|模块)$", "", text)
|
|
2555
|
+
text = re.sub(r"(?:要)?(?:渲染)?(?:清晰|清楚|可读|明显)$", "", text)
|
|
2556
|
+
text = re.sub(r"(?:这些)?元素$", "", text)
|
|
2557
|
+
text = re.sub(r"(?:[一二三四五六七八九十\d]+个)?步骤$", "", text)
|
|
2558
|
+
text = text.strip(" \t\n\r,,、。;;::.!?!?")
|
|
2559
|
+
if re.search(r"箭头.*关系|关系.*箭头|展示输入输出关系", text):
|
|
2560
|
+
continue
|
|
2561
|
+
if re.fullmatch(r"[A-Za-z]{1,2}", text) and text.upper() not in {"AI", "UI"}:
|
|
2562
|
+
continue
|
|
2563
|
+
if text in {"顶部导航", "底部导航", "左侧导航", "右侧导航", "导航栏", "顶部栏", "状态栏", "箭头关系", "箭头", "关系"}:
|
|
2564
|
+
continue
|
|
2565
|
+
key = re.sub(r"\s+", "", text)
|
|
2566
|
+
if text and key not in seen:
|
|
2567
|
+
seen.add(key)
|
|
2568
|
+
merged.append(text)
|
|
2569
|
+
return merged
|
|
628
2570
|
|
|
629
2571
|
|
|
630
|
-
def
|
|
2572
|
+
def extract_structural_labels(request: str, asset_type: str) -> list[str]:
|
|
2573
|
+
if asset_type not in {"diagram", "infographic", "slide", "ui", "poster"}:
|
|
2574
|
+
return []
|
|
2575
|
+
candidates: list[tuple[int, str]] = []
|
|
2576
|
+
list_intro = r"(?:需要)?(?:展示|呈现|列出|包含|包括|含有|分为|覆盖)"
|
|
2577
|
+
stop_words = r"(?:\b16\s*:\s*9\b|\b9\s*:\s*16\b|\b3\s*:\s*4\b|\b1\s*:\s*1\b|适合|用于|画幅|aspect|高质量|高清|clean|corporate)"
|
|
2578
|
+
patterns = [
|
|
2579
|
+
rf"{list_intro}\s*(?:这些|以下|对应的)?(?:模块|部分|层|栏目|节点|入口|能力|场景|列表|指标卡|卡片|步骤|分支)?[::\s]*([^。;;\n]{{2,180}})",
|
|
2580
|
+
rf"(?:模块|节点|栏目|部分|层|入口|能力|场景|列表|指标卡|卡片|步骤|分支)\s*(?:包括|包含|有|为)[::\s]*([^。;;\n]{{2,180}})",
|
|
2581
|
+
rf"(?:模块|节点|栏目|部分|层|入口|能力|场景|列表|指标卡|卡片|步骤|分支)[^。;;\n::]{{0,16}}[::]([^。;;\n]{{2,180}})",
|
|
2582
|
+
]
|
|
2583
|
+
for pattern in patterns:
|
|
2584
|
+
for match in re.finditer(pattern, request, flags=re.IGNORECASE):
|
|
2585
|
+
value = re.split(stop_words, match.group(1), maxsplit=1, flags=re.IGNORECASE)[0]
|
|
2586
|
+
for part_match in re.finditer(r"[^、,,;;/|]+", value):
|
|
2587
|
+
part = part_match.group(0).strip(" \t\n\r,,、::")
|
|
2588
|
+
part = re.sub(r"^(?:[^::]{0,12}(?:指标卡|卡片|列表|标题|模块|节点|步骤|栏目|分支))[::]", "", part)
|
|
2589
|
+
part = re.sub(r"^(?:和|与|及|以及|and)\s*", "", part, flags=re.IGNORECASE).strip()
|
|
2590
|
+
part = re.sub(r"\s*(?:和|与|及|以及|and)$", "", part, flags=re.IGNORECASE).strip()
|
|
2591
|
+
part = re.sub(r"(?:这些)?元素$", "", part)
|
|
2592
|
+
part = re.sub(r"(?:[一二三四五六七八九十\d]+个)?步骤$", "", part)
|
|
2593
|
+
part = part.strip(" \t\n\r,,、::")
|
|
2594
|
+
if not part:
|
|
2595
|
+
continue
|
|
2596
|
+
if re.fullmatch(r"\d+(?:\s*:\s*\d+)?", part):
|
|
2597
|
+
continue
|
|
2598
|
+
if len(part) > 36:
|
|
2599
|
+
continue
|
|
2600
|
+
if not re.search(r"[\u4e00-\u9fffA-Za-z]", part):
|
|
2601
|
+
continue
|
|
2602
|
+
candidates.append((match.start(1) + part_match.start(), part))
|
|
2603
|
+
candidates.sort(key=lambda item: item[0])
|
|
2604
|
+
return merge_texts([], [text for _, text in candidates])
|
|
2605
|
+
|
|
2606
|
+
|
|
2607
|
+
def infer_style_anchors(request: str, override: str | None, profile: dict, preset: str | None = None) -> list[str]:
|
|
631
2608
|
anchors: list[str] = []
|
|
632
|
-
if override:
|
|
633
|
-
anchors.extend(split_csv(override))
|
|
634
2609
|
lower = request.lower()
|
|
635
2610
|
for keys, anchor in STYLE_HINTS:
|
|
636
|
-
if any(k.lower()
|
|
2611
|
+
if any(keyword_in_text(k.lower(), lower) for k in keys):
|
|
637
2612
|
anchors.append(anchor)
|
|
2613
|
+
if override:
|
|
2614
|
+
anchors.extend(split_csv(override))
|
|
2615
|
+
profile_preset = str(profile.get("default_style_preset") or "").strip()
|
|
2616
|
+
chosen_preset = (preset or profile_preset or "auto").strip()
|
|
2617
|
+
for item in STYLE_PRESETS.get(chosen_preset, []):
|
|
2618
|
+
if item and item not in anchors:
|
|
2619
|
+
anchors.append(item)
|
|
638
2620
|
favored = profile.get("favored_styles") or []
|
|
639
2621
|
if isinstance(favored, str):
|
|
640
2622
|
favored = split_csv(favored)
|
|
@@ -646,12 +2628,19 @@ def infer_style_anchors(request: str, override: str | None, profile: dict) -> li
|
|
|
646
2628
|
return anchors[:4]
|
|
647
2629
|
|
|
648
2630
|
|
|
649
|
-
def infer_negative(asset_type: str, texts: list[str], profile: dict) -> list[str]:
|
|
650
|
-
negative = ["avoid vague generic AI gloss", "avoid clutter"]
|
|
2631
|
+
def infer_negative(asset_type: str, texts: list[str], profile: dict, request: str = "", template_id: str = "") -> list[str]:
|
|
2632
|
+
negative = ["avoid vague generic AI gloss", "avoid clutter", "avoid adding content not requested by the user"]
|
|
651
2633
|
if texts:
|
|
652
2634
|
negative.append("avoid garbled or wrong text")
|
|
653
|
-
if asset_type in {"poster", "ui", "infographic", "diagram", "logo"}:
|
|
2635
|
+
if asset_type in {"poster", "ui", "infographic", "slide", "diagram", "logo"}:
|
|
654
2636
|
negative.append("avoid fake logos and unreadable microtext")
|
|
2637
|
+
if asset_type == "slide":
|
|
2638
|
+
negative.extend(["avoid adding modules outside the user brief", "avoid changing the requested section structure"])
|
|
2639
|
+
if template_id == "poster_social_cover":
|
|
2640
|
+
lower = request.lower()
|
|
2641
|
+
people_terms = ["人物", "角色", "人像", "头像", "真人", "女孩", "男孩", "人类", "mascot", "avatar", "person", "people", "character", "portrait"]
|
|
2642
|
+
if not any(keyword_in_text(term, lower) for term in people_terms):
|
|
2643
|
+
negative.append("avoid unrequested people, faces, avatars, mascots, or character illustrations")
|
|
655
2644
|
if asset_type == "photography":
|
|
656
2645
|
negative.extend(["avoid HDR over-processing", "avoid plastic skin"])
|
|
657
2646
|
avoided = profile.get("avoided_elements") or []
|
|
@@ -671,9 +2660,11 @@ def infer_tags(asset_type: str, request: str, extra: str | None = None) -> list[
|
|
|
671
2660
|
"tea": ["茶", "茶饮", "冷泡"],
|
|
672
2661
|
"brand": ["品牌", "logo", "标识"],
|
|
673
2662
|
"promo": ["促销", "价格", "优惠", "新品"],
|
|
2663
|
+
"presentation": ["ppt", "powerpoint", "slide", "presentation", "汇报", "幻灯片"],
|
|
674
2664
|
"academic": ["论文", "学术", "系统", "模型"],
|
|
675
2665
|
}.items():
|
|
676
|
-
|
|
2666
|
+
lower = request.lower()
|
|
2667
|
+
if any(keyword_in_text(v, lower) for v in vals) and key not in tags:
|
|
677
2668
|
tags.append(key)
|
|
678
2669
|
for item in split_csv(extra):
|
|
679
2670
|
if item not in tags:
|
|
@@ -687,19 +2678,29 @@ def infer_template_id(request: str, asset_type: str, override: str | None = None
|
|
|
687
2678
|
lower = request.lower()
|
|
688
2679
|
if asset_type == "diagram" and "rag" in lower:
|
|
689
2680
|
return "diagram_rag"
|
|
2681
|
+
if asset_type == "poster":
|
|
2682
|
+
if any(keyword_in_text(kw, lower) for kw in ["小红书", "社媒", "方图", "封面", "cover", "social"]):
|
|
2683
|
+
return "poster_social_cover"
|
|
2684
|
+
if any(keyword_in_text(kw, lower) for kw in ["活动", "会议", "发布会", "展览", "event", "workshop", "讲座"]):
|
|
2685
|
+
return "poster_event"
|
|
2686
|
+
if any(keyword_in_text(kw, lower) for kw in ["品牌", "主视觉", "kv", "brand key visual"]):
|
|
2687
|
+
return "poster_brand_kv"
|
|
2688
|
+
if any(keyword_in_text(kw, lower) for kw in ["信息", "清单", "流程", "说明"]):
|
|
2689
|
+
return "poster_info_dense"
|
|
690
2690
|
candidates = {tid: meta for tid, meta in TEMPLATE_DEFS.items() if meta["asset_type"] == asset_type}
|
|
691
2691
|
best_id = ""
|
|
692
2692
|
best_score = -1
|
|
693
2693
|
for tid, meta in candidates.items():
|
|
694
|
-
score = sum(1 for kw in meta["keywords"] if kw
|
|
2694
|
+
score = sum(1 for kw in meta["keywords"] if keyword_in_text(str(kw), lower))
|
|
695
2695
|
if score > best_score:
|
|
696
2696
|
best_id = tid
|
|
697
2697
|
best_score = score
|
|
698
2698
|
if best_score > 0 and best_id:
|
|
699
2699
|
return best_id
|
|
700
2700
|
defaults = {
|
|
701
|
-
"poster": "
|
|
702
|
-
"ui": "
|
|
2701
|
+
"poster": "poster_general",
|
|
2702
|
+
"ui": "ui_requested_screen",
|
|
2703
|
+
"slide": "slide_corporate_report",
|
|
703
2704
|
"diagram": "diagram_system",
|
|
704
2705
|
"product": "product_hero",
|
|
705
2706
|
"illustration": "illustration_scene",
|
|
@@ -714,15 +2715,85 @@ def infer_layout(template_id: str, asset_type: str) -> str:
|
|
|
714
2715
|
"photography": "single realistic capture with foreground, subject, and environmental context",
|
|
715
2716
|
"character": "reference sheet grid with turnaround, expressions, details, and palette",
|
|
716
2717
|
"logo": "brand board grid with mark, wordmark, palette, type sample, and applications",
|
|
717
|
-
"
|
|
2718
|
+
"slide": "widescreen presentation slide that preserves the user's explicit regions, hierarchy, and reading order",
|
|
2719
|
+
"infographic": "preserve the requested information units, relationships, section count, and reading order",
|
|
718
2720
|
}
|
|
719
2721
|
return fallback.get(asset_type, "clear composition with named regions and stable visual hierarchy")
|
|
720
2722
|
|
|
721
2723
|
|
|
2724
|
+
CHINESE_ORDINALS = {
|
|
2725
|
+
"一": 1,
|
|
2726
|
+
"二": 2,
|
|
2727
|
+
"两": 2,
|
|
2728
|
+
"三": 3,
|
|
2729
|
+
"四": 4,
|
|
2730
|
+
"五": 5,
|
|
2731
|
+
"六": 6,
|
|
2732
|
+
"七": 7,
|
|
2733
|
+
"八": 8,
|
|
2734
|
+
"九": 9,
|
|
2735
|
+
"十": 10,
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
|
|
2739
|
+
def parse_ordinal(value: str) -> int | None:
|
|
2740
|
+
value = value.strip()
|
|
2741
|
+
if value.isdigit():
|
|
2742
|
+
return int(value)
|
|
2743
|
+
if value in CHINESE_ORDINALS:
|
|
2744
|
+
return CHINESE_ORDINALS[value]
|
|
2745
|
+
return None
|
|
2746
|
+
|
|
2747
|
+
|
|
2748
|
+
def infer_slide_column_count(request: str) -> int | None:
|
|
2749
|
+
lower = request.lower()
|
|
2750
|
+
if any(k in lower for k in ["three columns", "3 columns", "三栏", "三列", "三等分"]):
|
|
2751
|
+
return 3
|
|
2752
|
+
if any(k in lower for k in ["two columns", "2 columns", "双栏", "双列", "两栏", "两列"]):
|
|
2753
|
+
return 2
|
|
2754
|
+
max_col = 0
|
|
2755
|
+
for match in re.finditer(r"\bcolumn\s*(\d+)\b", lower):
|
|
2756
|
+
max_col = max(max_col, int(match.group(1)))
|
|
2757
|
+
for match in re.finditer(r"(?:第|栏目|栏|列)\s*([一二两三四五六七八九十\d]+)\s*(?:栏|列|部分|模块)?", request):
|
|
2758
|
+
parsed = parse_ordinal(match.group(1))
|
|
2759
|
+
if parsed:
|
|
2760
|
+
max_col = max(max_col, parsed)
|
|
2761
|
+
return max_col or None
|
|
2762
|
+
|
|
2763
|
+
|
|
2764
|
+
def slide_column_before(request: str, pos: int) -> int | None:
|
|
2765
|
+
prefix = request[:pos]
|
|
2766
|
+
matches: list[tuple[int, int]] = []
|
|
2767
|
+
for match in re.finditer(r"\bcolumn\s*(\d+)\b", prefix, flags=re.IGNORECASE):
|
|
2768
|
+
matches.append((match.start(), int(match.group(1)) - 1))
|
|
2769
|
+
for match in re.finditer(r"(?:第|栏目|栏|列)\s*([一二两三四五六七八九十\d]+)\s*(?:栏|列|部分|模块)?", prefix):
|
|
2770
|
+
parsed = parse_ordinal(match.group(1))
|
|
2771
|
+
if parsed:
|
|
2772
|
+
matches.append((match.start(), parsed - 1))
|
|
2773
|
+
if not matches:
|
|
2774
|
+
return None
|
|
2775
|
+
return max(matches, key=lambda item: item[0])[1]
|
|
2776
|
+
|
|
2777
|
+
|
|
2778
|
+
def text_positions_in_request(texts: list[str], request: str) -> list[int]:
|
|
2779
|
+
positions: list[int] = []
|
|
2780
|
+
cursor = 0
|
|
2781
|
+
for text in texts:
|
|
2782
|
+
pos = request.find(text, cursor)
|
|
2783
|
+
if pos < 0:
|
|
2784
|
+
pos = request.find(text)
|
|
2785
|
+
positions.append(pos)
|
|
2786
|
+
if pos >= 0:
|
|
2787
|
+
cursor = pos + len(text)
|
|
2788
|
+
return positions
|
|
2789
|
+
|
|
2790
|
+
|
|
722
2791
|
def infer_text_hierarchy(asset_type: str, texts: list[str], request: str) -> list[dict]:
|
|
723
2792
|
if not texts:
|
|
724
2793
|
return []
|
|
725
2794
|
roles = []
|
|
2795
|
+
positions = text_positions_in_request(texts, request)
|
|
2796
|
+
slide_rows: dict[int, int] = {}
|
|
726
2797
|
for idx, text in enumerate(texts):
|
|
727
2798
|
role = "label"
|
|
728
2799
|
area = "content area"
|
|
@@ -735,20 +2806,54 @@ def infer_text_hierarchy(asset_type: str, texts: list[str], request: str) -> lis
|
|
|
735
2806
|
else:
|
|
736
2807
|
role, area, priority = "supporting_copy", "secondary copy zone", "medium"
|
|
737
2808
|
elif asset_type == "ui":
|
|
738
|
-
|
|
2809
|
+
if re.search(r"(?:产品(?:叫|名)|app\s+name|应用名|品牌名)[^。;;\n]{0,20}" + re.escape(text), request, flags=re.IGNORECASE):
|
|
2810
|
+
role = "app_name"
|
|
2811
|
+
else:
|
|
2812
|
+
role = "ui_label"
|
|
2813
|
+
area, priority = "relevant UI component", "high"
|
|
739
2814
|
elif asset_type == "diagram":
|
|
740
2815
|
role, area, priority = "component_label", "inside its corresponding node box", "high"
|
|
2816
|
+
elif asset_type == "slide":
|
|
2817
|
+
if idx == 0:
|
|
2818
|
+
role, area, priority = "slide_title", "title band", "high"
|
|
2819
|
+
else:
|
|
2820
|
+
pos = positions[idx]
|
|
2821
|
+
context = request[max(0, pos - 80) : pos].lower() if pos >= 0 else ""
|
|
2822
|
+
column = slide_column_before(request, pos) if pos >= 0 else None
|
|
2823
|
+
if column is None:
|
|
2824
|
+
column = max(0, (idx - 1) % max(1, infer_slide_column_count(request) or 3))
|
|
2825
|
+
row = slide_rows.get(column, 0)
|
|
2826
|
+
slide_rows[column] = row + 1
|
|
2827
|
+
title_marker = max(context.rfind("title in chinese"), context.rfind("chinese title"), context.rfind("标题"))
|
|
2828
|
+
bullet_marker = max(context.rfind("bullet"), context.rfind("要点"), context.rfind("项目符号"))
|
|
2829
|
+
if title_marker > bullet_marker:
|
|
2830
|
+
role, priority = "slide_section_title", "high"
|
|
2831
|
+
else:
|
|
2832
|
+
role, priority = "slide_bullet", "medium"
|
|
2833
|
+
area = f"column {column + 1}, row {row + 1}"
|
|
2834
|
+
roles.append(
|
|
2835
|
+
{
|
|
2836
|
+
"text": text,
|
|
2837
|
+
"role": role,
|
|
2838
|
+
"area": area,
|
|
2839
|
+
"priority": priority,
|
|
2840
|
+
"column_index": column,
|
|
2841
|
+
"row_in_column": row,
|
|
2842
|
+
}
|
|
2843
|
+
)
|
|
2844
|
+
continue
|
|
741
2845
|
elif asset_type == "logo":
|
|
742
2846
|
role, area, priority = ("wordmark" if idx == 0 else "brand_label", "brand board", "high")
|
|
743
2847
|
roles.append({"text": text, "role": role, "area": area, "priority": priority})
|
|
744
2848
|
return roles
|
|
745
2849
|
|
|
746
2850
|
|
|
747
|
-
def infer_must_include(asset_type: str, template_id: str, texts: list[str]) -> list[str]:
|
|
2851
|
+
def infer_must_include(asset_type: str, template_id: str, texts: list[str], strict_text: bool = False) -> list[str]:
|
|
748
2852
|
base = {
|
|
749
|
-
"poster": ["main visual subject", "readable title
|
|
750
|
-
"ui": ["
|
|
751
|
-
"infographic": ["
|
|
2853
|
+
"poster": ["main visual subject", "readable title hierarchy", "clear negative space"],
|
|
2854
|
+
"ui": ["requested screen type", "requested UI sections", "clear component hierarchy"],
|
|
2855
|
+
"infographic": ["requested information units", "requested relationships", "clear visual hierarchy"],
|
|
2856
|
+
"slide": ["widescreen slide canvas", "all requested sections", "requested visual motifs", "crisp readable text hierarchy"],
|
|
752
2857
|
"diagram": ["labeled components", "directional arrows", "legend or flow semantics"],
|
|
753
2858
|
"product": ["single hero product", "visible material texture", "controlled studio lighting"],
|
|
754
2859
|
"photography": ["realistic subject", "specific scene details", "natural imperfections"],
|
|
@@ -758,8 +2863,10 @@ def infer_must_include(asset_type: str, template_id: str, texts: list[str]) -> l
|
|
|
758
2863
|
}.get(asset_type, ["main subject", "clear visual hierarchy"])
|
|
759
2864
|
if template_id == "diagram_rag":
|
|
760
2865
|
base = ["User node", "Retriever node", "Vector DB node", "LLM node", "Answer node", "left-to-right arrows"]
|
|
2866
|
+
if template_id == "poster_zh_promo":
|
|
2867
|
+
base = ["main visual subject", "readable title/offer hierarchy", "clear negative space"]
|
|
761
2868
|
if texts:
|
|
762
|
-
base.append("
|
|
2869
|
+
base.append("clean blank copy areas for later overlay" if strict_text else "exact readable text from the brief")
|
|
763
2870
|
return base
|
|
764
2871
|
|
|
765
2872
|
|
|
@@ -768,12 +2875,15 @@ def infer_acceptance_criteria(spec: dict) -> list[str]:
|
|
|
768
2875
|
f"Image uses {spec['aspect']} composition and matches {spec['asset_type']} intent.",
|
|
769
2876
|
"Main subject is visible and matches the request.",
|
|
770
2877
|
"Composition follows the named layout without incoherent overlap.",
|
|
2878
|
+
"No subjects, modules, text, relationships, or narrative elements are added beyond the user brief.",
|
|
771
2879
|
"No fake logos, garbled filler text, or unrelated decorative clutter.",
|
|
772
2880
|
]
|
|
773
2881
|
if spec.get("required_text"):
|
|
774
2882
|
criteria.append("Every required text string appears exactly once, unchanged, and readable.")
|
|
775
|
-
if spec["asset_type"] in {"diagram", "ui", "infographic"}:
|
|
2883
|
+
if spec["asset_type"] in {"diagram", "ui", "infographic", "slide"}:
|
|
776
2884
|
criteria.append("Labels are large enough to read and aligned to their components.")
|
|
2885
|
+
if spec["asset_type"] == "slide":
|
|
2886
|
+
criteria.append("The slide preserves the user's requested information architecture without adding unrelated modules.")
|
|
777
2887
|
if spec["asset_type"] == "product":
|
|
778
2888
|
criteria.append("Product material and silhouette are clear, with no CGI-plastic tell.")
|
|
779
2889
|
if spec.get("strict_text"):
|
|
@@ -783,6 +2893,8 @@ def infer_acceptance_criteria(spec: dict) -> list[str]:
|
|
|
783
2893
|
|
|
784
2894
|
def overlay_align_hint(item: dict) -> str:
|
|
785
2895
|
role = str(item.get("role") or "")
|
|
2896
|
+
if role == "slide_bullet":
|
|
2897
|
+
return "left"
|
|
786
2898
|
if role in {"price_or_offer", "supporting_copy"}:
|
|
787
2899
|
return "center"
|
|
788
2900
|
if role in {"component_label", "ui_label"}:
|
|
@@ -813,6 +2925,22 @@ def overlay_box_hint(spec: dict, item: dict, idx: int, total: int) -> list[float
|
|
|
813
2925
|
if idx == 0:
|
|
814
2926
|
return [0.08, 0.06, 0.84, 0.12]
|
|
815
2927
|
return [0.10, 0.22 + (idx - 1) * 0.14, 0.32, 0.09]
|
|
2928
|
+
if asset_type == "slide":
|
|
2929
|
+
if role == "slide_title" or idx == 0:
|
|
2930
|
+
return [0.08, 0.04, 0.84, 0.12]
|
|
2931
|
+
cols = max(1, min(4, int(spec.get("layout_column_count") or 3)))
|
|
2932
|
+
col = int(item.get("column_index") if item.get("column_index") is not None else max(0, idx - 1) % cols)
|
|
2933
|
+
row = int(item.get("row_in_column") if item.get("row_in_column") is not None else max(0, idx - 1) // cols)
|
|
2934
|
+
col = max(0, min(cols - 1, col))
|
|
2935
|
+
col_w = min(0.28, 0.84 / cols - 0.02)
|
|
2936
|
+
gap = (0.84 - col_w * cols) / max(1, cols - 1) if cols > 1 else 0
|
|
2937
|
+
x = 0.08 + col * (col_w + gap)
|
|
2938
|
+
if role == "slide_bullet":
|
|
2939
|
+
x += min(0.045, col_w * 0.18)
|
|
2940
|
+
col_w -= min(0.045, col_w * 0.18)
|
|
2941
|
+
y = 0.255 + row * 0.068
|
|
2942
|
+
height = 0.072 if role == "slide_section_title" else 0.055
|
|
2943
|
+
return [x, y, col_w, height]
|
|
816
2944
|
if asset_type == "logo":
|
|
817
2945
|
return [0.12, 0.72, 0.76, 0.12]
|
|
818
2946
|
if asset_type == "character":
|
|
@@ -852,20 +2980,26 @@ def build_text_overlay_spec(spec: dict) -> dict:
|
|
|
852
2980
|
|
|
853
2981
|
def build_spec(args: argparse.Namespace) -> dict:
|
|
854
2982
|
request = args.request_text
|
|
855
|
-
|
|
2983
|
+
visual_request = strip_nonvisual_request_meta(request)
|
|
2984
|
+
safe_request, safety_notes = sanitize_reference_risks(visual_request)
|
|
856
2985
|
profile, _ = read_profile()
|
|
857
2986
|
asset_type = route_asset_type(safe_request, getattr(args, "asset_type", None))
|
|
858
|
-
|
|
859
|
-
|
|
2987
|
+
explicit_texts = getattr(args, "text", None) or []
|
|
2988
|
+
texts = extract_required_texts(visual_request, explicit_texts)
|
|
2989
|
+
if not explicit_texts:
|
|
2990
|
+
texts = merge_texts(texts, extract_structural_labels(visual_request, asset_type))
|
|
2991
|
+
aspect = infer_aspect(visual_request, asset_type, getattr(args, "aspect", None), profile)
|
|
860
2992
|
size = infer_size(aspect, getattr(args, "size", None), asset_type)
|
|
861
|
-
quality = infer_quality(
|
|
2993
|
+
quality = infer_quality(visual_request, asset_type, texts, getattr(args, "quality", None), profile)
|
|
862
2994
|
subject = getattr(args, "subject", None) or safe_request
|
|
863
2995
|
template_id = infer_template_id(safe_request, asset_type, getattr(args, "template", None))
|
|
864
|
-
negative = list(dict.fromkeys(infer_negative(asset_type, texts, profile) + safety_avoid_list(safety_notes)))
|
|
2996
|
+
negative = list(dict.fromkeys(infer_negative(asset_type, texts, profile, safe_request, template_id) + safety_avoid_list(safety_notes)))
|
|
2997
|
+
style_preset = getattr(args, "style_preset", None) or str(profile.get("default_style_preset") or "auto")
|
|
865
2998
|
spec = {
|
|
866
2999
|
"schema_version": SCHEMA_VERSION,
|
|
867
3000
|
"compiler_version": COMPILER_VERSION,
|
|
868
3001
|
"request": request,
|
|
3002
|
+
"visual_request": visual_request,
|
|
869
3003
|
"safe_request": safe_request,
|
|
870
3004
|
"safety_rewrite": safety_notes,
|
|
871
3005
|
"asset_type": asset_type,
|
|
@@ -877,17 +3011,21 @@ def build_spec(args: argparse.Namespace) -> dict:
|
|
|
877
3011
|
"quality": quality,
|
|
878
3012
|
"subject": subject,
|
|
879
3013
|
"required_text": texts,
|
|
3014
|
+
"required_text_source": "explicit" if explicit_texts else "request",
|
|
880
3015
|
"strict_text": bool(getattr(args, "strict_text", False)),
|
|
3016
|
+
"prompt_mode": "strict_text_overlay" if bool(getattr(args, "strict_text", False)) else "single_pass",
|
|
881
3017
|
"layout": getattr(args, "layout", None) or infer_layout(template_id, asset_type),
|
|
882
|
-
"text_hierarchy": infer_text_hierarchy(asset_type, texts,
|
|
883
|
-
"
|
|
3018
|
+
"text_hierarchy": infer_text_hierarchy(asset_type, texts, visual_request),
|
|
3019
|
+
"layout_column_count": infer_slide_column_count(visual_request) if asset_type == "slide" else None,
|
|
3020
|
+
"style_preset": style_preset,
|
|
3021
|
+
"style_anchors": infer_style_anchors(safe_request, getattr(args, "style", None), profile, style_preset),
|
|
884
3022
|
"materials": split_csv(getattr(args, "materials", None)) or ["tactile, specific visible materials chosen for the subject"],
|
|
885
3023
|
"lighting": getattr(args, "lighting", None) or "controlled, readable light with clear subject hierarchy",
|
|
886
3024
|
"palette": split_csv(getattr(args, "palette", None)) or ["restrained palette matched to the asset type"],
|
|
887
3025
|
"negative": negative,
|
|
888
|
-
"must_include": infer_must_include(asset_type, template_id, texts),
|
|
3026
|
+
"must_include": infer_must_include(asset_type, template_id, texts, bool(getattr(args, "strict_text", False))),
|
|
889
3027
|
"must_avoid": negative,
|
|
890
|
-
"tags": infer_tags(asset_type,
|
|
3028
|
+
"tags": infer_tags(asset_type, visual_request, getattr(args, "tags", None)),
|
|
891
3029
|
}
|
|
892
3030
|
spec["acceptance_criteria"] = infer_acceptance_criteria(spec)
|
|
893
3031
|
if spec["strict_text"]:
|
|
@@ -905,6 +3043,17 @@ def exact_text_block(texts: list[str]) -> str:
|
|
|
905
3043
|
)
|
|
906
3044
|
|
|
907
3045
|
|
|
3046
|
+
def redact_required_texts(text: str, texts: list[str]) -> str:
|
|
3047
|
+
redacted = text
|
|
3048
|
+
for idx, item in enumerate(texts, start=1):
|
|
3049
|
+
if item:
|
|
3050
|
+
redacted = redacted.replace(item, "")
|
|
3051
|
+
redacted = re.sub(r"\s+([,,。.;;])", r"\1", redacted)
|
|
3052
|
+
redacted = re.sub(r"([::])\s*([,,。.;;])", r"\1", redacted)
|
|
3053
|
+
redacted = re.sub(r"\s{2,}", " ", redacted)
|
|
3054
|
+
return redacted
|
|
3055
|
+
|
|
3056
|
+
|
|
908
3057
|
def reserved_text_block(spec: dict) -> str:
|
|
909
3058
|
if not spec.get("required_text"):
|
|
910
3059
|
return "No required in-image text unless explicitly useful; avoid decorative fake text."
|
|
@@ -912,54 +3061,93 @@ def reserved_text_block(spec: dict) -> str:
|
|
|
912
3061
|
return (
|
|
913
3062
|
"Strict text mode: do not render the exact copy in the generated image. "
|
|
914
3063
|
f"Reserve clean, high-contrast text areas for later overlay ({roles}). "
|
|
915
|
-
"
|
|
3064
|
+
"Leave those areas visually empty: no placeholder words, no brackets, no lorem ipsum, no dummy text, and no fake characters."
|
|
916
3065
|
)
|
|
917
3066
|
|
|
918
3067
|
|
|
919
3068
|
def render_visual_prompt(spec: dict) -> str:
|
|
920
3069
|
visual_spec = dict(spec)
|
|
3070
|
+
visual_spec["prompt_mode"] = "strict_text_overlay_background"
|
|
921
3071
|
visual_spec["required_text"] = []
|
|
3072
|
+
visual_spec["subject"] = redact_required_texts(str(visual_spec.get("subject") or ""), spec.get("required_text", []))
|
|
3073
|
+
visual_spec["request"] = redact_required_texts(str(visual_spec.get("request") or ""), spec.get("required_text", []))
|
|
3074
|
+
visual_spec["safe_request"] = redact_required_texts(str(visual_spec.get("safe_request") or ""), spec.get("required_text", []))
|
|
922
3075
|
prompt = render_prompt(visual_spec)
|
|
923
3076
|
return prompt + "\n" + reserved_text_block(spec)
|
|
924
3077
|
|
|
925
3078
|
|
|
3079
|
+
def intent_preservation_block(spec: dict) -> str:
|
|
3080
|
+
return (
|
|
3081
|
+
"Intent preservation: treat the user's brief as the source of truth. "
|
|
3082
|
+
"Enhance visual quality, clarity, composition, materials, lighting, palette, and execution detail only. "
|
|
3083
|
+
"Do not reinterpret the goal, change the subject, change the requested layout/order, or add new modules, "
|
|
3084
|
+
"objects, scenes, brands, charts, text, people, or narrative elements unless they are explicitly requested "
|
|
3085
|
+
"or directly implied by the brief. Template guidance must be adapted to the brief and omitted when not applicable."
|
|
3086
|
+
)
|
|
3087
|
+
|
|
3088
|
+
|
|
3089
|
+
def original_brief_block(spec: dict) -> str:
|
|
3090
|
+
brief = str(spec.get("safe_request") or spec.get("visual_request") or spec.get("request") or "").strip()
|
|
3091
|
+
return "User visual brief (preserve verbatim; do not rewrite or reinterpret):\n" + brief
|
|
3092
|
+
|
|
3093
|
+
|
|
3094
|
+
def style_quality_block(spec: dict, *, label: str, extra: list[str] | None = None) -> str:
|
|
3095
|
+
style = "; ".join(spec["style_anchors"])
|
|
3096
|
+
materials = ", ".join(spec["materials"])
|
|
3097
|
+
palette = ", ".join(spec["palette"])
|
|
3098
|
+
lines = [
|
|
3099
|
+
f"Style / quality envelope for {label}: {style}.",
|
|
3100
|
+
"This envelope controls rendering quality and visual language only; if it conflicts with the user brief, the user brief wins.",
|
|
3101
|
+
f"Quality controls: {materials}; lighting/rendering: {spec['lighting']}; palette discipline: {palette}.",
|
|
3102
|
+
]
|
|
3103
|
+
for item in extra or []:
|
|
3104
|
+
if item:
|
|
3105
|
+
lines.append(item)
|
|
3106
|
+
return "\n".join(lines)
|
|
3107
|
+
|
|
3108
|
+
|
|
926
3109
|
def render_prompt(spec: dict) -> str:
|
|
927
3110
|
if spec.get("strict_text") and spec.get("required_text"):
|
|
928
3111
|
return render_visual_prompt(spec)
|
|
929
3112
|
asset_type = spec["asset_type"]
|
|
930
|
-
style = "; ".join(spec["style_anchors"])
|
|
931
|
-
materials = ", ".join(spec["materials"])
|
|
932
|
-
palette = ", ".join(spec["palette"])
|
|
933
3113
|
negative = "; ".join(spec["negative"])
|
|
934
3114
|
must_include = ", ".join(spec.get("must_include", []))
|
|
935
3115
|
text_block = exact_text_block(spec["required_text"])
|
|
936
3116
|
aspect = spec["aspect"]
|
|
937
|
-
subject = spec["subject"]
|
|
938
3117
|
layout = spec.get("layout", "clear composition with named regions")
|
|
3118
|
+
intent_block = intent_preservation_block(spec)
|
|
3119
|
+
brief_block = original_brief_block(spec)
|
|
939
3120
|
|
|
940
3121
|
if asset_type == "poster":
|
|
3122
|
+
hierarchy_guidance = (
|
|
3123
|
+
"Promotional information hierarchy must pass the three-glance test: silhouette first, key message second, texture/details third."
|
|
3124
|
+
if spec.get("template_id") == "poster_zh_promo"
|
|
3125
|
+
else "Visual hierarchy must pass the three-glance test: key message first, requested supporting content second, texture/details third."
|
|
3126
|
+
)
|
|
941
3127
|
return "\n".join(
|
|
942
3128
|
[
|
|
943
|
-
f"
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
f"
|
|
3129
|
+
f"Create a {aspect} poster from the user visual brief below.",
|
|
3130
|
+
brief_block,
|
|
3131
|
+
style_quality_block(spec, label="poster", extra=["Use a strong layout grid, clear hierarchy, and enough negative space for a readable commercial poster."]),
|
|
3132
|
+
f"Composition support: {spec.get('template_label', 'poster')}; layout guidance: {layout}.",
|
|
3133
|
+
intent_block,
|
|
3134
|
+
f"Main subject and scene density: make the core subject specific and visible, using only relevant supporting details from the brief.",
|
|
947
3135
|
f"Must include: {must_include}.",
|
|
948
|
-
f"Materials: {materials}. Lighting: {spec['lighting']}. Palette: {palette}.",
|
|
949
3136
|
text_block,
|
|
950
|
-
|
|
3137
|
+
hierarchy_guidance,
|
|
951
3138
|
f"Avoid: {negative}.",
|
|
952
3139
|
]
|
|
953
3140
|
)
|
|
954
3141
|
if asset_type == "ui":
|
|
955
3142
|
return "\n".join(
|
|
956
3143
|
[
|
|
957
|
-
f"
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
"
|
|
3144
|
+
f"Create a production-quality {aspect} UI mockup from the user visual brief below.",
|
|
3145
|
+
brief_block,
|
|
3146
|
+
style_quality_block(spec, label="UI", extra=["Use a coherent component system, precise spacing, realistic invented data, and crisp typography."]),
|
|
3147
|
+
f"Composition support: {spec.get('template_label', 'UI')}; layout guidance: {layout}.",
|
|
3148
|
+
intent_block,
|
|
3149
|
+
"Include only the screen regions, controls, lists, actions, charts, and navigation that are named or strongly implied by the requested interface.",
|
|
961
3150
|
f"Must include: {must_include}.",
|
|
962
|
-
f"Materials: {materials}. Lighting/rendering: {spec['lighting']}. Palette: {palette}.",
|
|
963
3151
|
text_block,
|
|
964
3152
|
f"Avoid: {negative}.",
|
|
965
3153
|
]
|
|
@@ -967,26 +3155,45 @@ def render_prompt(spec: dict) -> str:
|
|
|
967
3155
|
if asset_type == "infographic":
|
|
968
3156
|
return "\n".join(
|
|
969
3157
|
[
|
|
970
|
-
f"Create a {aspect} educational infographic
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
"
|
|
3158
|
+
f"Create a {aspect} educational infographic from the user visual brief below.",
|
|
3159
|
+
brief_block,
|
|
3160
|
+
style_quality_block(spec, label="infographic", extra=["Build a clear information hierarchy from the user's brief and preserve any named regions, section counts, and reading order."]),
|
|
3161
|
+
f"Composition support: {spec.get('template_label', 'infographic')}; layout guidance: {layout}.",
|
|
3162
|
+
intent_block,
|
|
3163
|
+
"Use leader lines, numbered callouts, labeled parts, and legends only where they clarify relationships already present in the brief.",
|
|
974
3164
|
f"Must include: {must_include}.",
|
|
975
|
-
f"Materials/linework: {materials}. Lighting: {spec['lighting']}. Palette: {palette}.",
|
|
976
3165
|
text_block,
|
|
977
3166
|
f"Avoid: {negative}.",
|
|
978
3167
|
]
|
|
979
3168
|
)
|
|
3169
|
+
if asset_type == "slide":
|
|
3170
|
+
return "\n".join(
|
|
3171
|
+
[
|
|
3172
|
+
f"Create a production-ready {aspect} presentation slide from the user visual brief below.",
|
|
3173
|
+
brief_block,
|
|
3174
|
+
style_quality_block(spec, label="presentation slide", extra=["Improve polish, hierarchy, spacing, and clarity while preserving the user's requested content and layout."]),
|
|
3175
|
+
f"Composition support: {spec.get('template_label', 'presentation slide')}; layout guidance: {layout}.",
|
|
3176
|
+
intent_block,
|
|
3177
|
+
"Follow the brief's information architecture exactly: keep the requested section count, order, relative areas, visual element subjects, footer treatment if any, and margins.",
|
|
3178
|
+
f"Must include: {must_include}.",
|
|
3179
|
+
text_block,
|
|
3180
|
+
f"Avoid: {negative}; do not add unrelated modules, extra charts, or decorative content outside the user brief.",
|
|
3181
|
+
]
|
|
3182
|
+
)
|
|
980
3183
|
if asset_type == "diagram":
|
|
3184
|
+
diagram_style = "; ".join(spec["style_anchors"])
|
|
3185
|
+
diagram_palette = ", ".join(spec["palette"])
|
|
981
3186
|
return "\n".join(
|
|
982
3187
|
[
|
|
983
|
-
f"
|
|
984
|
-
|
|
985
|
-
f"
|
|
986
|
-
"
|
|
3188
|
+
f"Create a presentation-ready {aspect} architecture diagram from the user visual brief below.",
|
|
3189
|
+
brief_block,
|
|
3190
|
+
f"Style / quality envelope for diagram: {diagram_style}. This only controls presentation polish; the user brief wins.",
|
|
3191
|
+
f"Quality controls: crisp typography, clear node grouping, balanced white space, precise alignment, high contrast, restrained palette ({diagram_palette}).",
|
|
3192
|
+
f"Recommended structure: {layout}. Use boxes, groups, arrows, and a feedback loop only where they match the brief.",
|
|
3193
|
+
"Do not over-template the diagram. Preserve the user's named modules, reading order, and product-review context.",
|
|
987
3194
|
f"Must include: {must_include}.",
|
|
988
|
-
f"Rendering: {materials}. Palette: {palette}.",
|
|
989
3195
|
text_block,
|
|
3196
|
+
"Intent preservation: use the brief as the source of truth; do not add, omit, rename, or reorder modules unless the brief asks for it.",
|
|
990
3197
|
f"Avoid: {negative}.",
|
|
991
3198
|
]
|
|
992
3199
|
)
|
|
@@ -995,15 +3202,17 @@ def render_prompt(spec: dict) -> str:
|
|
|
995
3202
|
[
|
|
996
3203
|
f"/* PRODUCT_RENDER_CONFIG VERSION: 1.0 ASPECT: {aspect} */",
|
|
997
3204
|
"{",
|
|
998
|
-
f' "
|
|
999
|
-
f' "
|
|
1000
|
-
|
|
3205
|
+
f' "USER_VISUAL_BRIEF": "{str(spec.get("safe_request") or spec.get("visual_request") or spec.get("request") or "").replace(chr(34), chr(39))}",',
|
|
3206
|
+
f' "STYLE_QUALITY_ENVELOPE": "{("; ".join(spec["style_anchors"])).replace(chr(34), chr(39))}",',
|
|
3207
|
+
' "STYLE_QUALITY_RULE": "This envelope controls rendering quality and visual language only; if it conflicts with USER_VISUAL_BRIEF, USER_VISUAL_BRIEF wins.",',
|
|
3208
|
+
f' "MATERIALS": "{(", ".join(spec["materials"])).replace(chr(34), chr(39))}",',
|
|
1001
3209
|
f' "LIGHTING": "{spec["lighting"]}",',
|
|
1002
|
-
f' "PALETTE": "{palette}",',
|
|
3210
|
+
f' "PALETTE": "{(", ".join(spec["palette"])).replace(chr(34), chr(39))}",',
|
|
1003
3211
|
f' "LAYOUT": "{layout}",',
|
|
1004
3212
|
f' "MUST_INCLUDE": "{must_include}",',
|
|
1005
|
-
' "COMPOSITION": "single
|
|
3213
|
+
' "COMPOSITION": "single requested product as the clear hero, sharp foreground, editorial finish without unrelated props"',
|
|
1006
3214
|
"}",
|
|
3215
|
+
intent_block,
|
|
1007
3216
|
text_block,
|
|
1008
3217
|
f"Avoid: {negative}.",
|
|
1009
3218
|
]
|
|
@@ -1011,12 +3220,13 @@ def render_prompt(spec: dict) -> str:
|
|
|
1011
3220
|
if asset_type == "photography":
|
|
1012
3221
|
return "\n".join(
|
|
1013
3222
|
[
|
|
1014
|
-
f"
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
"
|
|
3223
|
+
f"Create a candid documentary-style {aspect} photograph from the user visual brief below.",
|
|
3224
|
+
brief_block,
|
|
3225
|
+
style_quality_block(spec, label="photograph", extra=["Natural full-frame look, unprocessed realism, ordinary imperfect details."]),
|
|
3226
|
+
f"Layout guidance: {layout}.",
|
|
3227
|
+
intent_block,
|
|
3228
|
+
"Scene density: use concrete visible nouns from the brief; no staged studio posing or added story elements outside the request.",
|
|
1018
3229
|
f"Must include: {must_include}.",
|
|
1019
|
-
f"Light: {spec['lighting']}. Palette: {palette}.",
|
|
1020
3230
|
text_block,
|
|
1021
3231
|
f"Avoid: {negative}.",
|
|
1022
3232
|
]
|
|
@@ -1024,12 +3234,13 @@ def render_prompt(spec: dict) -> str:
|
|
|
1024
3234
|
if asset_type == "character":
|
|
1025
3235
|
return "\n".join(
|
|
1026
3236
|
[
|
|
1027
|
-
f"Create a {aspect} original character design reference sheet
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
"
|
|
3237
|
+
f"Create a {aspect} original character design reference sheet from the user visual brief below.",
|
|
3238
|
+
brief_block,
|
|
3239
|
+
style_quality_block(spec, label="character sheet", extra=["Clean model-sheet clarity on a neutral background."]),
|
|
3240
|
+
f"Layout guidance: {layout}.",
|
|
3241
|
+
intent_block,
|
|
3242
|
+
"Use reference-sheet panels, turnarounds, expression close-ups, props, or swatches only when the brief asks for a character sheet or implies reusable character design.",
|
|
1031
3243
|
f"Must include: {must_include}.",
|
|
1032
|
-
f"Materials/linework: {materials}. Lighting: {spec['lighting']}. Palette: {palette}.",
|
|
1033
3244
|
text_block,
|
|
1034
3245
|
f"Avoid: {negative}; avoid resemblance to existing IP.",
|
|
1035
3246
|
]
|
|
@@ -1037,20 +3248,23 @@ def render_prompt(spec: dict) -> str:
|
|
|
1037
3248
|
if asset_type == "logo":
|
|
1038
3249
|
return "\n".join(
|
|
1039
3250
|
[
|
|
1040
|
-
f"Create a {aspect} brand identity
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
f"
|
|
3251
|
+
f"Create a {aspect} brand identity visual from the user visual brief below.",
|
|
3252
|
+
brief_block,
|
|
3253
|
+
style_quality_block(spec, label="brand identity", extra=["Keep the requested logo or identity deliverable as the focus."]),
|
|
3254
|
+
f"Layout guidance: {layout}. Must include: {must_include}.",
|
|
3255
|
+
intent_block,
|
|
3256
|
+
"Include wordmarks, palette swatches, typography samples, or application mockups only when the brief asks for a brand board or identity system.",
|
|
1044
3257
|
text_block,
|
|
1045
3258
|
f"Avoid: {negative}; no stock clip-art, no resemblance to real-world brands.",
|
|
1046
3259
|
]
|
|
1047
3260
|
)
|
|
1048
3261
|
return "\n".join(
|
|
1049
3262
|
[
|
|
1050
|
-
f"Create a {aspect} stylized illustration
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
f"
|
|
3263
|
+
f"Create a {aspect} stylized illustration from the user visual brief below.",
|
|
3264
|
+
brief_block,
|
|
3265
|
+
style_quality_block(spec, label="illustration", extra=["Use concrete subject details, controlled composition, and a clear visual hierarchy."]),
|
|
3266
|
+
f"Composition support: {spec.get('template_label', 'illustration')}. Layout guidance: {layout}. Must include: {must_include}.",
|
|
3267
|
+
intent_block,
|
|
1054
3268
|
text_block,
|
|
1055
3269
|
f"Avoid: {negative}.",
|
|
1056
3270
|
]
|
|
@@ -1067,7 +3281,7 @@ def lint_prompt(prompt: str, asset_type: str | None, quality: str | None, requir
|
|
|
1067
3281
|
lower = compact.lower()
|
|
1068
3282
|
if len(compact) < 80:
|
|
1069
3283
|
add("error", "prompt.too_short", "Prompt 太短,缺少可执行的视觉约束。")
|
|
1070
|
-
if not re.search(r"
|
|
3284
|
+
if not re.search(r"(?<!\d)(?:3:4|4:5|4:3|16:9|9:16|1:1)(?!\d)|\b(?:portrait|landscape|square)\b", lower):
|
|
1071
3285
|
add("error", "prompt.missing_aspect", "Prompt 缺少画幅/宽高比/制品类型开场。")
|
|
1072
3286
|
if not re.search(r"\b(avoid|no |without|不要|避免)\b", lower):
|
|
1073
3287
|
add("warning", "prompt.missing_negative", "Prompt 缺少针对常见失败模式的否定项。")
|
|
@@ -1080,7 +3294,7 @@ def lint_prompt(prompt: str, asset_type: str | None, quality: str | None, requir
|
|
|
1080
3294
|
]
|
|
1081
3295
|
if not any(re.search(pat, compact) for pat in quoted_patterns):
|
|
1082
3296
|
add("error", "text.not_quoted", f"必显文字未用引号包住:{text}")
|
|
1083
|
-
if (required_texts or asset_type in {"poster", "ui", "infographic", "diagram", "logo"}) and quality and quality != "high":
|
|
3297
|
+
if (required_texts or asset_type in {"poster", "ui", "infographic", "slide", "diagram", "logo"}) and quality and quality != "high":
|
|
1084
3298
|
add("error", "quality.not_high", "文字/海报/UI/图表类转化应使用 high quality。")
|
|
1085
3299
|
if has_cjk(compact) and not re.search(r"(garbled|legible|readable|乱码|可读|清晰)", lower):
|
|
1086
3300
|
add("warning", "text.no_legibility_guard", "含中文文字时建议加入 legible / no garbled characters 约束。")
|
|
@@ -1092,6 +3306,188 @@ def lint_prompt(prompt: str, asset_type: str | None, quality: str | None, requir
|
|
|
1092
3306
|
return findings
|
|
1093
3307
|
|
|
1094
3308
|
|
|
3309
|
+
INTENT_TERM_GROUPS = {
|
|
3310
|
+
"props": ["道具", "props", "prop"],
|
|
3311
|
+
"people": ["人物", "人像", "真人", "人类", "people", "person", "human", "character"],
|
|
3312
|
+
"background_scene": ["背景场景", "场景背景", "scene", "background scene", "environment"],
|
|
3313
|
+
"charts": ["图表", "数据图表", "chart", "charts", "graph", "graphs", "diagram chart"],
|
|
3314
|
+
"icons": ["图标", "icon", "icons"],
|
|
3315
|
+
"columns": ["三栏", "三列", "两栏", "两列", "分栏", "栏目", "columns", "column"],
|
|
3316
|
+
"cards": ["卡片", "首页卡片", "content cards", "cards", "card"],
|
|
3317
|
+
"homepage": ["首页", "home screen", "homepage", "home page"],
|
|
3318
|
+
"modules": ["模块", "额外模块", "summary modules", "modules", "extra modules"],
|
|
3319
|
+
"brand_board": [
|
|
3320
|
+
"品牌板",
|
|
3321
|
+
"品牌系统板",
|
|
3322
|
+
"brand identity presentation board",
|
|
3323
|
+
"brand board",
|
|
3324
|
+
"wordmark",
|
|
3325
|
+
"palette swatches",
|
|
3326
|
+
"application mockups",
|
|
3327
|
+
],
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
HIGH_RISK_INTENT_GROUPS = {
|
|
3331
|
+
"props",
|
|
3332
|
+
"people",
|
|
3333
|
+
"background_scene",
|
|
3334
|
+
"charts",
|
|
3335
|
+
"icons",
|
|
3336
|
+
"columns",
|
|
3337
|
+
"cards",
|
|
3338
|
+
"homepage",
|
|
3339
|
+
"modules",
|
|
3340
|
+
"brand_board",
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
DENIED_ONLY_ALIASES = {
|
|
3344
|
+
"people": ["人", "任何人"],
|
|
3345
|
+
"columns": ["栏", "列"],
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
NEGATION_MARKERS = [
|
|
3349
|
+
"不要",
|
|
3350
|
+
"不需要",
|
|
3351
|
+
"不得",
|
|
3352
|
+
"不能",
|
|
3353
|
+
"避免",
|
|
3354
|
+
"禁止",
|
|
3355
|
+
"无",
|
|
3356
|
+
"没有",
|
|
3357
|
+
"no ",
|
|
3358
|
+
"not ",
|
|
3359
|
+
"without ",
|
|
3360
|
+
"avoid ",
|
|
3361
|
+
"do not ",
|
|
3362
|
+
"don't ",
|
|
3363
|
+
"outside the user brief",
|
|
3364
|
+
"not requested",
|
|
3365
|
+
"unless",
|
|
3366
|
+
"only when",
|
|
3367
|
+
"only where",
|
|
3368
|
+
"仅当",
|
|
3369
|
+
"只在",
|
|
3370
|
+
]
|
|
3371
|
+
|
|
3372
|
+
|
|
3373
|
+
def prompt_contains_positive(text: str, variants: list[str]) -> bool:
|
|
3374
|
+
lower = text.lower()
|
|
3375
|
+
for variant in variants:
|
|
3376
|
+
term = variant.lower().strip()
|
|
3377
|
+
if not term:
|
|
3378
|
+
continue
|
|
3379
|
+
if term.isascii() and re.search(r"[a-z0-9]", term):
|
|
3380
|
+
matches = list(re.finditer(rf"(?<![a-z0-9]){re.escape(term)}(?![a-z0-9])", lower))
|
|
3381
|
+
else:
|
|
3382
|
+
matches = list(re.finditer(re.escape(term), lower))
|
|
3383
|
+
for match in matches:
|
|
3384
|
+
context = lower[max(0, match.start() - 180) : min(len(lower), match.end() + 100)]
|
|
3385
|
+
if any(marker in context for marker in NEGATION_MARKERS):
|
|
3386
|
+
continue
|
|
3387
|
+
return True
|
|
3388
|
+
return False
|
|
3389
|
+
|
|
3390
|
+
|
|
3391
|
+
def request_mentions_any(request: str, variants: list[str]) -> bool:
|
|
3392
|
+
lower = request.lower()
|
|
3393
|
+
return any(keyword_in_text(variant, lower) for variant in variants)
|
|
3394
|
+
|
|
3395
|
+
|
|
3396
|
+
def intent_group_allowed_by_context(group: str, request: str, spec: dict) -> bool:
|
|
3397
|
+
lower = request.lower()
|
|
3398
|
+
asset_type = str(spec.get("asset_type") or "")
|
|
3399
|
+
if group == "cards" and asset_type == "ui":
|
|
3400
|
+
return any(k in lower for k in ["首页", "展示", "列表", "作品", "项目", "内容", "recent", "gallery", "feed"])
|
|
3401
|
+
if group == "modules" and asset_type in {"diagram", "infographic", "slide"}:
|
|
3402
|
+
return any(k in lower for k in ["架构", "系统", "模块", "流程", "步骤", "结构", "分析", "risk", "rag", "pipeline"])
|
|
3403
|
+
if group == "charts":
|
|
3404
|
+
return asset_type == "data-viz" or any(k in lower for k in ["数据", "指标", "趋势", "占比", "转化率", "漏斗", "报表", "chart", "graph"])
|
|
3405
|
+
if group == "people":
|
|
3406
|
+
return asset_type == "character" or any(k in lower for k in ["人物", "角色", "人像", "portrait", "character", "person", "people"])
|
|
3407
|
+
if group == "icons":
|
|
3408
|
+
return any(k in lower for k in ["图标", "icon", "icons", "符号", "标识符"])
|
|
3409
|
+
if group == "columns":
|
|
3410
|
+
return any(k in lower for k in ["三栏", "三列", "两栏", "两列", "分栏", "column", "columns", "grid"])
|
|
3411
|
+
if group == "brand_board":
|
|
3412
|
+
return asset_type == "logo" or any(k in lower for k in ["品牌系统", "品牌板", "vi", "identity", "brand board", "logo"])
|
|
3413
|
+
return False
|
|
3414
|
+
|
|
3415
|
+
|
|
3416
|
+
def denied_intent_groups(request: str) -> set[str]:
|
|
3417
|
+
lower = request.lower()
|
|
3418
|
+
denied: set[str] = set()
|
|
3419
|
+
spans: list[str] = []
|
|
3420
|
+
patterns = [
|
|
3421
|
+
r"(?:不要|不需要|不得|不能|避免|禁止|无|没有)([^,。;;,.、\n]{1,30})",
|
|
3422
|
+
r"\b(?:no|without|avoid|not)\s+([^,.;\n]{1,50})",
|
|
3423
|
+
]
|
|
3424
|
+
for pat in patterns:
|
|
3425
|
+
for match in re.finditer(pat, lower, flags=re.IGNORECASE):
|
|
3426
|
+
spans.append(match.group(1))
|
|
3427
|
+
for span in spans:
|
|
3428
|
+
compact_span = re.sub(r"\s+", "", span)
|
|
3429
|
+
for key, variants in INTENT_TERM_GROUPS.items():
|
|
3430
|
+
denied_only = DENIED_ONLY_ALIASES.get(key, [])
|
|
3431
|
+
if any(v.lower() in span for v in variants) or any(alias in compact_span for alias in denied_only):
|
|
3432
|
+
denied.add(key)
|
|
3433
|
+
return denied
|
|
3434
|
+
|
|
3435
|
+
|
|
3436
|
+
def intent_check_prompt(request: str, prompt: str, spec: dict | None = None) -> list[dict]:
|
|
3437
|
+
findings: list[dict] = []
|
|
3438
|
+
spec = spec or {}
|
|
3439
|
+
request = request or ""
|
|
3440
|
+
prompt = prompt or ""
|
|
3441
|
+
|
|
3442
|
+
def add(severity: str, rule: str, message: str, evidence: str = "") -> None:
|
|
3443
|
+
item = {"severity": severity, "rule": rule, "message": message}
|
|
3444
|
+
if evidence:
|
|
3445
|
+
item["evidence"] = evidence
|
|
3446
|
+
findings.append(item)
|
|
3447
|
+
|
|
3448
|
+
denied = denied_intent_groups(request)
|
|
3449
|
+
for key in sorted(denied):
|
|
3450
|
+
variants = INTENT_TERM_GROUPS[key]
|
|
3451
|
+
if prompt_contains_positive(prompt, variants):
|
|
3452
|
+
add(
|
|
3453
|
+
"error",
|
|
3454
|
+
"intent.denied_content_added",
|
|
3455
|
+
f"用户明确否定了 {key},但 prompt 中仍出现正向要求。",
|
|
3456
|
+
", ".join(variants[:4]),
|
|
3457
|
+
)
|
|
3458
|
+
|
|
3459
|
+
for key in sorted(HIGH_RISK_INTENT_GROUPS - denied):
|
|
3460
|
+
variants = INTENT_TERM_GROUPS[key]
|
|
3461
|
+
if request_mentions_any(request, variants):
|
|
3462
|
+
continue
|
|
3463
|
+
if intent_group_allowed_by_context(key, request, spec):
|
|
3464
|
+
continue
|
|
3465
|
+
if prompt_contains_positive(prompt, variants):
|
|
3466
|
+
add(
|
|
3467
|
+
"error",
|
|
3468
|
+
"intent.unrequested_module",
|
|
3469
|
+
f"prompt 引入了用户未要求的高风险模块:{key}。",
|
|
3470
|
+
", ".join(variants[:4]),
|
|
3471
|
+
)
|
|
3472
|
+
|
|
3473
|
+
required_text = normalize_text_list(spec.get("required_text")) if spec else []
|
|
3474
|
+
required_text_source = str(spec.get("required_text_source") or "")
|
|
3475
|
+
if required_text_source != "explicit":
|
|
3476
|
+
for text in required_text:
|
|
3477
|
+
if text and text not in request:
|
|
3478
|
+
add("warning", "intent.text_not_in_request", f"必显文字不在原始需求中:{text}", text)
|
|
3479
|
+
return findings
|
|
3480
|
+
|
|
3481
|
+
|
|
3482
|
+
def print_intent_findings(findings: list[dict]) -> None:
|
|
3483
|
+
if not findings:
|
|
3484
|
+
print("intent-check: pass")
|
|
3485
|
+
return
|
|
3486
|
+
for item in findings:
|
|
3487
|
+
evidence = f" evidence={item['evidence']}" if item.get("evidence") else ""
|
|
3488
|
+
print(f"{item['severity']}: {item['rule']} - {item['message']}{evidence}")
|
|
3489
|
+
|
|
3490
|
+
|
|
1095
3491
|
def print_lint(findings: list[dict]) -> None:
|
|
1096
3492
|
if not findings:
|
|
1097
3493
|
print("lint: pass")
|
|
@@ -1374,6 +3770,27 @@ def cmd_lint(args: argparse.Namespace) -> int:
|
|
|
1374
3770
|
return 1 if any(item["severity"] == "error" for item in findings) else 0
|
|
1375
3771
|
|
|
1376
3772
|
|
|
3773
|
+
def cmd_intent_check(args: argparse.Namespace) -> int:
|
|
3774
|
+
request = read_text_argument(args.request, args.request_file)
|
|
3775
|
+
prompt = read_text_argument(args.prompt, args.prompt_file)
|
|
3776
|
+
if not request or not prompt:
|
|
3777
|
+
print("用法:intent-check --request \"原始需求\" --prompt \"生成后的 prompt\"", file=sys.stderr)
|
|
3778
|
+
return 2
|
|
3779
|
+
spec = {}
|
|
3780
|
+
if args.spec:
|
|
3781
|
+
try:
|
|
3782
|
+
spec = extract_spec_payload(load_json_value(args.spec))
|
|
3783
|
+
except Exception as exc:
|
|
3784
|
+
print(f"读取 --spec 失败:{exc}", file=sys.stderr)
|
|
3785
|
+
return 2
|
|
3786
|
+
findings = intent_check_prompt(request, prompt, spec)
|
|
3787
|
+
if args.json:
|
|
3788
|
+
print(json.dumps({"findings": findings, "pass": not has_lint_error(findings)}, ensure_ascii=False, indent=2))
|
|
3789
|
+
else:
|
|
3790
|
+
print_intent_findings(findings)
|
|
3791
|
+
return 1 if has_lint_error(findings) else 0
|
|
3792
|
+
|
|
3793
|
+
|
|
1377
3794
|
def cmd_convert(args: argparse.Namespace) -> int:
|
|
1378
3795
|
if isinstance(args.request_text, list):
|
|
1379
3796
|
args.request_text = " ".join(args.request_text)
|
|
@@ -1384,6 +3801,7 @@ def cmd_convert(args: argparse.Namespace) -> int:
|
|
|
1384
3801
|
prompt = render_prompt(spec)
|
|
1385
3802
|
lint_texts = [] if spec.get("strict_text") else spec["required_text"]
|
|
1386
3803
|
findings = lint_prompt(prompt, spec["asset_type"], spec["quality"], lint_texts)
|
|
3804
|
+
intent_findings = intent_check_prompt(spec["request"], prompt, spec)
|
|
1387
3805
|
|
|
1388
3806
|
if args.record_pending:
|
|
1389
3807
|
rec = sample_record(
|
|
@@ -1413,13 +3831,14 @@ def cmd_convert(args: argparse.Namespace) -> int:
|
|
|
1413
3831
|
"text_overlay_spec": spec.get("text_overlay_spec"),
|
|
1414
3832
|
"acceptance_criteria": spec.get("acceptance_criteria", []),
|
|
1415
3833
|
"lint": findings,
|
|
3834
|
+
"intent_check": intent_findings,
|
|
1416
3835
|
"handoff": handoff,
|
|
1417
3836
|
},
|
|
1418
3837
|
ensure_ascii=False,
|
|
1419
3838
|
indent=2,
|
|
1420
3839
|
)
|
|
1421
3840
|
)
|
|
1422
|
-
return 1 if
|
|
3841
|
+
return 1 if has_lint_error(findings) or has_lint_error(intent_findings) else 0
|
|
1423
3842
|
|
|
1424
3843
|
print("## Prompt")
|
|
1425
3844
|
print(prompt)
|
|
@@ -1428,6 +3847,9 @@ def cmd_convert(args: argparse.Namespace) -> int:
|
|
|
1428
3847
|
print()
|
|
1429
3848
|
print("## Lint")
|
|
1430
3849
|
print_lint(findings)
|
|
3850
|
+
print()
|
|
3851
|
+
print("## Intent Check")
|
|
3852
|
+
print_intent_findings(intent_findings)
|
|
1431
3853
|
if handoff:
|
|
1432
3854
|
print()
|
|
1433
3855
|
print("## Handoff")
|
|
@@ -1443,7 +3865,7 @@ def cmd_convert(args: argparse.Namespace) -> int:
|
|
|
1443
3865
|
if "sample_id" in spec:
|
|
1444
3866
|
print()
|
|
1445
3867
|
print(f"sample_id: {spec['sample_id']}")
|
|
1446
|
-
return 1 if
|
|
3868
|
+
return 1 if has_lint_error(findings) or has_lint_error(intent_findings) else 0
|
|
1447
3869
|
|
|
1448
3870
|
|
|
1449
3871
|
def prompt_digest(prompt: str) -> str:
|
|
@@ -1469,6 +3891,7 @@ def namespace_from_case(case: dict) -> argparse.Namespace:
|
|
|
1469
3891
|
text=normalize_text_list(case.get("text") or case.get("required_text")),
|
|
1470
3892
|
subject=case.get("subject"),
|
|
1471
3893
|
style=case.get("style"),
|
|
3894
|
+
style_preset=case.get("style_preset"),
|
|
1472
3895
|
materials=case.get("materials"),
|
|
1473
3896
|
lighting=case.get("lighting"),
|
|
1474
3897
|
palette=case.get("palette"),
|
|
@@ -1513,12 +3936,14 @@ def convert_for_benchmark(case: dict) -> dict:
|
|
|
1513
3936
|
prompt = render_prompt(spec)
|
|
1514
3937
|
lint_texts = [] if spec.get("strict_text") else spec["required_text"]
|
|
1515
3938
|
findings = lint_prompt(prompt, spec["asset_type"], spec["quality"], lint_texts)
|
|
3939
|
+
intent_findings = intent_check_prompt(spec["request"], prompt, spec)
|
|
1516
3940
|
return {
|
|
1517
3941
|
"case_id": case.get("id") or case.get("name") or "",
|
|
1518
3942
|
"spec": spec,
|
|
1519
3943
|
"prompt": prompt,
|
|
1520
3944
|
"prompt_digest": prompt_digest(prompt),
|
|
1521
3945
|
"lint": findings,
|
|
3946
|
+
"intent_check": intent_findings,
|
|
1522
3947
|
"acceptance_criteria": spec.get("acceptance_criteria", []),
|
|
1523
3948
|
}
|
|
1524
3949
|
|
|
@@ -1530,7 +3955,8 @@ def cmd_benchmark(args: argparse.Namespace) -> int:
|
|
|
1530
3955
|
print(f"读取 benchmark cases 失败:{exc}", file=sys.stderr)
|
|
1531
3956
|
return 2
|
|
1532
3957
|
results = []
|
|
1533
|
-
|
|
3958
|
+
total_lint_errors = 0
|
|
3959
|
+
total_intent_errors = 0
|
|
1534
3960
|
unstable = 0
|
|
1535
3961
|
for idx, case in enumerate(cases, start=1):
|
|
1536
3962
|
runs = []
|
|
@@ -1540,12 +3966,15 @@ def cmd_benchmark(args: argparse.Namespace) -> int:
|
|
|
1540
3966
|
except ValueError as exc:
|
|
1541
3967
|
result = {"case_index": idx, "case_id": case.get("id", ""), "error": str(exc), "runs": []}
|
|
1542
3968
|
results.append(result)
|
|
1543
|
-
|
|
3969
|
+
total_lint_errors += 1
|
|
1544
3970
|
continue
|
|
1545
3971
|
digests = {run["prompt_digest"] for run in runs}
|
|
1546
3972
|
lint_errors = [item for run in runs for item in run["lint"] if item["severity"] == "error"]
|
|
3973
|
+
intent_errors = [item for run in runs for item in run["intent_check"] if item["severity"] == "error"]
|
|
1547
3974
|
if lint_errors:
|
|
1548
|
-
|
|
3975
|
+
total_lint_errors += len(lint_errors)
|
|
3976
|
+
if intent_errors:
|
|
3977
|
+
total_intent_errors += len(intent_errors)
|
|
1549
3978
|
if len(digests) > 1:
|
|
1550
3979
|
unstable += 1
|
|
1551
3980
|
results.append(
|
|
@@ -1560,15 +3989,18 @@ def cmd_benchmark(args: argparse.Namespace) -> int:
|
|
|
1560
3989
|
"prompt_digest": runs[0]["prompt_digest"],
|
|
1561
3990
|
"lint_errors": lint_errors,
|
|
1562
3991
|
"lint_warnings": [item for run in runs for item in run["lint"] if item["severity"] == "warning"],
|
|
3992
|
+
"intent_errors": intent_errors,
|
|
3993
|
+
"intent_warnings": [item for run in runs for item in run["intent_check"] if item["severity"] == "warning"],
|
|
1563
3994
|
"acceptance_criteria": runs[0]["acceptance_criteria"],
|
|
1564
3995
|
}
|
|
1565
3996
|
)
|
|
1566
3997
|
summary = {
|
|
1567
3998
|
"cases": len(cases),
|
|
1568
3999
|
"runs_per_case": args.runs,
|
|
1569
|
-
"lint_error_count":
|
|
4000
|
+
"lint_error_count": total_lint_errors,
|
|
4001
|
+
"intent_error_count": total_intent_errors,
|
|
1570
4002
|
"unstable_case_count": unstable,
|
|
1571
|
-
"pass":
|
|
4003
|
+
"pass": total_lint_errors == 0 and total_intent_errors == 0 and unstable == 0,
|
|
1572
4004
|
}
|
|
1573
4005
|
output = {"summary": summary, "results": results}
|
|
1574
4006
|
if args.json:
|
|
@@ -1579,13 +4011,15 @@ def cmd_benchmark(args: argparse.Namespace) -> int:
|
|
|
1579
4011
|
if result.get("error"):
|
|
1580
4012
|
print(f"- {result['case_id'] or result['case_index']}: error {result['error']}")
|
|
1581
4013
|
continue
|
|
1582
|
-
status = "PASS" if result["stable"] and not result["lint_errors"] else "FAIL"
|
|
4014
|
+
status = "PASS" if result["stable"] and not result["lint_errors"] and not result["intent_errors"] else "FAIL"
|
|
1583
4015
|
print(
|
|
1584
4016
|
f"- {result['case_id']}: {status} asset={result['asset_type']} "
|
|
1585
4017
|
f"template={result['template_id']} digest={result['prompt_digest']}"
|
|
1586
4018
|
)
|
|
1587
4019
|
for item in result["lint_errors"]:
|
|
1588
4020
|
print(f" error: {item['rule']} - {item['message']}")
|
|
4021
|
+
for item in result["intent_errors"]:
|
|
4022
|
+
print(f" intent-error: {item['rule']} - {item['message']}")
|
|
1589
4023
|
return 0 if summary["pass"] else 1
|
|
1590
4024
|
|
|
1591
4025
|
|
|
@@ -1741,6 +4175,17 @@ def load_json_value(value: str | None) -> dict:
|
|
|
1741
4175
|
return data
|
|
1742
4176
|
|
|
1743
4177
|
|
|
4178
|
+
def missing_pillow_message(command: str) -> str:
|
|
4179
|
+
return (
|
|
4180
|
+
f"{command} 需要 Pillow 图像库。请优先用 `uv run` / `npx @yuhan1124/draw-prompt ...` 自动安装依赖,"
|
|
4181
|
+
"或在当前 Python 环境中安装:python3 -m pip install pillow"
|
|
4182
|
+
)
|
|
4183
|
+
|
|
4184
|
+
|
|
4185
|
+
def is_missing_pillow(exc: BaseException) -> bool:
|
|
4186
|
+
return isinstance(exc, ModuleNotFoundError) and getattr(exc, "name", "") == "PIL"
|
|
4187
|
+
|
|
4188
|
+
|
|
1744
4189
|
def extract_spec_payload(data: dict) -> dict:
|
|
1745
4190
|
if "spec" in data and isinstance(data["spec"], dict):
|
|
1746
4191
|
return data["spec"]
|
|
@@ -1906,19 +4351,21 @@ def draw_text_overlays(image_path: Path, out_path: Path, spec: dict, texts: list
|
|
|
1906
4351
|
overlays = extract_overlay_items(spec, texts)
|
|
1907
4352
|
font_file = choose_font_path(font_path)
|
|
1908
4353
|
rendered = []
|
|
4354
|
+
asset_type = str(spec.get("asset_type") or "")
|
|
1909
4355
|
|
|
1910
4356
|
for idx, item in enumerate(overlays):
|
|
1911
4357
|
text = str(item.get("text") or "").strip()
|
|
1912
4358
|
if not text:
|
|
1913
4359
|
continue
|
|
1914
4360
|
role = str(item.get("role") or "")
|
|
4361
|
+
display_text = text
|
|
1915
4362
|
box = item.get("box")
|
|
1916
4363
|
if not isinstance(box, list) or len(box) != 4:
|
|
1917
4364
|
box = overlay_box_hint(spec, item, idx, len(overlays))
|
|
1918
4365
|
px = normalized_box_to_pixels([float(v) for v in box], width, height)
|
|
1919
4366
|
pad = max(8, int(min(width, height) * 0.012))
|
|
1920
4367
|
inner = (px[0] + pad, px[1] + pad, px[2] - pad, px[3] - pad)
|
|
1921
|
-
font, wrapped = fit_overlay_text(draw,
|
|
4368
|
+
font, wrapped = fit_overlay_text(draw, display_text, inner, font_file)
|
|
1922
4369
|
tw, th = text_bbox(draw, wrapped, font)
|
|
1923
4370
|
align = str(item.get("align") or "center")
|
|
1924
4371
|
if align == "left":
|
|
@@ -1929,7 +4376,25 @@ def draw_text_overlays(image_path: Path, out_path: Path, spec: dict, texts: list
|
|
|
1929
4376
|
tx = inner[0] + max(0, (inner[2] - inner[0] - tw) // 2)
|
|
1930
4377
|
ty = inner[1] + max(0, (inner[3] - inner[1] - th) // 2)
|
|
1931
4378
|
|
|
1932
|
-
if
|
|
4379
|
+
if asset_type == "slide":
|
|
4380
|
+
if role == "slide_title":
|
|
4381
|
+
text_fill = (255, 255, 255, 255)
|
|
4382
|
+
elif role == "slide_section_title":
|
|
4383
|
+
text_fill = (248, 252, 255, 255)
|
|
4384
|
+
else:
|
|
4385
|
+
text_fill = (220, 237, 252, 255)
|
|
4386
|
+
shadow_offset = max(2, int(min(width, height) * 0.002))
|
|
4387
|
+
shadow_fill = (0, 10, 30, 180)
|
|
4388
|
+
draw.multiline_text(
|
|
4389
|
+
(tx + shadow_offset, ty + shadow_offset),
|
|
4390
|
+
wrapped,
|
|
4391
|
+
font=font,
|
|
4392
|
+
fill=shadow_fill,
|
|
4393
|
+
align=align,
|
|
4394
|
+
spacing=max(2, int(getattr(font, "size", 12) * 0.14)),
|
|
4395
|
+
)
|
|
4396
|
+
draw.multiline_text((tx, ty), wrapped, font=font, fill=text_fill, align=align, spacing=max(2, int(getattr(font, "size", 12) * 0.14)))
|
|
4397
|
+
elif role == "price_or_offer":
|
|
1933
4398
|
fill = (31, 84, 55, 232)
|
|
1934
4399
|
outline = (255, 255, 255, 90)
|
|
1935
4400
|
text_fill = (255, 255, 245, 255)
|
|
@@ -1937,9 +4402,9 @@ def draw_text_overlays(image_path: Path, out_path: Path, spec: dict, texts: list
|
|
|
1937
4402
|
fill = (255, 255, 255, 218)
|
|
1938
4403
|
outline = (31, 84, 55, 65)
|
|
1939
4404
|
text_fill = (19, 35, 27, 255)
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
4405
|
+
radius = max(6, int(min(px[2] - px[0], px[3] - px[1]) * 0.12))
|
|
4406
|
+
draw.rounded_rectangle(px, radius=radius, fill=fill, outline=outline, width=max(1, int(pad * 0.14)))
|
|
4407
|
+
draw.multiline_text((tx, ty), wrapped, font=font, fill=text_fill, align=align, spacing=max(2, int(getattr(font, "size", 12) * 0.14)))
|
|
1943
4408
|
rendered.append({"text": text, "box": box, "font_size": getattr(font, "size", 0), "role": role})
|
|
1944
4409
|
|
|
1945
4410
|
final = Image.alpha_composite(image, layer).convert("RGB")
|
|
@@ -1955,6 +4420,11 @@ def cmd_overlay(args: argparse.Namespace) -> int:
|
|
|
1955
4420
|
image_path = Path(args.image).expanduser()
|
|
1956
4421
|
out_path = Path(args.out).expanduser() if args.out else default_overlay_out(image_path)
|
|
1957
4422
|
report = draw_text_overlays(image_path, out_path, spec, args.text, args.font)
|
|
4423
|
+
except ModuleNotFoundError as exc:
|
|
4424
|
+
if is_missing_pillow(exc):
|
|
4425
|
+
print(missing_pillow_message("overlay"), file=sys.stderr)
|
|
4426
|
+
return 2
|
|
4427
|
+
raise
|
|
1958
4428
|
except Exception as exc:
|
|
1959
4429
|
print(f"overlay 失败:{exc}", file=sys.stderr)
|
|
1960
4430
|
return 2
|
|
@@ -2027,6 +4497,11 @@ def cmd_visual_check(args: argparse.Namespace) -> int:
|
|
|
2027
4497
|
spec = extract_spec_payload(load_json_value(args.spec)) if args.spec else {}
|
|
2028
4498
|
expected = args.aspect or spec.get("aspect")
|
|
2029
4499
|
metrics = image_quality_metrics(Path(args.image).expanduser(), expected)
|
|
4500
|
+
except ModuleNotFoundError as exc:
|
|
4501
|
+
if is_missing_pillow(exc):
|
|
4502
|
+
print(missing_pillow_message("visual-check"), file=sys.stderr)
|
|
4503
|
+
return 2
|
|
4504
|
+
raise
|
|
2030
4505
|
except Exception as exc:
|
|
2031
4506
|
print(f"visual-check 失败:{exc}", file=sys.stderr)
|
|
2032
4507
|
return 2
|
|
@@ -2110,6 +4585,11 @@ def cmd_edit_check(args: argparse.Namespace) -> int:
|
|
|
2110
4585
|
args.threshold,
|
|
2111
4586
|
args.min_change,
|
|
2112
4587
|
)
|
|
4588
|
+
except ModuleNotFoundError as exc:
|
|
4589
|
+
if is_missing_pillow(exc):
|
|
4590
|
+
print(missing_pillow_message("edit-check"), file=sys.stderr)
|
|
4591
|
+
return 2
|
|
4592
|
+
raise
|
|
2113
4593
|
except Exception as exc:
|
|
2114
4594
|
print(f"edit-check 失败:{exc}", file=sys.stderr)
|
|
2115
4595
|
return 2
|
|
@@ -2136,6 +4616,7 @@ def cmd_visual_regress(args: argparse.Namespace) -> int:
|
|
|
2136
4616
|
return 2
|
|
2137
4617
|
results = []
|
|
2138
4618
|
lint_errors = 0
|
|
4619
|
+
intent_errors = 0
|
|
2139
4620
|
visual_errors = 0
|
|
2140
4621
|
missing_images = 0
|
|
2141
4622
|
for idx, case in enumerate(cases, start=1):
|
|
@@ -2160,7 +4641,9 @@ def cmd_visual_regress(args: argparse.Namespace) -> int:
|
|
|
2160
4641
|
continue
|
|
2161
4642
|
compiled = visual_case_compile(case)
|
|
2162
4643
|
lint = compiled["lint"]
|
|
4644
|
+
intent = compiled.get("intent_check", [])
|
|
2163
4645
|
lint_errors += sum(1 for item in lint if item.get("severity") == "error")
|
|
4646
|
+
intent_errors += sum(1 for item in intent if item.get("severity") == "error")
|
|
2164
4647
|
item = {
|
|
2165
4648
|
"id": case_id,
|
|
2166
4649
|
"scenario": case.get("scenario") or case.get("tool") or "convert",
|
|
@@ -2168,6 +4651,7 @@ def cmd_visual_regress(args: argparse.Namespace) -> int:
|
|
|
2168
4651
|
"asset_type": compiled["spec"]["asset_type"],
|
|
2169
4652
|
"aspect": compiled["spec"]["aspect"],
|
|
2170
4653
|
"lint": lint,
|
|
4654
|
+
"intent_check": intent,
|
|
2171
4655
|
"status": "compiled",
|
|
2172
4656
|
}
|
|
2173
4657
|
image = case.get("image") or case.get("output")
|
|
@@ -2189,21 +4673,28 @@ def cmd_visual_regress(args: argparse.Namespace) -> int:
|
|
|
2189
4673
|
visual_errors += sum(1 for f in edit_report["findings"] if f.get("severity") == "error")
|
|
2190
4674
|
item["edit_check"] = edit_report
|
|
2191
4675
|
results.append(item)
|
|
4676
|
+
except ModuleNotFoundError as exc:
|
|
4677
|
+
if is_missing_pillow(exc):
|
|
4678
|
+
results.append({"id": case_id, "status": "error", "error": missing_pillow_message("visual-regress")})
|
|
4679
|
+
else:
|
|
4680
|
+
results.append({"id": case_id, "status": "error", "error": str(exc)})
|
|
4681
|
+
visual_errors += 1
|
|
2192
4682
|
except Exception as exc:
|
|
2193
4683
|
visual_errors += 1
|
|
2194
4684
|
results.append({"id": case_id, "status": "error", "error": str(exc)})
|
|
2195
4685
|
summary = {
|
|
2196
4686
|
"cases": len(cases),
|
|
2197
4687
|
"lint_error_count": lint_errors,
|
|
4688
|
+
"intent_error_count": intent_errors,
|
|
2198
4689
|
"visual_error_count": visual_errors,
|
|
2199
4690
|
"missing_image_count": missing_images,
|
|
2200
|
-
"pass": lint_errors == 0 and visual_errors == 0 and missing_images == 0,
|
|
4691
|
+
"pass": lint_errors == 0 and intent_errors == 0 and visual_errors == 0 and missing_images == 0,
|
|
2201
4692
|
}
|
|
2202
4693
|
output = {"summary": summary, "results": results}
|
|
2203
4694
|
if args.json:
|
|
2204
4695
|
print(json.dumps(output, ensure_ascii=False, indent=2))
|
|
2205
4696
|
else:
|
|
2206
|
-
print(f"visual-regress: cases={summary['cases']} pass={summary['pass']} lint_errors={lint_errors} visual_errors={visual_errors} missing_images={missing_images}")
|
|
4697
|
+
print(f"visual-regress: cases={summary['cases']} pass={summary['pass']} lint_errors={lint_errors} intent_errors={intent_errors} visual_errors={visual_errors} missing_images={missing_images}")
|
|
2207
4698
|
for item in results:
|
|
2208
4699
|
print(f"- {item['id']}: {item['status']} asset={item.get('asset_type', '?')} digest={item.get('prompt_digest', '?')}")
|
|
2209
4700
|
return 0 if summary["pass"] else 1
|
|
@@ -2228,6 +4719,11 @@ def compact_title(text: str, limit: int = 28) -> str:
|
|
|
2228
4719
|
return clean[:limit].rstrip() + "..."
|
|
2229
4720
|
|
|
2230
4721
|
|
|
4722
|
+
def safe_slug(value: str, fallback: str = "style") -> str:
|
|
4723
|
+
slug = re.sub(r"[^a-zA-Z0-9_-]+", "-", value.strip().lower()).strip("-")
|
|
4724
|
+
return slug or fallback
|
|
4725
|
+
|
|
4726
|
+
|
|
2231
4727
|
def normalize_case_values(case: dict) -> dict:
|
|
2232
4728
|
out = dict(case)
|
|
2233
4729
|
for key in ("style", "materials", "palette", "tags"):
|
|
@@ -2251,6 +4747,7 @@ def compile_visual_case(
|
|
|
2251
4747
|
prompt = render_prompt(spec)
|
|
2252
4748
|
lint_texts = [] if spec.get("strict_text") else spec["required_text"]
|
|
2253
4749
|
findings = lint_prompt(prompt, spec["asset_type"], spec["quality"], lint_texts)
|
|
4750
|
+
intent_findings = intent_check_prompt(spec["request"], prompt, spec)
|
|
2254
4751
|
return {
|
|
2255
4752
|
"spec": spec,
|
|
2256
4753
|
"prompt": prompt,
|
|
@@ -2258,6 +4755,7 @@ def compile_visual_case(
|
|
|
2258
4755
|
"text_overlay_spec": spec.get("text_overlay_spec"),
|
|
2259
4756
|
"acceptance_criteria": spec.get("acceptance_criteria", []),
|
|
2260
4757
|
"lint": findings,
|
|
4758
|
+
"intent_check": intent_findings,
|
|
2261
4759
|
"handoff": handoff_text(prompt, args.out, spec["size"], spec["quality"], args.target) if include_handoff else None,
|
|
2262
4760
|
}
|
|
2263
4761
|
|
|
@@ -2345,8 +4843,9 @@ def extract_visual_labels(chunk: str, asset_type: str, limit: int = 5) -> list[s
|
|
|
2345
4843
|
add(match)
|
|
2346
4844
|
for match in re.findall(r"(?:标题|主题|模块|步骤|节点|页面)[::\s]*([^,。;;\n]{2,24})", chunk):
|
|
2347
4845
|
add(match)
|
|
2348
|
-
if
|
|
2349
|
-
|
|
4846
|
+
if asset_type in {"diagram", "infographic", "ui"}:
|
|
4847
|
+
for item in extract_structural_labels(chunk, asset_type):
|
|
4848
|
+
add(item)
|
|
2350
4849
|
return labels[:limit]
|
|
2351
4850
|
|
|
2352
4851
|
|
|
@@ -2383,6 +4882,7 @@ def cmd_compose(args: argparse.Namespace) -> int:
|
|
|
2383
4882
|
"request": f"{purpose}。根据这段内容生成对应画面:{chunk}",
|
|
2384
4883
|
"asset_type": asset_type,
|
|
2385
4884
|
"style": shared_style,
|
|
4885
|
+
"style_preset": args.style_preset,
|
|
2386
4886
|
"palette": args.palette,
|
|
2387
4887
|
"text": labels if (args.strict_text or asset_type in {"diagram", "infographic", "ui"}) else [],
|
|
2388
4888
|
"strict_text": args.strict_text,
|
|
@@ -2401,6 +4901,7 @@ def cmd_compose(args: argparse.Namespace) -> int:
|
|
|
2401
4901
|
"prompt": compiled["prompt"],
|
|
2402
4902
|
"handoff": compiled["handoff"],
|
|
2403
4903
|
"lint": compiled["lint"],
|
|
4904
|
+
"intent_check": compiled["intent_check"],
|
|
2404
4905
|
"text_overlay_spec": compiled["text_overlay_spec"],
|
|
2405
4906
|
"acceptance_criteria": compiled["acceptance_criteria"],
|
|
2406
4907
|
"spec": compiled["spec"],
|
|
@@ -2409,12 +4910,14 @@ def cmd_compose(args: argparse.Namespace) -> int:
|
|
|
2409
4910
|
result = {
|
|
2410
4911
|
"summary": compact_title(text, 80),
|
|
2411
4912
|
"shared_style": shared_style,
|
|
4913
|
+
"style_preset": args.style_preset or "auto",
|
|
2412
4914
|
"visual_plan": visual_plan,
|
|
2413
4915
|
"bundle_acceptance": [
|
|
2414
4916
|
"每张图对应长输入中的一个明确信息单元,不混淆主题。",
|
|
2415
4917
|
"整组图使用同一风格、配色和信息层级规则。",
|
|
2416
4918
|
"图表/架构/UI 类标签清晰可读;strict_text 时文字走 overlay_spec。",
|
|
2417
4919
|
"任一单图 lint 出现 error 时必须先修 prompt 再交给下游出图。",
|
|
4920
|
+
"任一单图 intent-check 出现 error 时必须先修 prompt,避免偏离原文。",
|
|
2418
4921
|
],
|
|
2419
4922
|
}
|
|
2420
4923
|
if args.json:
|
|
@@ -2422,13 +4925,13 @@ def cmd_compose(args: argparse.Namespace) -> int:
|
|
|
2422
4925
|
else:
|
|
2423
4926
|
print(f"compose: {len(visual_plan)} 张图 shared_style={shared_style}")
|
|
2424
4927
|
for item in visual_plan:
|
|
2425
|
-
status = "FAIL" if has_lint_error(item["lint"]) else "PASS"
|
|
4928
|
+
status = "FAIL" if has_lint_error(item["lint"]) or has_lint_error(item["intent_check"]) else "PASS"
|
|
2426
4929
|
print(f"\n## {item['id']} {item['purpose']} [{status}]")
|
|
2427
4930
|
print(item["prompt"])
|
|
2428
4931
|
if item["handoff"]:
|
|
2429
4932
|
print("\nHandoff:")
|
|
2430
4933
|
print(item["handoff"])
|
|
2431
|
-
return 1 if any(has_lint_error(item["lint"]) for item in visual_plan) else 0
|
|
4934
|
+
return 1 if any(has_lint_error(item["lint"]) or has_lint_error(item["intent_check"]) for item in visual_plan) else 0
|
|
2432
4935
|
|
|
2433
4936
|
|
|
2434
4937
|
def load_series_cases(file_path: str | None) -> list[dict | str]:
|
|
@@ -2484,6 +4987,8 @@ def cmd_series(args: argparse.Namespace) -> int:
|
|
|
2484
4987
|
case["style"] = "; ".join([shared_style, str(case.get("style") or "").strip()]).strip("; ")
|
|
2485
4988
|
if args.palette:
|
|
2486
4989
|
case["palette"] = args.palette
|
|
4990
|
+
if args.style_preset:
|
|
4991
|
+
case["style_preset"] = args.style_preset
|
|
2487
4992
|
if args.strict_text:
|
|
2488
4993
|
case["strict_text"] = True
|
|
2489
4994
|
case.setdefault("text", extract_visual_labels(brief, str(case.get("asset_type") or route_asset_type(brief))))
|
|
@@ -2501,7 +5006,7 @@ def cmd_series(args: argparse.Namespace) -> int:
|
|
|
2501
5006
|
"spec": compiled["spec"],
|
|
2502
5007
|
}
|
|
2503
5008
|
)
|
|
2504
|
-
result = {"shared_style": shared_style, "count": len(variants), "variants": variants}
|
|
5009
|
+
result = {"shared_style": shared_style, "style_preset": args.style_preset or "auto", "count": len(variants), "variants": variants}
|
|
2505
5010
|
if args.json:
|
|
2506
5011
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
2507
5012
|
else:
|
|
@@ -2512,6 +5017,104 @@ def cmd_series(args: argparse.Namespace) -> int:
|
|
|
2512
5017
|
return 1 if any(has_lint_error(item["lint"]) for item in variants) else 0
|
|
2513
5018
|
|
|
2514
5019
|
|
|
5020
|
+
def cmd_variants(args: argparse.Namespace) -> int:
|
|
5021
|
+
request = read_text_argument(args.request_text, args.file)
|
|
5022
|
+
if not request:
|
|
5023
|
+
print("用法:variants \"<同一个画图需求>\" --style-presets corporate,premium,flat-vector", file=sys.stderr)
|
|
5024
|
+
return 2
|
|
5025
|
+
try:
|
|
5026
|
+
presets = parse_style_preset_list(args.style_presets, DEFAULT_VARIANT_PRESETS)
|
|
5027
|
+
except ValueError as exc:
|
|
5028
|
+
print(str(exc), file=sys.stderr)
|
|
5029
|
+
return 2
|
|
5030
|
+
custom_styles = args.custom_style or []
|
|
5031
|
+
if not presets and not custom_styles:
|
|
5032
|
+
print("至少提供一个 --style-presets 或 --custom-style。", file=sys.stderr)
|
|
5033
|
+
return 2
|
|
5034
|
+
|
|
5035
|
+
variants = []
|
|
5036
|
+
entries: list[dict] = [{"kind": "preset", "style_preset": preset, "style": args.shared_style} for preset in presets]
|
|
5037
|
+
for custom in custom_styles:
|
|
5038
|
+
entries.append(
|
|
5039
|
+
{
|
|
5040
|
+
"kind": "custom",
|
|
5041
|
+
"style_preset": "auto",
|
|
5042
|
+
"style": "; ".join([args.shared_style or "", custom]).strip("; "),
|
|
5043
|
+
"custom_style": custom,
|
|
5044
|
+
}
|
|
5045
|
+
)
|
|
5046
|
+
|
|
5047
|
+
for idx, entry in enumerate(entries, start=1):
|
|
5048
|
+
style_label = entry.get("custom_style") or entry["style_preset"]
|
|
5049
|
+
out = None
|
|
5050
|
+
if args.out_dir:
|
|
5051
|
+
out = str(Path(args.out_dir).expanduser() / f"variant-{idx:02d}-{safe_slug(str(style_label))}.png")
|
|
5052
|
+
case = {
|
|
5053
|
+
"id": f"variant-{idx:02d}",
|
|
5054
|
+
"request": request,
|
|
5055
|
+
"asset_type": args.asset_type,
|
|
5056
|
+
"template": args.template,
|
|
5057
|
+
"aspect": args.aspect,
|
|
5058
|
+
"text": args.text or [],
|
|
5059
|
+
"style": entry.get("style"),
|
|
5060
|
+
"style_preset": entry["style_preset"],
|
|
5061
|
+
"materials": args.materials,
|
|
5062
|
+
"lighting": args.lighting,
|
|
5063
|
+
"palette": args.palette,
|
|
5064
|
+
"size": args.size,
|
|
5065
|
+
"quality": args.quality,
|
|
5066
|
+
"strict_text": args.strict_text,
|
|
5067
|
+
"target": args.target,
|
|
5068
|
+
"out": out,
|
|
5069
|
+
"tags": "variants,multi-style",
|
|
5070
|
+
}
|
|
5071
|
+
compiled = compile_visual_case(case, target=args.target, out=out)
|
|
5072
|
+
variants.append(
|
|
5073
|
+
{
|
|
5074
|
+
"id": case["id"],
|
|
5075
|
+
"style_preset": entry["style_preset"],
|
|
5076
|
+
"custom_style": entry.get("custom_style"),
|
|
5077
|
+
"brief": request,
|
|
5078
|
+
"asset_type": compiled["spec"]["asset_type"],
|
|
5079
|
+
"template_id": compiled["spec"]["template_id"],
|
|
5080
|
+
"prompt": compiled["prompt"],
|
|
5081
|
+
"prompt_digest": compiled["prompt_digest"],
|
|
5082
|
+
"handoff": compiled["handoff"],
|
|
5083
|
+
"lint": compiled["lint"],
|
|
5084
|
+
"intent_check": compiled["intent_check"],
|
|
5085
|
+
"text_overlay_spec": compiled["text_overlay_spec"],
|
|
5086
|
+
"acceptance_criteria": compiled["acceptance_criteria"],
|
|
5087
|
+
"spec": compiled["spec"],
|
|
5088
|
+
}
|
|
5089
|
+
)
|
|
5090
|
+
|
|
5091
|
+
result = {
|
|
5092
|
+
"request": request,
|
|
5093
|
+
"count": len(variants),
|
|
5094
|
+
"style_presets": presets,
|
|
5095
|
+
"custom_styles": custom_styles,
|
|
5096
|
+
"variants": variants,
|
|
5097
|
+
"bundle_acceptance": [
|
|
5098
|
+
"所有版本必须保留同一个 User visual brief,不改变主题、主体、文字、布局和信息结构。",
|
|
5099
|
+
"不同版本只允许在 Style / quality envelope 中改变视觉语言、质感、光照、配色和呈现媒介。",
|
|
5100
|
+
"任一版本 lint 或 intent-check 出现 error 时,必须先修 prompt 再交给下游出图。",
|
|
5101
|
+
],
|
|
5102
|
+
}
|
|
5103
|
+
if args.json:
|
|
5104
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
5105
|
+
else:
|
|
5106
|
+
print(f"variants: {len(variants)} 个风格版本")
|
|
5107
|
+
for item in variants:
|
|
5108
|
+
status = "FAIL" if has_lint_error(item["lint"]) or has_lint_error(item["intent_check"]) else "PASS"
|
|
5109
|
+
label = item["custom_style"] or item["style_preset"]
|
|
5110
|
+
print(f"\n## {item['id']} style={label} [{status}]")
|
|
5111
|
+
print(item["prompt"])
|
|
5112
|
+
if item["handoff"]:
|
|
5113
|
+
print("\nHandoff:")
|
|
5114
|
+
print(item["handoff"])
|
|
5115
|
+
return 1 if any(has_lint_error(item["lint"]) or has_lint_error(item["intent_check"]) for item in variants) else 0
|
|
5116
|
+
|
|
5117
|
+
|
|
2515
5118
|
def parse_reference(value: str) -> dict:
|
|
2516
5119
|
if ":" in value and not value.startswith("/"):
|
|
2517
5120
|
role, _, ref = value.partition(":")
|
|
@@ -2602,6 +5205,7 @@ def cmd_brand(args: argparse.Namespace) -> int:
|
|
|
2602
5205
|
"request": f"{args.request}\n{brand_block}",
|
|
2603
5206
|
"asset_type": args.asset_type,
|
|
2604
5207
|
"style": brand_profile["style"],
|
|
5208
|
+
"style_preset": args.style_preset,
|
|
2605
5209
|
"palette": args.palette,
|
|
2606
5210
|
"text": args.text or [],
|
|
2607
5211
|
"strict_text": args.strict_text,
|
|
@@ -2646,6 +5250,7 @@ def cmd_character(args: argparse.Namespace) -> int:
|
|
|
2646
5250
|
"request": f"角色设定三视图和表情板:{identity}",
|
|
2647
5251
|
"asset_type": "character",
|
|
2648
5252
|
"style": bible["style"],
|
|
5253
|
+
"style_preset": args.style_preset,
|
|
2649
5254
|
"palette": args.palette,
|
|
2650
5255
|
"text": [args.name],
|
|
2651
5256
|
"target": args.target,
|
|
@@ -2658,6 +5263,7 @@ def cmd_character(args: argparse.Namespace) -> int:
|
|
|
2658
5263
|
"request": f"{identity} 场景图:{scene}",
|
|
2659
5264
|
"asset_type": "illustration",
|
|
2660
5265
|
"style": bible["style"],
|
|
5266
|
+
"style_preset": args.style_preset,
|
|
2661
5267
|
"palette": args.palette,
|
|
2662
5268
|
"target": args.target,
|
|
2663
5269
|
"tags": "character,scene",
|
|
@@ -2714,6 +5320,20 @@ def infer_chart_type(request: str, columns: list[str], override: str | None) ->
|
|
|
2714
5320
|
return "bar chart"
|
|
2715
5321
|
|
|
2716
5322
|
|
|
5323
|
+
def infer_chart_title(request: str, override: str | None) -> str:
|
|
5324
|
+
if override:
|
|
5325
|
+
return override
|
|
5326
|
+
patterns = [
|
|
5327
|
+
r"(?:标题为|标题是|title\s*(?:is|:|:))\s*([^,,。.;;\n]{2,40})",
|
|
5328
|
+
r"(?:标题|title)\s*[::]\s*([^,,。.;;\n]{2,40})",
|
|
5329
|
+
]
|
|
5330
|
+
for pattern in patterns:
|
|
5331
|
+
match = re.search(pattern, request, flags=re.IGNORECASE)
|
|
5332
|
+
if match:
|
|
5333
|
+
return match.group(1).strip(" \t\n\r,,。;;::.!?!?")
|
|
5334
|
+
return compact_title(request, 24)
|
|
5335
|
+
|
|
5336
|
+
|
|
2717
5337
|
def cmd_data_viz(args: argparse.Namespace) -> int:
|
|
2718
5338
|
request = args.request or "根据数据生成清晰的信息图"
|
|
2719
5339
|
try:
|
|
@@ -2721,7 +5341,7 @@ def cmd_data_viz(args: argparse.Namespace) -> int:
|
|
|
2721
5341
|
except (OSError, json.JSONDecodeError) as exc:
|
|
2722
5342
|
print(f"读取数据失败:{exc}", file=sys.stderr)
|
|
2723
5343
|
return 2
|
|
2724
|
-
title =
|
|
5344
|
+
title = infer_chart_title(request, args.title)
|
|
2725
5345
|
chart_type = infer_chart_type(request, data_preview["columns"], args.chart_type)
|
|
2726
5346
|
facts = {
|
|
2727
5347
|
"title": title,
|
|
@@ -2783,6 +5403,7 @@ def cmd_rewrite(args: argparse.Namespace) -> int:
|
|
|
2783
5403
|
"request": source,
|
|
2784
5404
|
"asset_type": args.asset_type,
|
|
2785
5405
|
"style": args.style,
|
|
5406
|
+
"style_preset": args.style_preset,
|
|
2786
5407
|
"strict_text": args.strict_text,
|
|
2787
5408
|
"text": args.text or [],
|
|
2788
5409
|
"target": args.target,
|
|
@@ -2839,6 +5460,7 @@ def cmd_adapt(args: argparse.Namespace) -> int:
|
|
|
2839
5460
|
"aspect": aspect,
|
|
2840
5461
|
"layout": adapt_layout_for_aspect(aspect, asset_type),
|
|
2841
5462
|
"style": args.style,
|
|
5463
|
+
"style_preset": args.style_preset,
|
|
2842
5464
|
"text": args.text or [],
|
|
2843
5465
|
"strict_text": args.strict_text,
|
|
2844
5466
|
"target": args.target,
|
|
@@ -2864,6 +5486,86 @@ def cmd_adapt(args: argparse.Namespace) -> int:
|
|
|
2864
5486
|
return 1 if any(has_lint_error(item["lint"]) for item in variants) else 0
|
|
2865
5487
|
|
|
2866
5488
|
|
|
5489
|
+
def default_skill_parent(target: str) -> Path:
|
|
5490
|
+
if target == "claude":
|
|
5491
|
+
return Path.home() / ".claude" / "skills"
|
|
5492
|
+
return Path.home() / ".codex" / "skills"
|
|
5493
|
+
|
|
5494
|
+
|
|
5495
|
+
def is_draw_prompt_install(path: Path) -> bool:
|
|
5496
|
+
skill = path / "SKILL.md"
|
|
5497
|
+
if not skill.exists():
|
|
5498
|
+
return False
|
|
5499
|
+
try:
|
|
5500
|
+
head = skill.read_text(encoding="utf-8")[:500]
|
|
5501
|
+
except OSError:
|
|
5502
|
+
return False
|
|
5503
|
+
return "name: draw-prompt" in head
|
|
5504
|
+
|
|
5505
|
+
|
|
5506
|
+
def copy_packaged_skill(src_root: Path, dst: Path) -> list[str]:
|
|
5507
|
+
copied = []
|
|
5508
|
+
for rel in PACKAGED_SKILL_FILES:
|
|
5509
|
+
src = src_root / rel
|
|
5510
|
+
if not src.exists():
|
|
5511
|
+
raise FileNotFoundError(f"缺少发布文件:{src}")
|
|
5512
|
+
target = dst / rel
|
|
5513
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
5514
|
+
shutil.copy2(src, target)
|
|
5515
|
+
copied.append(rel)
|
|
5516
|
+
return copied
|
|
5517
|
+
|
|
5518
|
+
|
|
5519
|
+
def install_skill_to_path(dst: Path, mode: str, force: bool) -> dict:
|
|
5520
|
+
src_root = package_root()
|
|
5521
|
+
dst = dst.expanduser()
|
|
5522
|
+
if dst.exists() or dst.is_symlink():
|
|
5523
|
+
if not force:
|
|
5524
|
+
if is_draw_prompt_install(dst):
|
|
5525
|
+
return {
|
|
5526
|
+
"path": str(dst),
|
|
5527
|
+
"status": "exists",
|
|
5528
|
+
"message": "draw-prompt skill 已存在;如需更新请加 --force。",
|
|
5529
|
+
"mode": mode,
|
|
5530
|
+
}
|
|
5531
|
+
raise FileExistsError(f"目标路径已存在且不像 draw-prompt skill:{dst}。如确认覆盖,请加 --force。")
|
|
5532
|
+
if dst.is_symlink() or dst.is_file():
|
|
5533
|
+
dst.unlink()
|
|
5534
|
+
else:
|
|
5535
|
+
shutil.rmtree(dst)
|
|
5536
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
5537
|
+
if mode == "symlink":
|
|
5538
|
+
dst.symlink_to(src_root, target_is_directory=True)
|
|
5539
|
+
copied = []
|
|
5540
|
+
else:
|
|
5541
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
5542
|
+
copied = copy_packaged_skill(src_root, dst)
|
|
5543
|
+
return {"path": str(dst), "status": "installed", "mode": mode, "files": copied, "source": str(src_root)}
|
|
5544
|
+
|
|
5545
|
+
|
|
5546
|
+
def cmd_install_skill(args: argparse.Namespace) -> int:
|
|
5547
|
+
targets = ["codex", "claude"] if args.target == "both" else [args.target]
|
|
5548
|
+
if args.path and len(targets) > 1:
|
|
5549
|
+
print("--path 只能和单个 --target 一起使用,不能用于 --target both。", file=sys.stderr)
|
|
5550
|
+
return 2
|
|
5551
|
+
results = []
|
|
5552
|
+
try:
|
|
5553
|
+
for target in targets:
|
|
5554
|
+
dst = Path(args.path).expanduser() if args.path else default_skill_parent(target) / "draw-prompt"
|
|
5555
|
+
results.append({"target": target, **install_skill_to_path(dst, args.mode, args.force)})
|
|
5556
|
+
except Exception as exc:
|
|
5557
|
+
print(f"install-skill 失败:{exc}", file=sys.stderr)
|
|
5558
|
+
return 2
|
|
5559
|
+
if args.json:
|
|
5560
|
+
print(json.dumps({"results": results}, ensure_ascii=False, indent=2))
|
|
5561
|
+
else:
|
|
5562
|
+
for item in results:
|
|
5563
|
+
print(f"{item['target']}: {item['status']} {item['path']} ({item['mode']})")
|
|
5564
|
+
if item.get("message"):
|
|
5565
|
+
print(f" {item['message']}")
|
|
5566
|
+
return 0
|
|
5567
|
+
|
|
5568
|
+
|
|
2867
5569
|
# --------------------------------------------------------------------------- #
|
|
2868
5570
|
# status
|
|
2869
5571
|
# --------------------------------------------------------------------------- #
|
|
@@ -2879,12 +5581,42 @@ def cmd_status(args: argparse.Namespace) -> int:
|
|
|
2879
5581
|
print(f" 自带范例库 : {own} ({'可用' if own.exists() else '缺失!'})")
|
|
2880
5582
|
gi = Path.home() / ".claude" / "skills" / "gpt-image" / "references" / "gallery.md"
|
|
2881
5583
|
print(f" 可选扩展库 : gpt-image ({'可用' if gi.exists() else '未装(不影响)'})")
|
|
5584
|
+
codex_skill = Path.home() / ".codex" / "skills" / "draw-prompt"
|
|
5585
|
+
claude_skill = Path.home() / ".claude" / "skills" / "draw-prompt"
|
|
5586
|
+
print(f" Codex skill: {codex_skill} ({'已安装' if is_draw_prompt_install(codex_skill) else '未安装,可运行 install-skill --target codex'})")
|
|
5587
|
+
print(f" Claude skill: {claude_skill} ({'已安装' if is_draw_prompt_install(claude_skill) else '未安装,可运行 install-skill --target claude'})")
|
|
2882
5588
|
print(" ── 下游出图通道(本 skill 不主动调用,仅提示可用性)──")
|
|
2883
5589
|
print(f" codex CLI : {which('codex') or '未找到'}")
|
|
2884
5590
|
plugin = Path.home() / ".claude" / "plugins" / "cache" / "codex-image-in-cc"
|
|
2885
5591
|
print(f" codex-image: {'已安装' if plugin.exists() else '未安装(可 /codex-image:generate 出图)'}")
|
|
2886
|
-
print(" 核心转化命令: convert / compose / series / edit / brand / character / data-viz / rewrite / adapt")
|
|
2887
|
-
print(" 稳定性命令 : overlay / visual-check / edit-check / visual-regress / lint / benchmark / revise")
|
|
5592
|
+
print(" 核心转化命令: convert / compose / variants / series / edit / brand / character / data-viz / rewrite / adapt")
|
|
5593
|
+
print(" 稳定性命令 : overlay / visual-check / edit-check / visual-regress / lint / intent-check / benchmark / revise / styles")
|
|
5594
|
+
return 0
|
|
5595
|
+
|
|
5596
|
+
|
|
5597
|
+
def cmd_styles(args: argparse.Namespace) -> int:
|
|
5598
|
+
query = (args.query or "").strip().lower()
|
|
5599
|
+
names = available_style_presets(include_auto=args.include_auto)
|
|
5600
|
+
presets = []
|
|
5601
|
+
for name in names:
|
|
5602
|
+
anchors = STYLE_PRESETS[name]
|
|
5603
|
+
haystack = f"{name} {' '.join(anchors)}".lower()
|
|
5604
|
+
if query and query not in haystack:
|
|
5605
|
+
continue
|
|
5606
|
+
presets.append({"name": name, "anchors": anchors})
|
|
5607
|
+
result = {
|
|
5608
|
+
"count": len(presets),
|
|
5609
|
+
"total": len(names),
|
|
5610
|
+
"default_variant_presets": DEFAULT_VARIANT_PRESETS,
|
|
5611
|
+
"presets": presets,
|
|
5612
|
+
}
|
|
5613
|
+
if args.json:
|
|
5614
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
5615
|
+
else:
|
|
5616
|
+
print(f"style presets: {len(presets)} / {len(names)}")
|
|
5617
|
+
print(f"default variants: {', '.join(DEFAULT_VARIANT_PRESETS)}")
|
|
5618
|
+
for item in presets:
|
|
5619
|
+
print(f"- {item['name']}: {'; '.join(item['anchors'])}")
|
|
2888
5620
|
return 0
|
|
2889
5621
|
|
|
2890
5622
|
|
|
@@ -2895,6 +5627,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2895
5627
|
p = argparse.ArgumentParser(prog="prompt_cli.py", description="draw-prompt:自然语言画图需求 -> 生图 Prompt / handoff(不主动出图)")
|
|
2896
5628
|
sub = p.add_subparsers(dest="cmd", required=True)
|
|
2897
5629
|
|
|
5630
|
+
pis = sub.add_parser("install-skill", help="把当前包安装到 Codex/Claude skills 目录")
|
|
5631
|
+
pis.add_argument("--target", choices=["codex", "claude", "both"], default="codex", help="默认安装到 ~/.codex/skills/draw-prompt")
|
|
5632
|
+
pis.add_argument("--path", help="覆盖安装目标路径;只能和单个 target 一起用")
|
|
5633
|
+
pis.add_argument("--mode", choices=["copy", "symlink"], default="copy", help="默认复制发布文件,避免 npx 缓存路径失效")
|
|
5634
|
+
pis.add_argument("--force", action="store_true", help="覆盖已有 draw-prompt skill")
|
|
5635
|
+
pis.add_argument("--json", action="store_true")
|
|
5636
|
+
pis.set_defaults(func=cmd_install_skill)
|
|
5637
|
+
|
|
2898
5638
|
pc = sub.add_parser("convert", help="自然语言画图需求 -> 高质量生图 Prompt / handoff")
|
|
2899
5639
|
pc.add_argument("request_text", nargs="+", help="自然语言画图需求")
|
|
2900
5640
|
pc.add_argument("--asset-type", choices=sorted(ASSET_ROUTES.keys()), help="覆盖自动识别的资产类型")
|
|
@@ -2904,6 +5644,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2904
5644
|
pc.add_argument("--subject", help="覆盖主体描述")
|
|
2905
5645
|
pc.add_argument("--layout", help="覆盖布局规格")
|
|
2906
5646
|
pc.add_argument("--style", help="风格锚点,逗号分隔")
|
|
5647
|
+
pc.add_argument("--style-preset", choices=sorted(STYLE_PRESETS.keys()), help="风格预设,只控制风格/质量层,不改用户内容")
|
|
2907
5648
|
pc.add_argument("--materials", help="材料/质感,逗号分隔")
|
|
2908
5649
|
pc.add_argument("--lighting", help="光照描述")
|
|
2909
5650
|
pc.add_argument("--palette", help="配色,逗号分隔")
|
|
@@ -2912,7 +5653,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2912
5653
|
pc.add_argument("--out", help="期望输出路径")
|
|
2913
5654
|
pc.add_argument("--tags", help="额外样本标签,逗号分隔")
|
|
2914
5655
|
pc.add_argument("--target", choices=["codex-image", "codex-exec", "raw"], default="codex-image")
|
|
2915
|
-
pc.add_argument("--strict-text", action="store_true", help="
|
|
5656
|
+
pc.add_argument("--strict-text", action="store_true", help="可选兜底:输出 visual prompt + text_overlay_spec,用于文字必须绝对精确的场景")
|
|
2916
5657
|
pc.add_argument("--record-pending", action="store_true", help="把本次转化记录为 pending 样本")
|
|
2917
5658
|
pc.add_argument("--no-handoff", action="store_true", help="只输出 Prompt,不输出下游指令")
|
|
2918
5659
|
pc.add_argument("--json", action="store_true")
|
|
@@ -2923,8 +5664,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2923
5664
|
pco.add_argument("--file", help="从文件读取长输入")
|
|
2924
5665
|
pco.add_argument("--max-images", type=int, default=6, help="最多拆成多少张图")
|
|
2925
5666
|
pco.add_argument("--shared-style", help="整组图共享风格锚点")
|
|
5667
|
+
pco.add_argument("--style-preset", choices=sorted(STYLE_PRESETS.keys()), help="整组风格预设,只控制风格/质量层")
|
|
2926
5668
|
pco.add_argument("--palette", help="整组图共享配色,逗号分隔")
|
|
2927
|
-
pco.add_argument("--strict-text", action="store_true", help="
|
|
5669
|
+
pco.add_argument("--strict-text", action="store_true", help="可选兜底:文字/标签走 overlay_spec")
|
|
2928
5670
|
pco.add_argument("--out-dir", help="为每张 handoff 生成建议输出路径")
|
|
2929
5671
|
pco.add_argument("--target", choices=["codex-image", "codex-exec", "raw"], default="codex-image")
|
|
2930
5672
|
pco.add_argument("--json", action="store_true")
|
|
@@ -2935,12 +5677,34 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2935
5677
|
psr.add_argument("--file", help="JSON/JSONL/纯文本 briefs")
|
|
2936
5678
|
psr.add_argument("--asset-type", choices=sorted(ASSET_ROUTES.keys()), help="覆盖所有 brief 的资产类型")
|
|
2937
5679
|
psr.add_argument("--style", help="整组图共享风格")
|
|
5680
|
+
psr.add_argument("--style-preset", choices=sorted(STYLE_PRESETS.keys()), help="整组风格预设,只控制风格/质量层")
|
|
2938
5681
|
psr.add_argument("--palette", help="整组图共享配色")
|
|
2939
5682
|
psr.add_argument("--strict-text", action="store_true")
|
|
2940
5683
|
psr.add_argument("--target", choices=["codex-image", "codex-exec", "raw"], default="codex-image")
|
|
2941
5684
|
psr.add_argument("--json", action="store_true")
|
|
2942
5685
|
psr.set_defaults(func=cmd_series)
|
|
2943
5686
|
|
|
5687
|
+
pv = sub.add_parser("variants", help="同一需求 -> 多风格 Prompt 组")
|
|
5688
|
+
pv.add_argument("request_text", nargs="*", help="同一个自然语言画图需求;也可用 --file")
|
|
5689
|
+
pv.add_argument("--file", help="从文件读取画图需求")
|
|
5690
|
+
pv.add_argument("--style-presets", default=",".join(DEFAULT_VARIANT_PRESETS), help="逗号分隔风格预设;可用 all 生成全部内置预设")
|
|
5691
|
+
pv.add_argument("--custom-style", action="append", help="额外自定义风格版本,可重复")
|
|
5692
|
+
pv.add_argument("--shared-style", help="所有风格版本共同附加的风格约束")
|
|
5693
|
+
pv.add_argument("--asset-type", choices=sorted(ASSET_ROUTES.keys()), help="覆盖自动识别的资产类型")
|
|
5694
|
+
pv.add_argument("--template", choices=sorted(TEMPLATE_DEFS.keys()), help="覆盖细分模板")
|
|
5695
|
+
pv.add_argument("--aspect", help="覆盖画幅,如 3:4 / 16:9 / 1:1")
|
|
5696
|
+
pv.add_argument("--text", action="append", help="必须逐字显示的文字,可重复")
|
|
5697
|
+
pv.add_argument("--materials", help="材料/质感,逗号分隔")
|
|
5698
|
+
pv.add_argument("--lighting", help="光照描述")
|
|
5699
|
+
pv.add_argument("--palette", help="配色,逗号分隔")
|
|
5700
|
+
pv.add_argument("--size", help="size 预设或像素,如 portrait / 1024x1536")
|
|
5701
|
+
pv.add_argument("--quality", choices=["low", "medium", "high"], help="质量档")
|
|
5702
|
+
pv.add_argument("--out-dir", help="为每个 handoff 生成建议输出路径")
|
|
5703
|
+
pv.add_argument("--strict-text", action="store_true", help="可选兜底:文字/标签走 overlay_spec")
|
|
5704
|
+
pv.add_argument("--target", choices=["codex-image", "codex-exec", "raw"], default="codex-image")
|
|
5705
|
+
pv.add_argument("--json", action="store_true")
|
|
5706
|
+
pv.set_defaults(func=cmd_variants)
|
|
5707
|
+
|
|
2944
5708
|
pe = sub.add_parser("edit", help="参考图/改图需求 -> 编辑 Prompt")
|
|
2945
5709
|
pe.add_argument("--goal", required=True, help="改图目标")
|
|
2946
5710
|
pe.add_argument("--reference", action="append", help="参考图,格式 role:path 或 path,可重复")
|
|
@@ -2962,6 +5726,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2962
5726
|
pbr.add_argument("--values", help="品牌价值,逗号分隔")
|
|
2963
5727
|
pbr.add_argument("--palette", help="品牌配色,逗号分隔")
|
|
2964
5728
|
pbr.add_argument("--style", help="品牌视觉风格")
|
|
5729
|
+
pbr.add_argument("--style-preset", choices=sorted(STYLE_PRESETS.keys()), help="编译品牌资产时的风格预设")
|
|
2965
5730
|
pbr.add_argument("--avoid", help="禁用项,逗号分隔")
|
|
2966
5731
|
pbr.add_argument("--request", help="可选:直接用品牌档案编译一张图")
|
|
2967
5732
|
pbr.add_argument("--asset-type", choices=sorted(ASSET_ROUTES.keys()), default="poster")
|
|
@@ -2975,6 +5740,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2975
5740
|
pch.add_argument("--name", required=True)
|
|
2976
5741
|
pch.add_argument("--description", required=True)
|
|
2977
5742
|
pch.add_argument("--style")
|
|
5743
|
+
pch.add_argument("--style-preset", choices=sorted(STYLE_PRESETS.keys()), help="角色图编译时的风格预设")
|
|
2978
5744
|
pch.add_argument("--outfit")
|
|
2979
5745
|
pch.add_argument("--palette")
|
|
2980
5746
|
pch.add_argument("--scene", action="append", help="用同一角色生成的场景,可重复")
|
|
@@ -2996,6 +5762,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2996
5762
|
prw.add_argument("--file", help="从文件读取原始 prompt")
|
|
2997
5763
|
prw.add_argument("--asset-type", choices=sorted(ASSET_ROUTES.keys()))
|
|
2998
5764
|
prw.add_argument("--style")
|
|
5765
|
+
prw.add_argument("--style-preset", choices=sorted(STYLE_PRESETS.keys()), help="风格预设,只控制风格/质量层,不改用户内容")
|
|
2999
5766
|
prw.add_argument("--text", action="append", help="必须逐字显示的文字,可重复")
|
|
3000
5767
|
prw.add_argument("--strict-text", action="store_true")
|
|
3001
5768
|
prw.add_argument("--target", choices=["codex-image", "codex-exec", "raw"], default="codex-image")
|
|
@@ -3007,6 +5774,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3007
5774
|
pad.add_argument("--aspects", default="1:1,3:4,16:9,9:16", help="逗号分隔画幅列表")
|
|
3008
5775
|
pad.add_argument("--asset-type", choices=sorted(ASSET_ROUTES.keys()))
|
|
3009
5776
|
pad.add_argument("--style")
|
|
5777
|
+
pad.add_argument("--style-preset", choices=sorted(STYLE_PRESETS.keys()), help="风格预设,只控制风格/质量层,不改用户内容")
|
|
3010
5778
|
pad.add_argument("--text", action="append", help="必须逐字显示的文字,可重复")
|
|
3011
5779
|
pad.add_argument("--strict-text", action="store_true")
|
|
3012
5780
|
pad.add_argument("--target", choices=["codex-image", "codex-exec", "raw"], default="codex-image")
|
|
@@ -3055,6 +5823,21 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3055
5823
|
pl.add_argument("--json", action="store_true")
|
|
3056
5824
|
pl.set_defaults(func=cmd_lint)
|
|
3057
5825
|
|
|
5826
|
+
pst = sub.add_parser("styles", help="列出内置风格预设")
|
|
5827
|
+
pst.add_argument("--query", help="按名称或描述过滤")
|
|
5828
|
+
pst.add_argument("--include-auto", action="store_true", help="包含 auto")
|
|
5829
|
+
pst.add_argument("--json", action="store_true")
|
|
5830
|
+
pst.set_defaults(func=cmd_styles)
|
|
5831
|
+
|
|
5832
|
+
pic = sub.add_parser("intent-check", help="检查生成 Prompt 是否偏离或添加用户未要求内容")
|
|
5833
|
+
pic.add_argument("--request", nargs="+", help="原始用户需求")
|
|
5834
|
+
pic.add_argument("--request-file", help="从文件读取原始用户需求")
|
|
5835
|
+
pic.add_argument("--prompt", nargs="+", help="生成后的 prompt")
|
|
5836
|
+
pic.add_argument("--prompt-file", help="从文件读取生成后的 prompt")
|
|
5837
|
+
pic.add_argument("--spec", help="可选 spec JSON 或 @path,用于检查 required_text")
|
|
5838
|
+
pic.add_argument("--json", action="store_true")
|
|
5839
|
+
pic.set_defaults(func=cmd_intent_check)
|
|
5840
|
+
|
|
3058
5841
|
pb = sub.add_parser("benchmark", help="批量跑 golden cases,检查转化稳定性和 lint")
|
|
3059
5842
|
pb.add_argument("cases", help="JSONL 或 JSON cases 文件")
|
|
3060
5843
|
pb.add_argument("--runs", type=int, default=3, help="每个 case 重复转化次数,用于检查确定性")
|