nano banana text-to-image pane fixes (#1010)

fix reference images, resolution controls, etc.
This commit is contained in:
Brandon Thomas authored and GitHub committed 2026-01-25 19:05:20 -05:00
1 parent 76172e3956
commit 8e67654b9d
18 files changed
+293 -30

No files matched your search

@@ -1,8 +1,8 @@
use serde_derive::{Deserialize, Serialize};
/// This is a comprehensive list of common aspect ratios.
/// This is a comprehensive list of common aspect ratios you can specify when enqueuing a generation.
/// Not every model will support every aspect ratio.
/// In the case a model doesn't support the aspect ratio, pick the nearest option.
/// In the case a model doesn't support the aspect ratio, gracefully pick the nearest option.
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CommonAspectRatio {
@@ -1 +1,2 @@
pub mod aspect_ratio;
pub mod aspect_ratio;
pub mod resolution;
@@ -0,0 +1,17 @@
use serde_derive::{Deserialize, Serialize};
/// This is a comprehensive list of common resolutions you can specify when enqueuing a generation.
/// Not every model will support every resolution
/// In the case a model doesn't support the resolution, gracefully pick the nearest option.
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CommonResolution {
/// Models: Nano Banana Pro
OneK,
/// Models: Nano Banana Pro
TwoK,
/// Models: (None)
ThreeK,
/// Models: Nano Banana Pro
FourK,
}
@@ -0,0 +1 @@
pub mod resolution_to_artcraft_nano_banana_pro;
@@ -0,0 +1,12 @@
use crate::core::api_adapters::resolution::common_resolution::CommonResolution;
use artcraft_api_defs::generate::image::multi_function::nano_banana_pro_multi_function_image_gen::NanoBananaProMultiFunctionImageGenImageResolution;
pub fn resolution_to_artcraft_nano_banana_pro(resolution: CommonResolution) -> NanoBananaProMultiFunctionImageGenImageResolution {
match resolution {
CommonResolution::OneK => NanoBananaProMultiFunctionImageGenImageResolution::OneK,
CommonResolution::TwoK => NanoBananaProMultiFunctionImageGenImageResolution::TwoK,
// NB: ThreeK doesn't exist in Nano Banana Pro, use FourK as nearest higher resolution
CommonResolution::ThreeK => NanoBananaProMultiFunctionImageGenImageResolution::FourK,
CommonResolution::FourK => NanoBananaProMultiFunctionImageGenImageResolution::FourK,
}
}
@@ -0,0 +1,2 @@
pub mod common_resolution;
pub mod convert;
@@ -62,8 +62,7 @@ pub async fn handle_artcraft_gpt_image_1p5_text_to_image(
prompt: request.prompt.clone(),
image_size: aspect_ratio,
num_images,
// Not provided for text-to-image
image_media_tokens: None,
image_media_tokens: request.image_media_tokens.clone(),
mask_image_token: None,
input_fidelity: None,
// Not provided
@@ -1,5 +1,7 @@
use crate::core::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio;
use crate::core::api_adapters::aspect_ratio::convert::aspect_ratio_to_artcraft_nano_banana_pro::aspect_ratio_to_artcraft_nano_banana_pro;
use crate::core::api_adapters::resolution::common_resolution::CommonResolution;
use crate::core::api_adapters::resolution::convert::resolution_to_artcraft_nano_banana_pro::resolution_to_artcraft_nano_banana_pro;
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason, ProviderFailureReason};
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageResolution, EditImageSize};
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
@@ -61,18 +63,12 @@ pub async fn handle_artcraft_nano_banana_pro_text_to_image(
};
let aspect_ratio = get_aspect_ratio(request);
let resolution = request.image_resolution
.map(|image_resolution| match image_resolution {
TextToImageResolution::OneK => NanoBananaProMultiFunctionImageGenImageResolution::OneK,
TextToImageResolution::TwoK => NanoBananaProMultiFunctionImageGenImageResolution::TwoK,
TextToImageResolution::FourK => NanoBananaProMultiFunctionImageGenImageResolution::FourK,
});
let resolution = get_resolution(request);
let request = NanoBananaProMultiFunctionImageGenRequest {
uuid_idempotency_token,
prompt: request.prompt.clone(),
image_media_tokens: None,
image_media_tokens: request.image_media_tokens.clone(),
num_images,
resolution,
aspect_ratio,
@@ -126,3 +122,20 @@ fn get_aspect_ratio(request: &EnqueueTextToImageRequest) -> Option<NanoBananaPro
None
}
fn get_resolution(request: &EnqueueTextToImageRequest) -> Option<NanoBananaProMultiFunctionImageGenImageResolution> {
if let Some(common_resolution) = request.common_resolution {
return Some(resolution_to_artcraft_nano_banana_pro(common_resolution));
}
if let Some(resolution) = request.image_resolution {
// Deprecated resolution
return Some(match resolution {
TextToImageResolution::OneK => NanoBananaProMultiFunctionImageGenImageResolution::OneK,
TextToImageResolution::TwoK => NanoBananaProMultiFunctionImageGenImageResolution::TwoK,
TextToImageResolution::FourK => NanoBananaProMultiFunctionImageGenImageResolution::FourK,
})
}
None
}
@@ -1,3 +1,5 @@
use crate::core::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio;
use crate::core::api_adapters::resolution::common_resolution::CommonResolution;
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason};
use crate::core::commands::enqueue::image_edit::image_edit_models::image_edit_model_to_model_type;
@@ -16,6 +18,8 @@ use crate::core::events::generation_events::common::{GenerationAction, Generatio
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
use crate::core::events::sendable_event_trait::SendableEvent;
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
use crate::core::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
use crate::core::state::task_database::TaskDatabase;
@@ -34,9 +38,6 @@ use serde::{Deserialize, Serialize};
use sqlite_tasks::queries::create_task::{create_task, CreateTaskArgs};
use tauri::{AppHandle, State};
use tokens::tokens::media_files::MediaFileToken;
use crate::core::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio;
use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
use crate::core::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
/// This is used in the Tauri command bridge.
/// Don't change the serializations without coordinating with the frontend.
@@ -88,15 +89,20 @@ pub struct EnqueueTextToImageRequest {
/// Text prompt for the image generation. Required.
pub prompt: Option<String>,
/// Aspect ratio.
#[deprecated(note="use common_aspect_ratio")]
pub aspect_ratio: Option<TextToImageSize>,
/// New field for aspect ratio.
/// Not all models support each of these aspect ratios, but we can choose or interpolate sensibly.
pub common_aspect_ratio: Option<CommonAspectRatio>,
/// New field for resolution.
/// Not all models support each of these resolutions, but we can choose or interpolate sensibly.
pub common_resolution: Option<CommonResolution>,
/// Aspect ratio.
#[deprecated(note="use common_aspect_ratio")]
pub aspect_ratio: Option<TextToImageSize>,
/// Aspect ratio.
#[deprecated(note="use common_resolution")]
pub image_resolution: Option<TextToImageResolution>,
/// The number of images to generate.
@@ -153,7 +153,7 @@ pub async fn nano_banana_pro_multi_function_image_gen_handler(
None => EnqueueNanoBananaProEditImageNumImages::One, // Default to One
};
let resolution = match request.resolution {
let mut resolution = match request.resolution {
Some(NanoBananaProMultiFunctionImageGenImageResolution::OneK) => EnqueueNanoBananaProEditImageResolution::OneK,
Some(NanoBananaProMultiFunctionImageGenImageResolution::TwoK) => EnqueueNanoBananaProEditImageResolution::TwoK,
Some(NanoBananaProMultiFunctionImageGenImageResolution::FourK) => EnqueueNanoBananaProEditImageResolution::FourK,
@@ -177,6 +177,7 @@ pub async fn nano_banana_pro_multi_function_image_gen_handler(
if downgrade_for_free_user {
num_images = EnqueueNanoBananaProEditImageNumImages::One;
resolution = EnqueueNanoBananaProEditImageResolution::OneK;
}
let args = EnqueueNanoBananaProEditImageArgs {
@@ -207,7 +208,7 @@ pub async fn nano_banana_pro_multi_function_image_gen_handler(
None => EnqueueNanoBananaProTextToImageNumImages::One, // Default to One
};
let resolution = match request.resolution {
let mut resolution = match request.resolution {
Some(NanoBananaProMultiFunctionImageGenImageResolution::OneK) => EnqueueNanoBananaProTextToImageResolution::OneK,
Some(NanoBananaProMultiFunctionImageGenImageResolution::TwoK) => EnqueueNanoBananaProTextToImageResolution::TwoK,
Some(NanoBananaProMultiFunctionImageGenImageResolution::FourK) => EnqueueNanoBananaProTextToImageResolution::FourK,
@@ -231,6 +232,7 @@ pub async fn nano_banana_pro_multi_function_image_gen_handler(
if downgrade_for_free_user {
num_images = EnqueueNanoBananaProTextToImageNumImages::One;
resolution = EnqueueNanoBananaProTextToImageResolution::OneK;
}
let args = EnqueueNanoBananaProTextToImageArgs {
@@ -35,6 +35,8 @@ import { ImagePromptRow } from "./ImagePromptRow";
import { GenerationProvider } from "@storyteller/api-enums";
import { AspectRatioPicker } from "./common/AspectRatioPicker";
import { GenerationCountPicker } from "./common/GenerationCountPicker";
import { ResolutionPicker } from "./common/ResolutionPicker";
import { CommonResolution } from "@storyteller/model-list";
interface PromptBoxImageProps {
useJobContext: () => JobContextType;
@@ -114,6 +116,9 @@ export const PromptBoxImage = ({
// New aspect ratio state will begin to phase out old aspect ratio
const [commonAspectRatio, setCommonAspectRatio] = useState<CommonAspectRatio | undefined>(undefined);
// New resolution state will begin to phase out old resolution
const [commonResolution, setCommonResolution] = useState<CommonResolution | undefined>(undefined);
useEffect(() => {
onImageRowVisibilityChange?.(isImageRowVisible);
}, [isImageRowVisible, onImageRowVisibilityChange]);
@@ -280,6 +285,10 @@ export const PromptBoxImage = ({
request.common_aspect_ratio = commonAspectRatio;
}
if (selectedModel?.supportsNewResolution()) {
request.common_resolution = commonResolution;
}
if (
selectedModel?.canUseImagePrompt &&
!!referenceImages &&
@@ -472,7 +481,14 @@ export const PromptBoxImage = ({
/>
</Tooltip>
)}
{selectedModel?.canChangeResolution && (
{selectedModel?.supportsNewResolution() && (
<ResolutionPicker
model={selectedModel}
currentResolution={commonResolution}
handleCommonResolutionSelect={setCommonResolution}
/>
)}
{selectedModel?.canChangeResolution && !selectedModel?.supportsNewResolution() && (
<Tooltip
content="Resolution"
position="top"
@@ -0,0 +1,126 @@
import { faSquare, IconDefinition } from "@fortawesome/pro-regular-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { PopoverItem, PopoverMenu } from "@storyteller/ui-popover"
import { Tooltip } from "@storyteller/ui-tooltip"
import { CommonResolution, ImageModel } from "@storyteller/model-list"
import { faDiamond, faExpand, faHighDefinition, faRectangleHd, faStandardDefinition } from "@fortawesome/pro-solid-svg-icons"
interface ResolutionPickerProps {
model: ImageModel,
currentResolution?: CommonResolution,
handleCommonResolutionSelect: (selected: CommonResolution) => void;
//model: ImageModel | VideoModel
}
/**
* Stateless component.
*
* Picker for "common aspect ratios", the new data structure Tauri accepts for
* all image and video models (Note: this is not fully rolled out yet. Some/most
* models may still use the old format.)
*
* @param model - currently selected model
* @param currentAspectRatio - currently selected aspect ratio
* @param handleCommonResolutionSelect - callback when a resolution is selected
*/
export const ResolutionPicker = ({
model,
currentResolution,
handleCommonResolutionSelect,
}: ResolutionPickerProps) => {
const useResolution = currentResolution ?? model.defaultResolution ?? undefined;
console.log("resolution - currentResolution:", currentResolution);
console.log("resolution - useResolution:", useResolution);
const getCurrentResolutionIcon = () : IconDefinition => {
if (!useResolution) {
return faSquare;
}
return getResolutionIcon(useResolution);
}
const handleSelectAdapter = (item: PopoverItem) => {
const resolution = popOverLabelToResolution(item.label, model);
handleCommonResolutionSelect(resolution);
}
let resolutionList : PopoverItem[] = [];
model.resolutions?.forEach((resolution: CommonResolution) => {
resolutionList.push({
label: getResolutionTextLabel(resolution),
selected: useResolution === resolution,
description: `foo ${resolution}`,
icon: <FontAwesomeIcon icon={getResolutionIcon(resolution)} className="h-4 w-4" />,
});
});
return (
<>
<Tooltip
content="Aspect Ratio"
position="top"
className="z-50"
closeOnClick={true}
>
<PopoverMenu
items={resolutionList}
onSelect={handleSelectAdapter}
mode="toggle"
panelTitle="Resolution"
showIconsInList
triggerIcon={
<FontAwesomeIcon
icon={getCurrentResolutionIcon()}
className="h-4 w-4"
/>
}
/>
</Tooltip>
</>
)
}
const getResolutionIcon = (resolution: CommonResolution) : IconDefinition => {
switch (resolution) {
case CommonResolution.OneK:
return faStandardDefinition;
case CommonResolution.TwoK:
return faHighDefinition;
case CommonResolution.FourK:
return faHighDefinition; // TODO: Upgrade Font Awesome to include 4K icon
default:
console.error("Unknown resolution in icon mapping:", resolution);
return faStandardDefinition; // Fail open-ish
}
}
const getResolutionTextLabel = (resolution: CommonResolution) : string => {
switch (resolution) {
case CommonResolution.OneK:
return "1K";
case CommonResolution.TwoK:
return "2K";
case CommonResolution.FourK:
return "4K";
default:
console.error("Unknown resolution:", resolution);
return "1K"; // Fail open-ish
}
}
// Note: We only need this to deal with turning PopOverItems back into typesafe aspect ratios
const popOverLabelToResolution = (label: string, model: ImageModel): CommonResolution => {
switch (label) {
case "1K": return CommonResolution.OneK;
case "2K": return CommonResolution.TwoK;
case "4K": return CommonResolution.FourK;
}
console.error("Unknown resolution label:", label, "for model:", model.id);
// If we can't find it, return the model's default resolution or 1K as fallback
return model.defaultResolution || CommonResolution.OneK;
}
+1
View File
@@ -2,6 +2,7 @@ export * from "./lib/classes/ImageModel.js";
export * from "./lib/classes/Model.js";
export * from "./lib/classes/VideoModel.js";
export * from "./lib/classes/properties/CommonAspectRatio.js";
export * from "./lib/classes/properties/CommonResolution.js";
export * from "./lib/classes/metadata/ModelCreator.js";
export * from "./lib/classes/metadata/ModelCreatorIcons.js";
export * from "./lib/classes/metadata/ModelMapping.js";
@@ -4,6 +4,7 @@ import { ModelCategory } from "../legacy/ModelConfig.js";
import { ModelTag } from "./metadata/ModelTag.js";
import { GenerationProvider } from "@storyteller/api-enums";
import { CommonAspectRatio } from "./properties/CommonAspectRatio.js";
import { CommonResolution } from "./properties/CommonResolution.js";
export class ImageModel extends Model {
// Typescript type discriminator property
@@ -51,10 +52,16 @@ export class ImageModel extends Model {
// (This is a new field that will take time to roll out and replace the old aspect ratios)
readonly aspectRatios: CommonAspectRatio[];
readonly resolutions: CommonResolution[];
// Aspect ratio to use as default when nothing is selected.
// Otherwise, use `aspectRatios[0]` as default.
readonly defaultAspectRatio?: CommonAspectRatio;
// Resolution to use as default when nothing is selected.
// Otherwise, use `resolutions[0]` as default.
readonly defaultResolution?: CommonResolution;
constructor(args: {
id: string;
tauriId: string;
@@ -79,7 +86,9 @@ export class ImageModel extends Model {
progressBarTime?: number;
providers?: GenerationProvider[];
aspectRatios?: CommonAspectRatio[];
resolutions?: CommonResolution[];
defaultAspectRatio?: CommonAspectRatio;
defaultResolution?: CommonResolution;
}) {
if (args.maxGenerationCount < 1) {
throw new Error("maxGenerationCount must be at least 1");
@@ -105,7 +114,9 @@ export class ImageModel extends Model {
this.canChangeResolution = args.canChangeResolution ?? false;
this.canChangeAspectRatio = args.canChangeAspectRatio ?? false;
this.aspectRatios = args.aspectRatios ?? [];
this.resolutions = args.resolutions ?? [];
this.defaultAspectRatio = args.defaultAspectRatio ?? (args.aspectRatios && args.aspectRatios.length > 0 ? args.aspectRatios[0] : undefined);
this.defaultResolution = args.defaultResolution ?? (args.resolutions && args.resolutions.length > 0 ? args.resolutions[0] : undefined);
}
// If the model is a "Nano Banana"-type model, we may want to enable certain features.
@@ -131,6 +142,14 @@ export class ImageModel extends Model {
&& this.aspectRatios.length > 0;
}
// Rolling out new resolution controls to models.
// If we have them set on the model, we can use the new UI controls.
supportsNewResolution(): boolean {
return this.canChangeResolution
&& this.resolutions
&& this.resolutions.length > 0;
}
// Return whether the count of generations is valid for this model
isValidGenerationCount(count: number): boolean {
if (this.predefinedGenerationCounts) {
@@ -0,0 +1,9 @@
/// Common aspect ratio Tauri API
/// Not all models have each of these aspect ratios,
/// but Tauri will interpolate if a mistake is sent.
export enum CommonResolution {
OneK = "one_k",
TwoK = "two_k",
FourK = "four_k",
}
@@ -3,6 +3,7 @@ import { ImageModel } from "../classes/ImageModel.js";
import { ModelCreator } from "../classes/metadata/ModelCreator.js";
import { ModelTag } from "../classes/metadata/ModelTag.js";
import { CommonAspectRatio } from "../classes/properties/CommonAspectRatio.js";
import { CommonResolution } from "../classes/properties/CommonResolution.js";
// TODO: Some of the model configs, such as generation counts, are authoritatively controlled in `legacy/Models.ts`
@@ -224,6 +225,11 @@ export const IMAGE_MODELS: ImageModel[] = [
CommonAspectRatio.TallThreeByFour,
CommonAspectRatio.TallTwoByThree,
CommonAspectRatio.TallNineBySixteen,
],
resolutions: [
CommonResolution.OneK,
CommonResolution.TwoK,
CommonResolution.FourK,
]
}),
new ImageModel({
@@ -2,6 +2,7 @@ import { invoke } from "@tauri-apps/api/core";
import { CommandResult } from "../common/CommandStatus";
import { CommonAspectRatio, Model } from "@storyteller/model-list";
import { GenerationProvider } from "@storyteller/api-enums";
import { CommonResolution } from "@storyteller/model-list";
export enum EnqueueTextToImageErrorType {
/// Caller didn't specify a model
@@ -24,13 +25,17 @@ export interface EnqueueTextToImageRequest {
// The text prompt.
prompt?: string;
// This is the new aspect ratio.
common_aspect_ratio?: CommonAspectRatio;
// This is the new aspect ratio.
common_resolution?: CommonResolution;
// TODO: This is deprecated and will be phased out.
// The desired output aspect ratio.
aspect_ratio?: EnqueueTextToImageSize;
// This is the new aspect ratio.
common_aspect_ratio?: CommonAspectRatio;
// TODO: This is deprecated and will be phased out.
// Image resolution. Support: Nano Banana Pro
image_resolution?: EnqueueTextToImageResolution;
@@ -59,6 +64,7 @@ interface EnqueueTextToImageRawRequest {
prompt?: string;
aspect_ratio?: EnqueueTextToImageSize;
common_aspect_ratio?: CommonAspectRatio;
common_resolution?: CommonResolution;
image_resolution?: EnqueueTextToImageResolution;
number_images?: number;
image_media_tokens?: string[];
@@ -127,14 +133,18 @@ export const EnqueueTextToImage = async (request: EnqueueTextToImageRequest) : P
mutableRequest.provider = request.provider;
}
if (!!request.aspect_ratio) {
mutableRequest.aspect_ratio = request.aspect_ratio;
}
if (!!request.common_aspect_ratio) {
mutableRequest.common_aspect_ratio = request.common_aspect_ratio;
}
if (!!request.common_resolution) {
mutableRequest.common_resolution = request.common_resolution;
}
if (!!request.aspect_ratio) {
mutableRequest.aspect_ratio = request.aspect_ratio;
}
if (!!request.image_resolution) {
mutableRequest.image_resolution = request.image_resolution;
}
+23
View File
@@ -2895,6 +2895,7 @@
"os": [
"aix"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -2912,6 +2913,7 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -2929,6 +2931,7 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -2946,6 +2949,7 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -2963,6 +2967,7 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -2980,6 +2985,7 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -2997,6 +3003,7 @@
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3014,6 +3021,7 @@
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3031,6 +3039,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3048,6 +3057,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3065,6 +3075,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3082,6 +3093,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3099,6 +3111,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3116,6 +3129,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3133,6 +3147,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3150,6 +3165,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3167,6 +3183,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3201,6 +3218,7 @@
"os": [
"netbsd"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3235,6 +3253,7 @@
"os": [
"openbsd"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3269,6 +3288,7 @@
"os": [
"sunos"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3286,6 +3306,7 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3303,6 +3324,7 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -3320,6 +3342,7 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=12"
}