main.go 8.2 KB

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