- openai_gateway_model_availability.go: pass platform through to
listSchedulableAccounts so OpenAI/Grok diagnosis scopes to the
correct candidate pool (build break introduced by Grok subscription
PR #3310).
- no_account_error.go: drop redundant context.Context type on
ctx := context.Background() to satisfy staticcheck ST1023.
The risk control center's moderation log records had no field for the
keyword that triggered a keyword block, so the admin UI couldn't show
which keyword was hit (only the application slog logged it).
- migration 156: add matched_keyword column to content_moderation_logs
- ContentModerationLog gains MatchedKeyword; set it on keyword block
- repo CreateLog/ListLogs persist and read the column
- frontend: show "命中关键词" inline in the log table and detail modal
- i18n: add matchedKeyword (zh/en)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When gpt-image upstream returns response.completed with no image but a text
refusal (content moderation, e.g. the model replies "this request was judged
unsafe to generate"), the soft-failure path treated it as a probabilistic
upstream failure and returned a retryable UpstreamFailoverError (502). Retrying
or switching accounts is futile for a content-policy block — it just burns
other accounts' quota and still surfaces an opaque 502 to the client.
Distinguish the two no-image cases:
(A) model text refusal -> 400 content_policy_violation, no retry, refusal
reason passed through to the client.
(B) truly empty response -> unchanged: retryable UpstreamFailoverError (502).
Adds extractOpenAIImagesModelRefusal (extracts the refusal text from
output_text.delta / message output_text, capped at 600 chars) and two unit
tests; the existing empty-response retry test is unaffected.
The "[Billing] Using fallback pricing for model: X" warn was emitted on
every request for any model not in LiteLLM but matched by getFallbackPricing
(e.g. "glm-5.2" substring-matches the "glm-5" fallback price). Via the stdlib
log bridge it is inferred as WARN and persisted to ops_system_logs, producing
tens of thousands of rows/day. It fired even when channel pricing already
overrides the price, and from several call sites (resolver, account-stats
pricing, non-channel billing, admin lookup).
Dedup the warn at the source (BillingService.GetModelPricing) via a sync.Map
keyed by the already-lowercased model name, so each model logs at most once
per process while keeping one audit line. Billing amounts are unchanged.
Also clarify the model/mapping pattern conflict error to state that names are
matched case-insensitively, so an existing entry (e.g. "GLM-5.2") already
covers all case variants and the lowercase variant need not be added — the
behavior the issue mistook for a case-sensitivity bug.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>