Skip to content

Commit

Permalink
Merge pull request #2 from ifreitas/process-message
Browse files Browse the repository at this point in the history
Process Message
  • Loading branch information
ifreitas authored Mar 11, 2018
2 parents 73d4dcf + ba02495 commit 8e117f8
Show file tree
Hide file tree
Showing 28 changed files with 566 additions and 23 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ target
/project/project/
/project/target/
/project/scaffold.sbt
Pinky.eml
Pinky.iml
Pinky.ipr
Pinky.iws

31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,33 @@
It is a simple asynchronous and non-blocking web interface for Program AB. Program AB is the reference implementation of the AIML 2.0 draft specification.
AIML is a widely adopted standard for creating chat bots and mobile virtual assistants like ALICE, Mitsuku, English Tutor, The Professor, S.U.P.E.R. and many more.
Program AB was developed by Richard Wallace (contact [email protected]) and first released in January, 2013. More info about Program AB: https://code.google.com/archive/p/program-ab/.
That name comes from an american animated television series called Pinky and the Brain.
That name comes from an american animated television series called [Pinky](https://github.com/ifreitas/Pinky) and the [Brain](https://github.com/ifreitas/brain).

## Getting Started
### 1. Install
1.1. Download the pink-\<version\>.zip file from the latest Pinky release at [download page](https://github.com/ifreitas/Pinky/releases/latest).

1.2. Unzip the file

1.3. Edit the pink-\<version\>/conf/production.conf file (self explanatory)

### 2. Running
```shell
$ cd pinky-<version>/bin/
$ ./pinky
```
### 3. Testing
If you have curl installed, just run:

```shell
$ curl -d "text=test" -X POST http://localhost:9000/chat/sample/some_session
```
It should returns:
```js
{ "text" : "Congratulation. It works!"}
```

### 4. Deploying New Bots
Simply past your AIML 2.0 bot into pinky-\<version\>/bots dir. It does'nt require restart the server.

**The bots must follows the AIML 2.0 structure dir**. More info at [Program-AB](https://code.google.com/archive/p/program-ab/) project page.
51 changes: 51 additions & 0 deletions app/controllers/ChatController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package controllers

import javax.inject.{Inject, Singleton}

import akka.actor.ActorSystem
import models.ChatService
import play.api.data.Form
import play.api.data.Forms._
import play.api.libs.concurrent.CustomExecutionContext
import play.api.libs.json.Json
import play.api.mvc._

import scala.concurrent.{ExecutionContext, Future}

trait MyExecutionContext extends ExecutionContext

@Singleton
class MyExecutionContextImpl @Inject()(system: ActorSystem) extends CustomExecutionContext(system, "my-context") with MyExecutionContext

case class Message(text: String)

@Singleton
class ChatController @Inject()(chatService: ChatService, controllerComponents: ControllerComponents)(implicit myExecutionContext: MyExecutionContext) extends AbstractController(controllerComponents) {
implicit val messageWrites = Json.writes[Message]
val messageForm = Form(
mapping(
"text" -> nonEmptyText
)(Message.apply)(Message.unapply)
)

def chat(botId: String, chatId: String) = Action.async { implicit request =>
handleMessage {
chatService.process(botId, chatId, _)
}
}

private[this] def handleMessage(block: String => Future[String])(implicit request: Request[AnyContent]): Future[Result] = {
messageForm.bindFromRequest.fold(
formWithErrors => {
Future.successful(badRequest("The message's text is required."))
},
validMessage => {
block(validMessage.text).map(ok).recover { case exception => badRequest(exception.getLocalizedMessage) }
}
)
}

private[this] def ok(msg: String) = Ok(Json.toJson(Message(msg)))

private[this] def badRequest(exception: String) = BadRequest(Json.obj("error" -> exception))
}
15 changes: 0 additions & 15 deletions app/controllers/EchoController.scala

This file was deleted.

34 changes: 34 additions & 0 deletions app/models/BotService.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package models

import javax.inject.Inject

import org.alicebot.ab.Bot
import play.api.Configuration

import scala.concurrent.{ExecutionContext, Future}

trait BotService extends {
def newBot(botId: String)(implicit ec: ExecutionContext): Future[Bot] = Future {
validate(botId)
doNewBot(botId)
}

def validate(botId: String): Unit = if (!exists(botId)) throw new BotNotFoundException(botId)

private[this] def exists(botId: String): Boolean = {
val botDir = new java.io.File(pathTo(botId))
botDir.exists && botDir.isDirectory
}

private[this] def doNewBot(botId: String): Bot = new Bot(botId, bots_home)

private[this] def pathTo(botId: String) = s"$bots_home/bots/$botId"

protected def bots_home: String
}

class BotServiceImpl @Inject()(config: Configuration) extends BotService {
override def bots_home: String = config.getOptional[String]("bots_home").getOrElse(System.getProperty("user.dir"))
}

class BotNotFoundException(botId: String) extends RuntimeException(s"Bot '$botId' does not exists")
42 changes: 42 additions & 0 deletions app/models/ChatService.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package models

import javax.inject.{Inject, Singleton}

import org.alicebot.ab.Chat
import play.api.cache.AsyncCacheApi
import play.cache.NamedCache

import scala.concurrent.duration.DurationInt
import scala.concurrent.{ExecutionContext, Future}

trait ChatService {
def process(botId: String, conversationId: String, userMessage: String)(implicit ec: ExecutionContext): Future[String]

def set(key: String, chat: Future[Chat])(implicit ec: ExecutionContext): Future[Chat]

def newChat(botId: String)(implicit ec: ExecutionContext): Future[Chat]
}

@Singleton
class ChatServiceImpl @Inject()(@NamedCache("chat-cache") chatCache: AsyncCacheApi, botService: BotService) extends ChatService {
private val defaultDuration = 30.minutes

def process(botId: String, conversationId: String, userMessage: String)(implicit ec: ExecutionContext): Future[String] = {
val key = s"$botId.$conversationId"
chatCache.get[Chat](key).flatMap {
case None => set(key, newChat(botId))
case Some(chat) => Future.successful(chat)
}.map(_.multisentenceRespond(userMessage))
}

def set(key: String, futureChat: Future[Chat])(implicit ec: ExecutionContext): Future[Chat] = {
for {
chat <- futureChat
_ <- chatCache.set(key, chat, defaultDuration)
} yield chat
}

def newChat(botId: String)(implicit ec: ExecutionContext): Future[Chat] = newBot(botId).map(new Chat(_))

private def newBot(botId: String)(implicit ec: ExecutionContext) = botService.newBot(botId)
}
13 changes: 13 additions & 0 deletions app/modules/DI.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package modules

import com.google.inject.AbstractModule
import controllers.{MyExecutionContext, MyExecutionContextImpl}
import models.{BotService, BotServiceImpl, ChatService, ChatServiceImpl}

class DI extends AbstractModule {
def configure(): Unit = {
bind(classOf[MyExecutionContext]).to(classOf[MyExecutionContextImpl])
bind(classOf[ChatService]).to(classOf[ChatServiceImpl])
bind(classOf[BotService]).to(classOf[BotServiceImpl])
}
}
33 changes: 33 additions & 0 deletions bots/sample/aiml/sample.aiml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<aiml version="1.0.1">
<category>
<pattern>HI</pattern>
<template>
<random>
<li>Hi</li>
<li>Hello</li>
</random>
</template>
</category>
<category>
<pattern>TEST</pattern>
<template>Congratulation. It works!</template>
</category>
<category>
<pattern>MY NAME IS *</pattern>
<template>
Nice to meet you,<set>
<name>user_name</name>
<star>
<index>1</index>
</star>
</set>.
</template>
</category>
<category>
<pattern>WHAT IS MY NAME</pattern>
<template>
<get><name>user_name</name></get>
</template>
</category>
</aiml>
Empty file.
4 changes: 4 additions & 0 deletions bots/sample/aimlif/sample.aiml.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
0,HI,*,*,<random>#Newline <li>Hi</li>#Newline <li>Hello</li>#Newline </random>,sample.aiml
0,TEST,*,*,Congratulation. It works!,sample.aiml
0,MY NAME IS *,*,*,Nice to meet you#Comma<set>#Newline <name>user_name</name>#Newline <star>#Newline <index>1</index>#Newline </star>#Newline </set>.,sample.aiml
0,WHAT IS MY NAME,*,*,<get><name>user_name</name></get>,sample.aiml
1 change: 1 addition & 0 deletions bots/sample/aimlif/update.aiml.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0,BRAIN BUILD,*,*,Sat Mar 10 18:01:39 BRT 2018,update.aiml
17 changes: 17 additions & 0 deletions bots/sample/config/copyright.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
File: [filename]
Author: [botmaster]
Last modified: [date]

This AIML file is part of the [botname] [version] chat bot knowledge base.

The [botname] brain is Copyright &copy; [YYYY] by [organization].

The [botname] brain is released under the terms of the GNU Lesser General
Public License, as published by the Free Software Foundation.

This file is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

For more information see [url]


25 changes: 25 additions & 0 deletions bots/sample/config/denormal.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
" dot com ",".com"
" dot org ",".org"
" dot edu ",".edu"
" dot gov,".gov"
" dot uk ",".uk"
" dot net ",".net"
" dot ca ",".ca"
" dot de ",".de"
" dot jp ",".jp"
" dot fr ",".fr"
" dot au ",".au"
" dot us ",".us"
" dot ru ",".ru"
" dot ch ",".ch"
" dot it ",".it"
" dot nl ",".nl"
" dot se ",".se"
" dot no ",".no"
" dot es ",".es"
" dot mil ",".mil"
" dot co ",".co"
" smile ",":-)"
" smile ",":)"
" smile ",":-)"
" heart ","<3"
Empty file added bots/sample/config/gender.txt
Empty file.
26 changes: 26 additions & 0 deletions bots/sample/config/normal.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
".com"," dot com "
".org"," dot org "
".edu"," dot edu "
".gov"," dot gov
".uk"," dot uk "
".net"," dot net "
".ca"," dot ca "
".de"," dot de "
".jp"," dot jp "
".fr"," dot fr "
".au"," dot au "
".us"," dot us "
".ru"," dot ru "
".ch"," dot ch "
".it"," dot it "
".nl"," dot nl "
".se"," dot se "
".no"," dot no "
".es"," dot es "
".mil"," dot mil "
".co"," dot co "
":-)"," smile "
":)"," smile "
":-)"," smile "
";)"," smile "
"<3"," heart "
Empty file added bots/sample/config/person.txt
Empty file.
Empty file added bots/sample/config/person2.txt
Empty file.
Empty file.
2 changes: 2 additions & 0 deletions bots/sample/config/properties.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name:SAMPLE

15 changes: 10 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
name := """Pinky"""
organization := "ifreitas"

version := "0.1.0-SNAPSHOT"
version := "1.0.0-SNAPSHOT"

lazy val root = (project in file(".")).enablePlugins(PlayScala)

scalaVersion := "2.12.0"

EclipseKeys.preTasks := Seq(compile in Compile, compile in Test)

import NativePackagerHelper.directory
mappings in Universal ++= directory("bots")

libraryDependencies += guice
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "3.1.2" % Test;
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "3.1.2" % Test
libraryDependencies += "org.mockito" % "mockito-core" % "2.13.0" % "test"
libraryDependencies += ehcache

val akkaVersion = "2.5.11"
dependencyOverrides += "com.google.guava" % "guava" % "22.0";
dependencyOverrides += "com.typesafe.akka" %% "akka-actor" % akkaVersion;
dependencyOverrides += "com.typesafe.akka" %% "akka-stream" % akkaVersion;
dependencyOverrides += "com.google.guava" % "guava" % "22.0"
dependencyOverrides += "com.typesafe.akka" %% "akka-actor" % akkaVersion
dependencyOverrides += "com.typesafe.akka" %% "akka-stream" % akkaVersion
22 changes: 21 additions & 1 deletion conf/application.conf
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
# https://www.playframework.com/documentation/latest/Configuration
play.modules.enabled += "modules.DI"

play.cache.bindCaches = ["chat-cache"]

my-context {
executor = "thread-pool-executor"
throughput = 1
thread-pool-executor {
fixed-pool-size = 20
}
}

include "production"

play.http.secret.key = ${application_secret_key}

play.filters.hosts {
allowed = ${allowed_hosts}
}

http.port = ${http_port}
Loading

0 comments on commit 8e117f8

Please sign in to comment.