123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- package main
- import (
- "bytes"
- "embed"
- "encoding/json"
- "fmt"
- "html/template"
- "io/ioutil"
- "log"
- "net/http"
- "strings"
- "time"
- "git.riomhaire.com/gremlin/elizaservice/eliza"
- "git.riomhaire.com/gremlin/elizaservice/facades/cache"
- "git.riomhaire.com/gremlin/elizaservice/infrastructure"
- "github.com/gofrs/uuid"
- "github.com/jaffee/commandeer"
- "github.com/labstack/echo/v4"
- "github.com/labstack/echo/v4/middleware"
- )
- //go:embed web/*
- var web embed.FS
- //go:embed templates/*
- var templates embed.FS
- //go:embed bots/*
- var bots embed.FS
- var cacheManager cache.BotCache
- var cacheTTL int
- /***********************************************************************************************
- *
- ***********************************************************************************************/
- type Main struct {
- BotName string `help:"What This Bot is Called."`
- Port int `help:"What port bot is listening too"`
- Cache string `help:"What Cache configuration to use - redis format <host>:<port>:<db>"`
- CacheTTL int `help:"How long cache is allowed to store data in seconds"`
- }
- /***********************************************************************************************
- *
- ***********************************************************************************************/
- func NewMain() *Main {
- return &Main{
- BotName: "Eliza",
- Port: 8070,
- Cache: "empire:6379:1",
- CacheTTL: 3600,
- }
- }
- /********************************
- This appends to session in cache latest info
- *********************************/
- func storeConversation(sessionID string, sessionData eliza.SessionData) {
- // Save it as json
- j, _ := json.Marshal(sessionData)
- cacheManager.Store(sessionID, string(j), cacheTTL)
- }
- /********************************
- This retrieves the context from the cache
- *********************************/
- func retrieveConversation(sessionID, bot, botversion, user string) (sessionData eliza.SessionData) {
- // Lookup session
- jsonValue, found, _ := cacheManager.Retrieve(sessionID, false)
- if !found {
- sessionData = eliza.SessionData{SessionID: sessionID, Bot: bot, BotVersion: botversion, User: user, StartTime: time.Now().UTC().String(), Conversation: make([]eliza.ChatbotInteraction, 0)}
- }
- if found {
- if err := json.Unmarshal([]byte(jsonValue), &sessionData); err != nil {
- sessionData = eliza.SessionData{SessionID: sessionID, Bot: bot, BotVersion: botversion, User: user, StartTime: time.Now().UTC().String(), Conversation: make([]eliza.ChatbotInteraction, 0)}
- }
- }
- return
- }
- /***********************************************************************************************
- * This at the moment ... but it could be one passed in via LTI if available
- ***********************************************************************************************/
- func createSession(c echo.Context) string {
- guid, _ := uuid.NewV4()
- sessionID := guid.String()
- return sessionID
- }
- /***********************************************************************************************
- *
- ***********************************************************************************************/
- func findPersonality(name string) (personality eliza.Personality, err error) {
- filename := "bots/" + name + ".json"
- jsonFile, err := bots.Open(filename)
- // if we os.Open returns an error then handle it
- if err != nil {
- return
- }
- // fmt.Println("Successfully Opened ", filename)
- byteValue, _ := ioutil.ReadAll(jsonFile)
- // we initialize our Users array
- personality = eliza.Personality{}
- // we unmarshal our byteArray which contains our
- // jsonFile's content into 'users' which we defined above
- json.Unmarshal(byteValue, &personality)
- // defer the closing of our jsonFile so that we can parse it later on
- defer jsonFile.Close()
- return
- }
- /***********************************************************************************************
- *
- ***********************************************************************************************/
- func chantboInteractiontEndpoint(c echo.Context) error {
- // Extract question from GET request
- user := c.QueryParam("user")
- question := c.QueryParam("value")
- botname := c.QueryParam("bot")
- sessionID := c.QueryParam("session")
- // default to eliza
- if len(botname) == 0 {
- botname = "eliza"
- }
- personality, err := findPersonality(strings.ToLower(botname)) // files are in lowercase
- if err != nil {
- return c.String(http.StatusOK, err.Error())
- }
- // Restore Conversation Context
- sessionData := retrieveConversation(sessionID, botname, personality.Version, user)
- // Return Eliza's response's so long as the user doesn't give a quit statement
- chatboxContext := eliza.ChatbotContext{infrastructure.Version, sessionData}
- character := eliza.NewBotPersonality(&personality, &chatboxContext)
- botResponse := character.ReplyTo(question)
- msg := fmt.Sprintf("Bot [%s %s] Session ID [%s] Question [%s] Answer [%s]", botname, personality.Version, sessionID, question, botResponse.Answer)
- fmt.Println(msg)
- // update cache
- // OK addToConversation
- sessionData.Conversation = append(sessionData.Conversation, botResponse)
- storeConversation(sessionID, sessionData)
- // Return Eliza's answer
- return c.String(http.StatusOK, botResponse.Answer)
- }
- /***********************************************************************************************
- *
- ***********************************************************************************************/
- func chantbotEndpoint(c echo.Context) error {
- c.Response().WriteHeader(http.StatusOK)
- c.Response().Header().Add("Content-Type", "text/html")
- // Note the call to ParseFS instead of Parse
- personality := c.Param("personality")
- t, err := template.ParseFS(templates, fmt.Sprintf("templates/%s.html", personality))
- if err != nil {
- log.Fatal(err)
- }
- sessionID := createSession(c)
- fmt.Println("Session ID [", sessionID, "] Created")
- var tBuffer bytes.Buffer
- data := struct {
- SessionID string
- Id string
- GivenName string
- FamilyName string
- Name string
- Bot string
- }{
- SessionID: sessionID,
- Id: c.QueryParam("user_id"),
- GivenName: c.QueryParam("given_name"),
- FamilyName: c.QueryParam("family_name"),
- Name: c.QueryParam("name"),
- Bot: strings.Title(strings.ToLower(personality)),
- }
- // respond with the output of template execution
- t.Execute(&tBuffer, data)
- page := tBuffer.String()
- return c.String(http.StatusOK, page)
- }
- /***********************************************************************************************
- *
- ***********************************************************************************************/
- func (m *Main) Run() error {
- fmt.Println(`
- ███████╗██╗ ██╗███████╗ █████╗
- ██╔════╝██║ ██║╚══███╔╝██╔══██╗
- █████╗ ██║ ██║ ███╔╝ ███████║
- ██╔══╝ ██║ ██║ ███╔╝ ██╔══██║
- ███████╗███████╗██║███████╗██║ ██║
- ╚══════╝╚══════╝╚═╝╚══════╝╚═╝ ╚═╝`)
- fmt.Println("Application Version : ", infrastructure.Version)
- // Create Cache Manager (redis)
- cacheManager = cache.NewRedisCache(m.Cache)
- cacheTTL = m.CacheTTL
- e := echo.New()
- e.Use(middleware.CORS())
- var contentHandler = echo.WrapHandler(http.FileServer(http.FS(web)))
- var contentRewrite = middleware.Rewrite(map[string]string{"/*": "/web/$1"})
- e.GET("/*", contentHandler, contentRewrite)
- e.GET("/user-input", chantboInteractiontEndpoint)
- e.GET("/chatbot/:personality", chantbotEndpoint)
- e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", m.Port)))
- return nil
- }
- /***********************************************************************************************
- *
- ***********************************************************************************************/
- func main() {
- err := commandeer.Run(NewMain())
- if err != nil {
- fmt.Println(err)
- }
- }
|