@ -1159,7 +1159,7 @@ func (s *Server) executeImageToImageJob(record *job) {
defer cancel ( )
defer cancel ( )
input := buildImageToImageInput ( record )
input := buildImageToImageInput ( record )
ref , err := s . resolveImageToImageReference ( ctx , record , input )
imageRefs , err := s . resolveImageToImageReferences ( ctx , record , input )
if err != nil {
if err != nil {
if repoErr := s . store . failJob ( record . JobID , "REFERENCE_IMAGE_RESOLUTION_FAILED" , err . Error ( ) , map [ string ] any {
if repoErr := s . store . failJob ( record . JobID , "REFERENCE_IMAGE_RESOLUTION_FAILED" , err . Error ( ) , map [ string ] any {
"skill_id" : record . SkillID ,
"skill_id" : record . SkillID ,
@ -1169,15 +1169,23 @@ func (s *Server) executeImageToImageJob(record *job) {
}
}
return
return
}
}
log . Printf (
"popiartServer: image job %s using model=%s refs=%s preserve_composition=%t strength=%v" ,
record . JobID ,
modelID ,
summarizeImageEditReferenceRoles ( imageRefs ) ,
boolValue ( input [ "preserve_composition" ] ) ,
input [ "strength" ] ,
)
var (
var (
refs [ ] resultRef
resultRe fs [ ] resultRef
usage map [ string ] any
usage map [ string ] any
)
)
if useMiniMaxImageGenerations ( modelID ) {
if useMiniMaxImageGenerations ( modelID ) {
refs , usage , err = s . newapi . generateMiniMaxImageRefs ( ctx , record . UpstreamKey , modelID , input , [ ] imageEditReference { ref } )
resultRe fs , usage , err = s . newapi . generateMiniMaxImageRefs ( ctx , record . UpstreamKey , modelID , input , imageRefs )
} else {
} else {
refs , usage , err = s . newapi . generateEditedImageRefs ( ctx , record . UpstreamKey , modelID , input , ref )
resultRe fs , usage , err = s . newapi . generateEditedImageRefs ( ctx , record . UpstreamKey , modelID , input , imageRefs )
}
}
if err != nil {
if err != nil {
if repoErr := s . store . failJob ( record . JobID , "MODEL_REQUEST_FAILED" , err . Error ( ) , map [ string ] any {
if repoErr := s . store . failJob ( record . JobID , "MODEL_REQUEST_FAILED" , err . Error ( ) , map [ string ] any {
@ -1189,7 +1197,7 @@ func (s *Server) executeImageToImageJob(record *job) {
return
return
}
}
refs , err = s . persistResultRefs ( ctx , record , refs )
resultRe fs , err = s . persistResultRefs ( ctx , record , resultR efs )
if err != nil {
if err != nil {
if repoErr := s . store . failJob ( record . JobID , "MEDIA_PERSIST_FAILED" , "failed to persist generated media" , map [ string ] any {
if repoErr := s . store . failJob ( record . JobID , "MEDIA_PERSIST_FAILED" , "failed to persist generated media" , map [ string ] any {
"details" : err . Error ( ) ,
"details" : err . Error ( ) ,
@ -1201,7 +1209,7 @@ func (s *Server) executeImageToImageJob(record *job) {
return
return
}
}
if err := s . store . completeJobWithResults ( record . JobID , refs , usage ) ; err != nil {
if err := s . store . completeJobWithResults ( record . JobID , resultRe fs , usage ) ; err != nil {
log . Printf ( "popiartServer: complete job %s failed: %v" , record . JobID , err )
log . Printf ( "popiartServer: complete job %s failed: %v" , record . JobID , err )
if failErr := s . store . failJob ( record . JobID , "RESULT_REF_PERSIST_FAILED" , "failed to persist job result refs" , map [ string ] any {
if failErr := s . store . failJob ( record . JobID , "RESULT_REF_PERSIST_FAILED" , "failed to persist job result refs" , map [ string ] any {
"details" : err . Error ( ) ,
"details" : err . Error ( ) ,
@ -1394,33 +1402,45 @@ func inferRouteKeyForModel(modelID string, input map[string]any) string {
return "image.text2image"
return "image.text2image"
}
}
func ( s * Server ) resolveImageToImageReference ( ctx context . Context , record * job , input map [ string ] any ) ( imageEditReference , error ) {
func ( s * Server ) resolveImageToImageReferences ( ctx context . Context , record * job , input map [ string ] any ) ( [ ] imageEditReference , error ) {
if record == nil {
if record == nil {
return imageEditReference { } , fmt . Errorf ( "job record is required" )
return nil , fmt . Errorf ( "job record is required" )
}
}
if artifactID := strings . TrimSpace ( stringValue ( input [ "source_artifact_id" ] ) ) ; artifactID != "" {
if artifactID := strings . TrimSpace ( stringValue ( input [ "source_artifact_id" ] ) ) ; artifactID != "" {
item , r ef, exists , e rr := s . store . artifactRef ( record . UserID , artifactID )
sourceR ef, err := s . resolveArtifactImageReference ( ctx , record , artifactID , "source" )
if err != nil {
if err != nil {
return imageEditReference { } , err
return nil , err
}
}
if ! exists {
refs := [ ] imageEditReference { sourceRef }
return imageEditReference { } , fmt . Errorf ( "source artifact not found: %s" , artifactID )
seen := map [ string ] struct { } { artifactID : { } }
var appendArtifactRefs = func ( role string , values [ ] string ) error {
for _ , value := range values {
value = strings . TrimSpace ( value )
if value == "" {
continue
}
if _ , exists := seen [ value ] ; exists {
continue
}
ref , err := s . resolveArtifactImageReference ( ctx , record , value , role )
if err != nil {
return err
}
seen [ value ] = struct { } { }
refs = append ( refs , ref )
}
return nil
}
}
contentType , _ , reader , err := s . newapi . openResultRef ( ctx , record . UpstreamKey , ref )
if err := appendArtifactRefs ( "identity" , stringSliceValue ( input [ "identity_reference_artifact_ids" ] ) ) ; err != nil {
if err != nil {
return nil , err
return imageEditReference { } , err
}
}
defer reader . Close ( )
if err := appendArtifactRefs ( "style" , stringSliceValue ( input [ "style_reference_artifact_ids" ] ) ) ; err != nil {
content , err := io . ReadAll ( reader )
return nil , err
if err != nil {
}
return imageEditReference { } , err
if err := appendArtifactRefs ( "reference" , stringSliceValue ( input [ "reference_artifact_ids" ] ) ) ; err != nil {
return nil , err
}
}
return imageEditReference {
return refs , nil
Filename : item . Filename ,
ContentType : defaultString ( contentType , item . ContentType ) ,
Content : content ,
URL : item . URL ,
} , nil
}
}
refURL := strings . TrimSpace ( stringValue (
refURL := strings . TrimSpace ( stringValue (
@ -1432,9 +1452,25 @@ func (s *Server) resolveImageToImageReference(ctx context.Context, record *job,
refURL = defaultAliceReferenceURL
refURL = defaultAliceReferenceURL
}
}
if refURL == "" {
if refURL == "" {
return nil , fmt . Errorf ( "reference image is required" )
}
ref , err := s . downloadReferenceImage ( ctx , refURL )
if err != nil {
return nil , err
}
ref . Role = "source"
return [ ] imageEditReference { ref } , nil
}
func ( s * Server ) resolveImageToImageReference ( ctx context . Context , record * job , input map [ string ] any ) ( imageEditReference , error ) {
refs , err := s . resolveImageToImageReferences ( ctx , record , input )
if err != nil {
return imageEditReference { } , err
}
if len ( refs ) == 0 {
return imageEditReference { } , fmt . Errorf ( "reference image is required" )
return imageEditReference { } , fmt . Errorf ( "reference image is required" )
}
}
return s . downloadReferenceImage ( ctx , refURL )
return refs [ 0 ] , nil
}
}
func ( s * Server ) resolveVideoReferences ( ctx context . Context , record * job , input map [ string ] any ) ( [ ] imageEditReference , error ) {
func ( s * Server ) resolveVideoReferences ( ctx context . Context , record * job , input map [ string ] any ) ( [ ] imageEditReference , error ) {
@ -1651,6 +1687,32 @@ func buildAliceShowcasePrompt(input map[string]any, scenePrompt string) string {
return strings . Join ( parts , ", " )
return strings . Join ( parts , ", " )
}
}
func ( s * Server ) resolveArtifactImageReference ( ctx context . Context , record * job , artifactID , role string ) ( imageEditReference , error ) {
item , ref , exists , err := s . store . artifactRef ( record . UserID , artifactID )
if err != nil {
return imageEditReference { } , err
}
if ! exists {
return imageEditReference { } , fmt . Errorf ( "%s artifact not found: %s" , defaultString ( role , "reference" ) , artifactID )
}
contentType , _ , reader , err := s . newapi . openResultRef ( ctx , record . UpstreamKey , ref )
if err != nil {
return imageEditReference { } , err
}
defer reader . Close ( )
content , err := io . ReadAll ( reader )
if err != nil {
return imageEditReference { } , err
}
return imageEditReference {
Role : role ,
Filename : item . Filename ,
ContentType : defaultString ( contentType , item . ContentType ) ,
Content : content ,
URL : item . URL ,
} , nil
}
func buildGenericImageToImagePrompt ( input map [ string ] any , prompt string ) string {
func buildGenericImageToImagePrompt ( input map [ string ] any , prompt string ) string {
parts := make ( [ ] string , 0 , 6 )
parts := make ( [ ] string , 0 , 6 )
if prompt != "" {
if prompt != "" {
@ -1668,6 +1730,21 @@ func buildGenericImageToImagePrompt(input map[string]any, prompt string) string
return strings . Join ( parts , ", " )
return strings . Join ( parts , ", " )
}
}
func summarizeImageEditReferenceRoles ( refs [ ] imageEditReference ) string {
if len ( refs ) == 0 {
return "none"
}
parts := make ( [ ] string , 0 , len ( refs ) )
for _ , ref := range refs {
role := strings . TrimSpace ( ref . Role )
if role == "" {
role = "reference"
}
parts = append ( parts , role )
}
return strings . Join ( parts , "," )
}
func stringValue ( values ... any ) string {
func stringValue ( values ... any ) string {
for _ , value := range values {
for _ , value := range values {
if text , ok := value . ( string ) ; ok && strings . TrimSpace ( text ) != "" {
if text , ok := value . ( string ) ; ok && strings . TrimSpace ( text ) != "" {
@ -1677,6 +1754,40 @@ func stringValue(values ...any) string {
return ""
return ""
}
}
func boolValue ( value any ) bool {
switch typed := value . ( type ) {
case bool :
return typed
default :
return false
}
}
func stringSliceValue ( value any ) [ ] string {
switch typed := value . ( type ) {
case [ ] string :
return cleanedStringValues ( typed )
case [ ] any :
items := make ( [ ] string , 0 , len ( typed ) )
for _ , item := range typed {
items = append ( items , strings . TrimSpace ( fmt . Sprint ( item ) ) )
}
return cleanedStringValues ( items )
default :
return nil
}
}
func cleanedStringValues ( values [ ] string ) [ ] string {
items := make ( [ ] string , 0 , len ( values ) )
for _ , value := range values {
if trimmed := strings . TrimSpace ( value ) ; trimmed != "" {
items = append ( items , trimmed )
}
}
return items
}
func buildVideoResultRef ( baseURL , taskID , modelID string , task * videoTaskResult ) resultRef {
func buildVideoResultRef ( baseURL , taskID , modelID string , task * videoTaskResult ) resultRef {
urlValue := strings . TrimSpace ( task . URL )
urlValue := strings . TrimSpace ( task . URL )
if urlValue == "" {
if urlValue == "" {