tmp/webapp-docs/frontend/components/ProviderBox.js

/**
 * The provider's own controls (rendered inside the header provider-settings gear popover,
 * `ProviderGear`) — capability-driven from the provider's `settings.js`, each with an info tooltip.
 * What's NOT here: the BYOK API key lives in the header `ApiKeyField`; the provider label/tier is
 * shown by the gear popover header; and the negative prompt is edited on the composer via its
 * Prompt/Negative switch (so it's filtered out of these controls).
 * @module gui/components/ProviderBox
 */
import { useEffect } from "react";
import { useIntl, defineMessages } from "react-intl";
import { Text, Num, Toggle, Select } from "./Field.jsx";
import { getProvider } from "../lib/providers/index.js";
import { useProviderSettings } from "../lib/useProvider.js";
import { infoFor } from "../../../shared/_shared/fieldInfo.js";
import { jsxDEV as _jsxDEV, Fragment as _Fragment } from "react/jsx-dev-runtime";
const msgs = defineMessages({
  noSettings: {
    id: "providerBox.noSettings",
    defaultMessage: "This provider has no extra settings."
  },
  loading: {
    id: "providerBox.loading",
    defaultMessage: "Loading…"
  }
});

/**
 * A small info "i" with a tooltip.
 * @param {object} props `{ text }`.
 * @returns {(JSX.Element|null)}
 */
function InfoTip({
  text
}) {
  if (!text) return null;
  return /*#__PURE__*/_jsxDEV("span", {
    className: "info-tip",
    title: text,
    "aria-label": text,
    role: "img",
    children: "i"
  }, void 0, false);
}

/**
 * Render one capability-driven provider field (with its tooltip).
 * @param {object} props `{ f, params, setParam, optionData }`.
 * @returns {JSX.Element}
 */
function ProviderField({
  f,
  params,
  setParam,
  optionData
}) {
  const v = params[f.key];
  const label = /*#__PURE__*/_jsxDEV(_Fragment, {
    children: [f.label, /*#__PURE__*/_jsxDEV(InfoTip, {
      text: infoFor(f)
    }, void 0, false)]
  }, void 0, true);
  if (f.type === "checkbox") return /*#__PURE__*/_jsxDEV(Toggle, {
    label: label,
    value: v,
    onChange: x => setParam(f.key, x)
  }, void 0, false);
  if (f.type === "select") {
    const options = f.options || optionData[f.optionsFrom] || [];
    return /*#__PURE__*/_jsxDEV(Select, {
      label: label,
      value: v ?? "",
      options: options,
      onChange: x => setParam(f.key, x)
    }, void 0, false);
  }
  if (f.type === "number") {
    return /*#__PURE__*/_jsxDEV(Num, {
      label: label,
      value: v,
      min: f.min,
      max: f.max,
      step: f.step,
      onChange: x => setParam(f.key, x)
    }, void 0, false);
  }
  return /*#__PURE__*/_jsxDEV(Text, {
    label: label,
    value: v,
    onChange: x => setParam(f.key, x)
  }, void 0, false);
}

/**
 * The provider's own controls (rendered inside the header gear popover).
 * @param {object} props
 * @param {object} props.settings The current settings.
 * @param {Function} props.setSettings Update the settings.
 * @param {string} [props.providerId] Which provider's controls to render. Defaults to the active
 *   image provider (`settings.provider`); the gear accordion passes the text / upscale provider id
 *   so one box can drive any provider row.
 * @returns {(JSX.Element|null)}
 */
export default function ProviderBox({
  settings,
  setSettings,
  providerId
}) {
  const intl = useIntl();
  const provider = getProvider(providerId || settings.provider);
  const pid = provider?.id;
  const {
    schema,
    options
  } = useProviderSettings(pid);
  const params = settings.providerParams?.[pid] || {};
  const setParam = (key, value) => setSettings(s => ({
    ...s,
    providerParams: {
      ...s.providerParams,
      [pid]: {
        ...s.providerParams?.[pid],
        [key]: value
      }
    }
  }));

  // Seed missing param defaults when the provider's schema loads (without clobbering saved values).
  useEffect(() => {
    if (!schema?.defaults || !pid) return;
    setSettings(s => {
      const cur = s.providerParams?.[pid] || {};
      let changed = false;
      const next = {
        ...cur
      };
      for (const [k, val] of Object.entries(schema.defaults)) {
        if (next[k] === undefined) {
          next[k] = val;
          changed = true;
        }
      }
      return changed ? {
        ...s,
        providerParams: {
          ...s.providerParams,
          [pid]: next
        }
      } : s;
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [schema, pid]);
  if (!provider) return null;
  // What's edited elsewhere and so filtered out of this popover:
  //  • negativePrompt — on the composer (Prompt/Negative switch).
  //  • the common image knobs (batch + size/ratio/width/height) — inline at the bottom-left of the
  //    prompt box (`InlineImageControls`), for the in-app image (api-tier) providers.
  const inlineKeys = ["batchSize", "size", "imageSize", "aspectRatio", "ar", "imageWidth", "imageHeight"];
  const fields = (schema?.fields || []).filter(f => f.key !== "negativePrompt" && !inlineKeys.includes(f.key));
  if (!fields.length) return /*#__PURE__*/_jsxDEV("p", {
    className: "hint provider-controls-empty",
    children: intl.formatMessage(schema ? msgs.noSettings : msgs.loading)
  }, void 0, false);
  return /*#__PURE__*/_jsxDEV("div", {
    className: "provider-controls group-body",
    children: fields.map(f => /*#__PURE__*/_jsxDEV(ProviderField, {
      f: f,
      params: params,
      setParam: setParam,
      optionData: options
    }, f.key, false))
  }, void 0, false);
}