refactor(task): extract billing and polling logic from controller to service layer
Restructure the task relay system for better separation of concerns:
- Extract task billing into service/task_billing.go with unified settlement flow
- Move task polling loop from controller to service/task_polling.go (supports Suno + video platforms)
- Split RelayTask into fetch/submit paths with dedicated retry logic (taskSubmitWithRetry)
- Add TaskDto, TaskResponse generics, and FetchReq to dto/task.go
- Add taskcommon/helpers.go for shared task adaptor utilities
- Remove controller/task_video.go (logic consolidated into service layer)
- Update all task adaptors (ali, doubao, gemini, hailuo, jimeng, kling, sora, suno, vertex, vidu)
- Simplify frontend task logs to use new TaskDto response format
2026-02-10 20:40:33 +08:00
|
|
|
package taskcommon
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
2026-02-10 21:15:09 +08:00
|
|
|
"github.com/QuantumNous/new-api/model"
|
|
|
|
|
relaycommon "github.com/QuantumNous/new-api/relay/common"
|
refactor(task): extract billing and polling logic from controller to service layer
Restructure the task relay system for better separation of concerns:
- Extract task billing into service/task_billing.go with unified settlement flow
- Move task polling loop from controller to service/task_polling.go (supports Suno + video platforms)
- Split RelayTask into fetch/submit paths with dedicated retry logic (taskSubmitWithRetry)
- Add TaskDto, TaskResponse generics, and FetchReq to dto/task.go
- Add taskcommon/helpers.go for shared task adaptor utilities
- Remove controller/task_video.go (logic consolidated into service layer)
- Update all task adaptors (ali, doubao, gemini, hailuo, jimeng, kling, sora, suno, vertex, vidu)
- Simplify frontend task logs to use new TaskDto response format
2026-02-10 20:40:33 +08:00
|
|
|
"github.com/QuantumNous/new-api/setting/system_setting"
|
2026-02-10 21:15:09 +08:00
|
|
|
"github.com/gin-gonic/gin"
|
refactor(task): extract billing and polling logic from controller to service layer
Restructure the task relay system for better separation of concerns:
- Extract task billing into service/task_billing.go with unified settlement flow
- Move task polling loop from controller to service/task_polling.go (supports Suno + video platforms)
- Split RelayTask into fetch/submit paths with dedicated retry logic (taskSubmitWithRetry)
- Add TaskDto, TaskResponse generics, and FetchReq to dto/task.go
- Add taskcommon/helpers.go for shared task adaptor utilities
- Remove controller/task_video.go (logic consolidated into service layer)
- Update all task adaptors (ali, doubao, gemini, hailuo, jimeng, kling, sora, suno, vertex, vidu)
- Simplify frontend task logs to use new TaskDto response format
2026-02-10 20:40:33 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// UnmarshalMetadata converts a map[string]any metadata to a typed struct via JSON round-trip.
|
|
|
|
|
// This replaces the repeated pattern: json.Marshal(metadata) → json.Unmarshal(bytes, &target).
|
|
|
|
|
func UnmarshalMetadata(metadata map[string]any, target any) error {
|
|
|
|
|
if metadata == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2026-03-27 15:31:09 +08:00
|
|
|
// Prevent metadata from overriding model fields to avoid billing bypass.
|
|
|
|
|
delete(metadata, "model")
|
refactor(task): extract billing and polling logic from controller to service layer
Restructure the task relay system for better separation of concerns:
- Extract task billing into service/task_billing.go with unified settlement flow
- Move task polling loop from controller to service/task_polling.go (supports Suno + video platforms)
- Split RelayTask into fetch/submit paths with dedicated retry logic (taskSubmitWithRetry)
- Add TaskDto, TaskResponse generics, and FetchReq to dto/task.go
- Add taskcommon/helpers.go for shared task adaptor utilities
- Remove controller/task_video.go (logic consolidated into service layer)
- Update all task adaptors (ali, doubao, gemini, hailuo, jimeng, kling, sora, suno, vertex, vidu)
- Simplify frontend task logs to use new TaskDto response format
2026-02-10 20:40:33 +08:00
|
|
|
metaBytes, err := common.Marshal(metadata)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("marshal metadata failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if err := common.Unmarshal(metaBytes, target); err != nil {
|
|
|
|
|
return fmt.Errorf("unmarshal metadata failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DefaultString returns val if non-empty, otherwise fallback.
|
|
|
|
|
func DefaultString(val, fallback string) string {
|
|
|
|
|
if val == "" {
|
|
|
|
|
return fallback
|
|
|
|
|
}
|
|
|
|
|
return val
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DefaultInt returns val if non-zero, otherwise fallback.
|
|
|
|
|
func DefaultInt(val, fallback int) int {
|
|
|
|
|
if val == 0 {
|
|
|
|
|
return fallback
|
|
|
|
|
}
|
|
|
|
|
return val
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncodeLocalTaskID encodes an upstream operation name to a URL-safe base64 string.
|
|
|
|
|
// Used by Gemini/Vertex to store upstream names as task IDs.
|
|
|
|
|
func EncodeLocalTaskID(name string) string {
|
|
|
|
|
return base64.RawURLEncoding.EncodeToString([]byte(name))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DecodeLocalTaskID decodes a base64-encoded upstream operation name.
|
|
|
|
|
func DecodeLocalTaskID(id string) (string, error) {
|
|
|
|
|
b, err := base64.RawURLEncoding.DecodeString(id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return string(b), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// BuildProxyURL constructs the video proxy URL using the public task ID.
|
|
|
|
|
// e.g., "https://your-server.com/v1/videos/task_xxxx/content"
|
|
|
|
|
func BuildProxyURL(taskID string) string {
|
|
|
|
|
return fmt.Sprintf("%s/v1/videos/%s/content", system_setting.ServerAddress, taskID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Status-to-progress mapping constants for polling updates.
|
|
|
|
|
const (
|
|
|
|
|
ProgressSubmitted = "10%"
|
|
|
|
|
ProgressQueued = "20%"
|
|
|
|
|
ProgressInProgress = "30%"
|
|
|
|
|
ProgressComplete = "100%"
|
|
|
|
|
)
|
2026-02-10 21:15:09 +08:00
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// BaseBilling — embeddable no-op implementations for TaskAdaptor billing methods.
|
|
|
|
|
// Adaptors that do not need custom billing can embed this struct directly.
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
type BaseBilling struct{}
|
|
|
|
|
|
|
|
|
|
// EstimateBilling returns nil (no extra ratios; use base model price).
|
|
|
|
|
func (BaseBilling) EstimateBilling(_ *gin.Context, _ *relaycommon.RelayInfo) map[string]float64 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AdjustBillingOnSubmit returns nil (no submit-time adjustment).
|
|
|
|
|
func (BaseBilling) AdjustBillingOnSubmit(_ *relaycommon.RelayInfo, _ []byte) map[string]float64 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AdjustBillingOnComplete returns 0 (keep pre-charged amount).
|
|
|
|
|
func (BaseBilling) AdjustBillingOnComplete(_ *model.Task, _ *relaycommon.TaskInfo) int {
|
|
|
|
|
return 0
|
|
|
|
|
}
|