diff --git a/ai/ai.go b/ai/ai.go index 446101e..8eabdb8 100644 --- a/ai/ai.go +++ b/ai/ai.go @@ -1,7 +1,7 @@ package main import ( - "apigo.cc/ai/ai/js" + "apigo.cc/ai/ai" "fmt" "github.com/ssgo/u" "os" @@ -10,7 +10,7 @@ import ( func main() { if len(os.Args) > 1 && (os.Args[1] == "-e" || os.Args[1] == "export") { - imports, err := js.ExportForDev() + imports, err := ai.ExportForDev() if err != nil { fmt.Println(err.Error()) } else { @@ -40,7 +40,7 @@ function main(...args) { for i := 2; i < len(os.Args); i++ { args[i-2] = os.Args[i] } - result, err := js.RunFile(jsFile, args...) + result, err := ai.RunFile(jsFile, args...) if err != nil { fmt.Println(err.Error()) } else if result != nil { diff --git a/go.mod b/go.mod index 643c0f0..376e470 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22 require ( github.com/dop251/goja v0.0.0-20240828124009-016eb7256539 + github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc github.com/go-resty/resty/v2 v2.15.0 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/sashabaranov/go-openai v1.29.2 @@ -16,7 +17,6 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.11.4 // indirect - github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc // indirect github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -25,7 +25,3 @@ require ( golang.org/x/text v0.18.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace ( - github.com/ssgo/config v1.7.7 => ../../ssgo/config -) diff --git a/js/js.go b/js.go similarity index 67% rename from js/js.go rename to js.go index 662eb57..5d7e869 100644 --- a/js/js.go +++ b/js.go @@ -1,7 +1,7 @@ -package js +package ai import ( - "apigo.cc/ai/ai" + "apigo.cc/ai/ai/js" "apigo.cc/ai/ai/llm" "bytes" _ "embed" @@ -17,24 +17,27 @@ import ( "text/template" ) -//go:embed lib/ai.ts +//go:embed js/lib/ai.ts var aiTS string -//go:embed lib/console.ts +//go:embed js/lib/console.ts var consoleTS string -//go:embed lib/file.ts +//go:embed js/lib/file.ts var fileTS string +//go:embed js/lib/util.ts +var utilTS string + func RunFile(file string, args ...any) (any, error) { return Run(u.ReadFileN(file), file, args...) } func Run(code string, refFile string, args ...any) (any, error) { var r any - js, err := StartFromCode(code, refFile) + rt, err := StartFromCode(code, refFile) if err == nil { - r, err = js.Run(args...) + r, err = rt.Run(args...) } return r, err } @@ -44,7 +47,7 @@ var importLibMatcher = regexp.MustCompile(`(?im)^\s*(import)\s+(.+?)\s+from\s+[' var requireLibMatcher = regexp.MustCompile(`(?im)^\s*(const|let|var)\s+(.+?)\s*=\s*require\s*\(\s*['"][./\\\w:]+lib[/\\](.+?)(\.ts)?['"]\s*\)`) var checkMainMatcher = regexp.MustCompile(`(?im)^\s*function\s+main\s*\(`) -type JS struct { +type Runtime struct { vm *goja.Runtime required map[string]bool file string @@ -52,34 +55,40 @@ type JS struct { code string } -func (js *JS) requireMod(name string) error { +func (rt *Runtime) requireMod(name string) error { var err error if name == "console" || name == "" { - if !js.required["console"] { - js.required["console"] = true - err = js.vm.Set("console", requireConsole()) + if !rt.required["console"] { + rt.required["console"] = true + err = rt.vm.Set("console", js.RequireConsole()) } } if err == nil && (name == "file" || name == "") { - if !js.required["file"] { - js.required["file"] = true - err = js.vm.Set("file", requireFile()) + if !rt.required["file"] { + rt.required["file"] = true + err = rt.vm.Set("file", js.RequireFile()) + } + } + if err == nil && (name == "util" || name == "") { + if !rt.required["util"] { + rt.required["util"] = true + err = rt.vm.Set("util", js.RequireUtil()) } } if err == nil && (name == "ai" || name == "") { - if !js.required["ai"] { - js.required["ai"] = true + if !rt.required["ai"] { + rt.required["ai"] = true aiList := make(map[string]any) for name, lm := range llm.List() { - aiList[name] = requireAI(lm) + aiList[name] = js.RequireAI(lm) } - err = js.vm.Set("ai", aiList) + err = rt.vm.Set("ai", aiList) } } return err } -func (js *JS) makeImport(matcher *regexp.Regexp, code string) (string, int, error) { +func (rt *Runtime) makeImport(matcher *regexp.Regexp, code string) (string, int, error) { var modErr error importCount := 0 code = matcher.ReplaceAllStringFunc(code, func(str string) string { @@ -92,7 +101,7 @@ func (js *JS) makeImport(matcher *regexp.Regexp, code string) (string, int, erro modName := m[3] importCount++ if modErr == nil { - if err := js.requireMod(modName); err != nil { + if err := rt.requireMod(modName); err != nil { modErr = err } } @@ -105,11 +114,11 @@ func (js *JS) makeImport(matcher *regexp.Regexp, code string) (string, int, erro return code, importCount, modErr } -func StartFromFile(file string) (*JS, error) { +func StartFromFile(file string) (*Runtime, error) { return StartFromCode(u.ReadFileN(file), file) } -func StartFromCode(code, refFile string) (*JS, error) { +func StartFromCode(code, refFile string) (*Runtime, error) { if refFile == "" { refFile = "main.js" } @@ -118,9 +127,9 @@ func StartFromCode(code, refFile string) (*JS, error) { refFile = absFile } - ai.InitFrom(filepath.Dir(refFile)) + InitFrom(filepath.Dir(refFile)) - js := &JS{ + rt := &Runtime{ vm: goja.New(), required: map[string]bool{}, file: refFile, @@ -131,29 +140,29 @@ func StartFromCode(code, refFile string) (*JS, error) { // 按需加载引用 var importCount int var modErr error - js.code, importCount, modErr = js.makeImport(importLibMatcher, js.code) + rt.code, importCount, modErr = rt.makeImport(importLibMatcher, rt.code) if modErr == nil { importCount1 := importCount - js.code, importCount, modErr = js.makeImport(requireLibMatcher, js.code) + rt.code, importCount, modErr = rt.makeImport(requireLibMatcher, rt.code) importCount += importCount1 } // 将 import 转换为 require - js.code = importModMatcher.ReplaceAllString(js.code, "let $1 = require('$2')") + rt.code = importModMatcher.ReplaceAllString(rt.code, "let $1 = require('$2')") // 如果没有import,默认import所有 if modErr == nil && importCount == 0 { - modErr = js.requireMod("") + modErr = rt.requireMod("") } if modErr != nil { return nil, modErr } - //fmt.Println(u.BCyan(js.code)) + //fmt.Println(u.BCyan(rt.code)) // 处理模块引用 require.NewRegistryWithLoader(func(path string) ([]byte, error) { - refPath := filepath.Join(filepath.Dir(js.file), path) + refPath := filepath.Join(filepath.Dir(rt.file), path) if !strings.HasSuffix(refPath, ".js") && !u.FileExists(refPath) { refPath += ".js" } @@ -161,23 +170,23 @@ func StartFromCode(code, refFile string) (*JS, error) { if err != nil { return nil, err } - modCode, _, _ = js.makeImport(importLibMatcher, modCode) - modCode, _, _ = js.makeImport(requireLibMatcher, modCode) + modCode, _, _ = rt.makeImport(importLibMatcher, modCode) + modCode, _, _ = rt.makeImport(requireLibMatcher, modCode) return []byte(modCode), modErr - }).Enable(js.vm) + }).Enable(rt.vm) // 初始化主函数 - if !checkMainMatcher.MatchString(js.code) { - js.code = "function main(...args){" + js.code + "}" + if !checkMainMatcher.MatchString(rt.code) { + rt.code = "function main(...args){" + rt.code + "}" } - if _, err := js.vm.RunScript("main", js.code); err != nil { + if _, err := rt.vm.RunScript("main", rt.code); err != nil { return nil, err } - return js, nil + return rt, nil } -func (js *JS) Run(args ...any) (any, error) { +func (rt *Runtime) Run(args ...any) (any, error) { // 解析参数 for i, arg := range args { if str, ok := arg.(string); ok { @@ -188,10 +197,10 @@ func (js *JS) Run(args ...any) (any, error) { } } - if err := js.vm.Set("__args", args); err != nil { + if err := rt.vm.Set("__args", args); err != nil { return nil, err } - jsResult, err := js.vm.RunScript(js.file, "main(...__args)") + jsResult, err := rt.vm.RunScript(rt.file, "main(...__args)") var result any if err == nil { @@ -207,7 +216,7 @@ type Exports struct { } func ExportForDev() (string, error) { - ai.Init() + Init() if len(llm.List()) == 0 && !u.FileExists("env.yml") && !u.FileExists("env.json") && !u.FileExists("llm.yml") && !u.FileExists("llm.json") { return "", errors.New("no llm config found, please run `ai -e` on env.yml or llm.yml path") } @@ -231,8 +240,10 @@ func ExportForDev() (string, error) { _ = u.WriteFile(filepath.Join("lib", "console.ts"), consoleTS) _ = u.WriteFile(filepath.Join("lib", "file.ts"), fileTS) + _ = u.WriteFile(filepath.Join("lib", "util.ts"), utilTS) return `import {` + strings.Join(exports.LLMList, ", ") + `} from './lib/ai' import console from './lib/console' +import util from './lib/util' import file from './lib/file'`, nil } diff --git a/js/ai.go b/js/ai.go index 0f3aff8..4f60a32 100644 --- a/js/ai.go +++ b/js/ai.go @@ -22,7 +22,7 @@ type AIGCResult struct { Error string } -func requireAI(lm llm.LLM) map[string]any { +func RequireAI(lm llm.LLM) map[string]any { return map[string]any{ "ask": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value { conf, cb := getAskArgs(args.This, vm, args.Arguments) diff --git a/js/console.go b/js/console.go index fe5ec5d..e585ec3 100644 --- a/js/console.go +++ b/js/console.go @@ -7,7 +7,7 @@ import ( "strings" ) -func requireConsole() map[string]any { +func RequireConsole() map[string]any { return map[string]any{ "print": func(args goja.FunctionCall) goja.Value { consolePrint(args, "print", nil) diff --git a/js/file.go b/js/file.go index 90f7d7c..0fc0e2e 100644 --- a/js/file.go +++ b/js/file.go @@ -6,7 +6,7 @@ import ( "github.com/ssgo/u" ) -func requireFile() map[string]any { +func RequireFile() map[string]any { return map[string]any{ "read": func(args goja.FunctionCall, vm *goja.Runtime) goja.Value { if len(args.Arguments) < 1 { diff --git a/js/lib/ai.ts b/js/lib/ai.ts index 6139132..2cab9ab 100644 --- a/js/lib/ai.ts +++ b/js/lib/ai.ts @@ -2,15 +2,15 @@ {{range .LLMList}} let {{.}}: LLM -{{end}} +{{- end}} export default { -{{range .LLMList}} +{{- range .LLMList}} {{.}}, -{{end}} +{{- end}} } -interface ChatModelConfig { +interface ChatConfig { model: string ratio: number maxTokens: number @@ -27,6 +27,12 @@ interface ChatResult { error: string } +interface GCConfig { + model: string + size: string + ref: string +} + interface GCResult { result: string preview: string @@ -47,35 +53,20 @@ interface Support { } interface LLM { - ask(messages: any, config?: ChatModelConfig, callback?: (answer: string) => void): ChatResult - + ask(messages: any, config?: ChatConfig, callback?: (answer: string) => void): ChatResult fastAsk(messages: any, callback?: (answer: string) => void): ChatResult - longAsk(messages: any, callback?: (answer: string) => void): ChatResult - batterAsk(messages: any, callback?: (answer: string) => void): ChatResult - bestAsk(messages: any, callback?: (answer: string) => void): ChatResult - multiAsk(messages: any, callback?: (answer: string) => void): ChatResult - bestMultiAsk(messages: any, callback?: (answer: string) => void): ChatResult - codeInterpreterAsk(messages: any, callback?: (answer: string) => void): ChatResult - webSearchAsk(messages: any, callback?: (answer: string) => void): ChatResult - - makeImage(model: string, prompt: string, size?: string, refImage?: string): GCResult - - fastMakeImage(prompt: string, size?: string, refImage?: string): GCResult - - bestMakeImage(prompt: string, size?: string, refImage?: string): GCResult - - makeVideo(arg2: string, arg3: string, arg4: string, arg5: string): GCResult - - fastMakeVideo(prompt: string, size?: string, refImage?: string): GCResult - - bestMakeVideo(prompt: string, size?: string, refImage?: string): GCResult - + makeImage(prompt: string, config?: GCConfig): GCResult + fastMakeImage(prompt: string, config?: GCConfig): GCResult + bestMakeImage(prompt: string, config?: GCConfig): GCResult + makeVideo(prompt: string, config?: GCConfig): GCResult + fastMakeVideo(prompt: string, config?: GCConfig): GCResult + bestMakeVideo(prompt: string, config?: GCConfig): GCResult support: Support } diff --git a/js/lib/console.ts b/js/lib/console.ts index 8c40017..59c8812 100644 --- a/js/lib/console.ts +++ b/js/lib/console.ts @@ -11,27 +11,11 @@ export default { input, } -function print(...data: any[]): void { -} - -function println(...data: any[]): void { -} - -function log(...data: any[]): void { -} - -function debug(...data: any[]): void { -} - -function info(...data: any[]): void { -} - -function warn(...data: any[]): void { -} - -function error(...data: any[]): void { -} - -function input(...data: any[]): string { - return '' -} +function print(...data: any[]): void {} +function println(...data: any[]): void {} +function log(...data: any[]): void {} +function debug(...data: any[]): void {} +function info(...data: any[]): void {} +function warn(...data: any[]): void {} +function error(...data: any[]): void {} +function input(...data: any[]): string {return ''} diff --git a/js/lib/file.ts b/js/lib/file.ts index 0cdc810..265c481 100644 --- a/js/lib/file.ts +++ b/js/lib/file.ts @@ -7,21 +7,10 @@ export default { stat } -function read(filename: string): string { - return '' -} - -function write(filename: string, data: any): void { -} - - -function dir(filename: string): Array { - return null -} - -function stat(filename: string): FileInfo { - return null -} +function read(filename: string): string {return ''} +function write(filename: string, data: any): void {} +function dir(filename: string): Array {return null} +function stat(filename: string): FileInfo {return null} interface FileInfo { Name: string diff --git a/js/lib/util.ts b/js/lib/util.ts new file mode 100644 index 0000000..2495e57 --- /dev/null +++ b/js/lib/util.ts @@ -0,0 +1,51 @@ +// just for develop + +export default { + json, + jsonP, + unJson, + yaml, + unYaml, + base64, + unBase64, + urlBase64, + unUrlBase64, + hex, + unHex, + aes, + unAes, + gzip, + gunzip, + id, + uniqueId, + token, + md5, + sha1, + sha256, + sha512, + tpl +} + +function json(data:any): string {return ''} +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 base64(data:any): string {return ''} +function unBase64(data:string): any {return null} +function urlBase64(data:any): string {return ''} +function unUrlBase64(data:string): any {return null} +function hex(data:any): string {return ''} +function unHex(data:string): any {return null} +function aes(data:any, key:string, iv:string): string {return ''} +function unAes(data:string, key:string, iv:string): any {return null} +function gzip(data:any): string {return ''} +function gunzip(data:string): any {return null} +function id(): string {return ''} +function uniqueId(): string {return ''} +function token(size:number): string {return ''} +function md5(data:any): string {return ''} +function sha1(data:any): string {return ''} +function sha256(data:any): string {return ''} +function sha512(data:any): string {return ''} +function tpl(text:string, data:any): string {return ''} diff --git a/llm/openai/gc.go b/llm/openai/gc.go index a042e1b..aa0c5de 100644 --- a/llm/openai/gc.go +++ b/llm/openai/gc.go @@ -31,21 +31,23 @@ func (lm *LLM) MakeImage(prompt string, config llm.GCConfig) ([]string, error) { if lm.config.Endpoint != "" { openaiConf.BaseURL = lm.config.Endpoint } + config.SetDefault(&lm.config.GCConfig) c := openai.NewClientWithConfig(openaiConf) style := openai.CreateImageStyleVivid if (!strings.Contains(prompt, "vivid") || !strings.Contains(prompt, "生动的")) && (strings.Contains(prompt, "natural") || strings.Contains(prompt, "自然的")) { style = openai.CreateImageStyleNatural } quality := openai.CreateImageQualityStandard - if strings.HasSuffix(config.Model, "-hd") { + model := config.GetModel() + if strings.HasSuffix(model, "-hd") { quality = openai.CreateImageQualityHD - config.Model = config.Model[0 : len(config.Model)-3] + model = model[0 : len(model)-3] } r, err := c.CreateImage(context.Background(), openai.ImageRequest{ Prompt: prompt, - Model: config.Model, + Model: model, Quality: quality, - Size: config.Size, + Size: config.GetSize(), Style: style, ResponseFormat: openai.CreateImageResponseFormatURL, }) diff --git a/llm/zhipu/gc.go b/llm/zhipu/gc.go index 82f404a..57bf486 100644 --- a/llm/zhipu/gc.go +++ b/llm/zhipu/gc.go @@ -24,10 +24,9 @@ func (lm *LLM) MakeImage(prompt string, config llm.GCConfig) ([]string, error) { return nil, err } + config.SetDefault(&lm.config.GCConfig) cc := c.ImageGeneration(config.Model).SetPrompt(prompt) - if config.Size != "" { - cc.SetSize(config.Size) - } + cc.SetSize(config.GetSize()) if r, err := cc.Do(context.Background()); err == nil { results := make([]string, 0) @@ -56,10 +55,9 @@ func (lm *LLM) MakeVideo(prompt string, config llm.GCConfig) ([]string, []string return nil, nil, err } + config.SetDefault(&lm.config.GCConfig) cc := c.VideoGeneration(config.Model).SetPrompt(prompt) - if config.Ref != "" { - cc.SetImageURL(config.Ref) - } + cc.SetImageURL(config.GetRef()) if resp, err := cc.Do(context.Background()); err == nil { for i := 0; i < 1200; i++ { diff --git a/tests/chat_test.go b/tests/chat_test.go index 44c171b..e58ce2a 100644 --- a/tests/chat_test.go +++ b/tests/chat_test.go @@ -4,6 +4,7 @@ import ( "apigo.cc/ai/ai" "apigo.cc/ai/ai/js" "apigo.cc/ai/ai/llm" + "encoding/hex" "fmt" "github.com/ssgo/u" "testing" @@ -32,8 +33,9 @@ func TestAgent(t *testing.T) { //testMakeImage(t, "glm", "", keys.Zhipu, "冬天大雪纷飞,一个男人身穿军绿色棉大衣,戴着红色围巾和绿色帽子走在铺面大雪的小镇路上", "") //testMakeVideo(t, "glm", "", keys.Zhipu, "大雪纷飞,男人蹦蹦跳跳", "https://aigc-files.bigmodel.cn/api/cogview/20240904133130c4b7121019724aa3_0.png") - testJS(t) + //testJS(t) //testFile(t) + testUtil(t) } func testChat(t *testing.T, llmName string) { @@ -196,7 +198,7 @@ func testMakeVideo(t *testing.T, llmName, prompt, refImage string) { } func testJS(t *testing.T) { - r1, err := js.RunFile("test.js", "1+2=4吗") + r1, err := ai.RunFile("test.js", "1+2=4吗") if err != nil { t.Fatal("发生错误", err.Error()) } @@ -209,7 +211,7 @@ func testJS(t *testing.T) { } func testFile(t *testing.T) { - r1, err := js.Run(` + r1, err := ai.Run(` import fs from './lib/file' import out from './lib/console' let r = fs.read('test.js') @@ -222,3 +224,28 @@ return r fmt.Println() fmt.Println("result:", r1) } + +func testUtil(t *testing.T) { + fmt.Println(hex.EncodeToString(u.Sha256([]byte{0, 98, 2}))) + r, err := ai.Run(` +let s = String.fromCharCode(0, 98, 2) +//let r = util.base64(s) +//let r2 = util.unBase64(r) +//console.info(r) +//console.info(r2.length, r2) +//console.info(s.charCodeAt(0), r2.charCodeAt(0)) +//return r2 === s +let r = util.hex(util.sha256(s)) +console.info(r) +return r == '278f25d5e7c09b2cdde1ed72dd83fbb7a7d7dd668ef967c9af65f68aa04049cd' +`, "test.js") + if err != nil { + t.Fatal("发生错误", err.Error()) + } + if r != true { + t.Fatal("运行结果不正确", r) + } + + fmt.Println() + fmt.Println("result:", r) +}