Merge pull request #399 from trheyi/main
[add] Neo command prepare hook
This commit is contained in:
commit
1357415586
4 changed files with 211 additions and 3 deletions
4
go.mod
4
go.mod
|
|
@ -22,6 +22,8 @@ require (
|
|||
github.com/yaoapp/kun v0.9.0
|
||||
github.com/yaoapp/xun v0.9.0
|
||||
golang.org/x/crypto v0.7.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
rogchap.com/v8go v0.8.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
@ -106,8 +108,6 @@ require (
|
|||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
rogchap.com/v8go v0.8.0 // indirect
|
||||
)
|
||||
|
||||
// go env -w GOPRIVATE=github.com/yaoapp/*
|
||||
|
|
|
|||
49
neo/command/prompt.go
Normal file
49
neo/command/prompt.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/yaoapp/gou/helper"
|
||||
"github.com/yaoapp/kun/maps"
|
||||
)
|
||||
|
||||
// Replace the prompt with the context
|
||||
func (prompt Prompt) Replace(data maps.MapStrAny) Prompt {
|
||||
|
||||
v := map[string]interface{}{"role": prompt.Role, "content": prompt.Content}
|
||||
if prompt.Name != "" {
|
||||
v["name"] = prompt.Name
|
||||
}
|
||||
|
||||
replaced := helper.Bind(v, data)
|
||||
res, ok := replaced.(map[string]interface{})
|
||||
if !ok {
|
||||
return prompt
|
||||
}
|
||||
|
||||
if res["role"] == nil {
|
||||
prompt.Role = ""
|
||||
} else if role, ok := res["role"].(string); ok {
|
||||
prompt.Role = role
|
||||
}
|
||||
|
||||
if res["name"] == nil {
|
||||
prompt.Name = ""
|
||||
} else if name, ok := res["name"].(string); ok {
|
||||
prompt.Name = name
|
||||
}
|
||||
|
||||
switch content := res["content"].(type) {
|
||||
case string:
|
||||
prompt.Content = content
|
||||
|
||||
default:
|
||||
if content == nil {
|
||||
prompt.Content = ""
|
||||
|
||||
} else if bytes, err := jsoniter.Marshal(content); err == nil {
|
||||
prompt.Content = string(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
return prompt
|
||||
}
|
||||
39
neo/command/prompt_test.go
Normal file
39
neo/command/prompt_test.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/kun/maps"
|
||||
)
|
||||
|
||||
func TestPromptReplace(t *testing.T) {
|
||||
|
||||
prompt := Prompt{
|
||||
Role: "{{ role }}",
|
||||
Name: "{{ name }}",
|
||||
Content: "{{ content }}",
|
||||
}
|
||||
|
||||
data := maps.Of(map[string]interface{}{
|
||||
"role": "User",
|
||||
"name": "Name",
|
||||
"content": "- Content\n",
|
||||
}).Dot()
|
||||
|
||||
prompt = prompt.Replace(data)
|
||||
assert.Equal(t, "User", prompt.Role)
|
||||
assert.Equal(t, "Name", prompt.Name)
|
||||
assert.Equal(t, "- Content\n", prompt.Content)
|
||||
|
||||
prompt = Prompt{
|
||||
Role: "Role",
|
||||
Name: "{{ notfound }}",
|
||||
Content: "{{ content }}",
|
||||
}
|
||||
|
||||
prompt = prompt.Replace(data)
|
||||
assert.Equal(t, "Role", prompt.Role)
|
||||
assert.Equal(t, "", prompt.Name)
|
||||
assert.Equal(t, "- Content\n", prompt.Content)
|
||||
}
|
||||
|
|
@ -2,15 +2,30 @@ package command
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
v8 "github.com/yaoapp/gou/runtime/v8"
|
||||
"github.com/yaoapp/kun/maps"
|
||||
"github.com/yaoapp/kun/utils"
|
||||
"github.com/yaoapp/yao/neo/message"
|
||||
"rogchap.com/v8go"
|
||||
)
|
||||
|
||||
// Run the command
|
||||
func (req *Request) Run(messages []map[string]interface{}, cb func(msg *message.JSON) int) (interface{}, error) {
|
||||
|
||||
prompts, err := req.prepare(messages, cb)
|
||||
if err != nil {
|
||||
cb(req.msg().Text(fmt.Sprintf("Prepare Before Error: %s\n", err.Error())))
|
||||
cb(message.New().Done())
|
||||
req.Done()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
utils.Dump(prompts)
|
||||
|
||||
cb(req.msg().Text(fmt.Sprintf("- Command: %s\n", req.Command.Name)))
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
|
|
@ -24,6 +39,111 @@ func (req *Request) Run(messages []map[string]interface{}, cb func(msg *message.
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
// RunPrepare the command
|
||||
func (req *Request) prepare(messages []map[string]interface{}, cb func(msg *message.JSON) int) ([]Prompt, error) {
|
||||
|
||||
data, err := req.prepareBefore(messages, cb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// replace the pro
|
||||
prompts := []Prompt{}
|
||||
if req.Command.Prepare.Prompts != nil && len(req.Command.Prepare.Prompts) > 0 {
|
||||
prompts = append(prompts, req.Command.Prepare.Prompts...)
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
data = maps.Of(data).Dot()
|
||||
for i, prompt := range prompts {
|
||||
prompts[i] = prompt.Replace(data)
|
||||
}
|
||||
}
|
||||
|
||||
return prompts, nil
|
||||
}
|
||||
|
||||
// prepareBefore hook
|
||||
func (req *Request) prepareBefore(messages []map[string]interface{}, cb func(msg *message.JSON) int) (map[string]interface{}, error) {
|
||||
|
||||
if req.Prepare.Before == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// prepare the args
|
||||
args := []interface{}{
|
||||
map[string]interface{}{"stack": req.ctx.Stack, "path": req.ctx.Path},
|
||||
messages,
|
||||
}
|
||||
|
||||
return req.runScript(req.Prepare.Before, args, cb)
|
||||
}
|
||||
|
||||
func (req *Request) runScript(id string, args []interface{}, cb func(msg *message.JSON) int) (map[string]interface{}, error) {
|
||||
|
||||
namer := strings.Split(id, ".")
|
||||
method := namer[len(namer)-1]
|
||||
scriptID := strings.Join(namer[1:len(namer)-1], ".")
|
||||
script, err := v8.Select(scriptID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// make a new script context
|
||||
v8ctx, err := script.NewContext(req.sid, map[string]interface{}{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer v8ctx.Close()
|
||||
|
||||
// make a new bridge function ssWrite
|
||||
ssWriteT := v8go.NewFunctionTemplate(v8ctx.Isolate(), func(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||
args := info.Args()
|
||||
if len(args) != 1 {
|
||||
return v8go.Null(v8ctx.Isolate())
|
||||
}
|
||||
|
||||
text := args[0].String()
|
||||
cb(req.msg().Text(text))
|
||||
return v8go.Null(v8ctx.Isolate())
|
||||
})
|
||||
|
||||
// make a new bridge function done
|
||||
doneT := v8go.NewFunctionTemplate(v8ctx.Isolate(), func(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||
args := info.Args()
|
||||
if len(args) == 1 {
|
||||
text := args[0].String()
|
||||
cb(req.msg().Text(text))
|
||||
}
|
||||
|
||||
cb(message.New().Done())
|
||||
req.Done()
|
||||
return v8go.Null(v8ctx.Isolate())
|
||||
})
|
||||
|
||||
v8ctx.Global().Set("ssWrite", ssWriteT.GetFunction(v8ctx.Context))
|
||||
v8ctx.Global().Set("done", doneT.GetFunction(v8ctx.Context))
|
||||
|
||||
res, err := v8ctx.CallWith(req.ctx, method, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return data
|
||||
switch v := res.(type) {
|
||||
|
||||
case bool:
|
||||
return nil, nil
|
||||
|
||||
case map[string]interface{}:
|
||||
return v, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("script return type is not supported")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (req *Request) msg() *message.JSON {
|
||||
return message.New().Command(req.Command.Name, req.Command.ID, req.id)
|
||||
}
|
||||
|
|
@ -70,7 +190,7 @@ func (cmd *Command) NewRequest(ctx Context) (*Request, error) {
|
|||
|
||||
// Done the request done
|
||||
func (req *Request) Done() {
|
||||
if DefaultStore == nil {
|
||||
if DefaultStore != nil {
|
||||
DefaultStore.DelRequest(req.sid)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue