Skip to content

feat(ui): add prompt workbench wildcard support#9137

Draft
AsuraAce wants to merge 7 commits intoinvoke-ai:mainfrom
AsuraAce:codex/prompt-workbench-wildcards
Draft

feat(ui): add prompt workbench wildcard support#9137
AsuraAce wants to merge 7 commits intoinvoke-ai:mainfrom
AsuraAce:codex/prompt-workbench-wildcards

Conversation

@AsuraAce
Copy link
Copy Markdown

@AsuraAce AsuraAce commented May 8, 2026

Summary

Adds a Prompt Workbench foundation under the positive prompt field for local wildcard workflows and prompt intent diagnostics.

  • Adds local wildcard indexing and values endpoints for INVOKEAI_ROOT/wildcards, including nested text/JSON/YAML wildcard packs.
  • Extends dynamic prompt behavior with explicit random/all-combinations modes, random refresh cadence, reshuffle support, and per-image random batching.
  • Adds a compact Prompt intent panel with wildcard rows, behavior menus, weight visibility, model-aware diagnostics, and keyboard-friendly autocomplete/fixed-value flows.

This is opened as a draft because final UI polish and broader manual QA are still in progress. One known follow-up is a false-positive prompt-weight/wildcard detection case around hyphen-connected words such as dance-more.

View screenshots Screenshot 2026-05-08 175850
Screenshot 2026-05-08 175737
Screenshot 2026-05-08 175928

Related Issues / Discussions

None.

QA Instructions

Automated checks run locally:

  • pnpm exec prettier --check src/features/promptWorkbench/PromptInspector.tsx src/features/promptWorkbench/PromptWorkbench.tsx src/features/promptWorkbench/PromptWorkbenchBadge.tsx
  • pnpm exec eslint src/features/promptWorkbench/PromptInspector.tsx src/features/promptWorkbench/PromptWorkbench.tsx src/features/promptWorkbench/PromptWorkbenchBadge.tsx
  • pnpm test:run src/features/promptWorkbench/occurrences.test.ts src/features/promptWorkbench/diagnostics.test.ts
  • pnpm test:run src/features/promptWorkbench/occurrences.test.ts src/features/promptWorkbench/diagnostics.test.ts src/features/ui/layouts/navigation-api.test.ts
  • pnpm lint:tsc

Browser smoke at http://127.0.0.1:5174/app:

  1. Clear the positive prompt and confirm the Prompt intent panel is hidden.
  2. Type __cam and confirm wildcard autocomplete appears with local wildcard matches.
  3. Insert __camera/shot__ and confirm the Prompt intent panel appears with a wildcard row.
  4. Open the wildcard behavior dropdown and confirm token-local behavior actions are available.
  5. Confirm there is no framework overlay or Workbench-specific console error.

Additional reviewer QA recommended before marking ready:

  1. Test random/image vs random/invoke with batch size > 1.
  2. Test cyclic wildcards, fixed values, and all-combinations preview behavior.
  3. Test prompt weight rows on SDXL and on a model base that treats weights as literal text.
  4. Reproduce and fix the known dance-more false-positive edge case.

Merge Plan

Draft PR. Do not merge until the remaining UI polish, manual QA, and known hyphenated-word false positive are resolved.

Checklist

  • The PR has a short but descriptive title, suitable for a changelog
  • Tests added / updated (if applicable)
  • ❗Changes to a redux slice have a corresponding migration
  • Documentation added / updated (if applicable)
  • Updated What's New copy (if doing a release after this PR)

@github-actions github-actions Bot added api python PRs that change python files invocations PRs that change invocations frontend PRs that change frontend files python-tests PRs that change python tests labels May 8, 2026
@Pfannkuchensack
Copy link
Copy Markdown
Collaborator

Findings

High

invokeai/app/api/routers/utilities_wildcards.py:125-153 — single malformed structured file blocks get_wildcard_values for unrelated wildcards

get_wildcard_values iterates sorted(root.rglob("*")) and, for every JSON/YAML candidate, calls _get_structured_wildcard_values, which always invokes _read_structured_data before any path comparison. _read_structured_data calls json.loads / yaml.safe_load directly with no try/except, so a single malformed JSON or YAML file in INVOKEAI_ROOT/wildcards causes the exception to escape get_wildcard_values, propagate to the route handler at invokeai/app/api/routers/utilities.py:68-77, and return HTTP 500 even when the user is asking for an unrelated, valid wildcard.

The listing endpoint already swallows the same exception via try/except at invokeai/app/api/routers/utilities_wildcards.py:87-95 and demotes the file to a WildcardIndexError; the values endpoint is missing that guard, and the user-controlled traversal order means any user with a single broken file in their tree loses the ability to look up later wildcards. The .txt path short-circuits (compares the indexed path before reading), so this only affects structured files.

To expose this issue, add a test that places a malformed JSON file alongside a valid TXT wildcard and asserts that get_wildcard_values(wildcards_dir, "valid_txt_path") still returns the TXT values rather than raising.


Medium

invokeai/app/api/routers/utilities.py:48-77 — new wildcard routes have no auth dependency

The new GET /api/v1/utilities/wildcards and GET /api/v1/utilities/wildcards/values routes have no auth dependency. Other server-state routers in the codebase do use CurrentUserOrDefault / AdminUserOrDefault (see invokeai/app/api/routers/recall_parameters.py:10, invokeai/app/api/routers/client_state.py:4, invokeai/app/api/routers/custom_nodes.py); only this utilities file ships unauthenticated routes.

With multiuser mode enabled (config.multiuser=True in invokeai/app/api/auth_dependencies.py:90-99) any unauthenticated request can list and read text/JSON/YAML files under INVOKEAI_ROOT/wildcards and trigger expensive rglob walks. The wildcards directory is global, not per-user, so this is not a multi-tenant data leak in the traditional sense, but it is still authentication bypass on a server-state read endpoint and an unauthenticated-DoS vector via repeated lookups against large directories.

The pattern is consistent with the pre-existing parse_dynamicprompts / expand-prompt / image-to-prompt routes in the same file, but those should arguably also have auth in multiuser mode.

To expose this issue, add a test that constructs a TestClient with config.multiuser=True and asserts that GET /api/v1/utilities/wildcards without a Bearer token returns 401 rather than 200.


i18n is missing entirely from the new prompt workbench

invokeai/frontend/web/public/locales/en.json was not modified by this branch (verified against the merge base ca0582e89b), and none of the new files use useTranslation. Concrete user-visible English strings in JSX/labels/aria-labels:

  • invokeai/frontend/web/src/features/promptWorkbench/PromptWorkbench.tsx

    • :767 "Loading values..."
    • :793 `Matching "${query}"`
    • :794 "Local wildcards"
    • :812 "Prompt intent"
    • :828 "Random/image", :834 "Random/invoke"
    • :827, :832 title= strings on menu items
    • :752 aria-label={`Insert ${wildcard.path} with wildcard behavior`}
    • :753 tooltip="Wildcard behavior"
    • :819 `${diagnostic.description} Change random wildcard behavior.`
  • invokeai/frontend/web/src/features/promptWorkbench/PromptInspector.tsx

    • :164 aria-label={`${occurrence.path} wildcard intent`}
    • :216 "Loading values..."
    • :301-310 "Random wildcard", "Cycle through values", "Pick fixed value" + their title= strings
    • :339-351 "Open ... wildcard actions", "Wildcard actions", "Remove"
    • :438 aria-label={`${occurrence.text} prompt weight`}
    • :462 "prompt weight"
    • :490-503 weight actions menu
    • :609-624 "Missing wildcard", "Wildcard index unavailable", `Random wildcard${countText}`, `Cycle${countText}`, `All combinations${countText}` — also hardcoded plural ` \u00b7 ${valueCount} values`
  • invokeai/frontend/web/src/features/promptWorkbench/PromptWildcardBehaviorMenu.tsx:73-89 Random wildcard, Cycle through values, Pick fixed value, Remove

  • invokeai/frontend/web/src/features/promptWorkbench/occurrences.ts

    • :130-176 label helpers: Random per Image, Random per Invoke, Random preview, Cycle, All combinations, Missing, Unavailable, Random/image, Random/invoke, Preview, All
    • :209-213 Weight supported, Weight may be literal, Literal?
  • invokeai/frontend/web/src/features/promptWorkbench/diagnostics.ts:55-194 every diagnostic label and description string, including pluralization done via `Missing wildcard${missingWildcards.length === 1 ? '' : 's'}` at :76

  • invokeai/frontend/web/src/features/promptWorkbench/wildcards.ts:91-107 autocomplete status messages including `No local wildcards match "${query}".`

  • invokeai/frontend/web/src/features/dynamicPrompts/components/

    • ParamDynamicPromptsMode.tsx:18-29
    • ParamDynamicPromptsRandomRefreshMode.tsx:18-34
    • ParamDynamicPromptsReshuffle.tsx:20-23

    Combobox labels, descriptions, and <FormLabel>Mode</FormLabel> / Randomness / Preview / Reshuffle Now are all literal English even though the surrounding accordion uses useTranslation.

  • invokeai/frontend/web/src/features/queue/hooks/

    • useEnqueueGenerate.ts:37
    • useEnqueueCanvas.ts:39
    • useEnqueueUpscaling.ts:22

    toast({ status: 'error', title: 'Failed to resolve dynamic prompts' }) is shown to the user with a hardcoded English title.

Plurals like Wildcards ${count}, Missing ${count}, Index errors ${count}, \u00b7 ${count} values should go through the i18n pluralization machinery rather than concatenated strings.

To expose this issue automatically, add a test against getWildcardBehaviorLabel / getWildcardBehaviorShortLabel / getPromptDiagnostics that asserts they return translation keys (or call t()) rather than English literals; otherwise call out manual verification of the rendered labels — there is no approved DOM testing framework available at present.


invokeai/app/invocations/prompt.py:7 — invocation imports from app.api.routers

The DynamicPromptInvocation invocation now imports clean_dynamic_prompt_outputs from invokeai.app.api.routers.utilities_wildcards, i.e. an invocation node depends on the app.api.routers package. Routers should depend on services/invocations, not the other way around, and an invocation that runs in worker contexts ought not to reach into the API layer for shared logic. The shared helper should live in a service or util module.

This will not crash at runtime today (the module imports only pydantic / yaml), but it inverts the established layering and makes it easy for someone to add a FastAPI-bound import to utilities_wildcards.py later and silently break invocation execution.


Low

invokeai/app/invocations/prompt.py:35 — duplicated wildcards path resolution

wildcards_path = context.config.get().root_path / "wildcards" duplicates the path computation that get_wildcards_path already centralizes at invokeai/app/api/routers/utilities_wildcards.py:51-52. The WILDCARDS_DIR_NAME constant is the source of truth for the listing endpoint but is bypassed here, so a future rename would silently desync the workflow node and the API. Use get_wildcards_path (or move it to a shared util) instead of inlining the path.


invokeai/frontend/web/src/services/api/schema.ts is stale on this branch

The backend DynamicPromptsResponse at invokeai/app/api/routers/utilities.py:41-46 now declares warnings: list[str] and missing_wildcards: list[str], but invokeai/frontend/web/src/services/api/schema.ts:8805-8811 still only has prompts and error. The new WildcardsResponse and WildcardValuesResponse schemas are not present in schema.ts at all.

invokeai/frontend/web/src/features/dynamicPrompts/util/resolveDynamicPrompts.ts:11-15 papers over the gap with a hand-written & { warnings?: string[]; missing_wildcards?: string[]; } augmentation, and invokeai/frontend/web/src/services/api/endpoints/utilities.ts:38-61 redefines the wildcard response types by hand. Re-run the OpenAPI codegen so consumers get the real types and the manual augmentations can be deleted.


invokeai/frontend/web/src/features/dynamicPrompts/util/refreshDynamicPromptsForEnqueue.ts:14, :26 — dead code

MAX_RANDOM_PROMPTS_FOR_ENQUEUE and getDynamicPromptsEnqueueRandomSamples are only referenced by their own test file (verified via grep across invokeai/frontend/web/src). Either wire them into the enqueue path or delete them; right now the cap is enforced purely by MAX_DYNAMIC_PROMPTS = 10000 inside resolveDynamicPrompts.ts, and the exported helper exists only to satisfy the test.


invokeai/app/api/routers/utilities_wildcards.py:307-320 — top-level dict keys collide across files

_collect_wildcard_leaves builds wildcard paths from folder_prefix + dict-key chain and deliberately drops the file stem (e.g. wildcards/styles.json containing {"lighting": [...]} indexes as lighting, not styles/lighting). This matches dynamicprompts' WildcardManager lookup semantics, but two structured files in the same folder that share a top-level key will both index to the same path, and consumers like the frontend autocomplete (which keys by wildcard.path at invokeai/frontend/web/src/features/promptWorkbench/PromptWorkbench.tsx:719) will silently collapse them into a single entry whose backing values come from whichever file the iteration hits first via get_wildcard_values.

To expose this issue, add a test that creates wildcards/a.json {"k": ["x"]} and wildcards/b.json {"k": ["y"]} and asserts that index_wildcards either reports an error or yields disambiguated paths.


Open Questions

  • In invokeai/frontend/web/src/features/nodes/util/graph/buildLinearBatchConfig.ts:30-62, the per-output mode generates one unique seed per output and ignores state.dynamicPrompts.seedBehaviour. For pure per-image randomness the prompts are always unique so this is equivalent, but for mixed cyclic-only flows where prompts can repeat (3 cycle values across N iterations) a PER_PROMPT user might expect repeated seeds for repeated prompts. The intended interaction between seedBehaviour and randomRefreshMode should be confirmed and documented in a test.

AsuraAce added 2 commits May 9, 2026 08:12
Move wildcard helpers out of the API router layer, harden malformed and duplicate wildcard handling, add multiuser auth to wildcard and dynamic prompt utilities, refresh generated API types, and add i18n coverage for the Prompt Workbench and Dynamic Prompts UI.
@AsuraAce
Copy link
Copy Markdown
Author

AsuraAce commented May 9, 2026

Follow-up update after review feedback:

  • Moved wildcard shared logic out of the API router layer into invokeai.app.util.wildcards.
  • Hardened wildcard indexing/value lookup:
    • malformed JSON/YAML no longer blocks unrelated wildcard value lookups;
    • duplicate structured wildcard paths are reported as index errors instead of silently collapsing.
  • Added CurrentUserOrDefault auth to wildcard and dynamic prompt utility routes.
  • Refreshed generated frontend API schema and removed local response type patches/dead enqueue helpers.
  • Added i18n coverage for the new Prompt Workbench/Dynamic Prompts UI strings and queue error toast.
  • Documented/tested current per-output seed behavior.
  • Fixed the dance-more false positive so hyphenated words do not show as prompt weights, while valid suffix weights like rose- still work.
  • Tightened the Prompt intent panel sizing for the real left sidebar.

Validation run locally:

  • Backend wildcard/auth pytest targets.
  • Prompt Workbench/Dynamic Prompt Vitest targets.
  • Targeted ESLint.
  • pnpm lint:tsc.
  • Browser smoke

@lstein lstein moved this to 6.14.x Theme: LIBRARY UPDATES in Invoke - Community Roadmap May 9, 2026
@lstein lstein added the 6.14.x label May 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

6.14.x api frontend PRs that change frontend files invocations PRs that change invocations python PRs that change python files python-tests PRs that change python tests

Projects

Status: 6.14.x Theme: LIBRARY UPDATES

Development

Successfully merging this pull request may close these issues.

3 participants