ai_old/js.go

429 lines
10 KiB
Go
Raw Permalink Normal View History

2024-09-18 18:29:21 +08:00
package ai
2024-09-17 18:44:21 +08:00
import (
"apigo.cc/ai/ai/goja"
"apigo.cc/ai/ai/goja_nodejs/require"
"apigo.cc/ai/ai/interface/llm"
2024-09-18 18:29:21 +08:00
"apigo.cc/ai/ai/js"
"apigo.cc/ai/ai/watcher"
2024-09-17 18:44:21 +08:00
"bytes"
_ "embed"
"encoding/json"
"errors"
"fmt"
"github.com/ssgo/log"
2024-09-17 18:44:21 +08:00
"github.com/ssgo/u"
"os"
"os/signal"
2024-09-17 18:44:21 +08:00
"path/filepath"
"regexp"
"strings"
"syscall"
2024-09-17 18:44:21 +08:00
"text/template"
"time"
2024-09-17 18:44:21 +08:00
)
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
//go:embed js/lib/log.ts
var logTS string
//go:embed js/lib/db.ts
var dbTS 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())
}
}
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())
}
}
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
err = rt.vm.Set("ai", js.RequireAI())
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{
"logger": log.New(u.ShortUniqueId()),
}
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)
modCode = importModMatcher.ReplaceAllString(modCode, "let $1 = require('$2')")
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)
_ = u.WriteFile(filepath.Join("lib", "log.ts"), logTS)
_ = u.WriteFile(filepath.Join("lib", "db.ts"), dbTS)
2024-09-17 18:44:21 +08:00
return `import {` + strings.Join(exports.LLMList, ", ") + `} from './lib/ai'
import console from './lib/console'
import log from './lib/log'
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'
import db from './lib/db'
2024-09-17 18:44:21 +08:00
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
}
}