224 lines
7.9 KiB
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
|