Files
NianAIGC/backend/internal/providers/adapters.go

224 lines
7.9 KiB
Go

package providers
import (
"context"
"fmt"
"net/http"
"strings"
)
type EvoLink struct{ *httpAdapter }
func NewEvoLink(c Config, client HTTPClient) *EvoLink {
if client == nil {
client = http.DefaultClient
}
a := &httpAdapter{name: "evolink", config: c, client: client, submitPath: func(Request) string { return "/v1/images/generations" }, queryPath: func(id string) string { return "/v1/tasks/" + id }, payload: func(r Request) any {
payload := map[string]any{"model": requestModel(r, c.Model), "prompt": r.Prompt, "n": 1, "resolution": "1K"}
if len(r.InputURLs) > 0 {
payload["image_urls"] = r.InputURLs
}
if quality := strings.TrimSpace(stringValue(r.Settings["quality"])); quality != "" {
payload["quality"] = quality
}
if size := strings.TrimSpace(stringValue(r.Settings["size"])); size != "" {
payload["size"] = size
} else if width, widthOK := positiveInteger(r.Settings["width"]); widthOK {
if height, heightOK := positiveInteger(r.Settings["height"]); heightOK {
payload["size"] = supportedRatio(width, height)
}
}
return payload
}, decode: decodeEvoLink}
return &EvoLink{a}
}
func (a *EvoLink) Submit(c context.Context, r Request) (Result, error) { return a.submit(c, r) }
func (a *EvoLink) Query(c context.Context, id string) (Result, error) { return a.query(c, id) }
func decodeEvoLink(raw []byte) Result {
r := record(raw)
d := object(r["data"])
out := []string{}
for _, v := range []any{r["results"], d["results"], d["images"], d["image_urls"], d["output"], d["outputs"]} {
collectURLs(v, &out)
}
return Result{TaskID: stringValue(r["id"], r["task_id"], d["id"], d["task_id"]), Status: status(first(r["status"], d["status"])), OutputURLs: out}
}
type Bailian struct{ *httpAdapter }
func NewBailian(c Config, client HTTPClient) *Bailian {
if client == nil {
client = http.DefaultClient
}
a := &httpAdapter{name: "bailian", config: c, client: client, submitPath: func(r Request) string {
if r.Capability == "video.generate" {
return "/api/v1/services/aigc/video-generation/video-synthesis"
}
return "/api/v1/services/aigc/image-generation/generation"
}, queryPath: func(id string) string { return "/api/v1/tasks/" + id }, payload: func(r Request) any {
if r.Capability == "video.generate" {
media := make([]any, 0, len(r.InputURLs))
for index, inputURL := range r.InputURLs {
frameType := "first_frame"
if index > 0 {
frameType = "last_frame"
}
media = append(media, map[string]any{"type": frameType, "url": inputURL})
}
parameters := map[string]any{
"resolution": strings.ToUpper(stringValue(r.Settings["resolution"])),
"duration": r.Settings["duration"],
"prompt_extend": true,
"watermark": false,
}
if parameters["resolution"] == "" {
parameters["resolution"] = "720P"
}
if parameters["duration"] == nil {
parameters["duration"] = 10
}
return map[string]any{"model": requestModel(r, c.Model), "input": map[string]any{"prompt": r.Prompt, "media": media}, "parameters": parameters}
}
content := make([]any, 0, len(r.InputURLs)+1)
for _, inputURL := range r.InputURLs {
content = append(content, map[string]any{"image": inputURL})
}
content = append(content, map[string]any{"text": r.Prompt})
parameters := map[string]any{"size": "2K", "n": 1, "watermark": false}
if width, widthOK := positiveInteger(r.Settings["width"]); widthOK {
if height, heightOK := positiveInteger(r.Settings["height"]); heightOK {
parameters["size"] = fmt.Sprintf("%d*%d", width, height)
}
}
if len(r.InputURLs) == 0 {
parameters["thinking_mode"] = true
}
return map[string]any{"model": requestModel(r, c.Model), "input": map[string]any{"messages": []any{map[string]any{"role": "user", "content": content}}}, "parameters": parameters}
}, headers: func(r *http.Request) { r.Header.Set("X-DashScope-Async", "enable") }, decode: decodeBailian}
return &Bailian{a}
}
func (a *Bailian) Submit(c context.Context, r Request) (Result, error) { return a.submit(c, r) }
func (a *Bailian) Query(c context.Context, id string) (Result, error) { return a.query(c, id) }
func decodeBailian(raw []byte) Result {
r := record(raw)
o := object(r["output"])
out := []string{}
collectURLs(o["results"], &out)
if choices, ok := o["choices"].([]any); ok {
for _, choice := range choices {
message := object(object(choice)["message"])
if content, ok := message["content"].([]any); ok {
for _, item := range content {
collectURLs(object(item)["image"], &out)
}
}
}
}
collectURLs(o["video_url"], &out)
return Result{TaskID: stringValue(o["task_id"], r["task_id"]), Status: status(o["task_status"]), OutputURLs: out, ErrorMessage: stringValue(r["message"])}
}
type Seedance struct{ *httpAdapter }
func NewSeedance(c Config, client HTTPClient) *Seedance {
if client == nil {
client = http.DefaultClient
}
a := &httpAdapter{name: "seedance", config: c, client: client, submitPath: func(Request) string { return "/contents/generations/tasks" }, queryPath: func(id string) string { return "/contents/generations/tasks/" + id }, payload: func(r Request) any {
content := []any{map[string]any{"type": "text", "text": r.Prompt}}
materials := r.Materials
if len(materials) == 0 {
materials = make([]Material, 0, len(r.InputURLs))
for _, inputURL := range r.InputURLs {
materials = append(materials, Material{URL: inputURL, Type: MaterialImage})
}
}
for _, material := range materials {
materialType, urlKey, role := "image_url", "image_url", "reference_image"
switch material.Type {
case MaterialVideo:
materialType, urlKey, role = "video_url", "video_url", "reference_video"
case MaterialAudio:
materialType, urlKey, role = "audio_url", "audio_url", "reference_audio"
}
item := map[string]any{"type": materialType, urlKey: map[string]any{"url": material.URL}, "role": role}
if material.Label != "" {
item["label"] = material.Label
}
content = append(content, item)
}
p := map[string]any{"model": requestModel(r, c.Model), "content": content, "generate_audio": true, "watermark": false}
for k, v := range r.Settings {
p[k] = v
}
return p
}, decode: decodeSeedance}
return &Seedance{a}
}
func (a *Seedance) Submit(c context.Context, r Request) (Result, error) { return a.submit(c, r) }
func (a *Seedance) Query(c context.Context, id string) (Result, error) { return a.query(c, id) }
func decodeSeedance(raw []byte) Result {
r := record(raw)
d := object(r["data"])
content := object(r["content"])
if len(content) == 0 {
content = object(d["content"])
}
out := []string{}
for _, v := range []any{content, r["video_url"], r["url"], r["output"], d} {
collectURLs(v, &out)
}
usage := object(r["usage"])
if len(usage) == 0 {
usage = object(d["usage"])
}
u := map[string]int{}
if n, ok := usage["completion_tokens"].(float64); ok && n > 0 {
u["completionTokens"] = int(n)
}
return Result{TaskID: stringValue(r["id"], r["task_id"], d["id"], d["task_id"]), Status: status(first(r["status"], d["status"])), OutputURLs: out, ErrorMessage: stringValue(object(r["error"])["message"], object(d["error"])["message"]), Usage: u}
}
func first(values ...any) any {
for _, v := range values {
if v != nil {
return v
}
}
return nil
}
func positiveInteger(value any) (int, bool) {
switch number := value.(type) {
case int:
return number, number > 0
case int64:
return int(number), number > 0
case float64:
integer := int(number)
return integer, number > 0 && float64(integer) == number
default:
return 0, false
}
}
func supportedRatio(width, height int) string {
divisor := greatestCommonDivisor(width, height)
ratio := fmt.Sprintf("%d:%d", width/divisor, height/divisor)
switch ratio {
case "1:1", "1:2", "2:1", "1:3", "3:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "9:21", "21:9":
return ratio
default:
return fmt.Sprintf("%dx%d", width, height)
}
}
func greatestCommonDivisor(left, right int) int {
for right != 0 {
left, right = right, left%right
}
return left
}
var _ = fmt.Sprint