A Step-by-Step Guide to Create a Speaking Practice App with AWS Bedrock

Amelia DuttaAmelia Dutta
6 min read

Have you ever felt that rock climbing is easier than starting a conversation? Or does an unexpected speaking invitation make your mind go blank or cause you to sweat, even in November? If so, you're not alone. This is called Glossophobia or Speaking Anxiety, which is the most common phobia. Several treatments are available, and you should consult a specialist if it affects your daily life.

In this article, we'll explore how AI can help us create an application to address this issue using AWS Bedrock.

[NOTE: This app doesn't treat the issue. It is just for developing good speaking habits and providing tips.]

Why a Speaking Practice App?

It is proven that knowing your material beforehand, rehearsing your scripts until you have them memorized, and continuing to practice until you are comfortable with what you say can help treat speaking anxiety.

Why AWS Bedrock?

Amazon Bedrock is a fully managed service that provides high-performing foundation models (FMs) from top AI companies like AI21 Labs, Anthropic, Cohere, Meta, Mistral AI, Stability AI, and Amazon through a single API. It offers the capabilities needed to build generative AI applications with security, privacy, and responsible AI. It is also easy to use and beginner-friendly. By using AWS Bedrock, we can ensure our app is efficient, reliable, and easy to integrate with other AWS services.

[Read More]

Prerequisites

To follow along with this tutorial, you will need:

  • Basic knowledge of JavaScript/ES6

  • Node.js and npm installed on your machine

  • AWS credentials (access key ID and secret access key)

  • Familiarity with AWS Bedrock

  • Access to the Claude-v2 model

Let's Begin!

Setting Up the Project

First, let's set up our project on the local machine:

Create a new directory and navigate into it:

mkdir speaking-practice-app
cd speaking-practice-app

Initialize a new Node.js project:

npm init -y

Install the required dependencies: Install Vite for fast development and @aws-sdk/client-bedrock-runtime for running inference using Amazon Bedrock models.

npm install vite @aws-sdk/client-bedrock-runtime

Creating the User Interface

For the next steps, you can either follow the instructions or clone the entire application from here: https://github.com/amelia2802/glossocure

Create anindex.htmlfile and add the following code:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Speaking Practice App</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <header>
    <h1>GLOSSOCURE</h1>
  </header>
  <main id="app">
    <section id="daily-text">
      <section class="head-component">
        <h2>Read Aloud ๐Ÿ”Š</h2>
        <button id="getNewParagraph">Get New Paragraph</button>
      </section>
      <article id="paragraph">...</article>
    </section>
    <section class="daily-tips">
      <h3>Daily Tips ๐Ÿ’ก</h3>
      <p id="tips"></p>
    </section>
  </main>
  <script type="module" src="main.js"></script>
</body>
</html>

Add CSS styling instyle.css:

:root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;
  color-scheme: light dark;
  color: rgba(255, 255, 255, 0.968);
  background-color: #242424;
  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  margin: 0;
  display: block;
}

header {
  position: absolute;
  top: 0;
  width: 100%;
  text-align: center;
  box-shadow: 10px 0 10px rgb(243, 239, 61);
}

h1 {
  font-size: 3.2em;
  line-height: 1.1;
}

#app {
  position: absolute;
  top: 10rem;
  margin: 0 7rem;
}

.head-component {
  display: flex;
  justify-content: space-between;
}

#getNewParagraph {
  height: 100%;
  padding: 1rem;
  border-radius: 25px;
  background-color: rgb(240, 248, 151);
  font-weight: 600;
  font-family: inherit;
}

#getNewParagraph:hover, #getNewParagraph:focus {
  background-color: rgb(231, 246, 76);
  box-shadow: 1px 1px 5px rgb(249, 255, 186);
  color: black;
}

h2 {
  font-size: 1.5rem;
  font-style: oblique;
}

#paragraph {
  line-height: 27px;
}

.daily-tips {
  margin: auto;
  width: 30%;
  border: 2px dashed rgb(224, 248, 151);
  margin-top: 3rem;
  border-radius: 1rem 1rem 13%;
  text-align: center;
  padding: 0 1rem;
}

Integrating the Frontend with the Backend

Now let's link our frontend to AWS Bedrock to fetch paragraphs and tips.

Create amain.jsfile & import dependencies

import './style.css';
import { BedrockRuntimeClient, ConverseCommand } from "@aws-sdk/client-bedrock-runtime";

Setting Up Constants

const modelId = 'anthropic.claude-v2';
const paragraphPrompt = "Generate a paragraph for speaking practice. Imagine you are answering an interview(news,job,entertainment) question.";
const tips = "Give daily two liner tip to overcome speaking anxiety.";
  • modelId: The ID of the model we'll use from AWS Bedrock.

  • paragraphPrompt: A prompt that asks the model to generate a paragraph for speaking practice.

  • tips: A prompt that asks the model to provide daily tips for overcoming speaking anxiety.

Creating Conversation Objects & Declaring Variables

const paragraphConversation = [
  {
    role: "user",
    content: [{ text: paragraphPrompt }],
  },
];

const tipsConversation = [
  {
    role: "user",
    content: [{ text: tips }],
  },
];

let client = null;
  • paragraphConversation and tipsConversation: These are the messages we send to the model. The role: "user" indicates that the prompt is from the user.

  • client: This variable will hold the instance of BedrockRuntimeClient.

Fetching a New Paragraph & a New Tip

async function fetchNewParagraph() {
  disableButton(true);
  showLoadingAnimation();

  try {
    const response = await client.send(new ConverseCommand({ modelId, messages: paragraphConversation }));
    console.log("Response from Bedrock (Paragraph):", response);
    const paragraph = response.output.message.content[0].text;
    document.querySelector("#paragraph").innerHTML = paragraph;
  } catch (err) {
    console.error("Error fetching new paragraph:", err);
    document.querySelector("#paragraph").innerHTML = 'An error occurred while fetching the paragraph.';
  }

  disableButton(false);
}

async function fetchNewTip() {
  try {
    const response = await client.send(new ConverseCommand({ modelId, messages: tipsConversation }));
    console.log("Response from Bedrock (Tips):", response);
    const tip = response.output.message.content[0].text;
    document.querySelector("#tips").innerHTML = tip;
  } catch (err) {
    console.error("Error fetching daily tip:", err);
    document.querySelector("#tips").innerHTML = 'Unable to fetch daily tip.';
  }
}
  • fetchNewParagraph: An asynchronous function that fetches a new paragraph.

    • disableButton(true): Disables the button while fetching.

    • showLoadingAnimation(): Shows a loading spinner while fetching.

    • try-catch block: Attempts to fetch a new paragraph and update the DOM. If an error occurs, it logs the error and shows an error message.

  • fetchNewTip: Similar to fetchNewParagraph, but it fetches a tip instead.

Utility Function

function disableButton(isDisabled) {
  const paragraphButton = document.querySelector("#getNewParagraph");
  paragraphButton.disabled = isDisabled;
}

disableButton: Enables or disables the button to prevent multiple clicks during the fetch.

Initializing the App

async function init() {
  try {
    const creds = await fetchCredentials();
    client = await createBedrockClient(creds);
    await fetchNewParagraph();
    await fetchNewTip();
  } catch (err) {
    console.error("Error initializing application:", err);
    document.querySelector("#paragraph").innerHTML = 'An error occurred while initializing the application.';
    document.querySelector("#tips").innerHTML = 'An error occurred while initializing the application.';
  }

  const paragraphButton = document.querySelector("#getNewParagraph");
  paragraphButton.addEventListener("click", fetchNewParagraph);
}
  • init: An asynchronous function to initialize the app.

    • Fetches credentials and creates the Bedrock client.

    • Fetches the initial paragraph and tip.

    • Adds an event listener to the button to fetch a new paragraph when clicked.

Creating Bedrock Client

async function createBedrockClient(creds) {
  try {
    return new BedrockRuntimeClient({
      credentials: creds.credentials,
      region: creds.region,
    });
  } catch (err) {
    console.error("Error creating Bedrock client:", err);
    throw err;
  }
}

createBedrockClient: Creates and returns a new instance of BedrockRuntimeClient with the provided credentials and region.

Fetching Credentials

async function fetchCredentials() {
  return {
    region: "us-west-2",
    credentials: {
      accessKeyId: import.meta.env.VITE_AWS_ACCESS_KEY_ID,
      secretAccessKey: import.meta.env.VITE_AWS_SECRET_ACCESS_KEY,
    },
  };
}
init();

fetchCredentials: Returns the AWS credentials from environment variables and init(): Calls the init function to start the application.

Create a.envfile & add the AWS credentials to it:

VITE_AWS_DEFAULT_REGION=us-west-2 // YOUR REGION
VITE_AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY
VITE_AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY

Running the Application

npx vite

You should now see the app running, with functionalities to fetch new paragraphs for practice and daily tips.

Click on the Generate button and you can see a new paragraph.

Conclusion

Congratulations! You have successfully created a speaking practice app using AWS Bedrock. This app generates paragraphs for speaking practice and daily tips to overcome speaking anxiety, providing a useful tool for improving communication skills.

0
Subscribe to my newsletter

Read articles from Amelia Dutta directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Amelia Dutta
Amelia Dutta

Front-End Web Developer | React, JavaScript, HTML, CSS | AWS Certified | Creating Engaging User Experiences