alpha -> main - #1910
Conversation
fix:Account Management Status
feat: support UMAMI analytics
feat: allow stripe promotion code
feat: gemini urlContext
feat: if video cannot play open in a new tab
feat: add duplicate key removal function when edit or add new channel
# Conflicts: # main.go
# Conflicts: # web/src/components/settings/personal/cards/AccountManagement.jsx
WalkthroughAdds Stripe promotion code support via a new setting, integrates Umami analytics injection into the index page, introduces Gemini urlContext tool support, enhances video handling in task log modal, adds key deduplication and defaults in channel edit modal, and updates settings/UI and i18n accordingly. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant FE as Frontend
participant BE as Server
participant Stripe as Stripe API
Note over FE,BE: Stripe Checkout with promotion codes
FE->>BE: Request Stripe checkout session
BE->>BE: Read setting StripePromotionCodesEnabled
BE->>Stripe: Create Checkout Session<br/>(AllowPromotionCodes = setting)
Stripe-->>BE: Session
BE-->>FE: Session URL
sequenceDiagram
autonumber
participant Client
participant Relay as Gemini Relay
participant Gemini
Note over Client,Relay: urlContext tool handling
Client->>Relay: Request with Tools (includes "urlContext")
Relay->>Relay: Build Gemini tools list<br/>(attach URLContext payload)
Relay->>Gemini: Send content + tools
Gemini-->>Relay: Response
Relay-->>Client: Transformed response
sequenceDiagram
autonumber
participant UI as Task Logs UI
participant Modal as ContentModal
participant Net as Browser Loader
Note over UI,Modal: Video preview flow
UI->>Modal: Open with video URL
Modal->>Modal: isLoading=true
Modal->>Net: Load video
alt Load success
Net-->>Modal: loadedmetadata
Modal->>Modal: isLoading=false
else Load error
Net-->>Modal: error
Modal->>Modal: videoError=true, isLoading=false
Modal-->>UI: Show error actions (open, copy)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
web/src/components/table/channels/modals/EditChannelModal.jsx (2)
349-351: Avoid overwriting a previously chosen base_url when switching types.Only set the default for type 45 if base_url is empty to preserve user choice.
Apply this diff:
- case 45: - localModels = getChannelModels(value); - setInputs((prevInputs) => ({ ...prevInputs, base_url: 'https://ark.cn-beijing.volces.com' })); + case 45: + localModels = getChannelModels(value); + setInputs((prev) => ({ + ...prev, + base_url: + typeof prev.base_url === 'string' && prev.base_url.trim() + ? prev.base_url + : 'https://ark.cn-beijing.volces.com', + }));
967-1016: Harden key dedup to handle CRLF and mixed newlines.Use a newline-agnostic split to avoid stray “\r” on Windows inputs.
- const keyLines = currentKey.split('\n'); + const keyLines = currentKey.split(/\r?\n/);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
controller/topup_stripe.go(1 hunks)dto/gemini.go(1 hunks)main.go(3 hunks)model/option.go(2 hunks)relay/channel/gemini/relay-gemini.go(3 hunks)setting/payment_stripe.go(1 hunks)web/index.html(1 hunks)web/src/components/settings/PaymentSetting.jsx(1 hunks)web/src/components/table/channels/modals/EditChannelModal.jsx(8 hunks)web/src/components/table/task-logs/modals/ContentModal.jsx(1 hunks)web/src/i18n/locales/en.json(1 hunks)web/src/i18n/locales/zh.json(1 hunks)web/src/pages/Setting/Payment/SettingsPaymentGatewayStripe.jsx(4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-27T02:15:25.448Z
Learnt from: AAEE86
PR: QuantumNous/new-api#1658
File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569
Timestamp: 2025-08-27T02:15:25.448Z
Learning: In EditChannelModal.jsx, the database stores mapped keys (display names) in the models field after applying model mapping transformations. When loading a channel, data.models contains the mapped keys, not the original model names. The filtering logic if (data.models.includes(key)) in the initialization is correct.
Applied to files:
web/src/components/table/channels/modals/EditChannelModal.jsx
📚 Learning: 2025-08-27T02:15:25.448Z
Learnt from: AAEE86
PR: QuantumNous/new-api#1658
File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569
Timestamp: 2025-08-27T02:15:25.448Z
Learning: In EditChannelModal.jsx, the applyModelMapping function transforms the models list by replacing original model names (mapping values) with display names (mapping keys). The database stores this transformed list containing mapped keys. On channel load, data.models contains these mapped display names, making the initialization filter if (data.models.includes(key)) correct.
Applied to files:
web/src/components/table/channels/modals/EditChannelModal.jsx
🧬 Code graph analysis (6)
web/src/pages/Setting/Payment/SettingsPaymentGatewayStripe.jsx (1)
web/src/components/settings/PaymentSetting.jsx (1)
inputs(30-49)
model/option.go (2)
common/constants.go (1)
OptionMap(36-36)setting/payment_stripe.go (1)
StripePromotionCodesEnabled(8-8)
controller/topup_stripe.go (1)
setting/payment_stripe.go (1)
StripePromotionCodesEnabled(8-8)
relay/channel/gemini/relay-gemini.go (1)
dto/gemini.go (1)
GeminiChatTool(249-255)
web/src/components/table/task-logs/modals/ContentModal.jsx (2)
web/src/components/table/task-logs/TaskLogsActions.jsx (1)
Typography(25-25)web/src/hooks/task-logs/useTaskLogsData.js (2)
isModalOpen(67-67)modalContent(68-68)
web/src/components/table/channels/modals/EditChannelModal.jsx (1)
web/src/helpers/utils.jsx (2)
showInfo(161-163)showSuccess(157-159)
🔇 Additional comments (8)
controller/topup_stripe.go (1)
228-230: Promotion codes flag is cleanly threaded through
AllowPromotionCodesonly toggles when the new setting flips on, matching Stripe’s expectations while leaving existing sessions untouched. Nice surgical change.model/option.go (1)
85-86: Option plumbing stays consistentAppreciate that you registered
StripePromotionCodesEnabledin both the initializer and the runtime update switch—this keeps the flag aligned across in-memory settings and persisted options.Also applies to: 334-335
web/src/components/table/channels/modals/EditChannelModal.jsx (6)
458-465: Good fallback for Volcengine Ark base_url when loading channels.This guards empty base_url for type 45 cleanly.
1112-1146: Batch UI refinements look correct.Nice addition of “密钥聚合模式” and dedup action gating (hidden for Vertex).
1348-1351: Persisting vertex_key_type via settings is correct.Keeps top-level UI state while serializing to settings for backend.
1371-1372: Conditional rendering for Vertex JSON/manual vs upload is clear and consistent.Good separation of batch/file-only vs single/manual paths.
Also applies to: 1436-1437
848-851: Correct serialization of Vertex keys before submit.JSON-stringifying array vs single item matches the expected payload shape.
1946-1966: Make the base_url Select fully form-controlled and extensible.
- Remove
defaultValueand bind the select to the form state:- defaultValue='https://ark.cn-beijing.volces.com' + value={inputs.base_url}- Enable custom entries for future regions:
+ allowAdditions- Centralize the two official endpoints into a shared constant:
export const API_BASE_URLS = [ 'https://ark.cn-beijing.volces.com', // VolcEngine Ark Beijing (per VolcEngine docs) 'https://ark.ap-southeast.bytepluses.com' // BytePlus ModelArk AP-Southeast (per BytePlus docs) ];
| indexPage = bytes.ReplaceAll(indexPage, []byte("<analytics></analytics>\n"), []byte(analyticsInject)) | ||
|
|
There was a problem hiding this comment.
Don’t hardcode newline in the analytics placeholder replacement
bytes.ReplaceAll currently searches for <analytics></analytics>\n. If the built index.html uses \r\n (common on Windows) or the bundler strips the trailing newline, the placeholder never matches, so the Umami script is never injected even when UMAMI_WEBSITE_ID is set. Please match the tag without assuming a specific line ending (and append a newline only when you actually inject the script) to keep the feature reliable across build environments.
- analyticsInject := analyticsInjectBuilder.String()
- indexPage = bytes.ReplaceAll(indexPage, []byte("<analytics></analytics>\n"), []byte(analyticsInject))
+ analyticsInject := analyticsInjectBuilder.String()
+ placeholder := []byte("<analytics></analytics>")
+ replacement := []byte(analyticsInject)
+ if analyticsInject != "" {
+ replacement = append(replacement, '\n')
+ }
+ indexPage = bytes.ReplaceAll(indexPage, placeholder, replacement)📝 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.
| indexPage = bytes.ReplaceAll(indexPage, []byte("<analytics></analytics>\n"), []byte(analyticsInject)) | |
| analyticsInject := analyticsInjectBuilder.String() | |
| placeholder := []byte("<analytics></analytics>") | |
| replacement := []byte(analyticsInject) | |
| if analyticsInject != "" { | |
| replacement = append(replacement, '\n') | |
| } | |
| indexPage = bytes.ReplaceAll(indexPage, placeholder, replacement) |
🤖 Prompt for AI Agents
In main.go around lines 166-167, the replacement currently matches
"<analytics></analytics>\n" which fails on CRLF or when the trailing newline is
absent; change the logic to search for the tag without assuming a line ending
(e.g. match "<analytics></analytics>"), or normalize CRLF to LF first, then
perform bytes.ReplaceAll on the tag-only byte sequence; when you actually inject
the analytics script, append a single '\n' to the injected bytes so the output
keeps a newline only when injection occurs.
| useEffect(() => { | ||
| if (isModalOpen && isVideo) { | ||
| setVideoError(false); | ||
| setIsLoading(true); | ||
| } | ||
| }, [isModalOpen, isVideo]); |
There was a problem hiding this comment.
Reset the error state when changing sources. The effect never reruns when a new modalContent is shown while the modal stays open, so a prior failure leaves videoError === true and short-circuits rendering of the <video> element for the new source. Users remain stuck on the fallback even though the next URL might be playable. Please re-trigger the reset when modalContent changes.
- }, [isModalOpen, isVideo]);
+ }, [isModalOpen, isVideo, modalContent]);📝 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.
| useEffect(() => { | |
| if (isModalOpen && isVideo) { | |
| setVideoError(false); | |
| setIsLoading(true); | |
| } | |
| }, [isModalOpen, isVideo]); | |
| useEffect(() => { | |
| if (isModalOpen && isVideo) { | |
| setVideoError(false); | |
| setIsLoading(true); | |
| } | |
| }, [isModalOpen, isVideo, modalContent]); |
🤖 Prompt for AI Agents
In web/src/components/table/task-logs/modals/ContentModal.jsx around lines 35 to
40, the effect that resets videoError and sets loading only depends on
isModalOpen and isVideo, so when a new modalContent is shown while the modal
stays open a prior videoError can persist; update the effect to also depend on
modalContent and when isModalOpen && isVideo is true reset videoError to false
and setIsLoading(true) (so new sources clear previous error state and attempt to
load).
| <video | ||
| src={modalContent} | ||
| controls | ||
| style={{ width: '100%' }} | ||
| autoPlay | ||
| crossOrigin="anonymous" | ||
| onError={handleVideoError} | ||
| onLoadedData={handleVideoLoaded} | ||
| onLoadStart={() => setIsLoading(true)} | ||
| /> |
There was a problem hiding this comment.
Don’t force crossOrigin="anonymous" on every video. Adding this attribute makes the browser issue a CORS fetch; providers that don’t send Access-Control-Allow-Origin (many CDN/S3 links) now fail to load and always hit your error state, breaking previously working videos. Please drop the attribute or gate it behind an opt-in.
<video
src={modalContent}
controls
style={{ width: '100%' }}
autoPlay
- crossOrigin="anonymous"
onError={handleVideoError}
onLoadedData={handleVideoLoaded}
onLoadStart={() => setIsLoading(true)}
/>📝 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.
| <video | |
| src={modalContent} | |
| controls | |
| style={{ width: '100%' }} | |
| autoPlay | |
| crossOrigin="anonymous" | |
| onError={handleVideoError} | |
| onLoadedData={handleVideoLoaded} | |
| onLoadStart={() => setIsLoading(true)} | |
| /> | |
| <video | |
| src={modalContent} | |
| controls | |
| style={{ width: '100%' }} | |
| autoPlay | |
| onError={handleVideoError} | |
| onLoadedData={handleVideoLoaded} | |
| onLoadStart={() => setIsLoading(true)} | |
| /> |
🤖 Prompt for AI Agents
In web/src/components/table/task-logs/modals/ContentModal.jsx around lines 117
to 126, the video element currently forces crossOrigin="anonymous" which
triggers CORS fetches and breaks sources that don't send
Access-Control-Allow-Origin; remove the hardcoded crossOrigin attribute or make
it opt-in by adding a prop/flag (e.g., allowCrossOrigin or crossoriginEnabled)
to the component and only set the crossOrigin attribute when that prop is true;
update the component’s prop types/defaults and callers to pass the flag where
CORS-enabled sources are expected.
| StripePromotionCodesEnabled: | ||
| props.options.StripePromotionCodesEnabled !== undefined | ||
| ? props.options.StripePromotionCodesEnabled | ||
| : false, | ||
| }; |
There was a problem hiding this comment.
Coerce StripePromotionCodesEnabled to a boolean
props.options.StripePromotionCodesEnabled arrives as a persisted string ('true'/'false'). Passing that through as-is makes the switch render “on” even when the stored value is 'false', because any non-empty string is truthy and Semi UI’s checked prop expects an actual boolean.(semi.design)
Fix by normalizing the option to a boolean before seeding state:
- StripePromotionCodesEnabled:
- props.options.StripePromotionCodesEnabled !== undefined
- ? props.options.StripePromotionCodesEnabled
- : false,
+ StripePromotionCodesEnabled:
+ props.options.StripePromotionCodesEnabled !== undefined
+ ? ['true', true].includes(props.options.StripePromotionCodesEnabled)
+ : false,📝 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.
| StripePromotionCodesEnabled: | |
| props.options.StripePromotionCodesEnabled !== undefined | |
| ? props.options.StripePromotionCodesEnabled | |
| : false, | |
| }; | |
| StripePromotionCodesEnabled: | |
| props.options.StripePromotionCodesEnabled !== undefined | |
| ? ['true', true].includes(props.options.StripePromotionCodesEnabled) | |
| : false, | |
| }; |
🤖 Prompt for AI Agents
In web/src/pages/Setting/Payment/SettingsPaymentGatewayStripe.jsx around lines
67 to 71, props.options.StripePromotionCodesEnabled is coming in as a string
('true'/'false') so the switch renders incorrectly; normalize it to a real
boolean when seeding state by checking if the value is the boolean true or the
string 'true' (e.g. value === true || value === 'true') and use that result
instead of the raw prop.
…ult_baseurl alpha -> main
fix(openai): 修复 Codex 工具调用 call_id 处理
Summary by CodeRabbit