@ -397,6 +397,7 @@ func classifyModelIDFallback(modelID string) (string, []string) {
strings . Contains ( lowerID , "i2v" ) :
return "video" , [ ] string { "text2video" , "image2video" }
case strings . Contains ( lowerID , "image" ) ,
strings . Contains ( lowerID , "seedream" ) ,
strings . Contains ( lowerID , "flux" ) ,
strings . Contains ( lowerID , "recraft" ) ,
strings . Contains ( lowerID , "midjourney" ) ,
@ -416,6 +417,9 @@ func (c *newAPIClient) generateImageRefs(ctx context.Context, token, modelID str
if useGeminiImageGenerateContent ( modelID ) {
return c . generateGeminiImageRefs ( ctx , token , modelID , input , nil )
}
if useSeedreamImageGenerations ( modelID ) {
return c . generateSeedreamImageRefs ( ctx , token , modelID , input , nil )
}
token = c . authorizedToken ( token )
if token == "" {
return nil , nil , errors . New ( "PopiNewAPI token is not configured" )
@ -477,6 +481,9 @@ func (c *newAPIClient) generateEditedImageRefs(ctx context.Context, token, model
if useGeminiImageGenerateContent ( modelID ) {
return c . generateGeminiImageRefs ( ctx , token , modelID , input , [ ] imageEditReference { ref } )
}
if useSeedreamImageGenerations ( modelID ) {
return c . generateSeedreamImageRefs ( ctx , token , modelID , input , [ ] imageEditReference { ref } )
}
token = c . authorizedImageEditToken ( token )
if token == "" {
return nil , nil , errors . New ( "PopiNewAPI token is not configured" )
@ -549,6 +556,74 @@ func (c *newAPIClient) generateEditedImageRefs(ctx context.Context, token, model
return decodeImageGenerationResponse ( resp . StatusCode , respBody , modelID )
}
func ( c * newAPIClient ) generateSeedreamImageRefs ( ctx context . Context , token , modelID string , input map [ string ] any , refs [ ] imageEditReference ) ( [ ] resultRef , map [ string ] any , error ) {
if ! c . enabled ( ) {
return nil , nil , errors . New ( "PopiNewAPI base URL is not configured" )
}
token = c . authorizedToken ( token )
if token == "" {
return nil , nil , errors . New ( "PopiNewAPI token is not configured" )
}
prompt := strings . TrimSpace ( stringValue ( input [ "prompt" ] ) )
if prompt == "" {
return nil , nil , errors . New ( "prompt is required" )
}
payload := map [ string ] any {
"model" : modelID ,
"prompt" : prompt ,
"response_format" : defaultString ( strings . TrimSpace ( stringValue ( input [ "response_format" ] ) ) , "url" ) ,
}
if size := resolveSeedreamImageSize ( modelID , input ) ; size != "" {
payload [ "size" ] = size
}
if value , ok := input [ "seed" ] ; ok {
payload [ "seed" ] = value
}
if value , ok := input [ "watermark" ] ; ok {
payload [ "watermark" ] = value
}
if value , ok := input [ "sequential_image_generation" ] ; ok {
payload [ "sequential_image_generation" ] = value
}
if value , ok := input [ "sequential_image_generation_options" ] ; ok {
payload [ "sequential_image_generation_options" ] = value
}
if len ( refs ) > 0 {
imageInput , err := encodeSeedreamReferenceInputs ( refs )
if err != nil {
return nil , nil , err
}
payload [ "image" ] = imageInput
}
body , err := json . Marshal ( payload )
if err != nil {
return nil , nil , err
}
req , err := http . NewRequestWithContext ( ctx , http . MethodPost , c . baseURL + "/v1/images/generations" , bytes . NewReader ( body ) )
if err != nil {
return nil , nil , err
}
req . Header . Set ( "Authorization" , "Bearer " + token )
req . Header . Set ( "Content-Type" , "application/json" )
resp , err := c . httpClient . Do ( req )
if err != nil {
return nil , nil , err
}
defer resp . Body . Close ( )
respBody , err := io . ReadAll ( resp . Body )
if err != nil {
return nil , nil , err
}
return decodeImageGenerationResponse ( resp . StatusCode , respBody , modelID )
}
func ( c * newAPIClient ) generateGeminiImageRefs ( ctx context . Context , token , modelID string , input map [ string ] any , refs [ ] imageEditReference ) ( [ ] resultRef , map [ string ] any , error ) {
if ! c . enabled ( ) {
return nil , nil , errors . New ( "PopiNewAPI base URL is not configured" )
@ -1035,6 +1110,11 @@ func useGeminiImageGenerateContent(modelID string) bool {
return strings . Contains ( lowerID , "gemini" ) || strings . Contains ( lowerID , "banana" )
}
func useSeedreamImageGenerations ( modelID string ) bool {
lowerID := strings . ToLower ( strings . TrimSpace ( modelID ) )
return strings . Contains ( lowerID , "seedream" )
}
func resolveGeminiAspectRatio ( input map [ string ] any ) string {
if aspectRatio := strings . TrimSpace ( stringValue ( input [ "aspect_ratio" ] ) ) ; aspectRatio != "" {
return aspectRatio
@ -1074,6 +1154,62 @@ func resolveGeminiImageSize(input map[string]any) string {
}
}
func resolveSeedreamImageSize ( modelID string , input map [ string ] any ) string {
supported := supportedSeedreamSizes ( modelID )
for _ , value := range [ ] string {
strings . ToUpper ( strings . TrimSpace ( stringValue ( input [ "resolution" ] ) ) ) ,
strings . ToUpper ( strings . TrimSpace ( stringValue ( input [ "size" ] ) ) ) ,
} {
if _ , ok := supported [ value ] ; ok {
return value
}
}
return "2K"
}
func supportedSeedreamSizes ( modelID string ) map [ string ] struct { } {
lowerID := strings . ToLower ( strings . TrimSpace ( modelID ) )
if strings . Contains ( lowerID , "5-0" ) {
return map [ string ] struct { } {
"2K" : { } ,
"3K" : { } ,
}
}
return map [ string ] struct { } {
"2K" : { } ,
"4K" : { } ,
}
}
func encodeSeedreamReferenceInputs ( refs [ ] imageEditReference ) ( any , error ) {
encoded := make ( [ ] string , 0 , len ( refs ) )
for _ , ref := range refs {
value , err := encodeSeedreamReference ( ref )
if err != nil {
return nil , err
}
encoded = append ( encoded , value )
}
if len ( encoded ) == 1 {
return encoded [ 0 ] , nil
}
return encoded , nil
}
func encodeSeedreamReference ( ref imageEditReference ) ( string , error ) {
if rawURL := strings . TrimSpace ( ref . URL ) ; rawURL != "" {
return rawURL , nil
}
if len ( ref . Content ) == 0 {
return "" , errors . New ( "reference image content is required" )
}
contentType := strings . TrimSpace ( ref . ContentType )
if contentType == "" {
contentType = http . DetectContentType ( ref . Content )
}
return "data:" + contentType + ";base64," + base64 . StdEncoding . EncodeToString ( ref . Content ) , nil
}
func ( c * newAPIClient ) openResultRef ( ctx context . Context , token string , ref resultRef ) ( string , int64 , io . ReadCloser , error ) {
switch ref . Kind {
case "local_path" :