Create Your Personalized Gemini Chat with Conversational UI and Angular

danywalls

Dany Paredes

Posted on January 29, 2024

Create Your Personalized Gemini Chat with Conversational UI and Angular

When I see many chats connected with AI, a friend once said it's difficult, but it isn't. Today, I want to demonstrate how easy it is to build your own chat connected with AI and a nice interface using just a few lines of code.

we'll learn how to create an Angular application using the Gemini library and integrate it with Kendo Conversational UI for an impressive user interface like this.

Final

How long will it take? Surprisingly, it can be done fast. Let's get started!

Setup Project

First, create the Angular application and install the Gemini library using Angular CLI with the command ng new conversional-with-gemini. This generates a new, empty Angular project.

ng new conversional-with-gemini
Enter fullscreen mode Exit fullscreen mode

Navigate to the directory and generate a Gemini-client service using the CLI generator by executing the following command:

ng g s services/gemini
CREATE src/app/services/gemini.service.spec.ts (404 bytes)
CREATE src/app/services/gemini.service.ts (150 bytes)
Enter fullscreen mode Exit fullscreen mode

In Angular 17, since we don't have the environment file, create it using the command ng generate environments.

ng generate environments
Enter fullscreen mode Exit fullscreen mode

Using Gemini

To use Gemini, we need to utilize the Gemini SDK with a key. First, install the Gemini SDK in the project by running the following command:

npm i @google/generative-ai
Enter fullscreen mode Exit fullscreen mode

Next, obtain your API key from https://ai.google.dev/, copy it, and store the value in the environment.ts file, like this:

export const environment = {
  geminy_key: "YourAmazingKey"
};
Enter fullscreen mode Exit fullscreen mode

We have the key and the SDK installed; now it's time to use them in our Gemini client service. Let's do it!

Using Gemini SDK

We're going to use the Gemini SDK in the gemini-client service, creating an instance of Google AI with the key and generating a model, in our case, 'gemini-pro', which returns an instance of the model ready to work.

In our case, we want to use the model based on a prompt. For my specific needs, I want it to function like an Angular developer with experience in Kendo UI for Angular.

So my prompt will be like:

You are an expert in the Kendo UI library for Angular, provide a real-world scenario and how this product, helps to solve, with angular code examples and links for resources

The code must look like:

import { Injectable } from '@angular/core';
import { GoogleGenerativeAI } from '@google/generative-ai';
import { environment } from '../../environments/environment';

@Injectable({
  providedIn: 'root',
})
export class GeminiService {
  #generativeAI = new GoogleGenerativeAI(environment.geminy_key);

  #prompt =
    'You are an expert in the Kendo UI library for Angular, provide a real-world scenario and how this product ' +
    'helps to solve, with angular code examples and links for resources';

  #model = this.#generativeAI.getGenerativeModel({
    model: 'gemini-pro',
  });

}
Enter fullscreen mode Exit fullscreen mode

Now it's time to generate the content using the model. First, create a method called generate with the parameter textInput. Since the generateContent code is asynchronous, the signature of the generate method must also be async.

First, we define the parts to set up the content to generate, using the prompt and the textInput from the user. This helps provide context for the model. Next, we obtain the modelResult from the model, which returns several properties. In our case, the most important one is the response. This object includes the function text to obtain the raw value. To test our code, we can print it in the console using console.log().

We use try catch to handle any error in the generateContent request.

 async generate(textInput: string) {
    try {
      if (text) {
        const parts = [
          {
            text: this.#prompt,
          },
          { text: textInput }
        ];

        const modelResult = await this.#model.generateContent({
          contents: [{ role: 'user', parts }],
        });
        const response = modelResult.response;
        const text = response.text();
         console.log(text)
      }
    } catch (e: any) {
      console.log(e);
    }
  }
Enter fullscreen mode Exit fullscreen mode

To utilize our service, follow these steps: inject the GeminiClientService into the AppComponent.ts, add a new method called getAnswer, and then call the generate method from gemeniService.

export class AppComponent {
  geminiService = inject(GeminiService)

  async getAnswer(text: any) {
    await this.geminiService.generate(text);
  }

}
Enter fullscreen mode Exit fullscreen mode

In the HTML markup, add an input with a template reference #inputText. On the button, call the getAnswer method.

<input #userInput><button (click)="getAnswer(userInput.value)">Generate</button>
Enter fullscreen mode Exit fullscreen mode

Save the changes and see our "Kendo IA power by Gemini"! 🎉😎

Final

Alright, it works, but it doesn't look very nice. Let's make it visually appealing using Kendo Conversational UI.

Using Conversational UI

To begin, we need to install the Kendo UI for Angular Conversational UI to work using schematics:

ng add @progress/kendo-angular-conversational-ui
Enter fullscreen mode Exit fullscreen mode

Import the ChatModule in the imports section of the app.component.ts, as it provides the kendo-chat component.


@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule,   ChatModule],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss',
})
export class AppComponent {
Enter fullscreen mode Exit fullscreen mode

Let's start to do the magic in the gemini.service.ts, it will provide signals to sync the Gemini answer with the kendo-chat the user, and the messages.

We need to declare two users for the chat: one will be the kendoIA, and the other will be the user, like me. Each user must have a unique ID. To display an appealing user image, we can use assets images.

I'm using crypto.randomUUID() to generate unique id.

  readonly #kendoIA: User = {
    id: crypto.randomUUID(),
    name: 'Kendo UI',
    avatarUrl: './assets/kendo.png',
  };

  public readonly user: User = {
    id: crypto.randomUUID(),
    name: 'Dany',
    avatarUrl: './assets/dany.jpg',
  };
Enter fullscreen mode Exit fullscreen mode

Declare a signal $messages with an array of Message, and initialize with the initial message:

  $messages = signal<Message[]>([{
          author: this.#kendoIA,
          timestamp: new Date(),
          text: 'Hi! 👋 how I can help you with Kendo ?'
  }]);
Enter fullscreen mode Exit fullscreen mode

The kendo-chat returns a SendMessageEvent, which provides all information about the message from the UI, such as the user, message, and more. We update the generate method with the signature of SendMessage and update the initial logic reading the message.text.

async generate(textInput: SendMessageEvent) {
    try {
      if (textInput.message.text && this.#model) {
Enter fullscreen mode Exit fullscreen mode

Next, update the messages signal using the update method, incorporating the new message and the components needed to read the text from textInput.message.text.

     this.$messages.update((p) => [...p, textInput.message]);
        const parts = [
          {
            text: this.#prompt,
          },
          { text: textInput.message.text },
        ];
Enter fullscreen mode Exit fullscreen mode

Create a new message with the response from the model, assign the author: this.#kendoIA and update again the messages.

 const message = {
          author: this.#kendoIA,
          timestamp: new Date(),
          text,
        };

        this.$messages.update((p) => [...p, message]);
Enter fullscreen mode Exit fullscreen mode

The final code looks like this:

async generate(textInput: SendMessageEvent) {
    try {
      if (textInput.message.text && this.#model) {
        this.$messages.update((p) => [...p, textInput.message]);
        const parts = [
          {
            text: this.#prompt,
          },
          { text: textInput.message.text },
        ];

        const result = await this.#model.generateContent({
          contents: [{ role: 'user', parts }],
        });

        const response = result.response;
        const text = response.text();

        const message = {
          author: this.#kendoIA,
          timestamp: new Date(),
          text,
        };

        this.$messages.update((p) => [...p, message]);
      }
    } catch (e: any) {
      console.log(e);
    }
  }
Enter fullscreen mode Exit fullscreen mode

Connect the Kendo Chat with Signals

We have arrived at the final step. We will utilize the $messages and user from the Gemini service. First, declare the variables in the app.component.ts.

export class AppComponent {

  geminiService = inject(GeminiService)
  user = this.geminiService.user;
  messages = this.geminiService.$messages

  async generate(event: SendMessageEvent) {
    await this.geminiService.generate(event);
  }
}
Enter fullscreen mode Exit fullscreen mode

In the template, add the <kendo-chat> and bind the user property. Next, read the messages (with parentheses) and bind the sendMessage to the previously defined generate method.

Since we want to display both the avatar and the text, we modify the default template using the kendoChatMessageTemplate directive to gain access to the message variable.

The final markup looks like:

  <kendo-chat [user]="user" [messages]="messages()" (sendMessage)="generate($event)" width="450px">
    <ng-template kendoChatMessageTemplate let-message>
      <div>
      {{ message.text }}
      {{message.avatar}}
      </div>
  </ng-template>
Enter fullscreen mode Exit fullscreen mode

Save the changes, and voilà! You now have Gemini working with conversational UI and a sleek UI!

Recap

We learned how to combine the power of Gemini with Angular and integrate it with Angular Conversational UI for a user-friendly interface. We also demonstrated how to make the interface visually appealing using the Angular Conversational UI.

In the next article, we'll learn how to maintain the history and chat session with Gemini, but this is already a great starting point for working with Gemini and Conversational UI!

💖 💪 🙅 🚩
danywalls
Dany Paredes

Posted on January 29, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related