feat: complete remaining Go backend modules

This commit is contained in:
zn-admin committed 2026-08-14 00:02:43 +08:00
1 parent cea2751dc5
commit aef5a97165
145 files changed
+18376 -199

No files matched your search

+177
View File
@@ -0,0 +1,177 @@
package billing
import (
"context"
"fmt"
"math"
"strings"
)
const (
DefaultBillingMarkupMultiplier = 1.2
BillingCatalogObservedAt = "2026-08-11"
)
// PriceRuleSeeder is an optional store capability. Existing Store
// implementations remain source-compatible and seed-capable stores can make
// the official base catalog durable before quotes are read.
type PriceRuleSeeder interface {
SeedBillingPriceRules(context.Context, []PriceRule) error
}
func DefaultBillingPriceRules() []PriceRule {
rules := []PriceRule{
{ID: "base-volcengine-jimeng-seedream46", Provider: "volcengine-visual", Capability: "image.generate", ReqKey: "jimeng_seedream46_cvtob", Unit: UnitImage, StandardUnitPriceFen: 20, Note: "即梦4.6公开资源包基准。", Source: catalogSource("https://www.volcengine.com/activity/jimeng")},
{ID: "base-evolink-gpt-image-2", Provider: "evolink", Capability: "image.generate", ReqKey: "gpt-image-2", Unit: UnitImage, StandardUnitPriceFen: 34, Note: "medium / 1K / 1:1 / 无参考图基准。", Source: catalogSource("https://evolink.ai/zh/gpt-image-2"), Dimensions: evolinkDimensions()},
{ID: "base-bailian-wan27-image-pro", Provider: "bailian", Capability: "image.generate", ReqKey: "wan2.7-image-pro", Unit: UnitImage, StandardUnitPriceFen: 50, Source: catalogSource("https://help.aliyun.com/zh/model-studio/wan2-7-image-pro")},
{ID: "base-bailian-wan27-i2v-720p", Provider: "bailian", Capability: "video.generate", ReqKey: "wan2.7-i2v-2026-04-25", VariantKey: "resolution=720p", Unit: UnitVideoSecond, StandardUnitPriceFen: 60, Source: catalogSource("https://help.aliyun.com/zh/model-studio/wan2-7-i2v")},
{ID: "base-bailian-wan27-i2v-1080p", Provider: "bailian", Capability: "video.generate", ReqKey: "wan2.7-i2v-2026-04-25", VariantKey: "resolution=1080p", Unit: UnitVideoSecond, StandardUnitPriceFen: 100, Source: catalogSource("https://help.aliyun.com/zh/model-studio/wan2-7-i2v")},
{ID: "base-seedance-2-0-480p", Provider: "seedance", Capability: "video.generate", ReqKey: "doubao-seedance-2-0-260128", VariantKey: "resolution=480p", Unit: UnitVideoSecond, StandardUnitPriceFen: 46, Source: catalogSource("https://www.volcengine.com/docs/82379/1544106?lang=zh")},
{ID: "base-seedance-2-0-720p", Provider: "seedance", Capability: "video.generate", ReqKey: "doubao-seedance-2-0-260128", VariantKey: "resolution=720p", Unit: UnitVideoSecond, StandardUnitPriceFen: 99, Source: catalogSource("https://www.volcengine.com/docs/82379/1544106?lang=zh")},
{ID: "base-seedance-2-0-1080p", Provider: "seedance", Capability: "video.generate", ReqKey: "doubao-seedance-2-0-260128", VariantKey: "resolution=1080p", Unit: UnitVideoSecond, StandardUnitPriceFen: 248, Source: catalogSource("https://www.volcengine.com/docs/82379/1544106?lang=zh")},
{ID: "base-seedance-2-0-4k", Provider: "seedance", Capability: "video.generate", ReqKey: "doubao-seedance-2-0-260128", VariantKey: "resolution=4k", Unit: UnitVideoSecond, StandardUnitPriceFen: 505, Source: catalogSource("https://www.volcengine.com/docs/82379/1544106?lang=zh")},
}
for i := range rules {
rules[i].Enabled = true
rules[i].MarkupMultiplier = DefaultBillingMarkupMultiplier
}
return rules
}
func catalogSource(url string) map[string]any {
return map[string]any{"url": url, "observedAt": BillingCatalogObservedAt}
}
func evolinkDimensions() []ParameterDimension {
tiers := func(values ...any) []ParameterTier {
out := make([]ParameterTier, 0, len(values))
for index := 0; index < len(values); index += 2 {
out = append(out, ParameterTier{Value: values[index], StandardFactor: values[index+1].(float64), MarkupMultiplier: DefaultBillingMarkupMultiplier, Enabled: true})
}
return out
}
return []ParameterDimension{
{Key: "quality", BaselineValue: "medium", DefaultValue: "medium", Tiers: tiers("low", .11, "medium", 1.0, "high", 4.0)},
{Key: "resolution", BaselineValue: "1K", DefaultValue: "1K", Tiers: tiers("1K", 1.0, "2K", 4.0, "4K", 8.0)},
{Key: "aspectRatio", BaselineValue: "1:1", DefaultValue: "1:1", Tiers: tiers("1:1", 1.0, "4:3", 1.0, "16:9", 1.0, "9:16", 1.0)},
{Key: "referenceImageCount", BaselineValue: 0, DefaultValue: 0, Tiers: []ParameterTier{
{Value: 0, StandardFactor: 1, MarkupMultiplier: DefaultBillingMarkupMultiplier, Enabled: true},
{Value: "1–4", Match: map[string]any{"min": 1, "max": 4}, StandardFactor: 1, MarkupMultiplier: DefaultBillingMarkupMultiplier, Enabled: true},
{Value: "5–8", Match: map[string]any{"min": 5, "max": 8}, StandardFactor: 1, MarkupMultiplier: DefaultBillingMarkupMultiplier, Enabled: true},
{Value: "9–16", Match: map[string]any{"min": 9, "max": 16}, StandardFactor: 1, MarkupMultiplier: DefaultBillingMarkupMultiplier, Enabled: true},
}},
}
}
// NormalizeBillingParameters derives quote inputs from the persisted generation
// payload. Explicit parameters are retained only as a fallback for values the
// payload does not carry.
func NormalizeBillingParameters(payload map[string]any, fallback Parameters) Parameters {
out := Parameters{}
for key, value := range fallback {
out[key] = value
}
settings := record(payload["settings"])
providerPayload := record(payload["providerPayload"])
providerParameters := record(providerPayload["parameters"])
input := record(payload["input"])
inputSettings := record(input["settings"])
setText(out, "model", first(providerPayload["model"], input["model"], settings["model"]))
setText(out, "resolution", first(settings["resolution"], providerParameters["resolution"], providerPayload["resolution"], inputSettings["resolution"], input["resolution"]))
setText(out, "quality", first(input["quality"], settings["quality"], providerPayload["quality"], providerParameters["quality"]))
setNumber(out, "duration", first(settings["duration"], providerParameters["duration"], providerPayload["duration"], inputSettings["duration"], input["duration"]), false)
setNumber(out, "imageCount", first(providerPayload["n"], providerParameters["n"], input["n"], input["imageCount"]), true)
setNumber(out, "scale", first(input["scale"], settings["scale"]), false)
width, widthOK := finiteNumber(first(providerPayload["width"], input["width"]))
height, heightOK := finiteNumber(first(providerPayload["height"], input["height"]))
if size := normalizedText(first(providerPayload["size"], providerParameters["size"], inputSettings["size"], input["size"])); size != "" {
out["size"] = strings.ReplaceAll(size, "×", "*")
} else if widthOK && heightOK {
out["size"] = fmt.Sprintf("%g*%g", width, height)
}
if ratio := normalizedText(first(settings["ratio"], settings["aspectRatio"], providerPayload["ratio"], providerParameters["ratio"], inputSettings["ratio"], input["ratio"], input["aspectRatio"])); ratio != "" {
out["aspectRatio"] = ratio
} else if widthOK && heightOK && height > 0 {
out["aspectRatio"] = aspectRatio(width, height)
}
if count := referenceCount(payload, input, providerPayload); count > 0 {
out["referenceImageCount"] = float64(count)
}
if value, ok := boolean(first(settings["generate_audio"], settings["generateAudio"], providerParameters["generate_audio"], providerParameters["generateAudio"], input["generate_audio"], input["generateAudio"])); ok {
out["generateAudio"] = value
}
return out
}
func record(value any) map[string]any { result, _ := value.(map[string]any); return result }
func first(values ...any) any {
for _, value := range values {
if value != nil {
return value
}
}
return nil
}
func normalizedText(value any) string {
if value == nil {
return ""
}
return strings.ToLower(strings.TrimSpace(fmt.Sprint(value)))
}
func setText(out Parameters, key string, value any) {
if value != nil {
if text := normalizedText(value); text != "" {
out[key] = text
}
}
}
func setNumber(out Parameters, key string, value any, ceil bool) {
if number, ok := finiteNumber(value); ok && (!ceil || number > 0) {
if ceil {
number = math.Ceil(number)
}
out[key] = number
}
}
func finiteNumber(value any) (float64, bool) { return number(value) }
func boolean(value any) (bool, bool) {
switch v := value.(type) {
case bool:
return v, true
case string:
if strings.EqualFold(strings.TrimSpace(v), "true") {
return true, true
}
if strings.EqualFold(strings.TrimSpace(v), "false") {
return false, true
}
}
return false, false
}
func aspectRatio(width, height float64) string {
ratio := width / height
for _, item := range []struct {
value float64
label string
}{{1, "1:1"}, {4.0 / 3, "4:3"}, {16.0 / 9, "16:9"}, {3.0 / 4, "3:4"}, {9.0 / 16, "9:16"}, {21.0 / 9, "21:9"}} {
if math.Abs(ratio-item.value) < .02 {
return item.label
}
}
return fmt.Sprintf("%g:%g", width, height)
}
func referenceCount(payload, input, providerPayload map[string]any) int {
count := 0
for _, value := range []any{payload["imageUrls"], payload["inputUrls"], input["imageUrls"], providerPayload["image_urls"], providerPayload["imageUrls"]} {
switch items := value.(type) {
case []any:
count += len(items)
case []string:
count += len(items)
}
}
return count
}