From c19d4b0534f4bb201f11b0e72ada5b40aadb6cb3 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 10 Nov 2023 01:37:09 +0800 Subject: [PATCH] [add] moapi process and api --- engine/load.go | 8 +++ moapi/api.go | 27 ++++++++ moapi/moapi.go | 169 ++++++++++++++++++++++++++++++++++++++++++++++ moapi/process.go | 48 +++++++++++++ moapi/types.go | 34 ++++++++++ neo/neo.go | 7 +- openai/openai.go | 59 +++++++++++++--- service/static.go | 6 +- share/const.go | 15 ++++ share/types.go | 11 ++- 10 files changed, 370 insertions(+), 14 deletions(-) create mode 100644 moapi/api.go create mode 100644 moapi/moapi.go create mode 100644 moapi/process.go create mode 100644 moapi/types.go diff --git a/engine/load.go b/engine/load.go index 74639faa..134c3631 100644 --- a/engine/load.go +++ b/engine/load.go @@ -18,6 +18,7 @@ import ( "github.com/yaoapp/yao/fs" "github.com/yaoapp/yao/i18n" "github.com/yaoapp/yao/importer" + "github.com/yaoapp/yao/moapi" "github.com/yaoapp/yao/model" "github.com/yaoapp/yao/neo" "github.com/yaoapp/yao/pack" @@ -203,6 +204,12 @@ func Load(cfg config.Config) (err error) { printErr(cfg.Mode, "SUI", err) } + // Load Moapi + err = moapi.Load(cfg) + if err != nil { + printErr(cfg.Mode, "Moapi", err) + } + return nil } @@ -445,6 +452,7 @@ func loadApp(root string) error { var appData []byte var appFile string + // Read app setting if has, _ := application.App.Exists("app.yao"); has { appFile = "app.yao" diff --git a/moapi/api.go b/moapi/api.go new file mode 100644 index 00000000..a89bc7aa --- /dev/null +++ b/moapi/api.go @@ -0,0 +1,27 @@ +package moapi + +import "github.com/yaoapp/gou/api" + +var dsl = []byte(` +{ + "name": "Moapi API", + "description": "The API for Moapi", + "version": "1.0.0", + "guard": "bearer-jwt", + "group": "__moapi/v1", + "paths": [ + { + "path": "/images/generations", + "method": "POST", + "process": "moapi.images.Generations", + "in": ["$payload.model", "$payload.prompt", ":payload"], + "out": { "status": 200, "type": "application/json" } + } + ] +} +`) + +func registerAPI() error { + _, err := api.LoadSource(".yao", dsl, "moapi.v1") + return err +} diff --git a/moapi/moapi.go b/moapi/moapi.go new file mode 100644 index 00000000..d94fd916 --- /dev/null +++ b/moapi/moapi.go @@ -0,0 +1,169 @@ +package moapi + +// *** WARNING *** +// Temporarily: change after the moapi is open source +// + +import ( + "fmt" + + jsoniter "github.com/json-iterator/go" + "github.com/yaoapp/gou/http" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +// Mirrors list all the mirrors +var cacheMirrors = []*Mirror{} +var cacheApps = []*App{} +var cacheMirrorsMap = map[string]*Mirror{} + +// Models list all the models +var Models = []string{ + "gpt-4-1106-preview", + "gpt-4-1106-vision-preview", + "gpt-4", + "gpt-4-32k", + + "gpt-3.5-turbo", + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-instruct", + + "dall-e-3", + "dall-e-2", + + "tts-1", + "tts-1-hd", + + "text-moderation-latest", + "text-moderation-stable", + + "text-embedding-ada-002", + "whisper-1", +} + +// Load load the moapi +func Load(cfg config.Config) error { + return registerAPI() +} + +// Mirrors list all the mirrors +func Mirrors(cache bool) ([]*Mirror, error) { + if cache && len(cacheMirrors) > 0 { + return cacheMirrors, nil + } + + bytes, err := httpGet("/api/moapi/mirrors") + if err != nil { + return nil, err + } + + err = jsoniter.Unmarshal(bytes, &cacheMirrors) + if err != nil { + return nil, err + } + + for _, mirror := range cacheMirrors { + cacheMirrorsMap[mirror.Host] = mirror + } + + return cacheMirrors, nil +} + +// Apps list all the apps +func Apps(cache bool) ([]*App, error) { + if cache && len(cacheApps) > 0 { + return cacheApps, nil + } + + mirrors := SelectMirrors() + bytes, err := httpGet("/api/moapi/apps", mirrors...) + if err != nil { + return nil, err + } + + err = jsoniter.Unmarshal(bytes, &cacheApps) + if err != nil { + return nil, err + } + + channel := Channel() + if channel != "" { + for i := range cacheApps { + cacheApps[i].Homepage = cacheApps[i].Homepage + "?channel=" + channel + } + } + + return cacheApps, nil +} + +// Homepage get the home page url with the invite code +func Homepage() string { + channel := Channel() + if channel == "" { + return "https://store.moapi.ai" + } + return "https://store.moapi.ai" + "?channel=" + channel +} + +// Channel get the channel +func Channel() string { + + return share.App.Moapi.Channel +} + +// SelectMirrors select the mirrors +func SelectMirrors() []*Mirror { + + if share.App.Moapi.Mirrors == nil || len(share.App.Moapi.Mirrors) == 0 { + return []*Mirror{} + } + + _, err := Mirrors(true) + if err != nil { + return []*Mirror{} + } + + // pick the mirrors + var result []*Mirror + for _, host := range share.App.Moapi.Mirrors { + if mirror, ok := cacheMirrorsMap[host]; ok { + if mirror.Status == "on" { + result = append(result, mirror) + } + } + } + + return result +} + +// httpGet get the data from the api +func httpGet(api string, mirrors ...*Mirror) ([]byte, error) { + return httpGetRetry(api, mirrors, 0) +} + +func httpGetRetry(api string, mirrors []*Mirror, retryTimes int) ([]byte, error) { + + url := "https://" + share.MoapiHosts[retryTimes] + api + if len(mirrors) > retryTimes { + url = "https://" + mirrors[retryTimes].Host + api + } + + secret := share.App.Moapi.Secret + organization := share.App.Moapi.Organization + + http := http.New(url) + http.SetHeader("Authorization", "Bearer "+secret) + http.SetHeader("Content-Type", "application/json") + http.SetHeader("Moapi-Organization", organization) + + resp := http.Get() + if resp.Code >= 500 { + if retryTimes > 3 { + return nil, fmt.Errorf("Moapi Server Error: %s", resp.Data) + } + return httpGetRetry(api, mirrors, retryTimes+1) + } + + return jsoniter.Marshal(resp.Data) +} diff --git a/moapi/process.go b/moapi/process.go new file mode 100644 index 00000000..a5c70f62 --- /dev/null +++ b/moapi/process.go @@ -0,0 +1,48 @@ +package moapi + +import ( + "github.com/yaoapp/gou/process" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/kun/utils" + "github.com/yaoapp/yao/openai" +) + +func init() { + process.RegisterGroup("moapi", map[string]process.Handler{ + "images.generations": ImagesGenerations, + }) +} + +// ImagesGenerations Generate images +func ImagesGenerations(process *process.Process) interface{} { + + process.ValidateArgNums(2) + model := process.ArgsString(0) + prompt := process.ArgsString(1) + option := process.ArgsMap(2, map[string]interface{}{}) + + if model == "" { + exception.New("ImagesGenerations error: model is required", 400).Throw() + } + + if prompt == "" { + exception.New("ImagesGenerations error: prompt is required", 400).Throw() + } + + ai, err := openai.NewMoapi(model) + if err != nil { + exception.New("ImagesGenerations error: %s", 400, err).Throw() + } + + option["prompt"] = prompt + option["model"] = model + option["response_format"] = "url" + + res, ex := ai.ImagesGenerations(prompt, option) + if ex != nil { + utils.Dump(ex) + ex.Throw() + } + + return res +} diff --git a/moapi/types.go b/moapi/types.go new file mode 100644 index 00000000..8d0459f8 --- /dev/null +++ b/moapi/types.go @@ -0,0 +1,34 @@ +package moapi + +// Mirror is the mirror info +type Mirror struct { + Name string `json:"name"` + Host string `json:"host"` + Area string `json:"area"` // area code + Latency int `json:"latency"` // ms + Status string `json:"status"` // on, slow, off, +} + +// App is the app info +type App struct { + Name string `json:"name"` + UpdatedAt int64 `json:"updated_at"` + CreatedAt int64 `json:"created_at"` + Country string `json:"country"` + Creator string `json:"creator"` + Description string `json:"description"` + Version string `json:"version"` + Short string `json:"short"` + Icon string `json:"icon"` + Homepage string `json:"homepage"` + Images []string `json:"images,omitempty"` + Videos []string `json:"videos,omitempty"` + Stat AppStat `json:"stat,omitempty"` + Languages []string `json:"languages"` +} + +// AppStat is the app stat info +type AppStat struct { + Downloads int `json:"downloads"` + Stars int `json:"stars"` +} diff --git a/neo/neo.go b/neo/neo.go index fa504d84..0d546599 100644 --- a/neo/neo.go +++ b/neo/neo.go @@ -525,7 +525,12 @@ func (neo *DSL) getGuardHandlers() ([]gin.HandlerFunc, error) { func (neo *DSL) newAI() error { if neo.Connector == "" { - return fmt.Errorf("%s connector is required", neo.ID) + ai, err := openai.NewMoapi("gpt-3.5-turbo") + if err != nil { + return err + } + neo.AI = ai + return nil } conn, err := connector.Select(neo.Connector) diff --git a/openai/openai.go b/openai/openai.go index a1bb7edf..560ad9ac 100644 --- a/openai/openai.go +++ b/openai/openai.go @@ -4,11 +4,14 @@ import ( "context" "encoding/base64" "fmt" + "strings" "github.com/pkoukk/tiktoken-go" "github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/http" "github.com/yaoapp/kun/exception" + "github.com/yaoapp/kun/utils" + "github.com/yaoapp/yao/share" ) // Tiktoken get number of tokens @@ -23,10 +26,11 @@ func Tiktoken(model string, input string) (int, error) { // OpenAI struct type OpenAI struct { - key string - model string - host string - maxToken int + key string + model string + host string + organization string + maxToken int } // New create a new OpenAI instance by connector id @@ -42,10 +46,44 @@ func New(id string) (*OpenAI, error) { setting := c.Setting() return &OpenAI{ - key: setting["key"].(string), - model: setting["model"].(string), - host: setting["host"].(string), - maxToken: 2048, + key: setting["key"].(string), + model: setting["model"].(string), + host: setting["host"].(string), + organization: "", + maxToken: 2048, + }, nil +} + +// NewMoapi create a new OpenAI instance by model +// Temporarily: change after the moapi is open source +func NewMoapi(model string) (*OpenAI, error) { + + if model == "" { + model = "gpt-3.5-turbo" + } + + url := share.MoapiHosts[0] + + if share.App.Moapi.Mirrors != nil { + url = share.App.Moapi.Mirrors[0] + } + key := share.App.Moapi.Secret + organization := share.App.Moapi.Organization + + if !strings.HasPrefix(url, "http") { + url = "https://" + url + } + + if key == "" { + return nil, fmt.Errorf("The moapi secret is empty") + } + + return &OpenAI{ + key: key, + model: model, + host: url, + organization: organization, + maxToken: 16384, }, nil } @@ -163,6 +201,8 @@ func (openai OpenAI) ImagesGenerations(prompt string, option map[string]interfac } option["prompt"] = prompt + utils.Dump(option) + return openai.postWithoutModel("/v1/images/generations", option) } @@ -361,6 +401,9 @@ func (openai OpenAI) isError(res *http.Response) *exception.Exception { if res.Status != 200 { message := "OpenAI Error" + if v, ok := res.Data.(string); ok { + message = v + } if data, ok := res.Data.(map[string]interface{}); ok { if err, has := data["error"]; has { if err, ok := err.(map[string]interface{}); ok { diff --git a/service/static.go b/service/static.go index d275fd20..186044ca 100644 --- a/service/static.go +++ b/service/static.go @@ -51,15 +51,13 @@ func SetupStatic() error { // rewrite path func isPWA() bool { - if share.App.Static == nil { - return false - } + return share.App.Static.PWA } // rewrite path func spaApps() []string { - if share.App.Static == nil { + if share.App.Static.Apps == nil { return []string{} } return share.App.Static.Apps diff --git a/share/const.go b/share/const.go index 92124d76..64ca66f3 100644 --- a/share/const.go +++ b/share/const.go @@ -11,3 +11,18 @@ const BUILDIN = false // BUILDNAME 制品名称 const BUILDNAME = "yao" + +// MoapiHosts the master mirror +var MoapiHosts = []string{ + "master.moapi.ai", + "master-moon.moapi.ai", + "master-earth.moapi.ai", + "master-mars.moapi.ai", + "master-venus.moapi.ai", + "master-mercury.moapi.ai", + "master-jupiter.moapi.ai", + "master-saturn.moapi.ai", + "master-uranus.moapi.ai", + "master-neptune.moapi.ai", + "master-pluto.moapi.ai", +} diff --git a/share/types.go b/share/types.go index 9b8d9524..b0aaba2c 100644 --- a/share/types.go +++ b/share/types.go @@ -81,8 +81,17 @@ type AppInfo struct { Option map[string]interface{} `json:"option,omitempty"` XGen string `json:"xgen,omitempty"` AdminRoot string `json:"adminRoot,omitempty"` - Static *Static `json:"public,omitempty"` + Static Static `json:"public,omitempty"` Optional map[string]interface{} `json:"optional,omitempty"` + Moapi Moapi `json:"moapi"` +} + +// Moapi AIGC App Store API +type Moapi struct { + Channel string `json:"channel,omitempty"` + Mirrors []string `json:"mirrors,omitempty"` + Secret string `json:"secret,omitempty"` + Organization string `json:"organization,omitempty"` } // Static setting