2024-09-18 18:29:21 +08:00
|
|
|
|
package ai
|
2024-09-17 18:44:21 +08:00
|
|
|
|
|
|
|
|
|
import (
|
2024-09-24 13:34:32 +08:00
|
|
|
|
"apigo.cc/ai/ai/goja"
|
|
|
|
|
"apigo.cc/ai/ai/goja_nodejs/require"
|
2024-09-18 18:29:21 +08:00
|
|
|
|
"apigo.cc/ai/ai/js"
|
2024-09-17 18:44:21 +08:00
|
|
|
|
"apigo.cc/ai/ai/llm"
|
|
|
|
|
"bytes"
|
|
|
|
|
_ "embed"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"github.com/ssgo/u"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"regexp"
|
|
|
|
|
"strings"
|
|
|
|
|
"text/template"
|
|
|
|
|
)
|
|
|
|
|
|
2024-09-18 18:29:21 +08:00
|
|
|
|
//go:embed js/lib/ai.ts
|
2024-09-17 18:44:21 +08:00
|
|
|
|
var aiTS string
|
|
|
|
|
|
2024-09-18 18:29:21 +08:00
|
|
|
|
//go:embed js/lib/console.ts
|
2024-09-17 18:44:21 +08:00
|
|
|
|
var consoleTS string
|
|
|
|
|
|
2024-09-18 18:29:21 +08:00
|
|
|
|
//go:embed js/lib/file.ts
|
2024-09-17 18:44:21 +08:00
|
|
|
|
var fileTS string
|
|
|
|
|
|
2024-09-18 18:29:21 +08:00
|
|
|
|
//go:embed js/lib/util.ts
|
|
|
|
|
var utilTS string
|
|
|
|
|
|
2024-09-20 16:50:35 +08:00
|
|
|
|
//go:embed js/lib/http.ts
|
|
|
|
|
var httpTS string
|
|
|
|
|
|
2024-09-17 18:44:21 +08:00
|
|
|
|
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) {
|
2024-09-20 16:50:35 +08:00
|
|
|
|
rt := New()
|
2024-09-17 18:44:21 +08:00
|
|
|
|
var r any
|
2024-09-20 16:50:35 +08:00
|
|
|
|
_, err := rt.StartFromCode(code, refFile)
|
2024-09-17 18:44:21 +08:00
|
|
|
|
if err == nil {
|
2024-09-20 16:50:35 +08:00
|
|
|
|
r, err = rt.RunMain(args...)
|
2024-09-17 18:44:21 +08:00
|
|
|
|
}
|
|
|
|
|
return r, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var importModMatcher = regexp.MustCompile(`(?im)^\s*import\s+(.+?)\s+from\s+['"](.+?)['"]`)
|
|
|
|
|
var importLibMatcher = regexp.MustCompile(`(?im)^\s*(import)\s+(.+?)\s+from\s+['"][./\\\w:]+lib[/\\](.+?)(\.ts)?['"]`)
|
|
|
|
|
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*\(`)
|
|
|
|
|
|
2024-09-18 18:29:21 +08:00
|
|
|
|
type Runtime struct {
|
2024-09-20 16:50:35 +08:00
|
|
|
|
vm *goja.Runtime
|
|
|
|
|
required map[string]bool
|
|
|
|
|
file string
|
|
|
|
|
srcCode string
|
|
|
|
|
code string
|
|
|
|
|
moduleLoader func(string) string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (rt *Runtime) SetModuleLoader(fn func(filename string) string) {
|
|
|
|
|
rt.moduleLoader = fn
|
2024-09-17 18:44:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-09-23 18:15:02 +08:00
|
|
|
|
func (rt *Runtime) GetCallStack() []string {
|
|
|
|
|
callStacks := make([]string, 0)
|
|
|
|
|
for _, stack := range rt.vm.CaptureCallStack(0, nil) {
|
|
|
|
|
callStacks = append(callStacks, stack.Position().String())
|
|
|
|
|
}
|
|
|
|
|
return callStacks
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-18 18:29:21 +08:00
|
|
|
|
func (rt *Runtime) requireMod(name string) error {
|
2024-09-17 18:44:21 +08:00
|
|
|
|
var err error
|
|
|
|
|
if name == "console" || name == "" {
|
2024-09-18 18:29:21 +08:00
|
|
|
|
if !rt.required["console"] {
|
|
|
|
|
rt.required["console"] = true
|
|
|
|
|
err = rt.vm.Set("console", js.RequireConsole())
|
2024-09-17 18:44:21 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if err == nil && (name == "file" || name == "") {
|
2024-09-18 18:29:21 +08:00
|
|
|
|
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())
|
2024-09-17 18:44:21 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-09-20 16:50:35 +08:00
|
|
|
|
if err == nil && (name == "http" || name == "") {
|
|
|
|
|
if !rt.required["http"] {
|
|
|
|
|
rt.required["http"] = true
|
|
|
|
|
err = rt.vm.Set("http", js.RequireHTTP())
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-09-17 18:44:21 +08:00
|
|
|
|
if err == nil && (name == "ai" || name == "") {
|
2024-09-18 18:29:21 +08:00
|
|
|
|
if !rt.required["ai"] {
|
|
|
|
|
rt.required["ai"] = true
|
2024-09-17 18:44:21 +08:00
|
|
|
|
aiList := make(map[string]any)
|
|
|
|
|
for name, lm := range llm.List() {
|
2024-09-18 18:29:21 +08:00
|
|
|
|
aiList[name] = js.RequireAI(lm)
|
2024-09-17 18:44:21 +08:00
|
|
|
|
}
|
2024-09-18 18:29:21 +08:00
|
|
|
|
err = rt.vm.Set("ai", aiList)
|
2024-09-17 18:44:21 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-18 18:29:21 +08:00
|
|
|
|
func (rt *Runtime) makeImport(matcher *regexp.Regexp, code string) (string, int, error) {
|
2024-09-17 18:44:21 +08:00
|
|
|
|
var modErr error
|
|
|
|
|
importCount := 0
|
|
|
|
|
code = matcher.ReplaceAllStringFunc(code, func(str string) string {
|
|
|
|
|
if m := matcher.FindStringSubmatch(str); m != nil && len(m) > 3 {
|
|
|
|
|
optName := m[1]
|
|
|
|
|
if optName == "import" {
|
|
|
|
|
optName = "let"
|
|
|
|
|
}
|
|
|
|
|
varName := m[2]
|
|
|
|
|
modName := m[3]
|
|
|
|
|
importCount++
|
|
|
|
|
if modErr == nil {
|
2024-09-18 18:29:21 +08:00
|
|
|
|
if err := rt.requireMod(modName); err != nil {
|
2024-09-17 18:44:21 +08:00
|
|
|
|
modErr = err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if varName != modName {
|
|
|
|
|
return fmt.Sprintf("%s %s = %s", optName, varName, modName)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ""
|
|
|
|
|
})
|
|
|
|
|
return code, importCount, modErr
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 16:50:35 +08:00
|
|
|
|
func New() *Runtime {
|
2024-09-23 18:15:02 +08:00
|
|
|
|
vm := goja.New()
|
|
|
|
|
vm.GoData = map[string]any{}
|
2024-09-20 16:50:35 +08:00
|
|
|
|
return &Runtime{
|
2024-09-23 18:15:02 +08:00
|
|
|
|
vm: vm,
|
2024-09-20 16:50:35 +08:00
|
|
|
|
required: map[string]bool{},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (rt *Runtime) StartFromFile(file string) (any, error) {
|
|
|
|
|
return rt.StartFromCode(u.ReadFileN(file), file)
|
2024-09-17 18:44:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 16:50:35 +08:00
|
|
|
|
func (rt *Runtime) StartFromCode(code, refFile string) (any, error) {
|
|
|
|
|
if refFile != "" {
|
|
|
|
|
rt.file = refFile
|
|
|
|
|
}
|
|
|
|
|
if rt.file == "" {
|
|
|
|
|
rt.file = "main.js"
|
2024-09-17 18:44:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 16:50:35 +08:00
|
|
|
|
if absFile, err := filepath.Abs(rt.file); err == nil {
|
|
|
|
|
rt.file = absFile
|
2024-09-17 18:44:21 +08:00
|
|
|
|
}
|
2024-09-23 18:15:02 +08:00
|
|
|
|
refPath := filepath.Dir(refFile)
|
|
|
|
|
rt.vm.GoData["startPath"] = refPath
|
2024-09-17 18:44:21 +08:00
|
|
|
|
|
2024-09-23 18:15:02 +08:00
|
|
|
|
InitFrom(refPath)
|
2024-09-17 18:44:21 +08:00
|
|
|
|
|
2024-09-20 16:50:35 +08:00
|
|
|
|
if rt.srcCode == "" {
|
|
|
|
|
rt.srcCode = code
|
2024-09-17 18:44:21 +08:00
|
|
|
|
}
|
2024-09-20 16:50:35 +08:00
|
|
|
|
rt.code = code
|
2024-09-17 18:44:21 +08:00
|
|
|
|
|
|
|
|
|
// 按需加载引用
|
|
|
|
|
var importCount int
|
|
|
|
|
var modErr error
|
2024-09-18 18:29:21 +08:00
|
|
|
|
rt.code, importCount, modErr = rt.makeImport(importLibMatcher, rt.code)
|
2024-09-17 18:44:21 +08:00
|
|
|
|
if modErr == nil {
|
|
|
|
|
importCount1 := importCount
|
2024-09-18 18:29:21 +08:00
|
|
|
|
rt.code, importCount, modErr = rt.makeImport(requireLibMatcher, rt.code)
|
2024-09-17 18:44:21 +08:00
|
|
|
|
importCount += importCount1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将 import 转换为 require
|
2024-09-18 18:29:21 +08:00
|
|
|
|
rt.code = importModMatcher.ReplaceAllString(rt.code, "let $1 = require('$2')")
|
2024-09-17 18:44:21 +08:00
|
|
|
|
|
|
|
|
|
// 如果没有import,默认import所有
|
|
|
|
|
if modErr == nil && importCount == 0 {
|
2024-09-18 18:29:21 +08:00
|
|
|
|
modErr = rt.requireMod("")
|
2024-09-17 18:44:21 +08:00
|
|
|
|
}
|
|
|
|
|
if modErr != nil {
|
|
|
|
|
return nil, modErr
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-18 18:29:21 +08:00
|
|
|
|
//fmt.Println(u.BCyan(rt.code))
|
2024-09-17 18:44:21 +08:00
|
|
|
|
|
|
|
|
|
// 处理模块引用
|
|
|
|
|
require.NewRegistryWithLoader(func(path string) ([]byte, error) {
|
2024-09-23 18:15:02 +08:00
|
|
|
|
modFile := path
|
|
|
|
|
if !filepath.IsAbs(modFile) {
|
|
|
|
|
modFile = filepath.Join(filepath.Dir(rt.file), modFile)
|
|
|
|
|
}
|
|
|
|
|
if !strings.HasSuffix(modFile, ".js") && !u.FileExists(modFile) {
|
|
|
|
|
modFile += ".js"
|
2024-09-17 18:44:21 +08:00
|
|
|
|
}
|
2024-09-20 16:50:35 +08:00
|
|
|
|
modCode := ""
|
|
|
|
|
if rt.moduleLoader != nil {
|
2024-09-23 18:15:02 +08:00
|
|
|
|
modCode = rt.moduleLoader(modFile)
|
2024-09-20 16:50:35 +08:00
|
|
|
|
}
|
|
|
|
|
if modCode == "" {
|
|
|
|
|
var err error
|
2024-09-23 18:15:02 +08:00
|
|
|
|
modCode, err = u.ReadFile(modFile)
|
2024-09-20 16:50:35 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-09-17 18:44:21 +08:00
|
|
|
|
}
|
2024-09-18 18:29:21 +08:00
|
|
|
|
modCode, _, _ = rt.makeImport(importLibMatcher, modCode)
|
|
|
|
|
modCode, _, _ = rt.makeImport(requireLibMatcher, modCode)
|
2024-09-17 18:44:21 +08:00
|
|
|
|
return []byte(modCode), modErr
|
2024-09-18 18:29:21 +08:00
|
|
|
|
}).Enable(rt.vm)
|
2024-09-17 18:44:21 +08:00
|
|
|
|
|
|
|
|
|
// 初始化主函数
|
2024-09-18 18:29:21 +08:00
|
|
|
|
if !checkMainMatcher.MatchString(rt.code) {
|
|
|
|
|
rt.code = "function main(...args){" + rt.code + "}"
|
2024-09-17 18:44:21 +08:00
|
|
|
|
}
|
2024-09-23 18:15:02 +08:00
|
|
|
|
if r, err := rt.vm.RunScript(rt.file, rt.code); err != nil {
|
2024-09-17 18:44:21 +08:00
|
|
|
|
return nil, err
|
2024-09-20 16:50:35 +08:00
|
|
|
|
} else {
|
|
|
|
|
return r, nil
|
2024-09-17 18:44:21 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 16:50:35 +08:00
|
|
|
|
func (rt *Runtime) RunMain(args ...any) (any, error) {
|
2024-09-17 18:44:21 +08:00
|
|
|
|
// 解析参数
|
|
|
|
|
for i, arg := range args {
|
|
|
|
|
if str, ok := arg.(string); ok {
|
|
|
|
|
var v interface{}
|
|
|
|
|
if err := json.Unmarshal([]byte(str), &v); err == nil {
|
|
|
|
|
args[i] = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-18 18:29:21 +08:00
|
|
|
|
if err := rt.vm.Set("__args", args); err != nil {
|
2024-09-17 18:44:21 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-09-23 18:15:02 +08:00
|
|
|
|
jsResult, err := rt.vm.RunScript("main", "main(...__args)")
|
2024-09-17 18:44:21 +08:00
|
|
|
|
|
|
|
|
|
var result any
|
|
|
|
|
if err == nil {
|
|
|
|
|
if jsResult != nil && !jsResult.Equals(goja.Undefined()) {
|
|
|
|
|
result = jsResult.Export()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result, err
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 16:50:35 +08:00
|
|
|
|
func (rt *Runtime) RunCode(code string) (any, error) {
|
|
|
|
|
jsResult, err := rt.vm.RunScript(rt.file, code)
|
|
|
|
|
|
|
|
|
|
var result any
|
|
|
|
|
if err == nil {
|
|
|
|
|
if jsResult != nil && !jsResult.Equals(goja.Undefined()) {
|
|
|
|
|
result = jsResult.Export()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result, err
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-17 18:44:21 +08:00
|
|
|
|
type Exports struct {
|
|
|
|
|
LLMList []string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ExportForDev() (string, error) {
|
2024-09-18 18:29:21 +08:00
|
|
|
|
Init()
|
2024-09-17 18:44:21 +08:00
|
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
exports := Exports{}
|
|
|
|
|
for name, _ := range llm.List() {
|
|
|
|
|
exports.LLMList = append(exports.LLMList, name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
exportFile := filepath.Join("lib", "ai.ts")
|
|
|
|
|
var tpl *template.Template
|
|
|
|
|
var err error
|
|
|
|
|
if tpl, err = template.New(exportFile).Parse(aiTS); err == nil {
|
|
|
|
|
buf := bytes.NewBuffer(make([]byte, 0))
|
|
|
|
|
if err = tpl.Execute(buf, exports); err == nil {
|
|
|
|
|
err = u.WriteFileBytes(exportFile, buf.Bytes())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_ = u.WriteFile(filepath.Join("lib", "console.ts"), consoleTS)
|
|
|
|
|
_ = u.WriteFile(filepath.Join("lib", "file.ts"), fileTS)
|
2024-09-18 18:29:21 +08:00
|
|
|
|
_ = u.WriteFile(filepath.Join("lib", "util.ts"), utilTS)
|
2024-09-20 16:50:35 +08:00
|
|
|
|
_ = u.WriteFile(filepath.Join("lib", "http.ts"), httpTS)
|
2024-09-17 18:44:21 +08:00
|
|
|
|
|
|
|
|
|
return `import {` + strings.Join(exports.LLMList, ", ") + `} from './lib/ai'
|
|
|
|
|
import console from './lib/console'
|
2024-09-18 18:29:21 +08:00
|
|
|
|
import util from './lib/util'
|
2024-09-20 16:50:35 +08:00
|
|
|
|
import http from './lib/http'
|
2024-09-17 18:44:21 +08:00
|
|
|
|
import file from './lib/file'`, nil
|
|
|
|
|
}
|