@yuhan1124/draw-prompt 0.4.0 → 0.4.1
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 +59 -24
- package/SKILL.md +53 -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 +2755 -125
- 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 交付块生成。
|
|
@@ -16,6 +16,7 @@ codex exec)。本 CLI 的 `handoff` 子命令负责把 prompt 包装成一段
|
|
|
16
16
|
CLI 只干确定性的脏活:
|
|
17
17
|
convert 自然语言画图需求 -> Prompt / handoff
|
|
18
18
|
compose 长输入/文档 -> 多张配套图的视觉计划 + Prompt
|
|
19
|
+
variants 同一需求 -> 多风格 Prompt 组
|
|
19
20
|
series 多张同风格系列图 -> 稳定一致的 Prompt 组
|
|
20
21
|
edit 参考图/改图需求 -> 保留项、修改项、编辑 Prompt
|
|
21
22
|
brand 品牌风格档案 -> 可复用品牌 Prompt 块
|
|
@@ -33,6 +34,7 @@ CLI 只干确定性的脏活:
|
|
|
33
34
|
feedback 对样本回填采纳 / 弃用
|
|
34
35
|
judge 存储 agent 给出的评分(CLI 不评分、不看图、不调 Codex)
|
|
35
36
|
handoff 生成交给 Codex 的现成指令块(仅打印,不执行)
|
|
37
|
+
styles 列出内置风格预设
|
|
36
38
|
status 数据与下游通道健康检查
|
|
37
39
|
|
|
38
40
|
所有运行时数据写到数据目录(默认 ~/.local/share/draw-prompt/,可用
|
|
@@ -131,7 +133,7 @@ def ensure_home() -> None:
|
|
|
131
133
|
|
|
132
134
|
|
|
133
135
|
SCHEMA_VERSION = 1
|
|
134
|
-
COMPILER_VERSION = "0.4.
|
|
136
|
+
COMPILER_VERSION = "0.4.1"
|
|
135
137
|
|
|
136
138
|
|
|
137
139
|
# --------------------------------------------------------------------------- #
|
|
@@ -140,6 +142,7 @@ COMPILER_VERSION = "0.4.0"
|
|
|
140
142
|
PROFILE_TEMPLATE_FM = {
|
|
141
143
|
"default_aspect": "未设置",
|
|
142
144
|
"default_quality": "high",
|
|
145
|
+
"default_style_preset": "auto",
|
|
143
146
|
"favored_styles": [],
|
|
144
147
|
"avoided_elements": [],
|
|
145
148
|
"text_language": "zh",
|
|
@@ -280,7 +283,7 @@ ASSET_ROUTES = {
|
|
|
280
283
|
"ui": {
|
|
281
284
|
"label": "ui",
|
|
282
285
|
"title": "UI / 产品界面",
|
|
283
|
-
"keywords": ["ui", "界面", "app", "dashboard", "仪表盘", "网页", "web", "小程序", "mockup"],
|
|
286
|
+
"keywords": ["ui", "界面", "app", "dashboard", "仪表盘", "看板", "saas", "后台", "控制台", "网页", "web", "小程序", "mockup"],
|
|
284
287
|
"aspect": "9:16",
|
|
285
288
|
"size": "portrait",
|
|
286
289
|
"quality": "high",
|
|
@@ -295,6 +298,29 @@ ASSET_ROUTES = {
|
|
|
295
298
|
"quality": "high",
|
|
296
299
|
"tags": ["infographic"],
|
|
297
300
|
},
|
|
301
|
+
"slide": {
|
|
302
|
+
"label": "slide",
|
|
303
|
+
"title": "PPT / 汇报单页",
|
|
304
|
+
"keywords": [
|
|
305
|
+
"ppt",
|
|
306
|
+
"powerpoint",
|
|
307
|
+
"slide",
|
|
308
|
+
"slides",
|
|
309
|
+
"presentation",
|
|
310
|
+
"deck",
|
|
311
|
+
"widescreen",
|
|
312
|
+
"幻灯片",
|
|
313
|
+
"演示文稿",
|
|
314
|
+
"汇报页",
|
|
315
|
+
"汇报单页",
|
|
316
|
+
"企业汇报",
|
|
317
|
+
"PPT",
|
|
318
|
+
],
|
|
319
|
+
"aspect": "16:9",
|
|
320
|
+
"size": "landscape",
|
|
321
|
+
"quality": "high",
|
|
322
|
+
"tags": ["slide", "presentation"],
|
|
323
|
+
},
|
|
298
324
|
"diagram": {
|
|
299
325
|
"label": "diagram",
|
|
300
326
|
"title": "学术 / 系统架构图",
|
|
@@ -343,7 +369,7 @@ ASSET_ROUTES = {
|
|
|
343
369
|
"logo": {
|
|
344
370
|
"label": "logo",
|
|
345
371
|
"title": "Logo / 品牌系统板",
|
|
346
|
-
"keywords": ["logo", "标识", "
|
|
372
|
+
"keywords": ["logo", "标识", "品牌标识", "字标", "brand identity", "visual identity", "vi"],
|
|
347
373
|
"aspect": "1:1",
|
|
348
374
|
"size": "square",
|
|
349
375
|
"quality": "high",
|
|
@@ -362,6 +388,1692 @@ STYLE_HINTS = [
|
|
|
362
388
|
(["写实", "真实"], "realistic, natural, unprocessed visual language"),
|
|
363
389
|
]
|
|
364
390
|
|
|
391
|
+
STYLE_PRESETS = {
|
|
392
|
+
"auto": [],
|
|
393
|
+
"corporate": [
|
|
394
|
+
"quiet corporate communication style",
|
|
395
|
+
"clean grid, restrained contrast, executive presentation polish",
|
|
396
|
+
],
|
|
397
|
+
"premium": [
|
|
398
|
+
"premium editorial finish",
|
|
399
|
+
"controlled palette, tactile materials, generous negative space",
|
|
400
|
+
],
|
|
401
|
+
"minimal": [
|
|
402
|
+
"minimal clean visual system",
|
|
403
|
+
"precise spacing, low clutter, strong hierarchy",
|
|
404
|
+
],
|
|
405
|
+
"flat-vector": [
|
|
406
|
+
"modern flat vector language",
|
|
407
|
+
"crisp geometric forms, clean fills, consistent stroke weights",
|
|
408
|
+
],
|
|
409
|
+
"photoreal": [
|
|
410
|
+
"photorealistic rendering",
|
|
411
|
+
"natural material response, believable lighting, camera-real detail",
|
|
412
|
+
],
|
|
413
|
+
"clean-ui": [
|
|
414
|
+
"production-grade UI visual language",
|
|
415
|
+
"consistent components, dense but readable layout, precise spacing",
|
|
416
|
+
],
|
|
417
|
+
"editorial": [
|
|
418
|
+
"magazine editorial art direction",
|
|
419
|
+
"strong typographic hierarchy, refined cropping, deliberate negative space",
|
|
420
|
+
],
|
|
421
|
+
"cinematic": [
|
|
422
|
+
"cinematic visual language",
|
|
423
|
+
"dramatic but controlled lighting, lens depth, atmospheric composition",
|
|
424
|
+
],
|
|
425
|
+
"isometric": [
|
|
426
|
+
"clean isometric illustration style",
|
|
427
|
+
"precise geometric perspective, modular geometry, balanced depth",
|
|
428
|
+
],
|
|
429
|
+
"technical-blueprint": [
|
|
430
|
+
"technical blueprint visual language",
|
|
431
|
+
"thin precise lines, annotation discipline, structured spatial logic",
|
|
432
|
+
],
|
|
433
|
+
"hand-drawn": [
|
|
434
|
+
"hand-drawn illustration style",
|
|
435
|
+
"visible linework, tactile paper texture, warm organic imperfections",
|
|
436
|
+
],
|
|
437
|
+
"watercolor": [
|
|
438
|
+
"soft watercolor illustration style",
|
|
439
|
+
"transparent washes, gentle edges, paper grain texture",
|
|
440
|
+
],
|
|
441
|
+
"retro-print": [
|
|
442
|
+
"tasteful retro print design",
|
|
443
|
+
"halftone texture, restrained vintage palette, contemporary composition",
|
|
444
|
+
],
|
|
445
|
+
"bold-typographic": [
|
|
446
|
+
"bold typographic poster language",
|
|
447
|
+
"large type hierarchy, strong contrast, disciplined graphic blocks",
|
|
448
|
+
],
|
|
449
|
+
"data-journalism": [
|
|
450
|
+
"data journalism visual style",
|
|
451
|
+
"clear annotation system, evidence-first layout, sober color coding",
|
|
452
|
+
],
|
|
453
|
+
"luxury-product": [
|
|
454
|
+
"luxury product advertising style",
|
|
455
|
+
"premium material emphasis, controlled reflections, elegant restraint",
|
|
456
|
+
],
|
|
457
|
+
"soft-3d": [
|
|
458
|
+
"soft 3D illustration style",
|
|
459
|
+
"rounded forms, clay-like material, gentle studio lighting",
|
|
460
|
+
],
|
|
461
|
+
"monochrome-ink": [
|
|
462
|
+
"monochrome ink visual language",
|
|
463
|
+
"high-contrast black and white, expressive line weight, clean whitespace",
|
|
464
|
+
],
|
|
465
|
+
"executive-consulting": [
|
|
466
|
+
"executive consulting deck polish",
|
|
467
|
+
"precise hierarchy, restrained accents, boardroom clarity",
|
|
468
|
+
],
|
|
469
|
+
"annual-report": [
|
|
470
|
+
"annual report editorial design",
|
|
471
|
+
"measured typography, structured spreads, credible institutional tone",
|
|
472
|
+
],
|
|
473
|
+
"keynote": [
|
|
474
|
+
"premium keynote presentation style",
|
|
475
|
+
"large confident type, cinematic pacing, sparse supporting detail",
|
|
476
|
+
],
|
|
477
|
+
"swiss": [
|
|
478
|
+
"Swiss International Typographic style",
|
|
479
|
+
"strict grid discipline, asymmetric balance, clean sans-serif rhythm",
|
|
480
|
+
],
|
|
481
|
+
"bauhaus": [
|
|
482
|
+
"Bauhaus graphic design language",
|
|
483
|
+
"primary geometry, functional composition, disciplined color blocking",
|
|
484
|
+
],
|
|
485
|
+
"brutalist": [
|
|
486
|
+
"brutalist graphic design",
|
|
487
|
+
"raw hierarchy, stark contrast, utilitarian spacing",
|
|
488
|
+
],
|
|
489
|
+
"memphis": [
|
|
490
|
+
"Memphis-inspired postmodern design",
|
|
491
|
+
"playful geometry, patterned accents, controlled visual energy",
|
|
492
|
+
],
|
|
493
|
+
"art-deco": [
|
|
494
|
+
"Art Deco visual language",
|
|
495
|
+
"symmetry, stepped geometry, metallic elegance, refined ornament",
|
|
496
|
+
],
|
|
497
|
+
"art-nouveau": [
|
|
498
|
+
"Art Nouveau inspired ornament",
|
|
499
|
+
"flowing linework, botanical curves, elegant decorative rhythm",
|
|
500
|
+
],
|
|
501
|
+
"constructivist": [
|
|
502
|
+
"constructivist poster language",
|
|
503
|
+
"diagonal structure, bold geometry, urgent graphic rhythm",
|
|
504
|
+
],
|
|
505
|
+
"de-stijl": [
|
|
506
|
+
"De Stijl inspired composition",
|
|
507
|
+
"orthogonal geometry, primary color accents, balanced whitespace",
|
|
508
|
+
],
|
|
509
|
+
"suprematist": [
|
|
510
|
+
"suprematist abstract design language",
|
|
511
|
+
"floating geometry, sparse field, controlled asymmetry",
|
|
512
|
+
],
|
|
513
|
+
"mid-century": [
|
|
514
|
+
"mid-century modern visual style",
|
|
515
|
+
"warm geometry, simplified shapes, optimistic color restraint",
|
|
516
|
+
],
|
|
517
|
+
"scandinavian": [
|
|
518
|
+
"Scandinavian design restraint",
|
|
519
|
+
"quiet neutrals, natural material feel, airy functional spacing",
|
|
520
|
+
],
|
|
521
|
+
"wabi-sabi": [
|
|
522
|
+
"wabi-sabi visual restraint",
|
|
523
|
+
"imperfect texture, muted natural tones, calm asymmetry",
|
|
524
|
+
],
|
|
525
|
+
"new-chinese": [
|
|
526
|
+
"New Chinese visual style",
|
|
527
|
+
"restrained oriental composition, ink-like balance, modern elegance",
|
|
528
|
+
],
|
|
529
|
+
"zen-minimal": [
|
|
530
|
+
"zen minimal visual language",
|
|
531
|
+
"quiet emptiness, calm proportions, soft tonal contrast",
|
|
532
|
+
],
|
|
533
|
+
"ink-wash": [
|
|
534
|
+
"ink wash illustration style",
|
|
535
|
+
"soft tonal gradients, expressive brush texture, quiet negative space",
|
|
536
|
+
],
|
|
537
|
+
"ukiyo-e": [
|
|
538
|
+
"ukiyo-e inspired print language",
|
|
539
|
+
"flat color fields, elegant linework, woodblock texture",
|
|
540
|
+
],
|
|
541
|
+
"woodblock": [
|
|
542
|
+
"woodblock print texture",
|
|
543
|
+
"carved line edges, ink pressure variation, handmade print grain",
|
|
544
|
+
],
|
|
545
|
+
"linocut": [
|
|
546
|
+
"linocut print style",
|
|
547
|
+
"bold carved contours, limited palette, tactile ink coverage",
|
|
548
|
+
],
|
|
549
|
+
"risograph": [
|
|
550
|
+
"risograph print style",
|
|
551
|
+
"spot-color layering, slight registration offsets, paper grain",
|
|
552
|
+
],
|
|
553
|
+
"screenprint": [
|
|
554
|
+
"screenprint poster style",
|
|
555
|
+
"flat ink layers, crisp separations, tactile print texture",
|
|
556
|
+
],
|
|
557
|
+
"collage": [
|
|
558
|
+
"editorial collage style",
|
|
559
|
+
"cut-paper layering, mixed texture, disciplined visual contrast",
|
|
560
|
+
],
|
|
561
|
+
"paper-cut": [
|
|
562
|
+
"paper-cut illustration style",
|
|
563
|
+
"layered paper edges, soft shadow depth, crafted tactile surfaces",
|
|
564
|
+
],
|
|
565
|
+
"origami": [
|
|
566
|
+
"origami-inspired folded paper style",
|
|
567
|
+
"faceted paper planes, crisp folds, clean geometric shadows",
|
|
568
|
+
],
|
|
569
|
+
"gouache": [
|
|
570
|
+
"gouache illustration style",
|
|
571
|
+
"opaque pigment texture, soft matte surfaces, painterly edges",
|
|
572
|
+
],
|
|
573
|
+
"airbrush": [
|
|
574
|
+
"airbrush illustration finish",
|
|
575
|
+
"smooth gradients, soft specular bloom, polished retro futurism",
|
|
576
|
+
],
|
|
577
|
+
"oil-paint": [
|
|
578
|
+
"oil painting inspired finish",
|
|
579
|
+
"visible brush texture, layered pigment depth, rich tonal blending",
|
|
580
|
+
],
|
|
581
|
+
"acrylic": [
|
|
582
|
+
"acrylic paint inspired finish",
|
|
583
|
+
"opaque color blocks, expressive edges, matte surface texture",
|
|
584
|
+
],
|
|
585
|
+
"pastel-pencil": [
|
|
586
|
+
"pastel pencil illustration style",
|
|
587
|
+
"powdery color texture, soft edges, tactile drawing surface",
|
|
588
|
+
],
|
|
589
|
+
"charcoal": [
|
|
590
|
+
"charcoal drawing style",
|
|
591
|
+
"rich smudged blacks, expressive tonal range, textured paper",
|
|
592
|
+
],
|
|
593
|
+
"graphite": [
|
|
594
|
+
"graphite sketch style",
|
|
595
|
+
"fine tonal shading, precise line pressure, paper tooth texture",
|
|
596
|
+
],
|
|
597
|
+
"line-art": [
|
|
598
|
+
"clean line art style",
|
|
599
|
+
"confident contours, minimal fill, precise negative space",
|
|
600
|
+
],
|
|
601
|
+
"duotone": [
|
|
602
|
+
"duotone graphic style",
|
|
603
|
+
"two-color discipline, strong value contrast, simplified hierarchy",
|
|
604
|
+
],
|
|
605
|
+
"pastel-pop": [
|
|
606
|
+
"pastel pop visual style",
|
|
607
|
+
"soft saturated palette, friendly contrast, clean rounded forms",
|
|
608
|
+
],
|
|
609
|
+
"earth-tone": [
|
|
610
|
+
"earth-tone visual palette",
|
|
611
|
+
"warm natural colors, grounded contrast, tactile material feel",
|
|
612
|
+
],
|
|
613
|
+
"high-contrast": [
|
|
614
|
+
"high-contrast graphic language",
|
|
615
|
+
"bold value separation, sharp hierarchy, assertive clarity",
|
|
616
|
+
],
|
|
617
|
+
"muted-luxury": [
|
|
618
|
+
"muted luxury visual style",
|
|
619
|
+
"quiet neutrals, refined accents, premium restraint",
|
|
620
|
+
],
|
|
621
|
+
"pixel-art": [
|
|
622
|
+
"pixel art visual style",
|
|
623
|
+
"limited resolution grid, crisp pixel edges, controlled palette",
|
|
624
|
+
],
|
|
625
|
+
"voxel": [
|
|
626
|
+
"voxel 3D style",
|
|
627
|
+
"blocky volumetric geometry, crisp lighting, playful depth",
|
|
628
|
+
],
|
|
629
|
+
"low-poly": [
|
|
630
|
+
"low-poly 3D style",
|
|
631
|
+
"faceted geometry, simplified planes, clean directional lighting",
|
|
632
|
+
],
|
|
633
|
+
"clay-render": [
|
|
634
|
+
"clay render 3D style",
|
|
635
|
+
"matte sculpted surfaces, soft shadows, simplified form language",
|
|
636
|
+
],
|
|
637
|
+
"hard-surface-3d": [
|
|
638
|
+
"hard-surface 3D rendering",
|
|
639
|
+
"precise bevels, clean industrial surfaces, controlled reflections",
|
|
640
|
+
],
|
|
641
|
+
"chrome": [
|
|
642
|
+
"chrome material visual style",
|
|
643
|
+
"mirror-like reflections, sharp highlights, futuristic polish",
|
|
644
|
+
],
|
|
645
|
+
"glassmorphism": [
|
|
646
|
+
"glassmorphism visual language",
|
|
647
|
+
"translucent layers, soft blur, luminous edge highlights",
|
|
648
|
+
],
|
|
649
|
+
"holographic": [
|
|
650
|
+
"holographic visual finish",
|
|
651
|
+
"iridescent gradients, luminous refraction, futuristic material shimmer",
|
|
652
|
+
],
|
|
653
|
+
"iridescent": [
|
|
654
|
+
"iridescent material palette",
|
|
655
|
+
"shifting spectral highlights, pearlescent sheen, refined glow",
|
|
656
|
+
],
|
|
657
|
+
"neon-noir": [
|
|
658
|
+
"neon noir visual style",
|
|
659
|
+
"deep shadows, controlled neon accents, cinematic contrast",
|
|
660
|
+
],
|
|
661
|
+
"cyberpunk": [
|
|
662
|
+
"cyberpunk visual style",
|
|
663
|
+
"neon tech palette, dense luminous detail, dark futuristic atmosphere",
|
|
664
|
+
],
|
|
665
|
+
"synthwave": [
|
|
666
|
+
"synthwave visual style",
|
|
667
|
+
"magenta-cyan palette, retro-futurist glow, graphic horizon rhythm",
|
|
668
|
+
],
|
|
669
|
+
"vaporwave": [
|
|
670
|
+
"vaporwave visual style",
|
|
671
|
+
"soft surreal gradients, retro digital polish, pastel neon balance",
|
|
672
|
+
],
|
|
673
|
+
"y2k": [
|
|
674
|
+
"Y2K digital visual style",
|
|
675
|
+
"glossy surfaces, chrome accents, early-digital optimism",
|
|
676
|
+
],
|
|
677
|
+
"cyber-minimal": [
|
|
678
|
+
"cyber minimal visual language",
|
|
679
|
+
"dark clean surfaces, precise luminous accents, high-tech restraint",
|
|
680
|
+
],
|
|
681
|
+
"gradient-mesh": [
|
|
682
|
+
"gradient mesh visual style",
|
|
683
|
+
"smooth color fields, subtle depth, contemporary digital polish",
|
|
684
|
+
],
|
|
685
|
+
"high-key-photo": [
|
|
686
|
+
"high-key photographic style",
|
|
687
|
+
"bright tonal range, soft shadows, clean airy polish",
|
|
688
|
+
],
|
|
689
|
+
"low-key-photo": [
|
|
690
|
+
"low-key photographic style",
|
|
691
|
+
"deep controlled shadows, selective highlights, dramatic restraint",
|
|
692
|
+
],
|
|
693
|
+
"analog-film": [
|
|
694
|
+
"analog film photographic look",
|
|
695
|
+
"natural grain, soft highlight rolloff, tactile color response",
|
|
696
|
+
],
|
|
697
|
+
"polaroid": [
|
|
698
|
+
"instant film photographic look",
|
|
699
|
+
"soft contrast, nostalgic color shift, tactile print feel",
|
|
700
|
+
],
|
|
701
|
+
"macro-product": [
|
|
702
|
+
"macro product photography style",
|
|
703
|
+
"close material detail, shallow depth, crisp tactile focus",
|
|
704
|
+
],
|
|
705
|
+
"studio-product": [
|
|
706
|
+
"studio product photography style",
|
|
707
|
+
"controlled light shaping, clean reflections, commercial precision",
|
|
708
|
+
],
|
|
709
|
+
"natural-light": [
|
|
710
|
+
"natural light photographic style",
|
|
711
|
+
"soft believable illumination, gentle contrast, unforced realism",
|
|
712
|
+
],
|
|
713
|
+
"film-noir": [
|
|
714
|
+
"film noir visual language",
|
|
715
|
+
"stark shadows, dramatic light cuts, monochrome tension",
|
|
716
|
+
],
|
|
717
|
+
"sepia-photo": [
|
|
718
|
+
"sepia photographic finish",
|
|
719
|
+
"warm brown tonal range, aged print texture, soft contrast",
|
|
720
|
+
],
|
|
721
|
+
"architectural-photo": [
|
|
722
|
+
"architectural photography discipline",
|
|
723
|
+
"precise perspective, clean planes, controlled spatial rhythm",
|
|
724
|
+
],
|
|
725
|
+
"scientific-visual": [
|
|
726
|
+
"scientific visual communication style",
|
|
727
|
+
"clear annotation hierarchy, disciplined color coding, evidence-first clarity",
|
|
728
|
+
],
|
|
729
|
+
"patent-drawing": [
|
|
730
|
+
"patent drawing visual style",
|
|
731
|
+
"thin precise linework, numbered annotation discipline, white field clarity",
|
|
732
|
+
],
|
|
733
|
+
"schematic": [
|
|
734
|
+
"schematic visual language",
|
|
735
|
+
"clean line systems, measured spacing, unambiguous visual logic",
|
|
736
|
+
],
|
|
737
|
+
"whitepaper": [
|
|
738
|
+
"whitepaper figure style",
|
|
739
|
+
"restrained academic polish, readable labels, sober layout structure",
|
|
740
|
+
],
|
|
741
|
+
"museum-catalog": [
|
|
742
|
+
"museum catalog editorial style",
|
|
743
|
+
"archival restraint, spacious layout, refined caption hierarchy",
|
|
744
|
+
],
|
|
745
|
+
"newspaper": [
|
|
746
|
+
"newspaper editorial design",
|
|
747
|
+
"dense readable typography, monochrome structure, disciplined hierarchy",
|
|
748
|
+
],
|
|
749
|
+
"zine": [
|
|
750
|
+
"zine editorial style",
|
|
751
|
+
"raw print texture, handmade composition energy, independent publishing feel",
|
|
752
|
+
],
|
|
753
|
+
"renaissance-painting": [
|
|
754
|
+
"Renaissance painting inspired finish",
|
|
755
|
+
"balanced proportions, soft chiaroscuro, classical tonal depth",
|
|
756
|
+
],
|
|
757
|
+
"baroque": [
|
|
758
|
+
"Baroque visual drama",
|
|
759
|
+
"rich chiaroscuro, dynamic diagonals, ornate depth",
|
|
760
|
+
],
|
|
761
|
+
"rococo": [
|
|
762
|
+
"Rococo decorative style",
|
|
763
|
+
"soft pastel palette, delicate ornament, graceful asymmetry",
|
|
764
|
+
],
|
|
765
|
+
"neoclassical": [
|
|
766
|
+
"neoclassical visual restraint",
|
|
767
|
+
"orderly proportions, marble-like tones, classical calm",
|
|
768
|
+
],
|
|
769
|
+
"romanticism": [
|
|
770
|
+
"Romanticist visual language",
|
|
771
|
+
"dramatic atmosphere, emotional contrast, painterly depth",
|
|
772
|
+
],
|
|
773
|
+
"impressionist": [
|
|
774
|
+
"Impressionist painting style",
|
|
775
|
+
"broken color brushwork, luminous softness, optical texture",
|
|
776
|
+
],
|
|
777
|
+
"post-impressionist": [
|
|
778
|
+
"Post-Impressionist painting style",
|
|
779
|
+
"expressive color structure, visible brush rhythm, simplified forms",
|
|
780
|
+
],
|
|
781
|
+
"pointillist": [
|
|
782
|
+
"pointillist painting technique",
|
|
783
|
+
"small color marks, optical mixing, disciplined surface texture",
|
|
784
|
+
],
|
|
785
|
+
"fauvist": [
|
|
786
|
+
"Fauvist color language",
|
|
787
|
+
"bold nonnatural color, expressive contrast, simplified shapes",
|
|
788
|
+
],
|
|
789
|
+
"cubist": [
|
|
790
|
+
"Cubist visual structure",
|
|
791
|
+
"fragmented planes, angular geometry, multiple-view abstraction",
|
|
792
|
+
],
|
|
793
|
+
"futurist": [
|
|
794
|
+
"Futurist graphic energy",
|
|
795
|
+
"dynamic motion rhythm, diagonal force, modern mechanical tension",
|
|
796
|
+
],
|
|
797
|
+
"surrealist": [
|
|
798
|
+
"Surrealist visual logic",
|
|
799
|
+
"dreamlike juxtapositions, precise rendering, uncanny calm",
|
|
800
|
+
],
|
|
801
|
+
"expressionist": [
|
|
802
|
+
"Expressionist visual language",
|
|
803
|
+
"distorted energy, heightened color, emotional brushwork",
|
|
804
|
+
],
|
|
805
|
+
"abstract-expressionist": [
|
|
806
|
+
"Abstract Expressionist finish",
|
|
807
|
+
"gestural marks, layered pigment energy, large-field tension",
|
|
808
|
+
],
|
|
809
|
+
"color-field": [
|
|
810
|
+
"color field painting style",
|
|
811
|
+
"large tonal expanses, soft boundaries, meditative color presence",
|
|
812
|
+
],
|
|
813
|
+
"hard-edge": [
|
|
814
|
+
"hard-edge abstract style",
|
|
815
|
+
"clean color boundaries, geometric clarity, flat precision",
|
|
816
|
+
],
|
|
817
|
+
"geometric-abstract": [
|
|
818
|
+
"geometric abstraction",
|
|
819
|
+
"pure shapes, balanced color fields, disciplined spatial rhythm",
|
|
820
|
+
],
|
|
821
|
+
"lyrical-abstract": [
|
|
822
|
+
"lyrical abstraction",
|
|
823
|
+
"fluid marks, soft color movement, airy expressive balance",
|
|
824
|
+
],
|
|
825
|
+
"op-art": [
|
|
826
|
+
"Op Art visual style",
|
|
827
|
+
"optical vibration, precise pattern rhythm, high contrast geometry",
|
|
828
|
+
],
|
|
829
|
+
"pop-art": [
|
|
830
|
+
"Pop Art graphic style",
|
|
831
|
+
"bold flat color, comic-print texture, mass-media polish",
|
|
832
|
+
],
|
|
833
|
+
"psychedelic": [
|
|
834
|
+
"psychedelic visual style",
|
|
835
|
+
"warped curves, saturated gradients, rhythmic color flow",
|
|
836
|
+
],
|
|
837
|
+
"minimalist-art": [
|
|
838
|
+
"minimalist art visual language",
|
|
839
|
+
"reduced forms, exact spacing, quiet material presence",
|
|
840
|
+
],
|
|
841
|
+
"folk-art": [
|
|
842
|
+
"folk art visual style",
|
|
843
|
+
"decorative simplicity, handmade pattern rhythm, warm color restraint",
|
|
844
|
+
],
|
|
845
|
+
"naive-art": [
|
|
846
|
+
"naive art visual style",
|
|
847
|
+
"direct simplified forms, flat perspective, candid color charm",
|
|
848
|
+
],
|
|
849
|
+
"outsider-art": [
|
|
850
|
+
"outsider art inspired mark-making",
|
|
851
|
+
"raw pattern density, intuitive composition, handmade intensity",
|
|
852
|
+
],
|
|
853
|
+
"stained-glass": [
|
|
854
|
+
"stained-glass visual language",
|
|
855
|
+
"lead-like outlines, luminous color panels, jewel-toned contrast",
|
|
856
|
+
],
|
|
857
|
+
"mosaic": [
|
|
858
|
+
"mosaic surface style",
|
|
859
|
+
"small tiled color pieces, tactile seams, structured pattern field",
|
|
860
|
+
],
|
|
861
|
+
"fresco": [
|
|
862
|
+
"fresco painting texture",
|
|
863
|
+
"matte plaster surface, aged pigment softness, mineral tonal range",
|
|
864
|
+
],
|
|
865
|
+
"tapestry": [
|
|
866
|
+
"tapestry textile style",
|
|
867
|
+
"woven texture, muted threads, ornamental pattern depth",
|
|
868
|
+
],
|
|
869
|
+
"embroidery": [
|
|
870
|
+
"embroidery textile style",
|
|
871
|
+
"stitched linework, thread texture, tactile raised detail",
|
|
872
|
+
],
|
|
873
|
+
"batik": [
|
|
874
|
+
"batik textile visual style",
|
|
875
|
+
"wax-resist edges, organic dye textures, patterned color fields",
|
|
876
|
+
],
|
|
877
|
+
"marbling": [
|
|
878
|
+
"paper marbling style",
|
|
879
|
+
"fluid veined color, organic swirls, delicate surface pattern",
|
|
880
|
+
],
|
|
881
|
+
"terrazzo": [
|
|
882
|
+
"terrazzo material style",
|
|
883
|
+
"speckled stone chips, polished surface rhythm, playful aggregate texture",
|
|
884
|
+
],
|
|
885
|
+
"kintsugi": [
|
|
886
|
+
"kintsugi-inspired material accent",
|
|
887
|
+
"fine metallic fracture lines, repaired elegance, restrained imperfection",
|
|
888
|
+
],
|
|
889
|
+
"raku-ceramic": [
|
|
890
|
+
"raku ceramic finish",
|
|
891
|
+
"smoky glaze variation, crackle texture, earthy fired surface",
|
|
892
|
+
],
|
|
893
|
+
"porcelain": [
|
|
894
|
+
"porcelain material finish",
|
|
895
|
+
"smooth white surface, delicate glaze, refined translucent softness",
|
|
896
|
+
],
|
|
897
|
+
"enamel": [
|
|
898
|
+
"enamel material finish",
|
|
899
|
+
"glossy saturated color, smooth hard surface, crisp highlights",
|
|
900
|
+
],
|
|
901
|
+
"lacquerware": [
|
|
902
|
+
"lacquerware visual finish",
|
|
903
|
+
"deep glossy surface, subtle layered shine, refined dark richness",
|
|
904
|
+
],
|
|
905
|
+
"brushed-metal": [
|
|
906
|
+
"brushed metal material style",
|
|
907
|
+
"linear grain, cool highlights, industrial precision",
|
|
908
|
+
],
|
|
909
|
+
"carbon-fiber": [
|
|
910
|
+
"carbon fiber material style",
|
|
911
|
+
"woven dark texture, subtle reflectivity, high-performance polish",
|
|
912
|
+
],
|
|
913
|
+
"concrete": [
|
|
914
|
+
"architectural concrete material style",
|
|
915
|
+
"matte mineral texture, subtle pores, neutral gray restraint",
|
|
916
|
+
],
|
|
917
|
+
"matte-black": [
|
|
918
|
+
"matte black visual finish",
|
|
919
|
+
"low-reflection surface, deep tonal control, premium minimal contrast",
|
|
920
|
+
],
|
|
921
|
+
"felt-texture": [
|
|
922
|
+
"felt material texture",
|
|
923
|
+
"soft fiber surface, muted color absorption, tactile warmth",
|
|
924
|
+
],
|
|
925
|
+
"linen-texture": [
|
|
926
|
+
"linen texture visual style",
|
|
927
|
+
"woven fibers, natural irregularity, soft matte tactility",
|
|
928
|
+
],
|
|
929
|
+
"denim-texture": [
|
|
930
|
+
"denim textile texture",
|
|
931
|
+
"twill weave, indigo tonal variation, sturdy fabric grain",
|
|
932
|
+
],
|
|
933
|
+
"grainy-paper": [
|
|
934
|
+
"grainy paper visual texture",
|
|
935
|
+
"visible paper tooth, subtle fiber noise, tactile matte surface",
|
|
936
|
+
],
|
|
937
|
+
"newsprint": [
|
|
938
|
+
"newsprint print style",
|
|
939
|
+
"coarse paper grain, ink spread, economical monochrome texture",
|
|
940
|
+
],
|
|
941
|
+
"letterpress": [
|
|
942
|
+
"letterpress print style",
|
|
943
|
+
"pressed ink texture, debossed edges, tactile paper impression",
|
|
944
|
+
],
|
|
945
|
+
"engraving": [
|
|
946
|
+
"engraving illustration style",
|
|
947
|
+
"fine hatch lines, precise tonal modeling, antique print discipline",
|
|
948
|
+
],
|
|
949
|
+
"etching": [
|
|
950
|
+
"etching print style",
|
|
951
|
+
"delicate scratched lines, crosshatching, plate-mark texture",
|
|
952
|
+
],
|
|
953
|
+
"copperplate": [
|
|
954
|
+
"copperplate engraving style",
|
|
955
|
+
"elegant line weight, refined hatching, historical print polish",
|
|
956
|
+
],
|
|
957
|
+
"mezzotint": [
|
|
958
|
+
"mezzotint print style",
|
|
959
|
+
"velvety tonal gradients, deep blacks, soft print transitions",
|
|
960
|
+
],
|
|
961
|
+
"cyanotype": [
|
|
962
|
+
"cyanotype print style",
|
|
963
|
+
"Prussian blue tonal range, sun-print softness, archival texture",
|
|
964
|
+
],
|
|
965
|
+
"rubber-stamp": [
|
|
966
|
+
"rubber stamp print style",
|
|
967
|
+
"uneven ink edges, pressed texture, handmade registration variation",
|
|
968
|
+
],
|
|
969
|
+
"sticker-sheet": [
|
|
970
|
+
"sticker sheet graphic style",
|
|
971
|
+
"die-cut edges, glossy flat color, playful spacing rhythm",
|
|
972
|
+
],
|
|
973
|
+
"badge-emblem": [
|
|
974
|
+
"badge emblem graphic style",
|
|
975
|
+
"compact circular hierarchy, bold border rhythm, crisp lettering logic",
|
|
976
|
+
],
|
|
977
|
+
"monoline": [
|
|
978
|
+
"monoline drawing style",
|
|
979
|
+
"single-weight contours, clean curves, consistent stroke rhythm",
|
|
980
|
+
],
|
|
981
|
+
"flat-illustration": [
|
|
982
|
+
"flat illustration style",
|
|
983
|
+
"simplified shapes, solid color fields, clean graphic hierarchy",
|
|
984
|
+
],
|
|
985
|
+
"editorial-illustration": [
|
|
986
|
+
"editorial illustration style",
|
|
987
|
+
"conceptual clarity, textured shapes, publication-grade composition",
|
|
988
|
+
],
|
|
989
|
+
"storybook": [
|
|
990
|
+
"storybook illustration style",
|
|
991
|
+
"warm hand-rendered texture, gentle color, narrative softness",
|
|
992
|
+
],
|
|
993
|
+
"picture-book": [
|
|
994
|
+
"picture-book illustration style",
|
|
995
|
+
"friendly shapes, tactile color, clear readable composition",
|
|
996
|
+
],
|
|
997
|
+
"comic-book": [
|
|
998
|
+
"comic book print style",
|
|
999
|
+
"inked contours, halftone shading, bold panel-like energy",
|
|
1000
|
+
],
|
|
1001
|
+
"manga-tone": [
|
|
1002
|
+
"manga screentone style",
|
|
1003
|
+
"black ink linework, tone patterns, crisp monochrome contrast",
|
|
1004
|
+
],
|
|
1005
|
+
"anime-cel": [
|
|
1006
|
+
"anime cel finish",
|
|
1007
|
+
"flat color fills, clean outlines, crisp shadow shapes",
|
|
1008
|
+
],
|
|
1009
|
+
"cel-shaded": [
|
|
1010
|
+
"cel-shaded rendering",
|
|
1011
|
+
"flat shade bands, clean contours, graphic lighting simplification",
|
|
1012
|
+
],
|
|
1013
|
+
"toon-shaded": [
|
|
1014
|
+
"toon-shaded 3D style",
|
|
1015
|
+
"simplified lighting bands, clean outlines, stylized material response",
|
|
1016
|
+
],
|
|
1017
|
+
"cartoon-modern": [
|
|
1018
|
+
"modern cartoon graphic style",
|
|
1019
|
+
"rounded simplified forms, playful proportion, clean color blocks",
|
|
1020
|
+
],
|
|
1021
|
+
"chibi": [
|
|
1022
|
+
"chibi-inspired proportion style",
|
|
1023
|
+
"compact rounded forms, soft expression cues, playful simplicity",
|
|
1024
|
+
],
|
|
1025
|
+
"kawaii": [
|
|
1026
|
+
"kawaii visual style",
|
|
1027
|
+
"soft rounded forms, pastel palette, gentle playful polish",
|
|
1028
|
+
],
|
|
1029
|
+
"doodle": [
|
|
1030
|
+
"doodle drawing style",
|
|
1031
|
+
"casual linework, spontaneous marks, playful looseness",
|
|
1032
|
+
],
|
|
1033
|
+
"sketchnote": [
|
|
1034
|
+
"sketchnote visual style",
|
|
1035
|
+
"handwritten rhythm, simple line structure, organized roughness",
|
|
1036
|
+
],
|
|
1037
|
+
"whiteboard-sketch": [
|
|
1038
|
+
"whiteboard sketch style",
|
|
1039
|
+
"marker-like lines, clean white field, quick explanatory clarity",
|
|
1040
|
+
],
|
|
1041
|
+
"marker-render": [
|
|
1042
|
+
"marker rendering style",
|
|
1043
|
+
"broad strokes, layered translucent color, industrial design sketch finish",
|
|
1044
|
+
],
|
|
1045
|
+
"concept-art": [
|
|
1046
|
+
"concept art rendering style",
|
|
1047
|
+
"high-impact composition, atmospheric value control, polished ideation finish",
|
|
1048
|
+
],
|
|
1049
|
+
"matte-painting": [
|
|
1050
|
+
"matte painting finish",
|
|
1051
|
+
"seamless painterly depth, cinematic scale, controlled atmosphere",
|
|
1052
|
+
],
|
|
1053
|
+
"fantasy-illustration": [
|
|
1054
|
+
"fantasy illustration finish",
|
|
1055
|
+
"ornate detail, rich color depth, dramatic painterly atmosphere",
|
|
1056
|
+
],
|
|
1057
|
+
"sci-fi-illustration": [
|
|
1058
|
+
"science-fiction illustration style",
|
|
1059
|
+
"sleek futuristic surfaces, luminous accents, precise speculative polish",
|
|
1060
|
+
],
|
|
1061
|
+
"dark-fantasy": [
|
|
1062
|
+
"dark fantasy visual style",
|
|
1063
|
+
"shadowed painterly depth, muted drama, ornate texture",
|
|
1064
|
+
],
|
|
1065
|
+
"solarpunk": [
|
|
1066
|
+
"solarpunk visual style",
|
|
1067
|
+
"optimistic clean technology mood, warm natural palette, luminous clarity",
|
|
1068
|
+
],
|
|
1069
|
+
"cassette-futurism": [
|
|
1070
|
+
"cassette futurism visual style",
|
|
1071
|
+
"retro analog controls, chunky geometry, warm technical nostalgia",
|
|
1072
|
+
],
|
|
1073
|
+
"atompunk": [
|
|
1074
|
+
"atompunk retro-futurist style",
|
|
1075
|
+
"mid-century futurism, atomic geometry, glossy optimism",
|
|
1076
|
+
],
|
|
1077
|
+
"dieselpunk": [
|
|
1078
|
+
"dieselpunk visual style",
|
|
1079
|
+
"industrial grit, brass-dark palette, mechanical art-deco tension",
|
|
1080
|
+
],
|
|
1081
|
+
"steampunk": [
|
|
1082
|
+
"steampunk visual style",
|
|
1083
|
+
"brass mechanical texture, aged leather tones, Victorian industrial polish",
|
|
1084
|
+
],
|
|
1085
|
+
"biopunk": [
|
|
1086
|
+
"biopunk visual style",
|
|
1087
|
+
"organic technical textures, translucent material cues, speculative polish",
|
|
1088
|
+
],
|
|
1089
|
+
"afrofuturist": [
|
|
1090
|
+
"Afrofuturist visual language",
|
|
1091
|
+
"bold geometry, metallic accents, rhythmic futuristic pattern",
|
|
1092
|
+
],
|
|
1093
|
+
"retrofuturism": [
|
|
1094
|
+
"retrofuturist visual style",
|
|
1095
|
+
"past-era future optimism, rounded technical shapes, polished nostalgia",
|
|
1096
|
+
],
|
|
1097
|
+
"neo-futurism": [
|
|
1098
|
+
"neo-futurist visual style",
|
|
1099
|
+
"fluid geometry, sleek minimal surfaces, luminous structural clarity",
|
|
1100
|
+
],
|
|
1101
|
+
"minimal-tech": [
|
|
1102
|
+
"minimal technology visual style",
|
|
1103
|
+
"precise dark-light balance, sparse luminous accents, clean engineered feel",
|
|
1104
|
+
],
|
|
1105
|
+
"terminal-aesthetic": [
|
|
1106
|
+
"terminal aesthetic visual style",
|
|
1107
|
+
"monospace rhythm, dark field, phosphor-like glow",
|
|
1108
|
+
],
|
|
1109
|
+
"crt-screen": [
|
|
1110
|
+
"CRT screen visual finish",
|
|
1111
|
+
"scanlines, soft phosphor bloom, slight analog distortion",
|
|
1112
|
+
],
|
|
1113
|
+
"glitch-art": [
|
|
1114
|
+
"glitch art visual style",
|
|
1115
|
+
"digital fragmentation, chromatic offsets, controlled signal noise",
|
|
1116
|
+
],
|
|
1117
|
+
"datamosh": [
|
|
1118
|
+
"datamosh digital style",
|
|
1119
|
+
"compression smears, pixel drift, controlled digital breakdown",
|
|
1120
|
+
],
|
|
1121
|
+
"ascii-art": [
|
|
1122
|
+
"ASCII art visual style",
|
|
1123
|
+
"monospace glyph texture, grid-like tonal construction, retro computing feel",
|
|
1124
|
+
],
|
|
1125
|
+
"generative-art": [
|
|
1126
|
+
"generative art visual style",
|
|
1127
|
+
"algorithmic pattern logic, parametric rhythm, computational elegance",
|
|
1128
|
+
],
|
|
1129
|
+
"fractal": [
|
|
1130
|
+
"fractal visual style",
|
|
1131
|
+
"recursive pattern depth, mathematical symmetry, intricate scale variation",
|
|
1132
|
+
],
|
|
1133
|
+
"topographic": [
|
|
1134
|
+
"topographic line visual style",
|
|
1135
|
+
"contour-line rhythm, layered elevation feel, precise spatial abstraction",
|
|
1136
|
+
],
|
|
1137
|
+
"cartographic": [
|
|
1138
|
+
"cartographic visual style",
|
|
1139
|
+
"map-like line discipline, measured labeling rhythm, muted technical palette",
|
|
1140
|
+
],
|
|
1141
|
+
"infographic-clean": [
|
|
1142
|
+
"clean infographic visual style",
|
|
1143
|
+
"simple annotation hierarchy, restrained color coding, high readability",
|
|
1144
|
+
],
|
|
1145
|
+
"dashboard-dark": [
|
|
1146
|
+
"dark dashboard visual language",
|
|
1147
|
+
"deep surfaces, luminous data accents, dense but readable spacing",
|
|
1148
|
+
],
|
|
1149
|
+
"trading-terminal": [
|
|
1150
|
+
"trading terminal visual style",
|
|
1151
|
+
"dense numeric rhythm, dark grid, high-contrast signal colors",
|
|
1152
|
+
],
|
|
1153
|
+
"medical-illustration": [
|
|
1154
|
+
"medical illustration style",
|
|
1155
|
+
"clinical clarity, clean shading, precise explanatory linework",
|
|
1156
|
+
],
|
|
1157
|
+
"botanical-illustration": [
|
|
1158
|
+
"botanical illustration style",
|
|
1159
|
+
"delicate natural linework, subtle watercolor tones, scientific precision",
|
|
1160
|
+
],
|
|
1161
|
+
"anatomical-plate": [
|
|
1162
|
+
"anatomical plate illustration style",
|
|
1163
|
+
"fine explanatory linework, muted academic palette, archival precision",
|
|
1164
|
+
],
|
|
1165
|
+
"astronomical-plate": [
|
|
1166
|
+
"astronomical plate visual style",
|
|
1167
|
+
"deep field contrast, precise luminous points, archival scientific calm",
|
|
1168
|
+
],
|
|
1169
|
+
"field-guide": [
|
|
1170
|
+
"field guide illustration style",
|
|
1171
|
+
"clear specimen-like rendering, neutral spacing, concise annotation rhythm",
|
|
1172
|
+
],
|
|
1173
|
+
"catalog-product": [
|
|
1174
|
+
"catalog product visual style",
|
|
1175
|
+
"clean isolated presentation, consistent lighting, commercial comparability",
|
|
1176
|
+
],
|
|
1177
|
+
"packaging-render": [
|
|
1178
|
+
"packaging render style",
|
|
1179
|
+
"crisp dieline-like edges, material clarity, retail-grade polish",
|
|
1180
|
+
],
|
|
1181
|
+
"ecommerce-clean": [
|
|
1182
|
+
"e-commerce clean visual style",
|
|
1183
|
+
"white field clarity, accurate material detail, conversion-focused polish",
|
|
1184
|
+
],
|
|
1185
|
+
"luxury-editorial": [
|
|
1186
|
+
"luxury editorial visual style",
|
|
1187
|
+
"quiet opulence, elegant spacing, refined material emphasis",
|
|
1188
|
+
],
|
|
1189
|
+
"minimal-magazine": [
|
|
1190
|
+
"minimal magazine layout style",
|
|
1191
|
+
"large whitespace, refined typography, disciplined editorial pacing",
|
|
1192
|
+
],
|
|
1193
|
+
"photobook": [
|
|
1194
|
+
"photobook editorial style",
|
|
1195
|
+
"image-forward pacing, quiet captions, archival sequencing feel",
|
|
1196
|
+
],
|
|
1197
|
+
"scrapbook": [
|
|
1198
|
+
"scrapbook visual style",
|
|
1199
|
+
"paper layering, tape-like accents, handmade archival texture",
|
|
1200
|
+
],
|
|
1201
|
+
"grid-system": [
|
|
1202
|
+
"grid system visual style",
|
|
1203
|
+
"visible alignment discipline, consistent rhythm, rational spacing",
|
|
1204
|
+
],
|
|
1205
|
+
"asymmetric-layout": [
|
|
1206
|
+
"asymmetric layout style",
|
|
1207
|
+
"off-center balance, dynamic whitespace, controlled visual tension",
|
|
1208
|
+
],
|
|
1209
|
+
"centered-minimal": [
|
|
1210
|
+
"centered minimal composition",
|
|
1211
|
+
"single-axis calm, balanced margins, quiet focal strength",
|
|
1212
|
+
],
|
|
1213
|
+
"dense-editorial": [
|
|
1214
|
+
"dense editorial layout style",
|
|
1215
|
+
"compact hierarchy, precise spacing, information-rich polish",
|
|
1216
|
+
],
|
|
1217
|
+
"sparse-editorial": [
|
|
1218
|
+
"sparse editorial layout style",
|
|
1219
|
+
"generous whitespace, slow visual pacing, refined restraint",
|
|
1220
|
+
],
|
|
1221
|
+
"posterized": [
|
|
1222
|
+
"posterized graphic style",
|
|
1223
|
+
"reduced tonal steps, flat color separation, bold silhouette clarity",
|
|
1224
|
+
],
|
|
1225
|
+
"halftone": [
|
|
1226
|
+
"halftone print style",
|
|
1227
|
+
"dot-screen shading, print texture, graphic tonal compression",
|
|
1228
|
+
],
|
|
1229
|
+
"grainy-gradient": [
|
|
1230
|
+
"grainy gradient style",
|
|
1231
|
+
"soft gradient fields, fine noise texture, contemporary digital warmth",
|
|
1232
|
+
],
|
|
1233
|
+
"noir-comic": [
|
|
1234
|
+
"noir comic visual style",
|
|
1235
|
+
"heavy ink shadows, dramatic contrast, graphic suspense",
|
|
1236
|
+
],
|
|
1237
|
+
"pulp-cover": [
|
|
1238
|
+
"pulp cover illustration style",
|
|
1239
|
+
"bold painted drama, aged print texture, high-contrast title-area rhythm",
|
|
1240
|
+
],
|
|
1241
|
+
"retro-computer": [
|
|
1242
|
+
"retro computer visual style",
|
|
1243
|
+
"early digital geometry, phosphor glow, limited color palette",
|
|
1244
|
+
],
|
|
1245
|
+
"eight-bit": [
|
|
1246
|
+
"8-bit visual style",
|
|
1247
|
+
"chunky pixel grid, limited palette, crisp low-resolution charm",
|
|
1248
|
+
],
|
|
1249
|
+
"sixteen-bit": [
|
|
1250
|
+
"16-bit visual style",
|
|
1251
|
+
"richer pixel shading, crisp sprite-like edges, nostalgic color depth",
|
|
1252
|
+
],
|
|
1253
|
+
"lo-fi-digital": [
|
|
1254
|
+
"lo-fi digital visual style",
|
|
1255
|
+
"compressed texture, imperfect pixels, casual screen-era warmth",
|
|
1256
|
+
],
|
|
1257
|
+
"vhs": [
|
|
1258
|
+
"VHS visual finish",
|
|
1259
|
+
"analog color bleed, scan noise, retro tape softness",
|
|
1260
|
+
],
|
|
1261
|
+
"scanline": [
|
|
1262
|
+
"scanline visual finish",
|
|
1263
|
+
"horizontal line texture, subtle flicker feel, retro display rhythm",
|
|
1264
|
+
],
|
|
1265
|
+
"infrared-photo": [
|
|
1266
|
+
"infrared photographic look",
|
|
1267
|
+
"false-color tonal shift, glowing highlights, otherworldly contrast",
|
|
1268
|
+
],
|
|
1269
|
+
"thermal-imaging": [
|
|
1270
|
+
"thermal imaging palette",
|
|
1271
|
+
"heat-map color gradients, high contrast, technical false-color clarity",
|
|
1272
|
+
],
|
|
1273
|
+
"xray-render": [
|
|
1274
|
+
"X-ray inspired rendering",
|
|
1275
|
+
"translucent layered forms, cool monochrome tones, internal contour clarity",
|
|
1276
|
+
],
|
|
1277
|
+
"cross-section": [
|
|
1278
|
+
"cross-section visual style",
|
|
1279
|
+
"clean sliced planes, layered material clarity, explanatory depth",
|
|
1280
|
+
],
|
|
1281
|
+
"exploded-view": [
|
|
1282
|
+
"exploded-view rendering style",
|
|
1283
|
+
"separated layers, precise spacing, technical clarity",
|
|
1284
|
+
],
|
|
1285
|
+
"orthographic": [
|
|
1286
|
+
"orthographic rendering style",
|
|
1287
|
+
"flat projection, measured edges, exact technical presentation",
|
|
1288
|
+
],
|
|
1289
|
+
"axonometric": [
|
|
1290
|
+
"axonometric visual style",
|
|
1291
|
+
"parallel projection, clean depth, measured geometric consistency",
|
|
1292
|
+
],
|
|
1293
|
+
"blueprint-white": [
|
|
1294
|
+
"white blueprint variant",
|
|
1295
|
+
"blue linework on clean white field, precise technical spacing",
|
|
1296
|
+
],
|
|
1297
|
+
"blackpaper-gold": [
|
|
1298
|
+
"black paper gold-ink visual style",
|
|
1299
|
+
"deep matte field, metallic line accents, elegant contrast",
|
|
1300
|
+
],
|
|
1301
|
+
"silverpoint": [
|
|
1302
|
+
"silverpoint drawing style",
|
|
1303
|
+
"delicate metallic gray linework, restrained shading, archival subtlety",
|
|
1304
|
+
],
|
|
1305
|
+
"sumi-e": [
|
|
1306
|
+
"sumi-e ink painting style",
|
|
1307
|
+
"economical brush strokes, tonal ink wash, quiet asymmetry",
|
|
1308
|
+
],
|
|
1309
|
+
"gongbi": [
|
|
1310
|
+
"gongbi fine-line painting style",
|
|
1311
|
+
"meticulous contours, delicate color layers, refined precision",
|
|
1312
|
+
],
|
|
1313
|
+
"minhwa": [
|
|
1314
|
+
"minhwa folk painting style",
|
|
1315
|
+
"flat decorative color, symbolic pattern rhythm, handmade warmth",
|
|
1316
|
+
],
|
|
1317
|
+
"mandala": [
|
|
1318
|
+
"mandala geometric style",
|
|
1319
|
+
"radial symmetry, intricate pattern balance, meditative structure",
|
|
1320
|
+
],
|
|
1321
|
+
"arabesque": [
|
|
1322
|
+
"arabesque ornamental style",
|
|
1323
|
+
"flowing geometric ornament, interlaced curves, refined repetition",
|
|
1324
|
+
],
|
|
1325
|
+
"azulejo": [
|
|
1326
|
+
"azulejo tile visual style",
|
|
1327
|
+
"glazed blue-white patterning, ceramic shine, modular repetition",
|
|
1328
|
+
],
|
|
1329
|
+
"mudcloth": [
|
|
1330
|
+
"mudcloth textile visual style",
|
|
1331
|
+
"earthy geometric pattern, hand-dyed texture, rhythmic contrast",
|
|
1332
|
+
],
|
|
1333
|
+
"ikat": [
|
|
1334
|
+
"ikat textile visual style",
|
|
1335
|
+
"blurred woven edges, patterned dye rhythm, tactile fabric depth",
|
|
1336
|
+
],
|
|
1337
|
+
"tartan": [
|
|
1338
|
+
"tartan pattern style",
|
|
1339
|
+
"woven plaid structure, intersecting color bands, textile precision",
|
|
1340
|
+
],
|
|
1341
|
+
"quilted": [
|
|
1342
|
+
"quilted textile style",
|
|
1343
|
+
"stitched patch rhythm, padded texture, warm crafted geometry",
|
|
1344
|
+
],
|
|
1345
|
+
"macrame": [
|
|
1346
|
+
"macrame fiber style",
|
|
1347
|
+
"knotted cord texture, natural fiber warmth, handmade pattern rhythm",
|
|
1348
|
+
],
|
|
1349
|
+
"beadwork": [
|
|
1350
|
+
"beadwork surface style",
|
|
1351
|
+
"small reflective units, tactile pattern density, crafted shimmer",
|
|
1352
|
+
],
|
|
1353
|
+
"neon-sign": [
|
|
1354
|
+
"neon sign visual style",
|
|
1355
|
+
"glowing tube strokes, dark field contrast, luminous edge bloom",
|
|
1356
|
+
],
|
|
1357
|
+
"laser-cut": [
|
|
1358
|
+
"laser-cut material style",
|
|
1359
|
+
"precise cut edges, layered depth, clean manufactured detail",
|
|
1360
|
+
],
|
|
1361
|
+
"paper-engineering": [
|
|
1362
|
+
"paper engineering visual style",
|
|
1363
|
+
"folded layers, crisp tabs, dimensional paper construction",
|
|
1364
|
+
],
|
|
1365
|
+
"blue-hour": [
|
|
1366
|
+
"blue-hour photographic mood",
|
|
1367
|
+
"cool twilight tones, soft ambient glow, low contrast calm",
|
|
1368
|
+
],
|
|
1369
|
+
"golden-hour": [
|
|
1370
|
+
"golden-hour photographic mood",
|
|
1371
|
+
"warm directional glow, soft shadows, gentle luminous atmosphere",
|
|
1372
|
+
],
|
|
1373
|
+
"overcast-soft": [
|
|
1374
|
+
"overcast soft-light style",
|
|
1375
|
+
"diffuse illumination, muted contrast, natural color restraint",
|
|
1376
|
+
],
|
|
1377
|
+
"flash-photo": [
|
|
1378
|
+
"direct flash photographic style",
|
|
1379
|
+
"hard frontal light, crisp shadows, candid glossy energy",
|
|
1380
|
+
],
|
|
1381
|
+
"editorial-flash": [
|
|
1382
|
+
"editorial flash photography style",
|
|
1383
|
+
"controlled direct light, high-fashion contrast, polished immediacy",
|
|
1384
|
+
],
|
|
1385
|
+
"long-exposure": [
|
|
1386
|
+
"long-exposure photographic style",
|
|
1387
|
+
"motion trails, smooth light flow, temporal softness",
|
|
1388
|
+
],
|
|
1389
|
+
"tilt-shift": [
|
|
1390
|
+
"tilt-shift photographic look",
|
|
1391
|
+
"miniature-like focus falloff, selective sharpness, playful scale feel",
|
|
1392
|
+
],
|
|
1393
|
+
"fisheye": [
|
|
1394
|
+
"fisheye lens visual style",
|
|
1395
|
+
"wide curved perspective, strong spatial distortion, energetic framing",
|
|
1396
|
+
],
|
|
1397
|
+
"macro-texture": [
|
|
1398
|
+
"macro texture visual style",
|
|
1399
|
+
"extreme close detail, shallow depth, tactile surface emphasis",
|
|
1400
|
+
],
|
|
1401
|
+
"editorial-still-life": [
|
|
1402
|
+
"editorial still-life visual style",
|
|
1403
|
+
"arranged form balance, refined surfaces, publication-grade lighting",
|
|
1404
|
+
],
|
|
1405
|
+
"premium-packshot": [
|
|
1406
|
+
"premium packshot style",
|
|
1407
|
+
"accurate silhouette, clean reflections, commercial studio clarity",
|
|
1408
|
+
],
|
|
1409
|
+
"specular-luxury": [
|
|
1410
|
+
"specular luxury rendering",
|
|
1411
|
+
"controlled highlights, glossy material depth, elegant dark contrast",
|
|
1412
|
+
],
|
|
1413
|
+
"matte-studio": [
|
|
1414
|
+
"matte studio rendering",
|
|
1415
|
+
"soft non-reflective surfaces, clean shadow grounding, quiet polish",
|
|
1416
|
+
],
|
|
1417
|
+
"translucent-resin": [
|
|
1418
|
+
"translucent resin material style",
|
|
1419
|
+
"milky depth, soft internal glow, smooth polished surface",
|
|
1420
|
+
],
|
|
1421
|
+
"liquid-metal": [
|
|
1422
|
+
"liquid metal material style",
|
|
1423
|
+
"fluid reflective surface, smooth highlights, futuristic sheen",
|
|
1424
|
+
],
|
|
1425
|
+
"prismatic": [
|
|
1426
|
+
"prismatic visual finish",
|
|
1427
|
+
"split spectral highlights, crystalline refraction, clean luminous edges",
|
|
1428
|
+
],
|
|
1429
|
+
"crystal": [
|
|
1430
|
+
"crystal material style",
|
|
1431
|
+
"faceted transparency, sharp refraction, cool luminous clarity",
|
|
1432
|
+
],
|
|
1433
|
+
"paper-grain-quiet": [
|
|
1434
|
+
"quiet paper grain style",
|
|
1435
|
+
"subtle fibers, matte softness, understated print tactility",
|
|
1436
|
+
],
|
|
1437
|
+
"soft-shadow-ui": [
|
|
1438
|
+
"soft shadow UI visual style",
|
|
1439
|
+
"gentle elevation, rounded surfaces, restrained depth cues",
|
|
1440
|
+
],
|
|
1441
|
+
"flat-ui": [
|
|
1442
|
+
"flat UI visual style",
|
|
1443
|
+
"solid fills, simple hierarchy, crisp component spacing",
|
|
1444
|
+
],
|
|
1445
|
+
"skeuomorphic": [
|
|
1446
|
+
"skeuomorphic visual style",
|
|
1447
|
+
"realistic material cues, tactile controls, dimensional polish",
|
|
1448
|
+
],
|
|
1449
|
+
"neumorphic": [
|
|
1450
|
+
"neumorphic visual style",
|
|
1451
|
+
"soft extruded surfaces, subtle inner shadows, monochrome depth",
|
|
1452
|
+
],
|
|
1453
|
+
"liquid-glass": [
|
|
1454
|
+
"liquid glass visual style",
|
|
1455
|
+
"fluid translucent surfaces, refractive highlights, clean layered depth",
|
|
1456
|
+
],
|
|
1457
|
+
"branded-system": [
|
|
1458
|
+
"cohesive visual system style",
|
|
1459
|
+
"consistent spacing, controlled palette, repeatable identity logic",
|
|
1460
|
+
],
|
|
1461
|
+
"premium-saas": [
|
|
1462
|
+
"premium SaaS product visual style",
|
|
1463
|
+
"quiet interface polish, dense readable surfaces, restrained accent color",
|
|
1464
|
+
],
|
|
1465
|
+
"enterprise-dashboard": [
|
|
1466
|
+
"enterprise dashboard visual style",
|
|
1467
|
+
"organized density, neutral surfaces, precise status color logic",
|
|
1468
|
+
],
|
|
1469
|
+
"fintech": [
|
|
1470
|
+
"fintech visual style",
|
|
1471
|
+
"trustworthy polish, cool neutral palette, precise numeric hierarchy",
|
|
1472
|
+
],
|
|
1473
|
+
"health-tech": [
|
|
1474
|
+
"health-tech visual style",
|
|
1475
|
+
"clean clinical palette, calm hierarchy, accessible visual clarity",
|
|
1476
|
+
],
|
|
1477
|
+
"education-tech": [
|
|
1478
|
+
"education technology visual style",
|
|
1479
|
+
"friendly structure, clear learning hierarchy, warm restrained color",
|
|
1480
|
+
],
|
|
1481
|
+
"creator-economy": [
|
|
1482
|
+
"creator economy visual style",
|
|
1483
|
+
"vibrant gradients, social-native polish, energetic hierarchy",
|
|
1484
|
+
],
|
|
1485
|
+
"developer-tool": [
|
|
1486
|
+
"developer tool visual style",
|
|
1487
|
+
"monospace accents, dark-light contrast, precise technical density",
|
|
1488
|
+
],
|
|
1489
|
+
"open-source-docs": [
|
|
1490
|
+
"open-source documentation visual style",
|
|
1491
|
+
"plain clarity, code-like rhythm, accessible technical hierarchy",
|
|
1492
|
+
],
|
|
1493
|
+
"academic-conference": [
|
|
1494
|
+
"academic conference visual style",
|
|
1495
|
+
"poster-session clarity, structured sections, sober typography",
|
|
1496
|
+
],
|
|
1497
|
+
"premium-workshop": [
|
|
1498
|
+
"premium workshop visual style",
|
|
1499
|
+
"crafted instructional polish, warm neutral palette, practical hierarchy",
|
|
1500
|
+
],
|
|
1501
|
+
"event-poster": [
|
|
1502
|
+
"event poster visual style",
|
|
1503
|
+
"bold focal hierarchy, energetic typography, clean promotional rhythm",
|
|
1504
|
+
],
|
|
1505
|
+
"festival-poster": [
|
|
1506
|
+
"festival poster visual style",
|
|
1507
|
+
"vivid color rhythm, layered print texture, celebratory graphic energy",
|
|
1508
|
+
],
|
|
1509
|
+
"gallery-poster": [
|
|
1510
|
+
"gallery poster visual style",
|
|
1511
|
+
"minimal exhibition typography, generous whitespace, refined cultural tone",
|
|
1512
|
+
],
|
|
1513
|
+
"book-cover": [
|
|
1514
|
+
"book cover design style",
|
|
1515
|
+
"strong title-area hierarchy, symbolic composition, shelf-ready polish",
|
|
1516
|
+
],
|
|
1517
|
+
"album-cover": [
|
|
1518
|
+
"album cover visual style",
|
|
1519
|
+
"square-format impact, atmospheric abstraction, strong graphic identity",
|
|
1520
|
+
],
|
|
1521
|
+
"editorial-cover": [
|
|
1522
|
+
"editorial cover design style",
|
|
1523
|
+
"publication-grade hierarchy, striking crop logic, confident typography",
|
|
1524
|
+
],
|
|
1525
|
+
"poster-minimal": [
|
|
1526
|
+
"minimal poster design style",
|
|
1527
|
+
"single focal idea, strong whitespace, restrained graphic impact",
|
|
1528
|
+
],
|
|
1529
|
+
"poster-maximal": [
|
|
1530
|
+
"maximal poster design style",
|
|
1531
|
+
"layered detail density, energetic typography, controlled overload",
|
|
1532
|
+
],
|
|
1533
|
+
"neo-brutalist-web": [
|
|
1534
|
+
"neo-brutalist web visual style",
|
|
1535
|
+
"bold borders, raw spacing, stark digital contrast",
|
|
1536
|
+
],
|
|
1537
|
+
"anti-design": [
|
|
1538
|
+
"anti-design visual style",
|
|
1539
|
+
"deliberate imbalance, rough digital tension, expressive rule-breaking",
|
|
1540
|
+
],
|
|
1541
|
+
"clean-startup": [
|
|
1542
|
+
"clean startup visual style",
|
|
1543
|
+
"friendly whitespace, smooth gradients, approachable product polish",
|
|
1544
|
+
],
|
|
1545
|
+
"premium-minimal-product": [
|
|
1546
|
+
"premium minimal product style",
|
|
1547
|
+
"single-object clarity, quiet reflections, exact material emphasis",
|
|
1548
|
+
],
|
|
1549
|
+
"editorial-tech": [
|
|
1550
|
+
"editorial technology visual style",
|
|
1551
|
+
"thoughtful grid, subtle technical motifs, magazine-grade clarity",
|
|
1552
|
+
],
|
|
1553
|
+
"science-fiction-minimal": [
|
|
1554
|
+
"minimal science-fiction visual style",
|
|
1555
|
+
"sparse futuristic surfaces, cool luminous accents, clean speculative mood",
|
|
1556
|
+
],
|
|
1557
|
+
"space-opera": [
|
|
1558
|
+
"space opera visual style",
|
|
1559
|
+
"grand luminous scale, rich atmospheric contrast, polished dramatic depth",
|
|
1560
|
+
],
|
|
1561
|
+
"retro-space-age": [
|
|
1562
|
+
"retro space-age visual style",
|
|
1563
|
+
"rounded futuristic geometry, optimistic color, mid-century technical charm",
|
|
1564
|
+
],
|
|
1565
|
+
"industrial-design-sketch": [
|
|
1566
|
+
"industrial design sketch style",
|
|
1567
|
+
"marker shading, precise perspective lines, product ideation polish",
|
|
1568
|
+
],
|
|
1569
|
+
"automotive-render": [
|
|
1570
|
+
"automotive rendering style",
|
|
1571
|
+
"sleek reflections, precise surfacing, dynamic studio lighting",
|
|
1572
|
+
],
|
|
1573
|
+
"watch-render": [
|
|
1574
|
+
"watch rendering style",
|
|
1575
|
+
"macro metallic detail, polished bevels, luxury precision",
|
|
1576
|
+
],
|
|
1577
|
+
"jewelry-render": [
|
|
1578
|
+
"jewelry rendering style",
|
|
1579
|
+
"small-scale specular highlights, gemstone clarity, luxury surface control",
|
|
1580
|
+
],
|
|
1581
|
+
"cosmetic-campaign": [
|
|
1582
|
+
"cosmetic campaign visual style",
|
|
1583
|
+
"smooth material sheen, clean beauty lighting, refined color harmony",
|
|
1584
|
+
],
|
|
1585
|
+
"beverage-campaign": [
|
|
1586
|
+
"beverage campaign visual style",
|
|
1587
|
+
"fresh condensation detail, bright commercial clarity, appetizing color",
|
|
1588
|
+
],
|
|
1589
|
+
"outdoor-campaign": [
|
|
1590
|
+
"outdoor campaign visual style",
|
|
1591
|
+
"rugged material contrast, wide spatial feeling, crisp brand-ready energy",
|
|
1592
|
+
],
|
|
1593
|
+
"eco-campaign": [
|
|
1594
|
+
"eco campaign visual style",
|
|
1595
|
+
"natural color palette, recycled texture cues, clean optimistic restraint",
|
|
1596
|
+
],
|
|
1597
|
+
"nonprofit-campaign": [
|
|
1598
|
+
"nonprofit campaign visual style",
|
|
1599
|
+
"honest typography, warm restrained palette, credible emotional clarity",
|
|
1600
|
+
],
|
|
1601
|
+
"public-service": [
|
|
1602
|
+
"public service visual style",
|
|
1603
|
+
"clear warning hierarchy, accessible contrast, official communication tone",
|
|
1604
|
+
],
|
|
1605
|
+
"wayfinding": [
|
|
1606
|
+
"wayfinding visual style",
|
|
1607
|
+
"unambiguous arrows, high legibility, systematic spacing",
|
|
1608
|
+
],
|
|
1609
|
+
"signage-system": [
|
|
1610
|
+
"signage system visual style",
|
|
1611
|
+
"bold legible forms, consistent spacing, practical contrast",
|
|
1612
|
+
],
|
|
1613
|
+
"safety-manual": [
|
|
1614
|
+
"safety manual illustration style",
|
|
1615
|
+
"clear procedural layout, simple line forms, high-contrast caution palette",
|
|
1616
|
+
],
|
|
1617
|
+
"instruction-manual": [
|
|
1618
|
+
"instruction manual visual style",
|
|
1619
|
+
"stepwise clarity, precise linework, neutral explanatory tone",
|
|
1620
|
+
],
|
|
1621
|
+
"assembly-guide": [
|
|
1622
|
+
"assembly guide visual style",
|
|
1623
|
+
"exploded clarity, numbered rhythm, crisp technical linework",
|
|
1624
|
+
],
|
|
1625
|
+
"technical-manual": [
|
|
1626
|
+
"technical manual visual style",
|
|
1627
|
+
"measured diagrams, exact spacing, sober annotation hierarchy",
|
|
1628
|
+
],
|
|
1629
|
+
"luxury-brochure": [
|
|
1630
|
+
"luxury brochure editorial style",
|
|
1631
|
+
"generous margins, refined serif-sans contrast, premium print tactility",
|
|
1632
|
+
],
|
|
1633
|
+
"travel-editorial": [
|
|
1634
|
+
"travel editorial visual style",
|
|
1635
|
+
"warm photographic pacing, refined captions, aspirational color harmony",
|
|
1636
|
+
],
|
|
1637
|
+
"culinary-editorial": [
|
|
1638
|
+
"culinary editorial visual style",
|
|
1639
|
+
"appetizing texture detail, warm light, refined magazine composition",
|
|
1640
|
+
],
|
|
1641
|
+
"wellness-editorial": [
|
|
1642
|
+
"wellness editorial visual style",
|
|
1643
|
+
"soft neutral palette, calm spacing, gentle natural texture",
|
|
1644
|
+
],
|
|
1645
|
+
"sports-editorial": [
|
|
1646
|
+
"sports editorial visual style",
|
|
1647
|
+
"bold motion rhythm, high-contrast type, energetic graphic pacing",
|
|
1648
|
+
],
|
|
1649
|
+
"music-editorial": [
|
|
1650
|
+
"music editorial visual style",
|
|
1651
|
+
"rhythmic typography, atmospheric texture, expressive color contrast",
|
|
1652
|
+
],
|
|
1653
|
+
"culture-magazine": [
|
|
1654
|
+
"culture magazine visual style",
|
|
1655
|
+
"intelligent editorial hierarchy, refined image-text rhythm, contemporary polish",
|
|
1656
|
+
],
|
|
1657
|
+
"luxury-watch-ad": [
|
|
1658
|
+
"luxury watch advertising style",
|
|
1659
|
+
"precise metallic detail, deep shadows, refined highlight control",
|
|
1660
|
+
],
|
|
1661
|
+
"beauty-ad": [
|
|
1662
|
+
"beauty advertising visual style",
|
|
1663
|
+
"soft flawless gradients, glossy surfaces, elegant tonal control",
|
|
1664
|
+
],
|
|
1665
|
+
"tech-ad": [
|
|
1666
|
+
"technology advertising visual style",
|
|
1667
|
+
"sleek surfaces, luminous edges, minimal future polish",
|
|
1668
|
+
],
|
|
1669
|
+
"fashion-ad": [
|
|
1670
|
+
"fashion advertising visual style",
|
|
1671
|
+
"confident composition, strong negative space, editorial drama",
|
|
1672
|
+
],
|
|
1673
|
+
"toy-packaging": [
|
|
1674
|
+
"toy packaging visual style",
|
|
1675
|
+
"bright playful color, rounded typography, shelf-impact clarity",
|
|
1676
|
+
],
|
|
1677
|
+
"craft-packaging": [
|
|
1678
|
+
"craft packaging visual style",
|
|
1679
|
+
"handmade texture, warm paper tones, small-batch authenticity",
|
|
1680
|
+
],
|
|
1681
|
+
"apothecary-label": [
|
|
1682
|
+
"apothecary label visual style",
|
|
1683
|
+
"vintage typography, ornate borders, muted botanical color",
|
|
1684
|
+
],
|
|
1685
|
+
"minimal-label": [
|
|
1686
|
+
"minimal label design style",
|
|
1687
|
+
"small precise type, large whitespace, quiet product confidence",
|
|
1688
|
+
],
|
|
1689
|
+
"maximal-label": [
|
|
1690
|
+
"maximal label design style",
|
|
1691
|
+
"dense ornament, layered type, rich shelf presence",
|
|
1692
|
+
],
|
|
1693
|
+
"street-poster": [
|
|
1694
|
+
"street poster visual style",
|
|
1695
|
+
"paste-up texture, torn-paper edges, urban graphic immediacy",
|
|
1696
|
+
],
|
|
1697
|
+
"graffiti-inspired": [
|
|
1698
|
+
"graffiti-inspired graphic style",
|
|
1699
|
+
"spray texture, bold letter energy, high-contrast color rhythm",
|
|
1700
|
+
],
|
|
1701
|
+
"stencil": [
|
|
1702
|
+
"stencil graphic style",
|
|
1703
|
+
"cut-out shapes, hard edges, raw spray texture",
|
|
1704
|
+
],
|
|
1705
|
+
"chalkboard": [
|
|
1706
|
+
"chalkboard visual style",
|
|
1707
|
+
"chalk dust texture, hand-drawn lettering feel, dark matte surface",
|
|
1708
|
+
],
|
|
1709
|
+
"blueprint-dark": [
|
|
1710
|
+
"dark blueprint visual style",
|
|
1711
|
+
"cyan linework, dark field, measured technical clarity",
|
|
1712
|
+
],
|
|
1713
|
+
"circuit-board": [
|
|
1714
|
+
"circuit-board visual style",
|
|
1715
|
+
"fine conductive traces, technical grid rhythm, luminous electronic accents",
|
|
1716
|
+
],
|
|
1717
|
+
"microchip": [
|
|
1718
|
+
"microchip visual style",
|
|
1719
|
+
"dense etched pathways, metallic grid logic, high-tech precision",
|
|
1720
|
+
],
|
|
1721
|
+
"quantum-glow": [
|
|
1722
|
+
"quantum glow visual style",
|
|
1723
|
+
"fine particle-like light, dark scientific palette, subtle luminous fields",
|
|
1724
|
+
],
|
|
1725
|
+
"neural-network": [
|
|
1726
|
+
"neural network visual style",
|
|
1727
|
+
"connected point rhythm, luminous line structure, abstract technical clarity",
|
|
1728
|
+
],
|
|
1729
|
+
"wireframe-3d": [
|
|
1730
|
+
"wireframe 3D visual style",
|
|
1731
|
+
"transparent mesh lines, geometric construction, technical depth",
|
|
1732
|
+
],
|
|
1733
|
+
"point-cloud": [
|
|
1734
|
+
"point cloud visual style",
|
|
1735
|
+
"scattered luminous samples, volumetric depth, computational precision",
|
|
1736
|
+
],
|
|
1737
|
+
"parametric": [
|
|
1738
|
+
"parametric design style",
|
|
1739
|
+
"algorithmic curves, repeated structural rhythm, precise computational form",
|
|
1740
|
+
],
|
|
1741
|
+
"mesh-gradient": [
|
|
1742
|
+
"mesh gradient visual style",
|
|
1743
|
+
"smooth interpolated color, soft abstract depth, modern digital surface",
|
|
1744
|
+
],
|
|
1745
|
+
"aurora-gradient": [
|
|
1746
|
+
"aurora gradient visual style",
|
|
1747
|
+
"flowing luminous color bands, soft spectral transitions, airy depth",
|
|
1748
|
+
],
|
|
1749
|
+
"liquid-gradient": [
|
|
1750
|
+
"liquid gradient style",
|
|
1751
|
+
"fluid color mixing, smooth morphing surfaces, glossy digital polish",
|
|
1752
|
+
],
|
|
1753
|
+
"noise-texture": [
|
|
1754
|
+
"noise texture visual style",
|
|
1755
|
+
"fine grain overlay, tactile digital surface, softened flat fields",
|
|
1756
|
+
],
|
|
1757
|
+
"subtle-gradient": [
|
|
1758
|
+
"subtle gradient visual style",
|
|
1759
|
+
"barely-there tonal transitions, quiet polish, modern restraint",
|
|
1760
|
+
],
|
|
1761
|
+
"bold-gradient": [
|
|
1762
|
+
"bold gradient visual style",
|
|
1763
|
+
"high-saturation color transitions, strong focal energy, clean digital finish",
|
|
1764
|
+
],
|
|
1765
|
+
"monochrome-editorial": [
|
|
1766
|
+
"monochrome editorial style",
|
|
1767
|
+
"single-color discipline, strong tonal hierarchy, refined restraint",
|
|
1768
|
+
],
|
|
1769
|
+
"warm-neutral": [
|
|
1770
|
+
"warm neutral visual style",
|
|
1771
|
+
"cream-gray balance, soft contrast, calm material warmth",
|
|
1772
|
+
],
|
|
1773
|
+
"cool-neutral": [
|
|
1774
|
+
"cool neutral visual style",
|
|
1775
|
+
"silver-gray balance, precise contrast, modern calm",
|
|
1776
|
+
],
|
|
1777
|
+
"jewel-tone": [
|
|
1778
|
+
"jewel-tone visual style",
|
|
1779
|
+
"deep saturated palette, rich contrast, refined luminous color",
|
|
1780
|
+
],
|
|
1781
|
+
"monochrome-red": [
|
|
1782
|
+
"monochrome red visual style",
|
|
1783
|
+
"single hue dominance, strong value hierarchy, graphic intensity",
|
|
1784
|
+
],
|
|
1785
|
+
"monochrome-blue": [
|
|
1786
|
+
"monochrome blue visual style",
|
|
1787
|
+
"single hue discipline, cool depth, calm technical precision",
|
|
1788
|
+
],
|
|
1789
|
+
"black-white-red": [
|
|
1790
|
+
"black-white-red graphic style",
|
|
1791
|
+
"tri-color contrast, urgent hierarchy, bold poster energy",
|
|
1792
|
+
],
|
|
1793
|
+
"pastel-minimal": [
|
|
1794
|
+
"pastel minimal visual style",
|
|
1795
|
+
"soft low-saturation palette, quiet spacing, gentle hierarchy",
|
|
1796
|
+
],
|
|
1797
|
+
"acid-graphics": [
|
|
1798
|
+
"acid graphic style",
|
|
1799
|
+
"sharp saturated color, abrasive contrast, experimental digital energy",
|
|
1800
|
+
],
|
|
1801
|
+
"new-wave": [
|
|
1802
|
+
"new wave graphic style",
|
|
1803
|
+
"diagonal typography, bright contrast, experimental grid rhythm",
|
|
1804
|
+
],
|
|
1805
|
+
"postmodern-grid": [
|
|
1806
|
+
"postmodern grid style",
|
|
1807
|
+
"playful alignment shifts, expressive geometry, controlled disorder",
|
|
1808
|
+
],
|
|
1809
|
+
"neo-memphis": [
|
|
1810
|
+
"neo-Memphis visual style",
|
|
1811
|
+
"playful shapes, modern pastel-bright balance, clean postmodern rhythm",
|
|
1812
|
+
],
|
|
1813
|
+
"corporate-memphis": [
|
|
1814
|
+
"corporate Memphis visual style",
|
|
1815
|
+
"friendly geometric accents, clean business polish, restrained playfulness",
|
|
1816
|
+
],
|
|
1817
|
+
"friendly-saas": [
|
|
1818
|
+
"friendly SaaS visual style",
|
|
1819
|
+
"approachable rounded surfaces, clear hierarchy, warm accent color",
|
|
1820
|
+
],
|
|
1821
|
+
"serious-saas": [
|
|
1822
|
+
"serious SaaS visual style",
|
|
1823
|
+
"neutral density, exact alignment, enterprise-grade restraint",
|
|
1824
|
+
],
|
|
1825
|
+
"ai-product": [
|
|
1826
|
+
"AI product visual style",
|
|
1827
|
+
"abstract luminous gradients, technical clarity, polished futuristic restraint",
|
|
1828
|
+
],
|
|
1829
|
+
"robotics-lab": [
|
|
1830
|
+
"robotics lab visual style",
|
|
1831
|
+
"precision surfaces, cool technical lighting, mechanical clarity",
|
|
1832
|
+
],
|
|
1833
|
+
"space-tech": [
|
|
1834
|
+
"space technology visual style",
|
|
1835
|
+
"dark precision, luminous orbital lines, aerospace-grade polish",
|
|
1836
|
+
],
|
|
1837
|
+
"climate-tech": [
|
|
1838
|
+
"climate technology visual style",
|
|
1839
|
+
"natural green-blue palette, scientific clarity, optimistic restraint",
|
|
1840
|
+
],
|
|
1841
|
+
"biotech": [
|
|
1842
|
+
"biotech visual style",
|
|
1843
|
+
"soft scientific gradients, translucent material cues, clinical precision",
|
|
1844
|
+
],
|
|
1845
|
+
"legal-tech": [
|
|
1846
|
+
"legal technology visual style",
|
|
1847
|
+
"formal typography, sober palette, trust-first hierarchy",
|
|
1848
|
+
],
|
|
1849
|
+
"gov-tech": [
|
|
1850
|
+
"government technology visual style",
|
|
1851
|
+
"accessible clarity, official restraint, high legibility",
|
|
1852
|
+
],
|
|
1853
|
+
"security-tech": [
|
|
1854
|
+
"security technology visual style",
|
|
1855
|
+
"dark protective palette, sharp geometric accents, precise signal hierarchy",
|
|
1856
|
+
],
|
|
1857
|
+
"privacy-tech": [
|
|
1858
|
+
"privacy technology visual style",
|
|
1859
|
+
"calm protective tones, minimal lock-like geometry, trustworthy restraint",
|
|
1860
|
+
],
|
|
1861
|
+
"knowledge-base": [
|
|
1862
|
+
"knowledge base visual style",
|
|
1863
|
+
"organized documentation rhythm, calm typography, clear information grouping",
|
|
1864
|
+
],
|
|
1865
|
+
"research-lab": [
|
|
1866
|
+
"research lab visual style",
|
|
1867
|
+
"whitepaper clarity, subtle scientific texture, disciplined spacing",
|
|
1868
|
+
],
|
|
1869
|
+
"ops-dashboard": [
|
|
1870
|
+
"operations dashboard visual style",
|
|
1871
|
+
"dense status hierarchy, clear severity color, pragmatic interface polish",
|
|
1872
|
+
],
|
|
1873
|
+
"command-center": [
|
|
1874
|
+
"command center visual style",
|
|
1875
|
+
"dark operational density, luminous status accents, high-readability structure",
|
|
1876
|
+
],
|
|
1877
|
+
"map-visual": [
|
|
1878
|
+
"map visual style",
|
|
1879
|
+
"thin route lines, muted geography-like tones, precise labeling rhythm",
|
|
1880
|
+
],
|
|
1881
|
+
"timeline-editorial": [
|
|
1882
|
+
"timeline editorial style",
|
|
1883
|
+
"linear narrative rhythm, clear milestones, refined spacing",
|
|
1884
|
+
],
|
|
1885
|
+
"process-diagram": [
|
|
1886
|
+
"process diagram visual style",
|
|
1887
|
+
"sequential clarity, directional rhythm, clean explanatory structure",
|
|
1888
|
+
],
|
|
1889
|
+
"flowchart-clean": [
|
|
1890
|
+
"clean flowchart visual style",
|
|
1891
|
+
"simple node geometry, clear connector rhythm, high legibility",
|
|
1892
|
+
],
|
|
1893
|
+
"mind-map": [
|
|
1894
|
+
"mind-map visual style",
|
|
1895
|
+
"radial branching rhythm, organized idea clusters, clean linework",
|
|
1896
|
+
],
|
|
1897
|
+
"systems-map": [
|
|
1898
|
+
"systems map visual style",
|
|
1899
|
+
"relationship-first layout, precise grouping, sober annotation hierarchy",
|
|
1900
|
+
],
|
|
1901
|
+
"matrix-layout": [
|
|
1902
|
+
"matrix layout visual style",
|
|
1903
|
+
"two-axis comparison clarity, balanced cells, dense readability",
|
|
1904
|
+
],
|
|
1905
|
+
"scorecard": [
|
|
1906
|
+
"scorecard visual style",
|
|
1907
|
+
"compact metric hierarchy, strong label clarity, business-grade polish",
|
|
1908
|
+
],
|
|
1909
|
+
"kanban-board": [
|
|
1910
|
+
"kanban board visual style",
|
|
1911
|
+
"lane rhythm, compact task surfaces, clean operational hierarchy",
|
|
1912
|
+
],
|
|
1913
|
+
"roadmap": [
|
|
1914
|
+
"roadmap visual style",
|
|
1915
|
+
"phased progression, timeline clarity, strategic planning polish",
|
|
1916
|
+
],
|
|
1917
|
+
"okr-dashboard": [
|
|
1918
|
+
"OKR dashboard visual style",
|
|
1919
|
+
"goal hierarchy, progress emphasis, clean business structure",
|
|
1920
|
+
],
|
|
1921
|
+
"risk-matrix": [
|
|
1922
|
+
"risk matrix visual style",
|
|
1923
|
+
"severity-probability grid, alert color discipline, executive clarity",
|
|
1924
|
+
],
|
|
1925
|
+
"audit-report": [
|
|
1926
|
+
"audit report visual style",
|
|
1927
|
+
"formal table-like clarity, restrained palette, evidence-first structure",
|
|
1928
|
+
],
|
|
1929
|
+
"incident-report": [
|
|
1930
|
+
"incident report visual style",
|
|
1931
|
+
"clear severity hierarchy, timeline emphasis, operational readability",
|
|
1932
|
+
],
|
|
1933
|
+
"postmortem": [
|
|
1934
|
+
"postmortem report visual style",
|
|
1935
|
+
"root-cause clarity, sober tone, structured evidence hierarchy",
|
|
1936
|
+
],
|
|
1937
|
+
"qa-report": [
|
|
1938
|
+
"QA report visual style",
|
|
1939
|
+
"defect-status clarity, compact grids, pragmatic color coding",
|
|
1940
|
+
],
|
|
1941
|
+
"release-notes": [
|
|
1942
|
+
"release notes visual style",
|
|
1943
|
+
"versioned hierarchy, clean changelog rhythm, product communication polish",
|
|
1944
|
+
],
|
|
1945
|
+
"pitch-deck": [
|
|
1946
|
+
"pitch deck visual style",
|
|
1947
|
+
"bold narrative hierarchy, investor-grade polish, confident whitespace",
|
|
1948
|
+
],
|
|
1949
|
+
"strategy-deck": [
|
|
1950
|
+
"strategy deck visual style",
|
|
1951
|
+
"executive synthesis, restrained visuals, strong storyline hierarchy",
|
|
1952
|
+
],
|
|
1953
|
+
"board-deck": [
|
|
1954
|
+
"board deck visual style",
|
|
1955
|
+
"formal executive clarity, sparse high-signal layout, sober color accents",
|
|
1956
|
+
],
|
|
1957
|
+
"sales-deck": [
|
|
1958
|
+
"sales deck visual style",
|
|
1959
|
+
"benefit-led hierarchy, clean proof points, commercial polish",
|
|
1960
|
+
],
|
|
1961
|
+
"training-slide": [
|
|
1962
|
+
"training slide visual style",
|
|
1963
|
+
"instructional clarity, stepwise structure, readable learner pacing",
|
|
1964
|
+
],
|
|
1965
|
+
"lecture-slide": [
|
|
1966
|
+
"lecture slide visual style",
|
|
1967
|
+
"academic hierarchy, clear diagrams, minimal distraction",
|
|
1968
|
+
],
|
|
1969
|
+
"workshop-canvas": [
|
|
1970
|
+
"workshop canvas visual style",
|
|
1971
|
+
"sectioned workspace rhythm, writable zones, facilitation clarity",
|
|
1972
|
+
],
|
|
1973
|
+
"design-system": [
|
|
1974
|
+
"design system visual style",
|
|
1975
|
+
"component consistency, token-like spacing, systematic documentation clarity",
|
|
1976
|
+
],
|
|
1977
|
+
"style-tile": [
|
|
1978
|
+
"style tile visual format",
|
|
1979
|
+
"palette-type-texture grouping, compact design direction clarity",
|
|
1980
|
+
],
|
|
1981
|
+
"ui-kit": [
|
|
1982
|
+
"UI kit visual style",
|
|
1983
|
+
"component samples, consistent states, production-grade spacing",
|
|
1984
|
+
],
|
|
1985
|
+
"wireframe": [
|
|
1986
|
+
"wireframe visual style",
|
|
1987
|
+
"low-fidelity boxes, neutral lines, structure-first clarity",
|
|
1988
|
+
],
|
|
1989
|
+
"lo-fi-wireframe": [
|
|
1990
|
+
"low-fidelity wireframe style",
|
|
1991
|
+
"rough gray boxes, minimal decoration, quick structure communication",
|
|
1992
|
+
],
|
|
1993
|
+
"hi-fi-mockup": [
|
|
1994
|
+
"high-fidelity mockup style",
|
|
1995
|
+
"polished components, realistic spacing, production-ready surfaces",
|
|
1996
|
+
],
|
|
1997
|
+
"mobile-app-polish": [
|
|
1998
|
+
"mobile app polish",
|
|
1999
|
+
"compact hierarchy, thumb-friendly spacing, crisp interface surfaces",
|
|
2000
|
+
],
|
|
2001
|
+
"desktop-app-polish": [
|
|
2002
|
+
"desktop app polish",
|
|
2003
|
+
"dense controls, clear panes, precise productivity layout",
|
|
2004
|
+
],
|
|
2005
|
+
"web-landing-polish": [
|
|
2006
|
+
"web landing page polish",
|
|
2007
|
+
"strong hero hierarchy, clean conversion path, modern responsive feel",
|
|
2008
|
+
],
|
|
2009
|
+
"poster-grid": [
|
|
2010
|
+
"poster grid design style",
|
|
2011
|
+
"strong modular alignment, type-image rhythm, print-ready hierarchy",
|
|
2012
|
+
],
|
|
2013
|
+
"typographic-only": [
|
|
2014
|
+
"typographic-only visual style",
|
|
2015
|
+
"type as the primary graphic, strong spacing, expressive hierarchy",
|
|
2016
|
+
],
|
|
2017
|
+
"kinetic-type": [
|
|
2018
|
+
"kinetic typography visual style",
|
|
2019
|
+
"motion-implied type rhythm, dynamic spacing, energetic letterforms",
|
|
2020
|
+
],
|
|
2021
|
+
"calligraphic": [
|
|
2022
|
+
"calligraphic visual style",
|
|
2023
|
+
"flowing stroke contrast, ink rhythm, refined hand-drawn energy",
|
|
2024
|
+
],
|
|
2025
|
+
"brush-lettering": [
|
|
2026
|
+
"brush lettering style",
|
|
2027
|
+
"expressive stroke pressure, handmade curves, ink-like texture",
|
|
2028
|
+
],
|
|
2029
|
+
"blackletter": [
|
|
2030
|
+
"blackletter inspired typography",
|
|
2031
|
+
"dense angular letterforms, historical drama, high-contrast strokes",
|
|
2032
|
+
],
|
|
2033
|
+
"serif-editorial": [
|
|
2034
|
+
"serif editorial typography",
|
|
2035
|
+
"refined letter contrast, magazine-grade spacing, literary polish",
|
|
2036
|
+
],
|
|
2037
|
+
"grotesk-modern": [
|
|
2038
|
+
"grotesk modern typography",
|
|
2039
|
+
"neutral sans rhythm, exact spacing, contemporary clarity",
|
|
2040
|
+
],
|
|
2041
|
+
"mono-technical": [
|
|
2042
|
+
"monospace technical typography",
|
|
2043
|
+
"code-like rhythm, exact alignment, analytical clarity",
|
|
2044
|
+
],
|
|
2045
|
+
"variable-type": [
|
|
2046
|
+
"variable typography style",
|
|
2047
|
+
"dynamic weight contrast, flexible letter rhythm, modern type expression",
|
|
2048
|
+
],
|
|
2049
|
+
"ornamental-type": [
|
|
2050
|
+
"ornamental typography style",
|
|
2051
|
+
"decorative letter detail, display hierarchy, crafted type presence",
|
|
2052
|
+
],
|
|
2053
|
+
"handwritten": [
|
|
2054
|
+
"handwritten visual style",
|
|
2055
|
+
"natural stroke variation, informal rhythm, tactile personal feel",
|
|
2056
|
+
],
|
|
2057
|
+
"chalk-lettering": [
|
|
2058
|
+
"chalk lettering style",
|
|
2059
|
+
"powdery strokes, dark matte field, handmade signage texture",
|
|
2060
|
+
],
|
|
2061
|
+
"neon-lettering": [
|
|
2062
|
+
"neon lettering style",
|
|
2063
|
+
"tube-like strokes, luminous glow, dark-field contrast",
|
|
2064
|
+
],
|
|
2065
|
+
"metallic-type": [
|
|
2066
|
+
"metallic typography style",
|
|
2067
|
+
"reflective letter surfaces, bevel highlights, premium dimensionality",
|
|
2068
|
+
],
|
|
2069
|
+
"embossed-type": [
|
|
2070
|
+
"embossed typography style",
|
|
2071
|
+
"raised paper texture, soft shadows, tactile print depth",
|
|
2072
|
+
],
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
DEFAULT_VARIANT_PRESETS = ["corporate", "premium", "minimal", "flat-vector", "photoreal", "clean-ui"]
|
|
2076
|
+
|
|
365
2077
|
ASPECT_SIZE = {
|
|
366
2078
|
"3:4": "portrait",
|
|
367
2079
|
"4:3": "landscape",
|
|
@@ -373,6 +2085,12 @@ ASPECT_SIZE = {
|
|
|
373
2085
|
BUZZWORDS = ["stunning", "beautiful", "professional", "high quality", "nice", "modern", "高级感"]
|
|
374
2086
|
|
|
375
2087
|
TEMPLATE_DEFS = {
|
|
2088
|
+
"poster_general": {
|
|
2089
|
+
"asset_type": "poster",
|
|
2090
|
+
"label": "通用视觉海报",
|
|
2091
|
+
"layout": "strong title area, relevant main visual, supporting content blocks only when requested, quiet footer if needed",
|
|
2092
|
+
"keywords": [],
|
|
2093
|
+
},
|
|
376
2094
|
"poster_zh_promo": {
|
|
377
2095
|
"asset_type": "poster",
|
|
378
2096
|
"label": "中文促销海报",
|
|
@@ -397,18 +2115,36 @@ TEMPLATE_DEFS = {
|
|
|
397
2115
|
"layout": "modular grid with clear title, sections, callouts, and footer rules",
|
|
398
2116
|
"keywords": ["日程", "规则", "清单", "信息", "流程", "说明"],
|
|
399
2117
|
},
|
|
2118
|
+
"poster_social_cover": {
|
|
2119
|
+
"asset_type": "poster",
|
|
2120
|
+
"label": "社媒封面 / 方图",
|
|
2121
|
+
"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",
|
|
2122
|
+
"keywords": ["小红书", "社媒", "方图", "封面", "cover", "social"],
|
|
2123
|
+
},
|
|
400
2124
|
"ui_mobile_home": {
|
|
401
2125
|
"asset_type": "ui",
|
|
402
2126
|
"label": "移动 App 首页",
|
|
403
2127
|
"layout": "phone status bar, app header, content cards, primary action, bottom navigation",
|
|
404
|
-
"keywords": ["
|
|
2128
|
+
"keywords": ["首页", "home", "home screen", "app 首页", "手机首页", "移动首页", "小程序首页"],
|
|
2129
|
+
},
|
|
2130
|
+
"ui_requested_screen": {
|
|
2131
|
+
"asset_type": "ui",
|
|
2132
|
+
"label": "指定界面",
|
|
2133
|
+
"layout": "preserve the requested screen type, named sections, controls, and reading order; include only UI elements directly requested or implied by the screen type",
|
|
2134
|
+
"keywords": ["设置", "详情", "列表", "表单", "profile", "settings", "detail", "list", "form"],
|
|
405
2135
|
},
|
|
406
2136
|
"ui_dashboard": {
|
|
407
2137
|
"asset_type": "ui",
|
|
408
2138
|
"label": "Web / SaaS Dashboard",
|
|
409
|
-
"layout": "
|
|
2139
|
+
"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
2140
|
"keywords": ["dashboard", "仪表盘", "后台", "saas", "web"],
|
|
411
2141
|
},
|
|
2142
|
+
"slide_corporate_report": {
|
|
2143
|
+
"asset_type": "slide",
|
|
2144
|
+
"label": "企业汇报单页",
|
|
2145
|
+
"layout": "widescreen 16:9 corporate presentation slide; preserve every explicit region, section structure, visual element placement, and spacing requirement from the user brief",
|
|
2146
|
+
"keywords": ["ppt", "powerpoint", "slide", "presentation", "deck", "widescreen", "汇报", "报告", "分析", "风险", "幻灯片", "演示文稿"],
|
|
2147
|
+
},
|
|
412
2148
|
"diagram_rag": {
|
|
413
2149
|
"asset_type": "diagram",
|
|
414
2150
|
"label": "RAG 架构图",
|
|
@@ -424,7 +2160,7 @@ TEMPLATE_DEFS = {
|
|
|
424
2160
|
"product_hero": {
|
|
425
2161
|
"asset_type": "product",
|
|
426
2162
|
"label": "产品英雄图",
|
|
427
|
-
"layout": "single product hero,
|
|
2163
|
+
"layout": "single requested product hero, no props unless requested, clear material close-up, editorial finish",
|
|
428
2164
|
"keywords": ["产品", "商品", "渲染", "电商", "新品"],
|
|
429
2165
|
},
|
|
430
2166
|
"illustration_scene": {
|
|
@@ -502,10 +2238,59 @@ def split_csv(value: str | None) -> list[str]:
|
|
|
502
2238
|
return [v.strip() for v in re.split(r"[,,]", value) if v.strip()]
|
|
503
2239
|
|
|
504
2240
|
|
|
2241
|
+
def available_style_presets(include_auto: bool = False) -> list[str]:
|
|
2242
|
+
names = sorted(STYLE_PRESETS.keys())
|
|
2243
|
+
return names if include_auto else [name for name in names if name != "auto"]
|
|
2244
|
+
|
|
2245
|
+
|
|
2246
|
+
def parse_style_preset_list(value: str | None, default: list[str] | None = None) -> list[str]:
|
|
2247
|
+
if not value:
|
|
2248
|
+
return list(default or [])
|
|
2249
|
+
raw = [item.strip() for item in split_csv(value)]
|
|
2250
|
+
if any(item.lower() == "all" for item in raw):
|
|
2251
|
+
raw = available_style_presets()
|
|
2252
|
+
seen: set[str] = set()
|
|
2253
|
+
presets: list[str] = []
|
|
2254
|
+
invalid: list[str] = []
|
|
2255
|
+
for item in raw:
|
|
2256
|
+
if not item:
|
|
2257
|
+
continue
|
|
2258
|
+
if item not in STYLE_PRESETS:
|
|
2259
|
+
invalid.append(item)
|
|
2260
|
+
continue
|
|
2261
|
+
if item not in seen:
|
|
2262
|
+
seen.add(item)
|
|
2263
|
+
presets.append(item)
|
|
2264
|
+
if invalid:
|
|
2265
|
+
allowed = ", ".join(available_style_presets(include_auto=True)) + ", all"
|
|
2266
|
+
raise ValueError(f"未知风格预设:{', '.join(invalid)}。可选:{allowed}")
|
|
2267
|
+
return presets
|
|
2268
|
+
|
|
2269
|
+
|
|
505
2270
|
def normalize_ws(text: str) -> str:
|
|
506
2271
|
return " ".join(text.split())
|
|
507
2272
|
|
|
508
2273
|
|
|
2274
|
+
def strip_nonvisual_request_meta(text: str) -> str:
|
|
2275
|
+
"""移除尾部对 agent/skill 的交互指令,避免污染生图 prompt。"""
|
|
2276
|
+
lines = text.splitlines()
|
|
2277
|
+
while lines:
|
|
2278
|
+
line = lines[-1].strip()
|
|
2279
|
+
compact = re.sub(r"\s+", "", line.lower())
|
|
2280
|
+
if not line:
|
|
2281
|
+
lines.pop()
|
|
2282
|
+
continue
|
|
2283
|
+
has_tool_ref = any(k in compact for k in ["skill", "技能", "模型", "agent"])
|
|
2284
|
+
has_action_ref = any(k in compact for k in ["画图", "出图", "生成图片", "看看效果", "看效果", "试试", "测试"])
|
|
2285
|
+
is_request_meta = any(k in compact for k in ["这是我的一个需求", "这是我的需求", "这个需求"])
|
|
2286
|
+
if (has_tool_ref and has_action_ref) or (is_request_meta and has_action_ref):
|
|
2287
|
+
lines.pop()
|
|
2288
|
+
continue
|
|
2289
|
+
break
|
|
2290
|
+
cleaned = "\n".join(lines).strip()
|
|
2291
|
+
return cleaned or text.strip()
|
|
2292
|
+
|
|
2293
|
+
|
|
509
2294
|
def has_cjk(text: str) -> bool:
|
|
510
2295
|
return bool(re.search(r"[\u4e00-\u9fff]", text))
|
|
511
2296
|
|
|
@@ -540,15 +2325,49 @@ def safety_avoid_list(notes: list[str]) -> list[str]:
|
|
|
540
2325
|
]
|
|
541
2326
|
|
|
542
2327
|
|
|
2328
|
+
def keyword_in_text(keyword: str, text_lower: str) -> bool:
|
|
2329
|
+
kw = keyword.lower().strip()
|
|
2330
|
+
if not kw:
|
|
2331
|
+
return False
|
|
2332
|
+
positions: list[int] = []
|
|
2333
|
+
if kw.isascii() and re.search(r"[a-z0-9]", kw):
|
|
2334
|
+
positions = [m.start() for m in re.finditer(rf"(?<![a-z0-9]){re.escape(kw)}(?![a-z0-9])", text_lower)]
|
|
2335
|
+
else:
|
|
2336
|
+
start = 0
|
|
2337
|
+
while True:
|
|
2338
|
+
pos = text_lower.find(kw, start)
|
|
2339
|
+
if pos < 0:
|
|
2340
|
+
break
|
|
2341
|
+
positions.append(pos)
|
|
2342
|
+
start = pos + max(1, len(kw))
|
|
2343
|
+
for pos in positions:
|
|
2344
|
+
context = text_lower[max(0, pos - 14) : pos]
|
|
2345
|
+
if any(marker in context for marker in ["不要", "不需要", "不能", "不得", "无", "没有", "no ", "not ", "without "]):
|
|
2346
|
+
continue
|
|
2347
|
+
return True
|
|
2348
|
+
return False
|
|
2349
|
+
|
|
2350
|
+
|
|
543
2351
|
def route_asset_type(request: str, override: str | None = None) -> str:
|
|
544
2352
|
if override:
|
|
545
2353
|
return override
|
|
546
2354
|
lower = request.lower()
|
|
2355
|
+
explicit_routes = [
|
|
2356
|
+
("slide", ["powerpoint", "slide", "presentation", "ppt", "幻灯片", "演示文稿", "汇报单页"]),
|
|
2357
|
+
("ui", ["ui", "界面", "app", "dashboard", "仪表盘", "看板", "saas", "后台", "控制台", "网页", "mockup"]),
|
|
2358
|
+
("diagram", ["架构图", "系统图", "流程架构", "architecture diagram", "system diagram"]),
|
|
2359
|
+
("infographic", ["信息图", "infographic", "图解", "流程图", "时间线"]),
|
|
2360
|
+
("poster", ["海报", "poster", "banner", "主视觉", "kv", "封面"]),
|
|
2361
|
+
("logo", ["logo", "品牌标识", "字标", "visual identity"]),
|
|
2362
|
+
]
|
|
2363
|
+
for asset_type, keywords in explicit_routes:
|
|
2364
|
+
if any(keyword_in_text(kw, lower) for kw in keywords):
|
|
2365
|
+
return asset_type
|
|
547
2366
|
best = ("poster", 0)
|
|
548
2367
|
for asset_type, meta in ASSET_ROUTES.items():
|
|
549
2368
|
score = 0
|
|
550
2369
|
for kw in meta["keywords"]:
|
|
551
|
-
if kw
|
|
2370
|
+
if keyword_in_text(str(kw), lower):
|
|
552
2371
|
score += 1
|
|
553
2372
|
if score > best[1]:
|
|
554
2373
|
best = (asset_type, score)
|
|
@@ -584,10 +2403,10 @@ def infer_quality(request: str, asset_type: str, texts: list[str], override: str
|
|
|
584
2403
|
if override:
|
|
585
2404
|
return override
|
|
586
2405
|
lower = request.lower()
|
|
2406
|
+
if texts or asset_type in {"poster", "ui", "infographic", "slide", "diagram", "logo"}:
|
|
2407
|
+
return "high"
|
|
587
2408
|
if any(k in lower for k in ["草稿", "draft", "探索"]):
|
|
588
2409
|
return "medium"
|
|
589
|
-
if texts or asset_type in {"poster", "ui", "infographic", "diagram", "logo"}:
|
|
590
|
-
return "high"
|
|
591
2410
|
prof = str(profile.get("default_quality") or "").strip()
|
|
592
2411
|
if prof:
|
|
593
2412
|
return prof
|
|
@@ -596,19 +2415,39 @@ def infer_quality(request: str, asset_type: str, texts: list[str], override: str
|
|
|
596
2415
|
|
|
597
2416
|
def extract_required_texts(request: str, explicit_texts: list[str]) -> list[str]:
|
|
598
2417
|
seen: set[str] = set()
|
|
599
|
-
|
|
2418
|
+
candidates: list[tuple[int, str]] = []
|
|
2419
|
+
|
|
2420
|
+
def clean_text(text: str) -> str:
|
|
2421
|
+
text = text.strip(" \t\n\r,,、。;;::.!?!?")
|
|
2422
|
+
text = re.sub(r"^(?:[^::]{0,12}(?:指标卡|卡片|列表|标题|模块|节点|步骤|栏目|分支))[::]", "", text)
|
|
2423
|
+
text = re.sub(r"^(?:顶部|底部|中间|左侧|右侧|上方|下方|首页|页面)?(?:主标题|副标题|标题)\s+", "", text)
|
|
2424
|
+
text = re.sub(r"^(?:一个|一枚|一项)?(?:明显的|醒目的|主要的|primary\s+)?(.{1,16})按钮$", r"\1", text, flags=re.IGNORECASE)
|
|
2425
|
+
text = re.sub(r"(?:网格|列表|区域|模块)$", "", text)
|
|
2426
|
+
text = re.sub(r"(?:要)?(?:渲染)?(?:清晰|清楚|可读|明显)$", "", text)
|
|
2427
|
+
text = re.sub(r"(?:这些)?元素$", "", text)
|
|
2428
|
+
text = re.sub(r"(?:[一二三四五六七八九十\d]+个)?步骤$", "", text)
|
|
2429
|
+
if text in {"顶部导航", "底部导航", "左侧导航", "右侧导航", "导航栏", "顶部栏", "状态栏", "箭头关系", "箭头", "关系"}:
|
|
2430
|
+
return ""
|
|
2431
|
+
return text.strip(" \t\n\r,,、。;;::.!?!?")
|
|
2432
|
+
|
|
2433
|
+
def add_candidate(start: int, text: str) -> None:
|
|
2434
|
+
text = clean_text(text)
|
|
2435
|
+
key = re.sub(r"\s+", "", text)
|
|
2436
|
+
if 0 < len(text) <= 40 and key not in seen:
|
|
2437
|
+
seen.add(key)
|
|
2438
|
+
candidates.append((start, text))
|
|
600
2439
|
|
|
601
2440
|
def add(text: str) -> None:
|
|
602
|
-
text = text
|
|
2441
|
+
text = clean_text(text)
|
|
603
2442
|
key = re.sub(r"\s+", "", text)
|
|
604
2443
|
if 0 < len(text) <= 40 and key not in seen:
|
|
605
2444
|
seen.add(key)
|
|
606
|
-
|
|
2445
|
+
candidates.append((len(candidates), text))
|
|
607
2446
|
|
|
608
2447
|
for item in explicit_texts:
|
|
609
2448
|
add(item)
|
|
610
2449
|
if explicit_texts:
|
|
611
|
-
return
|
|
2450
|
+
return [text for _, text in candidates]
|
|
612
2451
|
patterns = [
|
|
613
2452
|
r'"([^"\n]{1,40})"',
|
|
614
2453
|
r"'([^'\n]{1,40})'",
|
|
@@ -617,24 +2456,108 @@ def extract_required_texts(request: str, explicit_texts: list[str]) -> list[str]
|
|
|
617
2456
|
r"『([^』\n]{1,40})』",
|
|
618
2457
|
]
|
|
619
2458
|
for pat in patterns:
|
|
620
|
-
for match in re.
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
2459
|
+
for match in re.finditer(pat, request):
|
|
2460
|
+
add_candidate(match.start(1), match.group(1))
|
|
2461
|
+
title_patterns = [
|
|
2462
|
+
r"\btitle\s*[::]\s*([^,,。.;;\n]{1,40})",
|
|
2463
|
+
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]|$)",
|
|
2464
|
+
r"(?:中文标题|标题)\s*[::]\s*(.+?)(?=\s+(?:要点|图标|栏目|底部)\b|[,,。.;;\n]|$)",
|
|
2465
|
+
]
|
|
2466
|
+
for pat in title_patterns:
|
|
2467
|
+
for match in re.finditer(pat, request, flags=re.IGNORECASE):
|
|
2468
|
+
add_candidate(match.start(1), match.group(1))
|
|
2469
|
+
bullet_patterns = [
|
|
2470
|
+
r"(?:Bullet\s+points?|bullets?)\s*[::]\s*(.+?)(?=\s+(?:Icon|Column\s+\d+|Bottom\s+area|High\s+contrast)\b|[。\n]|$)",
|
|
2471
|
+
r"(?:项目符号|要点|Bullet点)\s*[::]\s*(.+?)(?=\s+(?:图标|栏目|底部)\b|[。\n]|$)",
|
|
2472
|
+
]
|
|
2473
|
+
for pat in bullet_patterns:
|
|
2474
|
+
for match in re.finditer(pat, request, flags=re.IGNORECASE):
|
|
2475
|
+
value = match.group(1)
|
|
2476
|
+
for part_match in re.finditer(r"[^,,、;;]+", value):
|
|
2477
|
+
add_candidate(match.start(1) + part_match.start(), part_match.group(0))
|
|
2478
|
+
text_hint = r"(?:写上|写|显示|文案|标题|品牌名|wordmark)"
|
|
2479
|
+
for match in re.finditer(rf"{text_hint}[::\s]*(?:写上|写|显示|为|叫)?[::\s]*([^,,、。;;,.]{{2,24}})", request, flags=re.IGNORECASE):
|
|
2480
|
+
add_candidate(match.start(1), match.group(1))
|
|
2481
|
+
for match in re.finditer(r"(?:文字|文案)\s*(?:写上|写|显示|为|是|[::])\s*([^,,、。;;,.]{2,24})", request, flags=re.IGNORECASE):
|
|
2482
|
+
add_candidate(match.start(1), match.group(1))
|
|
2483
|
+
for match in re.finditer(r"(?:文字|文案)\s*([^,,、。;;,.]{2,24}?)(?:要)?(?:清晰|清楚|可读)", request, flags=re.IGNORECASE):
|
|
2484
|
+
add_candidate(match.start(1), match.group(1))
|
|
2485
|
+
for match in re.finditer(r"\d+(?:\s*/\s*\d+)?\s*元", request):
|
|
2486
|
+
add_candidate(match.start(), match.group(0))
|
|
2487
|
+
candidates.sort(key=lambda item: item[0])
|
|
2488
|
+
return [text for _, text in candidates]
|
|
2489
|
+
|
|
2490
|
+
|
|
2491
|
+
def merge_texts(primary: list[str], extra: list[str]) -> list[str]:
|
|
2492
|
+
merged: list[str] = []
|
|
2493
|
+
seen: set[str] = set()
|
|
2494
|
+
for item in primary + extra:
|
|
2495
|
+
text = item.strip(" \t\n\r,,、。;;::.!?!?")
|
|
2496
|
+
text = re.sub(r"^(?:[^::]{0,12}(?:指标卡|卡片|列表|标题|模块|节点|步骤|栏目|分支))[::]", "", text)
|
|
2497
|
+
text = re.sub(r"^(?:顶部|底部|中间|左侧|右侧|上方|下方|首页|页面)?(?:主标题|副标题|标题)\s+", "", text)
|
|
2498
|
+
text = re.sub(r"^(?:一个|一枚|一项)?(?:明显的|醒目的|主要的|primary\s+)?(.{1,16})按钮$", r"\1", text, flags=re.IGNORECASE)
|
|
2499
|
+
text = re.sub(r"(?:网格|列表|区域|模块)$", "", text)
|
|
2500
|
+
text = re.sub(r"(?:要)?(?:渲染)?(?:清晰|清楚|可读|明显)$", "", text)
|
|
2501
|
+
text = re.sub(r"(?:这些)?元素$", "", text)
|
|
2502
|
+
text = re.sub(r"(?:[一二三四五六七八九十\d]+个)?步骤$", "", text)
|
|
2503
|
+
text = text.strip(" \t\n\r,,、。;;::.!?!?")
|
|
2504
|
+
if text in {"顶部导航", "底部导航", "左侧导航", "右侧导航", "导航栏", "顶部栏", "状态栏", "箭头关系", "箭头", "关系"}:
|
|
2505
|
+
continue
|
|
2506
|
+
key = re.sub(r"\s+", "", text)
|
|
2507
|
+
if text and key not in seen:
|
|
2508
|
+
seen.add(key)
|
|
2509
|
+
merged.append(text)
|
|
2510
|
+
return merged
|
|
628
2511
|
|
|
629
2512
|
|
|
630
|
-
def
|
|
2513
|
+
def extract_structural_labels(request: str, asset_type: str) -> list[str]:
|
|
2514
|
+
if asset_type not in {"diagram", "infographic", "slide", "ui", "poster"}:
|
|
2515
|
+
return []
|
|
2516
|
+
candidates: list[tuple[int, str]] = []
|
|
2517
|
+
list_intro = r"(?:需要)?(?:展示|呈现|列出|包含|包括|含有|分为|覆盖)"
|
|
2518
|
+
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)"
|
|
2519
|
+
patterns = [
|
|
2520
|
+
rf"{list_intro}\s*(?:这些|以下|对应的)?(?:模块|部分|层|栏目|节点|入口|能力|场景|列表|指标卡|卡片|步骤|分支)?[::\s]*([^。;;\n]{{2,180}})",
|
|
2521
|
+
rf"(?:模块|节点|栏目|部分|层|入口|能力|场景|列表|指标卡|卡片|步骤|分支)\s*(?:包括|包含|有|为)[::\s]*([^。;;\n]{{2,180}})",
|
|
2522
|
+
rf"(?:模块|节点|栏目|部分|层|入口|能力|场景|列表|指标卡|卡片|步骤|分支)[^。;;\n::]{{0,16}}[::]([^。;;\n]{{2,180}})",
|
|
2523
|
+
]
|
|
2524
|
+
for pattern in patterns:
|
|
2525
|
+
for match in re.finditer(pattern, request, flags=re.IGNORECASE):
|
|
2526
|
+
value = re.split(stop_words, match.group(1), maxsplit=1, flags=re.IGNORECASE)[0]
|
|
2527
|
+
for part_match in re.finditer(r"[^、,,;;/|]+", value):
|
|
2528
|
+
part = part_match.group(0).strip(" \t\n\r,,、::")
|
|
2529
|
+
part = re.sub(r"^(?:[^::]{0,12}(?:指标卡|卡片|列表|标题|模块|节点|步骤|栏目|分支))[::]", "", part)
|
|
2530
|
+
part = re.sub(r"^(?:和|与|及|以及|and)\s*", "", part, flags=re.IGNORECASE).strip()
|
|
2531
|
+
part = re.sub(r"\s*(?:和|与|及|以及|and)$", "", part, flags=re.IGNORECASE).strip()
|
|
2532
|
+
part = re.sub(r"(?:这些)?元素$", "", part)
|
|
2533
|
+
part = re.sub(r"(?:[一二三四五六七八九十\d]+个)?步骤$", "", part)
|
|
2534
|
+
part = part.strip(" \t\n\r,,、::")
|
|
2535
|
+
if not part:
|
|
2536
|
+
continue
|
|
2537
|
+
if re.fullmatch(r"\d+(?:\s*:\s*\d+)?", part):
|
|
2538
|
+
continue
|
|
2539
|
+
if len(part) > 36:
|
|
2540
|
+
continue
|
|
2541
|
+
if not re.search(r"[\u4e00-\u9fffA-Za-z]", part):
|
|
2542
|
+
continue
|
|
2543
|
+
candidates.append((match.start(1) + part_match.start(), part))
|
|
2544
|
+
candidates.sort(key=lambda item: item[0])
|
|
2545
|
+
return merge_texts([], [text for _, text in candidates])
|
|
2546
|
+
|
|
2547
|
+
|
|
2548
|
+
def infer_style_anchors(request: str, override: str | None, profile: dict, preset: str | None = None) -> list[str]:
|
|
631
2549
|
anchors: list[str] = []
|
|
632
|
-
if override:
|
|
633
|
-
anchors.extend(split_csv(override))
|
|
634
2550
|
lower = request.lower()
|
|
635
2551
|
for keys, anchor in STYLE_HINTS:
|
|
636
|
-
if any(k.lower()
|
|
2552
|
+
if any(keyword_in_text(k.lower(), lower) for k in keys):
|
|
637
2553
|
anchors.append(anchor)
|
|
2554
|
+
if override:
|
|
2555
|
+
anchors.extend(split_csv(override))
|
|
2556
|
+
profile_preset = str(profile.get("default_style_preset") or "").strip()
|
|
2557
|
+
chosen_preset = (preset or profile_preset or "auto").strip()
|
|
2558
|
+
for item in STYLE_PRESETS.get(chosen_preset, []):
|
|
2559
|
+
if item and item not in anchors:
|
|
2560
|
+
anchors.append(item)
|
|
638
2561
|
favored = profile.get("favored_styles") or []
|
|
639
2562
|
if isinstance(favored, str):
|
|
640
2563
|
favored = split_csv(favored)
|
|
@@ -646,12 +2569,19 @@ def infer_style_anchors(request: str, override: str | None, profile: dict) -> li
|
|
|
646
2569
|
return anchors[:4]
|
|
647
2570
|
|
|
648
2571
|
|
|
649
|
-
def infer_negative(asset_type: str, texts: list[str], profile: dict) -> list[str]:
|
|
650
|
-
negative = ["avoid vague generic AI gloss", "avoid clutter"]
|
|
2572
|
+
def infer_negative(asset_type: str, texts: list[str], profile: dict, request: str = "", template_id: str = "") -> list[str]:
|
|
2573
|
+
negative = ["avoid vague generic AI gloss", "avoid clutter", "avoid adding content not requested by the user"]
|
|
651
2574
|
if texts:
|
|
652
2575
|
negative.append("avoid garbled or wrong text")
|
|
653
|
-
if asset_type in {"poster", "ui", "infographic", "diagram", "logo"}:
|
|
2576
|
+
if asset_type in {"poster", "ui", "infographic", "slide", "diagram", "logo"}:
|
|
654
2577
|
negative.append("avoid fake logos and unreadable microtext")
|
|
2578
|
+
if asset_type == "slide":
|
|
2579
|
+
negative.extend(["avoid adding modules outside the user brief", "avoid changing the requested section structure"])
|
|
2580
|
+
if template_id == "poster_social_cover":
|
|
2581
|
+
lower = request.lower()
|
|
2582
|
+
people_terms = ["人物", "角色", "人像", "头像", "真人", "女孩", "男孩", "人类", "mascot", "avatar", "person", "people", "character", "portrait"]
|
|
2583
|
+
if not any(keyword_in_text(term, lower) for term in people_terms):
|
|
2584
|
+
negative.append("avoid unrequested people, faces, avatars, mascots, or character illustrations")
|
|
655
2585
|
if asset_type == "photography":
|
|
656
2586
|
negative.extend(["avoid HDR over-processing", "avoid plastic skin"])
|
|
657
2587
|
avoided = profile.get("avoided_elements") or []
|
|
@@ -671,9 +2601,11 @@ def infer_tags(asset_type: str, request: str, extra: str | None = None) -> list[
|
|
|
671
2601
|
"tea": ["茶", "茶饮", "冷泡"],
|
|
672
2602
|
"brand": ["品牌", "logo", "标识"],
|
|
673
2603
|
"promo": ["促销", "价格", "优惠", "新品"],
|
|
2604
|
+
"presentation": ["ppt", "powerpoint", "slide", "presentation", "汇报", "幻灯片"],
|
|
674
2605
|
"academic": ["论文", "学术", "系统", "模型"],
|
|
675
2606
|
}.items():
|
|
676
|
-
|
|
2607
|
+
lower = request.lower()
|
|
2608
|
+
if any(keyword_in_text(v, lower) for v in vals) and key not in tags:
|
|
677
2609
|
tags.append(key)
|
|
678
2610
|
for item in split_csv(extra):
|
|
679
2611
|
if item not in tags:
|
|
@@ -687,19 +2619,27 @@ def infer_template_id(request: str, asset_type: str, override: str | None = None
|
|
|
687
2619
|
lower = request.lower()
|
|
688
2620
|
if asset_type == "diagram" and "rag" in lower:
|
|
689
2621
|
return "diagram_rag"
|
|
2622
|
+
if asset_type == "poster":
|
|
2623
|
+
if any(keyword_in_text(kw, lower) for kw in ["小红书", "社媒", "方图", "封面", "cover", "social"]):
|
|
2624
|
+
return "poster_social_cover"
|
|
2625
|
+
if any(keyword_in_text(kw, lower) for kw in ["品牌", "主视觉", "kv", "brand key visual"]):
|
|
2626
|
+
return "poster_brand_kv"
|
|
2627
|
+
if any(keyword_in_text(kw, lower) for kw in ["信息", "清单", "流程", "说明"]):
|
|
2628
|
+
return "poster_info_dense"
|
|
690
2629
|
candidates = {tid: meta for tid, meta in TEMPLATE_DEFS.items() if meta["asset_type"] == asset_type}
|
|
691
2630
|
best_id = ""
|
|
692
2631
|
best_score = -1
|
|
693
2632
|
for tid, meta in candidates.items():
|
|
694
|
-
score = sum(1 for kw in meta["keywords"] if kw
|
|
2633
|
+
score = sum(1 for kw in meta["keywords"] if keyword_in_text(str(kw), lower))
|
|
695
2634
|
if score > best_score:
|
|
696
2635
|
best_id = tid
|
|
697
2636
|
best_score = score
|
|
698
2637
|
if best_score > 0 and best_id:
|
|
699
2638
|
return best_id
|
|
700
2639
|
defaults = {
|
|
701
|
-
"poster": "
|
|
702
|
-
"ui": "
|
|
2640
|
+
"poster": "poster_general",
|
|
2641
|
+
"ui": "ui_requested_screen",
|
|
2642
|
+
"slide": "slide_corporate_report",
|
|
703
2643
|
"diagram": "diagram_system",
|
|
704
2644
|
"product": "product_hero",
|
|
705
2645
|
"illustration": "illustration_scene",
|
|
@@ -714,15 +2654,85 @@ def infer_layout(template_id: str, asset_type: str) -> str:
|
|
|
714
2654
|
"photography": "single realistic capture with foreground, subject, and environmental context",
|
|
715
2655
|
"character": "reference sheet grid with turnaround, expressions, details, and palette",
|
|
716
2656
|
"logo": "brand board grid with mark, wordmark, palette, type sample, and applications",
|
|
717
|
-
"
|
|
2657
|
+
"slide": "widescreen presentation slide that preserves the user's explicit regions, hierarchy, and reading order",
|
|
2658
|
+
"infographic": "preserve the requested information units, relationships, section count, and reading order",
|
|
718
2659
|
}
|
|
719
2660
|
return fallback.get(asset_type, "clear composition with named regions and stable visual hierarchy")
|
|
720
2661
|
|
|
721
2662
|
|
|
2663
|
+
CHINESE_ORDINALS = {
|
|
2664
|
+
"一": 1,
|
|
2665
|
+
"二": 2,
|
|
2666
|
+
"两": 2,
|
|
2667
|
+
"三": 3,
|
|
2668
|
+
"四": 4,
|
|
2669
|
+
"五": 5,
|
|
2670
|
+
"六": 6,
|
|
2671
|
+
"七": 7,
|
|
2672
|
+
"八": 8,
|
|
2673
|
+
"九": 9,
|
|
2674
|
+
"十": 10,
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
|
|
2678
|
+
def parse_ordinal(value: str) -> int | None:
|
|
2679
|
+
value = value.strip()
|
|
2680
|
+
if value.isdigit():
|
|
2681
|
+
return int(value)
|
|
2682
|
+
if value in CHINESE_ORDINALS:
|
|
2683
|
+
return CHINESE_ORDINALS[value]
|
|
2684
|
+
return None
|
|
2685
|
+
|
|
2686
|
+
|
|
2687
|
+
def infer_slide_column_count(request: str) -> int | None:
|
|
2688
|
+
lower = request.lower()
|
|
2689
|
+
if any(k in lower for k in ["three columns", "3 columns", "三栏", "三列", "三等分"]):
|
|
2690
|
+
return 3
|
|
2691
|
+
if any(k in lower for k in ["two columns", "2 columns", "双栏", "双列", "两栏", "两列"]):
|
|
2692
|
+
return 2
|
|
2693
|
+
max_col = 0
|
|
2694
|
+
for match in re.finditer(r"\bcolumn\s*(\d+)\b", lower):
|
|
2695
|
+
max_col = max(max_col, int(match.group(1)))
|
|
2696
|
+
for match in re.finditer(r"(?:第|栏目|栏|列)\s*([一二两三四五六七八九十\d]+)\s*(?:栏|列|部分|模块)?", request):
|
|
2697
|
+
parsed = parse_ordinal(match.group(1))
|
|
2698
|
+
if parsed:
|
|
2699
|
+
max_col = max(max_col, parsed)
|
|
2700
|
+
return max_col or None
|
|
2701
|
+
|
|
2702
|
+
|
|
2703
|
+
def slide_column_before(request: str, pos: int) -> int | None:
|
|
2704
|
+
prefix = request[:pos]
|
|
2705
|
+
matches: list[tuple[int, int]] = []
|
|
2706
|
+
for match in re.finditer(r"\bcolumn\s*(\d+)\b", prefix, flags=re.IGNORECASE):
|
|
2707
|
+
matches.append((match.start(), int(match.group(1)) - 1))
|
|
2708
|
+
for match in re.finditer(r"(?:第|栏目|栏|列)\s*([一二两三四五六七八九十\d]+)\s*(?:栏|列|部分|模块)?", prefix):
|
|
2709
|
+
parsed = parse_ordinal(match.group(1))
|
|
2710
|
+
if parsed:
|
|
2711
|
+
matches.append((match.start(), parsed - 1))
|
|
2712
|
+
if not matches:
|
|
2713
|
+
return None
|
|
2714
|
+
return max(matches, key=lambda item: item[0])[1]
|
|
2715
|
+
|
|
2716
|
+
|
|
2717
|
+
def text_positions_in_request(texts: list[str], request: str) -> list[int]:
|
|
2718
|
+
positions: list[int] = []
|
|
2719
|
+
cursor = 0
|
|
2720
|
+
for text in texts:
|
|
2721
|
+
pos = request.find(text, cursor)
|
|
2722
|
+
if pos < 0:
|
|
2723
|
+
pos = request.find(text)
|
|
2724
|
+
positions.append(pos)
|
|
2725
|
+
if pos >= 0:
|
|
2726
|
+
cursor = pos + len(text)
|
|
2727
|
+
return positions
|
|
2728
|
+
|
|
2729
|
+
|
|
722
2730
|
def infer_text_hierarchy(asset_type: str, texts: list[str], request: str) -> list[dict]:
|
|
723
2731
|
if not texts:
|
|
724
2732
|
return []
|
|
725
2733
|
roles = []
|
|
2734
|
+
positions = text_positions_in_request(texts, request)
|
|
2735
|
+
slide_rows: dict[int, int] = {}
|
|
726
2736
|
for idx, text in enumerate(texts):
|
|
727
2737
|
role = "label"
|
|
728
2738
|
area = "content area"
|
|
@@ -735,20 +2745,54 @@ def infer_text_hierarchy(asset_type: str, texts: list[str], request: str) -> lis
|
|
|
735
2745
|
else:
|
|
736
2746
|
role, area, priority = "supporting_copy", "secondary copy zone", "medium"
|
|
737
2747
|
elif asset_type == "ui":
|
|
738
|
-
|
|
2748
|
+
if re.search(r"(?:产品(?:叫|名)|app\s+name|应用名|品牌名)[^。;;\n]{0,20}" + re.escape(text), request, flags=re.IGNORECASE):
|
|
2749
|
+
role = "app_name"
|
|
2750
|
+
else:
|
|
2751
|
+
role = "ui_label"
|
|
2752
|
+
area, priority = "relevant UI component", "high"
|
|
739
2753
|
elif asset_type == "diagram":
|
|
740
2754
|
role, area, priority = "component_label", "inside its corresponding node box", "high"
|
|
2755
|
+
elif asset_type == "slide":
|
|
2756
|
+
if idx == 0:
|
|
2757
|
+
role, area, priority = "slide_title", "title band", "high"
|
|
2758
|
+
else:
|
|
2759
|
+
pos = positions[idx]
|
|
2760
|
+
context = request[max(0, pos - 80) : pos].lower() if pos >= 0 else ""
|
|
2761
|
+
column = slide_column_before(request, pos) if pos >= 0 else None
|
|
2762
|
+
if column is None:
|
|
2763
|
+
column = max(0, (idx - 1) % max(1, infer_slide_column_count(request) or 3))
|
|
2764
|
+
row = slide_rows.get(column, 0)
|
|
2765
|
+
slide_rows[column] = row + 1
|
|
2766
|
+
title_marker = max(context.rfind("title in chinese"), context.rfind("chinese title"), context.rfind("标题"))
|
|
2767
|
+
bullet_marker = max(context.rfind("bullet"), context.rfind("要点"), context.rfind("项目符号"))
|
|
2768
|
+
if title_marker > bullet_marker:
|
|
2769
|
+
role, priority = "slide_section_title", "high"
|
|
2770
|
+
else:
|
|
2771
|
+
role, priority = "slide_bullet", "medium"
|
|
2772
|
+
area = f"column {column + 1}, row {row + 1}"
|
|
2773
|
+
roles.append(
|
|
2774
|
+
{
|
|
2775
|
+
"text": text,
|
|
2776
|
+
"role": role,
|
|
2777
|
+
"area": area,
|
|
2778
|
+
"priority": priority,
|
|
2779
|
+
"column_index": column,
|
|
2780
|
+
"row_in_column": row,
|
|
2781
|
+
}
|
|
2782
|
+
)
|
|
2783
|
+
continue
|
|
741
2784
|
elif asset_type == "logo":
|
|
742
2785
|
role, area, priority = ("wordmark" if idx == 0 else "brand_label", "brand board", "high")
|
|
743
2786
|
roles.append({"text": text, "role": role, "area": area, "priority": priority})
|
|
744
2787
|
return roles
|
|
745
2788
|
|
|
746
2789
|
|
|
747
|
-
def infer_must_include(asset_type: str, template_id: str, texts: list[str]) -> list[str]:
|
|
2790
|
+
def infer_must_include(asset_type: str, template_id: str, texts: list[str], strict_text: bool = False) -> list[str]:
|
|
748
2791
|
base = {
|
|
749
|
-
"poster": ["main visual subject", "readable title
|
|
750
|
-
"ui": ["
|
|
751
|
-
"infographic": ["
|
|
2792
|
+
"poster": ["main visual subject", "readable title hierarchy", "clear negative space"],
|
|
2793
|
+
"ui": ["requested screen type", "requested UI sections", "clear component hierarchy"],
|
|
2794
|
+
"infographic": ["requested information units", "requested relationships", "clear visual hierarchy"],
|
|
2795
|
+
"slide": ["widescreen slide canvas", "all requested sections", "requested visual motifs", "crisp readable text hierarchy"],
|
|
752
2796
|
"diagram": ["labeled components", "directional arrows", "legend or flow semantics"],
|
|
753
2797
|
"product": ["single hero product", "visible material texture", "controlled studio lighting"],
|
|
754
2798
|
"photography": ["realistic subject", "specific scene details", "natural imperfections"],
|
|
@@ -758,8 +2802,10 @@ def infer_must_include(asset_type: str, template_id: str, texts: list[str]) -> l
|
|
|
758
2802
|
}.get(asset_type, ["main subject", "clear visual hierarchy"])
|
|
759
2803
|
if template_id == "diagram_rag":
|
|
760
2804
|
base = ["User node", "Retriever node", "Vector DB node", "LLM node", "Answer node", "left-to-right arrows"]
|
|
2805
|
+
if template_id == "poster_zh_promo":
|
|
2806
|
+
base = ["main visual subject", "readable title/offer hierarchy", "clear negative space"]
|
|
761
2807
|
if texts:
|
|
762
|
-
base.append("
|
|
2808
|
+
base.append("clean blank copy areas for later overlay" if strict_text else "exact readable text from the brief")
|
|
763
2809
|
return base
|
|
764
2810
|
|
|
765
2811
|
|
|
@@ -768,12 +2814,15 @@ def infer_acceptance_criteria(spec: dict) -> list[str]:
|
|
|
768
2814
|
f"Image uses {spec['aspect']} composition and matches {spec['asset_type']} intent.",
|
|
769
2815
|
"Main subject is visible and matches the request.",
|
|
770
2816
|
"Composition follows the named layout without incoherent overlap.",
|
|
2817
|
+
"No subjects, modules, text, relationships, or narrative elements are added beyond the user brief.",
|
|
771
2818
|
"No fake logos, garbled filler text, or unrelated decorative clutter.",
|
|
772
2819
|
]
|
|
773
2820
|
if spec.get("required_text"):
|
|
774
2821
|
criteria.append("Every required text string appears exactly once, unchanged, and readable.")
|
|
775
|
-
if spec["asset_type"] in {"diagram", "ui", "infographic"}:
|
|
2822
|
+
if spec["asset_type"] in {"diagram", "ui", "infographic", "slide"}:
|
|
776
2823
|
criteria.append("Labels are large enough to read and aligned to their components.")
|
|
2824
|
+
if spec["asset_type"] == "slide":
|
|
2825
|
+
criteria.append("The slide preserves the user's requested information architecture without adding unrelated modules.")
|
|
777
2826
|
if spec["asset_type"] == "product":
|
|
778
2827
|
criteria.append("Product material and silhouette are clear, with no CGI-plastic tell.")
|
|
779
2828
|
if spec.get("strict_text"):
|
|
@@ -783,6 +2832,8 @@ def infer_acceptance_criteria(spec: dict) -> list[str]:
|
|
|
783
2832
|
|
|
784
2833
|
def overlay_align_hint(item: dict) -> str:
|
|
785
2834
|
role = str(item.get("role") or "")
|
|
2835
|
+
if role == "slide_bullet":
|
|
2836
|
+
return "left"
|
|
786
2837
|
if role in {"price_or_offer", "supporting_copy"}:
|
|
787
2838
|
return "center"
|
|
788
2839
|
if role in {"component_label", "ui_label"}:
|
|
@@ -813,6 +2864,22 @@ def overlay_box_hint(spec: dict, item: dict, idx: int, total: int) -> list[float
|
|
|
813
2864
|
if idx == 0:
|
|
814
2865
|
return [0.08, 0.06, 0.84, 0.12]
|
|
815
2866
|
return [0.10, 0.22 + (idx - 1) * 0.14, 0.32, 0.09]
|
|
2867
|
+
if asset_type == "slide":
|
|
2868
|
+
if role == "slide_title" or idx == 0:
|
|
2869
|
+
return [0.08, 0.04, 0.84, 0.12]
|
|
2870
|
+
cols = max(1, min(4, int(spec.get("layout_column_count") or 3)))
|
|
2871
|
+
col = int(item.get("column_index") if item.get("column_index") is not None else max(0, idx - 1) % cols)
|
|
2872
|
+
row = int(item.get("row_in_column") if item.get("row_in_column") is not None else max(0, idx - 1) // cols)
|
|
2873
|
+
col = max(0, min(cols - 1, col))
|
|
2874
|
+
col_w = min(0.28, 0.84 / cols - 0.02)
|
|
2875
|
+
gap = (0.84 - col_w * cols) / max(1, cols - 1) if cols > 1 else 0
|
|
2876
|
+
x = 0.08 + col * (col_w + gap)
|
|
2877
|
+
if role == "slide_bullet":
|
|
2878
|
+
x += min(0.045, col_w * 0.18)
|
|
2879
|
+
col_w -= min(0.045, col_w * 0.18)
|
|
2880
|
+
y = 0.255 + row * 0.068
|
|
2881
|
+
height = 0.072 if role == "slide_section_title" else 0.055
|
|
2882
|
+
return [x, y, col_w, height]
|
|
816
2883
|
if asset_type == "logo":
|
|
817
2884
|
return [0.12, 0.72, 0.76, 0.12]
|
|
818
2885
|
if asset_type == "character":
|
|
@@ -852,20 +2919,26 @@ def build_text_overlay_spec(spec: dict) -> dict:
|
|
|
852
2919
|
|
|
853
2920
|
def build_spec(args: argparse.Namespace) -> dict:
|
|
854
2921
|
request = args.request_text
|
|
855
|
-
|
|
2922
|
+
visual_request = strip_nonvisual_request_meta(request)
|
|
2923
|
+
safe_request, safety_notes = sanitize_reference_risks(visual_request)
|
|
856
2924
|
profile, _ = read_profile()
|
|
857
2925
|
asset_type = route_asset_type(safe_request, getattr(args, "asset_type", None))
|
|
858
|
-
|
|
859
|
-
|
|
2926
|
+
explicit_texts = getattr(args, "text", None) or []
|
|
2927
|
+
texts = extract_required_texts(visual_request, explicit_texts)
|
|
2928
|
+
if not explicit_texts:
|
|
2929
|
+
texts = merge_texts(texts, extract_structural_labels(visual_request, asset_type))
|
|
2930
|
+
aspect = infer_aspect(visual_request, asset_type, getattr(args, "aspect", None), profile)
|
|
860
2931
|
size = infer_size(aspect, getattr(args, "size", None), asset_type)
|
|
861
|
-
quality = infer_quality(
|
|
2932
|
+
quality = infer_quality(visual_request, asset_type, texts, getattr(args, "quality", None), profile)
|
|
862
2933
|
subject = getattr(args, "subject", None) or safe_request
|
|
863
2934
|
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)))
|
|
2935
|
+
negative = list(dict.fromkeys(infer_negative(asset_type, texts, profile, safe_request, template_id) + safety_avoid_list(safety_notes)))
|
|
2936
|
+
style_preset = getattr(args, "style_preset", None) or str(profile.get("default_style_preset") or "auto")
|
|
865
2937
|
spec = {
|
|
866
2938
|
"schema_version": SCHEMA_VERSION,
|
|
867
2939
|
"compiler_version": COMPILER_VERSION,
|
|
868
2940
|
"request": request,
|
|
2941
|
+
"visual_request": visual_request,
|
|
869
2942
|
"safe_request": safe_request,
|
|
870
2943
|
"safety_rewrite": safety_notes,
|
|
871
2944
|
"asset_type": asset_type,
|
|
@@ -877,17 +2950,21 @@ def build_spec(args: argparse.Namespace) -> dict:
|
|
|
877
2950
|
"quality": quality,
|
|
878
2951
|
"subject": subject,
|
|
879
2952
|
"required_text": texts,
|
|
2953
|
+
"required_text_source": "explicit" if explicit_texts else "request",
|
|
880
2954
|
"strict_text": bool(getattr(args, "strict_text", False)),
|
|
2955
|
+
"prompt_mode": "strict_text_overlay" if bool(getattr(args, "strict_text", False)) else "single_pass",
|
|
881
2956
|
"layout": getattr(args, "layout", None) or infer_layout(template_id, asset_type),
|
|
882
|
-
"text_hierarchy": infer_text_hierarchy(asset_type, texts,
|
|
883
|
-
"
|
|
2957
|
+
"text_hierarchy": infer_text_hierarchy(asset_type, texts, visual_request),
|
|
2958
|
+
"layout_column_count": infer_slide_column_count(visual_request) if asset_type == "slide" else None,
|
|
2959
|
+
"style_preset": style_preset,
|
|
2960
|
+
"style_anchors": infer_style_anchors(safe_request, getattr(args, "style", None), profile, style_preset),
|
|
884
2961
|
"materials": split_csv(getattr(args, "materials", None)) or ["tactile, specific visible materials chosen for the subject"],
|
|
885
2962
|
"lighting": getattr(args, "lighting", None) or "controlled, readable light with clear subject hierarchy",
|
|
886
2963
|
"palette": split_csv(getattr(args, "palette", None)) or ["restrained palette matched to the asset type"],
|
|
887
2964
|
"negative": negative,
|
|
888
|
-
"must_include": infer_must_include(asset_type, template_id, texts),
|
|
2965
|
+
"must_include": infer_must_include(asset_type, template_id, texts, bool(getattr(args, "strict_text", False))),
|
|
889
2966
|
"must_avoid": negative,
|
|
890
|
-
"tags": infer_tags(asset_type,
|
|
2967
|
+
"tags": infer_tags(asset_type, visual_request, getattr(args, "tags", None)),
|
|
891
2968
|
}
|
|
892
2969
|
spec["acceptance_criteria"] = infer_acceptance_criteria(spec)
|
|
893
2970
|
if spec["strict_text"]:
|
|
@@ -905,6 +2982,17 @@ def exact_text_block(texts: list[str]) -> str:
|
|
|
905
2982
|
)
|
|
906
2983
|
|
|
907
2984
|
|
|
2985
|
+
def redact_required_texts(text: str, texts: list[str]) -> str:
|
|
2986
|
+
redacted = text
|
|
2987
|
+
for idx, item in enumerate(texts, start=1):
|
|
2988
|
+
if item:
|
|
2989
|
+
redacted = redacted.replace(item, "")
|
|
2990
|
+
redacted = re.sub(r"\s+([,,。.;;])", r"\1", redacted)
|
|
2991
|
+
redacted = re.sub(r"([::])\s*([,,。.;;])", r"\1", redacted)
|
|
2992
|
+
redacted = re.sub(r"\s{2,}", " ", redacted)
|
|
2993
|
+
return redacted
|
|
2994
|
+
|
|
2995
|
+
|
|
908
2996
|
def reserved_text_block(spec: dict) -> str:
|
|
909
2997
|
if not spec.get("required_text"):
|
|
910
2998
|
return "No required in-image text unless explicitly useful; avoid decorative fake text."
|
|
@@ -912,54 +3000,93 @@ def reserved_text_block(spec: dict) -> str:
|
|
|
912
3000
|
return (
|
|
913
3001
|
"Strict text mode: do not render the exact copy in the generated image. "
|
|
914
3002
|
f"Reserve clean, high-contrast text areas for later overlay ({roles}). "
|
|
915
|
-
"
|
|
3003
|
+
"Leave those areas visually empty: no placeholder words, no brackets, no lorem ipsum, no dummy text, and no fake characters."
|
|
916
3004
|
)
|
|
917
3005
|
|
|
918
3006
|
|
|
919
3007
|
def render_visual_prompt(spec: dict) -> str:
|
|
920
3008
|
visual_spec = dict(spec)
|
|
3009
|
+
visual_spec["prompt_mode"] = "strict_text_overlay_background"
|
|
921
3010
|
visual_spec["required_text"] = []
|
|
3011
|
+
visual_spec["subject"] = redact_required_texts(str(visual_spec.get("subject") or ""), spec.get("required_text", []))
|
|
3012
|
+
visual_spec["request"] = redact_required_texts(str(visual_spec.get("request") or ""), spec.get("required_text", []))
|
|
3013
|
+
visual_spec["safe_request"] = redact_required_texts(str(visual_spec.get("safe_request") or ""), spec.get("required_text", []))
|
|
922
3014
|
prompt = render_prompt(visual_spec)
|
|
923
3015
|
return prompt + "\n" + reserved_text_block(spec)
|
|
924
3016
|
|
|
925
3017
|
|
|
3018
|
+
def intent_preservation_block(spec: dict) -> str:
|
|
3019
|
+
return (
|
|
3020
|
+
"Intent preservation: treat the user's brief as the source of truth. "
|
|
3021
|
+
"Enhance visual quality, clarity, composition, materials, lighting, palette, and execution detail only. "
|
|
3022
|
+
"Do not reinterpret the goal, change the subject, change the requested layout/order, or add new modules, "
|
|
3023
|
+
"objects, scenes, brands, charts, text, people, or narrative elements unless they are explicitly requested "
|
|
3024
|
+
"or directly implied by the brief. Template guidance must be adapted to the brief and omitted when not applicable."
|
|
3025
|
+
)
|
|
3026
|
+
|
|
3027
|
+
|
|
3028
|
+
def original_brief_block(spec: dict) -> str:
|
|
3029
|
+
brief = str(spec.get("safe_request") or spec.get("visual_request") or spec.get("request") or "").strip()
|
|
3030
|
+
return "User visual brief (preserve verbatim; do not rewrite or reinterpret):\n" + brief
|
|
3031
|
+
|
|
3032
|
+
|
|
3033
|
+
def style_quality_block(spec: dict, *, label: str, extra: list[str] | None = None) -> str:
|
|
3034
|
+
style = "; ".join(spec["style_anchors"])
|
|
3035
|
+
materials = ", ".join(spec["materials"])
|
|
3036
|
+
palette = ", ".join(spec["palette"])
|
|
3037
|
+
lines = [
|
|
3038
|
+
f"Style / quality envelope for {label}: {style}.",
|
|
3039
|
+
"This envelope controls rendering quality and visual language only; if it conflicts with the user brief, the user brief wins.",
|
|
3040
|
+
f"Quality controls: {materials}; lighting/rendering: {spec['lighting']}; palette discipline: {palette}.",
|
|
3041
|
+
]
|
|
3042
|
+
for item in extra or []:
|
|
3043
|
+
if item:
|
|
3044
|
+
lines.append(item)
|
|
3045
|
+
return "\n".join(lines)
|
|
3046
|
+
|
|
3047
|
+
|
|
926
3048
|
def render_prompt(spec: dict) -> str:
|
|
927
3049
|
if spec.get("strict_text") and spec.get("required_text"):
|
|
928
3050
|
return render_visual_prompt(spec)
|
|
929
3051
|
asset_type = spec["asset_type"]
|
|
930
|
-
style = "; ".join(spec["style_anchors"])
|
|
931
|
-
materials = ", ".join(spec["materials"])
|
|
932
|
-
palette = ", ".join(spec["palette"])
|
|
933
3052
|
negative = "; ".join(spec["negative"])
|
|
934
3053
|
must_include = ", ".join(spec.get("must_include", []))
|
|
935
3054
|
text_block = exact_text_block(spec["required_text"])
|
|
936
3055
|
aspect = spec["aspect"]
|
|
937
|
-
subject = spec["subject"]
|
|
938
3056
|
layout = spec.get("layout", "clear composition with named regions")
|
|
3057
|
+
intent_block = intent_preservation_block(spec)
|
|
3058
|
+
brief_block = original_brief_block(spec)
|
|
939
3059
|
|
|
940
3060
|
if asset_type == "poster":
|
|
3061
|
+
hierarchy_guidance = (
|
|
3062
|
+
"Promotional information hierarchy must pass the three-glance test: silhouette first, key message second, texture/details third."
|
|
3063
|
+
if spec.get("template_id") == "poster_zh_promo"
|
|
3064
|
+
else "Visual hierarchy must pass the three-glance test: key message first, requested supporting content second, texture/details third."
|
|
3065
|
+
)
|
|
941
3066
|
return "\n".join(
|
|
942
3067
|
[
|
|
943
|
-
f"
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
f"
|
|
3068
|
+
f"Create a {aspect} poster from the user visual brief below.",
|
|
3069
|
+
brief_block,
|
|
3070
|
+
style_quality_block(spec, label="poster", extra=["Use a strong layout grid, clear hierarchy, and enough negative space for a readable commercial poster."]),
|
|
3071
|
+
f"Composition support: {spec.get('template_label', 'poster')}; layout guidance: {layout}.",
|
|
3072
|
+
intent_block,
|
|
3073
|
+
f"Main subject and scene density: make the core subject specific and visible, using only relevant supporting details from the brief.",
|
|
947
3074
|
f"Must include: {must_include}.",
|
|
948
|
-
f"Materials: {materials}. Lighting: {spec['lighting']}. Palette: {palette}.",
|
|
949
3075
|
text_block,
|
|
950
|
-
|
|
3076
|
+
hierarchy_guidance,
|
|
951
3077
|
f"Avoid: {negative}.",
|
|
952
3078
|
]
|
|
953
3079
|
)
|
|
954
3080
|
if asset_type == "ui":
|
|
955
3081
|
return "\n".join(
|
|
956
3082
|
[
|
|
957
|
-
f"
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
"
|
|
3083
|
+
f"Create a production-quality {aspect} UI mockup from the user visual brief below.",
|
|
3084
|
+
brief_block,
|
|
3085
|
+
style_quality_block(spec, label="UI", extra=["Use a coherent component system, precise spacing, realistic invented data, and crisp typography."]),
|
|
3086
|
+
f"Composition support: {spec.get('template_label', 'UI')}; layout guidance: {layout}.",
|
|
3087
|
+
intent_block,
|
|
3088
|
+
"Include only the screen regions, controls, lists, actions, charts, and navigation that are named or strongly implied by the requested interface.",
|
|
961
3089
|
f"Must include: {must_include}.",
|
|
962
|
-
f"Materials: {materials}. Lighting/rendering: {spec['lighting']}. Palette: {palette}.",
|
|
963
3090
|
text_block,
|
|
964
3091
|
f"Avoid: {negative}.",
|
|
965
3092
|
]
|
|
@@ -967,26 +3094,45 @@ def render_prompt(spec: dict) -> str:
|
|
|
967
3094
|
if asset_type == "infographic":
|
|
968
3095
|
return "\n".join(
|
|
969
3096
|
[
|
|
970
|
-
f"Create a {aspect} educational infographic
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
"
|
|
3097
|
+
f"Create a {aspect} educational infographic from the user visual brief below.",
|
|
3098
|
+
brief_block,
|
|
3099
|
+
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."]),
|
|
3100
|
+
f"Composition support: {spec.get('template_label', 'infographic')}; layout guidance: {layout}.",
|
|
3101
|
+
intent_block,
|
|
3102
|
+
"Use leader lines, numbered callouts, labeled parts, and legends only where they clarify relationships already present in the brief.",
|
|
974
3103
|
f"Must include: {must_include}.",
|
|
975
|
-
f"Materials/linework: {materials}. Lighting: {spec['lighting']}. Palette: {palette}.",
|
|
976
3104
|
text_block,
|
|
977
3105
|
f"Avoid: {negative}.",
|
|
978
3106
|
]
|
|
979
3107
|
)
|
|
3108
|
+
if asset_type == "slide":
|
|
3109
|
+
return "\n".join(
|
|
3110
|
+
[
|
|
3111
|
+
f"Create a production-ready {aspect} presentation slide from the user visual brief below.",
|
|
3112
|
+
brief_block,
|
|
3113
|
+
style_quality_block(spec, label="presentation slide", extra=["Improve polish, hierarchy, spacing, and clarity while preserving the user's requested content and layout."]),
|
|
3114
|
+
f"Composition support: {spec.get('template_label', 'presentation slide')}; layout guidance: {layout}.",
|
|
3115
|
+
intent_block,
|
|
3116
|
+
"Follow the brief's information architecture exactly: keep the requested section count, order, relative areas, visual element subjects, footer treatment if any, and margins.",
|
|
3117
|
+
f"Must include: {must_include}.",
|
|
3118
|
+
text_block,
|
|
3119
|
+
f"Avoid: {negative}; do not add unrelated modules, extra charts, or decorative content outside the user brief.",
|
|
3120
|
+
]
|
|
3121
|
+
)
|
|
980
3122
|
if asset_type == "diagram":
|
|
3123
|
+
diagram_style = "; ".join(spec["style_anchors"])
|
|
3124
|
+
diagram_palette = ", ".join(spec["palette"])
|
|
981
3125
|
return "\n".join(
|
|
982
3126
|
[
|
|
983
|
-
f"
|
|
984
|
-
|
|
985
|
-
f"
|
|
986
|
-
"
|
|
3127
|
+
f"Create a presentation-ready {aspect} architecture diagram from the user visual brief below.",
|
|
3128
|
+
brief_block,
|
|
3129
|
+
f"Style / quality envelope for diagram: {diagram_style}. This only controls presentation polish; the user brief wins.",
|
|
3130
|
+
f"Quality controls: crisp typography, clear node grouping, balanced white space, precise alignment, high contrast, restrained palette ({diagram_palette}).",
|
|
3131
|
+
f"Recommended structure: {layout}. Use boxes, groups, arrows, and a feedback loop only where they match the brief.",
|
|
3132
|
+
"Do not over-template the diagram. Preserve the user's named modules, reading order, and product-review context.",
|
|
987
3133
|
f"Must include: {must_include}.",
|
|
988
|
-
f"Rendering: {materials}. Palette: {palette}.",
|
|
989
3134
|
text_block,
|
|
3135
|
+
"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
3136
|
f"Avoid: {negative}.",
|
|
991
3137
|
]
|
|
992
3138
|
)
|
|
@@ -995,15 +3141,17 @@ def render_prompt(spec: dict) -> str:
|
|
|
995
3141
|
[
|
|
996
3142
|
f"/* PRODUCT_RENDER_CONFIG VERSION: 1.0 ASPECT: {aspect} */",
|
|
997
3143
|
"{",
|
|
998
|
-
f' "
|
|
999
|
-
f' "
|
|
1000
|
-
|
|
3144
|
+
f' "USER_VISUAL_BRIEF": "{str(spec.get("safe_request") or spec.get("visual_request") or spec.get("request") or "").replace(chr(34), chr(39))}",',
|
|
3145
|
+
f' "STYLE_QUALITY_ENVELOPE": "{("; ".join(spec["style_anchors"])).replace(chr(34), chr(39))}",',
|
|
3146
|
+
' "STYLE_QUALITY_RULE": "This envelope controls rendering quality and visual language only; if it conflicts with USER_VISUAL_BRIEF, USER_VISUAL_BRIEF wins.",',
|
|
3147
|
+
f' "MATERIALS": "{(", ".join(spec["materials"])).replace(chr(34), chr(39))}",',
|
|
1001
3148
|
f' "LIGHTING": "{spec["lighting"]}",',
|
|
1002
|
-
f' "PALETTE": "{palette}",',
|
|
3149
|
+
f' "PALETTE": "{(", ".join(spec["palette"])).replace(chr(34), chr(39))}",',
|
|
1003
3150
|
f' "LAYOUT": "{layout}",',
|
|
1004
3151
|
f' "MUST_INCLUDE": "{must_include}",',
|
|
1005
|
-
' "COMPOSITION": "single
|
|
3152
|
+
' "COMPOSITION": "single requested product as the clear hero, sharp foreground, editorial finish without unrelated props"',
|
|
1006
3153
|
"}",
|
|
3154
|
+
intent_block,
|
|
1007
3155
|
text_block,
|
|
1008
3156
|
f"Avoid: {negative}.",
|
|
1009
3157
|
]
|
|
@@ -1011,12 +3159,13 @@ def render_prompt(spec: dict) -> str:
|
|
|
1011
3159
|
if asset_type == "photography":
|
|
1012
3160
|
return "\n".join(
|
|
1013
3161
|
[
|
|
1014
|
-
f"
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
"
|
|
3162
|
+
f"Create a candid documentary-style {aspect} photograph from the user visual brief below.",
|
|
3163
|
+
brief_block,
|
|
3164
|
+
style_quality_block(spec, label="photograph", extra=["Natural full-frame look, unprocessed realism, ordinary imperfect details."]),
|
|
3165
|
+
f"Layout guidance: {layout}.",
|
|
3166
|
+
intent_block,
|
|
3167
|
+
"Scene density: use concrete visible nouns from the brief; no staged studio posing or added story elements outside the request.",
|
|
1018
3168
|
f"Must include: {must_include}.",
|
|
1019
|
-
f"Light: {spec['lighting']}. Palette: {palette}.",
|
|
1020
3169
|
text_block,
|
|
1021
3170
|
f"Avoid: {negative}.",
|
|
1022
3171
|
]
|
|
@@ -1024,12 +3173,13 @@ def render_prompt(spec: dict) -> str:
|
|
|
1024
3173
|
if asset_type == "character":
|
|
1025
3174
|
return "\n".join(
|
|
1026
3175
|
[
|
|
1027
|
-
f"Create a {aspect} original character design reference sheet
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
"
|
|
3176
|
+
f"Create a {aspect} original character design reference sheet from the user visual brief below.",
|
|
3177
|
+
brief_block,
|
|
3178
|
+
style_quality_block(spec, label="character sheet", extra=["Clean model-sheet clarity on a neutral background."]),
|
|
3179
|
+
f"Layout guidance: {layout}.",
|
|
3180
|
+
intent_block,
|
|
3181
|
+
"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
3182
|
f"Must include: {must_include}.",
|
|
1032
|
-
f"Materials/linework: {materials}. Lighting: {spec['lighting']}. Palette: {palette}.",
|
|
1033
3183
|
text_block,
|
|
1034
3184
|
f"Avoid: {negative}; avoid resemblance to existing IP.",
|
|
1035
3185
|
]
|
|
@@ -1037,20 +3187,23 @@ def render_prompt(spec: dict) -> str:
|
|
|
1037
3187
|
if asset_type == "logo":
|
|
1038
3188
|
return "\n".join(
|
|
1039
3189
|
[
|
|
1040
|
-
f"Create a {aspect} brand identity
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
f"
|
|
3190
|
+
f"Create a {aspect} brand identity visual from the user visual brief below.",
|
|
3191
|
+
brief_block,
|
|
3192
|
+
style_quality_block(spec, label="brand identity", extra=["Keep the requested logo or identity deliverable as the focus."]),
|
|
3193
|
+
f"Layout guidance: {layout}. Must include: {must_include}.",
|
|
3194
|
+
intent_block,
|
|
3195
|
+
"Include wordmarks, palette swatches, typography samples, or application mockups only when the brief asks for a brand board or identity system.",
|
|
1044
3196
|
text_block,
|
|
1045
3197
|
f"Avoid: {negative}; no stock clip-art, no resemblance to real-world brands.",
|
|
1046
3198
|
]
|
|
1047
3199
|
)
|
|
1048
3200
|
return "\n".join(
|
|
1049
3201
|
[
|
|
1050
|
-
f"Create a {aspect} stylized illustration
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
f"
|
|
3202
|
+
f"Create a {aspect} stylized illustration from the user visual brief below.",
|
|
3203
|
+
brief_block,
|
|
3204
|
+
style_quality_block(spec, label="illustration", extra=["Use concrete subject details, controlled composition, and a clear visual hierarchy."]),
|
|
3205
|
+
f"Composition support: {spec.get('template_label', 'illustration')}. Layout guidance: {layout}. Must include: {must_include}.",
|
|
3206
|
+
intent_block,
|
|
1054
3207
|
text_block,
|
|
1055
3208
|
f"Avoid: {negative}.",
|
|
1056
3209
|
]
|
|
@@ -1080,7 +3233,7 @@ def lint_prompt(prompt: str, asset_type: str | None, quality: str | None, requir
|
|
|
1080
3233
|
]
|
|
1081
3234
|
if not any(re.search(pat, compact) for pat in quoted_patterns):
|
|
1082
3235
|
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":
|
|
3236
|
+
if (required_texts or asset_type in {"poster", "ui", "infographic", "slide", "diagram", "logo"}) and quality and quality != "high":
|
|
1084
3237
|
add("error", "quality.not_high", "文字/海报/UI/图表类转化应使用 high quality。")
|
|
1085
3238
|
if has_cjk(compact) and not re.search(r"(garbled|legible|readable|乱码|可读|清晰)", lower):
|
|
1086
3239
|
add("warning", "text.no_legibility_guard", "含中文文字时建议加入 legible / no garbled characters 约束。")
|
|
@@ -1092,6 +3245,188 @@ def lint_prompt(prompt: str, asset_type: str | None, quality: str | None, requir
|
|
|
1092
3245
|
return findings
|
|
1093
3246
|
|
|
1094
3247
|
|
|
3248
|
+
INTENT_TERM_GROUPS = {
|
|
3249
|
+
"props": ["道具", "props", "prop"],
|
|
3250
|
+
"people": ["人物", "人像", "真人", "人类", "people", "person", "human", "character"],
|
|
3251
|
+
"background_scene": ["背景场景", "场景背景", "scene", "background scene", "environment"],
|
|
3252
|
+
"charts": ["图表", "数据图表", "chart", "charts", "graph", "graphs", "diagram chart"],
|
|
3253
|
+
"icons": ["图标", "icon", "icons"],
|
|
3254
|
+
"columns": ["三栏", "三列", "两栏", "两列", "分栏", "栏目", "columns", "column"],
|
|
3255
|
+
"cards": ["卡片", "首页卡片", "content cards", "cards", "card"],
|
|
3256
|
+
"homepage": ["首页", "home screen", "homepage", "home page"],
|
|
3257
|
+
"modules": ["模块", "额外模块", "summary modules", "modules", "extra modules"],
|
|
3258
|
+
"brand_board": [
|
|
3259
|
+
"品牌板",
|
|
3260
|
+
"品牌系统板",
|
|
3261
|
+
"brand identity presentation board",
|
|
3262
|
+
"brand board",
|
|
3263
|
+
"wordmark",
|
|
3264
|
+
"palette swatches",
|
|
3265
|
+
"application mockups",
|
|
3266
|
+
],
|
|
3267
|
+
}
|
|
3268
|
+
|
|
3269
|
+
HIGH_RISK_INTENT_GROUPS = {
|
|
3270
|
+
"props",
|
|
3271
|
+
"people",
|
|
3272
|
+
"background_scene",
|
|
3273
|
+
"charts",
|
|
3274
|
+
"icons",
|
|
3275
|
+
"columns",
|
|
3276
|
+
"cards",
|
|
3277
|
+
"homepage",
|
|
3278
|
+
"modules",
|
|
3279
|
+
"brand_board",
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
DENIED_ONLY_ALIASES = {
|
|
3283
|
+
"people": ["人", "任何人"],
|
|
3284
|
+
"columns": ["栏", "列"],
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
NEGATION_MARKERS = [
|
|
3288
|
+
"不要",
|
|
3289
|
+
"不需要",
|
|
3290
|
+
"不得",
|
|
3291
|
+
"不能",
|
|
3292
|
+
"避免",
|
|
3293
|
+
"禁止",
|
|
3294
|
+
"无",
|
|
3295
|
+
"没有",
|
|
3296
|
+
"no ",
|
|
3297
|
+
"not ",
|
|
3298
|
+
"without ",
|
|
3299
|
+
"avoid ",
|
|
3300
|
+
"do not ",
|
|
3301
|
+
"don't ",
|
|
3302
|
+
"outside the user brief",
|
|
3303
|
+
"not requested",
|
|
3304
|
+
"unless",
|
|
3305
|
+
"only when",
|
|
3306
|
+
"only where",
|
|
3307
|
+
"仅当",
|
|
3308
|
+
"只在",
|
|
3309
|
+
]
|
|
3310
|
+
|
|
3311
|
+
|
|
3312
|
+
def prompt_contains_positive(text: str, variants: list[str]) -> bool:
|
|
3313
|
+
lower = text.lower()
|
|
3314
|
+
for variant in variants:
|
|
3315
|
+
term = variant.lower().strip()
|
|
3316
|
+
if not term:
|
|
3317
|
+
continue
|
|
3318
|
+
if term.isascii() and re.search(r"[a-z0-9]", term):
|
|
3319
|
+
matches = list(re.finditer(rf"(?<![a-z0-9]){re.escape(term)}(?![a-z0-9])", lower))
|
|
3320
|
+
else:
|
|
3321
|
+
matches = list(re.finditer(re.escape(term), lower))
|
|
3322
|
+
for match in matches:
|
|
3323
|
+
context = lower[max(0, match.start() - 180) : min(len(lower), match.end() + 100)]
|
|
3324
|
+
if any(marker in context for marker in NEGATION_MARKERS):
|
|
3325
|
+
continue
|
|
3326
|
+
return True
|
|
3327
|
+
return False
|
|
3328
|
+
|
|
3329
|
+
|
|
3330
|
+
def request_mentions_any(request: str, variants: list[str]) -> bool:
|
|
3331
|
+
lower = request.lower()
|
|
3332
|
+
return any(keyword_in_text(variant, lower) for variant in variants)
|
|
3333
|
+
|
|
3334
|
+
|
|
3335
|
+
def intent_group_allowed_by_context(group: str, request: str, spec: dict) -> bool:
|
|
3336
|
+
lower = request.lower()
|
|
3337
|
+
asset_type = str(spec.get("asset_type") or "")
|
|
3338
|
+
if group == "cards" and asset_type == "ui":
|
|
3339
|
+
return any(k in lower for k in ["首页", "展示", "列表", "作品", "项目", "内容", "recent", "gallery", "feed"])
|
|
3340
|
+
if group == "modules" and asset_type in {"diagram", "infographic", "slide"}:
|
|
3341
|
+
return any(k in lower for k in ["架构", "系统", "模块", "流程", "步骤", "结构", "分析", "risk", "rag", "pipeline"])
|
|
3342
|
+
if group == "charts":
|
|
3343
|
+
return asset_type == "data-viz" or any(k in lower for k in ["数据", "指标", "趋势", "占比", "转化率", "漏斗", "报表", "chart", "graph"])
|
|
3344
|
+
if group == "people":
|
|
3345
|
+
return asset_type == "character" or any(k in lower for k in ["人物", "角色", "人像", "portrait", "character", "person", "people"])
|
|
3346
|
+
if group == "icons":
|
|
3347
|
+
return any(k in lower for k in ["图标", "icon", "icons", "符号", "标识符"])
|
|
3348
|
+
if group == "columns":
|
|
3349
|
+
return any(k in lower for k in ["三栏", "三列", "两栏", "两列", "分栏", "column", "columns", "grid"])
|
|
3350
|
+
if group == "brand_board":
|
|
3351
|
+
return asset_type == "logo" or any(k in lower for k in ["品牌系统", "品牌板", "vi", "identity", "brand board", "logo"])
|
|
3352
|
+
return False
|
|
3353
|
+
|
|
3354
|
+
|
|
3355
|
+
def denied_intent_groups(request: str) -> set[str]:
|
|
3356
|
+
lower = request.lower()
|
|
3357
|
+
denied: set[str] = set()
|
|
3358
|
+
spans: list[str] = []
|
|
3359
|
+
patterns = [
|
|
3360
|
+
r"(?:不要|不需要|不得|不能|避免|禁止|无|没有)([^,。;;,.、\n]{1,30})",
|
|
3361
|
+
r"\b(?:no|without|avoid|not)\s+([^,.;\n]{1,50})",
|
|
3362
|
+
]
|
|
3363
|
+
for pat in patterns:
|
|
3364
|
+
for match in re.finditer(pat, lower, flags=re.IGNORECASE):
|
|
3365
|
+
spans.append(match.group(1))
|
|
3366
|
+
for span in spans:
|
|
3367
|
+
compact_span = re.sub(r"\s+", "", span)
|
|
3368
|
+
for key, variants in INTENT_TERM_GROUPS.items():
|
|
3369
|
+
denied_only = DENIED_ONLY_ALIASES.get(key, [])
|
|
3370
|
+
if any(v.lower() in span for v in variants) or any(alias in compact_span for alias in denied_only):
|
|
3371
|
+
denied.add(key)
|
|
3372
|
+
return denied
|
|
3373
|
+
|
|
3374
|
+
|
|
3375
|
+
def intent_check_prompt(request: str, prompt: str, spec: dict | None = None) -> list[dict]:
|
|
3376
|
+
findings: list[dict] = []
|
|
3377
|
+
spec = spec or {}
|
|
3378
|
+
request = request or ""
|
|
3379
|
+
prompt = prompt or ""
|
|
3380
|
+
|
|
3381
|
+
def add(severity: str, rule: str, message: str, evidence: str = "") -> None:
|
|
3382
|
+
item = {"severity": severity, "rule": rule, "message": message}
|
|
3383
|
+
if evidence:
|
|
3384
|
+
item["evidence"] = evidence
|
|
3385
|
+
findings.append(item)
|
|
3386
|
+
|
|
3387
|
+
denied = denied_intent_groups(request)
|
|
3388
|
+
for key in sorted(denied):
|
|
3389
|
+
variants = INTENT_TERM_GROUPS[key]
|
|
3390
|
+
if prompt_contains_positive(prompt, variants):
|
|
3391
|
+
add(
|
|
3392
|
+
"error",
|
|
3393
|
+
"intent.denied_content_added",
|
|
3394
|
+
f"用户明确否定了 {key},但 prompt 中仍出现正向要求。",
|
|
3395
|
+
", ".join(variants[:4]),
|
|
3396
|
+
)
|
|
3397
|
+
|
|
3398
|
+
for key in sorted(HIGH_RISK_INTENT_GROUPS - denied):
|
|
3399
|
+
variants = INTENT_TERM_GROUPS[key]
|
|
3400
|
+
if request_mentions_any(request, variants):
|
|
3401
|
+
continue
|
|
3402
|
+
if intent_group_allowed_by_context(key, request, spec):
|
|
3403
|
+
continue
|
|
3404
|
+
if prompt_contains_positive(prompt, variants):
|
|
3405
|
+
add(
|
|
3406
|
+
"error",
|
|
3407
|
+
"intent.unrequested_module",
|
|
3408
|
+
f"prompt 引入了用户未要求的高风险模块:{key}。",
|
|
3409
|
+
", ".join(variants[:4]),
|
|
3410
|
+
)
|
|
3411
|
+
|
|
3412
|
+
required_text = normalize_text_list(spec.get("required_text")) if spec else []
|
|
3413
|
+
required_text_source = str(spec.get("required_text_source") or "")
|
|
3414
|
+
if required_text_source != "explicit":
|
|
3415
|
+
for text in required_text:
|
|
3416
|
+
if text and text not in request:
|
|
3417
|
+
add("warning", "intent.text_not_in_request", f"必显文字不在原始需求中:{text}", text)
|
|
3418
|
+
return findings
|
|
3419
|
+
|
|
3420
|
+
|
|
3421
|
+
def print_intent_findings(findings: list[dict]) -> None:
|
|
3422
|
+
if not findings:
|
|
3423
|
+
print("intent-check: pass")
|
|
3424
|
+
return
|
|
3425
|
+
for item in findings:
|
|
3426
|
+
evidence = f" evidence={item['evidence']}" if item.get("evidence") else ""
|
|
3427
|
+
print(f"{item['severity']}: {item['rule']} - {item['message']}{evidence}")
|
|
3428
|
+
|
|
3429
|
+
|
|
1095
3430
|
def print_lint(findings: list[dict]) -> None:
|
|
1096
3431
|
if not findings:
|
|
1097
3432
|
print("lint: pass")
|
|
@@ -1374,6 +3709,27 @@ def cmd_lint(args: argparse.Namespace) -> int:
|
|
|
1374
3709
|
return 1 if any(item["severity"] == "error" for item in findings) else 0
|
|
1375
3710
|
|
|
1376
3711
|
|
|
3712
|
+
def cmd_intent_check(args: argparse.Namespace) -> int:
|
|
3713
|
+
request = read_text_argument(args.request, args.request_file)
|
|
3714
|
+
prompt = read_text_argument(args.prompt, args.prompt_file)
|
|
3715
|
+
if not request or not prompt:
|
|
3716
|
+
print("用法:intent-check --request \"原始需求\" --prompt \"生成后的 prompt\"", file=sys.stderr)
|
|
3717
|
+
return 2
|
|
3718
|
+
spec = {}
|
|
3719
|
+
if args.spec:
|
|
3720
|
+
try:
|
|
3721
|
+
spec = extract_spec_payload(load_json_value(args.spec))
|
|
3722
|
+
except Exception as exc:
|
|
3723
|
+
print(f"读取 --spec 失败:{exc}", file=sys.stderr)
|
|
3724
|
+
return 2
|
|
3725
|
+
findings = intent_check_prompt(request, prompt, spec)
|
|
3726
|
+
if args.json:
|
|
3727
|
+
print(json.dumps({"findings": findings, "pass": not has_lint_error(findings)}, ensure_ascii=False, indent=2))
|
|
3728
|
+
else:
|
|
3729
|
+
print_intent_findings(findings)
|
|
3730
|
+
return 1 if has_lint_error(findings) else 0
|
|
3731
|
+
|
|
3732
|
+
|
|
1377
3733
|
def cmd_convert(args: argparse.Namespace) -> int:
|
|
1378
3734
|
if isinstance(args.request_text, list):
|
|
1379
3735
|
args.request_text = " ".join(args.request_text)
|
|
@@ -1384,6 +3740,7 @@ def cmd_convert(args: argparse.Namespace) -> int:
|
|
|
1384
3740
|
prompt = render_prompt(spec)
|
|
1385
3741
|
lint_texts = [] if spec.get("strict_text") else spec["required_text"]
|
|
1386
3742
|
findings = lint_prompt(prompt, spec["asset_type"], spec["quality"], lint_texts)
|
|
3743
|
+
intent_findings = intent_check_prompt(spec["request"], prompt, spec)
|
|
1387
3744
|
|
|
1388
3745
|
if args.record_pending:
|
|
1389
3746
|
rec = sample_record(
|
|
@@ -1413,13 +3770,14 @@ def cmd_convert(args: argparse.Namespace) -> int:
|
|
|
1413
3770
|
"text_overlay_spec": spec.get("text_overlay_spec"),
|
|
1414
3771
|
"acceptance_criteria": spec.get("acceptance_criteria", []),
|
|
1415
3772
|
"lint": findings,
|
|
3773
|
+
"intent_check": intent_findings,
|
|
1416
3774
|
"handoff": handoff,
|
|
1417
3775
|
},
|
|
1418
3776
|
ensure_ascii=False,
|
|
1419
3777
|
indent=2,
|
|
1420
3778
|
)
|
|
1421
3779
|
)
|
|
1422
|
-
return 1 if
|
|
3780
|
+
return 1 if has_lint_error(findings) or has_lint_error(intent_findings) else 0
|
|
1423
3781
|
|
|
1424
3782
|
print("## Prompt")
|
|
1425
3783
|
print(prompt)
|
|
@@ -1428,6 +3786,9 @@ def cmd_convert(args: argparse.Namespace) -> int:
|
|
|
1428
3786
|
print()
|
|
1429
3787
|
print("## Lint")
|
|
1430
3788
|
print_lint(findings)
|
|
3789
|
+
print()
|
|
3790
|
+
print("## Intent Check")
|
|
3791
|
+
print_intent_findings(intent_findings)
|
|
1431
3792
|
if handoff:
|
|
1432
3793
|
print()
|
|
1433
3794
|
print("## Handoff")
|
|
@@ -1443,7 +3804,7 @@ def cmd_convert(args: argparse.Namespace) -> int:
|
|
|
1443
3804
|
if "sample_id" in spec:
|
|
1444
3805
|
print()
|
|
1445
3806
|
print(f"sample_id: {spec['sample_id']}")
|
|
1446
|
-
return 1 if
|
|
3807
|
+
return 1 if has_lint_error(findings) or has_lint_error(intent_findings) else 0
|
|
1447
3808
|
|
|
1448
3809
|
|
|
1449
3810
|
def prompt_digest(prompt: str) -> str:
|
|
@@ -1469,6 +3830,7 @@ def namespace_from_case(case: dict) -> argparse.Namespace:
|
|
|
1469
3830
|
text=normalize_text_list(case.get("text") or case.get("required_text")),
|
|
1470
3831
|
subject=case.get("subject"),
|
|
1471
3832
|
style=case.get("style"),
|
|
3833
|
+
style_preset=case.get("style_preset"),
|
|
1472
3834
|
materials=case.get("materials"),
|
|
1473
3835
|
lighting=case.get("lighting"),
|
|
1474
3836
|
palette=case.get("palette"),
|
|
@@ -1513,12 +3875,14 @@ def convert_for_benchmark(case: dict) -> dict:
|
|
|
1513
3875
|
prompt = render_prompt(spec)
|
|
1514
3876
|
lint_texts = [] if spec.get("strict_text") else spec["required_text"]
|
|
1515
3877
|
findings = lint_prompt(prompt, spec["asset_type"], spec["quality"], lint_texts)
|
|
3878
|
+
intent_findings = intent_check_prompt(spec["request"], prompt, spec)
|
|
1516
3879
|
return {
|
|
1517
3880
|
"case_id": case.get("id") or case.get("name") or "",
|
|
1518
3881
|
"spec": spec,
|
|
1519
3882
|
"prompt": prompt,
|
|
1520
3883
|
"prompt_digest": prompt_digest(prompt),
|
|
1521
3884
|
"lint": findings,
|
|
3885
|
+
"intent_check": intent_findings,
|
|
1522
3886
|
"acceptance_criteria": spec.get("acceptance_criteria", []),
|
|
1523
3887
|
}
|
|
1524
3888
|
|
|
@@ -1530,7 +3894,8 @@ def cmd_benchmark(args: argparse.Namespace) -> int:
|
|
|
1530
3894
|
print(f"读取 benchmark cases 失败:{exc}", file=sys.stderr)
|
|
1531
3895
|
return 2
|
|
1532
3896
|
results = []
|
|
1533
|
-
|
|
3897
|
+
total_lint_errors = 0
|
|
3898
|
+
total_intent_errors = 0
|
|
1534
3899
|
unstable = 0
|
|
1535
3900
|
for idx, case in enumerate(cases, start=1):
|
|
1536
3901
|
runs = []
|
|
@@ -1540,12 +3905,15 @@ def cmd_benchmark(args: argparse.Namespace) -> int:
|
|
|
1540
3905
|
except ValueError as exc:
|
|
1541
3906
|
result = {"case_index": idx, "case_id": case.get("id", ""), "error": str(exc), "runs": []}
|
|
1542
3907
|
results.append(result)
|
|
1543
|
-
|
|
3908
|
+
total_lint_errors += 1
|
|
1544
3909
|
continue
|
|
1545
3910
|
digests = {run["prompt_digest"] for run in runs}
|
|
1546
3911
|
lint_errors = [item for run in runs for item in run["lint"] if item["severity"] == "error"]
|
|
3912
|
+
intent_errors = [item for run in runs for item in run["intent_check"] if item["severity"] == "error"]
|
|
1547
3913
|
if lint_errors:
|
|
1548
|
-
|
|
3914
|
+
total_lint_errors += len(lint_errors)
|
|
3915
|
+
if intent_errors:
|
|
3916
|
+
total_intent_errors += len(intent_errors)
|
|
1549
3917
|
if len(digests) > 1:
|
|
1550
3918
|
unstable += 1
|
|
1551
3919
|
results.append(
|
|
@@ -1560,15 +3928,18 @@ def cmd_benchmark(args: argparse.Namespace) -> int:
|
|
|
1560
3928
|
"prompt_digest": runs[0]["prompt_digest"],
|
|
1561
3929
|
"lint_errors": lint_errors,
|
|
1562
3930
|
"lint_warnings": [item for run in runs for item in run["lint"] if item["severity"] == "warning"],
|
|
3931
|
+
"intent_errors": intent_errors,
|
|
3932
|
+
"intent_warnings": [item for run in runs for item in run["intent_check"] if item["severity"] == "warning"],
|
|
1563
3933
|
"acceptance_criteria": runs[0]["acceptance_criteria"],
|
|
1564
3934
|
}
|
|
1565
3935
|
)
|
|
1566
3936
|
summary = {
|
|
1567
3937
|
"cases": len(cases),
|
|
1568
3938
|
"runs_per_case": args.runs,
|
|
1569
|
-
"lint_error_count":
|
|
3939
|
+
"lint_error_count": total_lint_errors,
|
|
3940
|
+
"intent_error_count": total_intent_errors,
|
|
1570
3941
|
"unstable_case_count": unstable,
|
|
1571
|
-
"pass":
|
|
3942
|
+
"pass": total_lint_errors == 0 and total_intent_errors == 0 and unstable == 0,
|
|
1572
3943
|
}
|
|
1573
3944
|
output = {"summary": summary, "results": results}
|
|
1574
3945
|
if args.json:
|
|
@@ -1579,13 +3950,15 @@ def cmd_benchmark(args: argparse.Namespace) -> int:
|
|
|
1579
3950
|
if result.get("error"):
|
|
1580
3951
|
print(f"- {result['case_id'] or result['case_index']}: error {result['error']}")
|
|
1581
3952
|
continue
|
|
1582
|
-
status = "PASS" if result["stable"] and not result["lint_errors"] else "FAIL"
|
|
3953
|
+
status = "PASS" if result["stable"] and not result["lint_errors"] and not result["intent_errors"] else "FAIL"
|
|
1583
3954
|
print(
|
|
1584
3955
|
f"- {result['case_id']}: {status} asset={result['asset_type']} "
|
|
1585
3956
|
f"template={result['template_id']} digest={result['prompt_digest']}"
|
|
1586
3957
|
)
|
|
1587
3958
|
for item in result["lint_errors"]:
|
|
1588
3959
|
print(f" error: {item['rule']} - {item['message']}")
|
|
3960
|
+
for item in result["intent_errors"]:
|
|
3961
|
+
print(f" intent-error: {item['rule']} - {item['message']}")
|
|
1589
3962
|
return 0 if summary["pass"] else 1
|
|
1590
3963
|
|
|
1591
3964
|
|
|
@@ -1741,6 +4114,17 @@ def load_json_value(value: str | None) -> dict:
|
|
|
1741
4114
|
return data
|
|
1742
4115
|
|
|
1743
4116
|
|
|
4117
|
+
def missing_pillow_message(command: str) -> str:
|
|
4118
|
+
return (
|
|
4119
|
+
f"{command} 需要 Pillow 图像库。请优先用 `uv run` / `npx @yuhan1124/draw-prompt ...` 自动安装依赖,"
|
|
4120
|
+
"或在当前 Python 环境中安装:python3 -m pip install pillow"
|
|
4121
|
+
)
|
|
4122
|
+
|
|
4123
|
+
|
|
4124
|
+
def is_missing_pillow(exc: BaseException) -> bool:
|
|
4125
|
+
return isinstance(exc, ModuleNotFoundError) and getattr(exc, "name", "") == "PIL"
|
|
4126
|
+
|
|
4127
|
+
|
|
1744
4128
|
def extract_spec_payload(data: dict) -> dict:
|
|
1745
4129
|
if "spec" in data and isinstance(data["spec"], dict):
|
|
1746
4130
|
return data["spec"]
|
|
@@ -1906,19 +4290,21 @@ def draw_text_overlays(image_path: Path, out_path: Path, spec: dict, texts: list
|
|
|
1906
4290
|
overlays = extract_overlay_items(spec, texts)
|
|
1907
4291
|
font_file = choose_font_path(font_path)
|
|
1908
4292
|
rendered = []
|
|
4293
|
+
asset_type = str(spec.get("asset_type") or "")
|
|
1909
4294
|
|
|
1910
4295
|
for idx, item in enumerate(overlays):
|
|
1911
4296
|
text = str(item.get("text") or "").strip()
|
|
1912
4297
|
if not text:
|
|
1913
4298
|
continue
|
|
1914
4299
|
role = str(item.get("role") or "")
|
|
4300
|
+
display_text = text
|
|
1915
4301
|
box = item.get("box")
|
|
1916
4302
|
if not isinstance(box, list) or len(box) != 4:
|
|
1917
4303
|
box = overlay_box_hint(spec, item, idx, len(overlays))
|
|
1918
4304
|
px = normalized_box_to_pixels([float(v) for v in box], width, height)
|
|
1919
4305
|
pad = max(8, int(min(width, height) * 0.012))
|
|
1920
4306
|
inner = (px[0] + pad, px[1] + pad, px[2] - pad, px[3] - pad)
|
|
1921
|
-
font, wrapped = fit_overlay_text(draw,
|
|
4307
|
+
font, wrapped = fit_overlay_text(draw, display_text, inner, font_file)
|
|
1922
4308
|
tw, th = text_bbox(draw, wrapped, font)
|
|
1923
4309
|
align = str(item.get("align") or "center")
|
|
1924
4310
|
if align == "left":
|
|
@@ -1929,7 +4315,25 @@ def draw_text_overlays(image_path: Path, out_path: Path, spec: dict, texts: list
|
|
|
1929
4315
|
tx = inner[0] + max(0, (inner[2] - inner[0] - tw) // 2)
|
|
1930
4316
|
ty = inner[1] + max(0, (inner[3] - inner[1] - th) // 2)
|
|
1931
4317
|
|
|
1932
|
-
if
|
|
4318
|
+
if asset_type == "slide":
|
|
4319
|
+
if role == "slide_title":
|
|
4320
|
+
text_fill = (255, 255, 255, 255)
|
|
4321
|
+
elif role == "slide_section_title":
|
|
4322
|
+
text_fill = (248, 252, 255, 255)
|
|
4323
|
+
else:
|
|
4324
|
+
text_fill = (220, 237, 252, 255)
|
|
4325
|
+
shadow_offset = max(2, int(min(width, height) * 0.002))
|
|
4326
|
+
shadow_fill = (0, 10, 30, 180)
|
|
4327
|
+
draw.multiline_text(
|
|
4328
|
+
(tx + shadow_offset, ty + shadow_offset),
|
|
4329
|
+
wrapped,
|
|
4330
|
+
font=font,
|
|
4331
|
+
fill=shadow_fill,
|
|
4332
|
+
align=align,
|
|
4333
|
+
spacing=max(2, int(getattr(font, "size", 12) * 0.14)),
|
|
4334
|
+
)
|
|
4335
|
+
draw.multiline_text((tx, ty), wrapped, font=font, fill=text_fill, align=align, spacing=max(2, int(getattr(font, "size", 12) * 0.14)))
|
|
4336
|
+
elif role == "price_or_offer":
|
|
1933
4337
|
fill = (31, 84, 55, 232)
|
|
1934
4338
|
outline = (255, 255, 255, 90)
|
|
1935
4339
|
text_fill = (255, 255, 245, 255)
|
|
@@ -1937,9 +4341,9 @@ def draw_text_overlays(image_path: Path, out_path: Path, spec: dict, texts: list
|
|
|
1937
4341
|
fill = (255, 255, 255, 218)
|
|
1938
4342
|
outline = (31, 84, 55, 65)
|
|
1939
4343
|
text_fill = (19, 35, 27, 255)
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
4344
|
+
radius = max(6, int(min(px[2] - px[0], px[3] - px[1]) * 0.12))
|
|
4345
|
+
draw.rounded_rectangle(px, radius=radius, fill=fill, outline=outline, width=max(1, int(pad * 0.14)))
|
|
4346
|
+
draw.multiline_text((tx, ty), wrapped, font=font, fill=text_fill, align=align, spacing=max(2, int(getattr(font, "size", 12) * 0.14)))
|
|
1943
4347
|
rendered.append({"text": text, "box": box, "font_size": getattr(font, "size", 0), "role": role})
|
|
1944
4348
|
|
|
1945
4349
|
final = Image.alpha_composite(image, layer).convert("RGB")
|
|
@@ -1955,6 +4359,11 @@ def cmd_overlay(args: argparse.Namespace) -> int:
|
|
|
1955
4359
|
image_path = Path(args.image).expanduser()
|
|
1956
4360
|
out_path = Path(args.out).expanduser() if args.out else default_overlay_out(image_path)
|
|
1957
4361
|
report = draw_text_overlays(image_path, out_path, spec, args.text, args.font)
|
|
4362
|
+
except ModuleNotFoundError as exc:
|
|
4363
|
+
if is_missing_pillow(exc):
|
|
4364
|
+
print(missing_pillow_message("overlay"), file=sys.stderr)
|
|
4365
|
+
return 2
|
|
4366
|
+
raise
|
|
1958
4367
|
except Exception as exc:
|
|
1959
4368
|
print(f"overlay 失败:{exc}", file=sys.stderr)
|
|
1960
4369
|
return 2
|
|
@@ -2027,6 +4436,11 @@ def cmd_visual_check(args: argparse.Namespace) -> int:
|
|
|
2027
4436
|
spec = extract_spec_payload(load_json_value(args.spec)) if args.spec else {}
|
|
2028
4437
|
expected = args.aspect or spec.get("aspect")
|
|
2029
4438
|
metrics = image_quality_metrics(Path(args.image).expanduser(), expected)
|
|
4439
|
+
except ModuleNotFoundError as exc:
|
|
4440
|
+
if is_missing_pillow(exc):
|
|
4441
|
+
print(missing_pillow_message("visual-check"), file=sys.stderr)
|
|
4442
|
+
return 2
|
|
4443
|
+
raise
|
|
2030
4444
|
except Exception as exc:
|
|
2031
4445
|
print(f"visual-check 失败:{exc}", file=sys.stderr)
|
|
2032
4446
|
return 2
|
|
@@ -2110,6 +4524,11 @@ def cmd_edit_check(args: argparse.Namespace) -> int:
|
|
|
2110
4524
|
args.threshold,
|
|
2111
4525
|
args.min_change,
|
|
2112
4526
|
)
|
|
4527
|
+
except ModuleNotFoundError as exc:
|
|
4528
|
+
if is_missing_pillow(exc):
|
|
4529
|
+
print(missing_pillow_message("edit-check"), file=sys.stderr)
|
|
4530
|
+
return 2
|
|
4531
|
+
raise
|
|
2113
4532
|
except Exception as exc:
|
|
2114
4533
|
print(f"edit-check 失败:{exc}", file=sys.stderr)
|
|
2115
4534
|
return 2
|
|
@@ -2136,6 +4555,7 @@ def cmd_visual_regress(args: argparse.Namespace) -> int:
|
|
|
2136
4555
|
return 2
|
|
2137
4556
|
results = []
|
|
2138
4557
|
lint_errors = 0
|
|
4558
|
+
intent_errors = 0
|
|
2139
4559
|
visual_errors = 0
|
|
2140
4560
|
missing_images = 0
|
|
2141
4561
|
for idx, case in enumerate(cases, start=1):
|
|
@@ -2160,7 +4580,9 @@ def cmd_visual_regress(args: argparse.Namespace) -> int:
|
|
|
2160
4580
|
continue
|
|
2161
4581
|
compiled = visual_case_compile(case)
|
|
2162
4582
|
lint = compiled["lint"]
|
|
4583
|
+
intent = compiled.get("intent_check", [])
|
|
2163
4584
|
lint_errors += sum(1 for item in lint if item.get("severity") == "error")
|
|
4585
|
+
intent_errors += sum(1 for item in intent if item.get("severity") == "error")
|
|
2164
4586
|
item = {
|
|
2165
4587
|
"id": case_id,
|
|
2166
4588
|
"scenario": case.get("scenario") or case.get("tool") or "convert",
|
|
@@ -2168,6 +4590,7 @@ def cmd_visual_regress(args: argparse.Namespace) -> int:
|
|
|
2168
4590
|
"asset_type": compiled["spec"]["asset_type"],
|
|
2169
4591
|
"aspect": compiled["spec"]["aspect"],
|
|
2170
4592
|
"lint": lint,
|
|
4593
|
+
"intent_check": intent,
|
|
2171
4594
|
"status": "compiled",
|
|
2172
4595
|
}
|
|
2173
4596
|
image = case.get("image") or case.get("output")
|
|
@@ -2189,21 +4612,28 @@ def cmd_visual_regress(args: argparse.Namespace) -> int:
|
|
|
2189
4612
|
visual_errors += sum(1 for f in edit_report["findings"] if f.get("severity") == "error")
|
|
2190
4613
|
item["edit_check"] = edit_report
|
|
2191
4614
|
results.append(item)
|
|
4615
|
+
except ModuleNotFoundError as exc:
|
|
4616
|
+
if is_missing_pillow(exc):
|
|
4617
|
+
results.append({"id": case_id, "status": "error", "error": missing_pillow_message("visual-regress")})
|
|
4618
|
+
else:
|
|
4619
|
+
results.append({"id": case_id, "status": "error", "error": str(exc)})
|
|
4620
|
+
visual_errors += 1
|
|
2192
4621
|
except Exception as exc:
|
|
2193
4622
|
visual_errors += 1
|
|
2194
4623
|
results.append({"id": case_id, "status": "error", "error": str(exc)})
|
|
2195
4624
|
summary = {
|
|
2196
4625
|
"cases": len(cases),
|
|
2197
4626
|
"lint_error_count": lint_errors,
|
|
4627
|
+
"intent_error_count": intent_errors,
|
|
2198
4628
|
"visual_error_count": visual_errors,
|
|
2199
4629
|
"missing_image_count": missing_images,
|
|
2200
|
-
"pass": lint_errors == 0 and visual_errors == 0 and missing_images == 0,
|
|
4630
|
+
"pass": lint_errors == 0 and intent_errors == 0 and visual_errors == 0 and missing_images == 0,
|
|
2201
4631
|
}
|
|
2202
4632
|
output = {"summary": summary, "results": results}
|
|
2203
4633
|
if args.json:
|
|
2204
4634
|
print(json.dumps(output, ensure_ascii=False, indent=2))
|
|
2205
4635
|
else:
|
|
2206
|
-
print(f"visual-regress: cases={summary['cases']} pass={summary['pass']} lint_errors={lint_errors} visual_errors={visual_errors} missing_images={missing_images}")
|
|
4636
|
+
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
4637
|
for item in results:
|
|
2208
4638
|
print(f"- {item['id']}: {item['status']} asset={item.get('asset_type', '?')} digest={item.get('prompt_digest', '?')}")
|
|
2209
4639
|
return 0 if summary["pass"] else 1
|
|
@@ -2228,6 +4658,11 @@ def compact_title(text: str, limit: int = 28) -> str:
|
|
|
2228
4658
|
return clean[:limit].rstrip() + "..."
|
|
2229
4659
|
|
|
2230
4660
|
|
|
4661
|
+
def safe_slug(value: str, fallback: str = "style") -> str:
|
|
4662
|
+
slug = re.sub(r"[^a-zA-Z0-9_-]+", "-", value.strip().lower()).strip("-")
|
|
4663
|
+
return slug or fallback
|
|
4664
|
+
|
|
4665
|
+
|
|
2231
4666
|
def normalize_case_values(case: dict) -> dict:
|
|
2232
4667
|
out = dict(case)
|
|
2233
4668
|
for key in ("style", "materials", "palette", "tags"):
|
|
@@ -2251,6 +4686,7 @@ def compile_visual_case(
|
|
|
2251
4686
|
prompt = render_prompt(spec)
|
|
2252
4687
|
lint_texts = [] if spec.get("strict_text") else spec["required_text"]
|
|
2253
4688
|
findings = lint_prompt(prompt, spec["asset_type"], spec["quality"], lint_texts)
|
|
4689
|
+
intent_findings = intent_check_prompt(spec["request"], prompt, spec)
|
|
2254
4690
|
return {
|
|
2255
4691
|
"spec": spec,
|
|
2256
4692
|
"prompt": prompt,
|
|
@@ -2258,6 +4694,7 @@ def compile_visual_case(
|
|
|
2258
4694
|
"text_overlay_spec": spec.get("text_overlay_spec"),
|
|
2259
4695
|
"acceptance_criteria": spec.get("acceptance_criteria", []),
|
|
2260
4696
|
"lint": findings,
|
|
4697
|
+
"intent_check": intent_findings,
|
|
2261
4698
|
"handoff": handoff_text(prompt, args.out, spec["size"], spec["quality"], args.target) if include_handoff else None,
|
|
2262
4699
|
}
|
|
2263
4700
|
|
|
@@ -2345,8 +4782,9 @@ def extract_visual_labels(chunk: str, asset_type: str, limit: int = 5) -> list[s
|
|
|
2345
4782
|
add(match)
|
|
2346
4783
|
for match in re.findall(r"(?:标题|主题|模块|步骤|节点|页面)[::\s]*([^,。;;\n]{2,24})", chunk):
|
|
2347
4784
|
add(match)
|
|
2348
|
-
if
|
|
2349
|
-
|
|
4785
|
+
if asset_type in {"diagram", "infographic", "ui"}:
|
|
4786
|
+
for item in extract_structural_labels(chunk, asset_type):
|
|
4787
|
+
add(item)
|
|
2350
4788
|
return labels[:limit]
|
|
2351
4789
|
|
|
2352
4790
|
|
|
@@ -2383,6 +4821,7 @@ def cmd_compose(args: argparse.Namespace) -> int:
|
|
|
2383
4821
|
"request": f"{purpose}。根据这段内容生成对应画面:{chunk}",
|
|
2384
4822
|
"asset_type": asset_type,
|
|
2385
4823
|
"style": shared_style,
|
|
4824
|
+
"style_preset": args.style_preset,
|
|
2386
4825
|
"palette": args.palette,
|
|
2387
4826
|
"text": labels if (args.strict_text or asset_type in {"diagram", "infographic", "ui"}) else [],
|
|
2388
4827
|
"strict_text": args.strict_text,
|
|
@@ -2401,6 +4840,7 @@ def cmd_compose(args: argparse.Namespace) -> int:
|
|
|
2401
4840
|
"prompt": compiled["prompt"],
|
|
2402
4841
|
"handoff": compiled["handoff"],
|
|
2403
4842
|
"lint": compiled["lint"],
|
|
4843
|
+
"intent_check": compiled["intent_check"],
|
|
2404
4844
|
"text_overlay_spec": compiled["text_overlay_spec"],
|
|
2405
4845
|
"acceptance_criteria": compiled["acceptance_criteria"],
|
|
2406
4846
|
"spec": compiled["spec"],
|
|
@@ -2409,12 +4849,14 @@ def cmd_compose(args: argparse.Namespace) -> int:
|
|
|
2409
4849
|
result = {
|
|
2410
4850
|
"summary": compact_title(text, 80),
|
|
2411
4851
|
"shared_style": shared_style,
|
|
4852
|
+
"style_preset": args.style_preset or "auto",
|
|
2412
4853
|
"visual_plan": visual_plan,
|
|
2413
4854
|
"bundle_acceptance": [
|
|
2414
4855
|
"每张图对应长输入中的一个明确信息单元,不混淆主题。",
|
|
2415
4856
|
"整组图使用同一风格、配色和信息层级规则。",
|
|
2416
4857
|
"图表/架构/UI 类标签清晰可读;strict_text 时文字走 overlay_spec。",
|
|
2417
4858
|
"任一单图 lint 出现 error 时必须先修 prompt 再交给下游出图。",
|
|
4859
|
+
"任一单图 intent-check 出现 error 时必须先修 prompt,避免偏离原文。",
|
|
2418
4860
|
],
|
|
2419
4861
|
}
|
|
2420
4862
|
if args.json:
|
|
@@ -2422,13 +4864,13 @@ def cmd_compose(args: argparse.Namespace) -> int:
|
|
|
2422
4864
|
else:
|
|
2423
4865
|
print(f"compose: {len(visual_plan)} 张图 shared_style={shared_style}")
|
|
2424
4866
|
for item in visual_plan:
|
|
2425
|
-
status = "FAIL" if has_lint_error(item["lint"]) else "PASS"
|
|
4867
|
+
status = "FAIL" if has_lint_error(item["lint"]) or has_lint_error(item["intent_check"]) else "PASS"
|
|
2426
4868
|
print(f"\n## {item['id']} {item['purpose']} [{status}]")
|
|
2427
4869
|
print(item["prompt"])
|
|
2428
4870
|
if item["handoff"]:
|
|
2429
4871
|
print("\nHandoff:")
|
|
2430
4872
|
print(item["handoff"])
|
|
2431
|
-
return 1 if any(has_lint_error(item["lint"]) for item in visual_plan) else 0
|
|
4873
|
+
return 1 if any(has_lint_error(item["lint"]) or has_lint_error(item["intent_check"]) for item in visual_plan) else 0
|
|
2432
4874
|
|
|
2433
4875
|
|
|
2434
4876
|
def load_series_cases(file_path: str | None) -> list[dict | str]:
|
|
@@ -2484,6 +4926,8 @@ def cmd_series(args: argparse.Namespace) -> int:
|
|
|
2484
4926
|
case["style"] = "; ".join([shared_style, str(case.get("style") or "").strip()]).strip("; ")
|
|
2485
4927
|
if args.palette:
|
|
2486
4928
|
case["palette"] = args.palette
|
|
4929
|
+
if args.style_preset:
|
|
4930
|
+
case["style_preset"] = args.style_preset
|
|
2487
4931
|
if args.strict_text:
|
|
2488
4932
|
case["strict_text"] = True
|
|
2489
4933
|
case.setdefault("text", extract_visual_labels(brief, str(case.get("asset_type") or route_asset_type(brief))))
|
|
@@ -2501,7 +4945,7 @@ def cmd_series(args: argparse.Namespace) -> int:
|
|
|
2501
4945
|
"spec": compiled["spec"],
|
|
2502
4946
|
}
|
|
2503
4947
|
)
|
|
2504
|
-
result = {"shared_style": shared_style, "count": len(variants), "variants": variants}
|
|
4948
|
+
result = {"shared_style": shared_style, "style_preset": args.style_preset or "auto", "count": len(variants), "variants": variants}
|
|
2505
4949
|
if args.json:
|
|
2506
4950
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
2507
4951
|
else:
|
|
@@ -2512,6 +4956,104 @@ def cmd_series(args: argparse.Namespace) -> int:
|
|
|
2512
4956
|
return 1 if any(has_lint_error(item["lint"]) for item in variants) else 0
|
|
2513
4957
|
|
|
2514
4958
|
|
|
4959
|
+
def cmd_variants(args: argparse.Namespace) -> int:
|
|
4960
|
+
request = read_text_argument(args.request_text, args.file)
|
|
4961
|
+
if not request:
|
|
4962
|
+
print("用法:variants \"<同一个画图需求>\" --style-presets corporate,premium,flat-vector", file=sys.stderr)
|
|
4963
|
+
return 2
|
|
4964
|
+
try:
|
|
4965
|
+
presets = parse_style_preset_list(args.style_presets, DEFAULT_VARIANT_PRESETS)
|
|
4966
|
+
except ValueError as exc:
|
|
4967
|
+
print(str(exc), file=sys.stderr)
|
|
4968
|
+
return 2
|
|
4969
|
+
custom_styles = args.custom_style or []
|
|
4970
|
+
if not presets and not custom_styles:
|
|
4971
|
+
print("至少提供一个 --style-presets 或 --custom-style。", file=sys.stderr)
|
|
4972
|
+
return 2
|
|
4973
|
+
|
|
4974
|
+
variants = []
|
|
4975
|
+
entries: list[dict] = [{"kind": "preset", "style_preset": preset, "style": args.shared_style} for preset in presets]
|
|
4976
|
+
for custom in custom_styles:
|
|
4977
|
+
entries.append(
|
|
4978
|
+
{
|
|
4979
|
+
"kind": "custom",
|
|
4980
|
+
"style_preset": "auto",
|
|
4981
|
+
"style": "; ".join([args.shared_style or "", custom]).strip("; "),
|
|
4982
|
+
"custom_style": custom,
|
|
4983
|
+
}
|
|
4984
|
+
)
|
|
4985
|
+
|
|
4986
|
+
for idx, entry in enumerate(entries, start=1):
|
|
4987
|
+
style_label = entry.get("custom_style") or entry["style_preset"]
|
|
4988
|
+
out = None
|
|
4989
|
+
if args.out_dir:
|
|
4990
|
+
out = str(Path(args.out_dir).expanduser() / f"variant-{idx:02d}-{safe_slug(str(style_label))}.png")
|
|
4991
|
+
case = {
|
|
4992
|
+
"id": f"variant-{idx:02d}",
|
|
4993
|
+
"request": request,
|
|
4994
|
+
"asset_type": args.asset_type,
|
|
4995
|
+
"template": args.template,
|
|
4996
|
+
"aspect": args.aspect,
|
|
4997
|
+
"text": args.text or [],
|
|
4998
|
+
"style": entry.get("style"),
|
|
4999
|
+
"style_preset": entry["style_preset"],
|
|
5000
|
+
"materials": args.materials,
|
|
5001
|
+
"lighting": args.lighting,
|
|
5002
|
+
"palette": args.palette,
|
|
5003
|
+
"size": args.size,
|
|
5004
|
+
"quality": args.quality,
|
|
5005
|
+
"strict_text": args.strict_text,
|
|
5006
|
+
"target": args.target,
|
|
5007
|
+
"out": out,
|
|
5008
|
+
"tags": "variants,multi-style",
|
|
5009
|
+
}
|
|
5010
|
+
compiled = compile_visual_case(case, target=args.target, out=out)
|
|
5011
|
+
variants.append(
|
|
5012
|
+
{
|
|
5013
|
+
"id": case["id"],
|
|
5014
|
+
"style_preset": entry["style_preset"],
|
|
5015
|
+
"custom_style": entry.get("custom_style"),
|
|
5016
|
+
"brief": request,
|
|
5017
|
+
"asset_type": compiled["spec"]["asset_type"],
|
|
5018
|
+
"template_id": compiled["spec"]["template_id"],
|
|
5019
|
+
"prompt": compiled["prompt"],
|
|
5020
|
+
"prompt_digest": compiled["prompt_digest"],
|
|
5021
|
+
"handoff": compiled["handoff"],
|
|
5022
|
+
"lint": compiled["lint"],
|
|
5023
|
+
"intent_check": compiled["intent_check"],
|
|
5024
|
+
"text_overlay_spec": compiled["text_overlay_spec"],
|
|
5025
|
+
"acceptance_criteria": compiled["acceptance_criteria"],
|
|
5026
|
+
"spec": compiled["spec"],
|
|
5027
|
+
}
|
|
5028
|
+
)
|
|
5029
|
+
|
|
5030
|
+
result = {
|
|
5031
|
+
"request": request,
|
|
5032
|
+
"count": len(variants),
|
|
5033
|
+
"style_presets": presets,
|
|
5034
|
+
"custom_styles": custom_styles,
|
|
5035
|
+
"variants": variants,
|
|
5036
|
+
"bundle_acceptance": [
|
|
5037
|
+
"所有版本必须保留同一个 User visual brief,不改变主题、主体、文字、布局和信息结构。",
|
|
5038
|
+
"不同版本只允许在 Style / quality envelope 中改变视觉语言、质感、光照、配色和呈现媒介。",
|
|
5039
|
+
"任一版本 lint 或 intent-check 出现 error 时,必须先修 prompt 再交给下游出图。",
|
|
5040
|
+
],
|
|
5041
|
+
}
|
|
5042
|
+
if args.json:
|
|
5043
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
5044
|
+
else:
|
|
5045
|
+
print(f"variants: {len(variants)} 个风格版本")
|
|
5046
|
+
for item in variants:
|
|
5047
|
+
status = "FAIL" if has_lint_error(item["lint"]) or has_lint_error(item["intent_check"]) else "PASS"
|
|
5048
|
+
label = item["custom_style"] or item["style_preset"]
|
|
5049
|
+
print(f"\n## {item['id']} style={label} [{status}]")
|
|
5050
|
+
print(item["prompt"])
|
|
5051
|
+
if item["handoff"]:
|
|
5052
|
+
print("\nHandoff:")
|
|
5053
|
+
print(item["handoff"])
|
|
5054
|
+
return 1 if any(has_lint_error(item["lint"]) or has_lint_error(item["intent_check"]) for item in variants) else 0
|
|
5055
|
+
|
|
5056
|
+
|
|
2515
5057
|
def parse_reference(value: str) -> dict:
|
|
2516
5058
|
if ":" in value and not value.startswith("/"):
|
|
2517
5059
|
role, _, ref = value.partition(":")
|
|
@@ -2602,6 +5144,7 @@ def cmd_brand(args: argparse.Namespace) -> int:
|
|
|
2602
5144
|
"request": f"{args.request}\n{brand_block}",
|
|
2603
5145
|
"asset_type": args.asset_type,
|
|
2604
5146
|
"style": brand_profile["style"],
|
|
5147
|
+
"style_preset": args.style_preset,
|
|
2605
5148
|
"palette": args.palette,
|
|
2606
5149
|
"text": args.text or [],
|
|
2607
5150
|
"strict_text": args.strict_text,
|
|
@@ -2646,6 +5189,7 @@ def cmd_character(args: argparse.Namespace) -> int:
|
|
|
2646
5189
|
"request": f"角色设定三视图和表情板:{identity}",
|
|
2647
5190
|
"asset_type": "character",
|
|
2648
5191
|
"style": bible["style"],
|
|
5192
|
+
"style_preset": args.style_preset,
|
|
2649
5193
|
"palette": args.palette,
|
|
2650
5194
|
"text": [args.name],
|
|
2651
5195
|
"target": args.target,
|
|
@@ -2658,6 +5202,7 @@ def cmd_character(args: argparse.Namespace) -> int:
|
|
|
2658
5202
|
"request": f"{identity} 场景图:{scene}",
|
|
2659
5203
|
"asset_type": "illustration",
|
|
2660
5204
|
"style": bible["style"],
|
|
5205
|
+
"style_preset": args.style_preset,
|
|
2661
5206
|
"palette": args.palette,
|
|
2662
5207
|
"target": args.target,
|
|
2663
5208
|
"tags": "character,scene",
|
|
@@ -2714,6 +5259,20 @@ def infer_chart_type(request: str, columns: list[str], override: str | None) ->
|
|
|
2714
5259
|
return "bar chart"
|
|
2715
5260
|
|
|
2716
5261
|
|
|
5262
|
+
def infer_chart_title(request: str, override: str | None) -> str:
|
|
5263
|
+
if override:
|
|
5264
|
+
return override
|
|
5265
|
+
patterns = [
|
|
5266
|
+
r"(?:标题为|标题是|title\s*(?:is|:|:))\s*([^,,。.;;\n]{2,40})",
|
|
5267
|
+
r"(?:标题|title)\s*[::]\s*([^,,。.;;\n]{2,40})",
|
|
5268
|
+
]
|
|
5269
|
+
for pattern in patterns:
|
|
5270
|
+
match = re.search(pattern, request, flags=re.IGNORECASE)
|
|
5271
|
+
if match:
|
|
5272
|
+
return match.group(1).strip(" \t\n\r,,。;;::.!?!?")
|
|
5273
|
+
return compact_title(request, 24)
|
|
5274
|
+
|
|
5275
|
+
|
|
2717
5276
|
def cmd_data_viz(args: argparse.Namespace) -> int:
|
|
2718
5277
|
request = args.request or "根据数据生成清晰的信息图"
|
|
2719
5278
|
try:
|
|
@@ -2721,7 +5280,7 @@ def cmd_data_viz(args: argparse.Namespace) -> int:
|
|
|
2721
5280
|
except (OSError, json.JSONDecodeError) as exc:
|
|
2722
5281
|
print(f"读取数据失败:{exc}", file=sys.stderr)
|
|
2723
5282
|
return 2
|
|
2724
|
-
title =
|
|
5283
|
+
title = infer_chart_title(request, args.title)
|
|
2725
5284
|
chart_type = infer_chart_type(request, data_preview["columns"], args.chart_type)
|
|
2726
5285
|
facts = {
|
|
2727
5286
|
"title": title,
|
|
@@ -2783,6 +5342,7 @@ def cmd_rewrite(args: argparse.Namespace) -> int:
|
|
|
2783
5342
|
"request": source,
|
|
2784
5343
|
"asset_type": args.asset_type,
|
|
2785
5344
|
"style": args.style,
|
|
5345
|
+
"style_preset": args.style_preset,
|
|
2786
5346
|
"strict_text": args.strict_text,
|
|
2787
5347
|
"text": args.text or [],
|
|
2788
5348
|
"target": args.target,
|
|
@@ -2839,6 +5399,7 @@ def cmd_adapt(args: argparse.Namespace) -> int:
|
|
|
2839
5399
|
"aspect": aspect,
|
|
2840
5400
|
"layout": adapt_layout_for_aspect(aspect, asset_type),
|
|
2841
5401
|
"style": args.style,
|
|
5402
|
+
"style_preset": args.style_preset,
|
|
2842
5403
|
"text": args.text or [],
|
|
2843
5404
|
"strict_text": args.strict_text,
|
|
2844
5405
|
"target": args.target,
|
|
@@ -2883,8 +5444,34 @@ def cmd_status(args: argparse.Namespace) -> int:
|
|
|
2883
5444
|
print(f" codex CLI : {which('codex') or '未找到'}")
|
|
2884
5445
|
plugin = Path.home() / ".claude" / "plugins" / "cache" / "codex-image-in-cc"
|
|
2885
5446
|
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")
|
|
5447
|
+
print(" 核心转化命令: convert / compose / variants / series / edit / brand / character / data-viz / rewrite / adapt")
|
|
5448
|
+
print(" 稳定性命令 : overlay / visual-check / edit-check / visual-regress / lint / intent-check / benchmark / revise / styles")
|
|
5449
|
+
return 0
|
|
5450
|
+
|
|
5451
|
+
|
|
5452
|
+
def cmd_styles(args: argparse.Namespace) -> int:
|
|
5453
|
+
query = (args.query or "").strip().lower()
|
|
5454
|
+
names = available_style_presets(include_auto=args.include_auto)
|
|
5455
|
+
presets = []
|
|
5456
|
+
for name in names:
|
|
5457
|
+
anchors = STYLE_PRESETS[name]
|
|
5458
|
+
haystack = f"{name} {' '.join(anchors)}".lower()
|
|
5459
|
+
if query and query not in haystack:
|
|
5460
|
+
continue
|
|
5461
|
+
presets.append({"name": name, "anchors": anchors})
|
|
5462
|
+
result = {
|
|
5463
|
+
"count": len(presets),
|
|
5464
|
+
"total": len(names),
|
|
5465
|
+
"default_variant_presets": DEFAULT_VARIANT_PRESETS,
|
|
5466
|
+
"presets": presets,
|
|
5467
|
+
}
|
|
5468
|
+
if args.json:
|
|
5469
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
5470
|
+
else:
|
|
5471
|
+
print(f"style presets: {len(presets)} / {len(names)}")
|
|
5472
|
+
print(f"default variants: {', '.join(DEFAULT_VARIANT_PRESETS)}")
|
|
5473
|
+
for item in presets:
|
|
5474
|
+
print(f"- {item['name']}: {'; '.join(item['anchors'])}")
|
|
2888
5475
|
return 0
|
|
2889
5476
|
|
|
2890
5477
|
|
|
@@ -2904,6 +5491,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2904
5491
|
pc.add_argument("--subject", help="覆盖主体描述")
|
|
2905
5492
|
pc.add_argument("--layout", help="覆盖布局规格")
|
|
2906
5493
|
pc.add_argument("--style", help="风格锚点,逗号分隔")
|
|
5494
|
+
pc.add_argument("--style-preset", choices=sorted(STYLE_PRESETS.keys()), help="风格预设,只控制风格/质量层,不改用户内容")
|
|
2907
5495
|
pc.add_argument("--materials", help="材料/质感,逗号分隔")
|
|
2908
5496
|
pc.add_argument("--lighting", help="光照描述")
|
|
2909
5497
|
pc.add_argument("--palette", help="配色,逗号分隔")
|
|
@@ -2912,7 +5500,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2912
5500
|
pc.add_argument("--out", help="期望输出路径")
|
|
2913
5501
|
pc.add_argument("--tags", help="额外样本标签,逗号分隔")
|
|
2914
5502
|
pc.add_argument("--target", choices=["codex-image", "codex-exec", "raw"], default="codex-image")
|
|
2915
|
-
pc.add_argument("--strict-text", action="store_true", help="
|
|
5503
|
+
pc.add_argument("--strict-text", action="store_true", help="可选兜底:输出 visual prompt + text_overlay_spec,用于文字必须绝对精确的场景")
|
|
2916
5504
|
pc.add_argument("--record-pending", action="store_true", help="把本次转化记录为 pending 样本")
|
|
2917
5505
|
pc.add_argument("--no-handoff", action="store_true", help="只输出 Prompt,不输出下游指令")
|
|
2918
5506
|
pc.add_argument("--json", action="store_true")
|
|
@@ -2923,8 +5511,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2923
5511
|
pco.add_argument("--file", help="从文件读取长输入")
|
|
2924
5512
|
pco.add_argument("--max-images", type=int, default=6, help="最多拆成多少张图")
|
|
2925
5513
|
pco.add_argument("--shared-style", help="整组图共享风格锚点")
|
|
5514
|
+
pco.add_argument("--style-preset", choices=sorted(STYLE_PRESETS.keys()), help="整组风格预设,只控制风格/质量层")
|
|
2926
5515
|
pco.add_argument("--palette", help="整组图共享配色,逗号分隔")
|
|
2927
|
-
pco.add_argument("--strict-text", action="store_true", help="
|
|
5516
|
+
pco.add_argument("--strict-text", action="store_true", help="可选兜底:文字/标签走 overlay_spec")
|
|
2928
5517
|
pco.add_argument("--out-dir", help="为每张 handoff 生成建议输出路径")
|
|
2929
5518
|
pco.add_argument("--target", choices=["codex-image", "codex-exec", "raw"], default="codex-image")
|
|
2930
5519
|
pco.add_argument("--json", action="store_true")
|
|
@@ -2935,12 +5524,34 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2935
5524
|
psr.add_argument("--file", help="JSON/JSONL/纯文本 briefs")
|
|
2936
5525
|
psr.add_argument("--asset-type", choices=sorted(ASSET_ROUTES.keys()), help="覆盖所有 brief 的资产类型")
|
|
2937
5526
|
psr.add_argument("--style", help="整组图共享风格")
|
|
5527
|
+
psr.add_argument("--style-preset", choices=sorted(STYLE_PRESETS.keys()), help="整组风格预设,只控制风格/质量层")
|
|
2938
5528
|
psr.add_argument("--palette", help="整组图共享配色")
|
|
2939
5529
|
psr.add_argument("--strict-text", action="store_true")
|
|
2940
5530
|
psr.add_argument("--target", choices=["codex-image", "codex-exec", "raw"], default="codex-image")
|
|
2941
5531
|
psr.add_argument("--json", action="store_true")
|
|
2942
5532
|
psr.set_defaults(func=cmd_series)
|
|
2943
5533
|
|
|
5534
|
+
pv = sub.add_parser("variants", help="同一需求 -> 多风格 Prompt 组")
|
|
5535
|
+
pv.add_argument("request_text", nargs="*", help="同一个自然语言画图需求;也可用 --file")
|
|
5536
|
+
pv.add_argument("--file", help="从文件读取画图需求")
|
|
5537
|
+
pv.add_argument("--style-presets", default=",".join(DEFAULT_VARIANT_PRESETS), help="逗号分隔风格预设;可用 all 生成全部内置预设")
|
|
5538
|
+
pv.add_argument("--custom-style", action="append", help="额外自定义风格版本,可重复")
|
|
5539
|
+
pv.add_argument("--shared-style", help="所有风格版本共同附加的风格约束")
|
|
5540
|
+
pv.add_argument("--asset-type", choices=sorted(ASSET_ROUTES.keys()), help="覆盖自动识别的资产类型")
|
|
5541
|
+
pv.add_argument("--template", choices=sorted(TEMPLATE_DEFS.keys()), help="覆盖细分模板")
|
|
5542
|
+
pv.add_argument("--aspect", help="覆盖画幅,如 3:4 / 16:9 / 1:1")
|
|
5543
|
+
pv.add_argument("--text", action="append", help="必须逐字显示的文字,可重复")
|
|
5544
|
+
pv.add_argument("--materials", help="材料/质感,逗号分隔")
|
|
5545
|
+
pv.add_argument("--lighting", help="光照描述")
|
|
5546
|
+
pv.add_argument("--palette", help="配色,逗号分隔")
|
|
5547
|
+
pv.add_argument("--size", help="size 预设或像素,如 portrait / 1024x1536")
|
|
5548
|
+
pv.add_argument("--quality", choices=["low", "medium", "high"], help="质量档")
|
|
5549
|
+
pv.add_argument("--out-dir", help="为每个 handoff 生成建议输出路径")
|
|
5550
|
+
pv.add_argument("--strict-text", action="store_true", help="可选兜底:文字/标签走 overlay_spec")
|
|
5551
|
+
pv.add_argument("--target", choices=["codex-image", "codex-exec", "raw"], default="codex-image")
|
|
5552
|
+
pv.add_argument("--json", action="store_true")
|
|
5553
|
+
pv.set_defaults(func=cmd_variants)
|
|
5554
|
+
|
|
2944
5555
|
pe = sub.add_parser("edit", help="参考图/改图需求 -> 编辑 Prompt")
|
|
2945
5556
|
pe.add_argument("--goal", required=True, help="改图目标")
|
|
2946
5557
|
pe.add_argument("--reference", action="append", help="参考图,格式 role:path 或 path,可重复")
|
|
@@ -2962,6 +5573,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2962
5573
|
pbr.add_argument("--values", help="品牌价值,逗号分隔")
|
|
2963
5574
|
pbr.add_argument("--palette", help="品牌配色,逗号分隔")
|
|
2964
5575
|
pbr.add_argument("--style", help="品牌视觉风格")
|
|
5576
|
+
pbr.add_argument("--style-preset", choices=sorted(STYLE_PRESETS.keys()), help="编译品牌资产时的风格预设")
|
|
2965
5577
|
pbr.add_argument("--avoid", help="禁用项,逗号分隔")
|
|
2966
5578
|
pbr.add_argument("--request", help="可选:直接用品牌档案编译一张图")
|
|
2967
5579
|
pbr.add_argument("--asset-type", choices=sorted(ASSET_ROUTES.keys()), default="poster")
|
|
@@ -2975,6 +5587,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2975
5587
|
pch.add_argument("--name", required=True)
|
|
2976
5588
|
pch.add_argument("--description", required=True)
|
|
2977
5589
|
pch.add_argument("--style")
|
|
5590
|
+
pch.add_argument("--style-preset", choices=sorted(STYLE_PRESETS.keys()), help="角色图编译时的风格预设")
|
|
2978
5591
|
pch.add_argument("--outfit")
|
|
2979
5592
|
pch.add_argument("--palette")
|
|
2980
5593
|
pch.add_argument("--scene", action="append", help="用同一角色生成的场景,可重复")
|
|
@@ -2996,6 +5609,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2996
5609
|
prw.add_argument("--file", help="从文件读取原始 prompt")
|
|
2997
5610
|
prw.add_argument("--asset-type", choices=sorted(ASSET_ROUTES.keys()))
|
|
2998
5611
|
prw.add_argument("--style")
|
|
5612
|
+
prw.add_argument("--style-preset", choices=sorted(STYLE_PRESETS.keys()), help="风格预设,只控制风格/质量层,不改用户内容")
|
|
2999
5613
|
prw.add_argument("--text", action="append", help="必须逐字显示的文字,可重复")
|
|
3000
5614
|
prw.add_argument("--strict-text", action="store_true")
|
|
3001
5615
|
prw.add_argument("--target", choices=["codex-image", "codex-exec", "raw"], default="codex-image")
|
|
@@ -3007,6 +5621,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3007
5621
|
pad.add_argument("--aspects", default="1:1,3:4,16:9,9:16", help="逗号分隔画幅列表")
|
|
3008
5622
|
pad.add_argument("--asset-type", choices=sorted(ASSET_ROUTES.keys()))
|
|
3009
5623
|
pad.add_argument("--style")
|
|
5624
|
+
pad.add_argument("--style-preset", choices=sorted(STYLE_PRESETS.keys()), help="风格预设,只控制风格/质量层,不改用户内容")
|
|
3010
5625
|
pad.add_argument("--text", action="append", help="必须逐字显示的文字,可重复")
|
|
3011
5626
|
pad.add_argument("--strict-text", action="store_true")
|
|
3012
5627
|
pad.add_argument("--target", choices=["codex-image", "codex-exec", "raw"], default="codex-image")
|
|
@@ -3055,6 +5670,21 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3055
5670
|
pl.add_argument("--json", action="store_true")
|
|
3056
5671
|
pl.set_defaults(func=cmd_lint)
|
|
3057
5672
|
|
|
5673
|
+
pst = sub.add_parser("styles", help="列出内置风格预设")
|
|
5674
|
+
pst.add_argument("--query", help="按名称或描述过滤")
|
|
5675
|
+
pst.add_argument("--include-auto", action="store_true", help="包含 auto")
|
|
5676
|
+
pst.add_argument("--json", action="store_true")
|
|
5677
|
+
pst.set_defaults(func=cmd_styles)
|
|
5678
|
+
|
|
5679
|
+
pic = sub.add_parser("intent-check", help="检查生成 Prompt 是否偏离或添加用户未要求内容")
|
|
5680
|
+
pic.add_argument("--request", nargs="+", help="原始用户需求")
|
|
5681
|
+
pic.add_argument("--request-file", help="从文件读取原始用户需求")
|
|
5682
|
+
pic.add_argument("--prompt", nargs="+", help="生成后的 prompt")
|
|
5683
|
+
pic.add_argument("--prompt-file", help="从文件读取生成后的 prompt")
|
|
5684
|
+
pic.add_argument("--spec", help="可选 spec JSON 或 @path,用于检查 required_text")
|
|
5685
|
+
pic.add_argument("--json", action="store_true")
|
|
5686
|
+
pic.set_defaults(func=cmd_intent_check)
|
|
5687
|
+
|
|
3058
5688
|
pb = sub.add_parser("benchmark", help="批量跑 golden cases,检查转化稳定性和 lint")
|
|
3059
5689
|
pb.add_argument("cases", help="JSONL 或 JSON cases 文件")
|
|
3060
5690
|
pb.add_argument("--runs", type=int, default=3, help="每个 case 重复转化次数,用于检查确定性")
|