Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Hosted-services / Azure

Building OpenAI Chat Application with Angular, ASP.NET API, and Azure

4.91/5 (3 votes)
13 Oct 2023CPOL4 min read 4.6K  
How to build an OpenAI chat application using Angular, ASP.NET API, and Azure
This project demonstrates how to create an OpenAI chat application using Angular, ASP.NET API, and Azure, emphasizing Azure setup, ASP.NET API configuration for OpenAI interaction, and building a responsive Angular front-end for a chat interface with features like message order and differentiation between user and OpenAI responses.

In this project, I will show you how to build an OpenAI chat application using Angular, ASP.NET API, and Azure.

For the latest updates on this project, please go to the source article at DotNetLead.

We will go over:

  • Setup needed on Azure
  • Code needed on the ASP.NET API
  • Code needed on the Angular front end

Azure OpenAI Setup

The first thing is to create the Azure OpenAI resource shown below. It’s a resource not available by default in Azure and you have to apply for access. Once Microsoft reviews your request, which takes a few days, you will be able to create the Azure OpenAI resource. You can apply for access HERE.

Image 1

Once the Azure OpenAI resource is created, go to the Model Deployment section to choose the OpenAI model that you want to use as shown below, Azure will take you to the Azure AI Studio where you can also test the chat functionality on their website.

Image 2

Create the deployment by choosing the ChatGpt version you like as shown below. At the time of publication, I chose the gpt-35-turbo.

Image 3

Once the model is created, note down the name of the deployment as shown below. You will need the name of the deployment for building the API.

Image 4

Go back to the Azure OpenAI resource and note down the EndPoint and Key needed for building the API as shown below. You only need 1 key in the API.

Image 5

ASP.NET API Setup

The code for getting the chat response from Azure OpenAI is shown below. First, create the OpenAIClient, followed by creating the ChatCompletionsOptions for preparing the query, which is the user chat input, then call the GetChatCompletionsAsync method to get the response from Azure OpenAI. In the controller method, we just return the content of the response from OpenAI.

C#
[HttpGet]
        public async Task<ChatMsg> GetResponse(string query)
        {
            OpenAIClient client = new OpenAIClient(
                new Uri(config["AzureEndPoint"]),
                new AzureKeyCredential(config["AzureKey"])
            );

            ChatCompletionsOptions option = new ChatCompletionsOptions { };
            option.Messages.Add(new ChatMessage(ChatRole.User, query));

            Response<ChatCompletions> response = await client.GetChatCompletionsAsync(
                    config["DeploymentName"],
                    option
                );
            return new ChatMsg { Message = response.Value.Choices[0].Message.Content };
        }

As noted in the ReadMe.txt file, you will need three configurations below using the .NET Secret Manager so that the configurations are not stored in plain text files.

AzureEndPoint
AzureKey
DeploymentName

For instructions on how to use the .NET Secret Manager, check out Securing ASP.NET Core configurations using the Secret Manager and Azure Key Vault.

Building Front End Chat UI using Angular

There are several properties needed for building the chat front end:

  1. The Chat window should stay static. In another words, it should not expand or contract due the content inside the chat window and should stay at the same location. When the text exceeds the window space, it should allow scrolling.
  2. The order in which the chat messages are being displayed should go from bottom to top, with the latest user input or response starting from the very bottom.

For the Chat window to stay static and allow scrolling as the contents expand, declare a fixed position flexbox container (shown as the chat-container class in the code below), and the first flexbox item would just be another flexbox (shown as the messages class in the code below), which has the flex-direction as column-reverse for showing the items from bottom to top, the overflow-y as scroll for vertical scrolling, and overflow-x as hidden to hide the horizontal scrolling.

The user input and the OpenAI response need to be shown differently, which can be done by declaring two different classes, which are the bot-message class for showing the OpenAI response and the user-message class for showing the user input. Notice the user-message class has align-self as flex-end so that it’s aligned to the right.

Below is the HTML:

HTML
<div class="chat-container">
        <div class="messages">   
            <div *ngFor="let msg of chatHistory"
                [ngClass]="{'user-message': msg.type == 1, 
                            'bot-message': msg.type == 2}" >
                <p>{{msg.message}}</p>
            </div>
        </div>
        ...

Below is the scss:

CSS
.chat-container {
    position: fixed;
    top: 4rem;
    bottom: 4rem;
    display: flex;
    flex-direction: column;
    width: 42rem;
    box-shadow: $shadow;
    .messages {
        flex: 1;
        display: flex;
        flex-direction: column-reverse; //default scroll to the bottom
        overflow-y: scroll;
        overflow-x: hidden;             //hide bottom scroll bar
        padding: 1rem 1.5rem;
        .bot-message {
            display: flex;
            align-items: center;
            p {
                @include message-item($bot-color);
            }                
        }
        .user-message {
            display: flex;
            align-items: center;                
            align-self: flex-end;   //move to the right
            p {
                @include message-item($user-color);
            }
        }
    }
    ...

Each of the message will have the message type of either 1 for user input or 2 for OpenAI response as shown in the html above. The definition for the messages are shown below:

C#
export enum MessageType {
    Send = 1,
    Receive = 2
}

export interface IChatMsg {
    type: MessageType,
    message: string
}

Below is the code of the component:

C#
export class ChatComponent implements OnInit {

    frmQuery: FormGroup;
    chatHistory: IChatMsg[];

    constructor(private chatSvc: ChatService,
        private fb: FormBuilder) { }

    ngOnInit(): void {
        this.frmQuery = this.fb.group({
            txtQuery: [null]
        });

        this.chatSvc.chat(null)
            .subscribe(aa => this.chatHistory = aa);
    }

    submitQuery() {
        //ignore if no query given
        if (!this.frmQuery.get("txtQuery").value)
            return;
        let queryMessage: IChatMsg = {
            type: MessageType.Send,
            message: this.frmQuery.get("txtQuery").value
        };
        this.chatSvc.chat(queryMessage);
        //blank out textbox for next user input
        this.frmQuery.get("txtQuery").setValue(null);
    }
}

Next, we need to show the latest message at the very bottom and push previous messages upwards. Although the CSS definition will show the messages from bottom to top, the order of the messages are from top to bottom which need to be reversed. Also, each call to the API is merely a request and a response, and the history of the chat will need to be kept. These can be solved by declaring an array to store the chat history, and use the reverse method to return the chat history as an Observable. The code for the chat service is shown below:

C#
export class ChatService {
    readonly url = this.appConfig.get('apiChat');

    private chatHistory = new BehaviorSubject<IChatMsg[] | null>(null);
    chatHistory$ = this.chatHistory.asObservable();

    //stores the chat history
    chatHistoryRecord : IChatMsg[] = [];

    constructor(private http:HttpClient,
                private appConfig:AppConfigService){}

    chat(queryMessage: IChatMsg): Observable<IChatMsg[]>{
        if (!queryMessage)
            return this.chatHistory$;
        this.chatHistoryRecord.push(queryMessage);
        this.chatHistory.next(this.chatHistoryRecord.slice().reverse());
        //get response
        let queryUrl = this.url + "?query=" + queryMessage.message;
        this.http.get<IChatMsg>(queryUrl).pipe()
            .subscribe(result=>{
                if (result){
                    const receiveMsg : IChatMsg = 
                        { type: MessageType.Receive, message : result.message };
                    this.chatHistoryRecord.push(receiveMsg);
                    this.chatHistory.next(this.chatHistoryRecord.slice().reverse());
                }                    
            }
        )
        return this.chatHistory$;
    }
}

And that’s all, hope you will find this project useful.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)