| import type { |
| GenerateImageInput, |
| ImageBackground, |
| ImageInputFidelity, |
| ImageModeration, |
| ImageOutputFormat, |
| ImageQuality, |
| ImageRatio |
| } from './types' |
|
|
| export const DEFAULT_MODEL = 'gpt-image-2' |
| export const DEFAULT_IMAGE_OUTPUT_FORMAT: ImageOutputFormat = 'png' |
| export const IMAGE_RATIOS: ImageRatio[] = ['1:1', '3:2', '2:3', '4:3', '3:4', '16:9', '9:16', '21:9', '9:21'] |
| export const IMAGE_QUALITIES: GenerateImageInput['quality'][] = ['auto', 'low', 'medium', 'high'] |
| export const IMAGE_OUTPUT_FORMATS: ImageOutputFormat[] = ['jpeg', 'png', 'webp'] |
| export const IMAGE_BACKGROUNDS: ImageBackground[] = ['auto', 'opaque'] |
| export const IMAGE_MODERATIONS: ImageModeration[] = ['auto', 'low'] |
| export const IMAGE_INPUT_FIDELITIES: ImageInputFidelity[] = ['low', 'high'] |
| export const IMAGE_QUALITY_LABELS: Record<ImageQuality, string> = { |
| auto: 'θͺε¨', |
| low: 'δ½', |
| medium: 'δΈ', |
| high: 'ι«' |
| } |
| export const IMAGE_OUTPUT_FORMAT_LABELS: Record<ImageOutputFormat, string> = { |
| png: 'PNG', |
| jpeg: 'JPEG', |
| webp: 'WebP' |
| } |
| export const IMAGE_BACKGROUND_LABELS: Record<ImageBackground, string> = { |
| auto: 'θͺε¨', |
| opaque: 'δΈιζ' |
| } |
| export const IMAGE_MODERATION_LABELS: Record<ImageModeration, string> = { |
| auto: 'θͺε¨', |
| low: 'δ½' |
| } |
| export const IMAGE_INPUT_FIDELITY_LABELS: Record<ImageInputFidelity, string> = { |
| low: 'δ½', |
| high: 'ι«' |
| } |
|
|
| export type ImageSizeOption = { |
| value: string |
| label: string |
| } |
|
|
| const imageSizeOptionsByRatio: Record<ImageRatio, ImageSizeOption[]> = { |
| '1:1': [ |
| { value: '1024x1024', label: 'ζ ε 1024Γ1024' }, |
| { value: '1536x1536', label: 'ι«ζΈ
1536Γ1536' }, |
| { value: '2048x2048', label: '2K 2048Γ2048' }, |
| { value: '2560x2560', label: 'ι«ε 2560Γ2560' } |
| ], |
| '3:2': [ |
| { value: '1536x1024', label: 'ζ ε 1536Γ1024' }, |
| { value: '3072x2048', label: 'ι«ε 3072Γ2048' } |
| ], |
| '2:3': [ |
| { value: '1024x1536', label: 'ζ ε 1024Γ1536' }, |
| { value: '2048x3072', label: 'ι«ε 2048Γ3072' } |
| ], |
| '4:3': [ |
| { value: '1024x768', label: 'ζ ε 1024Γ768' }, |
| { value: '1536x1152', label: 'ι«ζΈ
1536Γ1152' }, |
| { value: '2048x1536', label: 'ι«ε 2048Γ1536' }, |
| { value: '3072x2304', label: 'ι«ε 3072Γ2304' } |
| ], |
| '3:4': [ |
| { value: '768x1024', label: 'ζ ε 768Γ1024' }, |
| { value: '1152x1536', label: 'ι«ζΈ
1152Γ1536' }, |
| { value: '1536x2048', label: 'ι«ε 1536Γ2048' }, |
| { value: '2304x3072', label: 'ι«ε 2304Γ3072' } |
| ], |
| '16:9': [ |
| { value: '1280x720', label: '720P 1280Γ720' }, |
| { value: '1536x864', label: '1536Γ864' }, |
| { value: '1792x1008', label: 'ζ ε 1792Γ1008' }, |
| { value: '2048x1152', label: '2K 2048Γ1152' }, |
| { value: '2560x1440', label: '2K 2560Γ1440' }, |
| { value: '3072x1728', label: 'ι«ε 3072Γ1728' }, |
| { value: '3840x2160', label: '4K 3840Γ2160' } |
| ], |
| '9:16': [ |
| { value: '720x1280', label: '720P 720Γ1280' }, |
| { value: '864x1536', label: '864Γ1536' }, |
| { value: '1008x1792', label: 'ζ ε 1008Γ1792' }, |
| { value: '1152x2048', label: '2K 1152Γ2048' }, |
| { value: '1440x2560', label: '2K 1440Γ2560' }, |
| { value: '1728x3072', label: 'ι«ε 1728Γ3072' }, |
| { value: '2160x3840', label: '4K 2160Γ3840' } |
| ], |
| '21:9': [ |
| { value: '1344x576', label: 'ζ ε 1344Γ576' }, |
| { value: '1792x768', label: 'ι«ε 1792Γ768' }, |
| { value: '2240x960', label: 'ι«ε 2240Γ960' }, |
| { value: '2688x1152', label: 'ι«ε 2688Γ1152' }, |
| { value: '3136x1344', label: 'ι«ε 3136Γ1344' }, |
| { value: '3584x1536', label: 'ι«ε 3584Γ1536' } |
| ], |
| '9:21': [ |
| { value: '576x1344', label: 'ζ ε 576Γ1344' }, |
| { value: '768x1792', label: 'ι«ε 768Γ1792' }, |
| { value: '960x2240', label: 'ι«ε 960Γ2240' }, |
| { value: '1152x2688', label: 'ι«ε 1152Γ2688' }, |
| { value: '1344x3136', label: 'ι«ε 1344Γ3136' }, |
| { value: '1536x3584', label: 'ι«ε 1536Γ3584' } |
| ] |
| } |
|
|
| export function getImageSizeOptions(ratio: ImageRatio): ImageSizeOption[] { |
| return imageSizeOptionsByRatio[ratio] |
| } |
|
|
| export function getDefaultImageSize(ratio: ImageRatio): string { |
| return getImageSizeOptions(ratio).find((option) => option.label.startsWith('ζ ε'))?.value |
| || getImageSizeOptions(ratio)[0]?.value |
| || ratioToSize(ratio) |
| } |
|
|
| export function isImageSizeCompatible(ratio: ImageRatio, size: string): boolean { |
| return getImageSizeOptions(ratio).some((option) => option.value === size) |
| } |
|
|
| const ratioSizeMap: Record<ImageRatio, string> = { |
| '1:1': '1024x1024', |
| '3:2': '1536x1024', |
| '2:3': '1024x1536', |
| '4:3': '1024x768', |
| '3:4': '768x1024', |
| '16:9': '1792x1008', |
| '9:16': '1008x1792', |
| '21:9': '1344x576', |
| '9:21': '576x1344' |
| } |
|
|
| export function ratioToSize(ratio: ImageRatio): string { |
| return ratioSizeMap[ratio] |
| } |
|
|
| export function formatImageQuality(quality: ImageQuality): string { |
| return IMAGE_QUALITY_LABELS[quality] || quality |
| } |
|
|
| export function formatImageSize(size: string): string { |
| return size.replace('x', 'Γ') |
| } |
|
|
| function toOptionalNumber(value: number | null | undefined): number | undefined { |
| return typeof value === 'number' && Number.isFinite(value) ? value : undefined |
| } |
|
|
| export function buildImageEndpoint(baseURL: string): string { |
| const normalized = baseURL.trim().replace(/\/+$/, '') |
| return `${normalized}/v1/images/generations` |
| } |
|
|
| export function buildImageEditEndpoint(baseURL: string): string { |
| const normalized = baseURL.trim().replace(/\/+$/, '') |
| return `${normalized}/v1/images/edits` |
| } |
|
|
| export function supportsImageInputFidelity(model: string): boolean { |
| return !model.trim().toLowerCase().startsWith('gpt-image-2') |
| } |
|
|
| export function buildImageRequestBody(input: GenerateImageInput): Record<string, unknown> { |
| const body: Record<string, unknown> = { |
| prompt: input.prompt.trim(), |
| model: input.model.trim() || DEFAULT_MODEL, |
| size: input.size.trim() || getDefaultImageSize(input.ratio), |
| quality: input.quality, |
| n: Math.min(10, Math.max(1, input.n || 1)) |
| } |
| if (input.outputFormat) body.output_format = input.outputFormat |
| if (toOptionalNumber(input.outputCompression) != null) body.output_compression = input.outputCompression |
| if (input.background) body.background = input.background |
| if (input.moderation) body.moderation = input.moderation |
| if (input.stream) body.stream = true |
| if (toOptionalNumber(input.partialImages) != null) body.partial_images = input.partialImages |
| return body |
| } |
|
|