迁移音频生成与语音合成到 gateway 并补充 simulation 测试
This commit is contained in:
@@ -112,6 +112,24 @@ func (s *Service) billings(ctx context.Context, user *auth.User, kind string, bo
|
||||
"durationUnitCount": durationUnits,
|
||||
})}
|
||||
}
|
||||
if kind == "song.generations" || kind == "music.generations" {
|
||||
resource = "music"
|
||||
unit = "song"
|
||||
baseKey = "musicBase"
|
||||
amount := float64(count) * resourcePrice(config, resource, baseKey, "basePrice") * discount
|
||||
return []any{billingLine(candidate, resource, unit, count, roundPrice(amount), discount, simulated)}
|
||||
}
|
||||
if kind == "speech.generations" {
|
||||
resource = "audio"
|
||||
unit = "character"
|
||||
baseKey = "audioBase"
|
||||
quantity := len([]rune(stringFromMap(body, "text")))
|
||||
if quantity <= 0 {
|
||||
quantity = 1
|
||||
}
|
||||
amount := float64(quantity) * resourcePrice(config, resource, baseKey, "basePrice") * discount
|
||||
return []any{billingLine(candidate, resource, unit, quantity, roundPrice(amount), discount, simulated)}
|
||||
}
|
||||
amount := float64(count) * resourcePrice(config, resource, baseKey, "basePrice") * resourceWeight(config, resource, "qualityWeights", stringFromMap(body, "quality")) * resourceWeight(config, resource, "sizeWeights", stringFromMap(body, "size")) * resourceWeight(config, resource, "resolutionWeights", firstNonEmptyString(stringFromMap(body, "resolution"), stringFromMap(body, "size"))) * discount
|
||||
return []any{billingLine(candidate, resource, unit, count, roundPrice(amount), discount, simulated)}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,66 @@ func TestVideoBillingEstimateUsesFiveSecondUnitsAndDynamicWeights(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMusicBillingUsesSongResourceAndOutputCount(t *testing.T) {
|
||||
service := &Service{}
|
||||
candidate := store.RuntimeModelCandidate{
|
||||
ModelName: "suno-model",
|
||||
BaseBillingConfig: map[string]any{
|
||||
"musicBase": 6,
|
||||
"music": map[string]any{"basePrice": 9},
|
||||
},
|
||||
}
|
||||
|
||||
items := service.billings(context.Background(), nil, "song.generations", map[string]any{
|
||||
"prompt": "city lights",
|
||||
"count": 3,
|
||||
}, candidate, clients.Response{}, true)
|
||||
|
||||
line := firstBillingLine(t, items)
|
||||
if got, want := line["resourceType"], "music"; got != want {
|
||||
t.Fatalf("music resource type = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := line["unit"], "song"; got != want {
|
||||
t.Fatalf("music billing unit = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := line["quantity"], 3; got != want {
|
||||
t.Fatalf("music quantity = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := floatFromAny(line["amount"]), 18.0; got != want {
|
||||
t.Fatalf("music amount = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpeechBillingUsesAudioCharacters(t *testing.T) {
|
||||
service := &Service{}
|
||||
candidate := store.RuntimeModelCandidate{
|
||||
ModelName: "speech-model",
|
||||
BaseBillingConfig: map[string]any{
|
||||
"audioBase": 0.5,
|
||||
"audio": map[string]any{"basePrice": 0.8},
|
||||
},
|
||||
}
|
||||
|
||||
items := service.billings(context.Background(), nil, "speech.generations", map[string]any{
|
||||
"text": "你好abc",
|
||||
"voice_id": "female-shaonv",
|
||||
}, candidate, clients.Response{}, true)
|
||||
|
||||
line := firstBillingLine(t, items)
|
||||
if got, want := line["resourceType"], "audio"; got != want {
|
||||
t.Fatalf("speech resource type = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := line["unit"], "character"; got != want {
|
||||
t.Fatalf("speech billing unit = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := line["quantity"], 5; got != want {
|
||||
t.Fatalf("speech character quantity = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := floatFromAny(line["amount"]), 2.5; got != want {
|
||||
t.Fatalf("speech amount = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVideoBillingPrefersGeneratedDuration(t *testing.T) {
|
||||
service := &Service{}
|
||||
candidate := store.RuntimeModelCandidate{
|
||||
|
||||
@@ -64,6 +64,7 @@ func New(cfg config.Config, db *store.Store, logger *slog.Logger) *Service {
|
||||
"midjourney": clients.MidjourneyClient{HTTPClient: httpClients.none},
|
||||
"minimax": clients.MinimaxClient{HTTPClient: httpClients.none},
|
||||
"newapi": clients.NewAPIClient{HTTPClient: httpClients.none},
|
||||
"suno": clients.SunoClient{HTTPClient: httpClients.none},
|
||||
"tencent-hunyuan-image": clients.HunyuanImageClient{HTTPClient: httpClients.none},
|
||||
"tencent-hunyuan-video": clients.HunyuanVideoClient{HTTPClient: httpClients.none},
|
||||
"vidu": clients.ViduClient{HTTPClient: httpClients.none},
|
||||
@@ -957,6 +958,10 @@ func modelTypeFromKind(kind string, body map[string]any) string {
|
||||
return "image_to_video"
|
||||
}
|
||||
return "video_generate"
|
||||
case "song.generations", "music.generations":
|
||||
return "audio_generate"
|
||||
case "speech.generations":
|
||||
return "text_to_speech"
|
||||
default:
|
||||
return "task"
|
||||
}
|
||||
@@ -979,6 +984,10 @@ func canonicalModelType(value string) string {
|
||||
return "text_embedding"
|
||||
case "rerank", "reranks":
|
||||
return "text_rerank"
|
||||
case "audio", "music", "music_generate", "song", "songs":
|
||||
return "audio_generate"
|
||||
case "speech", "tts":
|
||||
return "text_to_speech"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
@@ -986,7 +995,7 @@ func canonicalModelType(value string) string {
|
||||
|
||||
func isKnownModelType(value string) bool {
|
||||
switch value {
|
||||
case "text_generate", "text_embedding", "text_rerank", "image_generate", "image_edit", "video_generate", "image_to_video", "text_to_video", "video_edit", "video_reference", "video_first_last_frame", "omni_video", "omni":
|
||||
case "text_generate", "text_embedding", "text_rerank", "image_generate", "image_edit", "video_generate", "image_to_video", "text_to_video", "video_edit", "video_reference", "video_first_last_frame", "omni_video", "omni", "audio_generate", "text_to_speech":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -1171,6 +1180,17 @@ func validateRequest(kind string, body map[string]any) error {
|
||||
if strings.TrimSpace(stringFromMap(body, "prompt")) == "" {
|
||||
return errors.New("prompt is required")
|
||||
}
|
||||
case "song.generations", "music.generations":
|
||||
if strings.TrimSpace(stringFromMap(body, "prompt")) == "" {
|
||||
return errors.New("prompt is required")
|
||||
}
|
||||
case "speech.generations":
|
||||
if strings.TrimSpace(stringFromMap(body, "text")) == "" && strings.TrimSpace(stringFromMap(body, "text_file_id")) == "" {
|
||||
return errors.New("text or text_file_id is required")
|
||||
}
|
||||
if strings.TrimSpace(stringFromMap(body, "voice_id")) == "" {
|
||||
return errors.New("voice_id is required")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -943,6 +943,9 @@ func mediaKindForAsset(taskKind string, item map[string]any, sourceKey string, c
|
||||
if strings.Contains(kind, "video") {
|
||||
return "video"
|
||||
}
|
||||
if strings.Contains(kind, "audio") || strings.Contains(kind, "song") || strings.Contains(kind, "music") || strings.Contains(kind, "speech") {
|
||||
return "audio"
|
||||
}
|
||||
if strings.Contains(kind, "image") {
|
||||
return "image"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user