/**
* The header provider-settings gear — a small gear button next to the provider picker that opens a
* popover holding an **accordion** of the chosen providers' own controls (`ProviderBox`): one
* section per role — Image (always), Text (when a rewrite AI is set), and Upscale (when an upscaler
* is set). This keeps every provider's knobs (model / size / sampler / negative prompt / …) out of
* the main prompt area while staying one click from the provider selection. Each section header
* shows that provider's label + tier and toggles its body open/closed.
* @module gui/components/ProviderGear
*/
import { useState } from "react";
import { useIntl, defineMessages } from "react-intl";
import { getProvider } from "../lib/providers/index.js";
import ProviderBox from "./ProviderBox.jsx";
import { jsxDEV as _jsxDEV, Fragment as _Fragment } from "react/jsx-dev-runtime";
const msgs = defineMessages({
tierApi: {
id: "providerGear.tier.api",
defaultMessage: "image API"
},
tierSyntax: {
id: "providerGear.tier.syntax",
defaultMessage: "copy-prompt"
},
tierPlain: {
id: "providerGear.tier.plain",
defaultMessage: "plain text"
},
settings: {
id: "providerGear.settings",
defaultMessage: "Provider settings"
},
close: {
id: "providerGear.close",
defaultMessage: "close"
},
none: {
id: "providerGear.none",
defaultMessage: "No provider selected — pick one in the Providers menu to see its settings."
},
roleImage: {
id: "providerGear.role.image",
defaultMessage: "Image"
},
roleText: {
id: "providerGear.role.text",
defaultMessage: "Text"
},
roleUpscale: {
id: "providerGear.role.upscale",
defaultMessage: "Upscale"
},
noTextSettings: {
id: "providerGear.noTextSettings",
defaultMessage: "This text AI uses its built-in model — no extra settings here."
},
noUpscaleSettings: {
id: "providerGear.noUpscaleSettings",
defaultMessage: "No separate upscale settings — this provider reuses its image settings above."
}
});
/**
* The gear cog icon.
* @returns {JSX.Element}
*/
function GearIcon() {
return /*#__PURE__*/_jsxDEV("svg", {
viewBox: "0 0 24 24",
width: "16",
height: "16",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
"aria-hidden": "true",
children: [/*#__PURE__*/_jsxDEV("circle", {
cx: "12",
cy: "12",
r: "3"
}, void 0, false), /*#__PURE__*/_jsxDEV("path", {
d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"
}, void 0, false)]
}, void 0, true);
}
/**
* One accordion section: a provider role's header (label + tier) over its collapsible `ProviderBox`.
* @param {object} props
* @param {string} props.role Localized role name (Image / Text / Upscale).
* @param {object} props.provider The provider manifest.
* @param {boolean} props.open Whether the section body is expanded.
* @param {Function} props.onToggle Toggle this section.
* @param {object} props.settings The current settings.
* @param {Function} props.setSettings Update the settings.
* @returns {JSX.Element}
*/
function GearSection({
roleId,
role,
provider,
open,
onToggle,
settings,
setSettings
}) {
const intl = useIntl();
const tierLabel = intl.formatMessage(provider.tier === "api" ? msgs.tierApi : provider.tier === "syntax" ? msgs.tierSyntax : msgs.tierPlain);
// A provider has ONE settings schema — its native one (image-generation for image providers,
// upscale params for upscale-only enhancers). So only render it for the role that schema serves:
// • image → the provider's image-gen schema (correct).
// • text → a rewrite AI has no applicable settings (it uses a fixed chat model) → note.
// • upscale → only upscale-ONLY providers carry an upscale schema; a dual image+upscale provider's
// schema is image-gen, edited under its Image section → note.
// This stops the old behaviour of showing image-gen knobs (model/sampler/steps) under Text/Upscale.
const showSchema = roleId === "image" || roleId === "upscale" && provider.upscaleOnly;
return /*#__PURE__*/_jsxDEV("div", {
className: `gear-acc-item${open ? " on" : ""}`,
children: [/*#__PURE__*/_jsxDEV("button", {
className: "gear-acc-head",
onClick: onToggle,
"aria-expanded": open,
children: [/*#__PURE__*/_jsxDEV("span", {
className: "gear-acc-caret",
"aria-hidden": "true",
children: "▸"
}, void 0, false), /*#__PURE__*/_jsxDEV("span", {
className: "gear-acc-role",
children: role
}, void 0, false), /*#__PURE__*/_jsxDEV("span", {
className: "gear-acc-name",
children: provider.label
}, void 0, false), /*#__PURE__*/_jsxDEV("span", {
className: "provider-tag",
children: tierLabel
}, void 0, false)]
}, void 0, true), open && /*#__PURE__*/_jsxDEV("div", {
className: "gear-acc-body",
children: showSchema ? /*#__PURE__*/_jsxDEV(ProviderBox, {
settings: settings,
setSettings: setSettings,
providerId: provider.id
}, void 0, false) : /*#__PURE__*/_jsxDEV("p", {
className: "hint provider-controls-empty",
children: intl.formatMessage(roleId === "text" ? msgs.noTextSettings : msgs.noUpscaleSettings)
}, void 0, false)
}, void 0, false)]
}, void 0, true);
}
/**
* @param {object} props
* @param {object} props.settings The current settings.
* @param {Function} props.setSettings Update the settings.
* @returns {(JSX.Element|null)}
*/
export default function ProviderGear({
settings,
setSettings
}) {
const intl = useIntl();
const [open, setOpen] = useState(false);
// Which accordion sections are expanded — Image leads, expanded by default.
const [expanded, setExpanded] = useState(() => new Set(["image"]));
const toggle = id => setExpanded(prev => {
const next = new Set(prev);
if (next.has(id)) next.delete(id);else next.add(id);
return next;
});
// Text / Upscale may be unset ("none"); the image provider is always set (Plain/"Unset" is real).
const image = getProvider(settings.provider);
const textId = settings.rewriteProvider && settings.rewriteProvider !== "none" ? settings.rewriteProvider : null;
const upscaleId = settings.upscaleProvider && settings.upscaleProvider !== "none" ? settings.upscaleProvider : null;
const text = textId ? getProvider(textId) : null;
const upscale = upscaleId ? getProvider(upscaleId) : null;
const sections = [image && {
id: "image",
role: intl.formatMessage(msgs.roleImage),
provider: image
}, text && {
id: "text",
role: intl.formatMessage(msgs.roleText),
provider: text
}, upscale && {
id: "upscale",
role: intl.formatMessage(msgs.roleUpscale),
provider: upscale
}].filter(Boolean);
return /*#__PURE__*/_jsxDEV("div", {
className: "field-menu-wrap provider-gear",
children: [/*#__PURE__*/_jsxDEV("button", {
className: `ps-gear${open ? " on" : ""}`,
onClick: () => setOpen(o => !o),
title: intl.formatMessage(msgs.settings),
"aria-label": intl.formatMessage(msgs.settings),
"aria-haspopup": "dialog",
"aria-expanded": open,
children: [/*#__PURE__*/_jsxDEV(GearIcon, {}, void 0, false), /*#__PURE__*/_jsxDEV("span", {
className: "ctl-label",
children: intl.formatMessage(msgs.settings)
}, void 0, false)]
}, void 0, true), open && /*#__PURE__*/_jsxDEV(_Fragment, {
children: [/*#__PURE__*/_jsxDEV("div", {
className: "gear-pop-scrim",
onClick: () => setOpen(false)
}, void 0, false), /*#__PURE__*/_jsxDEV("div", {
className: "gear-pop provider-gear-pop",
role: "dialog",
"aria-label": intl.formatMessage(msgs.settings),
children: [/*#__PURE__*/_jsxDEV("div", {
className: "gear-pop-head",
children: [/*#__PURE__*/_jsxDEV("span", {
className: "gear-pop-title",
children: intl.formatMessage(msgs.settings)
}, void 0, false), /*#__PURE__*/_jsxDEV("button", {
className: "link-btn",
onClick: () => setOpen(false),
children: intl.formatMessage(msgs.close)
}, void 0, false)]
}, void 0, true), /*#__PURE__*/_jsxDEV("div", {
className: "gear-pop-body",
children: sections.length ? /*#__PURE__*/_jsxDEV("div", {
className: "gear-acc",
children: sections.map(s => /*#__PURE__*/_jsxDEV(GearSection, {
roleId: s.id,
role: s.role,
provider: s.provider,
open: expanded.has(s.id),
onToggle: () => toggle(s.id),
settings: settings,
setSettings: setSettings
}, s.id, false))
}, void 0, false) : /*#__PURE__*/_jsxDEV("p", {
className: "hint provider-controls-empty",
children: intl.formatMessage(msgs.none)
}, void 0, false)
}, void 0, false)]
}, void 0, true)]
}, void 0, true)]
}, void 0, true);
}