diff --git a/ai.go b/ai.go index 5c26858..5ab0ffb 100644 --- a/ai.go +++ b/ai.go @@ -1,7 +1,7 @@ package ai import ( - "apigo.cc/ai/ai/llm" + "apigo.cc/ai/ai/interface/llm" _ "apigo.cc/ai/ai/llm/openai" _ "apigo.cc/ai/ai/llm/zhipu" "github.com/ssgo/config" diff --git a/ai/ai.go b/ai/ai.go index d436029..23498f6 100644 --- a/ai/ai.go +++ b/ai/ai.go @@ -2,9 +2,11 @@ package main import ( "apigo.cc/ai/ai" - "apigo.cc/ai/ai/ai/watcher" + "apigo.cc/ai/ai/watcher" "fmt" + _ "github.com/go-sql-driver/mysql" "github.com/ssgo/u" + _ "modernc.org/sqlite" "os" "os/signal" "path/filepath" diff --git a/go.mod b/go.mod index 5ae1d01..4e22d85 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,13 @@ require ( github.com/fsnotify/fsnotify v1.7.0 github.com/go-resty/resty/v2 v2.15.2 github.com/go-sourcemap/sourcemap v2.1.4+incompatible + github.com/go-sql-driver/mysql v1.5.0 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 github.com/sashabaranov/go-openai v1.30.3 github.com/ssgo/config v1.7.7 + github.com/ssgo/dao v0.1.1 + github.com/ssgo/db v1.7.8 github.com/ssgo/httpclient v1.7.7 github.com/ssgo/log v1.7.7 github.com/ssgo/u v1.7.7 @@ -22,12 +25,27 @@ require ( golang.org/x/text v0.18.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 + modernc.org/sqlite v1.33.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/kr/text v0.2.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.17 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/ssgo/standard v1.7.7 // indirect golang.org/x/sys v0.25.0 // indirect + modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect + modernc.org/libc v1.55.3 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/strutil v1.2.0 // indirect + modernc.org/token v1.1.0 // indirect ) diff --git a/llm/chat.go b/interface/llm/chat.go similarity index 80% rename from llm/chat.go rename to interface/llm/chat.go index 1bda649..386fb77 100644 --- a/llm/chat.go +++ b/interface/llm/chat.go @@ -1,5 +1,11 @@ package llm +import ( + "bytes" + "encoding/binary" + "math" +) + type ChatMessage struct { Role string Contents []ChatMessageContent @@ -59,10 +65,11 @@ func (chatConfig *ChatConfig) GetTools() map[string]any { return chatConfig.Tools } -type TokenUsage struct { +type Usage struct { AskTokens int64 AnswerTokens int64 TotalTokens int64 + UsedTime int64 } type MessagesMaker struct { @@ -143,3 +150,38 @@ func (m *MessagesMaker) Video(text string) *MessagesMaker { } return m } + +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 +} + +func Similarity(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) +} diff --git a/llm/gc.go b/interface/llm/gc.go similarity index 100% rename from llm/gc.go rename to interface/llm/gc.go diff --git a/llm/llm.go b/interface/llm/llm.go similarity index 77% rename from llm/llm.go rename to interface/llm/llm.go index bfc8f6a..d7a6b09 100644 --- a/llm/llm.go +++ b/interface/llm/llm.go @@ -35,21 +35,24 @@ type Config struct { type LLM interface { Support() Support - Ask(messages []ChatMessage, config ChatConfig, callback func(answer string)) (string, TokenUsage, error) - FastAsk(messages []ChatMessage, callback func(answer string)) (string, TokenUsage, error) - LongAsk(messages []ChatMessage, callback func(answer string)) (string, TokenUsage, error) - BatterAsk(messages []ChatMessage, callback func(answer string)) (string, TokenUsage, error) - BestAsk(messages []ChatMessage, callback func(answer string)) (string, TokenUsage, error) - MultiAsk(messages []ChatMessage, callback func(answer string)) (string, TokenUsage, error) - BestMultiAsk(messages []ChatMessage, callback func(answer string)) (string, TokenUsage, error) - CodeInterpreterAsk(messages []ChatMessage, callback func(answer string)) (string, TokenUsage, error) - WebSearchAsk(messages []ChatMessage, callback func(answer string)) (string, TokenUsage, error) - MakeImage(prompt string, config GCConfig) ([]string, error) - FastMakeImage(prompt string, config GCConfig) ([]string, error) - BestMakeImage(prompt string, config GCConfig) ([]string, error) - MakeVideo(prompt string, config GCConfig) ([]string, []string, error) - FastMakeVideo(prompt string, config GCConfig) ([]string, []string, error) - BestMakeVideo(prompt string, config GCConfig) ([]string, []string, error) + Ask(messages []ChatMessage, config ChatConfig, callback func(answer string)) (string, Usage, error) + FastAsk(messages []ChatMessage, callback func(answer string)) (string, Usage, error) + LongAsk(messages []ChatMessage, callback func(answer string)) (string, Usage, error) + BatterAsk(messages []ChatMessage, callback func(answer string)) (string, Usage, error) + BestAsk(messages []ChatMessage, callback func(answer string)) (string, Usage, error) + MultiAsk(messages []ChatMessage, callback func(answer string)) (string, Usage, error) + BestMultiAsk(messages []ChatMessage, callback func(answer string)) (string, Usage, error) + CodeInterpreterAsk(messages []ChatMessage, callback func(answer string)) (string, Usage, error) + WebSearchAsk(messages []ChatMessage, callback func(answer string)) (string, Usage, error) + MakeImage(prompt string, config GCConfig) ([]string, Usage, error) + FastMakeImage(prompt string, config GCConfig) ([]string, Usage, error) + BestMakeImage(prompt string, config GCConfig) ([]string, Usage, error) + MakeVideo(prompt string, config GCConfig) ([]string, []string, Usage, error) + FastMakeVideo(prompt string, config GCConfig) ([]string, []string, Usage, error) + BestMakeVideo(prompt string, config GCConfig) ([]string, []string, Usage, error) + Embedding(text string, model string) ([]byte, Usage, error) + FastEmbedding(text string) ([]byte, Usage, error) + BestEmbedding(text string) ([]byte, Usage, error) } var llmMakers = map[string]func(Config) LLM{} diff --git a/js.go b/js.go index fc0a884..8da1c69 100644 --- a/js.go +++ b/js.go @@ -3,18 +3,24 @@ package ai import ( "apigo.cc/ai/ai/goja" "apigo.cc/ai/ai/goja_nodejs/require" + "apigo.cc/ai/ai/interface/llm" "apigo.cc/ai/ai/js" - "apigo.cc/ai/ai/llm" + "apigo.cc/ai/ai/watcher" "bytes" _ "embed" "encoding/json" "errors" "fmt" + "github.com/ssgo/log" "github.com/ssgo/u" + "os" + "os/signal" "path/filepath" "regexp" "strings" + "syscall" "text/template" + "time" ) //go:embed js/lib/ai.ts @@ -32,6 +38,12 @@ var utilTS string //go:embed js/lib/http.ts var httpTS string +//go:embed js/lib/log.ts +var logTS string + +//go:embed js/lib/db.ts +var dbTS string + func RunFile(file string, args ...any) (any, error) { return Run(u.ReadFileN(file), file, args...) } @@ -98,14 +110,22 @@ func (rt *Runtime) requireMod(name string) error { err = rt.vm.Set("http", js.RequireHTTP()) } } + if err == nil && (name == "log" || name == "") { + if !rt.required["log"] { + rt.required["log"] = true + err = rt.vm.Set("log", js.RequireLog()) + } + } + if err == nil && (name == "db" || name == "") { + if !rt.required["db"] { + rt.required["db"] = true + err = rt.vm.Set("db", js.RequireDB()) + } + } if err == nil && (name == "ai" || name == "") { if !rt.required["ai"] { rt.required["ai"] = true - aiList := make(map[string]any) - for name, lm := range llm.List() { - aiList[name] = js.RequireAI(lm) - } - err = rt.vm.Set("ai", aiList) + err = rt.vm.Set("ai", js.RequireAI()) } } return err @@ -139,7 +159,9 @@ func (rt *Runtime) makeImport(matcher *regexp.Regexp, code string) (string, int, func New() *Runtime { vm := goja.New() - vm.GoData = map[string]any{} + vm.GoData = map[string]any{ + "logger": log.New(u.ShortUniqueId()), + } return &Runtime{ vm: vm, required: map[string]bool{}, @@ -216,6 +238,7 @@ func (rt *Runtime) StartFromCode(code, refFile string) (any, error) { } modCode, _, _ = rt.makeImport(importLibMatcher, modCode) modCode, _, _ = rt.makeImport(requireLibMatcher, modCode) + modCode = importModMatcher.ReplaceAllString(modCode, "let $1 = require('$2')") return []byte(modCode), modErr }).Enable(rt.vm) @@ -298,10 +321,108 @@ func ExportForDev() (string, error) { _ = u.WriteFile(filepath.Join("lib", "file.ts"), fileTS) _ = u.WriteFile(filepath.Join("lib", "util.ts"), utilTS) _ = u.WriteFile(filepath.Join("lib", "http.ts"), httpTS) + _ = u.WriteFile(filepath.Join("lib", "log.ts"), logTS) + _ = u.WriteFile(filepath.Join("lib", "db.ts"), dbTS) return `import {` + strings.Join(exports.LLMList, ", ") + `} from './lib/ai' import console from './lib/console' +import log from './lib/log' import util from './lib/util' import http from './lib/http' +import db from './lib/db' import file from './lib/file'`, nil } + +//func RunFile(file string, args ...any) (any, error) { +// return Run(u.ReadFileN(file), file, args...) +//} + +type WatchRunner struct { + w *watcher.Watcher +} + +func (wr *WatchRunner) WaitForKill() { + exitCh := make(chan os.Signal, 1) + closeCh := make(chan bool, 1) + signal.Notify(exitCh, os.Interrupt, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP) + go func() { + <-exitCh + closeCh <- true + }() + <-closeCh + wr.w.Stop() +} + +func (wr *WatchRunner) Stop() { + wr.w.Stop() +} + +func WatchRun(file string, extDirs, extTypes []string, args ...any) (*WatchRunner, error) { + wr := &WatchRunner{} + run := func() { + rt := New() + if wr.w != nil { + rt.SetModuleLoader(func(filename string) string { + filePath := filepath.Dir(filename) + needWatch := true + for _, v := range wr.w.WatchList() { + if v == filePath { + needWatch = false + break + } + } + if needWatch { + fmt.Println(u.BMagenta("[watching module path]"), filePath) + _ = wr.w.Add(filePath) + } + return u.ReadFileN(filename) + }) + } + _, err := rt.StartFromFile(file) + result, err := rt.RunMain(args...) + if err != nil { + fmt.Println(u.BRed(err.Error())) + fmt.Println(u.Red(" " + strings.Join(rt.GetCallStack(), "\n "))) + } else if result != nil { + fmt.Println(u.Cyan(u.JsonP(result))) + } + } + + var isWaitingRun = false + onChange := func(filename string, event string) { + if !isWaitingRun { + _, _ = os.Stdout.WriteString("\x1b[3;J\x1b[H\x1b[2J") + isWaitingRun = true + go func() { + time.Sleep(time.Millisecond * 10) + isWaitingRun = false + run() + }() + } + fmt.Println(u.BYellow("[changed]"), filename) + } + _, _ = os.Stdout.WriteString("\x1b[3;J\x1b[H\x1b[2J") + watchStartPath := filepath.Dir(file) + fmt.Println(u.BMagenta("[watching root path]"), watchStartPath) + watchDirs := []string{watchStartPath} + watchTypes := []string{"js", "json", "yml"} + if extDirs != nil { + for _, v := range extDirs { + watchDirs = append(watchDirs, v) + } + } + if extTypes != nil { + for _, v := range extTypes { + watchTypes = append(watchTypes, v) + } + } + if w, err := watcher.Start(watchDirs, watchTypes, onChange); err == nil { + wr.w = w + go func() { + run() + }() + return wr, nil + } else { + return nil, err + } +} diff --git a/js/ai.go b/js/ai.go index a6070e3..e383d12 100644 --- a/js/ai.go +++ b/js/ai.go @@ -2,121 +2,136 @@ package js import ( "apigo.cc/ai/ai/goja" - "apigo.cc/ai/ai/llm" + "apigo.cc/ai/ai/interface/llm" "github.com/ssgo/u" "reflect" "strings" ) -type ChatResult struct { - llm.TokenUsage - Result string - Error string +func RequireAI() map[string]any { + aiObj := map[string]any{ + "similarity": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(2) + return vm.ToValue(llm.Similarity(args.Bytes(0), args.Bytes(1))) + }, + } + for name, lm := range llm.List() { + aiObj[name] = RequireLLM(lm) + } + return aiObj } -type AIGCResult struct { - Result string - Preview string - Results []string - Previews []string - Error string -} - -func RequireAI(lm llm.LLM) map[string]any { +func RequireLLM(lm llm.LLM) map[string]any { return map[string]any{ "ask": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) conf, cb := getAskArgs(args.This, vm, args.Arguments) result, usage, err := lm.Ask(makeChatMessages(args.Arguments), conf, cb) - return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)}) + return makeChatResult(vm, result, &usage, err) }, "fastAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) _, cb := getAskArgs(args.This, vm, args.Arguments) result, usage, err := lm.FastAsk(makeChatMessages(args.Arguments), cb) - return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)}) + return makeChatResult(vm, result, &usage, err) }, "longAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) _, cb := getAskArgs(args.This, vm, args.Arguments) result, usage, err := lm.LongAsk(makeChatMessages(args.Arguments), cb) - return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)}) + return makeChatResult(vm, result, &usage, err) }, "batterAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) _, cb := getAskArgs(args.This, vm, args.Arguments) result, usage, err := lm.BatterAsk(makeChatMessages(args.Arguments), cb) - return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)}) + return makeChatResult(vm, result, &usage, err) }, "bestAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) _, cb := getAskArgs(args.This, vm, args.Arguments) result, usage, err := lm.BestAsk(makeChatMessages(args.Arguments), cb) - return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)}) + return makeChatResult(vm, result, &usage, err) }, "multiAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) _, cb := getAskArgs(args.This, vm, args.Arguments) result, usage, err := lm.MultiAsk(makeChatMessages(args.Arguments), cb) - return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)}) + return makeChatResult(vm, result, &usage, err) }, "bestMultiAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) _, cb := getAskArgs(args.This, vm, args.Arguments) result, usage, err := lm.BestMultiAsk(makeChatMessages(args.Arguments), cb) - return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)}) + return makeChatResult(vm, result, &usage, err) }, "codeInterpreterAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) _, cb := getAskArgs(args.This, vm, args.Arguments) result, usage, err := lm.CodeInterpreterAsk(makeChatMessages(args.Arguments), cb) - return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)}) + return makeChatResult(vm, result, &usage, err) }, "webSearchAsk": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) _, cb := getAskArgs(args.This, vm, args.Arguments) result, usage, err := lm.WebSearchAsk(makeChatMessages(args.Arguments), cb) - return vm.ToValue(map[string]any{"tokenUsage": toMap(usage), "result": result, "error": getErrorStr(err)}) + return makeChatResult(vm, result, &usage, err) }, "makeImage": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) - prompt, conf := getAIGCArgs(args.Arguments) - results, err := lm.MakeImage(prompt, conf) - return makeAIGCResult(vm, results, nil, err) + prompt, conf := getGCArgs(args.Arguments) + results, usage, err := lm.MakeImage(prompt, conf) + return makeGCResult(vm, results, nil, &usage, err) }, "fastMakeImage": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) - prompt, conf := getAIGCArgs(args.Arguments) - results, err := lm.FastMakeImage(prompt, conf) - return makeAIGCResult(vm, results, nil, err) + prompt, conf := getGCArgs(args.Arguments) + results, usage, err := lm.FastMakeImage(prompt, conf) + return makeGCResult(vm, results, nil, &usage, err) }, "bestMakeImage": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) - prompt, conf := getAIGCArgs(args.Arguments) - results, err := lm.BestMakeImage(prompt, conf) - return makeAIGCResult(vm, results, nil, err) + prompt, conf := getGCArgs(args.Arguments) + results, usage, err := lm.BestMakeImage(prompt, conf) + return makeGCResult(vm, results, nil, &usage, err) }, "makeVideo": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) - prompt, conf := getAIGCArgs(args.Arguments) - results, previews, err := lm.MakeVideo(prompt, conf) - return makeAIGCResult(vm, results, previews, err) + prompt, conf := getGCArgs(args.Arguments) + results, previews, usage, err := lm.MakeVideo(prompt, conf) + return makeGCResult(vm, results, previews, &usage, err) }, "fastMakeVideo": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) - prompt, conf := getAIGCArgs(args.Arguments) - results, previews, err := lm.FastMakeVideo(prompt, conf) - return makeAIGCResult(vm, results, previews, err) + prompt, conf := getGCArgs(args.Arguments) + results, previews, usage, err := lm.FastMakeVideo(prompt, conf) + return makeGCResult(vm, results, previews, &usage, err) }, "bestMakeVideo": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) - prompt, conf := getAIGCArgs(args.Arguments) - results, previews, err := lm.BestMakeVideo(prompt, conf) - return makeAIGCResult(vm, results, previews, err) + prompt, conf := getGCArgs(args.Arguments) + results, previews, usage, err := lm.BestMakeVideo(prompt, conf) + return makeGCResult(vm, results, previews, &usage, err) + }, + + "embedding": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(2) + results, usage, err := lm.Embedding(args.Str(0), args.Str(1)) + return makeEmbeddingResult(vm, results, &usage, err) + }, + "fastEmbedding": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + results, usage, err := lm.FastEmbedding(args.Str(0)) + return makeEmbeddingResult(vm, results, &usage, err) + }, + "bestEmbedding": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + results, usage, err := lm.BestEmbedding(args.Str(0)) + return makeEmbeddingResult(vm, results, &usage, err) }, "support": lm.Support(), @@ -130,7 +145,36 @@ func getErrorStr(err error) string { return "" } -func makeAIGCResult(vm *goja.Runtime, results []string, previews []string, err error) goja.Value { +func makeChatResult(vm *goja.Runtime, result string, usage *llm.Usage, err error) goja.Value { + if err != nil { + panic(vm.NewGoError(err)) + } + return vm.ToValue(map[string]any{ + "result": result, + "askTokens": usage.AskTokens, + "answerTokens": usage.AnswerTokens, + "totalTokens": usage.TotalTokens, + "usedTime": usage.UsedTime, + }) +} + +func makeEmbeddingResult(vm *goja.Runtime, result []byte, usage *llm.Usage, err error) goja.Value { + if err != nil { + panic(vm.NewGoError(err)) + } + return vm.ToValue(map[string]any{ + "result": result, + "askTokens": usage.AskTokens, + "answerTokens": usage.AnswerTokens, + "totalTokens": usage.TotalTokens, + "usedTime": usage.UsedTime, + }) +} + +func makeGCResult(vm *goja.Runtime, results []string, previews []string, usage *llm.Usage, err error) goja.Value { + if err != nil { + panic(vm.NewGoError(err)) + } result := "" preview := "" if len(results) > 0 { @@ -148,11 +192,11 @@ func makeAIGCResult(vm *goja.Runtime, results []string, previews []string, err e "preview": preview, "results": results, "previews": previews, - "error": getErrorStr(err), + "usedTime": usage.UsedTime, }) } -func getAIGCArgs(args []goja.Value) (string, llm.GCConfig) { +func getGCArgs(args []goja.Value) (string, llm.GCConfig) { prompt := "" var config llm.GCConfig if len(args) > 0 { @@ -173,7 +217,7 @@ func getAskArgs(thisArg goja.Value, vm *goja.Runtime, args []goja.Value) (llm.Ch callback = func(answer string) { _, _ = cb(thisArg, vm.ToValue(answer)) } - } else { + } else if args[i].ExportType() != nil { switch args[i].ExportType().Kind() { case reflect.Map, reflect.Struct: u.Convert(args[i].Export(), &chatConfig) @@ -192,72 +236,74 @@ func makeChatMessages(args []goja.Value) []llm.ChatMessage { v := args[0].Export() vv := reflect.ValueOf(v) t := args[0].ExportType() - lastRoleIsUser := false - switch t.Kind() { - // 数组,根据成员类型处理 - // 字符串: - // 含有媒体:单条多模态消息 - // 无媒体:多条文本消息 - // 数组:多条消息(第一个成员不是 role 则自动生成) - // 对象:多条消息(无 role 则自动生成)(支持 content 或 contents) - // 结构:转换为 llm.ChatMessage - // 对象:单条消息(支持 content 或 contents) - // 结构:转换为 llm.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 + if t != nil { + lastRoleIsUser := false + switch t.Kind() { + // 数组,根据成员类型处理 + // 字符串: + // 含有媒体:单条多模态消息 + // 无媒体:多条文本消息 + // 数组:多条消息(第一个成员不是 role 则自动生成) + // 对象:多条消息(无 role 则自动生成)(支持 content 或 contents) + // 结构:转换为 llm.ChatMessage + // 对象:单条消息(支持 content 或 contents) + // 结构:转换为 llm.ChatMessage + // 字符串:单条文本消息 + case reflect.Slice: + hasSub := false + hasMulti := false for i := 0; i < vv.Len(); i++ { - lastRoleIsUser = !lastRoleIsUser - if lastRoleIsUser { - defaultRole = llm.RoleUser - } else { - defaultRole = llm.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 := llm.ChatMessage{} - u.Convert(vv2.Interface(), &item) - out = append(out, item) - default: - out = append(out, llm.ChatMessage{Role: llm.RoleUser, Contents: []llm.ChatMessageContent{makeChatMessageContent(u.String(vv2.Interface()))}}) + 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 + } } - lastRoleIsUser = out[len(out)-1].Role != llm.RoleUser } - } else { - // 单条多模态消息 - out = append(out, makeChatMessageFromSlice(vv, llm.RoleUser)) + + if hasSub || !hasMulti { + // 有子对象或纯文本数组 + var defaultRole string + for i := 0; i < vv.Len(); i++ { + lastRoleIsUser = !lastRoleIsUser + if lastRoleIsUser { + defaultRole = llm.RoleUser + } else { + defaultRole = llm.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 := llm.ChatMessage{} + u.Convert(vv2.Interface(), &item) + out = append(out, item) + default: + out = append(out, llm.ChatMessage{Role: llm.RoleUser, Contents: []llm.ChatMessageContent{makeChatMessageContent(u.String(vv2.Interface()))}}) + } + lastRoleIsUser = out[len(out)-1].Role != llm.RoleUser + } + } else { + // 单条多模态消息 + out = append(out, makeChatMessageFromSlice(vv, llm.RoleUser)) + } + case reflect.Map: + out = append(out, makeChatMessageFromMap(vv, llm.RoleUser)) + case reflect.Struct: + item := llm.ChatMessage{} + u.Convert(v, &item) + out = append(out, item) + default: + out = append(out, llm.ChatMessage{Role: llm.RoleUser, Contents: []llm.ChatMessageContent{makeChatMessageContent(u.String(v))}}) } - case reflect.Map: - out = append(out, makeChatMessageFromMap(vv, llm.RoleUser)) - case reflect.Struct: - item := llm.ChatMessage{} - u.Convert(v, &item) - out = append(out, item) - default: - out = append(out, llm.ChatMessage{Role: llm.RoleUser, Contents: []llm.ChatMessageContent{makeChatMessageContent(u.String(v))}}) } } return out diff --git a/js/common.go b/js/common.go index 56487ba..bdd88b9 100644 --- a/js/common.go +++ b/js/common.go @@ -7,9 +7,9 @@ import ( "path/filepath" ) -func toMap(data any) map[string]any { - return u.UnJsonMap(u.FixedJson(data)) -} +//func toMap(data any) map[string]any { +// return u.UnJsonMap(u.FixedJson(data)) +//} type Args struct { This goja.Value @@ -39,6 +39,13 @@ func (args *Args) Int(index int) int { return 0 } +func (args *Args) Int64(index int) int64 { + if len(args.Arguments) > index { + return u.Int64(args.Arguments[index].Export()) + } + return 0 +} + func (args *Args) Any(index int) any { if len(args.Arguments) > index { return args.Arguments[index].Export() @@ -53,6 +60,20 @@ func (args *Args) Str(index int) string { return "" } +func (args *Args) Bytes(index int) []byte { + if len(args.Arguments) > index { + return u.Bytes(args.Arguments[index].Export()) + } + return []byte{} +} + +func (args *Args) Bool(index int) bool { + if len(args.Arguments) > index { + return u.Bool(args.Arguments[index].Export()) + } + return false +} + func (args *Args) Map(index int) map[string]any { out := map[string]any{} if len(args.Arguments) > index { @@ -72,6 +93,17 @@ func (args *Args) StrArr(startIndex int) []string { return make([]string, 0) } +func (args *Args) Arr(startIndex int) []any { + if len(args.Arguments) > startIndex { + a := make([]any, len(args.Arguments)-startIndex) + for index := startIndex; index < len(args.Arguments); index++ { + a[index-startIndex] = u.String(args.Arguments[index].Export()) + } + return a + } + return make([]any, 0) +} + func (args *Args) Map2StrArr(index int) []string { headerMap := args.Map(index) headers := make([]string, len(headerMap)*2) @@ -84,6 +116,18 @@ func (args *Args) Map2StrArr(index int) []string { return headers } +func (args *Args) Map2Arr(index int) []any { + arrMap := args.Map(index) + arr := make([]any, len(arrMap)*2) + i := 0 + for k, v := range arrMap { + arr[i] = k + arr[i+1] = u.String(v) + i += 2 + } + return arr +} + func findPath(vm *goja.Runtime, filename string) string { if u.FileExists(filename) { return filename @@ -99,3 +143,7 @@ func findPath(vm *goja.Runtime, filename string) string { } return filename } + +func (args *Args) Path(index int) string { + return findPath(args.VM, args.Str(index)) +} diff --git a/js/console.go b/js/console.go index 893ec64..4f9c8a9 100644 --- a/js/console.go +++ b/js/console.go @@ -4,6 +4,7 @@ import ( "apigo.cc/ai/ai/goja" "fmt" "github.com/ssgo/u" + "reflect" "strings" ) @@ -43,19 +44,106 @@ func RequireConsole() map[string]any { _, _ = fmt.Scanln(&line) return vm.ToValue(line) }, + "black": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.Black(fmt.Sprint(args.Arr(0)...))) + }, + "red": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.Red(fmt.Sprint(args.Arr(0)...))) + }, + "green": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.Green(fmt.Sprint(args.Arr(0)...))) + }, + "yellow": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.Yellow(fmt.Sprint(args.Arr(0)...))) + }, + "blue": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.Blue(fmt.Sprint(args.Arr(0)...))) + }, + "magenta": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.Magenta(fmt.Sprint(args.Arr(0)...))) + }, + "cyan": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.Cyan(fmt.Sprint(args.Arr(0)...))) + }, + "white": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.White(fmt.Sprint(args.Arr(0)...))) + }, + "dim": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.Dim(fmt.Sprint(args.Arr(0)...))) + }, + "italic": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.Italic(fmt.Sprint(args.Arr(0)...))) + }, + "bBlack": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.BBlack(fmt.Sprint(args.Arr(0)...))) + }, + "bRed": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.BRed(fmt.Sprint(args.Arr(0)...))) + }, + "bGreen": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.BGreen(fmt.Sprint(args.Arr(0)...))) + }, + "bYellow": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.BYellow(fmt.Sprint(args.Arr(0)...))) + }, + "bBlue": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.BBlue(fmt.Sprint(args.Arr(0)...))) + }, + "bMagenta": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.BMagenta(fmt.Sprint(args.Arr(0)...))) + }, + "bCyan": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.BCyan(fmt.Sprint(args.Arr(0)...))) + }, + "bWhite": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm) + return vm.ToValue(u.BWhite(fmt.Sprint(args.Arr(0)...))) + }, } } func consolePrint(args goja.FunctionCall, typ string, vm *goja.Runtime) { arr := make([]any, len(args.Arguments)) textColor := u.TextNone + bgColor := u.BgNone switch typ { case "info": textColor = u.TextCyan + bgColor = u.BgCyan case "warn": textColor = u.TextYellow + bgColor = u.BgYellow case "error": textColor = u.TextRed + bgColor = u.BgRed + } + + if len(args.Arguments) == 1 && args.Argument(0).ExportType().Kind() == reflect.Map { + ex := args.Argument(0).ToObject(vm) + message := ex.Get("message") + stack := ex.Get("stack") + if message != nil && stack != nil { + fmt.Println(u.Color(u.String(message.Export()), textColor, u.BgNone)) + fmt.Println(u.Color(u.String(stack.Export()), u.TextWhite, bgColor)) + return + } } for i, arg := range args.Arguments { @@ -69,7 +157,7 @@ func consolePrint(args goja.FunctionCall, typ string, vm *goja.Runtime) { if (typ == "warn" || typ == "error" || typ == "debug") && vm != nil { callStacks := make([]string, 0) for _, stack := range vm.CaptureCallStack(0, nil) { - callStacks = append(callStacks, u.Color(" "+stack.Position().String(), textColor, u.BgNone)) + callStacks = append(callStacks, u.Color(" "+stack.Position().String(), u.TextWhite, bgColor)) } fmt.Println(arr...) fmt.Println(strings.Join(callStacks, "\n")) diff --git a/js/db.go b/js/db.go new file mode 100644 index 0000000..f4a0192 --- /dev/null +++ b/js/db.go @@ -0,0 +1,214 @@ +package js + +import ( + "apigo.cc/ai/ai/goja" + "github.com/ssgo/dao/dao" + "github.com/ssgo/db" + "github.com/ssgo/log" + "github.com/ssgo/u" +) + +func RequireDB() map[string]any { + return map[string]any{ + "get": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + conn := db.GetDB(args.Str(0), getLogger(vm)) + return vm.ToValue(makeDBObject(conn, nil, vm)) + }, + } +} + +func makeDBObject(conn *db.DB, tx *db.Tx, vm *goja.Runtime) map[string]any { + obj := map[string]any{ + "conn": conn, + "tx": tx, + "query": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args, conn, tx, _ := initDBArgs(argsIn, vm, 1) + var r *db.QueryResult + if tx != nil { + r = tx.Query(args.Str(0), args.Arr(1)...) + } else { + r = conn.Query(args.Str(0), args.Arr(1)...) + } + if r.Error == nil { + return vm.ToValue(makeQueryResult(r, r.MapResults())) + } else { + panic(vm.NewGoError(r.Error)) + } + }, + "query1": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args, conn, tx, _ := initDBArgs(argsIn, vm, 1) + var r *db.QueryResult + if tx != nil { + r = tx.Query(args.Str(0), args.Arr(1)...) + } else { + r = conn.Query(args.Str(0), args.Arr(1)...) + } + if r.Error == nil { + return vm.ToValue(makeQueryResult(r, r.MapOnR1())) + } else { + panic(vm.NewGoError(r.Error)) + } + }, + "query11": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args, conn, tx, _ := initDBArgs(argsIn, vm, 1) + var r *db.QueryResult + if tx != nil { + r = tx.Query(args.Str(0), args.Arr(1)...) + } else { + r = conn.Query(args.Str(0), args.Arr(1)...) + } + if r.Error == nil { + a := r.SliceResults() + if len(a) > 0 && len(a[0]) > 0 { + return vm.ToValue(makeQueryResult(r, a[0][0])) + } + return vm.ToValue(nil) + } else { + panic(vm.NewGoError(r.Error)) + } + }, + "exec": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args, conn, tx, _ := initDBArgs(argsIn, vm, 1) + var r *db.ExecResult + if tx != nil { + r = tx.Exec(args.Str(0), args.Arr(1)...) + } else { + r = conn.Exec(args.Str(0), args.Arr(1)...) + } + if r.Error == nil { + return vm.ToValue(makeExecResult(r)) + } else { + panic(vm.NewGoError(r.Error)) + } + }, + "insert": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args, conn, tx, _ := initDBArgs(argsIn, vm, 2) + var r *db.ExecResult + if tx != nil { + r = tx.Insert(args.Str(0), args.Any(1)) + } else { + r = conn.Insert(args.Str(0), args.Any(1)) + } + if r.Error == nil { + return vm.ToValue(makeExecResult(r)) + } else { + panic(vm.NewGoError(r.Error)) + } + }, + "replace": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args, conn, tx, _ := initDBArgs(argsIn, vm, 2) + var r *db.ExecResult + if tx != nil { + r = tx.Replace(args.Str(0), args.Any(1)) + } else { + r = conn.Replace(args.Str(0), args.Any(1)) + } + if r.Error == nil { + return vm.ToValue(makeExecResult(r)) + } else { + panic(vm.NewGoError(r.Error)) + } + }, + "update": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args, conn, tx, _ := initDBArgs(argsIn, vm, 3) + var r *db.ExecResult + if tx != nil { + r = tx.Update(args.Str(0), args.Any(1), args.Str(2), args.Arr(3)...) + } else { + r = conn.Update(args.Str(0), args.Any(1), args.Str(2), args.Arr(3)...) + } + if r.Error == nil { + return vm.ToValue(makeExecResult(r)) + } else { + panic(vm.NewGoError(r.Error)) + } + }, + "delete": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args, conn, tx, _ := initDBArgs(argsIn, vm, 2) + var r *db.ExecResult + if tx != nil { + r = tx.Delete(args.Str(0), args.Str(1), args.Arr(2)...) + } else { + r = conn.Delete(args.Str(0), args.Str(1), args.Arr(2)...) + } + if r.Error == nil { + return vm.ToValue(makeExecResult(r)) + } else { + panic(vm.NewGoError(r.Error)) + } + }, + } + + if conn != nil { + obj["make"] = func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args, conn, _, logger := initDBArgs(argsIn, vm, 1) + arg0 := args.Str(0) + tryFile := findPath(vm, arg0) + if u.FileExists(tryFile) { + arg0 = u.ReadFileN(tryFile) + } + if err := dao.MakeDBFromDesc(conn, args.Str(0), logger); err == nil { + return nil + } else { + panic(vm.NewGoError(err)) + } + } + + obj["destroy"] = func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + _, conn, _, _ := initDBArgs(argsIn, vm, 0) + if err := conn.Destroy(); err == nil { + return nil + } else { + panic(vm.NewGoError(err)) + } + } + + obj["begin"] = func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + _, conn, _, _ := initDBArgs(argsIn, vm, 0) + return vm.ToValue(makeDBObject(nil, conn.Begin(), vm)) + } + } + + if tx != nil { + obj["end"] = func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args, _, tx, _ := initDBArgs(argsIn, vm, 1) + if err := tx.Finish(args.Bool(0)); err != nil { + panic(vm.NewGoError(err)) + } + return nil + } + } + return obj +} + +func makeQueryResult(r *db.QueryResult, result any) map[string]any { + return map[string]any{ + "sql": *r.Sql, + "args": r.Args, + "result": result, + } +} + +func makeExecResult(r *db.ExecResult) map[string]any { + return map[string]any{ + "sql": *r.Sql, + "args": r.Args, + "id": r.Id(), + "changes": r.Changes(), + } +} + +func initDBArgs(argsIn goja.FunctionCall, vm *goja.Runtime, checkArgsNum int) (*Args, *db.DB, *db.Tx, *log.Logger) { + args := MakeArgs(&argsIn, vm).Check(checkArgsNum) + logger := getLogger(vm) + conn, _ := args.This.ToObject(vm).Get("conn").Export().(*db.DB) + //if !connOk { + // panic(vm.NewGoError(errors.New("this is not a db object"))) + //} + var tx *db.Tx + if conn == nil { + tx, _ = args.This.ToObject(vm).Get("tx").Export().(*db.Tx) + } + return args, conn, tx, logger +} diff --git a/js/file.go b/js/file.go index e754680..066f528 100644 --- a/js/file.go +++ b/js/file.go @@ -3,13 +3,14 @@ package js import ( "apigo.cc/ai/ai/goja" "github.com/ssgo/u" + "os" ) func RequireFile() map[string]any { return map[string]any{ "read": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) - if r, err := u.ReadFile(findPath(vm, args.Str(0))); err == nil { + if r, err := u.ReadFile(args.Path(0)); err == nil { return vm.ToValue(r) } else { panic(vm.NewGoError(err)) @@ -17,7 +18,7 @@ func RequireFile() map[string]any { }, "write": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(2) - if err := u.WriteFileBytes(findPath(vm, args.Str(0)), u.Bytes(args.Any(0))); err == nil { + if err := u.WriteFileBytes(args.Path(0), u.Bytes(args.Any(0))); err == nil { return nil } else { panic(vm.NewGoError(err)) @@ -25,19 +26,49 @@ func RequireFile() map[string]any { }, "dir": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) - if r, err := u.ReadDir(findPath(vm, args.Str(0))); err == nil { - return vm.ToValue(r) + if r, err := u.ReadDir(args.Path(0)); err == nil { + list := make([]map[string]any, len(r)) + for i, info := range r { + list[i] = makeFileInfo(&info) + } + return vm.ToValue(list) } else { panic(vm.NewGoError(err)) } }, "stat": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) - return vm.ToValue(u.GetFileInfo(findPath(vm, args.Str(0)))) + return vm.ToValue(makeFileInfo(u.GetFileInfo(args.Path(0)))) }, "find": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) - return vm.ToValue(findPath(vm, args.Str(0))) + return vm.ToValue(args.Path(0)) + }, + "remove": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + if err := os.RemoveAll(args.Path(0)); err == nil { + return nil + } else { + panic(vm.NewGoError(err)) + } + }, + "copy": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(2) + if err := u.CopyFile(args.Path(0), args.Path(1)); err == nil { + return nil + } else { + panic(vm.NewGoError(err)) + } }, } } + +func makeFileInfo(info *u.FileInfo) map[string]any { + return map[string]any{ + "name": info.Name, + "size": info.Size, + "fullName": info.FullName, + "isDir": info.IsDir, + "modTime": info.ModTime.UnixMilli(), + } +} diff --git a/js/lib/ai.ts b/js/lib/ai.ts index 2cab9ab..15ef4c4 100644 --- a/js/lib/ai.ts +++ b/js/lib/ai.ts @@ -5,11 +5,14 @@ let {{.}}: LLM {{- end}} export default { + similarity, {{- range .LLMList}} {{.}}, {{- end}} } +function similarity(a: any, b: any): number{return 0} + interface ChatConfig { model: string ratio: number @@ -24,7 +27,7 @@ interface ChatResult { askTokens: number answerTokens: number totalTokens: number - error: string + usedTime: number } interface GCConfig { @@ -38,7 +41,15 @@ interface GCResult { preview: string results: Array previews: Array - error: string + usedTime: number +} + +interface EmbeddingResult { + result: string + askTokens: number + answerTokens: number + totalTokens: number + usedTime: number } interface Support { @@ -68,5 +79,8 @@ interface LLM { makeVideo(prompt: string, config?: GCConfig): GCResult fastMakeVideo(prompt: string, config?: GCConfig): GCResult bestMakeVideo(prompt: string, config?: GCConfig): GCResult + embedding(text: string, model: string): EmbeddingResult + fastEmbedding(text: string): EmbeddingResult + bestEmbedding(text: string): EmbeddingResult support: Support } diff --git a/js/lib/console.ts b/js/lib/console.ts index 59c8812..85c1689 100644 --- a/js/lib/console.ts +++ b/js/lib/console.ts @@ -9,6 +9,24 @@ export default { warn, error, input, + black, + red, + green, + yellow, + blue, + magenta, + cyan, + white, + dim, + italic, + bBlack, + bRed, + bGreen, + bYellow, + bBlue, + bMagenta, + bCyan, + bWhite, } function print(...data: any[]): void {} @@ -19,3 +37,22 @@ function info(...data: any[]): void {} function warn(...data: any[]): void {} function error(...data: any[]): void {} function input(...data: any[]): string {return ''} + +function black(...data: any[]): string {return ''} +function red(...data: any[]): string {return ''} +function green(...data: any[]): string {return ''} +function yellow(...data: any[]): string {return ''} +function blue(...data: any[]): string {return ''} +function magenta(...data: any[]): string {return ''} +function cyan(...data: any[]): string {return ''} +function white(...data: any[]): string {return ''} +function dim(...data: any[]): string {return ''} +function italic(...data: any[]): string {return ''} +function bBlack(...data: any[]): string {return ''} +function bRed(...data: any[]): string {return ''} +function bGreen(...data: any[]): string {return ''} +function bYellow(...data: any[]): string {return ''} +function bBlue(...data: any[]): string {return ''} +function bMagenta(...data: any[]): string {return ''} +function bCyan(...data: any[]): string {return ''} +function bWhite(...data: any[]): string {return ''} \ No newline at end of file diff --git a/js/lib/db.ts b/js/lib/db.ts new file mode 100644 index 0000000..fc62163 --- /dev/null +++ b/js/lib/db.ts @@ -0,0 +1,58 @@ +// just for develop + +export default { + get, +} + +function get(dbName: string): DB {return null} + +interface DB { + make(descFileOrContent: string): Array + query(sql: string, ...args:any): QueryResult + query1(sql: string, ...args:any): QueryResult1 + query11(sql: string, ...args:any): QueryResult11 + exec(sql: string, ...args:any): ExecResult + insert(table: string, data:Object): ExecResult + replace(table: string, data:Object): ExecResult + update(table: string, data:Object, where: string, ...args:any): ExecResult + delete(table: string, where: string, ...args:any): ExecResult + destroy(): void + begin(): Tx +} + +interface Tx { + query(sql: string, ...args:any): QueryResult + query1(sql: string, ...args:any): QueryResult1 + query11(sql: string, ...args:any): QueryResult11 + exec(sql: string, ...args:any): ExecResult + insert(table: string, data:Object): ExecResult + replace(table: string, data:Object): ExecResult + update(table: string, data:Object, where: string, ...args:any): ExecResult + delete(table: string, where: string, ...args:any): ExecResult + end(ok:boolean): void +} + +interface QueryResult { + sql: string + args: Array + result: Array +} + +interface QueryResult1 { + sql: string + args: Array + result: Object +} + +interface QueryResult11 { + sql: string + args: Array + result: any +} + +interface ExecResult { + sql: string + args: Array + id: number + changes: number +} diff --git a/js/lib/file.ts b/js/lib/file.ts index c687f6f..3c589fc 100644 --- a/js/lib/file.ts +++ b/js/lib/file.ts @@ -4,7 +4,10 @@ export default { read, write, dir, - stat + stat, + find, + remove, + copy } function read(filename: string): string {return ''} @@ -12,11 +15,13 @@ function write(filename: string, data: any): void {} function dir(filename: string): Array {return null} function stat(filename: string): FileInfo {return null} function find(filename: string): string {return ''} +function remove(filename: string): void {} +function copy(from: string, to: string): void {} interface FileInfo { - Name: string - FullName: string - IsDir: boolean - Size: number - ModTime: number + name: string + fullName: string + isDir: boolean + size: number + modTime: number } diff --git a/js/lib/log.ts b/js/lib/log.ts new file mode 100644 index 0000000..95de40e --- /dev/null +++ b/js/lib/log.ts @@ -0,0 +1,13 @@ +// just for develop + +export default { + debug, + info, + warn, + error, +} + +function debug(message:string, info?:Object): void {} +function info(message:string, info?:Object): void {} +function warn(message:string, info?:Object): void {} +function error(message:string, info?:Object): void {} diff --git a/js/lib/util.ts b/js/lib/util.ts index 4326e38..cf4485f 100644 --- a/js/lib/util.ts +++ b/js/lib/util.ts @@ -6,6 +6,8 @@ export default { unJson, yaml, unYaml, + load, + save, base64, unBase64, urlBase64, @@ -24,7 +26,11 @@ export default { sha256, sha512, tpl, - shell + shell, + toDatetime, + fromDatetime, + toDate, + fromDate } function json(data:any): string {return ''} @@ -32,6 +38,8 @@ function jsonP(data:any): string {return ''} function unJson(data:string): any {return null} function yaml(data:any): string {return ''} function unYaml(data:string): any {return null} +function load(filename:string): any {return null} +function save(filename:string, data:any) {} function base64(data:any): string {return ''} function unBase64(data:string): any {return null} function urlBase64(data:any): string {return ''} @@ -51,3 +59,7 @@ function sha256(data:any): string {return ''} function sha512(data:any): string {return ''} function tpl(text:string, data:any, functions?:Object): string {return ''} function shell(cmd:string, ...args:string[]): string[] {return []} +function toDatetime(timestamp:number): string {return ''} +function fromDatetime(datetimeStr:string): number {return 0} +function toDate(timestamp:number): string {return ''} +function fromDate(dateStr:string): number {return 0} diff --git a/js/log.go b/js/log.go new file mode 100644 index 0000000..ae80c79 --- /dev/null +++ b/js/log.go @@ -0,0 +1,40 @@ +package js + +import ( + "apigo.cc/ai/ai/goja" + "github.com/ssgo/log" +) + +func RequireLog() map[string]any { + return map[string]any{ + "info": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + getLogger(vm).Info(args.Str(0), args.Map2Arr(1)...) + return nil + }, + "warn": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + getLogger(vm).Warning(args.Str(0), args.Map2Arr(1)...) + return nil + }, + "error": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + getLogger(vm).Error(args.Str(0), args.Map2Arr(1)...) + return nil + }, + "debug": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + getLogger(vm).Debug(args.Str(0), args.Map2Arr(1)...) + return nil + }, + } +} + +func getLogger(vm *goja.Runtime) *log.Logger { + if vm.GoData["logger"] != nil { + if logger, ok := vm.GoData["logger"].(*log.Logger); ok { + return logger + } + } + return log.DefaultLogger +} diff --git a/js/util.go b/js/util.go index bf246be..1e48ee3 100644 --- a/js/util.go +++ b/js/util.go @@ -9,6 +9,7 @@ import ( "gopkg.in/yaml.v3" "strings" "text/template" + "time" ) func RequireUtil() map[string]any { @@ -104,7 +105,7 @@ func RequireUtil() map[string]any { }, "unBase64": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) - return vm.ToValue(u.UnBase64String(args.Str(0))) + return vm.ToValue(u.UnBase64(args.Str(0))) }, "urlBase64": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) @@ -112,7 +113,7 @@ func RequireUtil() map[string]any { }, "unUrlBase64": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) - return vm.ToValue(u.UnUrlBase64String(args.Str(0))) + return vm.ToValue(u.UnUrlBase64(args.Str(0))) }, "hex": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) @@ -121,7 +122,7 @@ func RequireUtil() map[string]any { "unHex": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) if r, err := hex.DecodeString(args.Str(0)); err == nil { - return vm.ToValue(string(r)) + return vm.ToValue(r) } else { panic(vm.NewGoError(err)) } @@ -129,7 +130,7 @@ func RequireUtil() map[string]any { "aes": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(3) if r, err := u.EncryptAesBytes(u.Bytes(args.Arguments[0].Export()), u.Bytes(args.Arguments[1].Export()), u.Bytes(args.Arguments[2].Export())); err == nil { - return vm.ToValue(string(r)) + return vm.ToValue(r) } else { panic(vm.NewGoError(err)) } @@ -137,7 +138,7 @@ func RequireUtil() map[string]any { "unAes": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(3) if r, err := u.DecryptAesBytes(u.Bytes(args.Arguments[0].Export()), u.Bytes(args.Arguments[1].Export()), u.Bytes(args.Arguments[2].Export())); err == nil { - return vm.ToValue(string(r)) + return vm.ToValue(r) } else { panic(vm.NewGoError(err)) } @@ -145,7 +146,7 @@ func RequireUtil() map[string]any { "gzip": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) if r, err := u.Gzip(u.Bytes(args.Arguments[0].Export())); err == nil { - return vm.ToValue(string(r)) + return vm.ToValue(r) } else { panic(vm.NewGoError(err)) } @@ -153,7 +154,7 @@ func RequireUtil() map[string]any { "gunzip": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := MakeArgs(&argsIn, vm).Check(1) if r, err := u.Gunzip(u.Bytes(args.Arguments[0].Export())); err == nil { - return vm.ToValue(string(r)) + return vm.ToValue(r) } else { panic(vm.NewGoError(err)) } @@ -232,5 +233,40 @@ func RequireUtil() map[string]any { panic(vm.NewGoError(err)) } }, + "toDatetime": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + return vm.ToValue(time.UnixMilli(args.Int64(0)).Format("2006-01-02 15:04:05")) + }, + "fromDatetime": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + timeStr := args.Str(0) + if len(timeStr) > 19 { + timeStr = timeStr[0:19] + } + if strings.ContainsRune(timeStr, 'T') { + timeStr = strings.ReplaceAll(timeStr, "T", " ") + } + if tm, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, time.Local); err == nil { + return vm.ToValue(tm.UnixMilli()) + } else { + panic(vm.NewGoError(err)) + } + }, + "toDate": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + return vm.ToValue(time.UnixMilli(args.Int64(0)).Format("2006-01-02")) + }, + "fromDate": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + timeStr := args.Str(0) + if len(timeStr) > 10 { + timeStr = timeStr[0:10] + } + if tm, err := time.ParseInLocation("2006-01-02", timeStr, time.Local); err == nil { + return vm.ToValue(tm.UnixMilli()) + } else { + panic(vm.NewGoError(err)) + } + }, } } diff --git a/llm/openai/chat.go b/llm/openai/chat.go index f7eb10c..d5247b0 100644 --- a/llm/openai/chat.go +++ b/llm/openai/chat.go @@ -1,64 +1,68 @@ package openai import ( - "apigo.cc/ai/ai/llm" + "apigo.cc/ai/ai/interface/llm" + "bytes" "context" + "encoding/binary" + "fmt" "github.com/sashabaranov/go-openai" "github.com/ssgo/log" "strings" + "time" ) -func (lm *LLM) FastAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) FastAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGPT_4o_mini_2024_07_18, }, callback) } -func (lm *LLM) LongAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) LongAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGPT_4_32k_0613, }, callback) } -func (lm *LLM) BatterAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) BatterAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGPT_4_turbo, }, callback) } -func (lm *LLM) BestAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) BestAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGPT_4o_2024_08_06, }, callback) } -func (lm *LLM) MultiAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) MultiAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGPT_4o_mini_2024_07_18, }, callback) } -func (lm *LLM) BestMultiAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) BestMultiAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGPT_4o_2024_08_06, }, callback) } -func (lm *LLM) CodeInterpreterAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) CodeInterpreterAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGPT_4o, Tools: map[string]any{llm.ToolCodeInterpreter: nil}, }, callback) } -func (lm *LLM) WebSearchAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) WebSearchAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGPT_4o_mini_2024_07_18, Tools: map[string]any{llm.ToolWebSearch: nil}, }, callback) } -func (lm *LLM) Ask(messages []llm.ChatMessage, config llm.ChatConfig, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) Ask(messages []llm.ChatMessage, config llm.ChatConfig, callback func(answer string)) (string, llm.Usage, error) { openaiConf := openai.DefaultConfig(lm.config.ApiKey) if lm.config.Endpoint != "" { openaiConf.BaseURL = lm.config.Endpoint @@ -124,7 +128,7 @@ func (lm *LLM) Ask(messages []llm.ChatMessage, config llm.ChatConfig, callback f r, err := c.CreateChatCompletionStream(context.Background(), opt) if err == nil { results := make([]string, 0) - usage := llm.TokenUsage{} + usage := llm.Usage{} for { if r2, err := r.Recv(); err == nil { if r2.Choices != nil { @@ -147,26 +151,75 @@ func (lm *LLM) Ask(messages []llm.ChatMessage, config llm.ChatConfig, callback f return strings.Join(results, ""), usage, nil } else { log.DefaultLogger.Error(err.Error()) - return "", llm.TokenUsage{}, err + return "", llm.Usage{}, err } } else { - r, err := c.CreateChatCompletion(context.Background(), opt) - - if err == nil { + t1 := time.Now().UnixMilli() + if r, err := c.CreateChatCompletion(context.Background(), opt); err == nil { + t2 := time.Now().UnixMilli() - t1 results := make([]string, 0) if r.Choices != nil { for _, ch := range r.Choices { results = append(results, ch.Message.Content) } } - return strings.Join(results, ""), llm.TokenUsage{ + return strings.Join(results, ""), llm.Usage{ AskTokens: int64(r.Usage.PromptTokens), AnswerTokens: int64(r.Usage.CompletionTokens), TotalTokens: int64(r.Usage.TotalTokens), + UsedTime: t2, }, nil } else { //fmt.Println(u.BMagenta(err.Error()), u.BMagenta(u.JsonP(r))) - return "", llm.TokenUsage{}, err + return "", llm.Usage{}, err } } } + +func (lm *LLM) FastEmbedding(text string) ([]byte, llm.Usage, error) { + return lm.Embedding(text, string(openai.AdaEmbeddingV2)) +} + +func (lm *LLM) BestEmbedding(text string) ([]byte, llm.Usage, error) { + return lm.Embedding(text, string(openai.LargeEmbedding3)) +} + +func (lm *LLM) Embedding(text, model string) ([]byte, llm.Usage, error) { + fmt.Println(111, model, text) + openaiConf := openai.DefaultConfig(lm.config.ApiKey) + if lm.config.Endpoint != "" { + openaiConf.BaseURL = lm.config.Endpoint + } + + c := openai.NewClientWithConfig(openaiConf) + req := openai.EmbeddingRequest{ + Input: text, + Model: openai.EmbeddingModel(model), + User: "", + EncodingFormat: "", + Dimensions: 0, + } + + t1 := time.Now().UnixMilli() + if r, err := c.CreateEmbeddings(context.Background(), req); err == nil { + t2 := time.Now().UnixMilli() - t1 + buf := new(bytes.Buffer) + if r.Data != nil { + for _, ch := range r.Data { + for _, v := range ch.Embedding { + _ = binary.Write(buf, binary.LittleEndian, v) + } + } + } + fmt.Println(len(buf.Bytes())) + return buf.Bytes(), llm.Usage{ + AskTokens: int64(r.Usage.PromptTokens), + AnswerTokens: int64(r.Usage.CompletionTokens), + TotalTokens: int64(r.Usage.TotalTokens), + UsedTime: t2, + }, nil + } else { + fmt.Println(err.Error()) + return nil, llm.Usage{}, err + } +} diff --git a/llm/openai/config.go b/llm/openai/config.go index d8f0dc1..20f53ec 100644 --- a/llm/openai/config.go +++ b/llm/openai/config.go @@ -1,7 +1,7 @@ package openai import ( - "apigo.cc/ai/ai/llm" + "apigo.cc/ai/ai/interface/llm" "github.com/sashabaranov/go-openai" ) diff --git a/llm/openai/gc.go b/llm/openai/gc.go index aa0c5de..2a8c8c6 100644 --- a/llm/openai/gc.go +++ b/llm/openai/gc.go @@ -1,32 +1,33 @@ package openai import ( - "apigo.cc/ai/ai/llm" + "apigo.cc/ai/ai/interface/llm" "context" "github.com/sashabaranov/go-openai" "strings" + "time" ) -// func (lm *LLM) FastMakeImage(prompt, size, refImage string) ([]string, error) { +// func (lm *LLM) FastMakeImage(prompt, size, refImage string) ([]string, llm.Usage, error) { // return lm.MakeImage(ModelDallE3Std, prompt, size, refImage) // } // -// func (lm *LLM) BestMakeImage(prompt, size, refImage string) ([]string, error) { +// func (lm *LLM) BestMakeImage(prompt, size, refImage string) ([]string, llm.Usage, error) { // return lm.MakeImage(ModelDallE3HD, prompt, size, refImage) // } // -// func (lm *LLM) MakeImage(model, prompt, size, refImage string) ([]string, error) { -func (lm *LLM) FastMakeImage(prompt string, config llm.GCConfig) ([]string, error) { +// func (lm *LLM) MakeImage(model, prompt, size, refImage string) ([]string, llm.Usage, error) { +func (lm *LLM) FastMakeImage(prompt string, config llm.GCConfig) ([]string, llm.Usage, error) { config.Model = ModelDallE3Std return lm.MakeImage(prompt, config) } -func (lm *LLM) BestMakeImage(prompt string, config llm.GCConfig) ([]string, error) { +func (lm *LLM) BestMakeImage(prompt string, config llm.GCConfig) ([]string, llm.Usage, error) { config.Model = ModelDallE3HD return lm.MakeImage(prompt, config) } -func (lm *LLM) MakeImage(prompt string, config llm.GCConfig) ([]string, error) { +func (lm *LLM) MakeImage(prompt string, config llm.GCConfig) ([]string, llm.Usage, error) { openaiConf := openai.DefaultConfig(lm.config.ApiKey) if lm.config.Endpoint != "" { openaiConf.BaseURL = lm.config.Endpoint @@ -43,6 +44,7 @@ func (lm *LLM) MakeImage(prompt string, config llm.GCConfig) ([]string, error) { quality = openai.CreateImageQualityHD model = model[0 : len(model)-3] } + t1 := time.Now().UnixMilli() r, err := c.CreateImage(context.Background(), openai.ImageRequest{ Prompt: prompt, Model: model, @@ -51,25 +53,31 @@ func (lm *LLM) MakeImage(prompt string, config llm.GCConfig) ([]string, error) { Style: style, ResponseFormat: openai.CreateImageResponseFormatURL, }) + t2 := time.Now().UnixMilli() - t1 if err == nil { results := make([]string, 0) for _, item := range r.Data { results = append(results, item.URL) } - return results, nil + return results, llm.Usage{ + AskTokens: 0, + AnswerTokens: 0, + TotalTokens: 0, + UsedTime: t2, + }, nil } else { - return nil, err + return nil, llm.Usage{}, err } } -func (lm *LLM) FastMakeVideo(prompt string, config llm.GCConfig) ([]string, []string, error) { +func (lm *LLM) FastMakeVideo(prompt string, config llm.GCConfig) ([]string, []string, llm.Usage, error) { return lm.MakeVideo(prompt, config) } -func (lm *LLM) BestMakeVideo(prompt string, config llm.GCConfig) ([]string, []string, error) { +func (lm *LLM) BestMakeVideo(prompt string, config llm.GCConfig) ([]string, []string, llm.Usage, error) { return lm.MakeVideo(prompt, config) } -func (lm *LLM) MakeVideo(prompt string, config llm.GCConfig) ([]string, []string, error) { - return nil, nil, nil +func (lm *LLM) MakeVideo(prompt string, config llm.GCConfig) ([]string, []string, llm.Usage, error) { + return nil, nil, llm.Usage{}, nil } diff --git a/llm/zhipu/chat.go b/llm/zhipu/chat.go index 3c60ced..1b6eea3 100644 --- a/llm/zhipu/chat.go +++ b/llm/zhipu/chat.go @@ -1,67 +1,70 @@ package zhipu import ( - "apigo.cc/ai/ai/llm" + "apigo.cc/ai/ai/interface/llm" "apigo.cc/ai/ai/llm/zhipu/zhipu" + "bytes" "context" + "encoding/binary" "strings" + "time" ) -func (lm *LLM) FastAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) FastAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM4Flash, }, callback) } -func (lm *LLM) LongAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) LongAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM4Long, }, callback) } -func (lm *LLM) BatterAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) BatterAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM4Plus, }, callback) } -func (lm *LLM) BestAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) BestAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM40520, }, callback) } -func (lm *LLM) MultiAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) MultiAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM4VPlus, }, callback) } -func (lm *LLM) BestMultiAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) BestMultiAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM4V, }, callback) } -func (lm *LLM) CodeInterpreterAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) CodeInterpreterAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM4AllTools, Tools: map[string]any{llm.ToolCodeInterpreter: nil}, }, callback) } -func (lm *LLM) WebSearchAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) WebSearchAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM4AllTools, Tools: map[string]any{llm.ToolWebSearch: nil}, }, callback) } -func (lm *LLM) Ask(messages []llm.ChatMessage, config llm.ChatConfig, callback func(answer string)) (string, llm.TokenUsage, error) { +func (lm *LLM) Ask(messages []llm.ChatMessage, config llm.ChatConfig, callback func(answer string)) (string, llm.Usage, error) { config.SetDefault(&lm.config.ChatConfig) c, err := zhipu.NewClient(zhipu.WithAPIKey(lm.config.ApiKey), zhipu.WithBaseURL(lm.config.Endpoint)) if err != nil { - return "", llm.TokenUsage{}, err + return "", llm.Usage{}, err } cc := c.ChatCompletion(config.GetModel()) @@ -126,19 +129,60 @@ func (lm *LLM) Ask(messages []llm.ChatMessage, config llm.ChatConfig, callback f }) } + t1 := time.Now().UnixMilli() if r, err := cc.Do(context.Background()); err == nil { + t2 := time.Now().UnixMilli() - t1 results := make([]string, 0) if r.Choices != nil { for _, ch := range r.Choices { results = append(results, ch.Message.Content) } } - return strings.Join(results, ""), llm.TokenUsage{ + return strings.Join(results, ""), llm.Usage{ AskTokens: r.Usage.PromptTokens, AnswerTokens: r.Usage.CompletionTokens, TotalTokens: r.Usage.TotalTokens, + UsedTime: t2, }, nil } else { - return "", llm.TokenUsage{}, err + return "", llm.Usage{}, err + } +} + +func (lm *LLM) FastEmbedding(text string) ([]byte, llm.Usage, error) { + return lm.Embedding(text, ModelEmbedding3) +} + +func (lm *LLM) BestEmbedding(text string) ([]byte, llm.Usage, error) { + return lm.Embedding(text, ModelEmbedding3) +} + +func (lm *LLM) Embedding(text, model string) ([]byte, llm.Usage, error) { + c, err := zhipu.NewClient(zhipu.WithAPIKey(lm.config.ApiKey), zhipu.WithBaseURL(lm.config.Endpoint)) + if err != nil { + return nil, llm.Usage{}, err + } + + cc := c.Embedding(model) + cc.SetInput(text) + t1 := time.Now().UnixMilli() + if r, err := cc.Do(context.Background()); err == nil { + t2 := time.Now().UnixMilli() - t1 + buf := new(bytes.Buffer) + if r.Data != nil { + for _, ch := range r.Data { + for _, v := range ch.Embedding { + _ = binary.Write(buf, binary.LittleEndian, float32(v)) + } + } + } + return buf.Bytes(), llm.Usage{ + AskTokens: r.Usage.PromptTokens, + AnswerTokens: r.Usage.CompletionTokens, + TotalTokens: r.Usage.TotalTokens, + UsedTime: t2, + }, nil + } else { + return nil, llm.Usage{}, err } } diff --git a/llm/zhipu/config.go b/llm/zhipu/config.go index d6dfb50..cdcb87b 100644 --- a/llm/zhipu/config.go +++ b/llm/zhipu/config.go @@ -1,7 +1,7 @@ package zhipu import ( - "apigo.cc/ai/ai/llm" + "apigo.cc/ai/ai/interface/llm" "apigo.cc/ai/ai/llm/zhipu/zhipu" ) diff --git a/llm/zhipu/gc.go b/llm/zhipu/gc.go index 57bf486..203327e 100644 --- a/llm/zhipu/gc.go +++ b/llm/zhipu/gc.go @@ -1,69 +1,75 @@ package zhipu import ( - "apigo.cc/ai/ai/llm" + "apigo.cc/ai/ai/interface/llm" "apigo.cc/ai/ai/llm/zhipu/zhipu" "context" "errors" "time" ) -func (lm *LLM) FastMakeImage(prompt string, config llm.GCConfig) ([]string, error) { +func (lm *LLM) FastMakeImage(prompt string, config llm.GCConfig) ([]string, llm.Usage, error) { config.Model = ModelCogView3Plus return lm.MakeImage(prompt, config) } -func (lm *LLM) BestMakeImage(prompt string, config llm.GCConfig) ([]string, error) { +func (lm *LLM) BestMakeImage(prompt string, config llm.GCConfig) ([]string, llm.Usage, error) { config.Model = ModelCogView3 return lm.MakeImage(prompt, config) } -func (lm *LLM) MakeImage(prompt string, config llm.GCConfig) ([]string, error) { +func (lm *LLM) MakeImage(prompt string, config llm.GCConfig) ([]string, llm.Usage, error) { c, err := zhipu.NewClient(zhipu.WithAPIKey(lm.config.ApiKey), zhipu.WithBaseURL(lm.config.Endpoint)) if err != nil { - return nil, err + return nil, llm.Usage{}, err } config.SetDefault(&lm.config.GCConfig) cc := c.ImageGeneration(config.Model).SetPrompt(prompt) cc.SetSize(config.GetSize()) + t1 := time.Now().UnixMilli() if r, err := cc.Do(context.Background()); err == nil { + t2 := time.Now().UnixMilli() - t1 results := make([]string, 0) for _, item := range r.Data { results = append(results, item.URL) } - return results, nil + return results, llm.Usage{ + UsedTime: t2, + }, nil } else { - return nil, err + return nil, llm.Usage{}, err } } -func (lm *LLM) FastMakeVideo(prompt string, config llm.GCConfig) ([]string, []string, error) { +func (lm *LLM) FastMakeVideo(prompt string, config llm.GCConfig) ([]string, []string, llm.Usage, error) { config.Model = ModelCogVideoX return lm.MakeVideo(prompt, config) } -func (lm *LLM) BestMakeVideo(prompt string, config llm.GCConfig) ([]string, []string, error) { +func (lm *LLM) BestMakeVideo(prompt string, config llm.GCConfig) ([]string, []string, llm.Usage, error) { config.Model = ModelCogVideoX return lm.MakeVideo(prompt, config) } -func (lm *LLM) MakeVideo(prompt string, config llm.GCConfig) ([]string, []string, error) { +func (lm *LLM) MakeVideo(prompt string, config llm.GCConfig) ([]string, []string, llm.Usage, error) { c, err := zhipu.NewClient(zhipu.WithAPIKey(lm.config.ApiKey), zhipu.WithBaseURL(lm.config.Endpoint)) if err != nil { - return nil, nil, err + return nil, nil, llm.Usage{}, err } config.SetDefault(&lm.config.GCConfig) cc := c.VideoGeneration(config.Model).SetPrompt(prompt) cc.SetImageURL(config.GetRef()) + t1 := time.Now().UnixMilli() if resp, err := cc.Do(context.Background()); err == nil { + t2 := time.Now().UnixMilli() - t1 for i := 0; i < 1200; i++ { r, err := c.AsyncResult(resp.ID).Do(context.Background()) if err != nil { - return nil, nil, err + return nil, nil, llm.Usage{}, err } if r.TaskStatus == zhipu.VideoGenerationTaskStatusSuccess { covers := make([]string, 0) @@ -72,15 +78,17 @@ func (lm *LLM) MakeVideo(prompt string, config llm.GCConfig) ([]string, []string results = append(results, item.URL) covers = append(covers, item.CoverImageURL) } - return results, covers, nil + return results, covers, llm.Usage{ + UsedTime: t2, + }, nil } if r.TaskStatus == zhipu.VideoGenerationTaskStatusFail { - return nil, nil, errors.New("fail on task " + resp.ID) + return nil, nil, llm.Usage{}, errors.New("fail on task " + resp.ID) } time.Sleep(3 * time.Second) } - return nil, nil, errors.New("timeout on task " + resp.ID) + return nil, nil, llm.Usage{}, errors.New("timeout on task " + resp.ID) } else { - return nil, nil, err + return nil, nil, llm.Usage{}, err } } diff --git a/tests/chat_test.go b/tests/chat_test.go index ea93129..54041f7 100644 --- a/tests/chat_test.go +++ b/tests/chat_test.go @@ -2,8 +2,8 @@ package tests import ( "apigo.cc/ai/ai" + llm2 "apigo.cc/ai/ai/interface/llm" "apigo.cc/ai/ai/js" - "apigo.cc/ai/ai/llm" "encoding/hex" "fmt" "github.com/ssgo/u" @@ -39,13 +39,13 @@ func TestAgent(t *testing.T) { } func testChat(t *testing.T, llmName string) { - lm := llm.Get(llmName) + lm := llm2.Get(llmName) if lm == nil { - t.Fatal("agent is nil") + t.Fatal("interface is nil") } - r, usage, err := lm.FastAsk(llm.Messages().User().Text("你是什么模型,请给出具体名称、版本号").Make(), func(text string) { + r, usage, err := lm.FastAsk(llm2.Messages().User().Text("你是什么模型,请给出具体名称、版本号").Make(), func(text string) { fmt.Print(u.BCyan(text)) fmt.Print(" ") }) @@ -60,13 +60,13 @@ func testChat(t *testing.T, llmName string) { } func testCode(t *testing.T, llmName string) { - lm := llm.Get(llmName) + lm := llm2.Get(llmName) if lm == nil { - t.Fatal("agent is nil") + t.Fatal("interface is nil") } - r, usage, err := lm.CodeInterpreterAsk(llm.Messages().User().Text("计算[5,10,20,700,99,310,978,100]的平均值和方差。").Make(), func(text string) { + r, usage, err := lm.CodeInterpreterAsk(llm2.Messages().User().Text("计算[5,10,20,700,99,310,978,100]的平均值和方差。").Make(), func(text string) { fmt.Print(u.BCyan(text)) fmt.Print(" ") }) @@ -81,13 +81,13 @@ func testCode(t *testing.T, llmName string) { } func testSearch(t *testing.T, llmName string) { - lm := llm.Get(llmName) + lm := llm2.Get(llmName) if lm == nil { - t.Fatal("agent is nil") + t.Fatal("interface is nil") } - r, usage, err := lm.WebSearchAsk(llm.Messages().User().Text("今天上海的天气怎么样?").Make(), func(text string) { + r, usage, err := lm.WebSearchAsk(llm2.Messages().User().Text("今天上海的天气怎么样?").Make(), func(text string) { fmt.Print(u.BCyan(text)) fmt.Print(" ") }) @@ -102,10 +102,10 @@ func testSearch(t *testing.T, llmName string) { } func testAskWithImage(t *testing.T, llmName, imageFile string) { - lm := llm.Get(llmName) + lm := llm2.Get(llmName) if lm == nil { - t.Fatal("agent is nil") + t.Fatal("interface is nil") } ask := `请回答: @@ -114,7 +114,7 @@ func testAskWithImage(t *testing.T, llmName, imageFile string) { 3、正在用什么软件播放什么歌?谁演唱的?歌曲的大意是? 4、后面的浏览器中正在浏览什么内容?猜测一下我浏览这个网页是想干嘛? ` - r, usage, err := lm.MultiAsk(llm.Messages().User().Text(ask).Image("data:image/jpeg;base64,"+u.Base64(u.ReadFileBytesN(imageFile))).Make(), func(text string) { + r, usage, err := lm.MultiAsk(llm2.Messages().User().Text(ask).Image("data:image/jpeg;base64,"+u.Base64(u.ReadFileBytesN(imageFile))).Make(), func(text string) { fmt.Print(u.BCyan(text)) fmt.Print(" ") }) @@ -129,10 +129,10 @@ func testAskWithImage(t *testing.T, llmName, imageFile string) { } func testAskWithVideo(t *testing.T, llmName, videoFile string) { - lm := llm.Get(llmName) + lm := llm2.Get(llmName) if lm == nil { - t.Fatal("agent is nil") + t.Fatal("interface is nil") } ask := `请回答: @@ -140,7 +140,7 @@ func testAskWithVideo(t *testing.T, llmName, videoFile string) { 4、后面的浏览器中正在浏览什么内容?猜测一下我浏览这个网页是想干嘛? ` - r, usage, err := lm.MultiAsk(llm.Messages().User().Text(ask).Video("data:video/mp4,"+u.Base64(u.ReadFileBytesN(videoFile))).Make(), func(text string) { + r, usage, err := lm.MultiAsk(llm2.Messages().User().Text(ask).Video("data:video/mp4,"+u.Base64(u.ReadFileBytesN(videoFile))).Make(), func(text string) { fmt.Print(u.BCyan(text)) fmt.Print(" ") }) @@ -155,13 +155,13 @@ func testAskWithVideo(t *testing.T, llmName, videoFile string) { } func testMakeImage(t *testing.T, llmName, prompt, refImage string) { - lm := llm.Get(llmName) + lm := llm2.Get(llmName) if lm == nil { - t.Fatal("agent is nil") + t.Fatal("interface is nil") } - r, err := lm.FastMakeImage(prompt, llm.GCConfig{ + r, err := lm.FastMakeImage(prompt, llm2.GCConfig{ Size: "1024x1024", }) @@ -176,13 +176,13 @@ func testMakeImage(t *testing.T, llmName, prompt, refImage string) { } func testMakeVideo(t *testing.T, llmName, prompt, refImage string) { - lm := llm.Get(llmName) + lm := llm2.Get(llmName) if lm == nil { - t.Fatal("agent is nil") + t.Fatal("interface is nil") } - r, covers, err := lm.FastMakeVideo(prompt, llm.GCConfig{ + r, covers, err := lm.FastMakeVideo(prompt, llm2.GCConfig{ Size: "1280x720", Ref: refImage, }) diff --git a/ai/watcher/watcher.go b/watcher/watcher.go similarity index 100% rename from ai/watcher/watcher.go rename to watcher/watcher.go