main.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. package main
  2. import (
  3. "bytes"
  4. "embed"
  5. "encoding/json"
  6. "fmt"
  7. "html/template"
  8. "io/ioutil"
  9. "log"
  10. "net/http"
  11. "strings"
  12. "time"
  13. "git.riomhaire.com/gremlin/elizaservice/eliza"
  14. "git.riomhaire.com/gremlin/elizaservice/facades/cache"
  15. "git.riomhaire.com/gremlin/elizaservice/infrastructure"
  16. "github.com/gofrs/uuid"
  17. "github.com/jaffee/commandeer"
  18. "github.com/labstack/echo/v4"
  19. "github.com/labstack/echo/v4/middleware"
  20. )
  21. //go:embed web/*
  22. var web embed.FS
  23. //go:embed templates/*
  24. var templates embed.FS
  25. //go:embed bots/*
  26. var bots embed.FS
  27. var cacheManager cache.BotCache
  28. var cacheTTL int
  29. /***********************************************************************************************
  30. *
  31. ***********************************************************************************************/
  32. type Main struct {
  33. BotName string `help:"What This Bot is Called."`
  34. Port int `help:"What port bot is listening too"`
  35. Cache string `help:"What Cache configuration to use - redis format <host>:<port>:<db>"`
  36. CacheTTL int `help:"How long cache is allowed to store data in seconds"`
  37. }
  38. /***********************************************************************************************
  39. *
  40. ***********************************************************************************************/
  41. func NewMain() *Main {
  42. return &Main{
  43. BotName: "Eliza",
  44. Port: 8070,
  45. Cache: "empire:6379:1",
  46. CacheTTL: 3600,
  47. }
  48. }
  49. /********************************
  50. This appends to session in cache latest info
  51. *********************************/
  52. func storeConversation(sessionID string, sessionData eliza.SessionData) {
  53. // Save it as json
  54. j, _ := json.Marshal(sessionData)
  55. cacheManager.Store(sessionID, string(j), cacheTTL)
  56. }
  57. /********************************
  58. This retrieves the context from the cache
  59. *********************************/
  60. func retrieveConversation(sessionID, bot, botversion, user string) (sessionData eliza.SessionData) {
  61. // Lookup session
  62. jsonValue, found, _ := cacheManager.Retrieve(sessionID, false)
  63. if !found {
  64. sessionData = eliza.SessionData{SessionID: sessionID, Bot: bot, BotVersion: botversion, User: user, StartTime: time.Now().UTC().String(), Conversation: make([]eliza.ChatbotInteraction, 0)}
  65. }
  66. if found {
  67. if err := json.Unmarshal([]byte(jsonValue), &sessionData); err != nil {
  68. sessionData = eliza.SessionData{SessionID: sessionID, Bot: bot, BotVersion: botversion, User: user, StartTime: time.Now().UTC().String(), Conversation: make([]eliza.ChatbotInteraction, 0)}
  69. }
  70. }
  71. return
  72. }
  73. /***********************************************************************************************
  74. * This at the moment ... but it could be one passed in via LTI if available
  75. ***********************************************************************************************/
  76. func createSession(c echo.Context) string {
  77. guid, _ := uuid.NewV4()
  78. sessionID := guid.String()
  79. return sessionID
  80. }
  81. /***********************************************************************************************
  82. *
  83. ***********************************************************************************************/
  84. func findPersonality(name string) (personality eliza.Personality, err error) {
  85. filename := "bots/" + name + ".json"
  86. jsonFile, err := bots.Open(filename)
  87. // if we os.Open returns an error then handle it
  88. if err != nil {
  89. return
  90. }
  91. // fmt.Println("Successfully Opened ", filename)
  92. byteValue, _ := ioutil.ReadAll(jsonFile)
  93. // we initialize our Users array
  94. personality = eliza.Personality{}
  95. // we unmarshal our byteArray which contains our
  96. // jsonFile's content into 'users' which we defined above
  97. json.Unmarshal(byteValue, &personality)
  98. // defer the closing of our jsonFile so that we can parse it later on
  99. defer jsonFile.Close()
  100. return
  101. }
  102. /***********************************************************************************************
  103. *
  104. ***********************************************************************************************/
  105. func chantboInteractiontEndpoint(c echo.Context) error {
  106. // Extract question from GET request
  107. user := c.QueryParam("user")
  108. question := c.QueryParam("value")
  109. botname := c.QueryParam("bot")
  110. sessionID := c.QueryParam("session")
  111. // default to eliza
  112. if len(botname) == 0 {
  113. botname = "eliza"
  114. }
  115. personality, err := findPersonality(strings.ToLower(botname)) // files are in lowercase
  116. if err != nil {
  117. return c.String(http.StatusOK, err.Error())
  118. }
  119. // Restore Conversation Context
  120. sessionData := retrieveConversation(sessionID, botname, personality.Version, user)
  121. // Return Eliza's response's so long as the user doesn't give a quit statement
  122. chatboxContext := eliza.ChatbotContext{infrastructure.Version, sessionData}
  123. character := eliza.NewBotPersonality(&personality, &chatboxContext)
  124. botResponse := character.ReplyTo(question)
  125. msg := fmt.Sprintf("Bot [%s %s] Session ID [%s] Question [%s] Answer [%s]", botname, personality.Version, sessionID, question, botResponse.Answer)
  126. fmt.Println(msg)
  127. // update cache
  128. // OK addToConversation
  129. sessionData.Conversation = append(sessionData.Conversation, botResponse)
  130. storeConversation(sessionID, sessionData)
  131. // Return Eliza's answer
  132. return c.String(http.StatusOK, botResponse.Answer)
  133. }
  134. /***********************************************************************************************
  135. *
  136. ***********************************************************************************************/
  137. func chantbotEndpoint(c echo.Context) error {
  138. c.Response().WriteHeader(http.StatusOK)
  139. c.Response().Header().Add("Content-Type", "text/html")
  140. // Note the call to ParseFS instead of Parse
  141. personality := c.Param("personality")
  142. t, err := template.ParseFS(templates, fmt.Sprintf("templates/%s.html", personality))
  143. if err != nil {
  144. log.Fatal(err)
  145. }
  146. sessionID := createSession(c)
  147. fmt.Println("Session ID [", sessionID, "] Created")
  148. var tBuffer bytes.Buffer
  149. data := struct {
  150. SessionID string
  151. Id string
  152. GivenName string
  153. FamilyName string
  154. Name string
  155. Bot string
  156. }{
  157. SessionID: sessionID,
  158. Id: c.QueryParam("user_id"),
  159. GivenName: c.QueryParam("given_name"),
  160. FamilyName: c.QueryParam("family_name"),
  161. Name: c.QueryParam("name"),
  162. Bot: strings.Title(strings.ToLower(personality)),
  163. }
  164. // respond with the output of template execution
  165. t.Execute(&tBuffer, data)
  166. page := tBuffer.String()
  167. return c.String(http.StatusOK, page)
  168. }
  169. /***********************************************************************************************
  170. *
  171. ***********************************************************************************************/
  172. func (m *Main) Run() error {
  173. fmt.Println(`
  174. ███████╗██╗ ██╗███████╗ █████╗
  175. ██╔════╝██║ ██║╚══███╔╝██╔══██╗
  176. █████╗ ██║ ██║ ███╔╝ ███████║
  177. ██╔══╝ ██║ ██║ ███╔╝ ██╔══██║
  178. ███████╗███████╗██║███████╗██║ ██║
  179. ╚══════╝╚══════╝╚═╝╚══════╝╚═╝ ╚═╝`)
  180. fmt.Println("Application Version : ", infrastructure.Version)
  181. // Create Cache Manager (redis)
  182. cacheManager = cache.NewRedisCache(m.Cache)
  183. cacheTTL = m.CacheTTL
  184. e := echo.New()
  185. e.Use(middleware.CORS())
  186. var contentHandler = echo.WrapHandler(http.FileServer(http.FS(web)))
  187. var contentRewrite = middleware.Rewrite(map[string]string{"/*": "/web/$1"})
  188. e.GET("/*", contentHandler, contentRewrite)
  189. e.GET("/user-input", chantboInteractiontEndpoint)
  190. e.GET("/chatbot/:personality", chantbotEndpoint)
  191. e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", m.Port)))
  192. return nil
  193. }
  194. /***********************************************************************************************
  195. *
  196. ***********************************************************************************************/
  197. func main() {
  198. err := commandeer.Run(NewMain())
  199. if err != nil {
  200. fmt.Println(err)
  201. }
  202. }