Building a simple alarm clock with Dasha, Express, Ejs and tailwind which force user to wake up

Most probably, the title doesn't give a good idea about what we will build together today! We are trying to build an alarm clock that will call users and solve a riddle. It'd not cut the call unless they got it correct. But there is a chance of saying no if they don't want to wake up at that point. This blog will be vast if I complete the full features. So I'll try to make a minimal version where we'll use Dasha(dasha.ai) to make this. We are going to use

  • Npm (for installing packages)
  • Express js (For the API development)
  • Ejs (templating language for HTML)
  • TailwindCSS (for styling)
  • Dasha (for making alarm clock interesting with the help of conversational AI)
  • Vscode (code editor)
  • Github (for getting dasha blank app)

Let's start building, but before that, let's learn what it'll look like. Here is the final screenshot -

final screenshot

By putting the phone number, we'll get a call to give the correct answer to end the call! We will build a short version of it because otherwise, the blog will be vast and hard to follow.

Let's explore the Dasha first about learning how we can start. For using Dasha, you need to get an account there first. It's straightforward to register an account in Dasha; go to this URL (auth.dasha.ai/account/register) and register your account for your key. It's still in beta mode, so that UI can change from time to time.

Now, let's install the Dasha to log in and use its features. We are following this URL (docs.dasha.ai/en-us/default)

npm i -g "@dasha.ai/cli@latest"
dasha account login

After the login, it'll be better to install Dasha studio in our vscode

dasha studio

We will use a blank app from Dasha to build our part! Let clone it from Github.

git clone https://github.com/dasha-samples/blank-slate-app

Now let's install the packages to run it.

npm install

Once we install, we've two paths to run this, either we can use chat or phone. I prefer the phone way so we'll run.

npm start PHONE_NUMBER_HERE

Running this will get me a call on my phone to talk with the Dasha AI bot. But the most exciting part is that there is nothing much to talk about, as this is a small functionality. So let's look at what we've inside

folder structure

The basic app comes with lots of stuff, but first, ignore the index.js file because that one is set up to run the Dasha part. So let's go to the app folder and look into the main.dsl where things started!

It quite looks like another programming, but there is some weird thing going on, right? Confusing right? It's a Dasha Scripting Language! So it's a little different from others. If we look into the first part

import "commonReactions/all.dsl";

It's importing something, we can check the common reactions folder, and it'll give us some idea about what is happening here. We are loading some prewritten libraries here. So we can ignore this part for now, and let's go to the second part.

context 
{
    input phone: string;
    input name: string = ""; 
    var1: string = "";
}

This is where we are getting the variables and doing stuff; we can create variables as we want from here and use them through this file.

Below this, you'll find a function like

external function function1(log: string): string;

We can also ignore this one because we're not going to use the complex stuff here. Let's check what is happening in the next part (root node)

start node root 
{
    do 
    {
        #connectSafe($phone); 
        #waitForSpeech(1000);
        #say("greeting", {name: $name} );
        wait *;
    }
    transitions 
    {
        yes: goto yes on #messageHasIntent("yes"); 
        no: goto no on #messageHasIntent("no"); 
    }
}

start node root is the first part where the conversation will start. This node currently has two parts; this has a do & transition. In the do part, it'll try to run it first; then, based on user talk, it'll go to function from transition. This basic app is a basic one, so it'll just ask if the user can hear the AI voice; if the user says something which has an intent of "yes" then it'll just go to "yes function"; otherwise, this will go in no intent route.

Before exploring the next node, I'll start building our part because I think it's good enough to understand what's happening here. As we are going to develop the alarm clock, we can have the common reaction for our app, so in our main.dsl, we are going to import the common reaction first. We'll just keep the phone number input because we're going to connect users using the phone. so it'll look like

import "commonReactions/all.dsl";

context
{
    input phone: string;
}

Now it's time to write our main starting root. We are creating two-part of the start node root; the first part is done. Here we'll try to connect with the phone first; then, we'll try to delay a moment for a user to give a time, then AI will start talking. We'll use the #sayText function, where we'll write what AI will ask the user. Then we'll wait for the user to reply. Here the code ->

start node root
{
    do
    {
        #connectSafe($phone);
        #waitForSpeech(1000);
        #sayText("Hello there! I am from Dasha AI and trying to call you as you requested! Are you interested to play a game?");
        wait *;
    }
}

Based on the user response, we can't go anywhere right now, so we'll create transitions now. For this basic case, we'll either go with yes intention or no intention user. The new code will look like

start node root
{
    do
    {
        #connectSafe($phone);
        #waitForSpeech(1000);
        #sayText("Hello there! I am from Dasha AI and trying to call you as you requested! Are you interested to play a game?");
        wait *;
    }
    transitions
    {
        yes: goto yes on #messageHasIntent("yes");
        no: goto no on #messageHasIntent("no");
    }
}

Now you must be thinking about how it's getting the intent of a user? Is this prewritten? Yes! Because we've cloned the blank app repo, now it's time to explore the data.json file. data.json file has all the intent listed for us. We are using yes & no intent for the first step, so we're not going to change anything right now and go back to our main.dsl file to write the transitions. So let's do the easy part first, suppose the user doesn't want to wake up and say something with no intent. What should we do? We need to on no node to run the rest. Let's write a no node for now -

node no
{
    do
    {
        #say("no");
        exit;
    }
}

We are not even writing a transition because we want to end the call when the user is not interested in waking up at this moment. But here, we didn't use any direct text like old-time; instead, we used #say("no"); which is going to data from phrasemap.json file. Let's have a look in there and change the no text to new text so that it works well with our desired idea.

"no": 
      {
        "first": 
        [{ "text": "Understandable! Have a nice sleep!" }]
      },

This upper one will be our data in the phrasemap.json file. If you create a custom phrasemap, don't forget to add that in macros in the down part of phrasemap.json file!

Now time to build the yes part. So we're going to ask a riddle-type question here to help the user to wake up! Let's keep it simple and use #sayText to tell the text to the user and wait for his response, and based on his response, let's take a transition. The code will look like this -

node yes
{
    do
    {
        #sayText("I am tall when I am young, and I am short when I am old. What am I?");
        wait *;
    }

    transitions
    {
        correct: goto correct on #messageHasIntent("correctAnswer");
        no: goto no on #messageHasIntent("no");
    }
}

Here we are using two transitions; the first part is if they got the correct answer, then it'll go to a correct node. Otherwise, it'll repeat the question once (from the importing common reaction part). If the user doesn't guess correctly or try to say no, it'll end the call for now. This question is tricky, so for answering this question, the user needs to be wake & think, and this is the alarm part! Now let's build the final part of the puzzle, the correct answer node.

The correct node will be really easy to work; we'll just say the answer is correct and end the call. Here is the simple node.

node correct
{
    do
    {
        #sayText("This is a correct answer! Have a nice morning!");
        exit;
    }
}

If it's hard to follow, you can check the whole code from the GitHub link -> (github.com/nerdjfpb/Dasha-alarm-clock-examp..)

We can test the app by running it.

npm start PHONE_NUMBER

But we will improve the app and build a view for the app, so first, start with the API. First, we need to import the expressjs, ejs to start the UI part. For the express API endpoints, we are going to write (in index.js file, delete the dasha code for now)

// importing express js
const express = require('express')
const app = express()

// using for getting json input
app.use(express.json())

// setting the view engine js so that we can load the file from views/pages
app.set('view engine', 'ejs')

// for showing the ui 
app.get('/', (_req, res) => {
  res.render('pages/index')
})

// post url to send the phone number and run the Dasha part
app.post('/', (req, res) => {
  // Calling dasha will be happen here
  res.json({ success: true })
})

// port selection to run
const port = process.env.PORT || 3000
app.listen(port, () => console.log(`listening on port ${port}`))

Now let's write the view part; I'm using tailwindcss as CDN (which is a really bad idea, but I don't want to really install lots of stuff now and this one is mostly for demonstrating this tutorial). For the UI index.ejs file will look like -

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Alarm Clock By Dasha & Nodejs</title>
  <script src="https://cdn.tailwindcss.com"></script>
</head>

<body>
  <main class="min-h-screen flex flex-col row-column items-center justify-center text-center">
    <h1 class="text-4xl font-bold">
      Alarm Clock With Dasha AI
    </h1>
    <div class="text-gray-400 text-xs italic py-2">please add the country code before your number</div>

    <form class="min-w-[40%]" onsubmit="return handleOnSubmit(event)" method="get" action="#">
      <input
        class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
        id="phone" type="text" placeholder="Enter your number..." />

      <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded my-2" type="submit">
        Alarm Call
      </button>

    </form>
  </main>

  <script>

    function handleOnSubmit(e) {
      e.preventDefault();

      const phone = document.getElementById('phone').value

      if (phone) {
        postData('/', { phone })
          .then(data => {
            if (data.success) {
              alert('Alarm call is coming on your way! Please wait....')
            } else {
              alert('Something went wrong!')
            }
          });
      }

    }


    async function postData(url = '', data = {}) {
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },

        body: JSON.stringify(data)
      });
      return response.json();
    }
  </script>
</body>

</html>

you can get the file from here -> github.com/nerdjfpb/Dasha-alarm-clock-examp..

We are ready to finalize the last part for running the Dasha once the user clicks on the Alarm Call. First, require the dasha

const dasha = require('@dasha.ai/sdk')

Now we'll simply use the things from blank-slate-app we are not going to use everything from there; we are just going to use it for a phone call, and here is the simple version.


async function dashaCall(phone) {
  const app = await dasha.deploy('./app')

  app.connectionProvider = async (conv) =>
    conv.input.phone === 'chat'
      ? dasha.chat.connect(await dasha.chat.createConsoleChat())
      : dasha.sip.connect(new dasha.sip.Endpoint('default'))

  app.ttsDispatcher = () => 'dasha'

  app.setExternal('function1', (args) => {
    console.log(args.log)
  })

  await app.start()

  const conv = app.createConversation({ phone: phone })

  if (conv.input.phone !== 'chat') conv.on('transcription', console.log)

  const result = await conv.execute()

  console.log(result.output)

  await app.stop()
  app.dispose()
}

Call this function from the post part of index.js, which will look like -

app.post('/', (req, res) => {
  dashaCall(req.body.phone)
  res.json({ success: true })
})

So we are ready to run our app by

npm start

And it will load, and you can put your number here to get a call from AI.

This app simplifies the overall idea; we can improve a lot. Like Riddles can be randomize UI can be rich with a dashboard and alarm time (which I didn't make because this will be really hard to follow if I do that) Sometimes users can just ignore the call, so if users can't solve a riddle, we should call them again. This feature we can add. We could put a number in the database if they correctly answered. Based on that, we can call again!

Overall there is a lot of scopes to improve. But this was a plan to demonstrate Dasha's conversational ai, which is fun to play with. I hope you enjoyed this tutorial.

If you are looking for the whole code, here is the Github repo - github.com/nerdjfpb/Dasha-alarm-clock-example