feat(upstream): custom 模型按媒体分类配置参考文件上限

- ModelConfig 新增 max_reference_videos / max_reference_audios
  (配合既有 max_reference_images);admin 读写接口透传
- custom 模型表单去掉「参考模式」下拉,改为参考图/参考视频/
  参考音频三个数量输入;合计张数走 totalRefLimit 校验
- vivid 上游 /v1/uploads 按文件魔数自动选 kind(image/video/
  audio)与 mime;openai 协议上游遇视频/音频参考自动跳过换号
- videoCaps 让 custom 模型的分类上限在画图台/文档徽标生效
- reference_mode 请求级 override 放行 "media"

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
2026-10-14 14:58:34 +08:00
co-authored by Devin
parent babfc71f13
commit 1be1b00511
9 changed files with 236 additions and 57 deletions
@@ -141,6 +141,56 @@ func TestVividErrorCodes(t *testing.T) {
}
}
func TestVividRefKindMime(t *testing.T) {
cases := []struct {
name string
data []byte
kind string
}{
{"png", []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 1, 2, 3, 4}, "image"},
{"jpeg", []byte{0xFF, 0xD8, 0xFF, 0xE0, 1, 2, 3, 4, 5, 6}, "image"},
{"mp4", []byte{0, 0, 0, 0, 'f', 't', 'y', 'p', 'i', 's', 'o', 'm'}, "video"},
{"mov", []byte{0, 0, 0, 0, 'f', 't', 'y', 'p', 'q', 't', ' ', ' '}, "video"},
{"webm", []byte{0x1A, 0x45, 0xDF, 0xA3, 1, 2, 3, 4}, "video"},
{"mp3-id3", []byte{'I', 'D', '3', 4, 0, 0, 1, 2}, "audio"},
{"mp3-sync", []byte{0xFF, 0xFB, 1, 2, 3, 4}, "audio"},
{"wav", []byte{'R', 'I', 'F', 'F', 1, 2, 3, 4, 'W', 'A', 'V', 'E'}, "audio"},
{"ogg", []byte{'O', 'g', 'g', 'S', 1, 2, 3, 4}, "audio"},
{"m4a", []byte{0, 0, 0, 0, 'f', 't', 'y', 'p', 'M', '4', 'A', ' '}, "audio"},
{"empty", []byte{}, "image"},
}
for _, tc := range cases {
kind, mime := vividRefKindMime(tc.data)
if kind != tc.kind {
t.Errorf("%s: kind=%s mime=%s", tc.name, kind, mime)
}
}
}
func TestVividUploadRefKindVideo(t *testing.T) {
var gotKind, gotMime string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/v1/uploads" {
var req map[string]any
_ = json.NewDecoder(r.Body).Decode(&req)
gotKind, _ = req["kind"].(string)
gotMime, _ = req["mime"].(string)
fmt.Fprintf(w, `{"key":"k1","url":"http://%s/up"}`, r.Host)
return
}
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
webm := []byte{0x1A, 0x45, 0xDF, 0xA3, 1, 2, 3, 4, 5, 6, 7, 8}
key, err := NewClient().vividUploadRef(context.Background(), srv.URL, "vk", webm)
if err != nil {
t.Fatalf("upload: %v", err)
}
if key != "k1" || gotKind != "video" || gotMime != "video/webm" {
t.Fatalf("key=%q kind=%q mime=%q", key, gotKind, gotMime)
}
}
func TestVividUploadRefFlow(t *testing.T) {
var putOK bool
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {