feat: sync all features to image2api

This commit is contained in:
2026-08-15 23:00:30 +08:00
parent 5403acc4c9
commit 4fb7fc9204
19 changed files with 1301 additions and 50 deletions
@@ -279,6 +279,38 @@ func (h *ProviderAdminHandler) ImportAdobeCookie(c *gin.Context) {
})
}
func (h *ProviderAdminHandler) ImportCreativeFabricaCookie(c *gin.Context) {
var body struct {
Cookie string `json:"cookie"`
Value string `json:"value"`
Name string `json:"name"`
ID string `json:"id"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": "invalid request body"})
return
}
cookie := body.Cookie
if cookie == "" {
cookie = body.Value
}
name := body.Name
if name == "" {
name = body.ID
}
item, err := h.tokens.ImportCreativeFabricaCookie(c.Request.Context(), cookie, name)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"ok": true,
"id": item.ID,
"status": item.Status,
"pending": item.Status == "pending",
})
}
func (h *ProviderAdminHandler) TokenUpdate(c *gin.Context) {
var body map[string]any
if err := c.ShouldBindJSON(&body); err != nil {
@@ -392,7 +424,7 @@ func (h *ProviderAdminHandler) AccountsList(c *gin.Context) {
// accountsStats reproduces the 账号 KPI strip: per-type 正常/失效/限额 counts plus a
// grand total and total dead count (drives 「删除异常账号 (N)」).
func accountsStats(rows []map[string]any) gin.H {
types := []string{"openai", "adobe", "runway", "leonardo", "krea", "imagine", "grok"}
types := []string{"openai", "adobe", "runway", "leonardo", "krea", "imagine", "grok", "creativefabrica"}
by := map[string]*struct{ N, Ok, Dead, Quota int }{}
for _, t := range types {
by[t] = &struct{ N, Ok, Dead, Quota int }{}
@@ -439,7 +439,7 @@ func (h *UserGenerationHandler) VideoPresets(c *gin.Context) {
"resolutions": []string{"720p", "1080p"},
},
{
"key": "seedance-2.0-fast",
"key": "adobe-seedance-2.0-fast",
"label": "Seedance 2.0 Fast",
"type": "video",
"provider": "adobe",
@@ -450,7 +450,7 @@ func (h *UserGenerationHandler) VideoPresets(c *gin.Context) {
"reference_mode": "style",
},
{
"key": "seedance-2.0",
"key": "adobe-seedance-2.0",
"label": "Seedance 2.0",
"type": "video",
"provider": "adobe",
@@ -460,6 +460,36 @@ func (h *UserGenerationHandler) VideoPresets(c *gin.Context) {
"max_reference_images": 9,
"reference_mode": "style",
},
{
"key": "seedance-2.0-fast",
"label": "Seedance 2.0 Fast (Creative Fabrica)",
"type": "video",
"provider": "creativefabrica",
"durations": []string{"14s"},
"ratios": []string{"16:9", "9:16"},
"resolutions": []string{"720p"},
// Creative Fabrica 上游只有普通参考图(VIDEO_FRAME_TYPE_REFERENCE),
// 没有首尾帧,也不收视频/音频参考。
"max_reference_images": 9,
"reference_mode": "asset",
"max_videos": 0,
"max_audios": 0,
},
{
"key": "seedance-2.0",
"label": "Seedance 2.0 (Creative Fabrica)",
"type": "video",
"provider": "creativefabrica",
"durations": []string{"10s"},
"ratios": []string{"16:9", "9:16"},
"resolutions": []string{"720p"},
// Creative Fabrica 上游只有普通参考图(VIDEO_FRAME_TYPE_REFERENCE),
// 没有首尾帧,也不收视频/音频参考。
"max_reference_images": 9,
"reference_mode": "asset",
"max_videos": 0,
"max_audios": 0,
},
{
"key": "seedance-2.0-不卡人脸",
"label": "Seedance 2.0 (Leonardo 私有)",
@@ -675,7 +705,7 @@ func (h *UserGenerationHandler) catalogEntries(c *gin.Context) ([]gin.H, error)
"description": "Adobe Firefly Video",
},
{
"id": "seedance-2.0-fast",
"id": "adobe-seedance-2.0-fast",
"provider": "adobe",
"type": "video",
"ratios": []string{"16:9", "9:16"},
@@ -687,7 +717,7 @@ func (h *UserGenerationHandler) catalogEntries(c *gin.Context) ([]gin.H, error)
"description": "Seedance 2.0 Fast",
},
{
"id": "seedance-2.0",
"id": "adobe-seedance-2.0",
"provider": "adobe",
"type": "video",
"ratios": []string{"16:9", "9:16"},
@@ -698,6 +728,32 @@ func (h *UserGenerationHandler) catalogEntries(c *gin.Context) ([]gin.H, error)
"reference_mode": "style",
"description": "Seedance 2.0",
},
{
"id": "seedance-2.0-fast",
"provider": "creativefabrica",
"type": "video",
"ratios": []string{"16:9", "9:16"},
"resolutions": []string{"720p"},
// 一次性账号:积分刚好够一次生成,时长固定 14 秒。
"durations": []string{"14s"},
// Creative Fabrica 上游只有普通参考图,没有首尾帧,也不收视频/音频参考。
"max_reference_images": 9,
"reference_mode": "asset",
"description": "Seedance 2.0 Fast (Creative Fabrica)",
},
{
"id": "seedance-2.0",
"provider": "creativefabrica",
"type": "video",
"ratios": []string{"16:9", "9:16"},
"resolutions": []string{"720p"},
// 一次性账号:积分刚好够一次生成,时长固定 10 秒。
"durations": []string{"10s"},
// Creative Fabrica 上游只有普通参考图,没有首尾帧,也不收视频/音频参考。
"max_reference_images": 9,
"reference_mode": "asset",
"description": "Seedance 2.0 (Creative Fabrica)",
},
{
"id": "runway-gen4-turbo",
"provider": "runway",
@@ -932,7 +988,7 @@ func (h *UserGenerationHandler) publicModels() ([]gin.H, error) {
"stub": false,
},
{
"id": "seedance-2.0-fast",
"id": "adobe-seedance-2.0-fast",
"provider": "adobe",
"kind": "video",
"ratios": []string{"16:9", "9:16"},
@@ -941,7 +997,7 @@ func (h *UserGenerationHandler) publicModels() ([]gin.H, error) {
"stub": false,
},
{
"id": "seedance-2.0",
"id": "adobe-seedance-2.0",
"provider": "adobe",
"kind": "video",
"ratios": []string{"16:9", "9:16"},
@@ -949,6 +1005,24 @@ func (h *UserGenerationHandler) publicModels() ([]gin.H, error) {
"description": "Seedance 2.0",
"stub": false,
},
{
"id": "seedance-2.0-fast",
"provider": "creativefabrica",
"kind": "video",
"ratios": []string{"16:9", "9:16"},
"resolutions": []string{"720p"},
"description": "Seedance 2.0 Fast (Creative Fabrica)",
"stub": false,
},
{
"id": "seedance-2.0",
"provider": "creativefabrica",
"kind": "video",
"ratios": []string{"16:9", "9:16"},
"resolutions": []string{"720p"},
"description": "Seedance 2.0 (Creative Fabrica)",
"stub": false,
},
{
"id": "runway-gen4-turbo",
"provider": "runway",
+8 -3
View File
@@ -292,19 +292,24 @@ func rawToString(raw json.RawMessage) string {
}
// videoSizeToInternal maps OpenAI's "WxH" size to our aspect ratio + resolution
// tier (height ≥1080 → 1080p, else 720p).
// tier. An absent/unparsable size leaves the resolution empty so the caller can
// fall back to whatever tier the model actually prices — hardcoding 720p here
// rejects models that only offer 1440p.
func videoSizeToInternal(size string) (ratio, resolution string) {
var w, h int
if s := strings.TrimSpace(strings.ToLower(size)); s != "" {
_, _ = fmt.Sscanf(s, "%dx%d", &w, &h)
}
if w == 0 || h == 0 {
return "16:9", "720p"
return "16:9", ""
}
// The "p" resolution is the SHORT edge (720p = 1280×720, 1080p = 1920×1080),
// so a standard 1280×720 must read as 720p — not 1080p off the long edge.
resolution = "720p"
if min(w, h) >= 1080 {
switch {
case min(w, h) >= 1440:
resolution = "1440p"
case min(w, h) >= 1080:
resolution = "1080p"
}
return guessRatioWH(w, h), resolution