Skip to content

[Feature requets] Support ToGraph work with PermitDynamic correctly #96

@mix5003

Description

@mix5003

for now when use PermitDynamic. and gen graph with sm.ToGraph()
it will not connect state node in PermitDynamic.

this code gen graph like this

and i expected to be like this . (BOT_MENU Connect with BOT_SELECT1,BOT_SELECT2,TO_CUSTOMER_SUPPORT)

i think this should use something like go generate or go tools for gen graph, because it can not determine which state should be connect at runtime. or may be it should make PermitDynamic to accept connectable state as params too.

Example code

package main

import (
	"bufio"
	"context"
	"fmt"
	"os"
	"strings"

	"github.com/qmuntal/stateless"
)

const (
	StateOpen              = "OPEN"
	StateBotMenu           = "BOT_MENU"
	StateBotSelect1        = "BOT_SELECT1"
	StateBotSelect2        = "BOT_SELECT2"
	StateToCustomerSupport = "TO_CUSTOMER_SUPPORT"
	StateClosed            = "CLOSED"
)

const (
	BotCompleted     = "BOT_COMPLETED"
	TriggerUserInput = "USER_INPUT"
)

var currentState stateless.State = StateOpen

func NewChatbot() *stateless.StateMachine {
	sm := stateless.NewStateMachineWithExternalStorage(
		func(ctx context.Context) (stateless.State, error) {
			return currentState, nil
		},
		func(ctx context.Context, state stateless.State) error {
			currentState = state.(string)
			return nil
		},
		stateless.FiringImmediate,
	)

	sm.Configure(StateOpen).
		Permit(TriggerUserInput, StateBotMenu)

	sm.Configure(StateBotMenu).
		OnEntry(func(ctx context.Context, args ...any) error {
			fmt.Println(">> Menu optins")
			fmt.Println("   1. Select 1")
			fmt.Println("   2. Select 2")
			fmt.Println("   3. Other")
			return nil
		}).
		PermitDynamic(TriggerUserInput, func(_ context.Context, args ...any) (stateless.State, error) {
			input := args[0].(string)
			switch input {
			case "1":
				return StateBotSelect1, nil
			case "2":
				return StateBotSelect2, nil
			default:
				return StateToCustomerSupport, nil
			}
		})

	// Configure State: StateBotSelect1
	sm.Configure(StateBotSelect1).
		OnEntry(func(ctx context.Context, args ...any) error {
			fmt.Println(">> Menu 1 selected")
			return sm.Fire(BotCompleted)
		}).
		Permit(BotCompleted, StateClosed)

	// Configure State: StateBotSelect2
	sm.Configure(StateBotSelect2).
		OnEntry(func(ctx context.Context, args ...any) error {
			fmt.Println(">> Menu 2 selected")
			return sm.Fire(BotCompleted)
		}).
		Permit(BotCompleted, StateClosed)

	// Configure State: TO_CUSTOMER_SUPPORT
	sm.Configure(StateToCustomerSupport).
		OnEntry(func(ctx context.Context, args ...any) error {
			fmt.Println(">> Redirect to Human  (to stop simulation type 'END')")
			return nil
		}).
		PermitDynamic(TriggerUserInput, func(_ context.Context, args ...any) (stateless.State, error) {
			input := args[0].(string)
			if strings.ToUpper(input) == "END" {
				return StateClosed, nil
			}
			fmt.Println(">> Human response (to stop simulation type 'END')")
			return StateToCustomerSupport, nil
		})

	// Configure State: CLOSED
	sm.Configure(StateClosed).
		OnEntry(func(ctx context.Context, args ...any) error {
			fmt.Println(">> End Conversation")
			return nil
		}).
		Permit(TriggerUserInput, StateOpen)

	return sm
}

func main() {
	fmt.Println("Chatbot is starting...")
	chatbot := NewChatbot()
	if err := chatbot.Fire(TriggerUserInput, ""); err != nil {
		fmt.Printf("Error starting chatbot: %v\n", err)
		return
	}

	fmt.Println(chatbot.ToGraph())

	scanner := bufio.NewScanner(os.Stdin)
	fmt.Print("> ")

	for scanner.Scan() {
		input := scanner.Text()

		chatbot = NewChatbot()
		if err := chatbot.Fire(TriggerUserInput, input); err != nil {
			fmt.Printf("Error processing input: %v\n", err)
		}

		fmt.Print("> ")
	}

	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading standard input:", err)
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions