Skip to content

增加豆包视频渠道 - #1951

Merged
seefs001 merged 3 commits into
QuantumNous:mainfrom
feitianbubu:pr/add-doubao-video
Oct 2, 2025
Merged

增加豆包视频渠道#1951
seefs001 merged 3 commits into
QuantumNous:mainfrom
feitianbubu:pr/add-doubao-video

Conversation

@feitianbubu

@feitianbubu feitianbubu commented Oct 1, 2025

Copy link
Copy Markdown
Member
  1. 新增豆包视频渠道
image
  1. 支持三个最新模型:
doubao-seedance-1-0-pro-250528
doubao-seedance-1-0-lite-t2v
doubao-seedance-1-0-lite-i2v
  1. 请求示例:
{
  "model": "doubao-seedance-1-0-pro-250528",
  "prompt": "一个穿着宇航服的宇航员在月球上行走",
  "images": [
    "https://h2.inkwai.com/bs2/upload-ylab-stunt/se/ai_portal_queue_mmu_image_upscale_aiweb/3214b798-e1b4-4b00-b7af-72b5b0417420_raw_image_0.jpg"
  ]
}
  1. 返回示例:
{
  "task_id": "cgt-20251002034625-4g7rp"
} 
  1. 视频生成:
image

@coderabbitai

coderabbitai Bot commented Oct 1, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds Doubao Video channel (type 54) with base URL and UI entries, introduces a Doubao task adaptor for video generation, extends TaskInfo with token fields, updates relay adaptor imports, marks Doubao video test as unsupported, and implements post-success token-based billing/refund in video task handling.

Changes

Cohort / File(s) Summary
Channel type and registry (backend)
constant/channel.go, relay/relay_adaptor.go
Adds ChannelTypeDoubaoVideo (54) and base URL entry; updates adaptor imports to include doubao task adaptor.
Doubao task adaptor
relay/channel/task/doubao/constants.go, relay/channel/task/doubao/adaptor.go
Introduces Doubao video channel name and model list; implements TaskAdaptor: init, validation, URL/header/body build, request/response handling, polling, model metadata, and result parsing with token propagation.
Controller updates
controller/channel-test.go, controller/task_video.go
Marks Doubao video channel test as unsupported; adds dynamic token-based billing/refund after successful video task completion using model/group ratios and quota adjustments.
Relay common types
relay/common/relay_info.go
Extends TaskInfo with completion_tokens and total_tokens fields.
Frontend UI additions
web/src/constants/channel.constants.js, web/src/helpers/render.jsx
Adds channel option for value 54 (豆包视频) and corresponding icon mapping.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant API as Server (Task API)
  participant DA as DoubaoAdaptor
  participant DB as Doubao API

  rect rgb(245,248,255)
    C->>API: Submit video task
    API->>DA: Build request (URL, headers, body)
    DA->>DB: POST /ark/v1/tasks (generate)
    DB-->>DA: { task_id, status }
    DA-->>API: task_id, raw response
    API-->>C: 202 Accepted with task_id
  end

  loop Poll status
    API->>DA: FetchTask(task_id)
    DA->>DB: GET /ark/v1/tasks/{task_id}
    DB-->>DA: { status, progress, url?, tokens? }
    DA-->>API: ParseTaskResult(TaskInfo)
  end

  alt Success
    API->>API: UpdateVideoSingleTask<br/>- compute actualQuota via model/group ratios<br/>- charge/refund delta
    API-->>C: Final result (url, status)
  else Failure
    API-->>C: Error status/reason
  end

  note over API: TaskInfo now carries completion_tokens / total_tokens for billing
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • seefs001
  • xyfacai

Poem

A whisk of code, a hop, a go—
Doubao’s channel joins the show.
Tokens tally, ratios rhyme,
Charge or refund, right on time.
Carrots counted, queues that sing—
Video sprouts with a springy ping! 🥕🎬

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title “增加豆包视频渠道” directly and succinctly describes the pull request’s main change of adding the Doubao video channel throughout the system, making it both clear and specific.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2938246 and 7ca65a5.

📒 Files selected for processing (9)
  • constant/channel.go (2 hunks)
  • controller/channel-test.go (1 hunks)
  • controller/task_video.go (2 hunks)
  • relay/channel/task/doubao/adaptor.go (1 hunks)
  • relay/channel/task/doubao/constants.go (1 hunks)
  • relay/common/relay_info.go (1 hunks)
  • relay/relay_adaptor.go (3 hunks)
  • web/src/constants/channel.constants.js (1 hunks)
  • web/src/helpers/render.jsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
controller/channel-test.go (1)
constant/channel.go (1)
  • ChannelTypeDoubaoVideo (54-54)
relay/relay_adaptor.go (3)
constant/channel.go (1)
  • ChannelTypeDoubaoVideo (54-54)
relay/channel/task/doubao/adaptor.go (1)
  • TaskAdaptor (67-71)
relay/channel/adapter.go (1)
  • TaskAdaptor (32-51)
relay/channel/task/doubao/adaptor.go (7)
relay/common/relay_info.go (3)
  • RelayInfo (74-121)
  • TaskSubmitReq (483-492)
  • TaskInfo (502-511)
relay/common/relay_utils.go (2)
  • ValidateBasicTaskRequest (67-96)
  • HasImage (18-20)
relay/channel/api_request.go (1)
  • DoTaskApiRequest (280-302)
service/error.go (1)
  • TaskErrorWrapper (139-155)
service/http_client.go (1)
  • GetHttpClient (32-34)
relay/channel/task/doubao/constants.go (2)
  • ModelList (3-7)
  • ChannelName (9-9)
model/task.go (4)
  • TaskStatusQueued (16-16)
  • TaskStatusInProgress (17-17)
  • TaskStatusSuccess (19-19)
  • TaskStatusFailure (18-18)
controller/task_video.go (6)
setting/ratio_setting/model_ratio.go (1)
  • GetModelRatio (430-441)
model/user.go (4)
  • GetUserById (287-299)
  • DecreaseUserQuota (777-792)
  • UpdateUserUsedQuotaAndRequestCount (823-830)
  • IncreaseUserQuota (752-767)
setting/ratio_setting/group_ratio.go (2)
  • GetGroupRatio (64-74)
  • GetGroupGroupRatio (76-89)
logger/logger.go (3)
  • LogInfo (55-57)
  • LogQuota (94-100)
  • LogError (63-65)
model/channel.go (1)
  • UpdateChannelUsedQuota (740-746)
model/log.go (2)
  • RecordLog (78-94)
  • LogTypeSystem (46-46)
🔇 Additional comments (7)
relay/common/relay_info.go (1)

502-511: LGTM! Token fields added for task billing.

The two new fields CompletionTokens and TotalTokens appropriately support token-based billing for video generation tasks. The omitempty JSON tags ensure backward compatibility, and the Chinese comments clearly indicate their purpose for multiplier-based billing.

constant/channel.go (2)

54-54: LGTM! New channel type constant added.

The new ChannelTypeDoubaoVideo = 54 constant follows the established pattern and is properly sequenced.


114-114: Confirm DoubaoVideo base URL: constant/channel.go:114 duplicates the VolcEngine entry at index 45. Ensure Doubao Video is intended to use the same base endpoint or update to its correct URL.

web/src/helpers/render.jsx (1)

340-341: LGTM! Icon mapping added for Doubao Video channel.

The new case for channel type 54 correctly maps to Doubao.Color icon, consistent with the existing Doubao/VolcEngine iconography. The bilingual comment and placement are appropriate.

controller/channel-test.go (1)

73-78: LGTM! Test exemption added for Doubao Video channel.

The early return for ChannelTypeDoubaoVideo follows the established pattern for async task-based channels (Midjourney, Kling, Jimeng, etc.). The error message is clear and the placement is logical.

relay/relay_adaptor.go (2)

4-4: LGTM! Imports updated for new Doubao task adaptor.

The new imports for submodel and taskdoubao are properly added to support the new channel integration. The gin import consolidation improves code organization.

Also applies to: 28-29


138-139: LGTM! Task adaptor routing added for Doubao Video.

The new case for ChannelTypeDoubaoVideo correctly returns a taskdoubao.TaskAdaptor instance, following the established pattern for other task-based channels.

Comment thread controller/task_video.go
Comment on lines +168 to +169
model.UpdateUserUsedQuotaAndRequestCount(task.UserId, quotaDelta)
model.UpdateChannelUsedQuota(task.ChannelId, quotaDelta)

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

Stop incrementing request_count during post-settlement charges

UpdateUserUsedQuotaAndRequestCount always bumps the user’s request_count by 1. Calling it here makes every post-settlement adjustment look like an extra request, which corrupts usage statistics. Please switch to a helper that only adjusts the used quota (or add one) so this reconciliation doesn’t double-count requests.

🤖 Prompt for AI Agents
In controller/task_video.go around lines 168-169, the code calls
model.UpdateUserUsedQuotaAndRequestCount which increments the user request_count
and thereby double-counts post-settlement adjustments; replace this call with a
helper that only adjusts the user's used quota (e.g.,
model.UpdateUserUsedQuota(userId, quotaDelta)) or add such a function in the
model layer if it doesn't exist, then call that instead while keeping
model.UpdateChannelUsedQuota(task.ChannelId, quotaDelta) as-is so reconciliation
only updates quota and not request_count.

Comment thread controller/task_video.go
Comment on lines +188 to +197
if err := model.IncreaseUserQuota(task.UserId, refundQuota, false); err != nil {
logger.LogError(ctx, fmt.Sprintf("退还预扣费失败: %s", err.Error()))
} else {
task.Quota = actualQuota // 更新任务记录的实际扣费额度

// 记录退款日志
logContent := fmt.Sprintf("视频任务成功退还多扣费用,模型倍率 %.2f,分组倍率 %.2f,tokens %d,预扣费 %s,实际扣费 %s,退还 %s",
modelRatio, finalGroupRatio, taskResult.TotalTokens,
logger.LogQuota(preConsumedQuota), logger.LogQuota(actualQuota), logger.LogQuota(refundQuota))
model.RecordLog(task.UserId, model.LogTypeSystem, logContent)

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

Refund must also rewind channel/user usage counters

When we refund refundQuota we only credit the user balance. The channel’s used quota and the user’s recorded used quota stay at the higher pre-charge value, so dashboards and reconciliation drift permanently. Mirror the charge path by subtracting refundQuota from the channel/user usage counters (e.g. UpdateChannelUsedQuota with a negative delta and an equivalent used-quota adjustment) so accounting remains consistent.

🤖 Prompt for AI Agents
In controller/task_video.go around lines 188 to 197, the refund only credits the
user balance but does not rewind the channel and user usage counters; update the
accounting by mirroring the original charge path — subtract refundQuota from the
channel's used quota (e.g., call the same UpdateChannelUsedQuota function with a
negative delta or equivalent) and decrement the user's recorded used quota by
refundQuota as well, perform these updates alongside IncreaseUserQuota so state
remains consistent, handle and log any errors from those updates and ensure the
operations are applied atomically or rolled back on failure.

Comment on lines +123 to +145
func (a *TaskAdaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (taskID string, taskData []byte, taskErr *dto.TaskError) {
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
taskErr = service.TaskErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError)
return
}
_ = resp.Body.Close()

// Parse Doubao response
var dResp responsePayload
if err := json.Unmarshal(responseBody, &dResp); err != nil {
taskErr = service.TaskErrorWrapper(errors.Wrapf(err, "body: %s", responseBody), "unmarshal_response_body_failed", http.StatusInternalServerError)
return
}

if dResp.ID == "" {
taskErr = service.TaskErrorWrapper(fmt.Errorf("task_id is empty"), "invalid_response", http.StatusInternalServerError)
return
}

c.JSON(http.StatusOK, gin.H{"task_id": dResp.ID})
return dResp.ID, responseBody, nil
}

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

Handle non-2xx Doubao responses before parsing success payload

When Doubao returns a 4xx/5xx (e.g., auth failure or validation error), we still unmarshal into responsePayload. Because id is empty, this path always bubbles up as our own 500/"task_id is empty", masking the original failure and misreporting the status. Please gate the success flow on resp.StatusCode so we surface upstream errors accurately and keep retries/backoffs sane.

@@
-	// Parse Doubao response
+	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
+		taskErr = service.TaskErrorWrapper(
+			fmt.Errorf("upstream status %d: %s", resp.StatusCode, string(responseBody)),
+			"upstream_request_failed",
+			resp.StatusCode,
+		)
+		return
+	}
+
+	// Parse Doubao response
 	var dResp responsePayload
📝 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 (a *TaskAdaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (taskID string, taskData []byte, taskErr *dto.TaskError) {
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
taskErr = service.TaskErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError)
return
}
_ = resp.Body.Close()
// Parse Doubao response
var dResp responsePayload
if err := json.Unmarshal(responseBody, &dResp); err != nil {
taskErr = service.TaskErrorWrapper(errors.Wrapf(err, "body: %s", responseBody), "unmarshal_response_body_failed", http.StatusInternalServerError)
return
}
if dResp.ID == "" {
taskErr = service.TaskErrorWrapper(fmt.Errorf("task_id is empty"), "invalid_response", http.StatusInternalServerError)
return
}
c.JSON(http.StatusOK, gin.H{"task_id": dResp.ID})
return dResp.ID, responseBody, nil
}
func (a *TaskAdaptor) DoResponse(
c *gin.Context,
resp *http.Response,
info *relaycommon.RelayInfo,
) (taskID string, taskData []byte, taskErr *dto.TaskError) {
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
taskErr = service.TaskErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError)
return
}
_ = resp.Body.Close()
// Handle non-2xx upstream responses
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
taskErr = service.TaskErrorWrapper(
fmt.Errorf("upstream status %d: %s", resp.StatusCode, string(responseBody)),
"upstream_request_failed",
resp.StatusCode,
)
return
}
// Parse Doubao response
var dResp responsePayload
if err := json.Unmarshal(responseBody, &dResp); err != nil {
taskErr = service.TaskErrorWrapper(
errors.Wrapf(err, "body: %s", responseBody),
"unmarshal_response_body_failed",
http.StatusInternalServerError,
)
return
}
if dResp.ID == "" {
taskErr = service.TaskErrorWrapper(
fmt.Errorf("task_id is empty"),
"invalid_response",
http.StatusInternalServerError,
)
return
}
c.JSON(http.StatusOK, gin.H{"task_id": dResp.ID})
return dResp.ID, responseBody, nil
}
🤖 Prompt for AI Agents
In relay/channel/task/doubao/adaptor.go around lines 123 to 145, the code
currently unmarshals the response body and treats missing dResp.ID as an
internal 500 error even when Doubao returned a non-2xx (4xx/5xx) error; change
the flow to first check resp.StatusCode and, for non-2xx responses, wrap and
return the original status and body as a TaskError (including status code and
response body text) without treating it as a missing task_id; only on 2xx should
you unmarshal into responsePayload and proceed to return the task ID and body,
ensuring resp.Body is closed and errors from ReadAll/Unmarshal are still wrapped
appropriately.

@seefs001
seefs001 changed the base branch from main to alpha October 2, 2025 06:40
@seefs001
seefs001 changed the base branch from alpha to main October 2, 2025 06:41
@seefs001
seefs001 merged commit acbfc9d into QuantumNous:main Oct 2, 2025
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Dec 3, 2025
x22x22 pushed a commit to x22x22/new-api that referenced this pull request Apr 24, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Apr 30, 2026
2 tasks
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