image_billing_size.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. package service
  2. import (
  3. "sort"
  4. "strconv"
  5. "strings"
  6. )
  7. const (
  8. ImageBillingSize1K = "1K"
  9. ImageBillingSize2K = "2K"
  10. ImageBillingSize4K = "4K"
  11. ImageSizeSourceOutput = "output"
  12. ImageSizeSourceInput = "input"
  13. ImageSizeSourceDefault = "default"
  14. ImageSizeSourceLegacy = "legacy"
  15. )
  16. type ImageBillingSizeResolution struct {
  17. BillingSize string
  18. InputSize string
  19. OutputSize string
  20. Source string
  21. Breakdown map[string]int
  22. }
  23. func ClassifyImageBillingTier(size string) (string, bool) {
  24. trimmed := strings.TrimSpace(size)
  25. normalized := strings.ToLower(trimmed)
  26. switch normalized {
  27. case "", "auto":
  28. return "", false
  29. case "1k":
  30. return ImageBillingSize1K, true
  31. case "2k":
  32. return ImageBillingSize2K, true
  33. case "4k":
  34. return ImageBillingSize4K, true
  35. case "2048x2048", "2048x1152":
  36. return ImageBillingSize2K, true
  37. case "3840x2160", "2160x3840":
  38. return ImageBillingSize4K, true
  39. }
  40. width, height, ok := parseImageBillingDimensions(trimmed)
  41. if !ok {
  42. return "", false
  43. }
  44. maxEdge := width
  45. if height > maxEdge {
  46. maxEdge = height
  47. }
  48. switch {
  49. case maxEdge <= 1024:
  50. return ImageBillingSize1K, true
  51. case maxEdge <= 2048:
  52. return ImageBillingSize2K, true
  53. default:
  54. return ImageBillingSize4K, true
  55. }
  56. }
  57. func NormalizeImageBillingTierOrDefault(size string) string {
  58. if tier, ok := ClassifyImageBillingTier(size); ok {
  59. return tier
  60. }
  61. return ImageBillingSize2K
  62. }
  63. func ResolveImageBillingSize(inputSize string, outputSizes []string) ImageBillingSizeResolution {
  64. inputSize = strings.TrimSpace(inputSize)
  65. outputSizes = compactTrimmedStrings(outputSizes)
  66. breakdown := map[string]int{}
  67. outputSize := firstDisplayImageOutputSize(outputSizes)
  68. outputTier := ""
  69. for _, output := range outputSizes {
  70. tier, ok := ClassifyImageBillingTier(output)
  71. if !ok {
  72. continue
  73. }
  74. breakdown[tier]++
  75. if imageTierRank(tier) > imageTierRank(outputTier) {
  76. outputTier = tier
  77. }
  78. }
  79. if outputTier != "" {
  80. return ImageBillingSizeResolution{
  81. BillingSize: outputTier,
  82. InputSize: inputSize,
  83. OutputSize: outputSize,
  84. Source: ImageSizeSourceOutput,
  85. Breakdown: normalizeImageSizeBreakdown(breakdown),
  86. }
  87. }
  88. if tier, ok := ClassifyImageBillingTier(inputSize); ok {
  89. return ImageBillingSizeResolution{
  90. BillingSize: tier,
  91. InputSize: inputSize,
  92. OutputSize: outputSize,
  93. Source: ImageSizeSourceInput,
  94. }
  95. }
  96. return ImageBillingSizeResolution{
  97. BillingSize: ImageBillingSize2K,
  98. InputSize: inputSize,
  99. OutputSize: outputSize,
  100. Source: ImageSizeSourceDefault,
  101. }
  102. }
  103. func ApplyOpenAIImageBillingResolution(result *OpenAIForwardResult) {
  104. if result == nil || result.ImageCount <= 0 {
  105. return
  106. }
  107. inputSize := strings.TrimSpace(result.ImageInputSize)
  108. if inputSize == "" && strings.TrimSpace(result.ImageSize) != ImageBillingSize2K {
  109. inputSize = strings.TrimSpace(result.ImageSize)
  110. }
  111. outputSizes := result.ImageOutputSizes
  112. if len(outputSizes) == 0 && strings.TrimSpace(result.ImageOutputSize) != "" {
  113. outputSizes = []string{result.ImageOutputSize}
  114. }
  115. resolved := ResolveImageBillingSize(inputSize, outputSizes)
  116. applyImageBillingResolution(
  117. &result.ImageSize,
  118. &result.ImageInputSize,
  119. &result.ImageOutputSize,
  120. &result.ImageSizeSource,
  121. &result.ImageSizeBreakdown,
  122. resolved,
  123. )
  124. }
  125. func ApplyForwardImageBillingResolution(result *ForwardResult) {
  126. if result == nil || result.ImageCount <= 0 {
  127. return
  128. }
  129. inputSize := strings.TrimSpace(result.ImageInputSize)
  130. if inputSize == "" && strings.TrimSpace(result.ImageSize) != ImageBillingSize2K {
  131. inputSize = strings.TrimSpace(result.ImageSize)
  132. }
  133. outputSizes := result.ImageOutputSizes
  134. if len(outputSizes) == 0 && strings.TrimSpace(result.ImageOutputSize) != "" {
  135. outputSizes = []string{result.ImageOutputSize}
  136. }
  137. resolved := ResolveImageBillingSize(inputSize, outputSizes)
  138. applyImageBillingResolution(
  139. &result.ImageSize,
  140. &result.ImageInputSize,
  141. &result.ImageOutputSize,
  142. &result.ImageSizeSource,
  143. &result.ImageSizeBreakdown,
  144. resolved,
  145. )
  146. }
  147. func applyImageBillingResolution(
  148. billingSize *string,
  149. inputSize *string,
  150. outputSize *string,
  151. source *string,
  152. breakdown *map[string]int,
  153. resolved ImageBillingSizeResolution,
  154. ) {
  155. *billingSize = resolved.BillingSize
  156. *inputSize = resolved.InputSize
  157. *outputSize = resolved.OutputSize
  158. *source = resolved.Source
  159. *breakdown = resolved.Breakdown
  160. }
  161. func parseImageBillingDimensions(size string) (int, int, bool) {
  162. parts := strings.Split(strings.ToLower(strings.TrimSpace(size)), "x")
  163. if len(parts) != 2 {
  164. return 0, 0, false
  165. }
  166. width, err := strconv.Atoi(strings.TrimSpace(parts[0]))
  167. if err != nil {
  168. return 0, 0, false
  169. }
  170. height, err := strconv.Atoi(strings.TrimSpace(parts[1]))
  171. if err != nil {
  172. return 0, 0, false
  173. }
  174. if width <= 0 || height <= 0 {
  175. return 0, 0, false
  176. }
  177. return width, height, true
  178. }
  179. func compactTrimmedStrings(values []string) []string {
  180. if len(values) == 0 {
  181. return nil
  182. }
  183. out := make([]string, 0, len(values))
  184. for _, value := range values {
  185. trimmed := strings.TrimSpace(value)
  186. if trimmed != "" {
  187. out = append(out, trimmed)
  188. }
  189. }
  190. return out
  191. }
  192. func firstDisplayImageOutputSize(outputSizes []string) string {
  193. for _, output := range outputSizes {
  194. if trimmed := strings.TrimSpace(output); trimmed != "" {
  195. return trimmed
  196. }
  197. }
  198. return ""
  199. }
  200. func imageTierRank(tier string) int {
  201. switch strings.ToUpper(strings.TrimSpace(tier)) {
  202. case ImageBillingSize1K:
  203. return 1
  204. case ImageBillingSize2K:
  205. return 2
  206. case ImageBillingSize4K:
  207. return 3
  208. default:
  209. return 0
  210. }
  211. }
  212. func normalizeImageSizeBreakdown(in map[string]int) map[string]int {
  213. if len(in) == 0 {
  214. return nil
  215. }
  216. out := make(map[string]int, len(in))
  217. for _, tier := range []string{ImageBillingSize1K, ImageBillingSize2K, ImageBillingSize4K} {
  218. if count := in[tier]; count > 0 {
  219. out[tier] = count
  220. }
  221. }
  222. if len(out) == 0 {
  223. return nil
  224. }
  225. return out
  226. }
  227. func SortedImageBillingBreakdownKeys(breakdown map[string]int) []string {
  228. keys := make([]string, 0, len(breakdown))
  229. for key := range breakdown {
  230. keys = append(keys, key)
  231. }
  232. sort.Slice(keys, func(i, j int) bool {
  233. left, right := imageTierRank(keys[i]), imageTierRank(keys[j])
  234. if left == right {
  235. return keys[i] < keys[j]
  236. }
  237. return left < right
  238. })
  239. return keys
  240. }