Skip to content

feat(ali): support happyhorse 1.0/1.1 video generation models - #4810

Open
feitianbubu wants to merge 4 commits into
QuantumNous:mainfrom
feitianbubu:pr/add-ali-happyhorse1.0
Open

feat(ali): support happyhorse 1.0/1.1 video generation models#4810
feitianbubu wants to merge 4 commits into
QuantumNous:mainfrom
feitianbubu:pr/add-ali-happyhorse1.0

Conversation

@feitianbubu

@feitianbubu feitianbubu commented May 12, 2026

Copy link
Copy Markdown
Member

⚠️ 提交说明 / PR Notice

支持阿里百炼快乐马 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

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

提交示例:
image
轮询成功:
image
查询成功:
image
生成视频:
image

@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This 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.

Changes

Alibaba Ali Video Generation Expansion

Layer / File(s) Summary
Data structures and contracts
relay/channel/task/ali/adaptor.go
Adds AliVideoMedia and expands Ali input, parameter, usage, and metadata structures with media, reference, generation, audio, shot, ratio, size, and duration fields.
Model catalog and multipart media mapping
relay/channel/task/ali/constants.go, relay/channel/task/ali/multipart.go
Adds wan2.7 and happyhorse model identifiers and maps multipart image, video, and audio fields to model-specific Ali media types.
Request validation and construction
relay/channel/task/ali/adaptor.go
Uses centralized generate-task validation, appends multipart media, updates model-specific image handling and defaults, and limits billing ratios to newer model families.
Response and numeric conversion
relay/channel/task/ali/adaptor.go, dto/values.go
Uses task.ToOpenAIVideo() for response initialization and accepts numeric JSON values through float-based integer conversion.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: creamlike1024, wxwwt

Poem

🐰 New models hop into the stream,

Media joins each video dream.
Wan fields bloom in formats bright,
Ali requests take graceful flight. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches a major part of the PR by adding HappyHorse 1.0/1.1 video model support.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with 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.

❤️ Share

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

@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

🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between aa56667 and 5cac039.

📒 Files selected for processing (3)
  • relay/channel/task/ali/adaptor.go
  • relay/channel/task/ali/constants.go
  • relay/channel/task/ali/multipart.go

Comment on lines +322 to 344
} 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"
}

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 | ⚡ Quick win

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.

Comment on lines +36 to +42
func appendImageURLsAsMedia(aliReq *AliVideoRequest, mediaType string, urls []string) {
for _, u := range urls {
aliReq.Input.Media = append(aliReq.Input.Media, AliVideoMedia{
Type: mediaType,
URL: u,
})
}

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 | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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.

@wlngo

wlngo commented May 25, 2026

Copy link
Copy Markdown

LGTM 什么时候可以合并呢

@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

🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5cac039 and bcf4b0f.

📒 Files selected for processing (2)
  • dto/values.go
  • relay/channel/task/ali/adaptor.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • relay/channel/task/ali/adaptor.go

Comment thread dto/values.go
Comment on lines +33 to +46
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))

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, locate and examine the dto/values.go file
find . -name "values.go" -path "*/dto/*" -type f

Repository: 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
fi

Repository: 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:]]*\"" 
fi

Repository: 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
fi

Repository: 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.

Comment thread dto/values.go
Comment on lines +34 to 40
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

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.

🛠️ 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.
@feitianbubu
feitianbubu force-pushed the pr/add-ali-happyhorse1.0 branch from bcf4b0f to 431748e Compare July 14, 2026 04:01
@feitianbubu feitianbubu changed the title 增加阿里快乐马happyhorse1.0和wan2.7视频生成支持 feat(ali): support happyhorse 1.0/1.1 video generation models Jul 14, 2026
JacksonsY added a commit to JacksonsY/new-api that referenced this pull request Jul 19, 2026
移植上游 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>
JacksonsY added a commit to JacksonsY/new-api that referenced this pull request Jul 19, 2026
移植上游 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>
JacksonsY added a commit to JacksonsY/new-api that referenced this pull request Jul 19, 2026
上一批移植(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>
@Calcium-Ion
Calcium-Ion force-pushed the main branch 2 times, most recently from 51fdfc5 to 2b6f1df Compare August 30, 2026 15:03
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