fix(creation): 创作与模型配置 - 修复附件预览隐藏和 MiniMax 2K 能力校验 (#433)

This commit is contained in:
MasterChiefCN
2026-09-07 17:05:47 +08:00
committed by GitHub
parent d4f3a7e7d3
commit 469627c792
5 changed files with 86 additions and 6 deletions

View File

@@ -65,6 +65,41 @@ func TestMatchCapabilityTreatsVideoResolutionSuffixAsEquivalent(t *testing.T) {
}
}
func TestVideoResolutionAliasesAcrossProductAndPricedRoutes(t *testing.T) {
product := CapabilitySpec{
Version: 1, Capability: "video",
Options: map[string]OptionConstraint{"vquality": {Values: []any{"768P", "2K"}}},
}
route := capabilitySpecWithPriceTiers(product, model.ChannelModel{
PriceTiers: []model.ChannelModelPriceTier{
{Resolution: "768P", Enabled: true, PriceConfigured: true},
{Resolution: "2K", Enabled: true, PriceConfigured: true},
},
})
if err := validateProductSpecWithinRoutes(product, []CapabilitySpec{route}); err != nil {
t.Fatalf("2K product rejected against priced route: %v", err)
}
for _, resolution := range []string{"2K", "2k", "1440P", "1440"} {
intent := ModelRequestIntent{Capability: "video", Options: map[string]any{
"vquality": normalizeModelRequestOption("vquality", resolution),
}}
for name, spec := range map[string]CapabilitySpec{"product": product, "route": route} {
if match := MatchCapability(spec, intent); !match.Matched {
t.Errorf("%s rejected %s: %#v", name, resolution, match)
}
}
}
unsupported := CapabilitySpec{Version: 1, Capability: "video", Options: map[string]OptionConstraint{
"vquality": {Values: []any{"4K"}},
}}
if err := validateProductSpecWithinRoutes(unsupported, []CapabilitySpec{route}); err == nil {
t.Fatal("2K route accepted unsupported 4K product")
}
if capabilityOptionValuesEqual("quality", "2K", "1440p") {
t.Fatal("video aliases must not affect image quality")
}
}
func TestValidateProductSpecWithinRoutesRejectsUnsupportedCapabilityValue(t *testing.T) {
routes := []CapabilitySpec{
{

View File

@@ -358,8 +358,9 @@ func capabilityOptionValuesEqual(name string, candidate any, value any) bool {
left := normalizedScalar(candidate)
right := normalizedScalar(value)
if canonicalCapabilityOptionName(name) == "vquality" {
left = strings.TrimSuffix(left, "p")
right = strings.TrimSuffix(right, "p")
// Compare using the same aliases as request intents and price tiers.
left = strings.TrimSuffix(normalizedScalar(normalizeModelRequestOption(name, left)), "p")
right = strings.TrimSuffix(normalizedScalar(normalizeModelRequestOption(name, right)), "p")
}
return left == right
}

View File

@@ -2706,6 +2706,39 @@ func TestRunMiniMaxVideoTaskCreatesPollsAndDownloads(t *testing.T) {
}
}
func TestRunMiniMaxVideoTaskSendsOfficial2KResolution(t *testing.T) {
t.Setenv("CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS", "127.0.0.1")
for _, quality := range []string{"2K", "1440p"} {
t.Run(quality, func(t *testing.T) {
requested := false
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || r.URL.Path != "/v2/video_generation" {
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
}
requested = true
var body miniMaxVideoRequest
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Errorf("decode request: %v", err)
}
if body.Resolution != "2K" {
t.Errorf("resolution = %q, want official API value 2K", body.Resolution)
}
// End at submission: this test inspects the wire payload without polling.
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{}`))
}))
defer server.Close()
_, err := runMiniMaxVideoTask(context.Background(), canvasGenerationInput{
Mode: "video", Prompt: "make it move",
Config: providerConfig{BaseURL: server.URL, APIKey: "test-key", Model: "MiniMax-H3", VQuality: quality},
})
if !requested || err == nil || !strings.Contains(err.Error(), "没有返回任务 ID") {
t.Fatalf("expected submission without task ID, requested=%v err=%v", requested, err)
}
})
}
}
func TestRunMiniMaxVideoTaskUsesExplicitReferenceRoles(t *testing.T) {
t.Setenv("CANVAS_ALLOW_PRIVATE_UPSTREAMS", "true")
var server *httptest.Server

View File

@@ -5,6 +5,17 @@ description: 当前项目已实现功能的手工验证清单与未确认边界
# 待测试
## 创作页面附件缩略图
- 聊天消息与分镜简报附件的放大图标样式排除 `.cached-resource-image-shell`,避免缓存图片容器被误设为透明和绝对定位;悬停与键盘聚焦仍显示放大图标。
- [ ] 在真实创作会话中确认远端图片加载后无需悬停即可显示,移开鼠标后保持可见,点击可放大;同时确认分镜简报附件、本地图片和视频预览正常。
## MiniMax H3 分辨率能力与价格档
- 视频能力比较复用请求意图和价格档的分辨率别名归一规则,修复 MiniMax H3 供应线路配置 2K 价格档后,前台模型声明 2K 时误报「创作端参数超出供应线路能力:vquality」。内部价格档仍使用归一值,MiniMax 请求保持官方参数 `resolution: "2K"`。
- [x] 回归测试在修复前复现相同保存校验错误,修复后通过;相关能力匹配、线路覆盖和 MiniMax 专项测试通过。模拟 HTTP 请求验证 2K 与内部 1440p 均发送 2K,未声明的 4K 仍被拒绝,图片 quality 参数不受视频别名影响。
- [ ] 在实际 `/admin/channels` 配置 768P / 2K 价格档后,于 `/admin/models` 保存包含 2K 的模型能力,确认创作端可选并正确报价;未调用真实收费生成接口或部署服务。
## 管理后台系统性能
- 管理后台「系统配置」新增独立「系统性能」页面,集中展示主机与 Go 进程、数据目录所在磁盘、系统/进程内存、数据库连接池与 Schema、PostgreSQL 统计、Redis 运行状态和运行时缓存 key 数量;连接地址、账号、密码、DSN 与 Redis URL 不返回前端。

View File

@@ -11318,10 +11318,10 @@ html:not(.dark) .creation-user-message-reference { background: rgba(28, 32, 37,
.creation-user-message-attachments button > .cached-resource-image-shell { display: block; width: 100%; height: 100%; }
.creation-user-message-attachments button > .cached-resource-image-shell > img { display: block; width: 100%; height: 100%; object-fit: cover; }
.creation-user-message-attachments button:focus-visible { outline: 2px solid var(--creation-text); outline-offset: 2px; }
.creation-user-message-attachments button > span { position: absolute; right: 3px; bottom: 3px; display: grid; width: 17px; height: 17px; place-items: center; border-radius: 5px; background: rgba(0, 0, 0, .68); color: #fff; opacity: 0; transition: opacity 140ms ease; }
.creation-user-message-attachments button:hover > span,
.creation-user-message-attachments button:focus-visible > span { opacity: 1; }
.creation-user-message-attachments button > span svg { width: 10px; height: 10px; }
.creation-user-message-attachments button > span:not(.cached-resource-image-shell) { position: absolute; right: 3px; bottom: 3px; display: grid; width: 17px; height: 17px; place-items: center; border-radius: 5px; background: rgba(0, 0, 0, .68); color: #fff; opacity: 0; transition: opacity 140ms ease; }
.creation-user-message-attachments button:hover > span:not(.cached-resource-image-shell),
.creation-user-message-attachments button:focus-visible > span:not(.cached-resource-image-shell) { opacity: 1; }
.creation-user-message-attachments button > span:not(.cached-resource-image-shell) svg { width: 10px; height: 10px; }
.creation-assistant-message { max-width: 900px; margin: 0 0 48px; }
.creation-message-heading { gap: 7px; color: var(--creation-faint); font-size: 11px; }