ai module for gojs

This commit is contained in:
Star 2024-10-31 15:20:14 +08:00
commit 205d635b7a
8 changed files with 887 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.*
!.gitignore
go.sum
env.yml
node_modules
package.json

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2024 apigo
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

78
README.md Normal file
View File

@ -0,0 +1,78 @@
# AI大模型低代码工具
### Sample
#### test.js
```javascript
import {zhipu} from 'apigo.cc/ai'
import console from 'apigo.cc/gojs/console'
function main(...args) {
let r = zhipu.fastAsk((args.length>0?args[0]:'你好', r => {
console.print(r)
})
console.println()
return r
}
```
### Configure
#### env.yml
```yaml
ai:
openai:
default:
apiKey: ...
aurze:
apiKey: ...
endpoint: ...
zhipu:
default:
apiKey: ...
```
#### encrypt apiKey
install sskey
```shell
go install github.com/ssgo/tool/sskey@latest
sskey -e 'your apiKey'
```
copy url base64 format encrypted apiKey into ai.yml or env.yml
## 将 [ai](https://apigo.cc/ai) 和 [低代码](https://apigo.cc/gojs) 集成到应用
### Install
```shell
go get -u apigo.cc/gojs
go get -u apigo.cc/ai
```
### Usage
```go
package main
import (
_ "apigo.cc/ai"
_ "apigo.cc/ai/zhipu"
"apigo.cc/gojs"
_ "apigo.cc/gojs/console"
)
func main() {
result, err := gojs.RunFile("test.js")
if err != nil {
fmt.Println(err.Error())
} else if result != nil {
fmt.Println(result)
}
}
```

339
agent.go Normal file
View File

@ -0,0 +1,339 @@
package ai
import (
"reflect"
"strings"
"sync"
"apigo.cc/gojs"
"apigo.cc/gojs/goja"
"github.com/ssgo/u"
)
type Agent struct {
ChatConfigs map[string]*ChatConfig
EmbeddingConfigs map[string]*EmbeddingConfig
ImageConfigs map[string]*ImageConfig
VideoConfigs map[string]*VideoConfig
Chat func(aiConf *AIConfig, messages []ChatMessage, callback func(string), chatConf ChatConfig) (ChatResult, error)
Embedding func(aiConf *AIConfig, text string, embeddingConf EmbeddingConfig) (EmbeddingResult, error)
MakeImage func(aiConf *AIConfig, imageConf ImageConfig) (ImageResult, error)
MakeVideo func(aiConf *AIConfig, videoConf VideoConfig) (string, error)
GetVideoResult func(aiConf *AIConfig, taskId string, waitSeconds int) (VideoResult, error)
}
type agentObj struct {
config *AIConfig
chatConfig *ChatConfig
embeddingConfig *EmbeddingConfig
imageConfig *ImageConfig
videoConfig *VideoConfig
agent *Agent
}
var agents = map[string]*Agent{}
var agentsLock = sync.RWMutex{}
func Register(aiName string, agent *Agent) {
if conf := aiList[aiName]; conf == nil {
agentsLock.Lock()
defer agentsLock.Unlock()
agents[aiName] = agent
}
}
func (ag *agentObj) Chat(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
msgs, conf, cb := ag.getAskArgs(args.This, vm, args.Arguments)
r, err := ag.agent.Chat(ag.config, msgs, cb, conf)
if err != nil {
panic(vm.NewGoError(err))
}
return vm.ToValue(gojs.MakeMap(r))
}
func (ag *agentObj) Embedding(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
conf := EmbeddingConfig{}
if confObj := args.Any(1); confObj != nil {
u.Convert(confObj, &conf)
}
if conf.Model == "" {
conf.Model = ag.embeddingConfig.Model
}
r, err := ag.agent.Embedding(ag.config, args.Str(0), conf)
if err != nil {
panic(vm.NewGoError(err))
}
return vm.ToValue(gojs.MakeMap(r))
}
func (ag *agentObj) MakeImage(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
conf := ag.getImageArgs(args.Arguments)
r, err := ag.agent.MakeImage(ag.config, conf)
if err != nil {
panic(vm.NewGoError(err))
}
return vm.ToValue(gojs.MakeMap(r))
}
func (ag *agentObj) MakeVideo(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
conf := ag.getVideoArgs(args.Arguments)
r, err := ag.agent.MakeVideo(ag.config, conf)
if err != nil {
panic(vm.NewGoError(err))
}
return vm.ToValue(r)
}
func (ag *agentObj) GetVideoResult(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
r, err := ag.agent.GetVideoResult(ag.config, args.Str(0), args.Int(1))
if err != nil {
panic(vm.NewGoError(err))
}
return vm.ToValue(gojs.MakeMap(r))
}
func (ag *agentObj) getAskArgs(thisArg goja.Value, vm *goja.Runtime, args []goja.Value) ([]ChatMessage, ChatConfig, func(string)) {
conf := ChatConfig{}
var callback func(answer string)
if len(args) > 0 {
for i := 1; i < len(args); i++ {
if cb, ok := goja.AssertFunction(args[i]); ok {
callback = func(answer string) {
_, _ = cb(thisArg, vm.ToValue(answer))
}
} else if args[i].ExportType() != nil {
switch args[i].ExportType().Kind() {
case reflect.Map, reflect.Struct:
u.Convert(args[i].Export(), &conf)
default:
conf.Model = u.String(args[i].Export())
}
}
}
}
if conf.Model == "" {
conf.Model = ag.chatConfig.Model
}
if conf.SystemPrompt == "" {
conf.SystemPrompt = ag.chatConfig.SystemPrompt
}
if conf.MaxTokens == 0 {
conf.MaxTokens = ag.chatConfig.MaxTokens
}
if conf.Temperature == 0 {
conf.Temperature = ag.chatConfig.Temperature
}
if conf.TopP == 0 {
conf.TopP = ag.chatConfig.TopP
}
if conf.Tools == nil {
conf.Tools = ag.chatConfig.Tools
}
return makeChatMessages(args), conf, callback
}
func (ag *agentObj) getImageArgs(args []goja.Value) ImageConfig {
conf := ImageConfig{}
u.Convert(args[0].Export(), &conf)
if conf.Model == "" {
conf.Model = ag.imageConfig.Model
}
if conf.SystemPrompt == "" {
conf.SystemPrompt = ag.imageConfig.SystemPrompt
}
if conf.NegativePrompt == "" {
conf.NegativePrompt = ag.imageConfig.NegativePrompt
}
if conf.Cref == 0 {
conf.Cref = ag.imageConfig.Cref
}
if conf.Sref == 0 {
conf.Sref = ag.imageConfig.Sref
}
if conf.Scale == 0 {
conf.Scale = ag.imageConfig.Scale
}
if conf.Steps == 0 {
conf.Steps = ag.imageConfig.Steps
}
if conf.Width == 0 {
conf.Width = ag.imageConfig.Width
}
if conf.Height == 0 {
conf.Height = ag.imageConfig.Height
}
if ag.imageConfig.Extra != nil {
extra := make(map[string]any)
for k, v := range ag.imageConfig.Extra {
extra[k] = v
}
if conf.Extra != nil {
for k, v := range conf.Extra {
extra[k] = v
}
}
conf.Extra = extra
}
return conf
}
func (ag *agentObj) getVideoArgs(args []goja.Value) VideoConfig {
conf := VideoConfig{}
u.Convert(args[0].Export(), &conf)
if conf.Model == "" {
conf.Model = ag.videoConfig.Model
}
if conf.SystemPrompt == "" {
conf.SystemPrompt = ag.videoConfig.SystemPrompt
}
if conf.NegativePrompt == "" {
conf.NegativePrompt = ag.videoConfig.NegativePrompt
}
if conf.Width == 0 {
conf.Width = ag.videoConfig.Width
}
if conf.Height == 0 {
conf.Height = ag.videoConfig.Height
}
if ag.imageConfig.Extra != nil {
extra := make(map[string]any)
for k, v := range ag.imageConfig.Extra {
extra[k] = v
}
if conf.Extra != nil {
for k, v := range conf.Extra {
extra[k] = v
}
}
conf.Extra = extra
}
return conf
}
func makeChatMessages(args []goja.Value) []ChatMessage {
out := make([]ChatMessage, 0)
if len(args) > 0 {
v := args[0].Export()
vv := reflect.ValueOf(v)
t := args[0].ExportType()
if t != nil {
lastRoleIsUser := false
switch t.Kind() {
// 数组,根据成员类型处理
// 字符串:
// 含有媒体:单条多模态消息
// 无媒体:多条文本消息
// 数组:多条消息(第一个成员不是 role 则自动生成)
// 对象:多条消息(无 role 则自动生成)(支持 content 或 contents
// 结构:转换为 ChatMessage
// 对象:单条消息(支持 content 或 contents
// 结构:转换为 ChatMessage
// 字符串:单条文本消息
case reflect.Slice:
hasSub := false
hasMulti := false
for i := 0; i < vv.Len(); i++ {
vv2 := u.FinalValue(vv.Index(i))
if vv2.Kind() == reflect.Slice || vv2.Kind() == reflect.Map || vv2.Kind() == reflect.Struct {
hasSub = true
break
}
if vv2.Kind() == reflect.String {
str := vv2.String()
if strings.HasPrefix(str, "data:") || strings.HasPrefix(str, "https://") || strings.HasPrefix(str, "http://") {
hasMulti = true
}
}
}
if hasSub || !hasMulti {
// 有子对象或纯文本数组
var defaultRole string
for i := 0; i < vv.Len(); i++ {
lastRoleIsUser = !lastRoleIsUser
if lastRoleIsUser {
defaultRole = RoleUser
} else {
defaultRole = RoleAssistant
}
vv2 := u.FinalValue(vv.Index(i))
switch vv2.Kind() {
case reflect.Slice:
out = append(out, makeChatMessageFromSlice(vv2, defaultRole))
case reflect.Map:
out = append(out, makeChatMessageFromSlice(vv2, defaultRole))
case reflect.Struct:
item := ChatMessage{}
u.Convert(vv2.Interface(), &item)
out = append(out, item)
default:
out = append(out, ChatMessage{Role: RoleUser, Contents: []ChatMessageContent{makeChatMessageContent(u.String(vv2.Interface()))}})
}
lastRoleIsUser = out[len(out)-1].Role != RoleUser
}
} else {
// 单条多模态消息
out = append(out, makeChatMessageFromSlice(vv, RoleUser))
}
case reflect.Map:
out = append(out, makeChatMessageFromMap(vv, RoleUser))
case reflect.Struct:
item := ChatMessage{}
u.Convert(v, &item)
out = append(out, item)
default:
out = append(out, ChatMessage{Role: RoleUser, Contents: []ChatMessageContent{makeChatMessageContent(u.String(v))}})
}
}
}
return out
}
func makeChatMessageFromSlice(vv reflect.Value, defaultRole string) ChatMessage {
role := u.String(vv.Index(0).Interface())
j := 0
if role == RoleUser || role == RoleAssistant || role == RoleSystem || role == RoleTool {
j = 1
} else {
role = defaultRole
}
contents := make([]ChatMessageContent, 0)
for ; j < vv.Len(); j++ {
contents = append(contents, makeChatMessageContent(u.String(vv.Index(j).Interface())))
}
return ChatMessage{Role: role, Contents: contents}
}
func makeChatMessageFromMap(vv reflect.Value, defaultRole string) ChatMessage {
role := u.String(vv.MapIndex(reflect.ValueOf(RoleUser)).Interface())
if role == "" {
role = defaultRole
}
contents := make([]ChatMessageContent, 0)
content := u.String(vv.MapIndex(reflect.ValueOf("content")).Interface())
if content != "" {
contents = append(contents, makeChatMessageContent(content))
} else {
contentsV := vv.MapIndex(reflect.ValueOf("contents"))
if contentsV.IsValid() && contentsV.Kind() == reflect.Slice {
for i := 0; i < contentsV.Len(); i++ {
contents = append(contents, makeChatMessageContent(u.String(contentsV.Index(i).Interface())))
}
}
}
return ChatMessage{Role: role, Contents: contents}
}
func makeChatMessageContent(contnet string) ChatMessageContent {
if strings.HasPrefix(contnet, "data:image/") || ((strings.HasPrefix(contnet, "https://") || strings.HasPrefix(contnet, "http://")) && (strings.HasSuffix(contnet, ".png") || strings.HasSuffix(contnet, ".jpg") || strings.HasSuffix(contnet, ".jpeg") || strings.HasSuffix(contnet, ".gif") || strings.HasSuffix(contnet, ".svg"))) {
return ChatMessageContent{Type: TypeImage, Content: contnet}
} else if strings.HasPrefix(contnet, "data:video/") || ((strings.HasPrefix(contnet, "https://") || strings.HasPrefix(contnet, "http://")) && (strings.HasSuffix(contnet, ".mp4") || strings.HasSuffix(contnet, ".mov") || strings.HasSuffix(contnet, ".m4v") || strings.HasSuffix(contnet, ".avi") || strings.HasSuffix(contnet, ".wmv"))) {
return ChatMessageContent{Type: TypeVideo, Content: contnet}
}
return ChatMessageContent{Type: TypeText, Content: contnet}
}

132
config.go Normal file
View File

@ -0,0 +1,132 @@
package ai
import (
"github.com/ssgo/u"
)
var confAes = u.NewAes([]byte("?GQ$0K0GgLdO=f+~L68PLm$uhKr4'=tV"), []byte("VFs7@sK61cj^f?HZ"))
var keysIsSet = false
func SetSSKey(key, iv []byte) {
if !keysIsSet {
confAes = u.NewAes(key, iv)
keysIsSet = true
}
}
const (
TypeText = "text"
TypeImage = "image"
TypeVideo = "video"
RoleSystem = "system"
RoleUser = "user"
RoleAssistant = "assistant"
RoleTool = "tool"
ToolFunction = "function"
ToolRetrieval = "retrieval"
ToolWebSearch = "webSearch"
ToolCodeInterpreter = "codeInterpreter"
ToolDrawingTool = "drawingTool"
ToolWebBrowser = "webBrowser"
)
type AIConfig struct {
ApiKey string
Endpoint string
Extra map[string]any
}
type AILoadConfig struct {
Agent string
ApiKey string
apiKey string
Endpoint string
Chat map[string]*ChatConfig
Embedding map[string]*EmbeddingConfig
Image map[string]*ImageConfig
Video map[string]*VideoConfig
Extra map[string]any
}
type ChatConfig struct {
Model string
MaxTokens int
Temperature float64
TopP float64
Tools map[string]any
SystemPrompt string
Extra map[string]any
}
type ChatMessage struct {
Role string
Contents []ChatMessageContent
}
type ChatMessageContent struct {
Type string // text, image, audio, video
Content string
}
type ChatResult struct {
Result string
AskTokens int64
AnswerTokens int64
TotalTokens int64
UsedTime int64
}
type EmbeddingConfig struct {
Model string
Extra map[string]any
}
type EmbeddingResult struct {
Result []byte
AskTokens int64
AnswerTokens int64
TotalTokens int64
UsedTime int64
}
type ImageConfig struct {
Prompt string
GenerateCount int
Model string
SystemPrompt string
NegativePrompt string
Style string
Quality string
Ref []string
Cref float32 // 角色参考权重 0~1
Sref float32 // 风格参考权重 0~1
Scale float32 // 影响文本描述的程度 0~1
Steps int // 采样步数 1~50
Width int // 图片宽度
Height int // 图片高度
Extra map[string]any
}
type ImageResult struct {
Results []string
UsedTime int64
}
type VideoConfig struct {
Prompt string
GenerateCount int
Model string
SystemPrompt string
NegativePrompt string
Ref []string
Width int
Height int
Extra map[string]any
}
type VideoResult struct {
Results []string
Previews []string
IsProcessing bool
UsedTime int64
}

92
export.ts Normal file
View File

@ -0,0 +1,92 @@
export default {
//----{{- range $aiName, $aiConf := .}}
//----{{$aiName}}: {
//---- {{- range $chatName, $chatConf := $aiConf.Chat}}
//---- {{$chatName}}(messages: any, callback?: (answer: string) => void, config?: ChatConfig): ChatResult { return null as any },
//---- {{- end }}
//---- {{- range $embeddingName, $embeddingConf := $aiConf.Embedding}}
//---- {{$embeddingName}}(messages: any, config?: EmbeddingConfig): EmbeddingResult { return null as any },
//---- {{- end }}
//---- {{- range $imageName, $imageConf := $aiConf.Image}}
//---- {{$imageName}}(config?: ImageConfig): ImageResult { return null as any },
//---- {{- end }}
//---- {{- range $videlName, $videlConf := $aiConf.Video}}
//---- {{$videlName}}(config?: VideoConfig): string { return '' },
//---- {{- end }}
//---- {{- if $aiConf.Video}}
//---- getVideoResult(taskId: string, waitSeconds?:number): VideoResult { return null as any },
//---- {{- end }}
//----},
//----{{- end }}
similarity
}
function similarity(a: any, b: any): number { return 0 }
interface ChatConfig {
model: string
maxTokens: number
temperature: number
topP: number
tools: Object
systemPrompt: string
}
interface ChatResult {
result: string
askTokens: number
answerTokens: number
totalTokens: number
usedTime: number
}
interface EmbeddingConfig {
model: string
}
interface EmbeddingResult {
result: any
askTokens: number
answerTokens: number
totalTokens: number
usedTime: number
}
interface ImageConfig {
prompt: string
generateCount: number
model: string
systemPrompt: string // 系统提示词
negativePrompt: string // 反向提示词
style: string // 风格
quality: string // 质量
ref: string[] // 参考图片
cref: number // 角色参考权重 0~1
sref: number // 风格参考权重 0~1
scale: number // 影响文本描述的程度 0~1
steps: number // 采样步数 1~50
width: number // 图片宽度
height: number // 图片高度
}
interface ImageResult {
results: string[]
usedTime: number
}
interface VideoConfig {
prompt: string
generateCount: number
model: string
systemPrompt: string
negativePrompt: string
ref: string[]
width: number
height: number
}
interface VideoResult {
results: string[]
previews: string[]
usedTime: number
}

22
go.mod Normal file
View File

@ -0,0 +1,22 @@
module apigo.cc/ai
go 1.18
require (
apigo.cc/gojs v0.0.4
github.com/ssgo/config v1.7.8
github.com/ssgo/u v1.7.9
)
require (
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
github.com/ssgo/log v1.7.7 // indirect
github.com/ssgo/standard v1.7.7 // indirect
github.com/ssgo/tool v0.4.27 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

209
gojs.go Normal file
View File

@ -0,0 +1,209 @@
package ai
import (
"bytes"
_ "embed"
"encoding/binary"
"html/template"
"math"
"strings"
"apigo.cc/gojs"
"apigo.cc/gojs/goja"
"github.com/ssgo/config"
)
//go:embed export.ts
var exportTS string
//go:embed README.md
var readmeMD string
func init() {
gojs.Register("apigo.cc/ai", gojs.Module{
ObjectMaker: makeJsObj,
TsCodeMaker: makeTsCode,
Desc: "ai plugin for gojs(http://apigo.cc/gojs)",
Example: readmeMD,
SetSSKey: SetSSKey,
})
}
var aiList map[string]*AILoadConfig
func makeAIList() {
if aiList == nil {
aiList = map[string]*AILoadConfig{}
_ = config.LoadConfig("ai", &aiList)
for aiName, aiConf := range aiList {
if aiConf.Agent == "" {
aiConf.Agent = aiName
}
if aiConf.ApiKey != "" {
aiConf.apiKey = confAes.DecryptUrlBase64ToString(aiConf.ApiKey)
aiConf.ApiKey = ""
}
if agent := agents[aiConf.Agent]; agent != nil {
if aiConf.Chat == nil {
aiConf.Chat = map[string]*ChatConfig{}
}
for confName, conf := range agent.ChatConfigs {
if aiConf.Chat[confName] == nil {
aiConf.Chat[confName] = conf
}
}
if aiConf.Embedding == nil {
aiConf.Embedding = map[string]*EmbeddingConfig{}
}
for confName, conf := range agent.EmbeddingConfigs {
if aiConf.Embedding[confName] == nil {
aiConf.Embedding[confName] = conf
}
}
if aiConf.Image == nil {
aiConf.Image = map[string]*ImageConfig{}
}
for confName, conf := range agent.ImageConfigs {
if aiConf.Image[confName] == nil {
aiConf.Image[confName] = conf
}
}
if aiConf.Video == nil {
aiConf.Video = map[string]*VideoConfig{}
}
for confName, conf := range agent.VideoConfigs {
if aiConf.Video[confName] == nil {
aiConf.Video[confName] = conf
}
}
}
}
}
}
func makeTsCode() string {
makeAIList()
var tpl *template.Template
var err error
aiTSCode := ""
if tpl, err = template.New("").Parse(strings.ReplaceAll(exportTS, "//----", "")); err == nil {
buf := bytes.NewBuffer(make([]byte, 0))
if err = tpl.Execute(buf, aiList); err == nil {
aiTSCode = buf.String()
} else {
println(err.Error())
}
} else {
println(err.Error())
}
return aiTSCode
}
var jsObj gojs.Map
func makeJsObj(vm *goja.Runtime) gojs.Map {
if jsObj == nil {
makeAIList()
jsObj = make(gojs.Map)
for aiName, aiLoadConf := range aiList {
agent := agents[aiLoadConf.Agent]
if agent == nil {
continue
}
aiConf := AIConfig{
ApiKey: aiLoadConf.apiKey,
Endpoint: aiLoadConf.Endpoint,
Extra: aiLoadConf.Extra,
}
aiObj := map[string]any{}
// 生成Chat方法
for confName, conf := range aiLoadConf.Chat {
chatObj := &agentObj{
config: &aiConf,
chatConfig: conf,
agent: agent,
}
aiObj[confName] = chatObj.Chat
}
// 生成Embedding方法
for confName, conf := range aiLoadConf.Embedding {
chatObj := &agentObj{
config: &aiConf,
embeddingConfig: conf,
}
aiObj[confName] = chatObj.Embedding
}
// 生成MakeImage方法
for confName, conf := range aiLoadConf.Image {
chatObj := &agentObj{
config: &aiConf,
imageConfig: conf,
agent: agent,
}
aiObj[confName] = chatObj.MakeImage
}
// 生成MakeVideo方法
for confName, conf := range aiLoadConf.Video {
chatObj := &agentObj{
config: &aiConf,
videoConfig: conf,
agent: agent,
}
aiObj[confName] = chatObj.MakeVideo
if aiObj["getVideoResult"] == nil {
aiObj["getVideoResult"] = chatObj.GetVideoResult
}
}
jsObj[aiName] = aiObj
}
// 生成similarity方法
jsObj["similarity"] = similarity
}
return jsObj
}
func similarity(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(2)
return vm.ToValue(makeSimilarity(args.Bytes(0), args.Bytes(1)))
}
func makeSimilarity(buf1, buf2 []byte) float64 {
a := bin2float64(buf1)
b := bin2float64(buf2)
if len(a) != len(b) {
return 0
}
var dotProduct, magnitudeA, magnitudeB float64
for i := 0; i < len(a); i++ {
dotProduct += a[i] * b[i]
magnitudeA += a[i] * a[i]
magnitudeB += b[i] * b[i]
}
magnitudeA = math.Sqrt(magnitudeA)
magnitudeB = math.Sqrt(magnitudeB)
if magnitudeA == 0 || magnitudeB == 0 {
return 0
}
return dotProduct / (magnitudeA * magnitudeB)
}
func bin2float64(in []byte) []float64 {
buf := bytes.NewBuffer(in)
out := make([]float64, len(in)/4)
for i := 0; i < len(out); i++ {
var f float32
_ = binary.Read(buf, binary.LittleEndian, &f)
out[i] = float64(f)
}
return out
}