Refactor UI rendering and add support for web UI
This commit is contained in:
parent
14a97da330
commit
52a008bbcb
5 changed files with 157 additions and 74 deletions
135
pipe/context.go
135
pipe/context.go
|
|
@ -49,6 +49,47 @@ func Close(id string) {
|
|||
contexts.Delete(id)
|
||||
}
|
||||
|
||||
// Resume the context by id
|
||||
func Resume(id string, args ...any) any {
|
||||
ctx := Open(id)
|
||||
v, err := ctx.resume(args...)
|
||||
if err != nil {
|
||||
exception.New("pipe: %s %s", 500, ctx.Name, err).Throw()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// resume the context by id
|
||||
func (ctx *Context) resume(args ...any) (any, error) {
|
||||
if ctx.current == nil {
|
||||
return nil, ctx.Errorf("pipe %s has no nodes", ctx.Name)
|
||||
}
|
||||
|
||||
node := ctx.current
|
||||
output, err := ctx.parseNodeOutput(node, args)
|
||||
if err != nil {
|
||||
return nil, node.Errorf(ctx, err.Error())
|
||||
}
|
||||
|
||||
// Next node
|
||||
next, eof, err := ctx.next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// End of the pipe
|
||||
if eof {
|
||||
defer Close(ctx.id)
|
||||
output, err := ctx.parseOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
return ctx.exec(next, anyToInput(output))
|
||||
}
|
||||
|
||||
// Run the pipe
|
||||
func (ctx *Context) Run(args ...any) any {
|
||||
v, err := ctx.Exec(args...)
|
||||
|
|
@ -58,53 +99,6 @@ func (ctx *Context) Run(args ...any) any {
|
|||
return v
|
||||
}
|
||||
|
||||
// ID the context id
|
||||
func (ctx *Context) ID() string {
|
||||
return ctx.id
|
||||
}
|
||||
|
||||
// Next the next node
|
||||
func (ctx *Context) Next() (*Node, bool, error) {
|
||||
|
||||
if ctx.current == nil {
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
// if the goto is not empty, then goto the node
|
||||
if ctx.current.Goto != "" {
|
||||
data := ctx.data(ctx.current)
|
||||
next, err := data.replaceString(ctx.current.Goto)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if next == "EOF" {
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
var has = false
|
||||
ctx.current, has = ctx.mapping[next]
|
||||
if !has {
|
||||
return nil, false, ctx.Errorf("node %s not found", next)
|
||||
}
|
||||
return ctx.current, false, nil
|
||||
}
|
||||
|
||||
// continue to the next node
|
||||
next := ctx.current.index + 1
|
||||
if next >= len(ctx.Nodes) {
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
ctx.current = &ctx.Nodes[next]
|
||||
return ctx.current, false, nil
|
||||
}
|
||||
|
||||
// IsEOF check if the error is EOF
|
||||
func IsEOF(err error) bool {
|
||||
return err != nil && err.Error() == "EOF"
|
||||
}
|
||||
|
||||
// Exec this is the entry point of the pipe
|
||||
func (ctx *Context) Exec(args ...any) (any, error) {
|
||||
if ctx.current == nil {
|
||||
|
|
@ -150,17 +144,23 @@ func (ctx *Context) exec(node *Node, input Input) (output any, err error) {
|
|||
}
|
||||
|
||||
case "user-input":
|
||||
out, err = node.Render(ctx, input)
|
||||
var pause bool = false
|
||||
out, pause, err = node.Render(ctx, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Pause the pipe waiting for user input
|
||||
if pause {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, node.Errorf(ctx, "type '%s' not support", node.Type)
|
||||
}
|
||||
|
||||
// Execute the next node
|
||||
next, eof, err := ctx.Next()
|
||||
next, eof, err := ctx.next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -180,6 +180,43 @@ func (ctx *Context) exec(node *Node, input Input) (output any, err error) {
|
|||
return ctx.exec(next, anyToInput(out))
|
||||
}
|
||||
|
||||
// Next the next node
|
||||
func (ctx *Context) next() (*Node, bool, error) {
|
||||
|
||||
if ctx.current == nil {
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
// if the goto is not empty, then goto the node
|
||||
if ctx.current.Goto != "" {
|
||||
data := ctx.data(ctx.current)
|
||||
next, err := data.replaceString(ctx.current.Goto)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if next == "EOF" {
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
var has = false
|
||||
ctx.current, has = ctx.mapping[next]
|
||||
if !has {
|
||||
return nil, false, ctx.Errorf("node %s not found", next)
|
||||
}
|
||||
return ctx.current, false, nil
|
||||
}
|
||||
|
||||
// continue to the next node
|
||||
next := ctx.current.index + 1
|
||||
if next >= len(ctx.Nodes) {
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
ctx.current = &ctx.Nodes[next]
|
||||
return ctx.current, false, nil
|
||||
}
|
||||
|
||||
// ParseNodeInput parse the node input
|
||||
func (ctx *Context) parseNodeInput(node *Node, input Input) (Input, error) {
|
||||
ctx.in[node] = input
|
||||
|
|
|
|||
29
pipe/node.go
29
pipe/node.go
|
|
@ -209,18 +209,33 @@ func (node *Node) aiMergeHistory(ctx *Context, prompts []Prompt) []Prompt {
|
|||
}
|
||||
|
||||
// Render Execute the user input
|
||||
func (node *Node) Render(ctx *Context, input Input) (any, error) {
|
||||
func (node *Node) Render(ctx *Context, input Input) (any, bool, error) {
|
||||
|
||||
switch node.UI {
|
||||
|
||||
case "cli":
|
||||
return node.renderCli(ctx, input)
|
||||
output, err := node.renderCli(ctx, input)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return output, false, nil
|
||||
|
||||
case "web":
|
||||
default:
|
||||
input, err := ctx.parseNodeInput(node, input)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
|
||||
return ResumeContext{
|
||||
ID: ctx.id,
|
||||
Input: input,
|
||||
Node: node,
|
||||
Data: ctx.data(node),
|
||||
Type: node.Type,
|
||||
UI: node.UI,
|
||||
}, true, nil
|
||||
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("pipe: %s %s", ctx.Name, "node type error")
|
||||
}
|
||||
|
||||
func (node *Node) renderCli(ctx *Context, input Input) (any, error) {
|
||||
|
|
@ -255,12 +270,12 @@ func (node *Node) renderCli(ctx *Context, input Input) (any, error) {
|
|||
|
||||
lines, err := cli.New(option).Render(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, node.Errorf(ctx, err.Error())
|
||||
}
|
||||
|
||||
output, err := ctx.parseNodeOutput(node, lines)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, node.Errorf(ctx, err.Error())
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package pipe
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -15,10 +14,10 @@ import (
|
|||
"github.com/yaoapp/yao/test"
|
||||
)
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
func TestRunCli(t *testing.T) {
|
||||
prepare(t)
|
||||
defer test.Clean()
|
||||
translator, err := Get("translator")
|
||||
translator, err := Get("cli.translator")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -31,7 +30,8 @@ func TestRun(t *testing.T) {
|
|||
With(context).
|
||||
WithGlobal(map[string]interface{}{"foo": "bar"}).
|
||||
WithSid(sid)
|
||||
defer Close(ctx.ID())
|
||||
defer Close(ctx.id)
|
||||
|
||||
output, err := ctx.Exec(map[string]interface{}{"placeholder": "translate\nhello world"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -43,16 +43,49 @@ func TestRun(t *testing.T) {
|
|||
assert.True(t, res.Has("output"))
|
||||
assert.True(t, res.Has("sid"))
|
||||
assert.True(t, res.Has("switch"))
|
||||
|
||||
assert.Equal(t, "bar", res.Get("global.foo"))
|
||||
assert.Equal(t, "translate\nhello world", res.Get("input[0].placeholder"))
|
||||
assert.Len(t, res.Get("switch"), 2)
|
||||
}
|
||||
|
||||
func TestRunWeb(t *testing.T) {
|
||||
prepare(t)
|
||||
defer test.Clean()
|
||||
translator, err := Get("web.translator")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sid := session.ID()
|
||||
context, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
ctx := translator.
|
||||
Create().
|
||||
With(context).
|
||||
WithGlobal(map[string]interface{}{"foo": "bar"}).
|
||||
WithSid(sid)
|
||||
defer Close(ctx.id)
|
||||
|
||||
web := ctx.Run("hello web world")
|
||||
resume := web.(ResumeContext)
|
||||
assert.Equal(t, Input{"hello web world"}, resume.Input)
|
||||
|
||||
output := Resume(resume.ID, "translate", "hello web world")
|
||||
|
||||
res := any.Of(output).Map().MapStrAny.Dot()
|
||||
assert.True(t, res.Has("global"))
|
||||
assert.True(t, res.Has("input"))
|
||||
assert.True(t, res.Has("output"))
|
||||
assert.True(t, res.Has("sid"))
|
||||
assert.True(t, res.Has("switch"))
|
||||
assert.Equal(t, "bar", res.Get("global.foo"))
|
||||
assert.Equal(t, "hello web world", res.Get("input[0]"))
|
||||
assert.Len(t, res.Get("switch"), 2)
|
||||
}
|
||||
|
||||
func prepare(t *testing.T) {
|
||||
test.Prepare(t, config.Conf)
|
||||
mirror := os.Getenv("TEST_MOAPI_MIRROR")
|
||||
fmt.Println(mirror)
|
||||
secret := os.Getenv("TEST_MOAPI_SECRET")
|
||||
share.App = share.AppInfo{
|
||||
Moapi: share.Moapi{Channel: "stable", Mirrors: []string{mirror}, Secret: secret},
|
||||
|
|
|
|||
|
|
@ -77,6 +77,16 @@ type Args []any
|
|||
// Data data for the template
|
||||
type Data map[string]interface{}
|
||||
|
||||
// ResumeContext the resume context
|
||||
type ResumeContext struct {
|
||||
ID string `json:"__id"`
|
||||
Type string `json:"__type"`
|
||||
UI string `json:"__ui"`
|
||||
Input Input `json:"input"`
|
||||
Node *Node `json:"node"`
|
||||
Data Data `json:"data"`
|
||||
}
|
||||
|
||||
// AutoFill the autofill
|
||||
type AutoFill struct {
|
||||
Value any `json:"value"`
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
package web
|
||||
|
||||
// Web the web UI
|
||||
type Web struct{}
|
||||
|
||||
// Option the web option
|
||||
type Option struct{}
|
||||
|
||||
// Render the Web UI
|
||||
func (web *Web) Render(args []any, option Option) error {
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue