feat(ali): support happyhorse 1.0/1.1 video generation models - #4810
feat(ali): support happyhorse 1.0/1.1 video generation models#4810feitianbubu wants to merge 4 commits into
Conversation
WalkthroughThis PR expands Alibaba Ali video generation support with newer model identifiers, richer request and response DTOs, multipart media assembly, model-specific request conversion and defaults, centralized task validation, updated billing ratios, and revised numeric and OpenAI video response conversion. ChangesAlibaba Ali Video Generation Expansion
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@relay/channel/task/ali/adaptor.go`:
- Around line 322-344: The branching that sets default Resolution/Size uses
req.Model (e.g., in the newFormatDefaultResolution check and the subsequent
strings.Contains/HasPrefix checks) which breaks when model mapping changed the
model; update this block to consistently use aliReq.Model (not req.Model) for
all model-based decisions (newFormatDefaultResolution,
strings.Contains(req.Model -> strings.Contains(aliReq.Model), and
strings.HasPrefix(req.Model -> strings.HasPrefix(aliReq.Model))) so
aliReq.Parameters gets defaults based on the mapped/upstream model.
In `@relay/channel/task/ali/multipart.go`:
- Around line 36-42: In appendImageURLsAsMedia, skip blank/whitespace-only URL
entries before appending to aliReq.Input.Media to avoid producing invalid
upstream media.url values; update the loop in appendImageURLsAsMedia to trim
each u (using strings.TrimSpace) and continue if the result is empty, otherwise
append the trimmed URL as AliVideoMedia with the given mediaType.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 55b685cd-d745-487a-ba24-45947d8283e4
📒 Files selected for processing (3)
relay/channel/task/ali/adaptor.gorelay/channel/task/ali/constants.gorelay/channel/task/ali/multipart.go
| } else if resolution, ok := newFormatDefaultResolution(aliReq.Model); ok { | ||
| aliReq.Parameters.Resolution = resolution | ||
| } else if strings.Contains(req.Model, "t2v") { // 旧版 t2v 默认 size | ||
| if strings.HasPrefix(req.Model, "wan2.5") { | ||
| aliReq.Parameters.Size = "1920*1080" | ||
| } else if strings.HasPrefix(req.Model, "wan2.2") { | ||
| aliReq.Parameters.Size = "1920*1080" | ||
| } else { | ||
| aliReq.Parameters.Size = "1280*720" | ||
| } | ||
| } else { | ||
| // 根据模型设置默认分辨率 | ||
| if strings.Contains(req.Model, "t2v") { // image to video | ||
| if strings.HasPrefix(req.Model, "wan2.5") { | ||
| aliReq.Parameters.Size = "1920*1080" | ||
| } else if strings.HasPrefix(req.Model, "wan2.2") { | ||
| aliReq.Parameters.Size = "1920*1080" | ||
| } else { | ||
| aliReq.Parameters.Size = "1280*720" | ||
| } | ||
| // 旧版 i2v 默认 resolution | ||
| if strings.HasPrefix(req.Model, "wan2.6") { | ||
| aliReq.Parameters.Resolution = "1080P" | ||
| } else if strings.HasPrefix(req.Model, "wan2.5") { | ||
| aliReq.Parameters.Resolution = "1080P" | ||
| } else if strings.HasPrefix(req.Model, "wan2.2-i2v-flash") { | ||
| aliReq.Parameters.Resolution = "720P" | ||
| } else if strings.HasPrefix(req.Model, "wan2.2-i2v-plus") { | ||
| aliReq.Parameters.Resolution = "1080P" | ||
| } else { | ||
| if strings.HasPrefix(req.Model, "wan2.6") { | ||
| aliReq.Parameters.Resolution = "1080P" | ||
| } else if strings.HasPrefix(req.Model, "wan2.5") { | ||
| aliReq.Parameters.Resolution = "1080P" | ||
| } else if strings.HasPrefix(req.Model, "wan2.2-i2v-flash") { | ||
| aliReq.Parameters.Resolution = "720P" | ||
| } else if strings.HasPrefix(req.Model, "wan2.2-i2v-plus") { | ||
| aliReq.Parameters.Resolution = "1080P" | ||
| } else { | ||
| aliReq.Parameters.Resolution = "720P" | ||
| } | ||
| aliReq.Parameters.Resolution = "720P" | ||
| } |
There was a problem hiding this comment.
Use upstream model for default param branching after model mapping.
Line 324+ still checks req.Model; when model mapping is enabled, defaults can be computed for the wrong model. Use aliReq.Model consistently in this block.
Proposed fix
- } else if strings.Contains(req.Model, "t2v") { // 旧版 t2v 默认 size
- if strings.HasPrefix(req.Model, "wan2.5") {
+ } else if strings.Contains(aliReq.Model, "t2v") { // 旧版 t2v 默认 size
+ if strings.HasPrefix(aliReq.Model, "wan2.5") {
aliReq.Parameters.Size = "1920*1080"
- } else if strings.HasPrefix(req.Model, "wan2.2") {
+ } else if strings.HasPrefix(aliReq.Model, "wan2.2") {
aliReq.Parameters.Size = "1920*1080"
} else {
aliReq.Parameters.Size = "1280*720"
}
} else {
// 旧版 i2v 默认 resolution
- if strings.HasPrefix(req.Model, "wan2.6") {
+ if strings.HasPrefix(aliReq.Model, "wan2.6") {
aliReq.Parameters.Resolution = "1080P"
- } else if strings.HasPrefix(req.Model, "wan2.5") {
+ } else if strings.HasPrefix(aliReq.Model, "wan2.5") {
aliReq.Parameters.Resolution = "1080P"
- } else if strings.HasPrefix(req.Model, "wan2.2-i2v-flash") {
+ } else if strings.HasPrefix(aliReq.Model, "wan2.2-i2v-flash") {
aliReq.Parameters.Resolution = "720P"
- } else if strings.HasPrefix(req.Model, "wan2.2-i2v-plus") {
+ } else if strings.HasPrefix(aliReq.Model, "wan2.2-i2v-plus") {
aliReq.Parameters.Resolution = "1080P"
} else {
aliReq.Parameters.Resolution = "720P"
}
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@relay/channel/task/ali/adaptor.go` around lines 322 - 344, The branching that
sets default Resolution/Size uses req.Model (e.g., in the
newFormatDefaultResolution check and the subsequent strings.Contains/HasPrefix
checks) which breaks when model mapping changed the model; update this block to
consistently use aliReq.Model (not req.Model) for all model-based decisions
(newFormatDefaultResolution, strings.Contains(req.Model ->
strings.Contains(aliReq.Model), and strings.HasPrefix(req.Model ->
strings.HasPrefix(aliReq.Model))) so aliReq.Parameters gets defaults based on
the mapped/upstream model.
| func appendImageURLsAsMedia(aliReq *AliVideoRequest, mediaType string, urls []string) { | ||
| for _, u := range urls { | ||
| aliReq.Input.Media = append(aliReq.Input.Media, AliVideoMedia{ | ||
| Type: mediaType, | ||
| URL: u, | ||
| }) | ||
| } |
There was a problem hiding this comment.
Skip blank media URLs before appending.
Line 37 currently appends raw form values; empty entries can produce invalid upstream media.url values.
Proposed fix
func appendImageURLsAsMedia(aliReq *AliVideoRequest, mediaType string, urls []string) {
for _, u := range urls {
+ u = strings.TrimSpace(u)
+ if u == "" {
+ continue
+ }
aliReq.Input.Media = append(aliReq.Input.Media, AliVideoMedia{
Type: mediaType,
URL: u,
})
}
}📝 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.
| func appendImageURLsAsMedia(aliReq *AliVideoRequest, mediaType string, urls []string) { | |
| for _, u := range urls { | |
| aliReq.Input.Media = append(aliReq.Input.Media, AliVideoMedia{ | |
| Type: mediaType, | |
| URL: u, | |
| }) | |
| } | |
| func appendImageURLsAsMedia(aliReq *AliVideoRequest, mediaType string, urls []string) { | |
| for _, u := range urls { | |
| u = strings.TrimSpace(u) | |
| if u == "" { | |
| continue | |
| } | |
| aliReq.Input.Media = append(aliReq.Input.Media, AliVideoMedia{ | |
| Type: mediaType, | |
| URL: u, | |
| }) | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@relay/channel/task/ali/multipart.go` around lines 36 - 42, In
appendImageURLsAsMedia, skip blank/whitespace-only URL entries before appending
to aliReq.Input.Media to avoid producing invalid upstream media.url values;
update the loop in appendImageURLsAsMedia to trim each u (using
strings.TrimSpace) and continue if the result is empty, otherwise append the
trimmed URL as AliVideoMedia with the given mediaType.
|
LGTM 什么时候可以合并呢 |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@dto/values.go`:
- Around line 34-40: The code in IntValue's unmarshal logic is directly calling
json.Unmarshal instead of using the common.Unmarshal wrapper function. Replace
both direct json.Unmarshal calls (one when unmarshaling into the float variable
f and one when unmarshaling into the string variable s) with calls to
common.Unmarshal from the common package to comply with the repo-wide JSON
handling policy. Ensure the error handling and logic remain unchanged after the
replacement.
- Around line 33-46: In the IntValue UnmarshalJSON method, replace all direct
json.Unmarshal() calls with common.Unmarshal() wrapper functions (specifically
the calls with &f and &s parameters). Additionally, add bounds and validity
checking before converting floats to integers on the int(f) and int(v) casts to
handle special values like NaN and Inf, as well as values exceeding the valid
integer range, preventing silent data corruption.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: fd3342a7-a35b-4f6d-8d5a-afe0678977cc
📒 Files selected for processing (2)
dto/values.gorelay/channel/task/ali/adaptor.go
🚧 Files skipped from review as they are similar to previous changes (1)
- relay/channel/task/ali/adaptor.go
| var f float64 | ||
| if err := json.Unmarshal(b, &f); err == nil { | ||
| *i = IntValue(int(f)) | ||
| return nil | ||
| } | ||
| var s string | ||
| if err := json.Unmarshal(b, &s); err != nil { | ||
| return err | ||
| } | ||
| v, err := strconv.Atoi(s) | ||
| v, err := strconv.ParseFloat(s, 64) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| *i = IntValue(v) | ||
| *i = IntValue(int(v)) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, locate and examine the dto/values.go file
find . -name "values.go" -path "*/dto/*" -type fRepository: QuantumNous/new-api
Length of output: 79
🏁 Script executed:
#!/bin/bash
# Check the actual content around lines 33-46 in dto/values.go
if [ -f "dto/values.go" ]; then
echo "=== Full file context ==="
cat -n dto/values.go | head -60
fiRepository: QuantumNous/new-api
Length of output: 1630
🏁 Script executed:
#!/bin/bash
# Check what json-related functions are imported and used
if [ -f "dto/values.go" ]; then
echo "=== Imports in dto/values.go ==="
head -30 dto/values.go | grep -E "^import|^[[:space:]]*\""
fiRepository: QuantumNous/new-api
Length of output: 133
🏁 Script executed:
#!/bin/bash
# Check if common/json.go wrapper functions exist
if [ -f "common/json.go" ]; then
echo "=== Available wrapper functions in common/json.go ==="
rg "^func \(|^func " common/json.go | head -20
fiRepository: QuantumNous/new-api
Length of output: 404
Replace direct encoding/json calls with common.* wrapper functions; add bounds checking to float-to-int conversion.
This file uses json.Unmarshal() and json.Marshal() directly instead of the required common.Unmarshal() and common.Marshal() wrapper functions (lines 12, 18, 23, 34, 39, 51, 58, and similar throughout). Per coding guidelines, all JSON operations in business code must use common/json.go wrappers.
Additionally, the float-to-int conversions on lines 35 and 46 lack bounds and validity checking. Values like NaN, Inf, or numbers exceeding int range will silently truncate to undefined integers, corrupting usage data. Add explicit checks before casting:
func (i *IntValue) UnmarshalJSON(b []byte) error {
var f float64
if err := json.Unmarshal(b, &f); err == nil {
+ if math.IsNaN(f) || math.IsInf(f, 0) || f > float64(math.MaxInt) || f < float64(math.MinInt) {
+ return errors.New("invalid numeric value")
+ }
- *i = IntValue(int(f))
+ *i = IntValue(int(math.Trunc(f)))
return nil
}
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
v, err := strconv.ParseFloat(s, 64)
if err != nil {
return err
}
+ if math.IsNaN(v) || math.IsInf(v, 0) || v > float64(math.MaxInt) || v < float64(math.MinInt) {
+ return errors.New("invalid numeric value")
+ }
- *i = IntValue(int(v))
+ *i = IntValue(int(math.Trunc(v)))
return nil
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@dto/values.go` around lines 33 - 46, In the IntValue UnmarshalJSON method,
replace all direct json.Unmarshal() calls with common.Unmarshal() wrapper
functions (specifically the calls with &f and &s parameters). Additionally, add
bounds and validity checking before converting floats to integers on the int(f)
and int(v) casts to handle special values like NaN and Inf, as well as values
exceeding the valid integer range, preventing silent data corruption.
| if err := json.Unmarshal(b, &f); err == nil { | ||
| *i = IntValue(int(f)) | ||
| return nil | ||
| } | ||
| var s string | ||
| if err := json.Unmarshal(b, &s); err != nil { | ||
| return err |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Use common.Unmarshal wrappers instead of direct json.Unmarshal.
Line 34 and Line 39 call json.Unmarshal directly in business code. Please route these through common.Unmarshal(...) to match the repo-wide JSON policy.
As per coding guidelines: “Do NOT directly import or call encoding/json in business code; use wrapper functions from common/json.go for marshal/unmarshal operations.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@dto/values.go` around lines 34 - 40, The code in IntValue's unmarshal logic
is directly calling json.Unmarshal instead of using the common.Unmarshal wrapper
function. Replace both direct json.Unmarshal calls (one when unmarshaling into
the float variable f and one when unmarshaling into the string variable s) with
calls to common.Unmarshal from the common package to comply with the repo-wide
JSON handling policy. Ensure the error handling and logic remain unchanged after
the replacement.
Source: Coding guidelines
Add wan2.7-r2v / wan2.7-videoedit to the model list, complete wan2.6/2.7 request fields (reference_video_urls, reference_voice, ratio, audio_setting, shot_type) and usage fields, map multipart media form fields into input.media, and let wan2.7 fall back to server-side default resolution.
… tasks
Successful Ali wan2.7 video tasks return fractional second values in
usage. Observed upstream response:
"usage": {
"duration": 20.02,
"input_video_duration": 10.01,
"output_video_duration": 10.01,
"video_count": 1,
"SR": 720
}
dto.IntValue.UnmarshalJSON tried int first, and Go's encoding/json
refuses to decode a float literal into int, so it fell through to the
string branch and failed:
json: cannot unmarshal number into Go struct field
AliUsage.usage.duration of type string
AliUsage.InputVideoDuration / OutputVideoDuration were plain int and
failed on 10.01 too. Any single field failing makes the whole
AliVideoResponse unmarshal fail -> ParseTaskResult errors ->
updateVideoSingleTask returns early without updating the task: the
already-SUCCEEDED video URL is never persisted, billing never settles,
and every polling round repeats the failure, leaving the task stuck.
Fix:
- IntValue.UnmarshalJSON now parses as float64 then truncates to int,
with a ParseFloat string fallback, accepting number/float/string forms.
- AliUsage.InputVideoDuration / OutputVideoDuration switched to
dto.IntValue to tolerate fractional seconds.
These usage fields are not used for billing (ParseTaskResult only reads
status/url), so truncation to int has no accounting impact.
…icing Add happyhorse-1.1 t2v/i2v/r2v to the model list and billing ratios. 1.1 charges 1.2元/秒 at 1080P (vs 1.6 for 1.0), so it uses its own resolution ratio map instead of reusing happyhorse-1.0's. Relax the new-format prefix match to cover all happyhorse versions.
bcf4b0f to
431748e
Compare
移植上游 PR QuantumNous#4810。 - ModelList 补 wan2.7-r2v / wan2.7-videoedit 与 happyhorse 1.0/1.1 - 补齐新版请求字段:reference_video_urls、reference_voice、ratio、 audio_setting、shot_type,以及 usage 的 size/ratio/输入输出时长 - 新增 multipart.go:把 image_url / video_url / audio_url 表单字段按 模型映射进 input.media(r2v 与 videoedit 归 reference_image, videoedit 的视频归 video,其余归 reference_video) - 校验入口从 ValidateMultipartDirect 统一为 ValidateBasicTaskRequest, 后者同样执行 validateTaskDurationBounds 且原生支持 multipart, 时长上界校验不丢失 - EstimateBilling 保留 min(Duration, MaxTaskDurationSeconds) 钳制 上游 PR QuantumNous#4078(万相 wan2.7)未单独移植:仓库已含 wan2.7-i2v/t2v,其余 内容(四个 wan2.7 模型、ratio/audio_setting、usage 扩展、分辨率倍率) 均为本 PR 的子集;其 AliUsage.Duration 改 float64 的方案也已由 QuantumNous#6174 的 IntValue 饱和解码更完整地覆盖。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
移植上游 PR QuantumNous#5124 的 playground 视频 UI 部分。 只有存在 happyhorse-* 模型时才出现 Chat/Video 分页,否则原样早返回 单栏聊天布局,现有 PlaygroundChat/PlaygroundInput 的 props 一字未改。 左栏为参数表单(模型类型、分辨率、时长、媒体输入、高级设置),右栏为 任务队列与播放器,提交后 5 秒轮询、任务列表持久化到 localStorage。 未采纳该 PR 的独立渠道方案:它的上游地址与既有 ali 渠道完全相同 (DashScope video-synthesis),happyhorse 已由 QuantumNous#4810 作为 ali 渠道下的 模型接入,再开一个渠道号只是重复——渠道号不可回收。前端改用模型名 前缀 happyhorse- 识别,同时覆盖仓库里的 1.0 与 1.1 两个系列。 在上游实现上修正三处: 1. 拖拽上传实际不可用 原实现对拖入文件调 URL.createObjectURL 生成 blob: URL 直接当 input_reference 提交。blob: 是仅在本标签页有效的句柄,上游服务器 取不到,而本地预览却能正常显示,用户要到任务失败才发现。改为只接受 http(s) URL(text/uri-list 或 text/plain)。真正的文件上传需要后端 对象存储接口,不在本次范围。 2. 明文 API Key 的多余留存 删除了写入后从未被读取的 taskApiKeys 引用与 TokenOption.key 字段。 密钥现仅在提交前按需拉取,存活于轮询闭包内。仍存在的残留面:未完成 任务会在每次进入 playground 时重新拉取明文密钥,根治需要后端会话 鉴权代理,已记录为后续项。 3. localStorage 无上限 任务历史无限追加,最终撑爆配额后静默吞掉 QuotaExceededError。现限制 50 条,写入与读取两侧都截断;并为 loadTasksFromStorage 补 Array 守卫——原实现遇到非数组值会整页崩溃。 补齐 32 个 i18n key × 7 种语言(上游 patch 只登记了其代码实际调用的 一部分,其余回退显示英文原串,且完全没有 zh-TW)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
上一批移植(QuantumNous#4810 + QuantumNous#5124)引入的三处缺陷,合起来使 happyhorse 图生视频 端到端不可用。三处单看都不像问题,是组合出来的。 1. input_reference 被静默丢弃 QuantumNous#4810 把 ali 的校验入口从 ValidateMultipartDirect 换成 ValidateBasicTaskRequest,而后者没有 InputReference→Images 的归一化。 旧模型与 wan2.7-i2v 因为走 firstTaskImage 兜底而幸免,happyhorse 全系 与 wan2.7-r2v/videoedit 的 isNewFormatModel 分支只读 req.Images,于是 收不到任何媒体——而 QuantumNous#5124 的新前端发的正是 input_reference。 归一化补在通用校验器里:input_reference 是 OpenAI 视频接口的标准字段, 其余用该校验器的渠道(xai/gemini/vertex)此前同样收不到它。 2. 刚提交的任务返回非协议状态值 "unknown" QuantumNous#4810 把状态源从 convertAliStatus(存档响应)换成 task.ToOpenAIVideo() (DB 行),而 InitTask 建行时是 NOT_START,ToVideoStatus 没有该分支,落到 default 返回 "unknown"。轮询 worker 15 秒才接手,这期间下游按四态解析 会整条落空——前端表现为任务提交后从队列里消失十几秒。 NOT_START 语义就是排队中,补进 queued 分支。 3. 首尾帧语义丢失 imageMediaType 对整个数组只返回一种类型,happyhorse-i2v 传两张图会被 打成两个 first_frame。改为按模型分型:参考类(r2v/videoedit)同型追加, 图生视频首张 first_frame、次张 last_frame,与 wan2.7-i2v 的 normalizeWan27I2VInput 对齐。 另修两处既有缺陷: - 时长兜底原本在 metadata 合并之前执行,而 metadata 能把 Duration 写回 0。 0 一头被 omitempty 从上游请求里抹掉(阿里改用自己的默认时长照常出片), 另一头让 seconds 倍率在 AddOtherRatio 的 ratio>0 守卫处被静默丢弃(按 1 秒计价)——净效果是付 1 秒的钱拿到默认时长的视频,稳定可复现。兜底移到 metadata 合并之后,使计费口径与上游下单口径一致。 - 双图走首尾帧字段的分支原本对所有旧模型生效,但只有 kf2v 系列真正支持 首尾帧,其余旧版图生视频只认 img_url,发 first_frame_url/last_frame_url 会被上游拒绝。收窄到 kf2v。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
51fdfc5 to
2b6f1df
Compare
支持阿里百炼快乐马 happyhorse-1.0 / happyhorse-1.1 系列视频模型(含 video-edit),1.1 的 1080P 档单独定价。同时补齐 wan2.6/2.7 协议字段(reference_video_urls、ratio、audio_setting、shot_type 等)、multipart 媒体字段映射,新增 wan2.7-r2v / wan2.7-videoedit。
原 PR 里的 wan2.7 基础支持已由 #4984 合入。分支已基于当前 main 重建,移除了重复实现,现在零冲突可直接合并。线上已稳定运行两个月。
usage.duration 浮点解析的修复为了尽快解决 #6166 拆成了单独 PR(#6174),先合哪个都不冲突。
🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
提交示例:




轮询成功:
查询成功:
生成视频: