Skip to content

fix 2522 - #3035

Closed
seefs001 wants to merge 4 commits into
QuantumNous:mainfrom
seefs001:pr-2522
Closed

fix 2522#3035
seefs001 wants to merge 4 commits into
QuantumNous:mainfrom
seefs001:pr-2522

Conversation

@seefs001

@seefs001 seefs001 commented Feb 27, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Added Image Output Ratio configuration setting for customizing image generation parameters.
    • Enhanced token tracking with separate categorization for text, audio, and image modalities.
  • Improvements

    • Refined pricing calculations to properly distinguish and bill image-related output tokens separately.
    • Updated settings interface to support the new image output ratio configuration option.

creamlike1024 and others added 4 commits December 25, 2025 13:34
# Conflicts:
#	dto/gemini.go
#	relay/channel/gemini/relay-gemini.go
#	setting/ratio_setting/exposed_cache.go
#	setting/ratio_setting/model_ratio.go
#	web/src/hooks/usage-logs/useUsageLogsData.jsx
@coderabbitai

coderabbitai Bot commented Feb 27, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

The PR adds image output ratio support for pricing calculations, introducing new configuration handling, token tracking structures, and pricing logic across backend controllers, relay handlers, settings, and frontend UI components.

Changes

Cohort / File(s) Summary
Configuration & Options
controller/option.go, model/option.go
Added ImageOutputRatio option key handling with JSON string updates and cache invalidation; wired into updateOptionMap for ratio settings updates.
Data Transfer Objects
dto/gemini.go, dto/openai_response.go
Consolidated Gemini token details into GeminiTokensDetails struct (Modality, TokenCount); added CandidatesTokensDetails field to metadata; added ImageTokens field to OpenAI OutputTokenDetails.
Type Definitions
types/price_data.go
Added ImageOutputRatio float64 field to PriceData struct for pricing calculations.
Ratio Settings Management
setting/ratio_setting/model_ratio.go, setting/ratio_setting/exposed_cache.go
Introduced thread-safe image output ratio map with mutex; added thread-safe getters (GetImageOutputRatio, GetImageOutputRatioCopy); exposed image output ratio in cached data alongside existing ratios.
Relay & Token Handling
relay/channel/gemini/relay-gemini.go
Added private helpers for mapping modality-based token details to usage; refactored streaming token aggregation with image counting logic; conditional completion token estimation based on image presence.
OpenAI Relay Handler
relay/channel/openai/relay-openai.go
Conditionally increment CompletionTokenDetails.ImageTokens by output tokens when image generation/edit mode is active and origin model has image output ratio.
Pricing & Billing
relay/compatible_handler.go, relay/helper/price.go
Added image output ratio to postConsumeQuota; split completion token calculations into base and image-related components; extended PriceData with image output ratio via ModelPriceHelper.
Frontend UI & Rendering
web/src/pages/Setting/Ratio/ModelRatioSettings.jsx, web/src/components/settings/RatioSetting.jsx
Added ImageOutputRatio configuration field and JSON textarea UI matching existing ratio field patterns.
Frontend Pricing Logic
web/src/helpers/render.jsx, web/src/hooks/usage-logs/useUsageLogsData.jsx
Refactored renderModelPrice to accept imageInputTokens, completionImageTokens, and imageOutputRatio; implemented split output rendering for text vs. image pricing when applicable; updated hook call to pass new parameters.
Minor Formatting
model/user_oauth_binding.go, service/task_billing_test.go
Field tag spacing adjustments in UserOAuthBinding; multi-line reformatting of mock adapter implementations with no functional changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Calcium-Ion
  • creamlike1024

Poem

🐰 A ratio so fine for images bright,
Output prices split left and right,
Tokens by modality counted with care,
New settings in config, now everywhere!
Frontend and backend in sync we appear,
Image output ratios crystal clear! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'fix 2522' is too vague and does not describe what the pull request actually changes. Replace with a descriptive title that summarizes the main change, such as 'Add image output ratio settings and token detail tracking' or similar.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
relay/channel/gemini/relay-gemini.go (1)

1360-1362: Magic number 1400 for image token estimation.

The hardcoded value 1400 tokens per image should be documented or extracted to a named constant for clarity and maintainability.

📝 Suggested improvement
+// geminiImageTokenEstimate is the estimated token count per generated image
+// when the API doesn't return explicit token counts
+const geminiImageTokenEstimate = 1400
+
 func geminiStreamHandler(...) {
     ...
     if imageCount != 0 && usage.CompletionTokens == 0 {
-        usage.CompletionTokens = imageCount * 1400
+        usage.CompletionTokens = imageCount * geminiImageTokenEstimate
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/channel/gemini/relay-gemini.go` around lines 1360 - 1362, Replace the
magic number 1400 used to estimate tokens per image with a named constant and
document it; specifically, define a descriptive constant (e.g.,
EstimatedImageTokensPerImage or IMAGE_TOKEN_ESTIMATE) near the top of the
package and use it in the conditional that sets usage.CompletionTokens when
imageCount != 0 and usage.CompletionTokens == 0, and add a short comment
explaining the basis for the estimate or reference to where it came from.
types/price_data.go (1)

41-43: ToSetting() doesn't include the new ImageOutputRatio field.

The ToSetting() method is used for debug logging (e.g., in relay/helper/price.go:139) but doesn't include the newly added ImageOutputRatio field, which could make debugging image output pricing issues harder.

📝 Suggested fix
 func (p *PriceData) ToSetting() string {
-	return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, CacheCreation5mRatio: %f, CacheCreation1hRatio: %f, QuotaToPreConsume: %d, ImageRatio: %f, AudioRatio: %f, AudioCompletionRatio: %f", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.CacheCreation5mRatio, p.CacheCreation1hRatio, p.QuotaToPreConsume, p.ImageRatio, p.AudioRatio, p.AudioCompletionRatio)
+	return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, CacheCreation5mRatio: %f, CacheCreation1hRatio: %f, QuotaToPreConsume: %d, ImageRatio: %f, ImageOutputRatio: %f, AudioRatio: %f, AudioCompletionRatio: %f", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.CacheCreation5mRatio, p.CacheCreation1hRatio, p.QuotaToPreConsume, p.ImageRatio, p.ImageOutputRatio, p.AudioRatio, p.AudioCompletionRatio)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@types/price_data.go` around lines 41 - 43, The ToSetting method on PriceData
is missing the new ImageOutputRatio field; update PriceData.ToSetting() (the
function named ToSetting) to include ImageOutputRatio in the fmt.Sprintf format
string and add p.ImageOutputRatio to the argument list so the debug string
prints the image output pricing ratio alongside the other fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@controller/option.go`:
- Around line 154-162: The controller is mutating runtime ratio state by calling
ratio_setting.UpdateImageOutputRatioByJSONString(option.Value.(string)) before
persistence; instead, change this flow so the controller only validates the
incoming ratio JSON (e.g., parse/validate the payload or call a non-mutating
validator) and do not call the mutating UpdateImageOutputRatioByJSONString here;
let model.UpdateOption (and its updateOptionMap path) be the single place that
applies the runtime mutation after the DB update succeeds. Concretely: remove
the direct mutation call from the controller, replace it with a validation step
(or a new ratio_setting.ValidateImageOutputRatioJSON function) to ensure the
value is well-formed, and ensure model.UpdateOption/updateOptionMap performs the
actual ratio_setting.UpdateImageOutputRatioByJSONString call after successful
persistence.

In `@setting/ratio_setting/model_ratio.go`:
- Around line 647-656: UpdateImageOutputRatioByJSONString currently clears
imageOutputRatioMap before unmarshaling, which wipes config on invalid JSON;
instead, unmarshal into a local temp map (e.g., tmp := map[string]float64{}),
keep imageOutputRatioMap untouched while decoding, and only acquire the
imageOutputRatioMapMutex to replace imageOutputRatioMap and call
InvalidateExposedDataCache when unmarshaling succeeds (preserve the lock usage
around the swap). Reference: UpdateImageOutputRatioByJSONString,
imageOutputRatioMap, imageOutputRatioMapMutex, InvalidateExposedDataCache.

---

Nitpick comments:
In `@relay/channel/gemini/relay-gemini.go`:
- Around line 1360-1362: Replace the magic number 1400 used to estimate tokens
per image with a named constant and document it; specifically, define a
descriptive constant (e.g., EstimatedImageTokensPerImage or
IMAGE_TOKEN_ESTIMATE) near the top of the package and use it in the conditional
that sets usage.CompletionTokens when imageCount != 0 and usage.CompletionTokens
== 0, and add a short comment explaining the basis for the estimate or reference
to where it came from.

In `@types/price_data.go`:
- Around line 41-43: The ToSetting method on PriceData is missing the new
ImageOutputRatio field; update PriceData.ToSetting() (the function named
ToSetting) to include ImageOutputRatio in the fmt.Sprintf format string and add
p.ImageOutputRatio to the argument list so the debug string prints the image
output pricing ratio alongside the other fields.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 982dc5c and ee959d2.

📒 Files selected for processing (17)
  • controller/option.go
  • dto/gemini.go
  • dto/openai_response.go
  • model/option.go
  • model/user_oauth_binding.go
  • relay/channel/gemini/relay-gemini.go
  • relay/channel/openai/relay-openai.go
  • relay/compatible_handler.go
  • relay/helper/price.go
  • service/task_billing_test.go
  • setting/ratio_setting/exposed_cache.go
  • setting/ratio_setting/model_ratio.go
  • types/price_data.go
  • web/src/components/settings/RatioSetting.jsx
  • web/src/helpers/render.jsx
  • web/src/hooks/usage-logs/useUsageLogsData.jsx
  • web/src/pages/Setting/Ratio/ModelRatioSettings.jsx

Comment thread controller/option.go
Comment on lines +154 to +162
case "ImageOutputRatio":
err = ratio_setting.UpdateImageOutputRatioByJSONString(option.Value.(string))
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "图片输出倍率设置失败: " + err.Error(),
})
return
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t mutate runtime ratio state during controller validation.

Line 155 applies the update before model.UpdateOption persists it. If DB/update fails later, memory state can diverge from stored state.

🔧 Suggested direction
 case "ImageOutputRatio":
-	err = ratio_setting.UpdateImageOutputRatioByJSONString(option.Value.(string))
+	err = ratio_setting.CheckImageOutputRatioByJSONString(option.Value.(string))
 	if err != nil {
 		c.JSON(http.StatusOK, gin.H{
 			"success": false,
 			"message": "图片输出倍率设置失败: " + err.Error(),
 		})
 		return
 	}

Then keep the actual mutation only in model.UpdateOption -> updateOptionMap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/option.go` around lines 154 - 162, The controller is mutating
runtime ratio state by calling
ratio_setting.UpdateImageOutputRatioByJSONString(option.Value.(string)) before
persistence; instead, change this flow so the controller only validates the
incoming ratio JSON (e.g., parse/validate the payload or call a non-mutating
validator) and do not call the mutating UpdateImageOutputRatioByJSONString here;
let model.UpdateOption (and its updateOptionMap path) be the single place that
applies the runtime mutation after the DB update succeeds. Concretely: remove
the direct mutation call from the controller, replace it with a validation step
(or a new ratio_setting.ValidateImageOutputRatioJSON function) to ensure the
value is well-formed, and ensure model.UpdateOption/updateOptionMap performs the
actual ratio_setting.UpdateImageOutputRatioByJSONString call after successful
persistence.

Comment on lines +647 to +656
func UpdateImageOutputRatioByJSONString(jsonStr string) error {
imageOutputRatioMapMutex.Lock()
defer imageOutputRatioMapMutex.Unlock()
imageOutputRatioMap = make(map[string]float64)
err := common.Unmarshal([]byte(jsonStr), &imageOutputRatioMap)
if err == nil {
InvalidateExposedDataCache()
}
return err
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid clearing existing ratios on invalid JSON updates.

Line 650 resets imageOutputRatioMap before decode. If Line 651 fails, runtime config is silently wiped.

🔧 Proposed fix
 func UpdateImageOutputRatioByJSONString(jsonStr string) error {
-	imageOutputRatioMapMutex.Lock()
-	defer imageOutputRatioMapMutex.Unlock()
-	imageOutputRatioMap = make(map[string]float64)
-	err := common.Unmarshal([]byte(jsonStr), &imageOutputRatioMap)
-	if err == nil {
-		InvalidateExposedDataCache()
-	}
-	return err
+	next := make(map[string]float64)
+	if err := common.Unmarshal([]byte(jsonStr), &next); err != nil {
+		return err
+	}
+	imageOutputRatioMapMutex.Lock()
+	defer imageOutputRatioMapMutex.Unlock()
+	imageOutputRatioMap = next
+	InvalidateExposedDataCache()
+	return nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func UpdateImageOutputRatioByJSONString(jsonStr string) error {
imageOutputRatioMapMutex.Lock()
defer imageOutputRatioMapMutex.Unlock()
imageOutputRatioMap = make(map[string]float64)
err := common.Unmarshal([]byte(jsonStr), &imageOutputRatioMap)
if err == nil {
InvalidateExposedDataCache()
}
return err
}
func UpdateImageOutputRatioByJSONString(jsonStr string) error {
next := make(map[string]float64)
if err := common.Unmarshal([]byte(jsonStr), &next); err != nil {
return err
}
imageOutputRatioMapMutex.Lock()
defer imageOutputRatioMapMutex.Unlock()
imageOutputRatioMap = next
InvalidateExposedDataCache()
return nil
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@setting/ratio_setting/model_ratio.go` around lines 647 - 656,
UpdateImageOutputRatioByJSONString currently clears imageOutputRatioMap before
unmarshaling, which wipes config on invalid JSON; instead, unmarshal into a
local temp map (e.g., tmp := map[string]float64{}), keep imageOutputRatioMap
untouched while decoding, and only acquire the imageOutputRatioMapMutex to
replace imageOutputRatioMap and call InvalidateExposedDataCache when
unmarshaling succeeds (preserve the lock usage around the swap). Reference:
UpdateImageOutputRatioByJSONString, imageOutputRatioMap,
imageOutputRatioMapMutex, InvalidateExposedDataCache.

@seefs001 seefs001 closed this Feb 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants