Restore MiniMax media routing parity across image and video flows
The server now keeps portable image ratio normalization, enforces documented Seedream size presets, routes MiniMax img2img through generations-style payloads, and submits Hailuo video variants through the video generations API with proper reference-image handling. Constraint: The deployed test chain relies on popiartServer as the lowest-risk place to adapt provider-specific routing without broad newapi changes Rejected: Expand newapi relay modes for every MiniMax image/video branch | larger blast radius than needed for the verified server-managed flows Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep MiniMax-specific media routing in popiartServer unless upstream newapi adapters are upgraded to the same contract Tested: go test ./internal/server -run TestInferRouteKeyForModelRecognizesViduAsVideo|TestGenerate|TestResolve|TestSubmitMiniMaxVideoTask|TestExecuteImageToImageJobUsesGenerationsPathForMiniMax|TestExecuteImageToVideoJob|TestResolveVideoReferencesSupportsImagesArray Not-tested: Full PopiNewAPI unit suite (blocked earlier by external module checksum drift in local checkout)
This commit is contained in:
+115
-10
@@ -1170,7 +1170,15 @@ func (s *Server) executeImageToImageJob(record *job) {
|
||||
return
|
||||
}
|
||||
|
||||
refs, usage, err := s.newapi.generateEditedImageRefs(ctx, record.UpstreamKey, modelID, input, ref)
|
||||
var (
|
||||
refs []resultRef
|
||||
usage map[string]any
|
||||
)
|
||||
if useMiniMaxImageGenerations(modelID) {
|
||||
refs, usage, err = s.newapi.generateMiniMaxImageRefs(ctx, record.UpstreamKey, modelID, input, []imageEditReference{ref})
|
||||
} else {
|
||||
refs, usage, err = s.newapi.generateEditedImageRefs(ctx, record.UpstreamKey, modelID, input, ref)
|
||||
}
|
||||
if err != nil {
|
||||
if repoErr := s.store.failJob(record.JobID, "MODEL_REQUEST_FAILED", err.Error(), map[string]any{
|
||||
"model_id": modelID,
|
||||
@@ -1224,18 +1232,41 @@ func (s *Server) executeImageToVideoJob(record *job) {
|
||||
submitCtx, cancelSubmit := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancelSubmit()
|
||||
|
||||
ref, err := s.resolveImageToImageReference(submitCtx, record, input)
|
||||
if err != nil {
|
||||
if repoErr := s.store.failJob(record.JobID, "REFERENCE_IMAGE_RESOLUTION_FAILED", err.Error(), map[string]any{
|
||||
"skill_id": record.SkillID,
|
||||
"route_key": record.RouteKey,
|
||||
}); repoErr != nil {
|
||||
log.Printf("popiartServer: fail job %s failed: %v", record.JobID, repoErr)
|
||||
var refsForMiniMax []imageEditReference
|
||||
var ref imageEditReference
|
||||
var err error
|
||||
if useMiniMaxVideoGenerations(modelID) {
|
||||
if hasVideoReferenceInput(input) {
|
||||
refsForMiniMax, err = s.resolveVideoReferences(submitCtx, record, input)
|
||||
if err != nil {
|
||||
if repoErr := s.store.failJob(record.JobID, "REFERENCE_IMAGE_RESOLUTION_FAILED", err.Error(), map[string]any{
|
||||
"skill_id": record.SkillID,
|
||||
"route_key": record.RouteKey,
|
||||
}); repoErr != nil {
|
||||
log.Printf("popiartServer: fail job %s failed: %v", record.JobID, repoErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ref, err = s.resolveImageToImageReference(submitCtx, record, input)
|
||||
if err != nil {
|
||||
if repoErr := s.store.failJob(record.JobID, "REFERENCE_IMAGE_RESOLUTION_FAILED", err.Error(), map[string]any{
|
||||
"skill_id": record.SkillID,
|
||||
"route_key": record.RouteKey,
|
||||
}); repoErr != nil {
|
||||
log.Printf("popiartServer: fail job %s failed: %v", record.JobID, repoErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
upstreamTaskID, err := s.newapi.submitImageToVideoTask(submitCtx, record.UpstreamKey, modelID, input, ref)
|
||||
var upstreamTaskID string
|
||||
if useMiniMaxVideoGenerations(modelID) {
|
||||
upstreamTaskID, err = s.newapi.submitMiniMaxVideoTask(submitCtx, record.UpstreamKey, modelID, input, refsForMiniMax)
|
||||
} else {
|
||||
upstreamTaskID, err = s.newapi.submitImageToVideoTask(submitCtx, record.UpstreamKey, modelID, input, ref)
|
||||
}
|
||||
if err != nil {
|
||||
if repoErr := s.store.failJob(record.JobID, "MODEL_REQUEST_FAILED", err.Error(), map[string]any{
|
||||
"model_id": modelID,
|
||||
@@ -1305,6 +1336,18 @@ func (s *Server) executeImageToVideoJob(record *job) {
|
||||
}
|
||||
}
|
||||
|
||||
func hasVideoReferenceInput(input map[string]any) bool {
|
||||
if input == nil {
|
||||
return false
|
||||
}
|
||||
return strings.TrimSpace(stringValue(
|
||||
input["source_artifact_id"],
|
||||
input["image"],
|
||||
input["image_url"],
|
||||
input["reference_image_url"],
|
||||
)) != "" || len(extractStringValues(input["images"])) > 0
|
||||
}
|
||||
|
||||
func (s *Server) executeUnsupportedSkill(record *job) {
|
||||
if _, _, err := s.store.startJob(record.JobID); err != nil {
|
||||
log.Printf("popiartServer: start job %s failed: %v", record.JobID, err)
|
||||
@@ -1394,6 +1437,68 @@ func (s *Server) resolveImageToImageReference(ctx context.Context, record *job,
|
||||
return s.downloadReferenceImage(ctx, refURL)
|
||||
}
|
||||
|
||||
func (s *Server) resolveVideoReferences(ctx context.Context, record *job, input map[string]any) ([]imageEditReference, error) {
|
||||
if record == nil {
|
||||
return nil, fmt.Errorf("job record is required")
|
||||
}
|
||||
if artifactID := strings.TrimSpace(stringValue(input["source_artifact_id"])); artifactID != "" {
|
||||
ref, err := s.resolveImageToImageReference(ctx, record, map[string]any{
|
||||
"source_artifact_id": artifactID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []imageEditReference{ref}, nil
|
||||
}
|
||||
|
||||
urls := extractStringValues(input["images"])
|
||||
if len(urls) == 0 {
|
||||
if refURL := strings.TrimSpace(stringValue(
|
||||
input["image"],
|
||||
input["reference_image_url"],
|
||||
input["image_url"],
|
||||
)); refURL != "" {
|
||||
urls = []string{refURL}
|
||||
}
|
||||
}
|
||||
if len(urls) == 0 {
|
||||
return nil, fmt.Errorf("reference image is required")
|
||||
}
|
||||
|
||||
refs := make([]imageEditReference, 0, len(urls))
|
||||
for _, rawURL := range urls {
|
||||
ref, err := s.downloadReferenceImage(ctx, rawURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refs = append(refs, ref)
|
||||
}
|
||||
return refs, nil
|
||||
}
|
||||
|
||||
func extractStringValues(value any) []string {
|
||||
switch typed := value.(type) {
|
||||
case []string:
|
||||
out := make([]string, 0, len(typed))
|
||||
for _, item := range typed {
|
||||
if text := strings.TrimSpace(item); text != "" {
|
||||
out = append(out, text)
|
||||
}
|
||||
}
|
||||
return out
|
||||
case []any:
|
||||
out := make([]string, 0, len(typed))
|
||||
for _, item := range typed {
|
||||
if text := strings.TrimSpace(stringValue(item)); text != "" {
|
||||
out = append(out, text)
|
||||
}
|
||||
}
|
||||
return out
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) waitForVideoTask(ctx context.Context, token, taskID string) (*videoTaskResult, error) {
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
Reference in New Issue
Block a user