mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-20 17:02:53 -04:00
artcraft desktop gpt-image cost estimates + quality arg (#1425)
This commit is contained in:
+2
-1
@@ -15,11 +15,12 @@ body:
|
||||
type: json
|
||||
content: |
|
||||
{
|
||||
"model": "seedream_5_lite",
|
||||
"model": "gpt_image_2",
|
||||
"provider": "artcraft",
|
||||
"generation_mode": {"type": "text_to_image" },
|
||||
"aspect_ratio": "wide_sixteen_by_nine",
|
||||
"resolution": "two_k",
|
||||
"quality": "low",
|
||||
"image_batch_count": 1
|
||||
}
|
||||
order: 2000
|
||||
|
||||
+8
-2
@@ -3,13 +3,14 @@ use utoipa::ToSchema;
|
||||
|
||||
use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
|
||||
use enums::common::generation::common_image_model::CommonImageModel;
|
||||
use enums::common::generation::common_quality::CommonQuality;
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
|
||||
pub const ESTIMATE_IMAGE_COST_PATH: &str = "/v1/generate/cost_estimate/image";
|
||||
|
||||
/// Request body for the image cost estimate endpoint.
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug)]
|
||||
pub struct EstimateImageCostRequest {
|
||||
/// The image model to estimate costs for.
|
||||
pub model: CommonImageModel,
|
||||
@@ -29,12 +30,15 @@ pub struct EstimateImageCostRequest {
|
||||
/// Optional resolution.
|
||||
pub resolution: Option<CommonResolution>,
|
||||
|
||||
// Optional quality.
|
||||
pub quality: Option<CommonQuality>,
|
||||
|
||||
/// Number of images to generate in parallel.
|
||||
pub image_batch_count: Option<u16>,
|
||||
}
|
||||
|
||||
/// Describes the type of image generation being requested.
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum GenerationMode {
|
||||
/// No input images — generate from prompt only.
|
||||
@@ -94,6 +98,7 @@ mod tests {
|
||||
generation_mode: GenerationMode::TextToImage,
|
||||
aspect_ratio: None,
|
||||
resolution: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
};
|
||||
let serialized = serde_json::to_string(&request).unwrap();
|
||||
@@ -108,6 +113,7 @@ mod tests {
|
||||
generation_mode: GenerationMode::ImageEdit { count: 2 },
|
||||
aspect_ratio: None,
|
||||
resolution: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
};
|
||||
let serialized = serde_json::to_string(&request).unwrap();
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ pub async fn estimate_image_cost_command(
|
||||
request: EstimateImageCostRequest,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
) -> ResponseOrError<EstimateImageCostResponse, EstimateImageCostError> {
|
||||
debug!("estimate_image_cost_command called");
|
||||
debug!("estimate_image_cost_command called: {:?}", request);
|
||||
|
||||
let result = estimate_image_cost(
|
||||
&app_env_configs.storyteller_host,
|
||||
|
||||
@@ -378,6 +378,10 @@ fn build_omni_gen_image_model_providers() -> Vec<OmniGenImageModelProviderDetail
|
||||
model: CommonImageModel::GptImage1p5,
|
||||
overrides: None,
|
||||
},
|
||||
OmniGenImageProviderModelDetails {
|
||||
model: CommonImageModel::GptImage2,
|
||||
overrides: None,
|
||||
},
|
||||
OmniGenImageProviderModelDetails {
|
||||
model: CommonImageModel::Seedream4,
|
||||
overrides: None,
|
||||
|
||||
+13
-2
@@ -9,6 +9,7 @@ use artcraft_api_defs::generate::cost_estimate::estimate_image_cost::{
|
||||
};
|
||||
use artcraft_router::api::common_aspect_ratio::CommonAspectRatio as RouterAspectRatio;
|
||||
use artcraft_router::api::common_image_model::CommonImageModel as RouterImageModel;
|
||||
use artcraft_router::api::common_quality::CommonQuality as RouterQuality;
|
||||
use artcraft_router::api::common_resolution::CommonResolution as RouterResolution;
|
||||
use artcraft_router::api::provider::Provider as RouterProvider;
|
||||
use artcraft_router::client::generation_mode_mismatch_strategy::GenerationModeMismatchStrategy;
|
||||
@@ -16,6 +17,7 @@ use artcraft_router::client::request_mismatch_mitigation_strategy::RequestMismat
|
||||
use artcraft_router::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
|
||||
use enums::common::generation::common_image_model::CommonImageModel;
|
||||
use enums::common::generation::common_quality::CommonQuality;
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
|
||||
@@ -37,6 +39,7 @@ pub async fn estimate_image_cost_handler(
|
||||
let router_model = map_image_model(request.model)?;
|
||||
let router_aspect_ratio = request.aspect_ratio.map(map_aspect_ratio);
|
||||
let router_resolution = request.resolution.map(map_resolution);
|
||||
let router_quality = request.quality.map(map_quality);
|
||||
|
||||
let router_request = GenerateImageRequest {
|
||||
model: router_model,
|
||||
@@ -45,7 +48,7 @@ pub async fn estimate_image_cost_handler(
|
||||
image_inputs: None, // TODO: Only some models charge for this - we'll need to add later.
|
||||
resolution: router_resolution,
|
||||
aspect_ratio: router_aspect_ratio,
|
||||
quality: None,
|
||||
quality: router_quality,
|
||||
image_batch_count: request.image_batch_count,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::GenerateAnyway),
|
||||
@@ -132,13 +135,13 @@ fn map_image_model(model: CommonImageModel) -> Result<RouterImageModel, HandlerE
|
||||
CommonImageModel::FluxPro11Ultra => RouterImageModel::FluxPro11Ultra,
|
||||
CommonImageModel::GptImage1 => RouterImageModel::GptImage1,
|
||||
CommonImageModel::GptImage1p5 => RouterImageModel::GptImage1p5,
|
||||
CommonImageModel::GptImage2 => RouterImageModel::GptImage2,
|
||||
CommonImageModel::NanoBanana => RouterImageModel::NanoBanana,
|
||||
CommonImageModel::NanoBanana2 => RouterImageModel::NanoBanana2,
|
||||
CommonImageModel::NanoBananaPro => RouterImageModel::NanoBananaPro,
|
||||
CommonImageModel::Seedream4 => RouterImageModel::Seedream4,
|
||||
CommonImageModel::Seedream4p5 => RouterImageModel::Seedream4p5,
|
||||
CommonImageModel::Seedream5Lite => RouterImageModel::Seedream5Lite,
|
||||
CommonImageModel::GptImage2 => RouterImageModel::GptImage2,
|
||||
};
|
||||
Ok(router_model)
|
||||
}
|
||||
@@ -178,3 +181,11 @@ fn map_resolution(res: CommonResolution) -> RouterResolution {
|
||||
CommonResolution::TenEightyP => RouterResolution::TenEightyP,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_quality(res: CommonQuality) -> RouterQuality {
|
||||
match res {
|
||||
CommonQuality::Low => RouterQuality::Low,
|
||||
CommonQuality::Medium => RouterQuality::Medium,
|
||||
CommonQuality::High => RouterQuality::High,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export enum CommonImageModel {
|
||||
FluxPro11Ultra = "flux_pro_1p1_ultra",
|
||||
GptImage1 = "gpt_image_1",
|
||||
GptImage1p5 = "gpt_image_1p5",
|
||||
GptImage2 = "gpt_image_2",
|
||||
NanaBanana = "nano_banana",
|
||||
NanaBanana2 = "nano_banana_2",
|
||||
NanaBananaPro = "nano_banana_pro",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
// NOTE: These are defined in Rust (as the source of truth) and duplicated in the frontend.
|
||||
// In the future, we should use code gen (protobufs or similar) to keep the two sides in sync.
|
||||
|
||||
export enum CommonQuality {
|
||||
High = "high",
|
||||
Medium = "medium",
|
||||
Low = "low",
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from "./CommonImageModel.js";
|
||||
export * from "./CommonVideoModel.js";
|
||||
export * from "./CommonAspectRatio.js";
|
||||
export * from "./CommonQuality.js";
|
||||
export * from "./CommonVideoResolution.js";
|
||||
export * from "./GenerationMode.js";
|
||||
export * from "./ImageGenerationMode.js";
|
||||
|
||||
@@ -14,8 +14,12 @@ export function imageModelToCommonImageModel(
|
||||
return CommonImageModel.FluxPro11;
|
||||
case "flux_pro_11_ultra":
|
||||
return CommonImageModel.FluxPro11Ultra;
|
||||
case "gpt_image_1":
|
||||
return CommonImageModel.GptImage1;
|
||||
case "gpt_image_1p5":
|
||||
return CommonImageModel.GptImage1p5;
|
||||
case "gpt_image_2":
|
||||
return CommonImageModel.GptImage2;
|
||||
case "nano_banana":
|
||||
return CommonImageModel.NanaBanana;
|
||||
case "nano_banana_2":
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { CommonQuality } from "@storyteller/api-enums";
|
||||
|
||||
/**
|
||||
* Convert a quality value to CommonQuality for the image cost estimate API.
|
||||
*
|
||||
* Accepts a CommonQuality string value from @storyteller/model-list ("high", "medium", "low").
|
||||
*/
|
||||
export function imageQualityToCommonQuality(
|
||||
quality: string | undefined,
|
||||
): CommonQuality | null {
|
||||
switch (quality) {
|
||||
case "high":
|
||||
return CommonQuality.High;
|
||||
case "medium":
|
||||
return CommonQuality.Medium;
|
||||
case "low":
|
||||
return CommonQuality.Low;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,6 @@ export * from "./videoAspectRatioToCommonAspectRatio.js";
|
||||
export * from "./videoStoreToGenerationMode.js";
|
||||
export * from "./imageModelToCommonImageModel.js";
|
||||
export * from "./imageAspectRatioToCommonAspectRatio.js";
|
||||
export * from "./imageQualityToCommonQuality.js";
|
||||
export * from "./imageResolutionToCommonVideoResolution.js";
|
||||
export * from "./imageStoreToGenerationMode.js";
|
||||
|
||||
@@ -277,6 +277,7 @@ export function CostBreakdownModal({ activeTabId }: CostBreakdownModalProps) {
|
||||
"flux_pro_11",
|
||||
"flux_pro_11_ultra",
|
||||
"gpt_image_1p5",
|
||||
"gpt_image_2",
|
||||
"nano_banana",
|
||||
"nano_banana_2",
|
||||
"nano_banana_pro",
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useCostBreakdownModalStore } from "./cost-breakdown-modal-store";
|
||||
import {
|
||||
imageModelToCommonImageModel,
|
||||
imageAspectRatioToCommonAspectRatio,
|
||||
imageQualityToCommonQuality,
|
||||
imageResolutionToCommonVideoResolution,
|
||||
} from "./convert/index.js";
|
||||
|
||||
@@ -44,6 +45,7 @@ export function useImageCostEstimate(
|
||||
const imageLegacyResolution = usePromptImageStore((s) => s.resolution);
|
||||
const imageReferenceImages = usePromptImageStore((s) => s.referenceImages);
|
||||
const imageGenerationCount = usePromptImageStore((s) => s.generationCount);
|
||||
const imageQuality = usePromptImageStore((s) => s.commonQuality);
|
||||
|
||||
// Canvas2D store
|
||||
const prompt2DAspectRatio = usePrompt2DStore((s) => s.aspectRatio);
|
||||
@@ -74,6 +76,7 @@ export function useImageCostEstimate(
|
||||
let aspectRatioStr: string | undefined;
|
||||
let legacyAspectRatioStr: string | undefined;
|
||||
let resolutionStr: string | undefined;
|
||||
let qualityStr: string | undefined;
|
||||
let referenceImageCount = 0;
|
||||
let generationCount = 1;
|
||||
|
||||
@@ -82,6 +85,7 @@ export function useImageCostEstimate(
|
||||
aspectRatioStr = imageAspectRatio;
|
||||
legacyAspectRatioStr = imageLegacyAspectRatio;
|
||||
resolutionStr = imageResolution ?? imageLegacyResolution;
|
||||
qualityStr = imageQuality;
|
||||
referenceImageCount = imageReferenceImages.length;
|
||||
generationCount = imageGenerationCount;
|
||||
break;
|
||||
@@ -114,6 +118,7 @@ export function useImageCostEstimate(
|
||||
);
|
||||
const commonResolution =
|
||||
imageResolutionToCommonVideoResolution(resolutionStr);
|
||||
const commonQuality = imageQualityToCommonQuality(qualityStr);
|
||||
const generationMode =
|
||||
referenceImageCount > 0
|
||||
? { type: "image_edit" as const, count: referenceImageCount }
|
||||
@@ -131,6 +136,7 @@ export function useImageCostEstimate(
|
||||
generation_mode: generationMode,
|
||||
aspect_ratio: commonAspectRatio ?? undefined,
|
||||
resolution: commonResolution ?? undefined,
|
||||
quality: commonQuality ?? undefined,
|
||||
})
|
||||
.then((result) => {
|
||||
if (isEstimateImageCostSuccess(result)) {
|
||||
@@ -160,6 +166,7 @@ export function useImageCostEstimate(
|
||||
imageLegacyResolution,
|
||||
imageReferenceImages.length,
|
||||
imageGenerationCount,
|
||||
imageQuality,
|
||||
prompt2DAspectRatio,
|
||||
prompt2DResolution,
|
||||
prompt2DReferenceImages.length,
|
||||
|
||||
@@ -296,12 +296,12 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
progressBarTime: 60000,
|
||||
maxPromptLength: 1500,
|
||||
canChangeAspectRatio: true,
|
||||
// qualityOptions: [
|
||||
// CommonQuality.High,
|
||||
// CommonQuality.Medium,
|
||||
// CommonQuality.Low,
|
||||
// ],
|
||||
// defaultQuality: CommonQuality.High,
|
||||
defaultQuality: CommonQuality.High,
|
||||
qualityOptions: [
|
||||
CommonQuality.High,
|
||||
CommonQuality.Medium,
|
||||
CommonQuality.Low,
|
||||
],
|
||||
}),
|
||||
new ImageModel({
|
||||
id: "gpt_image_1p5",
|
||||
@@ -327,12 +327,12 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
CommonAspectRatio.Wide,
|
||||
CommonAspectRatio.Tall,
|
||||
],
|
||||
// qualityOptions: [
|
||||
// CommonQuality.High,
|
||||
// CommonQuality.Medium,
|
||||
// CommonQuality.Low,
|
||||
// ],
|
||||
// defaultQuality: CommonQuality.High,
|
||||
defaultQuality: CommonQuality.High,
|
||||
qualityOptions: [
|
||||
CommonQuality.High,
|
||||
CommonQuality.Medium,
|
||||
CommonQuality.Low,
|
||||
],
|
||||
}),
|
||||
new ImageModel({
|
||||
id: "gpt_image_2",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CommandResult, CommandSuccessStatus } from "../common/CommandStatus";
|
||||
import {
|
||||
CommonImageModel,
|
||||
CommonAspectRatio,
|
||||
CommonQuality,
|
||||
CommonVideoResolution,
|
||||
ImageGenerationMode,
|
||||
GenerationProvider,
|
||||
@@ -14,6 +15,7 @@ export interface EstimateImageCostRequest {
|
||||
generation_mode: ImageGenerationMode;
|
||||
aspect_ratio?: CommonAspectRatio;
|
||||
resolution?: CommonVideoResolution;
|
||||
quality?: CommonQuality;
|
||||
image_batch_count?: number;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user