Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c837d1ba4 | ||
|
|
60e84c559c | ||
|
|
d3e3da791d | ||
|
|
d88930b07a | ||
|
|
d52dfdd16b |
@@ -0,0 +1,24 @@
|
||||
name: ci
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Version sync
|
||||
run: python tools/version_sync.py check
|
||||
- name: Validate styles
|
||||
run: python tools/validate_styles.py
|
||||
- name: Audit styles
|
||||
run: python tools/audit_styles.py
|
||||
- name: Runtime regression tests
|
||||
run: python -m unittest discover -s tests
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.2.0"
|
||||
".": "0.3.2"
|
||||
}
|
||||
|
||||
@@ -6,6 +6,30 @@ The format is based on Keep a Changelog, and this project follows Semantic Versi
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.3.2] - 2026-03-07
|
||||
|
||||
### Added
|
||||
|
||||
- CI checks for version sync, style validation, style audit, and runtime regression tests.
|
||||
- `tools/sync_legacy_styles.py` and `tools/version_sync.py` for compatibility snapshot and release metadata maintenance.
|
||||
- New medium-first style packs: `styles/packs/32_print_reprographic.json`, `styles/packs/42_decorative_arts_material_processes.json`, and `styles/packs/43_digital_craft.json`.
|
||||
- Expanded `styles/packs/36_mixed_media.json` to deepen the mixed-media family with 18 new tactile/process-driven styles.
|
||||
- `STYLE_TAXONOMY.md` documenting pack purpose, target size, and routing rules for future additions.
|
||||
- Runtime regression coverage for legacy prompt composition and conditioning validation.
|
||||
|
||||
### Changed
|
||||
|
||||
- `PromptStyler: Prompt -> Conditioning (Style Picker)` now uses the narrow legacy-compatible prompt-to-conditioning contract again for stability.
|
||||
- Broken or non-finite conditioning now raises an explicit node error instead of silently returning a malformed output.
|
||||
- `styles/styles_v1.json` is now treated as a generated compatibility snapshot of merged packs.
|
||||
- README and maintainer documentation now describe the restored stable-node runtime surface and release workflow.
|
||||
|
||||
## [0.3.1] - 2026-01-31
|
||||
|
||||
### Added
|
||||
|
||||
- New Fine Art artist-reference pack: `styles/packs/34_fine_art_artists.json` (25 styles under `Fine Art/Artists`).
|
||||
|
||||
## [0.3.0] - 2026-01-31
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,44 +1,26 @@
|
||||
# ComfyUI PromptStyler
|
||||
|
||||
PromptStyler is a **custom ComfyUI node** that helps you keep your prompts consistent by applying a selected “style template” to your **positive prompt**, then outputting **CONDITIONING** for KSampler.
|
||||
PromptStyler is a custom ComfyUI node pack for applying curated style templates to a positive prompt and returning `CONDITIONING` for KSampler.
|
||||
|
||||
If you are new to ComfyUI: you can think of CONDITIONING as the “encoded text prompt” that the sampler uses. PromptStyler builds a *styled prompt string*, encodes it with the model’s text encoder (CLIP), and passes the result to KSampler.
|
||||
This release intentionally splits the runtime surface into a stable default node and an opt-in advanced node.
|
||||
|
||||
## What This Node Does (Exact Behavior)
|
||||
## Nodes
|
||||
|
||||
Node name in ComfyUI:
|
||||
Stable node:
|
||||
|
||||
- `PromptStyler: Prompt -> Conditioning (Style Picker)`
|
||||
- outputs: `positive`, `styled_prompt`
|
||||
- behavior: legacy-compatible prompt composition and direct conditioning output
|
||||
|
||||
Step-by-step:
|
||||
Advanced node:
|
||||
|
||||
1. You type a normal prompt in `prompt` (your subject + what you want).
|
||||
2. You pick one style from the dropdown (the node loads style templates from JSON packs).
|
||||
3. If `apply_style` is enabled:
|
||||
- It builds a new prompt: `style.prefix + your prompt + style.suffix`
|
||||
- For `template_variant=default`, it merges comma-separated phrases and de-dupes duplicates.
|
||||
- For `template_variant=flux_2_klein`, it appends a short prose style guide after your prompt (when available).
|
||||
4. It tokenizes and encodes the final prompt using the `text_encoder` (CLIP).
|
||||
5. It outputs:
|
||||
- `positive` (CONDITIONING) for KSampler
|
||||
- `styled_prompt` (STRING) so you can see the exact final prompt
|
||||
- `PromptStyler: Advanced Prompt -> Conditioning`
|
||||
- outputs: `positive`, `styled_prompt`, `resolved_style_id`, `resolved_style_meta`
|
||||
- behavior: search, overlays, stronger filtering controls, and JSON debug metadata
|
||||
|
||||
Important: styles are written to describe **style only** (medium/process/lighting/palette/texture/composition) and to avoid injecting unrelated objects that you didn’t ask for.
|
||||
Use the stable node unless you specifically need the advanced controls.
|
||||
|
||||
## Install (Beginner Friendly)
|
||||
|
||||
1. Copy or clone this repo into:
|
||||
|
||||
```text
|
||||
ComfyUI/custom_nodes/ComfyUI_PromptStyler/
|
||||
```
|
||||
|
||||
2. Restart ComfyUI (or use **Reload custom nodes**).
|
||||
3. Confirm the node exists by searching for `PromptStyler` in the node menu.
|
||||
|
||||
## How To Use (Recommended Wiring)
|
||||
|
||||
Typical workflow:
|
||||
## Recommended Wiring
|
||||
|
||||
1. Add `CheckpointLoaderSimple`
|
||||
2. Add `PromptStyler: Prompt -> Conditioning (Style Picker)`
|
||||
@@ -47,43 +29,62 @@ Typical workflow:
|
||||
Connect:
|
||||
|
||||
- `CheckpointLoaderSimple.CLIP` -> `PromptStyler.text_encoder`
|
||||
- Your text -> `PromptStyler.prompt`
|
||||
- your text source -> `PromptStyler.prompt`
|
||||
- `PromptStyler.positive` -> `KSampler.positive`
|
||||
|
||||
Optional (debugging):
|
||||
`styled_prompt` is the exact text that was encoded.
|
||||
|
||||
- Use the `styled_prompt` output to verify that the style template is doing what you expect.
|
||||
## Stable Node Inputs
|
||||
|
||||
## Inputs / Outputs (Explained)
|
||||
- `prompt`: your positive prompt
|
||||
- `apply_style`: bypass styling while still encoding the prompt
|
||||
- `style`: dropdown entry in `Category | Name | id` format
|
||||
- `template_variant`: `default` or any model-specific variant present in the packs
|
||||
- `style_id_override`: direct id selection for newly-added or scripted styles
|
||||
- `text_encoder`: connect from your checkpoint/model loader
|
||||
|
||||
Inputs:
|
||||
Stable node rules:
|
||||
|
||||
- `prompt` (STRING): your prompt text (subject/action/attributes). Works best with comma+space phrases.
|
||||
- `apply_style` (BOOLEAN): when off, PromptStyler passes your prompt through unchanged (still outputs conditioning).
|
||||
- `style` (dropdown): pick **one** style. Dropdown entries are `Category | Name | id`.
|
||||
- `template_variant`:
|
||||
- `default`: uses comma+space “phrase tokens” (best for SD-like prompt token lists).
|
||||
- `flux_2_klein`: uses prose guidance when present (helpful for Flux-like models).
|
||||
- `style_id_override` (STRING): advanced. If set, the node uses that style `id` directly (useful right after adding a new style).
|
||||
- `text_encoder` (CLIP): wire this from your checkpoint/model loader.
|
||||
- `style_id_override` takes precedence over the dropdown when set
|
||||
- `default` uses legacy comma-phrase composition and de-dupe behavior
|
||||
- model-specific variants use their explicit variant text and otherwise fall back to `default`
|
||||
- malformed conditioning now raises an explicit node error instead of silently returning a broken output
|
||||
|
||||
Outputs:
|
||||
## Advanced Node Inputs
|
||||
|
||||
- `positive` (CONDITIONING): connect to KSampler `positive`.
|
||||
- `styled_prompt` (STRING): the final prompt string used for conditioning.
|
||||
Core inputs are the same as the stable node, plus:
|
||||
|
||||
## Style Library (Where Styles Live)
|
||||
- `selection_mode`: `dropdown`, `search`, or `id`
|
||||
- `style_search`: free-text search used by `selection_mode=search`
|
||||
- `category_hint`: optional substring filter applied before search scoring
|
||||
- `tag_hint`: optional comma-separated tag filter applied before search scoring
|
||||
- `style_strength`: `subtle`, `normal`, or `strong`
|
||||
- `dedupe_mode`: `smart` or `off`
|
||||
- `overlay_style_id`: optional secondary style id, restricted to categories starting with `Color Grade`
|
||||
- `on_missing_style`: `error` or `passthrough`
|
||||
|
||||
Styles live in JSON packs:
|
||||
Advanced node outputs:
|
||||
|
||||
- `styles/packs/*.json` (merged at runtime in filename order)
|
||||
- Legacy fallback (only used if packs are missing): `styles/styles_v1.json`
|
||||
- `resolved_style_id`: the final base style id that was resolved
|
||||
- `resolved_style_meta`: JSON payload describing resolution inputs and composition details
|
||||
|
||||
Pack file naming:
|
||||
## Style Library
|
||||
|
||||
- Numeric prefixes control merge order (example: `10_cinema.json` loads before `20_photography.json`).
|
||||
Primary runtime source:
|
||||
|
||||
Style entry schema:
|
||||
- `styles/packs/*.json`
|
||||
|
||||
Compatibility snapshot:
|
||||
|
||||
- `styles/styles_v1.json`
|
||||
|
||||
Pack filenames use numeric prefixes for merge order, for example:
|
||||
|
||||
- `10_cinema.json`
|
||||
- `96_color_grades.json`
|
||||
- `99_user_custom.json`
|
||||
|
||||
Schema:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -96,22 +97,11 @@ Style entry schema:
|
||||
}
|
||||
```
|
||||
|
||||
## Adding Your Own Styles (No Coding Required)
|
||||
## Adding Your Own Styles
|
||||
|
||||
This repo includes a helper tool that writes a custom pack file (gitignored by default):
|
||||
Default user-local pack:
|
||||
|
||||
- Default user pack: `styles/packs/99_user_custom.json`
|
||||
|
||||
Wizard (recommended):
|
||||
|
||||
```powershell
|
||||
C:\Comfyui\python_embeded\python.exe .\tools\add_styles.py wizard
|
||||
```
|
||||
|
||||
After adding a style:
|
||||
|
||||
- Copy the printed `id`
|
||||
- Paste it into `style_id_override` to use it immediately (no ComfyUI reload required)
|
||||
- `styles/packs/99_user_custom.json`
|
||||
|
||||
List categories:
|
||||
|
||||
@@ -119,65 +109,77 @@ List categories:
|
||||
C:\Comfyui\python_embeded\python.exe .\tools\add_styles.py categories
|
||||
```
|
||||
|
||||
Add a style directly:
|
||||
Launch the wizard:
|
||||
|
||||
```powershell
|
||||
C:\Comfyui\python_embeded\python.exe .\tools\add_styles.py wizard
|
||||
```
|
||||
|
||||
Add one style directly:
|
||||
|
||||
```powershell
|
||||
C:\Comfyui\python_embeded\python.exe .\tools\add_styles.py add --name "Ink Noir (Modern)" --category "Fine Art" --core "ink wash, chiaroscuro, high contrast" --details "paper texture, soft bleed, dramatic mood" --tags "ink,noir"
|
||||
```
|
||||
|
||||
## Prompting Tips (So Styles Stay Accurate)
|
||||
After adding a style, paste the generated id into `style_id_override` to use it immediately.
|
||||
|
||||
- Put **subjects/objects** in your `prompt` (the user input), not in the style template.
|
||||
- Write prompts as comma+space phrases: `", "` is the splitter used by the node’s default template mode.
|
||||
- If a style is explicitly specific (its name is a scene/subject), it may include those nouns. Otherwise templates aim to stay subject-agnostic.
|
||||
## Maintainer Commands
|
||||
|
||||
## Validate / Audit (Recommended Before Editing Packs)
|
||||
|
||||
Validate required fields and uniqueness:
|
||||
Validate packs:
|
||||
|
||||
```powershell
|
||||
C:\Comfyui\python_embeded\python.exe .\tools\validate_styles.py
|
||||
```
|
||||
|
||||
Optional consistency audit (also flags banned gear terms):
|
||||
Audit pack consistency:
|
||||
|
||||
```powershell
|
||||
C:\Comfyui\python_embeded\python.exe .\tools\audit_styles.py
|
||||
```
|
||||
|
||||
## Regenerate Curated Packs (For Maintainers)
|
||||
Sync the legacy compatibility snapshot:
|
||||
|
||||
Curated packs are generated by:
|
||||
```powershell
|
||||
C:\Comfyui\python_embeded\python.exe .\tools\sync_legacy_styles.py
|
||||
```
|
||||
|
||||
Regenerate curated packs and refresh the legacy snapshot:
|
||||
|
||||
```powershell
|
||||
C:\Comfyui\python_embeded\python.exe .\tools\generate_style_packs.py
|
||||
```
|
||||
|
||||
After regenerating packs, restart ComfyUI (or reload custom nodes) to refresh the dropdown.
|
||||
Check release/version metadata consistency:
|
||||
|
||||
```powershell
|
||||
C:\Comfyui\python_embeded\python.exe .\tools\version_sync.py check
|
||||
```
|
||||
|
||||
Run runtime regression tests:
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s tests
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Node doesn’t show up:
|
||||
- Verify the folder path is exactly `ComfyUI/custom_nodes/ComfyUI_PromptStyler/`
|
||||
- Restart ComfyUI or use **Reload custom nodes**
|
||||
- Styles missing or dropdown says “(no styles found)”:
|
||||
- Check that `styles/packs/*.json` exists and is valid JSON
|
||||
- Run `.\tools\validate_styles.py`
|
||||
- A style feels like it “adds extra objects”:
|
||||
- Try `template_variant=default` first (token style)
|
||||
- Keep your subject strongly stated in the user `prompt`
|
||||
- If you find a template adding unwanted nouns, it should be fixed in the pack (PRs welcome)
|
||||
- Stable node returns an error instead of an image:
|
||||
- inspect `styled_prompt`
|
||||
- confirm the connected `text_encoder` matches the active model family
|
||||
- if the error mentions invalid conditioning, compare the same `styled_prompt` through stock `CLIPTextEncode`
|
||||
- Advanced search fails unexpectedly:
|
||||
- relax `category_hint` and `tag_hint`
|
||||
- inspect `resolved_style_meta`
|
||||
- Styles look stale:
|
||||
- rerun `tools/sync_legacy_styles.py` or `tools/generate_style_packs.py`
|
||||
- reload custom nodes in ComfyUI
|
||||
|
||||
## Versioning / GitHub Releases (Preparing v0.3.0)
|
||||
## Release Notes
|
||||
|
||||
Source of truth:
|
||||
Version metadata is managed from `VERSION`.
|
||||
|
||||
- Version: `VERSION` (mirrored in `__init__.py` as `__version__`)
|
||||
- Changelog: `CHANGELOG.md` (Keep a Changelog)
|
||||
- `__init__.py` reads `VERSION` at runtime
|
||||
- `.release-please-manifest.json` should be synced from `VERSION` with `tools/version_sync.py write`
|
||||
- `CHANGELOG.md` follows Keep a Changelog
|
||||
|
||||
Suggested release checklist:
|
||||
|
||||
1. Run `.\tools\validate_styles.py` and `.\tools\audit_styles.py` (include output in your PR).
|
||||
2. Ensure `README.md` describes new behavior/inputs.
|
||||
3. Ensure `CHANGELOG.md` has the release notes under the new version.
|
||||
4. Push to GitHub and let Release Please create the release PR (see `RELEASING.md`).
|
||||
See `RELEASING.md` for the maintainer flow.
|
||||
+39
-14
@@ -1,25 +1,50 @@
|
||||
# Releasing
|
||||
|
||||
This repo uses **Semantic Versioning** and **Release Please** to keep releases organized.
|
||||
This repo uses Semantic Versioning and Release Please.
|
||||
|
||||
## Normal Release Flow (recommended)
|
||||
## Source Of Truth
|
||||
|
||||
1. Merge changes into `main`.
|
||||
2. The `release-please` workflow opens/updates a **Release PR** that:
|
||||
- bumps the version
|
||||
- updates `CHANGELOG.md`
|
||||
3. Merge the Release PR.
|
||||
4. Release Please creates a Git tag (e.g. `v0.2.0`) and a GitHub Release.
|
||||
Human-edited version source:
|
||||
|
||||
## Manual Release (if automation is disabled)
|
||||
- `VERSION`
|
||||
|
||||
1. Update `__init__.py` `__version__` and `VERSION` to the new version.
|
||||
2. Add notes under `CHANGELOG.md` and move them into a new version section.
|
||||
3. Commit with a message like: `chore(release): vX.Y.Z`
|
||||
4. Tag and push:
|
||||
Mirrors and checks:
|
||||
|
||||
- `__init__.py` reads `VERSION` at runtime
|
||||
- `.release-please-manifest.json` is synced from `VERSION` with `python tools/version_sync.py write`
|
||||
- CI verifies version alignment with `python tools/version_sync.py check`
|
||||
|
||||
## Normal Maintainer Flow
|
||||
|
||||
1. Update `VERSION`.
|
||||
2. Run `python tools/version_sync.py write`.
|
||||
3. Run `python tools/validate_styles.py`.
|
||||
4. Run `python tools/audit_styles.py`.
|
||||
5. If packs changed, run `python tools/sync_legacy_styles.py` or `python tools/generate_style_packs.py`.
|
||||
6. Update `CHANGELOG.md` and any user-facing docs that changed behavior.
|
||||
7. Merge to `main`.
|
||||
8. Release Please updates or opens the release PR.
|
||||
9. Merge the release PR.
|
||||
|
||||
## CI
|
||||
|
||||
The repository CI job runs on pushes to `main` and on pull requests. It checks:
|
||||
|
||||
- version sync
|
||||
- style validation
|
||||
- style audit
|
||||
|
||||
## Manual Release
|
||||
|
||||
If automation is disabled:
|
||||
|
||||
1. Update `VERSION`.
|
||||
2. Run `python tools/version_sync.py write`.
|
||||
3. Update `CHANGELOG.md`.
|
||||
4. Commit the release metadata.
|
||||
5. Tag the commit:
|
||||
|
||||
```bash
|
||||
git tag vX.Y.Z
|
||||
git push --tags
|
||||
```
|
||||
|
||||
|
||||
+11
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
## Current Library Snapshot (as of 2026-01-31)
|
||||
|
||||
- Total styles: 500 (merged from `styles/packs/*.json` in filename order).
|
||||
- Total styles: 525 (merged from `styles/packs/*.json` in filename order).
|
||||
- Categories are consistent and already cover the big buckets: Photography, Cinema, Illustration, Fine Art, Graphic Design, 3D/CG, Architecture/Interior, Fashion/Product, Nature, Experimental, Color Grade, plus focused sub-buckets (e.g. `Photography/Alt Process`, `Decorative Arts/Textiles`, `Color Grade/Film Lab`).
|
||||
- Style format is stable: each style has `id`, `name`, `category`, and `default.prefix`/`default.suffix` (comma+space separated phrases). The node de-dupes phrases case-insensitively.
|
||||
|
||||
@@ -40,6 +40,16 @@ When tightening a technique or movement, prefer authoritative glossaries and mus
|
||||
- Cyanotype process characteristics: National Gallery of Art (USA) and similar museum education references.
|
||||
- Film noir traits (low-key lighting, high-contrast look, mood conventions): established film history references (e.g., Britannica / major film institutes).
|
||||
|
||||
## New Additions: Fine Art/Artists Pack
|
||||
|
||||
Added `styles/packs/34_fine_art_artists.json` with 25 artist-referenced styles under `Fine Art/Artists`. Each style is written to stay **subject-agnostic** (palette, brushwork, surface, composition, light) and avoid photo/cinema gear terms.
|
||||
|
||||
Research references used while drafting these templates (high quality / museum / encyclopedia sources):
|
||||
|
||||
- Encyclopaedia Britannica (artist biographies + movement/technique definitions): https://www.britannica.com/
|
||||
- The Metropolitan Museum of Art (movement essays and collection learning resources): https://www.metmuseum.org/
|
||||
- The Museum of Modern Art (artist/movement resources, e.g. action painting / Pollock): https://www.moma.org/
|
||||
|
||||
## New Additions: 500-style Expansion Packs
|
||||
|
||||
Added four focused packs (25 styles each) to reach 500 total styles:
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Style Taxonomy
|
||||
|
||||
This document defines how curated packs should be organized and expanded.
|
||||
|
||||
## Pack Policy
|
||||
|
||||
- Default organization is medium/process/material first.
|
||||
- Target size per pack is 15-25 styles.
|
||||
- Split a pack when it grows past about 30 styles or starts mixing unrelated subfamilies.
|
||||
- Preserve existing style ids and visible category labels unless a migration is explicitly planned.
|
||||
- Extend an existing pack when the new styles share the same medium or production logic.
|
||||
- Create a new pack only when a coherent process family would otherwise become a grab bag inside an unrelated pack.
|
||||
|
||||
## Current Curated Packs
|
||||
|
||||
| Pack | Purpose | Categories Covered | Target Range |
|
||||
| --- | --- | --- | --- |
|
||||
| `10_cinema.json` | Film still, cinematography, and cinematic look packages | `Cinema` | 15-25 |
|
||||
| `20_photography.json` | General photographic genres, techniques, and perspective looks | `Photography` | 25-35 |
|
||||
| `21_photography_alt_process.json` | Darkroom and historical photographic chemistry/process looks | `Photography/Alt Process` | 15-25 |
|
||||
| `30_illustration.json` | General hand-drawn, painted, manga, vector, pixel, and technical illustration styles | `Illustration`, `Anime/Manga`, `Pixel Art`, `Vector`, `Technical` | 25-35 |
|
||||
| `31_printmaking_expanded.json` | Historical and fine-art printmaking processes | `Printmaking/Expanded` | 20-30 |
|
||||
| `32_print_reprographic.json` | Mechanical duplication, proofing, repro, and press-surface looks | `Printmaking/Expanded`, `Photography/Alt Process`, `Graphic Design` | 14-22 |
|
||||
| `34_fine_art_artists.json` | Artist-reference fine art styles | `Fine Art/Artists` | 20-30 |
|
||||
| `35_fine_art.json` | Movement and material-led fine art styles | `Fine Art` | 25-35 |
|
||||
| `36_mixed_media.json` | Layered, tactile, composite-media processes | `Mixed Media` | 20-30 |
|
||||
| `37_street_art.json` | Street-applied paint, poster, and wall-surface styles | `Street Art` | 12-20 |
|
||||
| `38_decorative_arts.json` | Core decorative crafts and surface-rich artisan media | `Decorative Arts` | 12-20 |
|
||||
| `39_internet_aesthetics.json` | Mood-first internet aesthetic families | `Aesthetics/*` | 30-45 |
|
||||
| `40_graphic_design.json` | Posters, layouts, branding, and print-graphic looks | `Graphic Design` | 25-35 |
|
||||
| `41_decorative_arts_textiles.json` | Textile, fiber, and woven decorative processes | `Decorative Arts/Textiles` | 20-30 |
|
||||
| `42_decorative_arts_material_processes.json` | Glass, ceramic, metal, cast, etched, and inlay process styles | `Decorative Arts` | 15-25 |
|
||||
| `43_digital_craft.json` | Diagrammatic, vector, and constrained-color digital craft styles | `Technical`, `Vector`, `Pixel Art` | 12-20 |
|
||||
| `50_3d_cg.json` | 3D, render, miniature-set, and CG material looks | `3D/CG` | 15-25 |
|
||||
| `60_architecture_interior.json` | Architecture and interior presentation looks | `Architecture/Interior` | 15-25 |
|
||||
| `70_fashion.json` | Fashion/editorial looks | `Fashion` | 12-20 |
|
||||
| `80_product.json` | Product and commercial object presentation looks | `Product` | 10-18 |
|
||||
| `90_nature.json` | Landscape and nature presentation looks | `Nature` | 12-20 |
|
||||
| `95_experimental.json` | Abstract, prismatic, and process-bending visual styles | `Experimental` | 12-20 |
|
||||
| `96_color_grades.json` | General color grade overlays | `Color Grade` | 15-25 |
|
||||
| `97_color_grade_film_lab.json` | Film-lab process, scan, and stock-behavior overlays | `Color Grade/Film Lab` | 20-30 |
|
||||
|
||||
## Routing Rules For New Styles
|
||||
|
||||
- Add tactile layered work to `36_mixed_media.json` before considering any new mixed-media pack.
|
||||
- Add glass, ceramic, metal, lacquer, and inlay surfaces to the decorative arts family before using `Fine Art` as a fallback.
|
||||
- Add repro, proofing, duplication, and press artifacts to `32_print_reprographic.json` instead of scattering them across `Graphic Design` or `Experimental`.
|
||||
- Add `Technical`, `Vector`, and `Pixel Art` growth to `43_digital_craft.json` so those categories stop living as incidental outliers inside the broad illustration pack.
|
||||
- Use `39_internet_aesthetics.json` only when a style is fundamentally mood/subculture driven rather than process driven.
|
||||
- Prefer extending an existing process family over creating a new pack with fewer than about 12 coherent styles.
|
||||
@@ -0,0 +1,119 @@
|
||||
# PromptStyler Maintainer Notes
|
||||
|
||||
This document is for maintainers. It tracks architecture, pack behavior, and the rules that matter when changing runtime style resolution.
|
||||
|
||||
## Architecture
|
||||
|
||||
Runtime surface:
|
||||
|
||||
- `nodes.py`: ComfyUI node definition and input/output wiring
|
||||
- `style_library.py`: shared style loading, indexing, search, filtering, prompt composition, and legacy snapshot generation
|
||||
|
||||
Maintainer tooling:
|
||||
|
||||
- `tools/validate_styles.py`: strict schema and uniqueness validation
|
||||
- `tools/audit_styles.py`: consistency audit and quality checks
|
||||
- `tools/add_styles.py`: user/custom pack helper
|
||||
- `tools/generate_style_packs.py`: curated pack generation plus legacy snapshot refresh
|
||||
- `tools/sync_legacy_styles.py`: regenerate `styles/styles_v1.json` from merged packs
|
||||
- `tools/version_sync.py`: check or write release metadata derived from `VERSION`
|
||||
- `STYLE_TAXONOMY.md`: pack purpose, target size, and routing rules for future style additions
|
||||
|
||||
Data sources:
|
||||
|
||||
- `styles/packs/*.json`: primary runtime source
|
||||
- `styles/styles_v1.json`: generated compatibility snapshot
|
||||
|
||||
## Load Policies
|
||||
|
||||
`style_library.py` exposes two load modes:
|
||||
|
||||
- `strict`: fail on malformed files or required-field issues
|
||||
- `lenient`: warn and skip unusable files so the runtime node can stay available
|
||||
|
||||
Use `strict` in maintainer tooling and CI.
|
||||
Use `lenient` in the ComfyUI runtime node.
|
||||
|
||||
## Runtime Resolution Model
|
||||
|
||||
The node resolves one base style and optionally one overlay style.
|
||||
|
||||
Base style resolution modes:
|
||||
|
||||
- `dropdown`
|
||||
- `id`
|
||||
- `search`
|
||||
|
||||
Search ranking order:
|
||||
|
||||
1. exact id match
|
||||
2. exact name match
|
||||
3. token matches across id, name, category, and tags
|
||||
|
||||
Search filters are applied before scoring:
|
||||
|
||||
- `category_hint`
|
||||
- `tag_hint`
|
||||
|
||||
If multiple candidates tie for the best score, resolution fails with the top candidates listed.
|
||||
|
||||
## Prompt Composition Rules
|
||||
|
||||
Recommended composition model:
|
||||
|
||||
- base style
|
||||
- optional color-grade overlay
|
||||
- optional search-driven style lookup
|
||||
|
||||
Default phrase-based variant:
|
||||
|
||||
- `style_strength=subtle`: first 3 prefix phrases and first 3 suffix phrases
|
||||
- `style_strength=normal`: first 6 prefix phrases and first 6 suffix phrases
|
||||
- `style_strength=strong`: all phrases
|
||||
- `dedupe_mode=smart`: remove only style-added duplicates, never collapse the raw user prompt
|
||||
- `dedupe_mode=off`: preserve all style phrases in original order
|
||||
|
||||
Overlay rule:
|
||||
|
||||
- `overlay_style_id` must resolve to a category starting with `Color Grade`
|
||||
- the overlay contributes suffix-only styling
|
||||
|
||||
Variant rule:
|
||||
|
||||
- `template_variant=default` uses phrase-based composition
|
||||
- model-specific variants use the style's model entry when available and fall back to `default` otherwise
|
||||
|
||||
## Pack Authoring Rules
|
||||
|
||||
Required fields per style:
|
||||
|
||||
- `id`
|
||||
- `name`
|
||||
- `default.prefix`
|
||||
- `default.suffix`
|
||||
|
||||
Conventions:
|
||||
|
||||
- `id` must be unique and `snake_case`
|
||||
- phrase-based text should use comma+space separators
|
||||
- keep style text subject-agnostic unless the style itself is explicitly subject-specific
|
||||
- avoid photo/cinema gear terms in curated styles
|
||||
|
||||
## Release Metadata
|
||||
|
||||
Version metadata flow:
|
||||
|
||||
- edit `VERSION`
|
||||
- run `python tools/version_sync.py write`
|
||||
- CI enforces that `VERSION`, `__init__.py`, and `.release-please-manifest.json` are aligned
|
||||
|
||||
## Maintenance Checklist
|
||||
|
||||
When changing curated style packs:
|
||||
|
||||
1. regenerate or update the packs
|
||||
2. run `python tools/validate_styles.py`
|
||||
3. run `python tools/audit_styles.py`
|
||||
4. run `python tools/sync_legacy_styles.py` if needed
|
||||
5. update `README.md` when user-visible behavior changed
|
||||
6. update `CHANGELOG.md`
|
||||
+12
-2
@@ -5,9 +5,19 @@ Drop this folder into: ComfyUI/custom_nodes/ComfyUI_PromptStyler
|
||||
Then restart ComfyUI (or use "Reload custom nodes").
|
||||
"""
|
||||
|
||||
__version__ = "0.3.0"
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
|
||||
def _read_version() -> str:
|
||||
here = os.path.dirname(os.path.realpath(__file__))
|
||||
with open(os.path.join(here, "VERSION"), "r", encoding="utf-8") as handle:
|
||||
return handle.read().strip()
|
||||
|
||||
|
||||
__version__ = _read_version()
|
||||
|
||||
from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "__version__"]
|
||||
|
||||
|
||||
@@ -277,4 +277,4 @@ NODE_CLASS_MAPPINGS = {
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"PromptStylerConditioning": "PromptStyler: Prompt -> Conditioning (Style Picker)",
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,859 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple
|
||||
|
||||
|
||||
ROOT = os.path.dirname(os.path.realpath(__file__))
|
||||
LEGACY_STYLES_PATH = os.path.join(ROOT, "styles", "styles_v1.json")
|
||||
STYLE_PACKS_DIR = os.path.join(ROOT, "styles", "packs")
|
||||
|
||||
LOAD_POLICY_LENIENT = "lenient"
|
||||
LOAD_POLICY_STRICT = "strict"
|
||||
DEFAULT_VARIANT = "default"
|
||||
DEFAULT_PHRASE_SEPARATOR = ", "
|
||||
NO_STYLES_CHOICE = "(no styles found) | (no styles) | __none__"
|
||||
Z_SEP = ", "
|
||||
|
||||
# Shared prompt fragments reused by the generator and user-style tooling.
|
||||
CATEGORY_BASE_PREFIX = {
|
||||
"Cinema": (
|
||||
"cinematic film still",
|
||||
"professional cinematography",
|
||||
"cinematic framing",
|
||||
"carefully composed shot",
|
||||
"atmospheric depth",
|
||||
"realistic lighting",
|
||||
),
|
||||
"Photography": (
|
||||
"professional photography",
|
||||
"realistic lighting",
|
||||
"natural color rendition",
|
||||
"coherent shadows",
|
||||
"true-to-life textures",
|
||||
),
|
||||
"Product": (
|
||||
"commercial product photography",
|
||||
"controlled studio lighting",
|
||||
"minimal background",
|
||||
"controlled reflections",
|
||||
"clean composition",
|
||||
),
|
||||
"Fashion": (
|
||||
"fashion editorial photography",
|
||||
"magazine look",
|
||||
"high-end styling",
|
||||
"clean composition",
|
||||
"stylized studio lighting",
|
||||
),
|
||||
"Architecture/Interior": (
|
||||
"architectural photography",
|
||||
"straight verticals",
|
||||
"clean geometry",
|
||||
"wide field of view",
|
||||
"realistic materials",
|
||||
"balanced natural light",
|
||||
),
|
||||
"Nature": (
|
||||
"nature photography",
|
||||
"natural light",
|
||||
"atmospheric depth",
|
||||
"fine detail",
|
||||
"realistic textures",
|
||||
"environmental realism",
|
||||
),
|
||||
"3D/CG": (
|
||||
"3d render",
|
||||
"high quality render",
|
||||
"clean geometry",
|
||||
"global illumination",
|
||||
"studio lighting",
|
||||
"soft shadows",
|
||||
),
|
||||
"Graphic Design": (
|
||||
"graphic design",
|
||||
"poster-like composition",
|
||||
"clear hierarchy",
|
||||
"bold shapes",
|
||||
"limited palette",
|
||||
"print-ready layout",
|
||||
),
|
||||
"Illustration": (
|
||||
"detailed illustration",
|
||||
"cohesive style",
|
||||
"readable shapes",
|
||||
"intentional composition",
|
||||
"handcrafted look",
|
||||
),
|
||||
"Anime/Manga": (
|
||||
"anime/manga illustration",
|
||||
"clean linework",
|
||||
"cel shading",
|
||||
"expressive character design",
|
||||
"vibrant palette",
|
||||
),
|
||||
"Pixel Art": ("pixel art", "16-bit", "limited palette", "pixel-perfect"),
|
||||
"Vector": ("vector illustration", "flat colors", "clean shapes", "crisp edges", "smooth curves"),
|
||||
"Technical": ("technical illustration", "clean lines", "diagrammatic layout", "precise geometry", "labels and callouts"),
|
||||
"Printmaking": ("printmaking", "ink on paper", "handcrafted texture", "carved lines", "high contrast"),
|
||||
"Experimental": ("experimental aesthetic", "bold composition", "graphic lighting", "textural detail"),
|
||||
"Color Grade": ("intentional color grade", "filmic tone mapping"),
|
||||
"Fine Art": ("fine art", "museum-quality", "traditional techniques", "gallery piece", "handcrafted"),
|
||||
"Mixed Media": ("mixed media artwork", "layered textures", "handcrafted marks", "tactile surface"),
|
||||
"Street Art": ("street art mural", "spray paint texture", "urban wall", "bold graphic shapes"),
|
||||
}
|
||||
|
||||
CATEGORY_BASE_SUFFIX = {
|
||||
"Cinema": (
|
||||
"filmic color science",
|
||||
"smooth highlight rolloff",
|
||||
"deep blacks",
|
||||
"subtle film grain",
|
||||
"soft halation",
|
||||
"high dynamic range",
|
||||
),
|
||||
"Photography": (
|
||||
"sharp focus",
|
||||
"high detail",
|
||||
"balanced exposure",
|
||||
"fine texture detail",
|
||||
"natural contrast",
|
||||
"clean highlights",
|
||||
),
|
||||
"Product": (
|
||||
"crisp edges",
|
||||
"clean specular highlights",
|
||||
"premium look",
|
||||
"high detail",
|
||||
"minimal clutter",
|
||||
),
|
||||
"Fashion": (
|
||||
"editorial color grade",
|
||||
"sharp focus",
|
||||
"skin texture",
|
||||
"catchlights",
|
||||
"high detail",
|
||||
),
|
||||
"Architecture/Interior": (
|
||||
"crisp lines",
|
||||
"balanced exposure",
|
||||
"clean shadows",
|
||||
"high detail",
|
||||
"material realism",
|
||||
),
|
||||
"Nature": (
|
||||
"natural color",
|
||||
"clean contrast",
|
||||
"high detail",
|
||||
"fine microtexture",
|
||||
"realistic atmosphere",
|
||||
),
|
||||
"3D/CG": (
|
||||
"clean render",
|
||||
"high detail",
|
||||
"sharp edges",
|
||||
"high resolution",
|
||||
"clean shading",
|
||||
),
|
||||
"Graphic Design": (
|
||||
"crisp edges",
|
||||
"high contrast",
|
||||
"clean alignment",
|
||||
"balanced negative space",
|
||||
"print texture",
|
||||
),
|
||||
"Illustration": ("cohesive palette", "clean composition", "high detail", "believable texture"),
|
||||
"Anime/Manga": ("high clarity", "crisp edges", "clean gradients", "sharp eyes"),
|
||||
"Pixel Art": ("crisp pixels", "dithered shading", "readable silhouette", "sprite-like clarity"),
|
||||
"Vector": ("clean layout", "consistent stroke weight", "crisp edges", "smooth curves"),
|
||||
"Technical": ("high legibility", "crisp lines", "precise layout", "flat lighting"),
|
||||
"Printmaking": ("paper texture", "ink texture", "rough edges", "high contrast"),
|
||||
"Experimental": ("intentional design", "high contrast", "tactile texture", "coherent palette"),
|
||||
"Color Grade": ("smooth highlight rolloff", "clean shadows", "color separation", "balanced contrast"),
|
||||
"Fine Art": ("high detail", "handcrafted surface texture", "gallery lighting", "balanced composition"),
|
||||
"Mixed Media": ("high detail", "tactile texture", "paper grain", "layered depth", "cohesive composition"),
|
||||
"Street Art": ("bold color", "high contrast", "wall texture", "urban grit", "spray paint overspray"),
|
||||
}
|
||||
|
||||
|
||||
class StyleLibraryError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class StyleTemplate:
|
||||
id: str
|
||||
name: str
|
||||
category: str
|
||||
prefix: str
|
||||
suffix: str
|
||||
tags: Tuple[str, ...]
|
||||
variants: Dict[str, Tuple[str, str]]
|
||||
|
||||
def variant_prompt(self, variant: str) -> Tuple[str, str, str]:
|
||||
if variant == DEFAULT_VARIANT:
|
||||
return DEFAULT_VARIANT, self.prefix, self.suffix
|
||||
|
||||
prefix, suffix = self.variants.get(variant, ("", ""))
|
||||
if prefix.strip() or suffix.strip():
|
||||
return variant, prefix, suffix
|
||||
|
||||
return DEFAULT_VARIANT, self.prefix, self.suffix
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class StyleLibrary:
|
||||
styles: Tuple[StyleTemplate, ...]
|
||||
by_id: Dict[str, StyleTemplate]
|
||||
choices: List[str]
|
||||
variants: Tuple[str, ...]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SearchCandidate:
|
||||
style: StyleTemplate
|
||||
score: int
|
||||
matched_fields: Tuple[str, ...]
|
||||
|
||||
|
||||
_CACHE_SIGS: Dict[str, Tuple[Tuple[str, float, int], ...]] = {}
|
||||
_CACHE_LIBRARIES: Dict[str, StyleLibrary] = {}
|
||||
|
||||
|
||||
def norm_space(value: str) -> str:
|
||||
return " ".join((value or "").replace("\r", " ").replace("\n", " ").split())
|
||||
|
||||
|
||||
def split_phrases(value: str, sep: str = DEFAULT_PHRASE_SEPARATOR) -> List[str]:
|
||||
value = norm_space(value)
|
||||
if not value:
|
||||
return []
|
||||
return [part for part in (p.strip() for p in value.split(sep)) if part]
|
||||
|
||||
|
||||
def dedupe_phrases(phrases: Sequence[str]) -> List[str]:
|
||||
seen = set()
|
||||
out: List[str] = []
|
||||
for phrase in phrases:
|
||||
key = phrase.casefold()
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
out.append(phrase)
|
||||
return out
|
||||
|
||||
|
||||
def z_join(parts: Sequence[str]) -> str:
|
||||
return Z_SEP.join(dedupe_phrases([p.strip() for p in parts if (p or "").strip()]))
|
||||
|
||||
|
||||
def flux_join_sentences(parts: Sequence[str]) -> str:
|
||||
parts2 = [p.strip().rstrip(".") for p in parts if (p or "").strip()]
|
||||
return ". ".join(parts2).strip() + ("." if parts2 else "")
|
||||
|
||||
|
||||
def style_to_dict(style: StyleTemplate) -> Dict[str, Any]:
|
||||
models = {
|
||||
name: {"prefix": prefix, "suffix": suffix}
|
||||
for name, (prefix, suffix) in sorted(style.variants.items())
|
||||
if prefix or suffix
|
||||
}
|
||||
return {
|
||||
"id": style.id,
|
||||
"name": style.name,
|
||||
"category": style.category,
|
||||
"default": {"prefix": style.prefix, "suffix": style.suffix},
|
||||
"models": models,
|
||||
"tags": list(style.tags),
|
||||
}
|
||||
|
||||
|
||||
def _pack_sort_key(name: str) -> Tuple[int, str]:
|
||||
match = re.match(r"^(\d+)", name)
|
||||
if not match:
|
||||
return (9999, name.casefold())
|
||||
return (int(match.group(1)), name.casefold())
|
||||
|
||||
|
||||
def iter_pack_paths() -> List[str]:
|
||||
if not os.path.isdir(STYLE_PACKS_DIR):
|
||||
return []
|
||||
out: List[str] = []
|
||||
for name in sorted(os.listdir(STYLE_PACKS_DIR), key=_pack_sort_key):
|
||||
if name.lower().endswith(".json"):
|
||||
out.append(os.path.join(STYLE_PACKS_DIR, name))
|
||||
return out
|
||||
|
||||
|
||||
def _file_sig(path: str) -> Tuple[str, float, int]:
|
||||
try:
|
||||
st = os.stat(path)
|
||||
return (path, float(st.st_mtime), int(st.st_size))
|
||||
except OSError:
|
||||
return (path, -1.0, -1)
|
||||
|
||||
|
||||
def style_sources_sig(legacy_path: str = LEGACY_STYLES_PATH) -> Tuple[Tuple[str, float, int], ...]:
|
||||
pack_paths = iter_pack_paths()
|
||||
sources = (pack_paths + [legacy_path]) if pack_paths else [legacy_path]
|
||||
return tuple(_file_sig(path) for path in sources)
|
||||
|
||||
|
||||
def _emit_problem(load_policy: str, message: str) -> None:
|
||||
if load_policy == LOAD_POLICY_STRICT:
|
||||
raise StyleLibraryError(message)
|
||||
print(f"PromptStyler: WARN: {message}")
|
||||
|
||||
|
||||
def _read_json(path: str, load_policy: str) -> Optional[Dict[str, Any]]:
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
data = json.load(handle) or {}
|
||||
except Exception as exc:
|
||||
_emit_problem(load_policy, f"unable to read style pack: {path} ({exc})")
|
||||
return None
|
||||
|
||||
if not isinstance(data, dict):
|
||||
_emit_problem(load_policy, f"style file must be a JSON object: {path}")
|
||||
return None
|
||||
return data
|
||||
|
||||
|
||||
def _iter_raw_style_entries(path: str, load_policy: str) -> List[Dict[str, Any]]:
|
||||
data = _read_json(path, load_policy)
|
||||
if data is None:
|
||||
return []
|
||||
styles = data.get("styles", [])
|
||||
if not isinstance(styles, list):
|
||||
_emit_problem(load_policy, f"'styles' must be a list in: {path}")
|
||||
return []
|
||||
return [item for item in styles if isinstance(item, dict)]
|
||||
|
||||
|
||||
def _normalize_variants(raw_models: Any, *, path: str, index: int, load_policy: str) -> Dict[str, Tuple[str, str]]:
|
||||
if raw_models is None:
|
||||
return {}
|
||||
if not isinstance(raw_models, dict):
|
||||
_emit_problem(load_policy, f"styles[{index}] models must be an object in: {path}")
|
||||
return {}
|
||||
|
||||
out: Dict[str, Tuple[str, str]] = {}
|
||||
for key, raw_value in raw_models.items():
|
||||
if not isinstance(key, str) or not key.strip():
|
||||
_emit_problem(load_policy, f"styles[{index}] has a blank model key in: {path}")
|
||||
continue
|
||||
if not isinstance(raw_value, dict):
|
||||
_emit_problem(load_policy, f"styles[{index}] model '{key}' must be an object in: {path}")
|
||||
continue
|
||||
prefix = str(raw_value.get("prefix", "") or "")
|
||||
suffix = str(raw_value.get("suffix", "") or "")
|
||||
out[key] = (prefix, suffix)
|
||||
return out
|
||||
|
||||
|
||||
def _normalize_style(raw: Dict[str, Any], *, path: str, index: int, load_policy: str) -> Optional[StyleTemplate]:
|
||||
sid = raw.get("id")
|
||||
name = raw.get("name")
|
||||
if sid is None or name is None:
|
||||
_emit_problem(load_policy, f"styles[{index}] missing id or name in: {path}")
|
||||
return None
|
||||
|
||||
default = raw.get("default", {})
|
||||
if not isinstance(default, dict):
|
||||
_emit_problem(load_policy, f"styles[{index}] default must be an object in: {path}")
|
||||
return None
|
||||
if "prefix" not in default or "suffix" not in default:
|
||||
_emit_problem(load_policy, f"styles[{index}] default must include prefix and suffix in: {path}")
|
||||
return None
|
||||
|
||||
tags_raw = raw.get("tags", []) or []
|
||||
if not isinstance(tags_raw, list):
|
||||
_emit_problem(load_policy, f"styles[{index}] tags must be a list in: {path}")
|
||||
tags_raw = []
|
||||
|
||||
variants = _normalize_variants(raw.get("models", {}), path=path, index=index, load_policy=load_policy)
|
||||
return StyleTemplate(
|
||||
id=str(sid),
|
||||
name=str(name),
|
||||
category=str(raw.get("category", "Uncategorized")),
|
||||
prefix=str(default.get("prefix", "") or ""),
|
||||
suffix=str(default.get("suffix", "") or ""),
|
||||
tags=tuple(str(tag).strip() for tag in tags_raw if str(tag).strip()),
|
||||
variants=variants,
|
||||
)
|
||||
|
||||
|
||||
def _normalize_styles(raw_styles: Sequence[Dict[str, Any]], *, path: str, load_policy: str) -> List[StyleTemplate]:
|
||||
out: List[StyleTemplate] = []
|
||||
seen_ids = set()
|
||||
seen_names = set()
|
||||
for index, raw in enumerate(raw_styles):
|
||||
style = _normalize_style(raw, path=path, index=index, load_policy=load_policy)
|
||||
if style is None:
|
||||
continue
|
||||
if style.id in seen_ids:
|
||||
_emit_problem(load_policy, f"duplicate id '{style.id}' in: {path}")
|
||||
continue
|
||||
if style.name in seen_names:
|
||||
_emit_problem(load_policy, f"duplicate name '{style.name}' in: {path}")
|
||||
continue
|
||||
seen_ids.add(style.id)
|
||||
seen_names.add(style.name)
|
||||
out.append(style)
|
||||
return out
|
||||
|
||||
|
||||
def load_style_library(load_policy: str = LOAD_POLICY_LENIENT, legacy_path: str = LEGACY_STYLES_PATH) -> StyleLibrary:
|
||||
if load_policy not in (LOAD_POLICY_LENIENT, LOAD_POLICY_STRICT):
|
||||
raise ValueError(f"Unsupported load_policy: {load_policy}")
|
||||
|
||||
pack_paths = iter_pack_paths()
|
||||
styles: List[StyleTemplate] = []
|
||||
by_id: Dict[str, StyleTemplate] = {}
|
||||
variants = {DEFAULT_VARIANT}
|
||||
|
||||
if not pack_paths:
|
||||
raw_styles = _iter_raw_style_entries(legacy_path, load_policy)
|
||||
styles = _normalize_styles(raw_styles, path=legacy_path, load_policy=load_policy)
|
||||
else:
|
||||
for path in pack_paths:
|
||||
raw_styles = _iter_raw_style_entries(path, load_policy)
|
||||
normalized = _normalize_styles(raw_styles, path=path, load_policy=load_policy)
|
||||
for style in normalized:
|
||||
if style.id in by_id:
|
||||
_emit_problem(load_policy, f"duplicate id '{style.id}' across packs")
|
||||
continue
|
||||
styles.append(style)
|
||||
by_id[style.id] = style
|
||||
variants.update(style.variants.keys())
|
||||
|
||||
if not styles:
|
||||
if load_policy == LOAD_POLICY_STRICT:
|
||||
raise StyleLibraryError("no usable style packs found")
|
||||
raw_styles = _iter_raw_style_entries(legacy_path, load_policy)
|
||||
styles = _normalize_styles(raw_styles, path=legacy_path, load_policy=load_policy)
|
||||
|
||||
if not by_id:
|
||||
for style in styles:
|
||||
if style.id not in by_id:
|
||||
by_id[style.id] = style
|
||||
variants.update(style.variants.keys())
|
||||
|
||||
choices = choices_for_styles(styles) if styles else [NO_STYLES_CHOICE]
|
||||
return StyleLibrary(
|
||||
styles=tuple(styles),
|
||||
by_id=by_id,
|
||||
choices=choices,
|
||||
variants=tuple([DEFAULT_VARIANT] + sorted(v for v in variants if v != DEFAULT_VARIANT)),
|
||||
)
|
||||
|
||||
|
||||
def get_cached_style_library(load_policy: str = LOAD_POLICY_LENIENT, legacy_path: str = LEGACY_STYLES_PATH) -> StyleLibrary:
|
||||
sig = style_sources_sig(legacy_path)
|
||||
if _CACHE_SIGS.get(load_policy) == sig:
|
||||
return _CACHE_LIBRARIES[load_policy]
|
||||
library = load_style_library(load_policy=load_policy, legacy_path=legacy_path)
|
||||
_CACHE_SIGS[load_policy] = sig
|
||||
_CACHE_LIBRARIES[load_policy] = library
|
||||
return library
|
||||
|
||||
|
||||
def choices_for_styles(styles: Sequence[StyleTemplate]) -> List[str]:
|
||||
styles_sorted = sorted(styles, key=lambda style: (style.category.casefold(), style.name.casefold(), style.id))
|
||||
return [f"{style.category} | {style.name} | {style.id}" for style in styles_sorted]
|
||||
|
||||
|
||||
def style_by_choice(styles: Sequence[StyleTemplate], choice: str) -> Optional[StyleTemplate]:
|
||||
if not choice:
|
||||
return None
|
||||
sid = choice.rsplit("|", 1)[-1].strip()
|
||||
for style in styles:
|
||||
if style.id == sid:
|
||||
return style
|
||||
return None
|
||||
|
||||
|
||||
def resolve_style_legacy(
|
||||
library: StyleLibrary,
|
||||
*,
|
||||
choice: str,
|
||||
style_id_override: str,
|
||||
) -> StyleTemplate:
|
||||
style_id_override = (style_id_override or "").strip()
|
||||
if style_id_override:
|
||||
chosen = library.by_id.get(style_id_override)
|
||||
if chosen is None:
|
||||
raise StyleLibraryError(f"Unknown style_id_override: {style_id_override}")
|
||||
return chosen
|
||||
|
||||
chosen = style_by_choice(library.styles, choice)
|
||||
if chosen is None:
|
||||
raise StyleLibraryError("No style selected.")
|
||||
return chosen
|
||||
|
||||
|
||||
def parse_tag_hint(value: str) -> Tuple[str, ...]:
|
||||
if not value:
|
||||
return ()
|
||||
return tuple(part.strip().casefold() for part in value.split(",") if part.strip())
|
||||
|
||||
|
||||
def _style_matches_filters(style: StyleTemplate, *, category_hint: str, tags: Sequence[str]) -> bool:
|
||||
if category_hint and category_hint.casefold() not in style.category.casefold():
|
||||
return False
|
||||
if tags:
|
||||
style_tags = {tag.casefold() for tag in style.tags}
|
||||
if any(tag not in style_tags for tag in tags):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def filter_styles(
|
||||
styles: Sequence[StyleTemplate],
|
||||
*,
|
||||
category_hint: str = "",
|
||||
tag_hint: str = "",
|
||||
) -> List[StyleTemplate]:
|
||||
category_hint = category_hint.strip()
|
||||
tags = parse_tag_hint(tag_hint)
|
||||
return [style for style in styles if _style_matches_filters(style, category_hint=category_hint, tags=tags)]
|
||||
|
||||
|
||||
def _search_tokens(query: str) -> List[str]:
|
||||
return [token for token in re.split(r"[^a-z0-9_]+", query.casefold()) if token]
|
||||
|
||||
|
||||
def _search_candidate(style: StyleTemplate, query: str, tokens: Sequence[str]) -> Optional[SearchCandidate]:
|
||||
query_cf = query.casefold()
|
||||
if query_cf == style.id.casefold():
|
||||
return SearchCandidate(style=style, score=10000, matched_fields=("id_exact",))
|
||||
if query_cf == style.name.casefold():
|
||||
return SearchCandidate(style=style, score=9000, matched_fields=("name_exact",))
|
||||
|
||||
matched_fields: List[str] = []
|
||||
score = 0
|
||||
id_cf = style.id.casefold()
|
||||
name_cf = style.name.casefold()
|
||||
category_cf = style.category.casefold()
|
||||
tags_cf = [tag.casefold() for tag in style.tags]
|
||||
|
||||
for token in tokens:
|
||||
token_score = 0
|
||||
if token in id_cf:
|
||||
token_score += 60
|
||||
matched_fields.append("id")
|
||||
if token in name_cf:
|
||||
token_score += 40
|
||||
matched_fields.append("name")
|
||||
if token in category_cf:
|
||||
token_score += 25
|
||||
matched_fields.append("category")
|
||||
if any(token == tag for tag in tags_cf):
|
||||
token_score += 35
|
||||
matched_fields.append("tag_exact")
|
||||
elif any(token in tag for tag in tags_cf):
|
||||
token_score += 15
|
||||
matched_fields.append("tag")
|
||||
score += token_score
|
||||
|
||||
if score <= 0:
|
||||
return None
|
||||
|
||||
score += min(len(tokens), 5)
|
||||
return SearchCandidate(style=style, score=score, matched_fields=tuple(matched_fields))
|
||||
|
||||
|
||||
def search_styles(
|
||||
styles: Sequence[StyleTemplate],
|
||||
query: str,
|
||||
*,
|
||||
category_hint: str = "",
|
||||
tag_hint: str = "",
|
||||
) -> List[SearchCandidate]:
|
||||
query = query.strip()
|
||||
if not query:
|
||||
return []
|
||||
|
||||
filtered = filter_styles(styles, category_hint=category_hint, tag_hint=tag_hint)
|
||||
tokens = _search_tokens(query)
|
||||
if not tokens:
|
||||
return []
|
||||
|
||||
candidates: List[SearchCandidate] = []
|
||||
for style in filtered:
|
||||
candidate = _search_candidate(style, query, tokens)
|
||||
if candidate is not None:
|
||||
candidates.append(candidate)
|
||||
|
||||
candidates.sort(
|
||||
key=lambda item: (
|
||||
-item.score,
|
||||
item.style.category.casefold(),
|
||||
item.style.name.casefold(),
|
||||
item.style.id,
|
||||
)
|
||||
)
|
||||
return candidates
|
||||
|
||||
|
||||
def resolve_style(
|
||||
library: StyleLibrary,
|
||||
*,
|
||||
selection_mode: str,
|
||||
choice: str,
|
||||
style_id_override: str,
|
||||
style_search: str,
|
||||
category_hint: str = "",
|
||||
tag_hint: str = "",
|
||||
) -> StyleTemplate:
|
||||
style_id_override = (style_id_override or "").strip()
|
||||
category_hint = (category_hint or "").strip()
|
||||
tag_hint = (tag_hint or "").strip()
|
||||
|
||||
# Preserve old graphs: if a saved workflow already uses style_id_override and
|
||||
# the new selection mode input is left at its default, still honor the id.
|
||||
if selection_mode == "id" or (selection_mode == "dropdown" and style_id_override):
|
||||
chosen = library.by_id.get(style_id_override)
|
||||
if chosen is None:
|
||||
raise StyleLibraryError(f"Unknown style_id_override: {style_id_override}")
|
||||
return chosen
|
||||
|
||||
if selection_mode == "dropdown":
|
||||
chosen = style_by_choice(library.styles, choice)
|
||||
if chosen is None:
|
||||
raise StyleLibraryError("No style selected.")
|
||||
return chosen
|
||||
|
||||
if selection_mode != "search":
|
||||
raise StyleLibraryError(f"Unsupported selection_mode: {selection_mode}")
|
||||
|
||||
matches = search_styles(
|
||||
library.styles,
|
||||
style_search,
|
||||
category_hint=category_hint,
|
||||
tag_hint=tag_hint,
|
||||
)
|
||||
if not matches:
|
||||
raise StyleLibraryError(
|
||||
f"No styles matched search query: {style_search.strip() or '(empty query)'}"
|
||||
)
|
||||
|
||||
top = matches[0]
|
||||
tied = [item for item in matches if item.score == top.score]
|
||||
if len(tied) > 1:
|
||||
preview = "; ".join(
|
||||
f"{item.style.category} | {item.style.name} | {item.style.id}"
|
||||
for item in matches[:5]
|
||||
)
|
||||
raise StyleLibraryError(f"Ambiguous style search '{style_search}': {preview}")
|
||||
return top.style
|
||||
|
||||
|
||||
def _style_limit(style_strength: str) -> Optional[int]:
|
||||
return {
|
||||
"subtle": 3,
|
||||
"normal": 6,
|
||||
"strong": None,
|
||||
}.get(style_strength, None)
|
||||
|
||||
|
||||
def _limit_phrases(phrases: Sequence[str], style_strength: str) -> List[str]:
|
||||
limit = _style_limit(style_strength)
|
||||
if limit is None:
|
||||
return list(phrases)
|
||||
return list(phrases[:limit])
|
||||
|
||||
|
||||
def compose_default_prompt(
|
||||
prompt: str,
|
||||
style: StyleTemplate,
|
||||
*,
|
||||
style_strength: str = "strong",
|
||||
dedupe_mode: str = "smart",
|
||||
overlay_style: Optional[StyleTemplate] = None,
|
||||
) -> Tuple[str, Dict[str, Any]]:
|
||||
base_prefix = _limit_phrases(split_phrases(style.prefix), style_strength)
|
||||
base_suffix = _limit_phrases(split_phrases(style.suffix), style_strength)
|
||||
overlay_suffix = _limit_phrases(split_phrases(overlay_style.suffix), style_strength) if overlay_style else []
|
||||
|
||||
user_phrase_keys = {phrase.casefold() for phrase in split_phrases(prompt)}
|
||||
style_phrase_keys = set()
|
||||
|
||||
def include_phrase(phrase: str) -> bool:
|
||||
key = phrase.casefold()
|
||||
if dedupe_mode == "off":
|
||||
return True
|
||||
if key in style_phrase_keys:
|
||||
return False
|
||||
if key in user_phrase_keys:
|
||||
return False
|
||||
style_phrase_keys.add(key)
|
||||
return True
|
||||
|
||||
prefix_phrases = [phrase for phrase in base_prefix if include_phrase(phrase)]
|
||||
suffix_phrases = [phrase for phrase in base_suffix if include_phrase(phrase)]
|
||||
overlay_phrases = [phrase for phrase in overlay_suffix if include_phrase(phrase)]
|
||||
|
||||
parts = [
|
||||
DEFAULT_PHRASE_SEPARATOR.join(prefix_phrases),
|
||||
prompt,
|
||||
DEFAULT_PHRASE_SEPARATOR.join(suffix_phrases + overlay_phrases),
|
||||
]
|
||||
styled_prompt = DEFAULT_PHRASE_SEPARATOR.join([part for part in parts if part])
|
||||
return styled_prompt, {
|
||||
"base_prefix_phrases": prefix_phrases,
|
||||
"base_suffix_phrases": suffix_phrases,
|
||||
"overlay_suffix_phrases": overlay_phrases,
|
||||
}
|
||||
|
||||
|
||||
def compose_prompt(
|
||||
prompt: str,
|
||||
style: StyleTemplate,
|
||||
*,
|
||||
template_variant: str,
|
||||
style_strength: str = "strong",
|
||||
dedupe_mode: str = "smart",
|
||||
overlay_style: Optional[StyleTemplate] = None,
|
||||
) -> Tuple[str, Dict[str, Any]]:
|
||||
prompt = prompt or ""
|
||||
applied_variant, prefix, suffix = style.variant_prompt(template_variant)
|
||||
overlay_variant = DEFAULT_VARIANT
|
||||
overlay_suffix = ""
|
||||
|
||||
if overlay_style is not None:
|
||||
overlay_variant, _overlay_prefix, overlay_suffix = overlay_style.variant_prompt(template_variant)
|
||||
|
||||
if applied_variant == DEFAULT_VARIANT:
|
||||
styled_prompt, details = compose_default_prompt(
|
||||
prompt,
|
||||
style,
|
||||
style_strength=style_strength,
|
||||
dedupe_mode=dedupe_mode,
|
||||
overlay_style=overlay_style,
|
||||
)
|
||||
details["variant"] = applied_variant
|
||||
details["overlay_variant"] = overlay_variant if overlay_style else ""
|
||||
return styled_prompt, details
|
||||
|
||||
parts = [prompt, prefix, suffix, overlay_suffix]
|
||||
styled_prompt = norm_space(" ".join(part for part in parts if part))
|
||||
return styled_prompt, {
|
||||
"variant": applied_variant,
|
||||
"overlay_variant": overlay_variant if overlay_style else "",
|
||||
"base_prefix_phrases": [],
|
||||
"base_suffix_phrases": [],
|
||||
"overlay_suffix_phrases": [],
|
||||
}
|
||||
|
||||
|
||||
def compose_prompt_legacy(
|
||||
prompt: str,
|
||||
style: StyleTemplate,
|
||||
*,
|
||||
template_variant: str,
|
||||
) -> Tuple[str, Dict[str, Any]]:
|
||||
prompt = prompt or ""
|
||||
applied_variant, prefix, suffix = style.variant_prompt(template_variant)
|
||||
|
||||
if applied_variant != DEFAULT_VARIANT:
|
||||
styled_prompt = norm_space(" ".join(part for part in (prompt, prefix, suffix) if part))
|
||||
return styled_prompt, {
|
||||
"variant": applied_variant,
|
||||
"base_prefix_phrases": [],
|
||||
"base_suffix_phrases": [],
|
||||
}
|
||||
|
||||
prefix_phrases = split_phrases(style.prefix)
|
||||
prompt_phrases = split_phrases(prompt)
|
||||
suffix_phrases = split_phrases(style.suffix)
|
||||
phrases = dedupe_phrases(prefix_phrases + prompt_phrases + suffix_phrases)
|
||||
styled_prompt = DEFAULT_PHRASE_SEPARATOR.join(phrase for phrase in phrases if phrase)
|
||||
return styled_prompt, {
|
||||
"variant": DEFAULT_VARIANT,
|
||||
"base_prefix_phrases": prefix_phrases,
|
||||
"base_suffix_phrases": suffix_phrases,
|
||||
}
|
||||
|
||||
|
||||
def is_color_grade_style(style: StyleTemplate) -> bool:
|
||||
return style.category.startswith("Color Grade")
|
||||
|
||||
|
||||
def resolve_overlay_style(library: StyleLibrary, overlay_style_id: str) -> Optional[StyleTemplate]:
|
||||
overlay_style_id = (overlay_style_id or "").strip()
|
||||
if not overlay_style_id:
|
||||
return None
|
||||
chosen = library.by_id.get(overlay_style_id)
|
||||
if chosen is None:
|
||||
raise StyleLibraryError(f"Unknown overlay_style_id: {overlay_style_id}")
|
||||
if not is_color_grade_style(chosen):
|
||||
raise StyleLibraryError(
|
||||
f"overlay_style_id must reference a Color Grade style: {overlay_style_id}"
|
||||
)
|
||||
return chosen
|
||||
|
||||
|
||||
def make_style_meta(
|
||||
*,
|
||||
chosen_style: Optional[StyleTemplate],
|
||||
overlay_style: Optional[StyleTemplate],
|
||||
requested_variant: str,
|
||||
applied_variant: str,
|
||||
selection_mode: str,
|
||||
style_search: str,
|
||||
category_hint: str,
|
||||
tag_hint: str,
|
||||
style_strength: str,
|
||||
dedupe_mode: str,
|
||||
on_missing_style: str,
|
||||
prompt_details: Mapping[str, Any],
|
||||
error: str = "",
|
||||
) -> str:
|
||||
payload = {
|
||||
"status": "error" if error else "ok",
|
||||
"error": error,
|
||||
"selection_mode": selection_mode,
|
||||
"style_search": style_search,
|
||||
"category_hint": category_hint,
|
||||
"tag_hint": tag_hint,
|
||||
"requested_variant": requested_variant,
|
||||
"applied_variant": applied_variant,
|
||||
"style_strength": style_strength,
|
||||
"dedupe_mode": dedupe_mode,
|
||||
"on_missing_style": on_missing_style,
|
||||
"style": {
|
||||
"id": chosen_style.id if chosen_style else "",
|
||||
"name": chosen_style.name if chosen_style else "",
|
||||
"category": chosen_style.category if chosen_style else "",
|
||||
"tags": list(chosen_style.tags) if chosen_style else [],
|
||||
},
|
||||
"overlay": {
|
||||
"id": overlay_style.id if overlay_style else "",
|
||||
"name": overlay_style.name if overlay_style else "",
|
||||
"category": overlay_style.category if overlay_style else "",
|
||||
"tags": list(overlay_style.tags) if overlay_style else [],
|
||||
},
|
||||
"prompt_details": dict(prompt_details),
|
||||
}
|
||||
return json.dumps(payload, ensure_ascii=True, sort_keys=True)
|
||||
|
||||
|
||||
def available_categories(styles: Sequence[StyleTemplate]) -> List[str]:
|
||||
categories = {style.category for style in styles if style.category.strip()}
|
||||
categories.update(CATEGORY_BASE_PREFIX.keys())
|
||||
categories.update(CATEGORY_BASE_SUFFIX.keys())
|
||||
return sorted(categories, key=lambda value: value.casefold())
|
||||
|
||||
|
||||
def write_legacy_snapshot(path: str = LEGACY_STYLES_PATH, load_policy: str = LOAD_POLICY_STRICT) -> int:
|
||||
library = load_style_library(load_policy=load_policy)
|
||||
payload = {"version": 1, "styles": [style_to_dict(style) for style in library.styles]}
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, "w", encoding="utf-8") as handle:
|
||||
json.dump(payload, handle, indent=2, ensure_ascii=True)
|
||||
handle.write("\n")
|
||||
return len(library.styles)
|
||||
@@ -0,0 +1,285 @@
|
||||
{
|
||||
"version": 1,
|
||||
"styles": [
|
||||
{
|
||||
"id": "repro_mimeograph_duplication",
|
||||
"name": "Reprographic: Mimeograph Duplication",
|
||||
"category": "Printmaking/Expanded",
|
||||
"default": {
|
||||
"prefix": "printmaking, ink on paper, handcrafted texture, carved lines, high contrast, mimeograph duplication, stencil master, inked drum texture, duplicated sheets",
|
||||
"suffix": "slight misregistration, inky density shifts, porous paper texture, office-print rhythm, paper texture, ink texture, rough edges, high contrast"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mimeograph duplication with stencil-master imperfections. Lighting: flat even light preserving paper and ink density variation. Texture: inked drum transfer, porous paper, and repeated-sheet inconsistency. Key cues: mimeograph duplication, stencil master, inked drum texture, duplicated sheets. Finish cues: slight misregistration, inky density shifts, porous paper texture, office-print rhythm. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"reprographic",
|
||||
"mimeograph",
|
||||
"print"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "repro_spirit_duplicator_copy",
|
||||
"name": "Reprographic: Spirit Duplicator",
|
||||
"category": "Printmaking/Expanded",
|
||||
"default": {
|
||||
"prefix": "printmaking, ink on paper, handcrafted texture, carved lines, high contrast, spirit duplicator copy, purple aniline tones, soft duplicate texturing, school-office print",
|
||||
"suffix": "lavender cast, soft edges, faded pressure transfer, paper softness, paper texture, ink texture, rough edges, high contrast"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: spirit-duplicator copy with characteristic purple cast. Lighting: flat diffuse light that keeps the faded duplicate texture legible. Texture: soft transfer edges, pale dye density, and absorbent paper. Key cues: spirit duplicator copy, purple aniline tones, soft duplicate texturing, school-office print. Finish cues: lavender cast, soft edges, faded pressure transfer, paper softness. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"reprographic",
|
||||
"spirit_duplicator",
|
||||
"print"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "repro_diazo_blueprint",
|
||||
"name": "Reprographic: Diazo Blueprint",
|
||||
"category": "Photography/Alt Process",
|
||||
"default": {
|
||||
"prefix": "cyanotype print, blueprint blue, pale white linework, coated paper texture, graphic plan aesthetic",
|
||||
"suffix": "paper grain, soft process fading, crisp line edges, matte coated sheet, controlled contrast"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: cyanotype-like blueprint print with pale linework on deep blue paper. Lighting: flat even light preserving paper grain and line clarity. Texture: matte coated paper, soft process fading, and crisp pale lines. Key cues: blueprint blue, pale white linework, coated paper texture, graphic plan aesthetic. Finish cues: paper grain, soft process fading, crisp line edges, matte coated sheet. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"reprographic",
|
||||
"diazo",
|
||||
"blueprint"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "repro_whiteprint_copy",
|
||||
"name": "Reprographic: Whiteprint Copy",
|
||||
"category": "Photography/Alt Process",
|
||||
"default": {
|
||||
"prefix": "whiteprint aesthetic, pale paper ground, soft blue linework, engineering-print look, matte sheet texture",
|
||||
"suffix": "fine paper grain, faint blue tint, clean line edges, document paper wear, restrained contrast"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: whiteprint-style reproduction with blue linework on pale paper. Lighting: neutral even light preserving line clarity and sheet texture. Texture: matte paper, faint blue tint, and lightly worn print edges. Key cues: pale paper ground, soft blue linework, engineering-print look, matte sheet texture. Finish cues: fine paper grain, faint blue tint, clean line edges, document paper wear. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"reprographic",
|
||||
"whiteprint",
|
||||
"technical_copy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "repro_offset_litho_misregistration",
|
||||
"name": "Reprographic: Offset Misregistration",
|
||||
"category": "Graphic Design",
|
||||
"default": {
|
||||
"prefix": "offset print look, slight CMYK drift, layered overprint color, poster ink texture, graphic print surface",
|
||||
"suffix": "registration drift, ink overlap edges, paper tooth, press imperfection texture, bold printed contrast"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: offset print with slight CMYK misregistration and overprinted color layers. Lighting: flat print-preview light with minimal shading. Texture: paper tooth, ink overlap edges, and gentle press drift. Key cues: offset print look, slight CMYK drift, layered overprint color, poster ink texture. Finish cues: registration drift, ink overlap edges, paper tooth, press imperfection texture. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"reprographic",
|
||||
"offset",
|
||||
"graphic"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "repro_newsprint_halftone",
|
||||
"name": "Reprographic: Newsprint Halftone",
|
||||
"category": "Graphic Design",
|
||||
"default": {
|
||||
"prefix": "newsprint print look, coarse halftone dots, absorbent paper stock, editorial print texture, monochrome or limited color",
|
||||
"suffix": "dot gain, soft ink spread, paper fuzz, cheap stock texture, gritty printed contrast"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: newsprint reproduction with coarse halftone dots and absorbent paper texture. Lighting: flat editorial light keeping dot structure readable. Texture: dot gain, soft ink spread, and cheap stock fuzz. Key cues: coarse halftone dots, absorbent paper stock, editorial print texture, limited-color print. Finish cues: dot gain, soft ink spread, paper fuzz, cheap stock texture. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"reprographic",
|
||||
"newsprint",
|
||||
"halftone"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "repro_xerox_zine_photocopy",
|
||||
"name": "Reprographic: Xerox Zine Photocopy",
|
||||
"category": "Graphic Design",
|
||||
"default": {
|
||||
"prefix": "photocopy zine aesthetic, stark black toner, rough collage print, DIY copy-art texture, matte office paper",
|
||||
"suffix": "toner speckle, edge shadowing, paper curl, rough copy texture, gritty contrast"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: photocopy zine print with stark black toner and rough copy-art texture. Lighting: flat copy-table light with no dramatic shading. Texture: toner speckle, edge shadowing, and matte office paper curl. Key cues: photocopy zine aesthetic, stark black toner, rough collage print, DIY copy-art texture. Finish cues: toner speckle, edge shadowing, paper curl, rough copy texture. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"reprographic",
|
||||
"photocopy",
|
||||
"zine"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "repro_carbon_copy_manifold",
|
||||
"name": "Reprographic: Carbon Copy Manifold",
|
||||
"category": "Printmaking/Expanded",
|
||||
"default": {
|
||||
"prefix": "printmaking, ink on paper, handcrafted texture, carved lines, high contrast, carbon copy manifold, pressure transfer typing, duplicate form, ghosted text impression",
|
||||
"suffix": "soft duplicate offset, paper stack pressure marks, form-stock texture, typed imprint feel, paper texture, ink texture, rough edges, high contrast"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: carbon-copy manifold form with pressure-transferred duplicate impression. Lighting: neutral even light revealing paper stack pressure and duplicate offset. Texture: soft transfer, form-stock grain, and faint stacked-sheet marks. Key cues: carbon copy manifold, pressure transfer typing, duplicate form, ghosted text impression. Finish cues: soft duplicate offset, paper stack pressure marks, form-stock texture, typed imprint feel. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"reprographic",
|
||||
"carbon_copy",
|
||||
"office_print"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "repro_contact_sheet_proof",
|
||||
"name": "Reprographic: Contact Sheet Proof",
|
||||
"category": "Photography/Alt Process",
|
||||
"default": {
|
||||
"prefix": "contact-sheet darkroom print, small photo borders, glossy proof paper, analog print-sheet aesthetic",
|
||||
"suffix": "glossy paper sheen, narrow frame borders, archival print texture, soft darkroom proof feel"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: darkroom contact-sheet print with small bordered frames and glossy proof paper. Lighting: soft even light preserving paper sheen and border traces. Texture: glossy proof paper, archival print surface, and subtle frame spacing. Key cues: contact-sheet darkroom print, small photo borders, glossy proof paper, analog print-sheet aesthetic. Finish cues: glossy paper sheen, narrow frame borders, archival print texture, soft darkroom proof feel. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"reprographic",
|
||||
"contact_sheet",
|
||||
"analog_print"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "repro_rotogravure_ink_pattern",
|
||||
"name": "Reprographic: Rotogravure Ink Pattern",
|
||||
"category": "Printmaking/Expanded",
|
||||
"default": {
|
||||
"prefix": "printmaking, ink on paper, handcrafted texture, carved lines, high contrast, rotogravure print, copper-cell ink pattern, smooth tonal reproduction, press cylinder texture",
|
||||
"suffix": "fine cell grain, velvety darks, printed paper texture, high-volume press character, paper texture, ink texture, rough edges, high contrast"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: rotogravure reproduction with smooth tonal printing and fine cell patterning. Lighting: flat print-preview light preserving tonal reproduction and cell grain. Texture: velvety darks, fine gravure grain, and coated paper response. Key cues: rotogravure print, copper-cell ink pattern, smooth tonal reproduction, press cylinder texture. Finish cues: fine cell grain, velvety darks, printed paper texture, high-volume press character. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"reprographic",
|
||||
"rotogravure",
|
||||
"print"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "repro_cmyk_registration_proof",
|
||||
"name": "Reprographic: CMYK Registration Proof",
|
||||
"category": "Graphic Design",
|
||||
"default": {
|
||||
"prefix": "CMYK proof print, layered process color, slight registration drift, print-shop proof texture, offset ink surface",
|
||||
"suffix": "ink alignment variation, paper tooth, printed color layering, proof-sheet texture, clean graphic contrast"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: CMYK proof print with layered process color and slight registration drift. Lighting: neutral inspection light with minimal glare. Texture: paper tooth, printed color layering, and subtle alignment variation. Key cues: CMYK proof print, layered process color, slight registration drift, print-shop proof texture. Finish cues: ink alignment variation, paper tooth, printed color layering, proof-sheet texture. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"reprographic",
|
||||
"cmyk",
|
||||
"proof"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "repro_thermographic_raised_ink",
|
||||
"name": "Reprographic: Thermographic Raised Ink",
|
||||
"category": "Printmaking/Expanded",
|
||||
"default": {
|
||||
"prefix": "printmaking, ink on paper, handcrafted texture, carved lines, high contrast, thermographic raised ink, powder-fused lettering, slight gloss, printed stationery",
|
||||
"suffix": "raised ink relief, subtle sheen, paper texture, formal print finish, ink texture, rough edges, high contrast"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: thermographic print with powder-fused raised ink. Lighting: soft raking light revealing slight relief and glossy edges. Texture: raised lettering, subtle sheen, and fine stationery stock. Key cues: thermographic raised ink, powder-fused lettering, slight gloss, printed stationery. Finish cues: raised ink relief, subtle sheen, paper texture, formal print finish. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"reprographic",
|
||||
"thermography",
|
||||
"raised_ink"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "repro_rubber_stamp_overprint",
|
||||
"name": "Reprographic: Rubber Stamp Overprint",
|
||||
"category": "Printmaking/Expanded",
|
||||
"default": {
|
||||
"prefix": "printmaking, ink on paper, handcrafted texture, carved lines, high contrast, rubber stamp overprint, layered ink stamps, imperfect repeat, manual registration",
|
||||
"suffix": "ink pooling, stamp edge chatter, paper tooth, hand-pressed irregularity, paper texture, ink texture, rough edges, high contrast"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: rubber-stamped overprint with imperfect manual registration. Lighting: flat diffuse light preserving ink pooling and stamp edge chatter. Texture: hand-pressed irregularity, paper tooth, and layered ink density. Key cues: rubber stamp overprint, layered ink stamps, imperfect repeat, manual registration. Finish cues: ink pooling, stamp edge chatter, paper tooth, hand-pressed irregularity. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"reprographic",
|
||||
"rubber_stamp",
|
||||
"overprint"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "repro_dot_gain_press_sheet",
|
||||
"name": "Reprographic: Dot Gain Press Sheet",
|
||||
"category": "Graphic Design",
|
||||
"default": {
|
||||
"prefix": "screened print texture, dot gain, soft ink spread, absorbent press paper, calibration-print look",
|
||||
"suffix": "softened dot edges, paper absorbency, density variation, press-sheet texture, matte printed surface"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: screened print showing dot gain and soft ink spread on absorbent paper. Lighting: neutral inspection light that keeps screened texture readable. Texture: softened dot edges, density variation, and matte press paper. Key cues: screened print texture, dot gain, soft ink spread, absorbent press paper. Finish cues: softened dot edges, paper absorbency, density variation, press-sheet texture. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"reprographic",
|
||||
"dot_gain",
|
||||
"press"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
{
|
||||
"version": 1,
|
||||
"styles": [
|
||||
{
|
||||
"id": "artist_leonardo_da_vinci_sfumato",
|
||||
"name": "Artist: Leonardo da Vinci (Sfumato + Subtle Chiaroscuro)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Leonardo da Vinci, High Renaissance, sfumato, subtle tonal transitions, delicate glazing, gentle chiaroscuro, atmospheric perspective",
|
||||
"suffix": "smooth gradients, refined realism, calm harmony, soft atmospheric depth, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: High Renaissance painting in the style of Leonardo da Vinci, refined realism with sfumato. Lighting: gentle chiaroscuro with soft shadow transitions. Technique: delicate glazing, subtle tonal gradients, atmospheric perspective. Key cues: in the style of Leonardo da Vinci, High Renaissance, sfumato, subtle tonal transitions, delicate glazing, gentle chiaroscuro, atmospheric perspective. Finish cues: smooth gradients, refined realism, calm harmony, soft atmospheric depth. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "leonardo_da_vinci", "high_renaissance", "sfumato"]
|
||||
},
|
||||
{
|
||||
"id": "artist_raphael_harmonious_renaissance",
|
||||
"name": "Artist: Raphael (Harmonious High Renaissance)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Raphael, High Renaissance, harmonious composition, idealized forms, graceful linework, clear perspective, soft modeling, balanced symmetry",
|
||||
"suffix": "serene mood, smooth paint handling, gentle highlights, harmonious color, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: High Renaissance painting in the style of Raphael, clarity and harmony. Composition: balanced symmetry, clear perspective, idealized forms, graceful contours. Color: harmonious palette with soft transitions and calm unity. Key cues: in the style of Raphael, High Renaissance, harmonious composition, idealized forms, graceful linework, clear perspective, balanced symmetry. Finish cues: serene mood, smooth paint handling, gentle highlights, harmonious color. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "raphael", "high_renaissance"]
|
||||
},
|
||||
{
|
||||
"id": "artist_michelangelo_monumental_fresco",
|
||||
"name": "Artist: Michelangelo (Monumental Forms + Fresco)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Michelangelo, High Renaissance, monumental forms, sculptural anatomy, dramatic foreshortening, powerful gestures, fresco painting, bold modeling",
|
||||
"suffix": "monumental scale, dramatic value range, strong highlights and shadows, dynamic composition, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: monumental High Renaissance fresco painting in the style of Michelangelo. Form: sculptural anatomy, heroic scale, dramatic foreshortening, powerful gestures. Shading: bold modeling with a wide value range and clear form separation. Key cues: in the style of Michelangelo, High Renaissance, monumental forms, sculptural anatomy, dramatic foreshortening, fresco painting, bold modeling. Finish cues: monumental scale, dramatic value range, strong highlights and shadows, dynamic composition. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "michelangelo", "high_renaissance", "fresco"]
|
||||
},
|
||||
{
|
||||
"id": "artist_botticelli_linear_grace_tempera",
|
||||
"name": "Artist: Sandro Botticelli (Linear Grace + Tempera)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Sandro Botticelli, Early Renaissance, linear grace, flowing contours, delicate tempera-like handling, decorative patterning, clear silhouettes, gentle pastel palette",
|
||||
"suffix": "crisp contours, delicate shading, ornamental detail, elegant rhythm, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Early Renaissance painting in the style of Sandro Botticelli, emphasizing linear grace. Line: flowing contours, clear silhouettes, elegant rhythm, ornamental detail. Technique: tempera-like precision with delicate shading and gentle color harmony. Key cues: in the style of Sandro Botticelli, Early Renaissance, linear grace, flowing contours, decorative patterning, clear silhouettes. Finish cues: crisp contours, delicate shading, ornamental detail, elegant rhythm. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "botticelli", "early_renaissance"]
|
||||
},
|
||||
{
|
||||
"id": "artist_caravaggio_tenebrism",
|
||||
"name": "Artist: Caravaggio (Tenebrism + Dramatic Realism)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Caravaggio, Baroque, tenebrism, intense chiaroscuro, dramatic realism, strong directional light, deep shadow fields, theatrical contrast",
|
||||
"suffix": "high contrast, hard-edged light falloff, rich darks, warm highlights, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Baroque oil painting in the style of Caravaggio, dramatic realism with tenebrism. Lighting: intense chiaroscuro with deep shadow fields and hard-edged light falloff. Mood: tense and theatrical, with bold value contrasts. Key cues: in the style of Caravaggio, Baroque, tenebrism, intense chiaroscuro, dramatic realism, strong directional light. Finish cues: high contrast, rich darks, warm highlights, hard-edged light falloff. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "caravaggio", "baroque", "tenebrism"]
|
||||
},
|
||||
{
|
||||
"id": "artist_rembrandt_chiaroscuro_impasto",
|
||||
"name": "Artist: Rembrandt van Rijn (Chiaroscuro + Impasto)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Rembrandt van Rijn, Dutch Golden Age, warm chiaroscuro, impasto highlights, rich earth tones, painterly brushwork, subtle glazing, psychological depth",
|
||||
"suffix": "thick paint texture, warm amber light, deep soft shadows, rich tonal range, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Dutch Golden Age oil painting in the style of Rembrandt van Rijn, warm chiaroscuro and painterly depth. Lighting: dramatic but warm light with deep soft shadows and strong form modeling. Texture: impasto highlights with visible brushwork and subtle glazing in midtones. Key cues: in the style of Rembrandt van Rijn, Dutch Golden Age, warm chiaroscuro, impasto highlights, rich earth tones, painterly brushwork. Finish cues: thick paint texture, warm amber light, deep soft shadows, rich tonal range. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "rembrandt", "dutch_golden_age", "chiaroscuro", "impasto"]
|
||||
},
|
||||
{
|
||||
"id": "artist_vermeer_luminous_daylight",
|
||||
"name": "Artist: Johannes Vermeer (Luminous Soft Daylight)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Johannes Vermeer, Dutch Golden Age, luminous daylight, soft diffused side light, tranquil composition, subtle color harmony, smooth glazing, quiet realism",
|
||||
"suffix": "pearl-like highlights, clean edges, calm atmosphere, delicate texture, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Dutch Golden Age painting in the style of Johannes Vermeer, luminous realism and calm clarity. Lighting: soft diffused daylight from one side, gentle shadow transitions, pearl-like highlights. Technique: smooth glazing, clean edges, subtle color harmony, quiet atmosphere. Key cues: in the style of Johannes Vermeer, Dutch Golden Age, luminous daylight, soft diffused side light, smooth glazing, subtle color harmony. Finish cues: pearl-like highlights, clean edges, calm atmosphere, delicate texture. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "vermeer", "dutch_golden_age", "luminous"]
|
||||
},
|
||||
{
|
||||
"id": "artist_velazquez_tonal_realism",
|
||||
"name": "Artist: Diego Velázquez (Tonal Realism + Loose Brushwork)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Diego Velázquez, Spanish Baroque, tonal realism, loose confident brushwork, subdued palette, atmospheric space, naturalistic rendering, understated highlights",
|
||||
"suffix": "painterly edges, tonal unity, subtle highlights, atmospheric depth, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Spanish Baroque painting in the style of Diego Velázquez, naturalistic tonal realism. Brushwork: loose and confident with painterly edges and subtle transitions. Color: subdued palette with tonal unity and understated highlights, emphasizing atmosphere. Key cues: in the style of Diego Velázquez, Spanish Baroque, tonal realism, loose confident brushwork, subdued palette, atmospheric space. Finish cues: painterly edges, tonal unity, subtle highlights, atmospheric depth. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "velazquez", "spanish_baroque", "tonal_painting"]
|
||||
},
|
||||
{
|
||||
"id": "artist_goya_expressive_romanticism",
|
||||
"name": "Artist: Francisco Goya (Expressive Romanticism)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Francisco Goya, Romanticism, expressive brushwork, somber palette, dramatic contrast, psychological intensity, raw painterly texture, moody atmosphere",
|
||||
"suffix": "bold value contrasts, rough brush texture, dark tonal weight, intense mood, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Romantic-era painting in the style of Francisco Goya, expressive and psychologically intense. Tone: somber palette with dark tonal weight and bold value contrasts. Brushwork: raw painterly texture, energetic marks, heightened dramatic contrast. Key cues: in the style of Francisco Goya, Romanticism, expressive brushwork, somber palette, dramatic contrast, psychological intensity. Finish cues: bold value contrasts, rough brush texture, dark tonal weight, intense mood. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "goya", "romanticism", "expressive"]
|
||||
},
|
||||
{
|
||||
"id": "artist_turner_luminous_atmosphere",
|
||||
"name": "Artist: J. M. W. Turner (Luminous Atmosphere)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of J. M. W. Turner, Romanticism, luminous atmosphere, dissolving forms, swirling light, vaporous haze, radiant color, atmospheric perspective",
|
||||
"suffix": "glowing light effects, soft edges, misty diffusion, energetic paint movement, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Romantic painting in the style of J. M. W. Turner, luminous atmosphere and radiant color. Atmosphere: vaporous haze, dissolving forms, soft edges, misty diffusion. Light: glowing light effects with energetic paint movement and dramatic tonal transitions. Key cues: in the style of J. M. W. Turner, Romanticism, luminous atmosphere, dissolving forms, swirling light, vaporous haze. Finish cues: glowing light effects, soft edges, misty diffusion, radiant color. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "turner", "romanticism", "atmospheric"]
|
||||
},
|
||||
{
|
||||
"id": "artist_monet_impressionist_light",
|
||||
"name": "Artist: Claude Monet (Impressionist Light)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Claude Monet, Impressionism, broken brushstrokes, plein-air light, shimmering color, soft edges, optical color mixing, atmospheric haze",
|
||||
"suffix": "sparkling highlights, loose strokes, airy atmosphere, light-drenched palette, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Impressionist painting in the style of Claude Monet, focused on light and atmosphere. Brushwork: short broken strokes with soft edges and shimmering color transitions. Color: optical mixing and plein-air light effects with a light-drenched palette and gentle haze. Key cues: in the style of Claude Monet, Impressionism, broken brushstrokes, plein-air light, shimmering color, atmospheric haze. Finish cues: sparkling highlights, loose strokes, airy atmosphere, light-drenched palette. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "monet", "impressionism"]
|
||||
},
|
||||
{
|
||||
"id": "artist_seurat_pointillism",
|
||||
"name": "Artist: Georges Seurat (Pointillism)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Georges Seurat, Neo-Impressionism, pointillism, tiny color dots, optical color mixing, precise composition, clear silhouettes, controlled palette",
|
||||
"suffix": "granular dot texture, luminous color vibration, crisp contours, structured composition, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Neo-Impressionist painting in the style of Georges Seurat, built from pointillist color dots. Technique: tiny dots and dabs that create optical color mixing and luminous vibration at a distance. Composition: precise and structured with crisp contours and controlled rhythm. Key cues: in the style of Georges Seurat, Neo-Impressionism, pointillism, tiny color dots, optical color mixing, precise composition. Finish cues: granular dot texture, luminous color vibration, crisp contours, structured composition. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "seurat", "neo_impressionism", "pointillism"]
|
||||
},
|
||||
{
|
||||
"id": "artist_cezanne_constructive_brushwork",
|
||||
"name": "Artist: Paul Cézanne (Constructive Brushwork)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Paul Cézanne, Post-Impressionism, constructive brushwork, chromatic planes, simplified geometry, underlying structure, warm-cool modulation, architectonic composition",
|
||||
"suffix": "blocky color planes, structured forms, balanced weight, textured strokes, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Post-Impressionist painting in the style of Paul Cézanne, emphasizing structure and form. Brushwork: constructive strokes that build planes of color and underlying geometry. Color: warm-cool modulation and chromatic planes that create solid, architectonic composition. Key cues: in the style of Paul Cézanne, Post-Impressionism, constructive brushwork, chromatic planes, simplified geometry, underlying structure. Finish cues: blocky color planes, structured forms, balanced weight, textured strokes. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "cezanne", "post_impressionism", "constructive"]
|
||||
},
|
||||
{
|
||||
"id": "artist_van_gogh_expressive_impasto",
|
||||
"name": "Artist: Vincent van Gogh (Expressive Impasto)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Vincent van Gogh, Post-Impressionism, striking color, emphatic brushwork, thick impasto, swirling directional strokes, bold outlines, high-chroma palette",
|
||||
"suffix": "visible paint texture, energetic strokes, intense complementary contrast, dynamic movement, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Post-Impressionist oil painting in the style of Vincent van Gogh, energetic and expressive. Brushwork: emphatic directional strokes with thick impasto and visible paint texture. Color: striking high-chroma palette with intense complementary contrasts and bold outlines. Key cues: in the style of Vincent van Gogh, Post-Impressionism, striking color, emphatic brushwork, thick impasto, bold outlines. Finish cues: visible paint texture, energetic strokes, intense complementary contrast, dynamic movement. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "van_gogh", "post_impressionism", "impasto", "expressive"]
|
||||
},
|
||||
{
|
||||
"id": "artist_gauguin_synthetism",
|
||||
"name": "Artist: Paul Gauguin (Synthetism)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Paul Gauguin, Post-Impressionism, synthetism, bold flat color areas, simplified shapes, strong outlines, symbolic color, decorative pattern",
|
||||
"suffix": "flat planes, clean contours, saturated color blocks, poster-like clarity, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Post-Impressionist painting in the style of Paul Gauguin, synthetist and decorative. Shape: simplified forms with strong outlines and bold flat color areas. Color: symbolic, saturated blocks with poster-like clarity and patterned surfaces. Key cues: in the style of Paul Gauguin, Post-Impressionism, synthetism, bold flat color areas, simplified shapes, strong outlines. Finish cues: flat planes, clean contours, saturated color blocks, poster-like clarity. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "gauguin", "post_impressionism", "synthetism"]
|
||||
},
|
||||
{
|
||||
"id": "artist_matisse_fauvist_color",
|
||||
"name": "Artist: Henri Matisse (Fauvist Color)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Henri Matisse, Fauvism, bold saturated color, expressive contours, flattened space, decorative pattern, simplified forms, lively composition",
|
||||
"suffix": "vivid palette, clean color fields, strong outlines, rhythmic shapes, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Fauvist painting in the style of Henri Matisse, emphasizing bold expressive color. Color: vivid saturated palette with large luminous patches and simplified forms. Line: expressive contours and flattened space with decorative rhythm and strong outlines. Key cues: in the style of Henri Matisse, Fauvism, bold saturated color, expressive contours, flattened space, decorative pattern. Finish cues: vivid palette, clean color fields, strong outlines, rhythmic shapes. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "matisse", "fauvism", "color"]
|
||||
},
|
||||
{
|
||||
"id": "artist_picasso_analytical_cubism",
|
||||
"name": "Artist: Pablo Picasso (Analytical Cubism)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Pablo Picasso, Cubism, multiple viewpoints, fractured planes, faceted geometry, flattened picture plane, angular forms, limited earthy palette",
|
||||
"suffix": "geometric fragmentation, sharp edges, planar shading, abstracted structure, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: analytical Cubism in the style of Pablo Picasso, fractured structure and multiple viewpoints. Form: faceted geometry, angular planes, flattened picture plane, abstracted construction. Color: limited earthy palette with planar shading and sharp edges. Key cues: in the style of Pablo Picasso, Cubism, multiple viewpoints, fractured planes, faceted geometry, flattened picture plane. Finish cues: geometric fragmentation, sharp edges, planar shading, abstracted structure. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "picasso", "cubism", "geometric"]
|
||||
},
|
||||
{
|
||||
"id": "artist_kandinsky_geometric_abstraction",
|
||||
"name": "Artist: Wassily Kandinsky (Geometric Abstraction)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Wassily Kandinsky, abstract art, geometric abstraction, dynamic lines, bold shapes, color-form relationships, rhythmic composition, nonobjective",
|
||||
"suffix": "vibrant color interplay, clean shapes, energetic balance, abstract spatial tension, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: geometric abstraction in the style of Wassily Kandinsky, nonobjective and rhythmic. Composition: dynamic lines and bold shapes arranged with energetic balance and abstract spatial tension. Color: intentional color-form relationships with vibrant interplay and clear structure. Key cues: in the style of Wassily Kandinsky, abstract art, geometric abstraction, dynamic lines, bold shapes, color-form relationships. Finish cues: vibrant color interplay, clean shapes, energetic balance, abstract spatial tension. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "kandinsky", "abstract", "geometric"]
|
||||
},
|
||||
{
|
||||
"id": "artist_mondrian_de_stijl_grid",
|
||||
"name": "Artist: Piet Mondrian (De Stijl Grid)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Piet Mondrian, De Stijl, neoplasticism, orthogonal grid, primary colors, black lines, flat planes, strict geometry",
|
||||
"suffix": "clean grid structure, flat color blocks, sharp edges, minimal composition, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: De Stijl abstraction in the style of Piet Mondrian, rigorous geometric order. Composition: orthogonal grid with black lines and flat planes of primary colors. Design: minimal, balanced, and precise with sharp edges and clean structure. Key cues: in the style of Piet Mondrian, De Stijl, neoplasticism, orthogonal grid, primary colors, black lines. Finish cues: clean grid structure, flat color blocks, sharp edges, minimal composition. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "mondrian", "de_stijl", "neoplasticism"]
|
||||
},
|
||||
{
|
||||
"id": "artist_klimt_ornamental_gold",
|
||||
"name": "Artist: Gustav Klimt (Ornamental Gold)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Gustav Klimt, Vienna Secession, ornamental patterning, gold leaf accents, flat decorative surfaces, linear drawing, mosaic-like texture, symbolic elegance",
|
||||
"suffix": "golden shimmer, intricate ornament, flat stylization, rich decorative detail, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Vienna Secession painting in the style of Gustav Klimt, ornamental and richly decorative. Surface: flat stylization with linear drawing, intricate patterning, and gold leaf accents. Texture: mosaic-like ornament with golden shimmer and refined symbolic elegance. Key cues: in the style of Gustav Klimt, Vienna Secession, ornamental patterning, gold leaf accents, flat decorative surfaces, linear drawing. Finish cues: golden shimmer, intricate ornament, flat stylization, rich decorative detail. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "klimt", "vienna_secession", "ornament", "gold_leaf"]
|
||||
},
|
||||
{
|
||||
"id": "artist_dali_surreal_precision",
|
||||
"name": "Artist: Salvador Dalí (Surreal Precision)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Salvador Dalí, Surrealism, dreamlike atmosphere, meticulous realism, crisp detail, uncanny juxtaposition, smooth gradients, sharp highlights",
|
||||
"suffix": "surreal mood, highly detailed realism, clean edges, uncanny clarity, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Surrealist painting in the style of Salvador Dalí, dreamlike and uncanny with meticulous realism. Detail: crisp rendering and clean edges with smooth gradients and sharp highlights. Mood: surreal and disquieting, emphasizing precise clarity and uncanny juxtaposition. Key cues: in the style of Salvador Dalí, Surrealism, dreamlike atmosphere, meticulous realism, crisp detail, uncanny juxtaposition. Finish cues: surreal mood, highly detailed realism, clean edges, uncanny clarity. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "dali", "surrealism", "meticulous"]
|
||||
},
|
||||
{
|
||||
"id": "artist_pollock_action_painting",
|
||||
"name": "Artist: Jackson Pollock (Action Painting Drips)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Jackson Pollock, Abstract Expressionism, action painting, drip and pour technique, all-over composition, layered splatter lines, energetic rhythm, tangled paint trails",
|
||||
"suffix": "dense layered drips, dynamic movement, intertwined lines, raw texture, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Abstract Expressionist action painting in the style of Jackson Pollock, energetic and all-over. Technique: drip and pour marks with layered splatter lines and tangled paint trails across the full surface. Texture: dense intertwined lines and raw motion, emphasizing gesture and rhythm. Key cues: in the style of Jackson Pollock, Abstract Expressionism, action painting, drip and pour technique, all-over composition, layered splatter lines. Finish cues: dense layered drips, dynamic movement, intertwined lines, raw texture. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "pollock", "abstract_expressionism", "action_painting"]
|
||||
},
|
||||
{
|
||||
"id": "artist_warhol_pop_art_silkscreen",
|
||||
"name": "Artist: Andy Warhol (Pop Art Silkscreen)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Andy Warhol, Pop Art, silkscreen print, bold flat colors, repetition, high-contrast graphic shapes, commercial print aesthetic, halftone texture",
|
||||
"suffix": "flat color fields, crisp edges, screenprint texture, repeating motif structure, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Pop Art print in the style of Andy Warhol, bold and graphic with silkscreen aesthetics. Color: flat high-contrast fields with repetition and commercial print clarity. Texture: screenprint feel with halftone texture and crisp edges, emphasizing iconic simplicity. Key cues: in the style of Andy Warhol, Pop Art, silkscreen print, bold flat colors, repetition, commercial print aesthetic. Finish cues: flat color fields, crisp edges, screenprint texture, repeating motif structure. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "warhol", "pop_art", "silkscreen"]
|
||||
},
|
||||
{
|
||||
"id": "artist_hokusai_ukiyo_e_woodblock",
|
||||
"name": "Artist: Katsushika Hokusai (Ukiyo-e Woodblock)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Katsushika Hokusai, ukiyo-e, Japanese woodblock print, bold fluid outlines, flat color planes, stylized perspective, patterned surfaces, clear silhouettes",
|
||||
"suffix": "crisp linework, flat ink-and-color finish, paper texture, clean negative space, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: ukiyo-e woodblock print in the style of Katsushika Hokusai, graphic and refined. Line: bold fluid outlines with clear silhouettes and stylized perspective. Color: flat planes and patterned surfaces with a crisp ink-and-color finish and paper texture. Key cues: in the style of Katsushika Hokusai, ukiyo-e, Japanese woodblock print, bold fluid outlines, flat color planes, patterned surfaces. Finish cues: crisp linework, flat ink-and-color finish, paper texture, clean negative space. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "hokusai", "ukiyo_e", "woodblock"]
|
||||
},
|
||||
{
|
||||
"id": "artist_kahlo_symbolic_realism",
|
||||
"name": "Artist: Frida Kahlo (Symbolic Realism)",
|
||||
"category": "Fine Art/Artists",
|
||||
"default": {
|
||||
"prefix": "fine art, museum-quality, traditional techniques, gallery piece, handcrafted, in the style of Frida Kahlo, Mexican modernism, folk art influence, vivid palette, symbolic surrealism, detailed brushwork, frontal composition, crisp outlines",
|
||||
"suffix": "high-detail rendering, saturated colors, emblematic symbolism, strong central framing, high detail, handcrafted surface texture, gallery lighting, balanced composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Mexican modernist painting in the style of Frida Kahlo, symbolic and vividly rendered. Composition: frontal, centered, with crisp outlines and strong central framing. Color: saturated palette with folk-art influence and emblematic surreal symbolism, rendered with high detail. Key cues: in the style of Frida Kahlo, Mexican modernism, folk art influence, vivid palette, symbolic surrealism, frontal composition. Finish cues: high-detail rendering, saturated colors, emblematic symbolism, strong central framing. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": ["fine_art", "artist", "kahlo", "mexican_modernism", "symbolic"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,278 @@
|
||||
{
|
||||
"version": 1,
|
||||
"styles": [
|
||||
{
|
||||
"id": "mixed_acrylic_sgraffito_ground",
|
||||
"name": "Acrylic Sgraffito Ground",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, acrylic sgraffito, scraped paint layers, revealed underpainting, layered acrylic ground",
|
||||
"suffix": "incised grooves, color reveal, matte paint texture, layered scraping marks, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media acrylic surface worked with sgraffito scraping. Lighting: soft raking light that catches incised grooves and revealed color layers. Texture: matte acrylic, scraped channels, and exposed underpainting. Key cues: acrylic sgraffito, scraped paint layers, revealed underpainting, layered acrylic ground. Finish cues: incised grooves, color reveal, matte paint texture, layered scraping marks. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media",
|
||||
"sgraffito",
|
||||
"acrylic"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_assemblage_artwork",
|
||||
"name": "Assemblage Artwork",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, assemblage, found objects, mixed materials, handcrafted",
|
||||
"suffix": "tactile texture, rough surfaces, layered depth, high detail, paper grain, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: assemblage artwork built from found objects and mixed materials. Lighting: soft gallery spotlights with gentle falloff and controlled highlights. Texture: rough surfaces, layered depth, tactile materials. Key cues: assemblage, found objects, mixed materials, handcrafted. Finish cues: tactile texture, rough surfaces, layered depth. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_cyanotype_print_analog",
|
||||
"name": "Cyanotype Print (Analog)",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, cyanotype print, blue monochrome, sun print, paper texture",
|
||||
"suffix": "grain, chemical texture, vintage print, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: analog cyanotype print on paper. Lighting: soft gallery spotlights with gentle falloff and controlled highlights. Mood: minimal, photographic, deep blue. Key cues: cyanotype print, blue monochrome, sun print, paper texture. Finish cues: grain, chemical texture, vintage print. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_decollage_poster_peel",
|
||||
"name": "Decollage Poster Peel",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, decollage poster peel, torn poster layers, weathered paste, exposed underlayers",
|
||||
"suffix": "ragged tears, glue residue, paper strata, urban paper texture, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media decollage with peeled poster layers and exposed strata. Lighting: flat daylight that keeps the layered tears and residue readable. Texture: ragged tears, glue residue, and stacked paper history. Key cues: decollage poster peel, torn poster layers, weathered paste, exposed underlayers. Finish cues: ragged tears, glue residue, paper strata, urban paper texture. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media",
|
||||
"decollage",
|
||||
"paper"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_encaustic_collage",
|
||||
"name": "Encaustic Collage",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, encaustic collage, translucent wax layers, embedded paper fragments, scraped surface",
|
||||
"suffix": "wax bloom, subtle crackle, tactile depth, layered translucency, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media encaustic collage with embedded paper fragments. Lighting: soft gallery light with gentle falloff and mild surface sheen. Texture: translucent wax layers, scraped marks, and tactile depth. Key cues: encaustic collage, translucent wax layers, embedded paper fragments, scraped surface. Finish cues: wax bloom, subtle crackle, tactile depth, layered translucency. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media",
|
||||
"encaustic",
|
||||
"collage"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_fabric_scrap_collage",
|
||||
"name": "Fabric Scrap Collage",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, fabric scrap collage, stitched textile fragments, raw edges, patchwork layering",
|
||||
"suffix": "frayed fibers, cloth weave, stitched joins, soft tactile depth, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media collage assembled from layered textile scraps. Lighting: soft diffuse light revealing cloth weave and stitched joins. Texture: frayed fibers, raw edges, and patchwork depth. Key cues: fabric scrap collage, stitched textile fragments, raw edges, patchwork layering. Finish cues: frayed fibers, cloth weave, stitched joins, soft tactile depth. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media",
|
||||
"fabric",
|
||||
"collage"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_foil_leaf_overlay",
|
||||
"name": "Foil Leaf Overlay",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, foil leaf overlay, metal leaf fragments, mixed surface, hand-applied shimmer",
|
||||
"suffix": "broken foil edges, micro-reflections, adhesive texture, layered contrast, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media surface with hand-applied foil leaf overlays. Lighting: controlled light that catches metallic fragments without blowing them out. Texture: broken foil edges, adhesive texture, and small reflective flashes. Key cues: foil leaf overlay, metal leaf fragments, mixed surface, hand-applied shimmer. Finish cues: broken foil edges, micro-reflections, adhesive texture, layered contrast. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media",
|
||||
"foil",
|
||||
"leaf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_frottage_rubbing_study",
|
||||
"name": "Frottage Rubbing Study",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, frottage rubbing, found-surface textures, graphite transfer, pressure marks",
|
||||
"suffix": "rubbed grain, uneven density, paper drag marks, tactile impressions, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media frottage study built from rubbed surface textures. Lighting: soft raking light that reveals paper tooth and rubbing variation. Texture: transferred grain, pressure marks, and uneven graphite density. Key cues: frottage rubbing, found-surface textures, graphite transfer, pressure marks. Finish cues: rubbed grain, uneven density, paper drag marks, tactile impressions. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media",
|
||||
"frottage",
|
||||
"rubbing"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_gel_medium_image_transfer",
|
||||
"name": "Gel Medium Image Transfer",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, gel medium image transfer, lifted image layers, translucent medium, paper support",
|
||||
"suffix": "soft edge lifting, adhesive sheen, surface irregularities, layered translucency, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media gel-medium transfer with lifted translucent image layers. Lighting: soft gallery light with enough angle to reveal transfer texture. Texture: adhesive sheen, edge lifting, and layered translucent film. Key cues: gel medium image transfer, lifted image layers, translucent medium, paper support. Finish cues: soft edge lifting, adhesive sheen, surface irregularities, layered translucency. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media",
|
||||
"transfer",
|
||||
"acrylic"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_gel_plate_monoprint",
|
||||
"name": "Gel Plate Monoprint",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, gel plate monoprint, ink layers, brayer texture, printmaking",
|
||||
"suffix": "paper grain, ink transfer artifacts, handmade print, high detail, tactile texture, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: gel plate monoprint with layered ink textures. Lighting: soft gallery spotlights with gentle falloff and controlled highlights. Texture: ink transfer artifacts, paper grain, layered prints. Key cues: gel plate monoprint, ink layers, brayer texture, printmaking. Finish cues: paper grain, ink transfer artifacts, handmade print. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_gesso_charcoal_board",
|
||||
"name": "Gesso and Charcoal Board",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, gesso ground, charcoal marks, chalky board surface, erased highlights",
|
||||
"suffix": "powdery smudge, toothy ground, lifted erasures, matte surface, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media board with gesso ground and charcoal drawing. Lighting: soft studio light revealing chalky tooth and erased highlights. Texture: powdery charcoal, matte gesso, and lifted erasure marks. Key cues: gesso ground, charcoal marks, chalky board surface, erased highlights. Finish cues: powdery smudge, toothy ground, lifted erasures, matte surface. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media",
|
||||
"gesso",
|
||||
"charcoal"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_handmade_paper_pulp_relief",
|
||||
"name": "Handmade Paper Pulp Relief",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, handmade paper pulp, cast relief, fibrous surface, pressed pulp forms",
|
||||
"suffix": "deckled edges, fiber clumps, raised paper relief, soft shadow depth, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media handmade paper relief with cast pulp forms. Lighting: soft raking light to reveal paper relief and deckled edges. Texture: fibrous pulp, soft relief shadows, and visible paper formation. Key cues: handmade paper pulp, cast relief, fibrous surface, pressed pulp forms. Finish cues: deckled edges, fiber clumps, raised paper relief, soft shadow depth. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media",
|
||||
"paper",
|
||||
"relief"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_mixed_media_acrylic_ink",
|
||||
"name": "Mixed Media Acrylic + Ink",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, mixed media, acrylic paint, ink linework, layered marks",
|
||||
"suffix": "texture paste, splatter, handmade imperfections, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media artwork combining acrylic paint and ink linework. Lighting: soft gallery spotlights with gentle falloff and controlled highlights. Texture: layered paint, ink marks, tactile surface. Key cues: mixed media, acrylic paint, ink linework, layered marks. Finish cues: texture paste, splatter, handmade imperfections. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_monoprint_collage",
|
||||
"name": "Monoprint Collage",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, monoprint collage, ink pull textures, layered cut paper, one-off print fragments",
|
||||
"suffix": "brayer texture, ink ghosts, paper layering, soft registration drift, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media collage assembled from monoprint pulls and paper fragments. Lighting: soft gallery light keeping ink texture and layered edges visible. Texture: brayer marks, ink ghosts, and collage depth. Key cues: monoprint collage, ink pull textures, layered cut paper, one-off print fragments. Finish cues: brayer texture, ink ghosts, paper layering, soft registration drift. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media",
|
||||
"monoprint",
|
||||
"collage"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_paper_collage_mixed_media",
|
||||
"name": "Paper Collage (Mixed Media)",
|
||||
@@ -38,75 +310,63 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_mixed_media_acrylic_ink",
|
||||
"name": "Mixed Media Acrylic + Ink",
|
||||
"id": "mixed_resin_sealed_collage",
|
||||
"name": "Resin-Sealed Collage",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, mixed media, acrylic paint, ink linework, layered marks",
|
||||
"suffix": "texture paste, splatter, handmade imperfections, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, resin-sealed collage, encapsulated layers, embedded fragments, glossy surface",
|
||||
"suffix": "glass-like sheen, suspended depth, trapped inclusions, smooth surface reflections, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media artwork combining acrylic paint and ink linework. Lighting: soft gallery spotlights with gentle falloff and controlled highlights. Texture: layered paint, ink marks, tactile surface. Key cues: mixed media, acrylic paint, ink linework, layered marks. Finish cues: texture paste, splatter, handmade imperfections. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
"suffix": "Style: mixed media collage sealed under a glossy resin layer. Lighting: controlled light with smooth reflections and visible embedded depth. Texture: glass-like sheen, suspended fragments, and encapsulated layers. Key cues: resin-sealed collage, encapsulated layers, embedded fragments, glossy surface. Finish cues: glass-like sheen, suspended depth, trapped inclusions, smooth surface reflections. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media"
|
||||
"mixed_media",
|
||||
"resin",
|
||||
"collage"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_gel_plate_monoprint",
|
||||
"name": "Gel Plate Monoprint",
|
||||
"id": "mixed_sand_pigment_ground",
|
||||
"name": "Sand and Pigment Ground",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, gel plate monoprint, ink layers, brayer texture, printmaking",
|
||||
"suffix": "paper grain, ink transfer artifacts, handmade print, high detail, tactile texture, layered depth, cohesive composition"
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, sand and pigment ground, granular surface, earth pigments, rough substrate",
|
||||
"suffix": "gritty relief, dry brush drag, mineral texture, matte layered surface, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: gel plate monoprint with layered ink textures. Lighting: soft gallery spotlights with gentle falloff and controlled highlights. Texture: ink transfer artifacts, paper grain, layered prints. Key cues: gel plate monoprint, ink layers, brayer texture, printmaking. Finish cues: paper grain, ink transfer artifacts, handmade print. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
"suffix": "Style: mixed media surface built from sanded ground and dry pigments. Lighting: raking light to reveal granular relief and dry brush drag. Texture: mineral grit, matte layering, and rough substrate variation. Key cues: sand and pigment ground, granular surface, earth pigments, rough substrate. Finish cues: gritty relief, dry brush drag, mineral texture, matte layered surface. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media"
|
||||
"mixed_media",
|
||||
"sand",
|
||||
"pigment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_cyanotype_print_analog",
|
||||
"name": "Cyanotype Print (Analog)",
|
||||
"id": "mixed_scratchboard_ink_wash",
|
||||
"name": "Scratchboard Ink Wash",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, cyanotype print, blue monochrome, sun print, paper texture",
|
||||
"suffix": "grain, chemical texture, vintage print, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, scratchboard, ink wash, scraped highlights, black ground",
|
||||
"suffix": "incised lines, white reveal marks, ink pooling, high contrast surface, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: analog cyanotype print on paper. Lighting: soft gallery spotlights with gentle falloff and controlled highlights. Mood: minimal, photographic, deep blue. Key cues: cyanotype print, blue monochrome, sun print, paper texture. Finish cues: grain, chemical texture, vintage print. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
"suffix": "Style: scratchboard mixed media with ink wash and scraped highlights. Lighting: controlled studio light emphasizing incised lines and contrast. Texture: sharp reveal marks, pooled ink, and a dense black ground. Key cues: scratchboard, ink wash, scraped highlights, black ground. Finish cues: incised lines, white reveal marks, ink pooling, high contrast surface. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_assemblage_artwork",
|
||||
"name": "Assemblage Artwork",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, assemblage, found objects, mixed materials, handcrafted",
|
||||
"suffix": "tactile texture, rough surfaces, layered depth, high detail, paper grain, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: assemblage artwork built from found objects and mixed materials. Lighting: soft gallery spotlights with gentle falloff and controlled highlights. Texture: rough surfaces, layered depth, tactile materials. Key cues: assemblage, found objects, mixed materials, handcrafted. Finish cues: tactile texture, rough surfaces, layered depth. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media"
|
||||
"mixed_media",
|
||||
"scratchboard",
|
||||
"ink"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -126,6 +386,106 @@
|
||||
"tags": [
|
||||
"mixed_media"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_thread_stitched_paper_collage",
|
||||
"name": "Thread-Stitched Paper Collage",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, thread-stitched paper collage, pierced paper, visible seams, layered cut paper",
|
||||
"suffix": "thread relief, needle holes, paper grain, hand-sewn irregularity, high detail, tactile texture, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media paper collage held together with visible stitched seams. Lighting: soft raking light that reveals thread relief and pierced paper. Texture: needle holes, paper grain, and hand-sewn irregularity. Key cues: thread-stitched paper collage, pierced paper, visible seams, layered cut paper. Finish cues: thread relief, needle holes, paper grain, hand-sewn irregularity. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media",
|
||||
"thread",
|
||||
"paper"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_tissue_paper_layering",
|
||||
"name": "Tissue Paper Layering",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, tissue paper layering, translucent color veils, wrinkled paper films, stacked fragments",
|
||||
"suffix": "soft wrinkles, color veiling, feathered overlap edges, lightweight translucency, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media layering built from translucent tissue fragments. Lighting: soft top light preserving the color veils and overlap shadows. Texture: tissue wrinkles, feathered edges, and lightweight translucency. Key cues: tissue paper layering, translucent color veils, wrinkled paper films, stacked fragments. Finish cues: soft wrinkles, color veiling, feathered overlap edges, lightweight translucency. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media",
|
||||
"tissue_paper",
|
||||
"layered"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_vellum_overlay_assemblage",
|
||||
"name": "Vellum Overlay Assemblage",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, vellum overlays, translucent sheets, stacked drawing layers, soft veiling",
|
||||
"suffix": "semi-transparent layers, paper curl, subtle overlap shadows, airy depth, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media assemblage built from translucent vellum overlays. Lighting: soft top light creating delicate overlap shadows between sheets. Texture: vellum curl, semi-transparent stacking, and layered veiling. Key cues: vellum overlays, translucent sheets, stacked drawing layers, soft veiling. Finish cues: semi-transparent layers, paper curl, subtle overlap shadows, airy depth. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media",
|
||||
"vellum",
|
||||
"assemblage"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_wax_resist_layers",
|
||||
"name": "Wax Resist Mixed Media",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, wax resist, layered inks, masked paper, drawn resist lines",
|
||||
"suffix": "resist crackle, ink pooling, paper tooth, layered transparency, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media wax-resist artwork with masked lines and layered inks. Lighting: soft diffuse light that keeps paper texture and layered transparencies visible. Texture: resist crackle, pooled pigment, and masked edge variation. Key cues: wax resist, layered inks, masked paper, drawn resist lines. Finish cues: resist crackle, ink pooling, paper tooth, layered transparency. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media",
|
||||
"wax_resist",
|
||||
"layered"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mixed_xerox_transfer_collage",
|
||||
"name": "Xerox Transfer Collage",
|
||||
"category": "Mixed Media",
|
||||
"default": {
|
||||
"prefix": "mixed media artwork, layered textures, handcrafted marks, tactile surface, xerox transfer collage, toner transfer, distressed paper, layered copy fragments",
|
||||
"suffix": "toner wear, uneven transfer, matte paper texture, zine-like edges, high detail, tactile texture, paper grain, layered depth, cohesive composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: mixed media collage built from toner-transfer copy fragments. Lighting: flat diffuse light that keeps paper layers readable. Texture: toner wear, distressed edges, and matte paper grain. Key cues: xerox transfer collage, toner transfer, distressed paper, layered copy fragments. Finish cues: toner wear, uneven transfer, matte paper texture, zine-like edges. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"mixed_media",
|
||||
"transfer",
|
||||
"photocopy"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
{
|
||||
"version": 1,
|
||||
"styles": [
|
||||
{
|
||||
"id": "decor_copper_foil_stained_glass",
|
||||
"name": "Copper Foil Stained Glass",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, copper foil stained glass, soldered seams, colored glass pieces, translucent mosaic",
|
||||
"suffix": "backlit glow, solder line detail, glass texture, jewel-toned translucency, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: copper-foil stained glass with soldered seams and translucent color. Lighting: backlit illumination creating jewel-like glow through the glass. Texture: solder line detail, subtle rippled glass, and luminous color panels. Key cues: copper foil stained glass, soldered seams, colored glass pieces, translucent mosaic. Finish cues: backlit glow, solder line detail, glass texture, jewel-toned translucency. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"glass",
|
||||
"foil"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "decor_pate_de_verre_glass",
|
||||
"name": "Pate de Verre Glass",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, pate de verre glass, granular cast glass, soft translucency, powdered glass color",
|
||||
"suffix": "frosted surface, subtle translucence, cast texture, powder-soft color, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: pate de verre glass with softly cast translucent color. Lighting: soft transmitted light with gentle highlights on frosted surfaces. Texture: granular cast glass, matte translucency, and powder-soft color depth. Key cues: pate de verre glass, granular cast glass, soft translucency, powdered glass color. Finish cues: frosted surface, subtle translucence, cast texture, powder-soft color. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"glass",
|
||||
"cast"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "decor_slumped_kiln_formed_glass",
|
||||
"name": "Slumped Kiln-Formed Glass",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, slumped kiln-formed glass, flowed glass contours, heat-shaped form, smooth translucent surface",
|
||||
"suffix": "soft edge rounding, subtle thickness variation, glass sheen, formed contour shadows, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: kiln-formed slumped glass with heat-shaped contours. Lighting: controlled studio light emphasizing contour thickness and translucency. Texture: smooth glass sheen, softened edges, and gentle thickness variation. Key cues: slumped kiln-formed glass, flowed glass contours, heat-shaped form, smooth translucent surface. Finish cues: soft edge rounding, subtle thickness variation, glass sheen, formed contour shadows. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"glass",
|
||||
"kiln_formed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "decor_raku_crackle_ceramic",
|
||||
"name": "Raku Crackle Ceramic",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, raku ceramic, crackle glaze, smoke-fired surface, irregular luster",
|
||||
"suffix": "black crack lines, smoky glaze variation, porous clay texture, unpredictable firing marks, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: raku-fired ceramic with crackled glaze and smoky variation. Lighting: soft studio light revealing crackle networks and firing irregularities. Texture: black crack lines, porous clay, and unpredictable glaze shifts. Key cues: raku ceramic, crackle glaze, smoke-fired surface, irregular luster. Finish cues: black crack lines, smoky glaze variation, porous clay texture, unpredictable firing marks. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"ceramic",
|
||||
"raku"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "decor_pit_fired_ceramic",
|
||||
"name": "Pit-Fired Ceramic",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, pit-fired ceramic, fire clouds, smoke marks, unglazed clay surface",
|
||||
"suffix": "burnished sheen, smoke staining, earthy mineral variation, handmade surface, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: pit-fired ceramic with smoke clouds and earthy firing marks. Lighting: warm studio light showing burnished highlights and smoke variation. Texture: unglazed clay, mineral staining, and atmospheric fire marking. Key cues: pit-fired ceramic, fire clouds, smoke marks, unglazed clay surface. Finish cues: burnished sheen, smoke staining, earthy mineral variation, handmade surface. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"ceramic",
|
||||
"pit_fired"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "decor_celadon_glaze_stoneware",
|
||||
"name": "Celadon Glaze Stoneware",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, celadon glaze, pale green stoneware, pooled glaze, refined ceramic surface",
|
||||
"suffix": "glaze pooling, cool translucence, soft crackle, subtle sheen, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: celadon-glazed stoneware with cool translucent pooling. Lighting: soft controlled light preserving glaze depth and subtle crackle. Texture: pooled glaze, pale green translucence, and refined ceramic sheen. Key cues: celadon glaze, pale green stoneware, pooled glaze, refined ceramic surface. Finish cues: glaze pooling, cool translucence, soft crackle, subtle sheen. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"ceramic",
|
||||
"celadon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "decor_crystalline_glaze_porcelain",
|
||||
"name": "Crystalline Glaze Porcelain",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, crystalline glaze porcelain, starburst crystal blooms, glossy fired surface, porcelain body",
|
||||
"suffix": "crystal clusters, gloss reflections, glaze depth, smooth porcelain surface, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: crystalline-glazed porcelain with visible starburst crystal blooms. Lighting: controlled highlights emphasizing glossy glaze and crystal structure. Texture: smooth porcelain, glossy depth, and flower-like crystal growth. Key cues: crystalline glaze porcelain, starburst crystal blooms, glossy fired surface, porcelain body. Finish cues: crystal clusters, gloss reflections, glaze depth, smooth porcelain surface. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"ceramic",
|
||||
"crystalline_glaze"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "decor_mishima_inlay_pottery",
|
||||
"name": "Mishima Inlay Pottery",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, mishima inlay pottery, incised lines, contrasting slip inlay, refined ceramic pattern",
|
||||
"suffix": "filled linework, matte glaze, fine carving detail, quiet surface contrast, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: Mishima pottery with incised patterns filled by contrasting slip. Lighting: soft studio light revealing carved linework and matte ceramic surface. Texture: filled incisions, quiet glaze, and fine carved detail. Key cues: mishima inlay pottery, incised lines, contrasting slip inlay, refined ceramic pattern. Finish cues: filled linework, matte glaze, fine carving detail, quiet surface contrast. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"ceramic",
|
||||
"inlay"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "decor_sgraffito_pottery",
|
||||
"name": "Sgraffito Pottery",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, sgraffito pottery, scratched slip decoration, revealed clay body, layered ceramic surface",
|
||||
"suffix": "incised motifs, matte slip texture, fine carving marks, earthy fired surface, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: sgraffito-decorated pottery with scratched slip and revealed clay. Lighting: soft angled light that catches carved ceramic marks. Texture: matte slip, incised motifs, and an earthy fired surface. Key cues: sgraffito pottery, scratched slip decoration, revealed clay body, layered ceramic surface. Finish cues: incised motifs, matte slip texture, fine carving marks, earthy fired surface. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"ceramic",
|
||||
"sgraffito"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "decor_hammered_copper_patina",
|
||||
"name": "Hammered Copper Patina",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, hammered copper, hand-raised metal surface, aged patina, warm metallic tones",
|
||||
"suffix": "hammer marks, oxidized color shifts, soft metallic sheen, worked metal relief, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: hammered copper decorative metalwork with aged patina. Lighting: warm studio light emphasizing hammered relief and patinated color shifts. Texture: hand-raised dimples, oxidized surface, and soft metallic sheen. Key cues: hammered copper, hand-raised metal surface, aged patina, warm metallic tones. Finish cues: hammer marks, oxidized color shifts, soft metallic sheen, worked metal relief. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"metal",
|
||||
"patina"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "decor_repousse_metalwork",
|
||||
"name": "Repousse Metalwork",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, repousse metalwork, raised relief ornament, chased detailing, worked sheet metal",
|
||||
"suffix": "relief contours, fine chased lines, aged metallic finish, ornamental depth, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: repousse metalwork with raised relief and chased detail. Lighting: directional studio light to reveal relief contours and fine tooling. Texture: raised ornament, chased lines, and aged metallic finish. Key cues: repousse metalwork, raised relief ornament, chased detailing, worked sheet metal. Finish cues: relief contours, fine chased lines, aged metallic finish, ornamental depth. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"metal",
|
||||
"repousse"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "decor_guilloche_enamel",
|
||||
"name": "Guilloche Enamel",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, guilloche enamel, engine-turned pattern, translucent enamel layer, radiating metal geometry",
|
||||
"suffix": "subsurface pattern shimmer, gloss enamel finish, precise engraved rhythm, luxury surface, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: guilloche enamel over engine-turned metal patterning. Lighting: controlled highlights that reveal the pattern beneath translucent enamel. Texture: glossy enamel depth, precise engraved rhythm, and subsurface shimmer. Key cues: guilloche enamel, engine-turned pattern, translucent enamel layer, radiating metal geometry. Finish cues: subsurface pattern shimmer, gloss enamel finish, precise engraved rhythm, luxury surface. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"enamel",
|
||||
"guilloche"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "decor_acid_etched_glass",
|
||||
"name": "Acid-Etched Glass",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, acid-etched glass, frosted transparency, matte glass surface, ornamental etching",
|
||||
"suffix": "soft diffusion, satin glass finish, etched motif edges, subtle translucence, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: acid-etched glass with frosted translucency and engraved motifs. Lighting: soft transmitted light with minimal glare to preserve the satin finish. Texture: matte frosted glass, subtle translucence, and etched boundaries. Key cues: acid-etched glass, frosted transparency, matte glass surface, ornamental etching. Finish cues: soft diffusion, satin glass finish, etched motif edges, subtle translucence. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"glass",
|
||||
"etched"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "decor_cut_crystal_faceting",
|
||||
"name": "Cut Crystal Faceting",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, cut crystal, faceted glass, prismatic refraction, precision cutting",
|
||||
"suffix": "sharp facets, caustic sparkle, high clarity, polished reflections, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: cut crystal decorative glass with sharp faceting and prismatic sparkle. Lighting: crisp controlled highlights and small caustics across polished facets. Texture: sharp facet edges, high clarity, and jewel-like reflections. Key cues: cut crystal, faceted glass, prismatic refraction, precision cutting. Finish cues: sharp facets, caustic sparkle, high clarity, polished reflections. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"glass",
|
||||
"crystal"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "decor_lost_wax_cast_bronze",
|
||||
"name": "Lost-Wax Cast Bronze",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, lost-wax cast bronze, cast metal surface, hand-finished patina, sculptural metalwork",
|
||||
"suffix": "cast texture, warm bronze highlights, subtle tooling marks, aged patina depth, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: lost-wax cast bronze with hand-finished patina. Lighting: warm directional light showing cast surface, tooling, and metallic depth. Texture: cast grain, hand-finished marks, and aged bronze patina. Key cues: lost-wax cast bronze, cast metal surface, hand-finished patina, sculptural metalwork. Finish cues: cast texture, warm bronze highlights, subtle tooling marks, aged patina depth. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"bronze",
|
||||
"cast"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "decor_pietra_dura_inlay",
|
||||
"name": "Pietra Dura Stone Inlay",
|
||||
"category": "Decorative Arts",
|
||||
"default": {
|
||||
"prefix": "decorative arts, handcrafted, artisan-made, museum-quality, pietra dura inlay, polished stone mosaic, hardstone floral inlay, precise stone fitting",
|
||||
"suffix": "polished stone sheen, crisp inlay seams, rich mineral color, luxury surface detail, tactile surface, premium craftsmanship, controlled studio lighting, clean composition"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: pietra dura hardstone inlay with tightly fitted polished stone pieces. Lighting: soft controlled light revealing polished mineral surfaces and seam precision. Texture: crisp stone joins, polished sheen, and rich mineral color variation. Key cues: pietra dura inlay, polished stone mosaic, hardstone floral inlay, precise stone fitting. Finish cues: polished stone sheen, crisp inlay seams, rich mineral color, luxury surface detail. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"decorative_arts",
|
||||
"stone",
|
||||
"inlay"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"version": 1,
|
||||
"styles": [
|
||||
{
|
||||
"id": "tech_exploded_cutaway_diagram",
|
||||
"name": "Exploded Cutaway Diagram",
|
||||
"category": "Technical",
|
||||
"default": {
|
||||
"prefix": "technical illustration, clean lines, precise geometry, exploded cutaway view, separated components, sectional clarity",
|
||||
"suffix": "clean line hierarchy, crisp geometry, sectional detail, flat rendering, high legibility"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: technical cutaway illustration with separated components and crisp geometry. Lighting: flat even rendering with no dramatic shading. Composition: clear separations, sectional clarity, and measured structure. Key cues: exploded cutaway view, separated components, technical linework, precise geometry. Finish cues: clean line hierarchy, crisp geometry, sectional detail, flat rendering. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"technical",
|
||||
"cutaway",
|
||||
"diagram"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tech_orthographic_projection_plate",
|
||||
"name": "Orthographic Projection Plate",
|
||||
"category": "Technical",
|
||||
"default": {
|
||||
"prefix": "technical line illustration, orthographic views, aligned geometry, drafting clarity, multiview structure",
|
||||
"suffix": "clean projection lines, measured spacing, flat rendering, crisp technical linework, restrained contrast"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: orthographic technical illustration with aligned multiview geometry. Lighting: flat drafting presentation with minimal tonal variation. Composition: measured spacing, crisp geometry, and projection clarity. Key cues: orthographic views, aligned geometry, drafting clarity, multiview structure. Finish cues: clean projection lines, measured spacing, flat rendering, crisp technical linework. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"technical",
|
||||
"orthographic",
|
||||
"blueprint"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tech_patent_plate_diagram",
|
||||
"name": "Patent Plate Diagram",
|
||||
"category": "Technical",
|
||||
"default": {
|
||||
"prefix": "technical engraving style, fine drafting lines, archival patent aesthetic, precise mechanical illustration, paper grain",
|
||||
"suffix": "fine linework, etched line texture, paper grain, restrained contrast, crisp technical detail"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: archival patent-style technical illustration with fine drafting lines and paper grain. Lighting: flat document light that keeps linework readable. Texture: etched line detail, paper grain, and restrained contrast. Key cues: technical engraving style, archival patent aesthetic, precise mechanical illustration, fine drafting lines. Finish cues: fine linework, etched line texture, paper grain, restrained contrast. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"technical",
|
||||
"patent",
|
||||
"diagram"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tech_section_elevation_drawing",
|
||||
"name": "Section and Elevation Drawing",
|
||||
"category": "Technical",
|
||||
"default": {
|
||||
"prefix": "technical architectural drawing, section cut hatching, elevation linework, measured geometry, clean drafting",
|
||||
"suffix": "hatching clarity, precise line weights, crisp geometry, flat rendering, restrained document contrast"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: architectural technical drawing with section hatching and elevation linework. Lighting: flat presentation emphasizing geometry and line-weight hierarchy. Composition: measured structure, clean hatch patterns, and crisp drafting. Key cues: section cut hatching, elevation linework, measured geometry, clean drafting. Finish cues: hatching clarity, precise line weights, crisp geometry, flat rendering. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"technical",
|
||||
"section",
|
||||
"elevation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "vec_gradient_mesh_illustration",
|
||||
"name": "Gradient Mesh Illustration",
|
||||
"category": "Vector",
|
||||
"default": {
|
||||
"prefix": "vector illustration, flat colors, clean shapes, crisp edges, smooth curves, gradient mesh vector, smooth tonal blends, scalable curves, clean vector shapes",
|
||||
"suffix": "smooth color transitions, crisp bezier edges, clean layout, polished vector finish, consistent stroke weight, crisp edges, smooth curves"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: vector illustration built from gradient-mesh color transitions. Lighting: graphic flat-light treatment with emphasis on controlled tonal blending. Texture: none; clean scalable curves and polished vector edges. Key cues: gradient mesh vector, smooth tonal blends, scalable curves, clean vector shapes. Finish cues: smooth color transitions, crisp bezier edges, clean layout, polished vector finish. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"vector",
|
||||
"gradient_mesh",
|
||||
"illustration"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "vec_monoline_icon_system",
|
||||
"name": "Monoline Icon System",
|
||||
"category": "Vector",
|
||||
"default": {
|
||||
"prefix": "vector illustration, clean shapes, monoline forms, uniform stroke weight, modular geometry, flat graphic style",
|
||||
"suffix": "consistent line weight, crisp curves, balanced spacing, polished vector edges, clean flat finish"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: monoline vector illustration with modular geometry and uniform stroke weight. Lighting: flat graphic treatment with no material shading. Composition: balanced spacing, crisp curves, and scalable clarity. Key cues: monoline forms, uniform stroke weight, modular geometry, flat graphic style. Finish cues: consistent line weight, crisp curves, balanced spacing, polished vector edges. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"vector",
|
||||
"icon",
|
||||
"monoline"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "vec_wayfinding_graphic_system",
|
||||
"name": "Wayfinding Graphic System",
|
||||
"category": "Vector",
|
||||
"default": {
|
||||
"prefix": "vector illustration, bold graphic symbols, flat colors, clean geometry, strong directional shapes, transit-graphic aesthetic",
|
||||
"suffix": "high contrast clarity, clean alignment, modular spacing, crisp vector edges, polished flat finish"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: bold vector graphics with strong directional shapes and flat-color clarity. Lighting: flat design presentation with emphasis on readability. Composition: modular spacing, crisp geometry, and strong symbol silhouettes. Key cues: bold graphic symbols, clean geometry, strong directional shapes, transit-graphic aesthetic. Finish cues: high contrast clarity, clean alignment, modular spacing, crisp vector edges. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"vector",
|
||||
"wayfinding",
|
||||
"infographic"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "vec_flat_isometric_explainer",
|
||||
"name": "Flat Isometric Explainer",
|
||||
"category": "Vector",
|
||||
"default": {
|
||||
"prefix": "vector illustration, flat isometric geometry, clean planes, simplified depth, crisp graphic shapes",
|
||||
"suffix": "crisp isometric edges, controlled flat shadows, clean geometry, polished vector finish, restrained contrast"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: flat isometric vector illustration with clean geometric depth. Lighting: minimal graphic shading with controlled flat shadows. Composition: tidy geometry, crisp edges, and scalable forms. Key cues: flat isometric geometry, clean planes, simplified depth, crisp graphic shapes. Finish cues: crisp isometric edges, controlled flat shadows, clean geometry, polished vector finish. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"vector",
|
||||
"isometric",
|
||||
"diagram"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "pix_one_bit_dithered_pixel",
|
||||
"name": "One-Bit Dithered Pixel",
|
||||
"category": "Pixel Art",
|
||||
"default": {
|
||||
"prefix": "pixel art, 16-bit, limited palette, pixel-perfect, one-bit pixel art, black and white dithering, high contrast sprite look, coarse pixel grid",
|
||||
"suffix": "checker dither, crisp pixels, readable silhouette, hard-edged contrast, dithered shading, sprite-like clarity"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: one-bit pixel art using black and white dithering only. Lighting: implied through dither pattern rather than gradients. Texture: coarse pixel grid, checker dithers, and hard-edged contrast. Key cues: one-bit pixel art, black and white dithering, high contrast sprite look, coarse pixel grid. Finish cues: checker dither, crisp pixels, readable silhouette, hard-edged contrast. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"pixel_art",
|
||||
"one_bit",
|
||||
"dither"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "pix_four_color_handheld_pixel",
|
||||
"name": "Four-Color Handheld Pixel",
|
||||
"category": "Pixel Art",
|
||||
"default": {
|
||||
"prefix": "pixel art, 16-bit, limited palette, pixel-perfect, four-color handheld pixel art, tiny tile shading, portable-screen look",
|
||||
"suffix": "restricted tones, crisp pixels, readable forms, small-scale tile texture, dithered shading, readable silhouette, sprite-like clarity"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: limited-palette handheld pixel art with only four tonal values. Lighting: implied with tiny tile shading and palette restriction. Texture: small pixel grid, restrained tones, and compact sprite readability. Key cues: four-color handheld pixel art, limited palette, tiny tile shading, portable-screen look. Finish cues: restricted tones, crisp pixels, readable forms, small-scale tile texture. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"pixel_art",
|
||||
"handheld",
|
||||
"limited_palette"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "pix_isometric_sprite_sheet",
|
||||
"name": "Isometric Sprite Sheet",
|
||||
"category": "Pixel Art",
|
||||
"default": {
|
||||
"prefix": "pixel art, 16-bit, limited palette, pixel-perfect, isometric sprite sheet, tile-based game art, modular sprites, pixel grid alignment",
|
||||
"suffix": "crisp pixels, modular repetition, readable silhouette, dithered shading, sprite-like clarity"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: isometric sprite-sheet pixel art built from modular game tiles. Lighting: compact pixel shading supporting isometric form readability. Texture: crisp grid alignment, modular repetition, and dithered shading. Key cues: isometric sprite sheet, tile-based game art, modular sprites, pixel grid alignment. Finish cues: crisp pixels, modular repetition, readable silhouette, dithered shading. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"pixel_art",
|
||||
"sprite_sheet",
|
||||
"isometric"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "pix_tilemap_interface",
|
||||
"name": "Tilemap Interface",
|
||||
"category": "Pixel Art",
|
||||
"default": {
|
||||
"prefix": "pixel art, limited palette, crisp pixel grid, retro game overlay aesthetic, chunky interface framing, modular tiles",
|
||||
"suffix": "sharp pixels, clean tile seams, limited tones, compact shading, readable silhouette, sprite clarity"
|
||||
},
|
||||
"models": {
|
||||
"flux_2_klein": {
|
||||
"prefix": "",
|
||||
"suffix": "Style: retro pixel-art overlay with chunky framing and modular tile structure. Lighting: flat pixel shading with palette-limited contrast. Texture: crisp grid alignment, clean tile seams, and sharp pixels. Key cues: retro game overlay aesthetic, chunky interface framing, modular tiles, crisp pixel grid. Finish cues: sharp pixels, clean tile seams, limited tones, compact shading. Instruction: preserve the user prompt subject and add only stylistic cues."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"pixel_art",
|
||||
"tilemap",
|
||||
"interface"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+11329
-16
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,150 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
import nodes
|
||||
import style_library
|
||||
|
||||
|
||||
class FakeTextEncoder:
|
||||
def __init__(self):
|
||||
self.last_text = None
|
||||
self.last_tokens = None
|
||||
|
||||
def tokenize(self, text):
|
||||
self.last_text = text
|
||||
self.last_tokens = {"text": text}
|
||||
return self.last_tokens
|
||||
|
||||
def encode_from_tokens_scheduled(self, tokens):
|
||||
return [[tokens, {"source": tokens["text"]}]]
|
||||
|
||||
|
||||
class PromptStylerTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.library = style_library.get_cached_style_library(load_policy=style_library.LOAD_POLICY_STRICT)
|
||||
cls.stable_node = nodes.PromptStylerConditioning()
|
||||
cls.primary_choice = next(choice for choice in cls.library.choices if choice.endswith("| cinematic_anamorphic"))
|
||||
cls.secondary_choice = next(choice for choice in cls.library.choices if choice.endswith("| cin_film_noir"))
|
||||
|
||||
def test_apply_style_false_returns_raw_prompt(self):
|
||||
encoder = FakeTextEncoder()
|
||||
conditioning, styled_prompt = self.stable_node.encode(
|
||||
prompt="red fox",
|
||||
apply_style=False,
|
||||
style=self.primary_choice,
|
||||
template_variant="default",
|
||||
style_id_override="",
|
||||
text_encoder=encoder,
|
||||
)
|
||||
self.assertEqual("red fox", styled_prompt)
|
||||
self.assertEqual("red fox", encoder.last_text)
|
||||
self.assertEqual(1, len(conditioning))
|
||||
|
||||
def test_style_id_override_takes_precedence(self):
|
||||
encoder = FakeTextEncoder()
|
||||
conditioning, styled_prompt = self.stable_node.encode(
|
||||
prompt="portrait",
|
||||
apply_style=True,
|
||||
style=self.secondary_choice,
|
||||
template_variant="default",
|
||||
style_id_override="cinematic_anamorphic",
|
||||
text_encoder=encoder,
|
||||
)
|
||||
expected_style = style_library.resolve_style_legacy(
|
||||
self.library,
|
||||
choice=self.primary_choice,
|
||||
style_id_override="cinematic_anamorphic",
|
||||
)
|
||||
expected_prompt, _details = style_library.compose_prompt_legacy(
|
||||
"portrait",
|
||||
expected_style,
|
||||
template_variant="default",
|
||||
)
|
||||
self.assertEqual(expected_prompt, styled_prompt)
|
||||
self.assertEqual(expected_prompt, encoder.last_text)
|
||||
self.assertEqual(1, len(conditioning))
|
||||
|
||||
def test_legacy_default_variant_matches_expected_fixture(self):
|
||||
encoder = FakeTextEncoder()
|
||||
conditioning, styled_prompt = self.stable_node.encode(
|
||||
prompt="cinematic film still, robot detective",
|
||||
apply_style=True,
|
||||
style=self.primary_choice,
|
||||
template_variant="default",
|
||||
style_id_override="",
|
||||
text_encoder=encoder,
|
||||
)
|
||||
expected = (
|
||||
"cinematic film still, professional cinematography, cinematic framing, carefully composed shot, "
|
||||
"atmospheric depth, realistic lighting, cinematic, anamorphic look, shallow depth of field, "
|
||||
"dramatic lighting, robot detective, film grain, subtle halation, rich contrast, color graded, "
|
||||
"filmic color science, smooth highlight rolloff, deep blacks, subtle film grain, soft halation, "
|
||||
"high dynamic range"
|
||||
)
|
||||
self.assertEqual(expected, styled_prompt)
|
||||
self.assertEqual(expected, encoder.last_text)
|
||||
self.assertEqual(1, len(conditioning))
|
||||
|
||||
def test_flux_variant_uses_variant_text_when_present(self):
|
||||
encoder = FakeTextEncoder()
|
||||
conditioning, styled_prompt = self.stable_node.encode(
|
||||
prompt="robot detective",
|
||||
apply_style=True,
|
||||
style=self.primary_choice,
|
||||
template_variant="flux_2_klein",
|
||||
style_id_override="",
|
||||
text_encoder=encoder,
|
||||
)
|
||||
expected_style = self.library.by_id["cinematic_anamorphic"]
|
||||
expected_prompt, _details = style_library.compose_prompt_legacy(
|
||||
"robot detective",
|
||||
expected_style,
|
||||
template_variant="flux_2_klein",
|
||||
)
|
||||
self.assertEqual(expected_prompt, styled_prompt)
|
||||
self.assertEqual(expected_prompt, encoder.last_text)
|
||||
self.assertEqual(1, len(conditioning))
|
||||
|
||||
def test_missing_variant_falls_back_to_default(self):
|
||||
style = self.library.by_id["cinematic_anamorphic"]
|
||||
default_prompt, _default_details = style_library.compose_prompt_legacy(
|
||||
"robot detective",
|
||||
style,
|
||||
template_variant="default",
|
||||
)
|
||||
fallback_prompt, details = style_library.compose_prompt_legacy(
|
||||
"robot detective",
|
||||
style,
|
||||
template_variant="missing_variant",
|
||||
)
|
||||
self.assertEqual(default_prompt, fallback_prompt)
|
||||
self.assertEqual(style_library.DEFAULT_VARIANT, details["variant"])
|
||||
|
||||
def test_missing_text_encoder_raises_explicit_error(self):
|
||||
with self.assertRaisesRegex(RuntimeError, "text_encoder input is invalid"):
|
||||
self.stable_node.encode(
|
||||
prompt="robot detective",
|
||||
apply_style=True,
|
||||
style=self.primary_choice,
|
||||
template_variant="default",
|
||||
style_id_override="",
|
||||
text_encoder=None,
|
||||
)
|
||||
|
||||
def test_unknown_style_id_override_raises_error(self):
|
||||
encoder = FakeTextEncoder()
|
||||
with self.assertRaisesRegex(ValueError, "Unknown style_id_override"):
|
||||
self.stable_node.encode(
|
||||
prompt="robot detective",
|
||||
apply_style=True,
|
||||
style=self.primary_choice,
|
||||
template_variant="default",
|
||||
style_id_override="does_not_exist",
|
||||
text_encoder=encoder,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,88 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>ComfyUI PromptStyler - App Summary</title>
|
||||
<style>
|
||||
@page { size: Letter; margin: 0.42in; }
|
||||
:root { --ink:#13212f; --muted:#526171; --rule:#d7dee7; --panel:#f4f7fa; --accent:#1f5f8b; --accent-soft:#dbeaf5; }
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin:0; padding:0; font-family:"Segoe UI", Arial, sans-serif; color:var(--ink); background:#fff; }
|
||||
body { font-size:10.3pt; line-height:1.28; }
|
||||
.hero { display:grid; grid-template-columns:1.55fr 0.95fr; gap:14px; margin-bottom:10px; padding-bottom:10px; border-bottom:1px solid var(--rule); }
|
||||
.title { margin:0 0 4px 0; font-size:22pt; line-height:1.05; letter-spacing:-0.02em; }
|
||||
.subtitle { margin:0; color:var(--muted); font-size:10pt; }
|
||||
.badge { display:inline-block; margin-bottom:8px; padding:4px 8px; border-radius:999px; background:var(--accent-soft); color:var(--accent); font-size:8.5pt; font-weight:700; text-transform:uppercase; letter-spacing:0.06em; }
|
||||
.meta { background:linear-gradient(180deg, #f9fbfd 0%, var(--panel) 100%); border:1px solid var(--rule); border-radius:10px; padding:10px 11px; }
|
||||
.meta-row { margin:0 0 8px 0; }
|
||||
.meta-row:last-child { margin-bottom:0; }
|
||||
.label { display:block; margin-bottom:2px; color:var(--muted); font-size:8.2pt; font-weight:700; text-transform:uppercase; letter-spacing:0.05em; }
|
||||
.value { margin:0; font-size:9.9pt; }
|
||||
.grid { display:grid; grid-template-columns:1fr 1fr; gap:12px 14px; }
|
||||
section { break-inside:avoid; }
|
||||
h2 { margin:0 0 6px 0; padding-bottom:3px; border-bottom:1px solid var(--rule); font-size:10pt; letter-spacing:0.02em; text-transform:uppercase; }
|
||||
p { margin:0 0 6px 0; }
|
||||
ul { margin:0; padding-left:16px; }
|
||||
li { margin:0 0 4px 0; }
|
||||
.architecture { grid-column:1 / -1; }
|
||||
.small { font-size:9.1pt; color:var(--muted); }
|
||||
.code { font-family:Consolas, "Courier New", monospace; font-size:9pt; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="hero">
|
||||
<div>
|
||||
<div class="badge">Repo Summary</div>
|
||||
<h1 class="title">ComfyUI PromptStyler</h1>
|
||||
<p class="subtitle">Version 3.1.1. Custom ComfyUI node pack that applies curated style templates to a positive prompt and returns conditioning for downstream generation. It is structured around JSON style packs plus lightweight Python runtime code that composes the final prompt text at node execution time.</p>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<div class="meta-row">
|
||||
<span class="label">Who It’s For</span>
|
||||
<p class="value">Primary persona: ComfyUI users building image-generation workflows who want reusable, curated prompt styling without hand-writing long style suffixes.</p>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="label">Install Shape</span>
|
||||
<p class="value">Drop the repo into <span class="code">ComfyUI/custom_nodes/ComfyUI_PromptStyler</span> and reload custom nodes.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<section>
|
||||
<h2>What It Does</h2>
|
||||
<ul>
|
||||
<li>Exposes a ComfyUI node named <span class="code">PromptStyler: Prompt -> Conditioning (Style Picker)</span>.</li>
|
||||
<li>Loads style templates from <span class="code">styles/packs/*.json</span> in filename order.</li>
|
||||
<li>Falls back to <span class="code">styles/styles_v1.json</span> if packs are missing or unusable.</li>
|
||||
<li>Presents styles as categorized dropdown choices in <span class="code">Category | Name | id</span> format.</li>
|
||||
<li>Supports direct <span class="code">style_id_override</span> and a <span class="code">template_variant</span> selector.</li>
|
||||
<li>Builds a deduplicated styled prompt, then tokenizes and encodes it through the connected CLIP text encoder.</li>
|
||||
<li>Ships maintainer tools to validate, audit, generate, sync, and add style packs.</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h2>How To Run</h2>
|
||||
<ul>
|
||||
<li>Clone or copy this folder into <span class="code">ComfyUI/custom_nodes/ComfyUI_PromptStyler</span>.</li>
|
||||
<li>Restart ComfyUI or use "Reload custom nodes".</li>
|
||||
<li>Add <span class="code">CheckpointLoaderSimple</span>, PromptStyler, and <span class="code">KSampler</span> to a workflow.</li>
|
||||
<li>Wire <span class="code">CLIP -> text_encoder</span>, your prompt text into <span class="code">prompt</span>, and <span class="code">positive -> KSampler.positive</span>.</li>
|
||||
<li>Optional repo validation: run <span class="code">tools/validate_styles.py</span>; audit with <span class="code">tools/audit_styles.py</span>.</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="architecture">
|
||||
<h2>How It Works</h2>
|
||||
<ul>
|
||||
<li><span class="code">__init__.py</span> is the ComfyUI entry point: it reads <span class="code">VERSION</span> and exports node mappings.</li>
|
||||
<li><span class="code">nodes.py</span> defines the runtime node that accepts the user prompt, style selection inputs, and a ComfyUI <span class="code">CLIP</span> text encoder connection.</li>
|
||||
<li>The node scans <span class="code">styles/packs/</span>, merges JSON packs in sorted order, or falls back to the legacy single-file library.</li>
|
||||
<li>Selected style prefix/suffix phrases are combined with the user prompt, normalized, and de-duplicated before encoding.</li>
|
||||
<li>The encoded result is returned as <span class="code">CONDITIONING</span> plus the exact <span class="code">styled_prompt</span> string for inspection/debugging.</li>
|
||||
<li><span class="code">style_library.py</span>, tests, and maintainer docs describe broader search/overlay logic, but an advanced node implementation was <strong>Not found in repo</strong> in the current <span class="code">nodes.py</span>.</li>
|
||||
<li><span class="code">.github/workflows/ci.yml</span> runs version sync, style validation, style audit, and unit tests on pushes and pull requests.</li>
|
||||
</ul>
|
||||
<p class="small">If you need external services, hosted APIs, or a packaged installer: <strong>Not found in repo</strong>.</p>
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
+161
-252
@@ -1,63 +1,43 @@
|
||||
"""
|
||||
PromptStyler style pack tool.
|
||||
|
||||
This script helps you add more styles without editing python source by hand.
|
||||
This script helps you add more styles without editing JSON by hand.
|
||||
It writes (or updates) a JSON pack under styles/packs/, e.g.:
|
||||
styles/packs/99_user_custom.json
|
||||
|
||||
Usage examples:
|
||||
|
||||
# List categories that exist (including ones supported by the generator)
|
||||
C:\\Comfyui\\python_embeded\\python.exe .\\tools\\add_styles.py categories
|
||||
|
||||
# Add one style (core -> goes into prefix, details -> goes into suffix)
|
||||
C:\\Comfyui\\python_embeded\\python.exe .\\tools\\add_styles.py add ^
|
||||
--name "Anime Style (Soft Shading)" ^
|
||||
--category "Anime/Manga" ^
|
||||
--core "soft cel shading, warm palette, gentle gradients" ^
|
||||
--details "clean line weight, subtle texture, high clarity" ^
|
||||
--tags "anime, manga, illustration"
|
||||
|
||||
# Bulk add from CSV (columns: name,category,core,details,tags)
|
||||
C:\\Comfyui\\python_embeded\\python.exe .\\tools\\add_styles.py bulk --csv .\\tools\\new_styles.csv
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import Counter
|
||||
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple
|
||||
from typing import Any, Dict, List, Optional, Sequence
|
||||
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
if ROOT not in sys.path:
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
from style_library import (
|
||||
CATEGORY_BASE_PREFIX,
|
||||
CATEGORY_BASE_SUFFIX,
|
||||
LOAD_POLICY_STRICT,
|
||||
StyleLibraryError,
|
||||
available_categories,
|
||||
flux_join_sentences,
|
||||
load_style_library,
|
||||
z_join,
|
||||
)
|
||||
|
||||
|
||||
PACKS_DIR = os.path.join(ROOT, "styles", "packs")
|
||||
DEFAULT_PACK_PATH = os.path.join(PACKS_DIR, "99_user_custom.json")
|
||||
|
||||
|
||||
def _load_generator_module():
|
||||
"""
|
||||
Import tools/generate_style_packs.py as a module so we can reuse:
|
||||
- CATEGORY_BASE_PREFIX / CATEGORY_BASE_SUFFIX
|
||||
- z_join() (which de-dupes)
|
||||
"""
|
||||
path = os.path.join(ROOT, "tools", "generate_style_packs.py")
|
||||
spec = importlib.util.spec_from_file_location("promptstyler_generate_style_packs", path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError(f"Unable to import generator module: {path}")
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
# dataclasses (on some Python versions) expects the module to already exist in sys.modules
|
||||
# when evaluating annotations. Insert before exec_module to avoid NoneType errors.
|
||||
sys.modules[spec.name] = mod
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
def _slugify(name: str) -> str:
|
||||
base = "".join(ch.lower() if ch.isalnum() else "_" for ch in (name or "")).strip("_")
|
||||
while "__" in base:
|
||||
@@ -66,12 +46,6 @@ def _slugify(name: str) -> str:
|
||||
|
||||
|
||||
def _normalize_user_subcategory(value: str) -> str:
|
||||
"""
|
||||
Normalize a user subcategory string for "User/<subcategory>" categories:
|
||||
- keep letters/numbers/spaces
|
||||
- replace everything else with spaces
|
||||
- collapse whitespace
|
||||
"""
|
||||
cleaned = "".join(ch if (ch.isalnum() or ch.isspace()) else " " for ch in (value or ""))
|
||||
cleaned = " ".join(cleaned.split()).strip()
|
||||
return cleaned or "Custom"
|
||||
@@ -80,137 +54,52 @@ def _normalize_user_subcategory(value: str) -> str:
|
||||
def _split_csv_list(value: Optional[str]) -> List[str]:
|
||||
if not value:
|
||||
return []
|
||||
return [p.strip() for p in value.split(",") if p.strip()]
|
||||
return [part.strip() for part in value.split(",") if part.strip()]
|
||||
|
||||
|
||||
def _read_json(path: str) -> Dict[str, Any]:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f) or {}
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
return json.load(handle) or {}
|
||||
|
||||
|
||||
def _write_json(path: str, data: Dict[str, Any]) -> None:
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=True)
|
||||
f.write("\n")
|
||||
with open(path, "w", encoding="utf-8") as handle:
|
||||
json.dump(data, handle, indent=2, ensure_ascii=True)
|
||||
handle.write("\n")
|
||||
|
||||
|
||||
def _iter_pack_paths() -> Iterable[str]:
|
||||
if not os.path.isdir(PACKS_DIR):
|
||||
return []
|
||||
for name in sorted(os.listdir(PACKS_DIR)):
|
||||
if name.lower().endswith(".json"):
|
||||
yield os.path.join(PACKS_DIR, name)
|
||||
def _load_library():
|
||||
return load_style_library(load_policy=LOAD_POLICY_STRICT)
|
||||
|
||||
|
||||
def _load_all_styles() -> List[Dict[str, Any]]:
|
||||
styles: List[Dict[str, Any]] = []
|
||||
for path in _iter_pack_paths():
|
||||
try:
|
||||
data = _read_json(path)
|
||||
except Exception:
|
||||
# If a pack is broken, keep going; validate_styles.py will catch it.
|
||||
continue
|
||||
styles.extend(data.get("styles", []) or [])
|
||||
return styles
|
||||
def _existing_identity_sets() -> tuple[set[str], set[str]]:
|
||||
library = _load_library()
|
||||
return ({style.id for style in library.styles}, {style.name for style in library.styles})
|
||||
|
||||
|
||||
def _ensure_unique_id(style_id: str, existing_ids: set) -> str:
|
||||
def _ensure_unique_id(style_id: str, existing_ids: set[str]) -> str:
|
||||
if style_id not in existing_ids:
|
||||
return style_id
|
||||
i = 2
|
||||
while True:
|
||||
cand = f"{style_id}_{i}"
|
||||
if cand not in existing_ids:
|
||||
return cand
|
||||
candidate = f"{style_id}_{i}"
|
||||
if candidate not in existing_ids:
|
||||
return candidate
|
||||
i += 1
|
||||
|
||||
|
||||
def _ensure_unique_name(name: str, existing_names: set) -> str:
|
||||
def _ensure_unique_name(name: str, existing_names: set[str]) -> str:
|
||||
if name not in existing_names:
|
||||
return name
|
||||
i = 2
|
||||
while True:
|
||||
cand = f"{name} ({i})"
|
||||
if cand not in existing_names:
|
||||
return cand
|
||||
candidate = f"{name} ({i})"
|
||||
if candidate not in existing_names:
|
||||
return candidate
|
||||
i += 1
|
||||
|
||||
|
||||
def _available_categories(existing_styles: Sequence[Dict[str, Any]], gen_mod) -> List[str]:
|
||||
cats = set()
|
||||
for s in existing_styles:
|
||||
c = s.get("category")
|
||||
if isinstance(c, str) and c.strip():
|
||||
cats.add(c.strip())
|
||||
# Also include categories we have base templates for.
|
||||
cats.update((gen_mod.CATEGORY_BASE_PREFIX or {}).keys())
|
||||
cats.update((gen_mod.CATEGORY_BASE_SUFFIX or {}).keys())
|
||||
return sorted(cats, key=lambda x: x.casefold())
|
||||
|
||||
|
||||
def _make_style_entry(
|
||||
gen_mod,
|
||||
*,
|
||||
name: str,
|
||||
category: str,
|
||||
core: Sequence[str],
|
||||
details: Sequence[str],
|
||||
tags: Sequence[str],
|
||||
style_id: Optional[str],
|
||||
id_prefix: str,
|
||||
flux_suffix: Optional[str] = None,
|
||||
existing_ids: Optional[set] = None,
|
||||
existing_names: Optional[set] = None,
|
||||
) -> Dict[str, Any]:
|
||||
if existing_ids is None or existing_names is None:
|
||||
all_styles = _load_all_styles()
|
||||
existing_ids = {str(s.get("id")) for s in all_styles if s.get("id") is not None}
|
||||
existing_names = {str(s.get("name")) for s in all_styles if s.get("name") is not None}
|
||||
|
||||
base_id = style_id.strip() if style_id else f"{id_prefix}_{_slugify(name)}"
|
||||
final_id = _ensure_unique_id(base_id, existing_ids)
|
||||
final_name = _ensure_unique_name(name.strip(), existing_names)
|
||||
existing_ids.add(final_id)
|
||||
existing_names.add(final_name)
|
||||
|
||||
base_prefix = tuple((gen_mod.CATEGORY_BASE_PREFIX or {}).get(category, ()))
|
||||
base_suffix = tuple((gen_mod.CATEGORY_BASE_SUFFIX or {}).get(category, ()))
|
||||
|
||||
prefix = gen_mod.z_join(tuple(base_prefix) + tuple(core))
|
||||
suffix = gen_mod.z_join(tuple(details) + tuple(base_suffix))
|
||||
|
||||
tags2 = [t.strip() for t in tags if (t or "").strip()]
|
||||
if not tags2:
|
||||
tags2 = ["user"]
|
||||
|
||||
if flux_suffix is None:
|
||||
core_hint = gen_mod.z_join(core[:12]) if core else ""
|
||||
detail_hint = gen_mod.z_join(details[:12]) if details else ""
|
||||
parts = [f"Style: {final_name}"]
|
||||
if core_hint:
|
||||
parts.append(f"Core cues: {core_hint}")
|
||||
if detail_hint:
|
||||
parts.append(f"Details: {detail_hint}")
|
||||
parts.append("Lighting: coherent and intentional")
|
||||
parts.append("Mood: consistent with the user prompt")
|
||||
flux_suffix2 = gen_mod.flux_join_sentences(parts)
|
||||
else:
|
||||
flux_suffix2 = str(flux_suffix or "").strip()
|
||||
if flux_suffix2 and not re.search(r"[.!?]$", flux_suffix2):
|
||||
flux_suffix2 = flux_suffix2 + "."
|
||||
|
||||
entry: Dict[str, Any] = {
|
||||
"id": final_id,
|
||||
"name": final_name,
|
||||
"category": category,
|
||||
"default": {"prefix": prefix, "suffix": suffix},
|
||||
"models": {"flux_2_klein": {"prefix": "", "suffix": flux_suffix2}},
|
||||
"tags": tags2,
|
||||
}
|
||||
return entry
|
||||
|
||||
|
||||
def _load_or_init_pack(path: str) -> Dict[str, Any]:
|
||||
if os.path.isfile(path):
|
||||
data = _read_json(path)
|
||||
@@ -224,41 +113,109 @@ def _load_or_init_pack(path: str) -> Dict[str, Any]:
|
||||
return {"version": 1, "styles": []}
|
||||
|
||||
|
||||
def cmd_categories(args) -> int:
|
||||
gen_mod = _load_generator_module()
|
||||
styles = _load_all_styles()
|
||||
cats = _available_categories(styles, gen_mod)
|
||||
counts = Counter(s.get("category", "Uncategorized") for s in styles)
|
||||
for c in cats:
|
||||
print(f"{c} ({counts.get(c, 0)})")
|
||||
def _make_style_entry(
|
||||
*,
|
||||
name: str,
|
||||
category: str,
|
||||
core: Sequence[str],
|
||||
details: Sequence[str],
|
||||
tags: Sequence[str],
|
||||
style_id: Optional[str],
|
||||
id_prefix: str,
|
||||
flux_suffix: Optional[str] = None,
|
||||
existing_ids: Optional[set[str]] = None,
|
||||
existing_names: Optional[set[str]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
if existing_ids is None or existing_names is None:
|
||||
existing_ids, existing_names = _existing_identity_sets()
|
||||
|
||||
base_id = style_id.strip() if style_id else f"{id_prefix}_{_slugify(name)}"
|
||||
final_id = _ensure_unique_id(base_id, existing_ids)
|
||||
final_name = _ensure_unique_name(name.strip(), existing_names)
|
||||
existing_ids.add(final_id)
|
||||
existing_names.add(final_name)
|
||||
|
||||
prefix = z_join(tuple(CATEGORY_BASE_PREFIX.get(category, ())) + tuple(core))
|
||||
suffix = z_join(tuple(details) + tuple(CATEGORY_BASE_SUFFIX.get(category, ())))
|
||||
|
||||
tags2 = [tag.strip() for tag in tags if (tag or "").strip()]
|
||||
if not tags2:
|
||||
tags2 = ["user"]
|
||||
|
||||
if flux_suffix is None:
|
||||
parts = [f"Style: {final_name}"]
|
||||
core_hint = z_join(core[:12]) if core else ""
|
||||
detail_hint = z_join(details[:12]) if details else ""
|
||||
if core_hint:
|
||||
parts.append(f"Core cues: {core_hint}")
|
||||
if detail_hint:
|
||||
parts.append(f"Details: {detail_hint}")
|
||||
parts.append("Lighting: coherent and intentional")
|
||||
parts.append("Mood: consistent with the user prompt")
|
||||
flux_suffix2 = flux_join_sentences(parts)
|
||||
else:
|
||||
flux_suffix2 = str(flux_suffix or "").strip()
|
||||
if flux_suffix2 and not re.search(r"[.!?]$", flux_suffix2):
|
||||
flux_suffix2 += "."
|
||||
|
||||
return {
|
||||
"id": final_id,
|
||||
"name": final_name,
|
||||
"category": category,
|
||||
"default": {"prefix": prefix, "suffix": suffix},
|
||||
"models": {"flux_2_klein": {"prefix": "", "suffix": flux_suffix2}},
|
||||
"tags": tags2,
|
||||
}
|
||||
|
||||
|
||||
def _read_bulk_csv(path: str) -> List[Dict[str, str]]:
|
||||
rows: List[Dict[str, str]] = []
|
||||
with open(path, "r", encoding="utf-8-sig", newline="") as handle:
|
||||
reader = csv.DictReader(handle)
|
||||
for row in reader:
|
||||
if not row:
|
||||
continue
|
||||
rows.append({key.strip(): (value or "").strip() for key, value in row.items() if key})
|
||||
return rows
|
||||
|
||||
|
||||
def _read_bulk_json(path: str) -> List[Dict[str, Any]]:
|
||||
data = _read_json(path)
|
||||
if isinstance(data, dict) and "styles" in data:
|
||||
data = data["styles"]
|
||||
if not isinstance(data, list):
|
||||
raise ValueError("JSON bulk file must be a list (or an object with a 'styles' list).")
|
||||
return [item for item in data if isinstance(item, dict)]
|
||||
|
||||
|
||||
def cmd_categories(_args) -> int:
|
||||
library = _load_library()
|
||||
counts = Counter(style.category or "Uncategorized" for style in library.styles)
|
||||
for category in available_categories(library.styles):
|
||||
print(f"{category} ({counts.get(category, 0)})")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_stats(args) -> int:
|
||||
styles = _load_all_styles()
|
||||
counts = Counter(s.get("category", "Uncategorized") for s in styles)
|
||||
def cmd_stats(_args) -> int:
|
||||
library = _load_library()
|
||||
counts = Counter(style.category or "Uncategorized" for style in library.styles)
|
||||
total = sum(counts.values())
|
||||
print(f"styles {total}")
|
||||
for c, n in counts.most_common():
|
||||
print(f"{n:4d} {c}")
|
||||
for category, count in counts.most_common():
|
||||
print(f"{count:4d} {category}")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_add(args) -> int:
|
||||
gen_mod = _load_generator_module()
|
||||
pack_path = os.path.abspath(args.pack)
|
||||
pack = _load_or_init_pack(pack_path)
|
||||
|
||||
all_styles = _load_all_styles()
|
||||
existing_ids = {str(s.get("id")) for s in all_styles if s.get("id") is not None}
|
||||
existing_names = {str(s.get("name")) for s in all_styles if s.get("name") is not None}
|
||||
existing_ids, existing_names = _existing_identity_sets()
|
||||
|
||||
core = _split_csv_list(args.core) + _split_csv_list(args.prefix_extra)
|
||||
details = _split_csv_list(args.details) + _split_csv_list(args.suffix_extra)
|
||||
tags = _split_csv_list(args.tags)
|
||||
|
||||
entry = _make_style_entry(
|
||||
gen_mod,
|
||||
name=args.name,
|
||||
category=args.category,
|
||||
core=core,
|
||||
@@ -273,7 +230,11 @@ def cmd_add(args) -> int:
|
||||
pack["styles"].append(entry)
|
||||
pack["styles"] = sorted(
|
||||
pack["styles"],
|
||||
key=lambda s: (str(s.get("category", "")).casefold(), str(s.get("name", "")).casefold(), str(s.get("id", ""))),
|
||||
key=lambda style: (
|
||||
str(style.get("category", "")).casefold(),
|
||||
str(style.get("name", "")).casefold(),
|
||||
str(style.get("id", "")),
|
||||
),
|
||||
)
|
||||
_write_json(pack_path, pack)
|
||||
|
||||
@@ -282,39 +243,10 @@ def cmd_add(args) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _read_bulk_csv(path: str) -> List[Dict[str, str]]:
|
||||
rows: List[Dict[str, str]] = []
|
||||
with open(path, "r", encoding="utf-8-sig", newline="") as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
if not row:
|
||||
continue
|
||||
rows.append({k.strip(): (v or "").strip() for k, v in row.items() if k})
|
||||
return rows
|
||||
|
||||
|
||||
def _read_bulk_json(path: str) -> List[Dict[str, Any]]:
|
||||
data = _read_json(path)
|
||||
if isinstance(data, dict) and "styles" in data:
|
||||
data = data["styles"]
|
||||
if not isinstance(data, list):
|
||||
raise ValueError("JSON bulk file must be a list (or an object with a 'styles' list).")
|
||||
out: List[Dict[str, Any]] = []
|
||||
for item in data:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
out.append(item)
|
||||
return out
|
||||
|
||||
|
||||
def cmd_bulk(args) -> int:
|
||||
gen_mod = _load_generator_module()
|
||||
pack_path = os.path.abspath(args.pack)
|
||||
pack = _load_or_init_pack(pack_path)
|
||||
|
||||
all_styles = _load_all_styles()
|
||||
existing_ids = {str(s.get("id")) for s in all_styles if s.get("id") is not None}
|
||||
existing_names = {str(s.get("name")) for s in all_styles if s.get("name") is not None}
|
||||
existing_ids, existing_names = _existing_identity_sets()
|
||||
|
||||
if args.csv:
|
||||
items = _read_bulk_csv(os.path.abspath(args.csv))
|
||||
@@ -330,22 +262,15 @@ def cmd_bulk(args) -> int:
|
||||
if not name or not category:
|
||||
continue
|
||||
|
||||
core = _split_csv_list(item.get("core")) + _split_csv_list(item.get("prefix_extra"))
|
||||
details = _split_csv_list(item.get("details")) + _split_csv_list(item.get("suffix_extra"))
|
||||
tags = _split_csv_list(item.get("tags"))
|
||||
style_id = (item.get("id") or "").strip() or None
|
||||
flux_suffix = (item.get("flux") or item.get("flux_suffix") or "").strip() or None
|
||||
|
||||
entry = _make_style_entry(
|
||||
gen_mod,
|
||||
name=name,
|
||||
category=category,
|
||||
core=core,
|
||||
details=details,
|
||||
tags=tags,
|
||||
style_id=style_id,
|
||||
core=_split_csv_list(item.get("core")) + _split_csv_list(item.get("prefix_extra")),
|
||||
details=_split_csv_list(item.get("details")) + _split_csv_list(item.get("suffix_extra")),
|
||||
tags=_split_csv_list(item.get("tags")),
|
||||
style_id=(item.get("id") or "").strip() or None,
|
||||
id_prefix=args.id_prefix,
|
||||
flux_suffix=flux_suffix,
|
||||
flux_suffix=(item.get("flux") or item.get("flux_suffix") or "").strip() or None,
|
||||
existing_ids=existing_ids,
|
||||
existing_names=existing_names,
|
||||
)
|
||||
@@ -354,7 +279,11 @@ def cmd_bulk(args) -> int:
|
||||
|
||||
pack["styles"] = sorted(
|
||||
pack["styles"],
|
||||
key=lambda s: (str(s.get("category", "")).casefold(), str(s.get("name", "")).casefold(), str(s.get("id", ""))),
|
||||
key=lambda style: (
|
||||
str(style.get("category", "")).casefold(),
|
||||
str(style.get("name", "")).casefold(),
|
||||
str(style.get("id", "")),
|
||||
),
|
||||
)
|
||||
_write_json(pack_path, pack)
|
||||
print(f"Added {added} styles to {pack_path}")
|
||||
@@ -364,55 +293,35 @@ def cmd_bulk(args) -> int:
|
||||
def _prompt_line(label: str, *, default: Optional[str] = None) -> str:
|
||||
if default is None:
|
||||
return input(f"{label}: ").strip()
|
||||
v = input(f"{label} [{default}]: ").strip()
|
||||
return v if v else default
|
||||
value = input(f"{label} [{default}]: ").strip()
|
||||
return value if value else default
|
||||
|
||||
|
||||
def cmd_wizard(args) -> int:
|
||||
"""
|
||||
Interactive style creation helper.
|
||||
|
||||
Writes (or updates) a JSON pack under styles/packs/, defaulting to:
|
||||
styles/packs/99_user_custom.json
|
||||
|
||||
User styles are always categorized under: User/<subcategory>
|
||||
"""
|
||||
gen_mod = _load_generator_module()
|
||||
pack_path = os.path.abspath(args.pack)
|
||||
pack = _load_or_init_pack(pack_path)
|
||||
|
||||
all_styles = _load_all_styles()
|
||||
existing_ids = {str(s.get("id")) for s in all_styles if s.get("id") is not None}
|
||||
existing_names = {str(s.get("name")) for s in all_styles if s.get("name") is not None}
|
||||
existing_ids, existing_names = _existing_identity_sets()
|
||||
|
||||
print("PromptStyler: User Style Wizard")
|
||||
print("Tip: use comma-separated phrases (node splits default templates on ', ').")
|
||||
print("")
|
||||
|
||||
subcat_raw = _prompt_line("User subcategory (creates category User/<subcategory>)", default="Custom")
|
||||
subcat = _normalize_user_subcategory(subcat_raw)
|
||||
subcat = _normalize_user_subcategory(
|
||||
_prompt_line("User subcategory (creates category User/<subcategory>)", default="Custom")
|
||||
)
|
||||
category = f"User/{subcat}"
|
||||
|
||||
name = _prompt_line("Style name")
|
||||
if not name.strip():
|
||||
print("ERROR: style name is required")
|
||||
return 2
|
||||
|
||||
core_raw = _prompt_line("Core descriptors (comma-separated)", default="")
|
||||
details_raw = _prompt_line("Detail descriptors (comma-separated)", default="")
|
||||
tags_raw = _prompt_line("Tags (comma-separated, optional)", default="")
|
||||
|
||||
core = _split_csv_list(core_raw)
|
||||
details = _split_csv_list(details_raw)
|
||||
tags = _split_csv_list(tags_raw)
|
||||
|
||||
# Always tag user styles for easier filtering/searching.
|
||||
core = _split_csv_list(_prompt_line("Core descriptors (comma-separated)", default=""))
|
||||
details = _split_csv_list(_prompt_line("Detail descriptors (comma-separated)", default=""))
|
||||
tags = _split_csv_list(_prompt_line("Tags (comma-separated, optional)", default=""))
|
||||
tags.extend(["user", _slugify(subcat)])
|
||||
|
||||
flux = _prompt_line("Flux suffix (optional; blank to auto-generate)", default="").strip() or None
|
||||
flux_suffix = _prompt_line("Flux suffix (optional; blank to auto-generate)", default="").strip() or None
|
||||
|
||||
entry = _make_style_entry(
|
||||
gen_mod,
|
||||
name=name,
|
||||
category=category,
|
||||
core=core,
|
||||
@@ -420,7 +329,7 @@ def cmd_wizard(args) -> int:
|
||||
tags=tags,
|
||||
style_id=None,
|
||||
id_prefix=args.id_prefix,
|
||||
flux_suffix=flux,
|
||||
flux_suffix=flux_suffix,
|
||||
existing_ids=existing_ids,
|
||||
existing_names=existing_names,
|
||||
)
|
||||
@@ -444,7 +353,11 @@ def cmd_wizard(args) -> int:
|
||||
pack["styles"].append(entry)
|
||||
pack["styles"] = sorted(
|
||||
pack["styles"],
|
||||
key=lambda s: (str(s.get("category", "")).casefold(), str(s.get("name", "")).casefold(), str(s.get("id", ""))),
|
||||
key=lambda style: (
|
||||
str(style.get("category", "")).casefold(),
|
||||
str(style.get("name", "")).casefold(),
|
||||
str(style.get("id", "")),
|
||||
),
|
||||
)
|
||||
_write_json(pack_path, pack)
|
||||
|
||||
@@ -471,16 +384,8 @@ def main(argv: Optional[List[str]] = None) -> int:
|
||||
p_add.add_argument("--id", default=None, help="Optional explicit id (must be unique)")
|
||||
p_add.add_argument("--name", required=True, help="Style display name")
|
||||
p_add.add_argument("--category", required=True, help="Style category (as shown in the dropdown)")
|
||||
p_add.add_argument(
|
||||
"--core",
|
||||
default="",
|
||||
help="Comma-separated core descriptors (added to prefix after category base)",
|
||||
)
|
||||
p_add.add_argument(
|
||||
"--details",
|
||||
default="",
|
||||
help="Comma-separated detail descriptors (added to suffix before category base)",
|
||||
)
|
||||
p_add.add_argument("--core", default="", help="Comma-separated core descriptors")
|
||||
p_add.add_argument("--details", default="", help="Comma-separated detail descriptors")
|
||||
p_add.add_argument("--prefix-extra", default="", help="Alias for --core")
|
||||
p_add.add_argument("--suffix-extra", default="", help="Alias for --details")
|
||||
p_add.add_argument("--tags", default="", help="Comma-separated tags")
|
||||
@@ -499,7 +404,11 @@ def main(argv: Optional[List[str]] = None) -> int:
|
||||
p_wizard.set_defaults(func=cmd_wizard)
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
return int(args.func(args))
|
||||
try:
|
||||
return int(args.func(args))
|
||||
except StyleLibraryError as exc:
|
||||
print(f"ERROR: {exc}")
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+59
-98
@@ -1,127 +1,91 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import Counter
|
||||
from typing import Any, Dict, Iterable, List, Tuple
|
||||
|
||||
from typing import List, Tuple
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
PACKS_DIR = os.path.join(ROOT, "styles", "packs")
|
||||
if ROOT not in sys.path:
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
from style_library import LOAD_POLICY_STRICT, StyleLibraryError, iter_pack_paths, load_style_library
|
||||
|
||||
|
||||
def _iter_pack_paths() -> Iterable[str]:
|
||||
if not os.path.isdir(PACKS_DIR):
|
||||
return []
|
||||
for name in sorted(os.listdir(PACKS_DIR)):
|
||||
if name.lower().endswith(".json"):
|
||||
yield os.path.join(PACKS_DIR, name)
|
||||
|
||||
|
||||
def _load_all_styles() -> Tuple[List[Dict[str, Any]], List[str]]:
|
||||
styles: List[Dict[str, Any]] = []
|
||||
bad_packs: List[str] = []
|
||||
for path in _iter_pack_paths():
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f) or {}
|
||||
except Exception:
|
||||
bad_packs.append(path)
|
||||
continue
|
||||
styles.extend(data.get("styles", []) or [])
|
||||
return styles, bad_packs
|
||||
ID_RE = re.compile(r"^[a-z0-9_]+$")
|
||||
COMMA_RE = re.compile(r",(?!\s)")
|
||||
BANNED_GEAR_TERMS = (
|
||||
"softbox",
|
||||
"soft box",
|
||||
"strobe",
|
||||
"speedlight",
|
||||
"beauty dish",
|
||||
"octabox",
|
||||
"umbrella",
|
||||
"ring light",
|
||||
"ringlight",
|
||||
"on-camera",
|
||||
"on camera",
|
||||
"tripod shot",
|
||||
)
|
||||
BANNED_GEAR_RES = [(term, re.compile(re.escape(term), re.IGNORECASE)) for term in BANNED_GEAR_TERMS]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
styles, bad_packs = _load_all_styles()
|
||||
try:
|
||||
library = load_style_library(load_policy=LOAD_POLICY_STRICT)
|
||||
except StyleLibraryError as exc:
|
||||
print(f"ERROR: {exc}")
|
||||
return 2
|
||||
|
||||
if bad_packs:
|
||||
print("WARN: unable to read some packs:")
|
||||
for p in bad_packs:
|
||||
print(f" - {p}")
|
||||
|
||||
ids = [str(s.get("id", "")) for s in styles]
|
||||
names = [str(s.get("name", "")) for s in styles]
|
||||
cats = [str(s.get("category", "Uncategorized")) for s in styles]
|
||||
styles = list(library.styles)
|
||||
ids = [style.id for style in styles]
|
||||
names = [style.name for style in styles]
|
||||
categories = [style.category or "Uncategorized" for style in styles]
|
||||
|
||||
print(f"styles: {len(styles)}")
|
||||
print(f"packs: {len(list(_iter_pack_paths()))}")
|
||||
print(f"packs: {len(iter_pack_paths())}")
|
||||
print("categories:")
|
||||
for c, n in Counter(cats).most_common():
|
||||
print(f" {n:4d} {c}")
|
||||
for category, count in Counter(categories).most_common():
|
||||
print(f" {count:4d} {category}")
|
||||
|
||||
warnings: List[str] = []
|
||||
|
||||
id_re = re.compile(r"^[a-z0-9_]+$")
|
||||
bad_ids = [sid for sid in ids if sid and not id_re.match(sid)]
|
||||
bad_ids = [style_id for style_id in ids if style_id and not ID_RE.match(style_id)]
|
||||
if bad_ids:
|
||||
warnings.append(f"bad_ids: {len(bad_ids)} (expected snake_case [a-z0-9_])")
|
||||
|
||||
empty_prefix = 0
|
||||
empty_suffix = 0
|
||||
missing_default = 0
|
||||
missing_tags = 0
|
||||
missing_flux_2_klein = 0
|
||||
missing_variant = 0
|
||||
comma_without_space: List[Tuple[str, str]] = []
|
||||
comma_re = re.compile(r",(?!\s)")
|
||||
banned_gear_hits: List[Tuple[str, str, str]] = []
|
||||
|
||||
banned_gear_terms = (
|
||||
"softbox",
|
||||
"soft box",
|
||||
"strobe",
|
||||
"speedlight",
|
||||
"beauty dish",
|
||||
"octabox",
|
||||
"umbrella",
|
||||
"ring light",
|
||||
"ringlight",
|
||||
"on-camera",
|
||||
"on camera",
|
||||
"tripod shot",
|
||||
)
|
||||
banned_gear_hits: List[Tuple[str, str, str]] = [] # (id, field, term)
|
||||
banned_gear_res = [(t, re.compile(re.escape(t), re.IGNORECASE)) for t in banned_gear_terms]
|
||||
|
||||
for s in styles:
|
||||
sid = str(s.get("id", ""))
|
||||
default = s.get("default", None)
|
||||
if not isinstance(default, dict):
|
||||
missing_default += 1
|
||||
continue
|
||||
|
||||
prefix = str(default.get("prefix", "") or "")
|
||||
suffix = str(default.get("suffix", "") or "")
|
||||
for style in styles:
|
||||
prefix = style.prefix or ""
|
||||
suffix = style.suffix or ""
|
||||
if not prefix.strip():
|
||||
empty_prefix += 1
|
||||
if not suffix.strip():
|
||||
empty_suffix += 1
|
||||
|
||||
for field, text in (("prefix", prefix), ("suffix", suffix)):
|
||||
if comma_re.search(text):
|
||||
comma_without_space.append((sid, field))
|
||||
if COMMA_RE.search(text):
|
||||
comma_without_space.append((style.id, field))
|
||||
|
||||
models = s.get("models", {})
|
||||
flux_suffix = ""
|
||||
if isinstance(models, dict):
|
||||
flux = models.get("flux_2_klein", {})
|
||||
if isinstance(flux, dict):
|
||||
flux_suffix = str(flux.get("suffix", "") or "")
|
||||
variant_prefix, variant_suffix = style.variants.get("flux_2_klein", ("", ""))
|
||||
if "flux_2_klein" not in style.variants:
|
||||
missing_variant += 1
|
||||
|
||||
for field, text in (("prefix", prefix), ("suffix", suffix), ("flux_2_klein.suffix", flux_suffix)):
|
||||
for field, text in (("prefix", prefix), ("suffix", suffix), ("flux_2_klein.prefix", variant_prefix), ("flux_2_klein.suffix", variant_suffix)):
|
||||
if not text:
|
||||
continue
|
||||
for term, rx in banned_gear_res:
|
||||
if rx.search(text):
|
||||
banned_gear_hits.append((sid, field, term))
|
||||
for term, pattern in BANNED_GEAR_RES:
|
||||
if pattern.search(text):
|
||||
banned_gear_hits.append((style.id, field, term))
|
||||
|
||||
tags = s.get("tags", None)
|
||||
if not isinstance(tags, list) or not any(str(t).strip() for t in tags):
|
||||
if not style.tags:
|
||||
missing_tags += 1
|
||||
|
||||
if not (isinstance(models, dict) and "flux_2_klein" in models):
|
||||
missing_flux_2_klein += 1
|
||||
|
||||
if missing_default:
|
||||
warnings.append(f"missing_or_bad_default: {missing_default}")
|
||||
if empty_prefix:
|
||||
warnings.append(f"empty_prefix: {empty_prefix}")
|
||||
if empty_suffix:
|
||||
@@ -130,25 +94,23 @@ def main() -> int:
|
||||
warnings.append(f"comma_without_space: {len(comma_without_space)} (node splits on ', ')")
|
||||
if missing_tags:
|
||||
warnings.append(f"missing_tags: {missing_tags}")
|
||||
if missing_flux_2_klein:
|
||||
warnings.append(f"missing_models.flux_2_klein: {missing_flux_2_klein}")
|
||||
if banned_gear_hits:
|
||||
warnings.append(f"banned_gear_terms: {len({sid for sid, _field, _term in banned_gear_hits})} styles")
|
||||
|
||||
# Quick uniqueness sanity (validate_styles.py is the source of truth).
|
||||
if missing_variant:
|
||||
warnings.append(f"missing_models.flux_2_klein: {missing_variant}")
|
||||
if len(set(ids)) != len(ids):
|
||||
warnings.append("duplicate_ids: detected")
|
||||
if len(set(names)) != len(names):
|
||||
warnings.append("duplicate_names: detected")
|
||||
if banned_gear_hits:
|
||||
warnings.append(f"banned_gear_terms: {len({style_id for style_id, _field, _term in banned_gear_hits})} styles")
|
||||
|
||||
if warnings:
|
||||
print("warnings:")
|
||||
for w in warnings:
|
||||
print(f" - {w}")
|
||||
for warning in warnings:
|
||||
print(f" - {warning}")
|
||||
if banned_gear_hits:
|
||||
print("banned gear term examples:")
|
||||
for sid, field, term in banned_gear_hits[:20]:
|
||||
print(f" - {sid} ({field}): {term}")
|
||||
for style_id, field, term in banned_gear_hits[:20]:
|
||||
print(f" - {style_id} ({field}): {term}")
|
||||
else:
|
||||
print("warnings: none")
|
||||
|
||||
@@ -157,4 +119,3 @@ def main() -> int:
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
|
||||
@@ -9,11 +9,17 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Iterable, List, Optional, Sequence
|
||||
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
if ROOT not in sys.path:
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
from style_library import write_legacy_snapshot
|
||||
|
||||
PACKS_DIR = os.path.join(ROOT, "styles", "packs")
|
||||
|
||||
|
||||
@@ -1626,8 +1632,13 @@ def build() -> None:
|
||||
grades,
|
||||
)
|
||||
)
|
||||
snapshot_total = write_legacy_snapshot()
|
||||
print(f"Wrote style packs: {total} styles -> {PACKS_DIR}")
|
||||
print(f"Updated legacy snapshot: {snapshot_total} styles -> {os.path.join(ROOT, 'styles', 'styles_v1.json')}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
build()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
if ROOT not in sys.path:
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
from style_library import LOAD_POLICY_STRICT, StyleLibraryError, write_legacy_snapshot
|
||||
|
||||
|
||||
def main() -> int:
|
||||
try:
|
||||
total = write_legacy_snapshot(load_policy=LOAD_POLICY_STRICT)
|
||||
except StyleLibraryError as exc:
|
||||
print(f"ERROR: {exc}")
|
||||
return 2
|
||||
|
||||
print(f"Updated styles/styles_v1.json with {total} merged styles")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+18
-45
@@ -1,64 +1,37 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
if ROOT not in sys.path:
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
from style_library import LOAD_POLICY_STRICT, StyleLibraryError, load_style_library
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
packs_dir = os.path.join(root, "styles", "packs")
|
||||
|
||||
data = {"styles": []}
|
||||
if os.path.isdir(packs_dir):
|
||||
for name in sorted(os.listdir(packs_dir)):
|
||||
if not name.lower().endswith(".json"):
|
||||
continue
|
||||
path = os.path.join(packs_dir, name)
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
part = json.load(f) or {}
|
||||
data["styles"].extend(part.get("styles", []) or [])
|
||||
else:
|
||||
path = os.path.join(root, "styles", "styles_v1.json")
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
styles = data.get("styles", [])
|
||||
if not isinstance(styles, list):
|
||||
print("ERROR: styles must be a list")
|
||||
try:
|
||||
library = load_style_library(load_policy=LOAD_POLICY_STRICT)
|
||||
except StyleLibraryError as exc:
|
||||
print(f"ERROR: {exc}")
|
||||
return 2
|
||||
|
||||
ids = set()
|
||||
names = set()
|
||||
ok = True
|
||||
for i, s in enumerate(styles):
|
||||
sid = s.get("id")
|
||||
name = s.get("name")
|
||||
if not sid or not name:
|
||||
print(f"ERROR: styles[{i}] missing id or name")
|
||||
for style in library.styles:
|
||||
if style.id in ids:
|
||||
print(f"ERROR: duplicate id: {style.id}")
|
||||
ok = False
|
||||
continue
|
||||
if sid in ids:
|
||||
print(f"ERROR: duplicate id: {sid}")
|
||||
if style.name in names:
|
||||
print(f"ERROR: duplicate name: {style.name}")
|
||||
ok = False
|
||||
if name in names:
|
||||
print(f"ERROR: duplicate name: {name}")
|
||||
ok = False
|
||||
ids.add(sid)
|
||||
names.add(name)
|
||||
|
||||
default = s.get("default", {})
|
||||
if not isinstance(default, dict):
|
||||
print(f"ERROR: styles[{i}].default must be an object")
|
||||
ok = False
|
||||
continue
|
||||
for k in ("prefix", "suffix"):
|
||||
if k not in default:
|
||||
print(f"ERROR: styles[{i}].default missing {k}")
|
||||
ok = False
|
||||
ids.add(style.id)
|
||||
names.add(style.name)
|
||||
|
||||
if not ok:
|
||||
return 2
|
||||
|
||||
print(f"OK: {len(styles)} styles; {len(ids)} unique ids; {len(names)} unique names")
|
||||
print(f"OK: {len(library.styles)} styles; {len(ids)} unique ids; {len(names)} unique names")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
VERSION_PATH = os.path.join(ROOT, "VERSION")
|
||||
INIT_PATH = os.path.join(ROOT, "__init__.py")
|
||||
MANIFEST_PATH = os.path.join(ROOT, ".release-please-manifest.json")
|
||||
|
||||
|
||||
def _read_version() -> str:
|
||||
with open(VERSION_PATH, "r", encoding="utf-8") as handle:
|
||||
return handle.read().strip()
|
||||
|
||||
|
||||
def _read_manifest_version() -> str:
|
||||
with open(MANIFEST_PATH, "r", encoding="utf-8") as handle:
|
||||
data = json.load(handle) or {}
|
||||
return str(data.get(".", "")).strip()
|
||||
|
||||
|
||||
def _write_manifest_version(version: str) -> None:
|
||||
with open(MANIFEST_PATH, "w", encoding="utf-8") as handle:
|
||||
json.dump({".": version}, handle, indent=2, ensure_ascii=True)
|
||||
handle.write("\n")
|
||||
|
||||
|
||||
def _init_reads_version_file() -> bool:
|
||||
with open(INIT_PATH, "r", encoding="utf-8") as handle:
|
||||
text = handle.read()
|
||||
return "_read_version()" in text and 'os.path.join(here, "VERSION")' in text
|
||||
|
||||
|
||||
def cmd_check(_args) -> int:
|
||||
version = _read_version()
|
||||
manifest_version = _read_manifest_version()
|
||||
ok = True
|
||||
|
||||
if not _init_reads_version_file():
|
||||
print("ERROR: __init__.py is no longer reading VERSION directly")
|
||||
ok = False
|
||||
|
||||
if manifest_version != version:
|
||||
print(f"ERROR: .release-please-manifest.json ({manifest_version}) does not match VERSION ({version})")
|
||||
ok = False
|
||||
|
||||
if not ok:
|
||||
return 2
|
||||
|
||||
print(f"OK: VERSION, __init__.py, and .release-please-manifest.json are aligned at {version}")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_write(_args) -> int:
|
||||
version = _read_version()
|
||||
_write_manifest_version(version)
|
||||
print(f"Updated .release-please-manifest.json -> {version}")
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(prog="version_sync.py", description="Check or sync version metadata")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
|
||||
p_check = sub.add_parser("check", help="Validate VERSION, __init__.py, and release manifest alignment")
|
||||
p_check.set_defaults(func=cmd_check)
|
||||
|
||||
p_write = sub.add_parser("write", help="Write manifest version from VERSION")
|
||||
p_write.set_defaults(func=cmd_write)
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
return int(args.func(args))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user