RianNegreiros

  • Blog
  • Projetos
  • Currículo

Site Logo
  • Blog
  • Projetos
  • Currículo
  • RSS Feed
© 2025 riannegreiros.xyz. Todos os direitos reservados.
LinkedIn

Criando um chatbot com ASP.NET Core, Next.js e Google Gemini

Publicado 28 de fevereiro de 2024

Demonstração da aplicação em funcionamento
Image

Vamos analisar o código passo a passo para entender como ele funciona e o que cada parte faz.

Estrutura do projeto

O projeto está estruturado da seguinte forma:

1├── ChatBot.sln
2├── compose.yaml
3└── src
4    ├── API
5    │   ├── Dockerfile
6    │   ├── Extensions
7    │   │   ├── BuilderExtensions.cs
8    │   │   └── GoogleCloudLanguageApiHealthCheck.cs
9    │   ├── Program.cs
10    │   ├── Using.cs
11    │   ├── appsettings.json
12    └── client
13        ├── Dockerfile
14        ├── app
15        │   ├── components
16        │   │   ├── chat.tsx
17        │   │   ├── danger-error.tsx
18        │   │   └── loading.tsx
19        │   ├── hooks
20        │   │   └── useChat.ts
21        │   ├── layout.tsx
22        │   └── page.tsx
23

Program.cs

Este é o principal ponto de entrada da aplicação. Estabelece a aplicação Web, configura os serviços e define o pipeline de middleware.

1using System.Text;
2using System.Text.Json;
3using System.Threading.RateLimiting;
4
5var builder = WebApplication.CreateBuilder(args);
6
7// Add services to the container.
8// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
9builder.Services.AddEndpointsApiExplorer();
10builder.Services.AddServicesExtension();
11builder.Services.AddSwaggerExtension();
12builder.Services.AddCorsExtension(builder.Configuration);
13builder.Services.AddHealthChecksExtension(builder.Configuration);
14builder.Services.AddRateLimiter(options =>
15{
16    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
17
18    options.AddPolicy("fixed", httpContext =>
19            RateLimitPartition.GetFixedWindowLimiter(
20                partitionKey: httpContext.Connection.RemoteIpAddress?.ToString(),
21                factory: partition => new FixedWindowRateLimiterOptions
22                {
23                    PermitLimit = 10,
24                    Window = TimeSpan.FromSeconds(10)
25                }));
26});
27
28var app = builder.Build();
29
30// Configure the HTTP request pipeline.
31app.UseSwagger();
32app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "API for a Chat Bot"));
33
34app.MapHealthChecks("/health");
35
36app.UseHttpsRedirection();
37
38app.UseRateLimiter();
39
40app.MapPost("/prompt/{text}", async (
41    string text,
42    IHttpClientFactory factory,
43    HttpContext httpContext) =>
44{
45    var languageModelApiKey = app.Configuration["LANGUAGE_MODEL:API_KEY"];
46    var languageModelUrl = $"{app.Configuration["LANGUAGE_MODEL:URL"]}?key={languageModelApiKey}";
47
48    var payload = new
49    {
50        prompt = new { messages = new[] { new { content = text } } },
51        temperature = 0.1,
52        candidate_count = 1
53    };
54
55    try
56    {
57        using var httpClient = factory.CreateClient();
58        var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
59        var response = await httpClient.PostAsync(languageModelUrl, content);
60        var data = await response.Content.ReadAsStringAsync();
61
62        app.Logger.LogInformation($"Response received from the API: {data}");
63
64        await httpContext.Response.WriteAsync(data);
65    }
66    catch (Exception ex)
67    {
68        app.Logger.LogError(ex, "An error occurred while contacting the API.");
69    }
70})
71.WithName("Generate Language Model Response")
72.WithSummary("Return a Language Model Response")
73.WithDescription("Return a Language Model Response from PaLM API")
74.WithOpenApi(generatedOperation =>
75{
76    var parameter = generatedOperation.Parameters[0];
77    parameter.Description = "The text to be processed by the language model";
78    return generatedOperation;
79})
80.Produces(StatusCodes.Status200OK)
81.Produces(StatusCodes.Status400BadRequest)
82.Produces(StatusCodes.Status500InternalServerError)
83.RequireRateLimiting("fixed");
84
85app.UseCors("AllowClient");
86
87app.Run();

Partes-chave:

  1. Configuração do serviço:
    • builder.Services.AddServicesExtension(): adiciona serviços personalizados definidos em BuilderExtensions.cs.
    • builder.Services.AddSwaggerExtension(): Adiciona Swagger para documentação da API.
    • builder.Services.AddCorsExtension(builder.Configuration): adiciona política CORS.
    • builder.Services.AddHealthChecksExtension(builder.Configuration): adiciona os health checks.
    • builder.Services.AddRateLimiter(...): configura o rate limiting para evitar abusos.
  2. Middleware Pipeline:
    • app.MapHealthChecks(“/health”): mapeia o endpoint de health check.
    • app.UseRateLimiter(): habilita o middleware de rate limiting.
    • app.MapPost(“/prompt/{text}”, ...): define um endpoint para gerar uma resposta da API do Google Gemini.

Extensions/BuilderExtensions.cs

Esse arquivo contém os métodos para adicionar os serviços para o contêiner de injeção de dependência.

1using Microsoft.OpenApi.Models;
2
3namespace API.Extensions;
4
5public static class BuilderExtensions
6{
7    public static void AddSwaggerExtension(this IServiceCollection services) {
8        services.AddSwaggerGen(options => options.SwaggerDoc("v1", new OpenApiInfo
9        {
10            Version = "v1",
11            Title = "Chat bot API",
12            Description = "A ASP.NET Core 8 minimal API to generate messages for a chat bot using PaLM 2 API",
13        }));
14    }
15
16    public static void AddCorsExtension(this IServiceCollection services, IConfiguration config) {
17        var clientUrl = config["Client_Url"] ?? "http://localhost:3000";
18
19        services.AddCors(options => options.AddPolicy(name: "AllowClient", policy =>
20        policy.WithOrigins(clientUrl)
21        .AllowAnyHeader()
22        .WithMethods("POST")));
23    }
24
25    public static void AddHealthChecksExtension(this IServiceCollection services, IConfiguration config) {
26        services.AddHttpClient();
27        services.AddHealthChecks()
28          .AddCheck("google-api", new GoogleColabHealthCheck(services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>(), config, services.BuildServiceProvider().GetRequiredService<ILogger<GoogleColabHealthCheck>>()));
29    }
30
31    public static void AddServicesExtension(this IServiceCollection services) {
32        services.AddHttpClient();
33    }
34}

Métodos-chave

  1. AddSwaggerExtension: Configura o Swagger para a documentação da API.
  2. AddCorsExtension: Configura o CORS para permitir requisições dos clientes especificadas.
  3. AddHealthChecksExtension: Adiciona verificações de integridade, incluindo uma verificação personalizada para a API do Google.
  4. AddServicesExtension: Adiciona serviços de cliente HTTP.

Extensions/GoogleCloudLanguageApiHealthCheck.cs

Este arquivo define um controller de health check personalizado para a API do Google.

1using System.Text;
2
3using Microsoft.Extensions.Diagnostics.HealthChecks;
4
5namespace API.Extensions;
6
7public class GoogleColabHealthCheck(IHttpClientFactory httpClientFactory, IConfiguration configuration, ILogger<GoogleColabHealthCheck> logger) : IHealthCheck {
8    private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;
9    private readonly IConfiguration _configuration = configuration;
10    private readonly ILogger<GoogleColabHealthCheck> _logger = logger;
11
12    public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken) {
13        try
14        {
15            var httpClient = _httpClientFactory.CreateClient();
16            var languageModelApiKey = _configuration["LANGUAGE_MODEL:API_KEY"];
17            var request = new HttpRequestMessage(HttpMethod.Post, $"{_configuration["LANGUAGE_MODEL:URL"]}?key={languageModelApiKey}")
18            {
19                Content = new StringContent(
20                "{ \"contents\":[{\"parts\":[{\"text\":\"hi\"}]}] }",
21                Encoding.UTF8,
22                "application/json")
23            };
24
25            HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken);
26
27            if (response.IsSuccessStatusCode)
28            {
29                _logger.LogInformation("Google API responded with success status code.");
30                return HealthCheckResult.Healthy();
31            }
32            else
33            {
34                _logger.LogError($"Google API responded with: {response.StatusCode} - {response.ReasonPhrase}");
35                return HealthCheckResult.Unhealthy($"Google API responded with: {response.StatusCode} - {response.ReasonPhrase}");
36            }
37        }
38        catch (Exception ex)
39        {
40            _logger.LogError(ex, "An error occurred while contacting Google API.");
41            return HealthCheckResult.Unhealthy("An error occurred while contacting Google API: " + ex.Message);
42        }
43    }
44}

Pontos-chave

  • CheckHealthAsync: Este método envia uma request para a API do Google e verifica se a resposta é bem sucedida. Se a API responder com um código de sucesso, devolve HealthCheckResult.Healthy(). Caso contrário, devolve HealthCheckResult.Unhealthy() com os detalhes do erro.

appsettings.json

1{
2  "Logging": {
3    "LogLevel": {
4      "Default": "Information",
5      "Microsoft.AspNetCore": "Warning"
6    }
7  },
8  "LANGUAGE_MODEL": {
9    "URL": "https://generativelanguage.googleapis.com/v1beta1/models/chat-bison-001:generateMessage",
10    "API_KEY": ""
11  }
12  "AllowedHosts": "*",
13  "ClientUrl": "http://localhost:3000"
14}
15

Definições de chave:

  • ClientUrl: Especifica o URL da aplicação cliente.
  • LANGUAGE_MODEL:URL: URL da API para fazer requests à API do PaLM.
  • LANGUAGE_MODEL:API_KEY: chave para dar acesso à API do PaLM.

Pode-se obter a chave da API e o URL da API REST aqui:

Tutorial: Get started with the Gemini API

Using.cs (Opcional)

Esse arquivo contém diretivas de uso globais para simplificar o código.

1global using API.Extensions;

Resumo

  • Program.cs: configura o aplicativo da Web, configura os serviços e define o pipeline de middleware.
  • BuilderExtensions.cs: contém métodos de extensão para adicionar vários serviços ao contêiner DI.
  • GoogleCloudLanguageApiHealthCheck.cs: define uma health check personalizada para a API do Google.

Cliente

Agora vamos analisar o código do cliente do chatbot.

Configuração do projeto

1. Criando o projeto:

1cd src
2npx create-next-app@latest client --typescript
3cd client

2. Instalando o TailwindCSS:

1npm install -D tailwindcss postcss autoprefixer
2npx tailwindcss init -p

3. Configurando o TailwindCSS:

tailwind.config.js:

1import type { Config } from 'tailwindcss'
2
3   const config: Config = {
4     content: [
5       './pages/**/*.{js,ts,jsx,tsx,mdx}',
6       './components/**/*.{js,ts,jsx,tsx,mdx}',
7       './app/**/*.{js,ts,jsx,tsx,mdx}',
8     ],
9     theme: {
10       extend: {
11         backgroundImage: {
12           'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
13           'gradient-conic':
14             'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
15         },
16         animation: {
17           'pulse-slow': 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
18           'pulse-normal': 'pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite',
19           'pulse-fast': 'pulse 1s cubic-bezier(0.4, 0, 0.6, 1) infinite',
20         },
21       },
22     },
23     plugins: [],
24   }
25   export default config

styles/globals.css:

1@tailwind base;
2@tailwind components;
3@tailwind utilities;

4. Configuração de variáveis de ambiente:

.env.local:

1NEXT_PUBLIC_API_URL=http://localhost:8080

Componentes

components/chat.tsx

Este é o componente principal do chat que lida com a entrada do usuário, exibe mensagens e mostra os estados de carregamento e erro.

O styling desse component é feito a partir desse componente da Flowbite:

Tailwind CSS Textarea - Flowbite

1'use client'
2
3import React, { useEffect, useRef } from 'react'
4import Loading from './loading'
5import ReactMarkdown from 'react-markdown'
6import DangerError from './danger-error'
7import { useChat } from '../hooks/useChat'
8
9export default function Chat() {
10  const {
11    text,
12    setText,
13    messages,
14    isLoading,
15    errorMessage,
16    handleSubmit,
17    dismissError,
18  } = useChat()
19
20  const inputRef = useRef<HTMLInputElement>(null);
21  const messagesEndRef = useRef<HTMLDivElement>(null)
22
23  useEffect(() => {
24    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
25  }, [messages])
26
27  useEffect(() => {
28    if (!isLoading) {
29      inputRef.current?.focus();
30    }
31  }, [isLoading]);
32
33  return (
34    <div className='p-6'>
35      {errorMessage && (
36        <DangerError message={errorMessage} dismissError={dismissError} />
37      )}
38      <div className='mb-4 rounded-lg border border-gray-200 p-4 dark:border-gray-700 dark:bg-gray-900'>
39        {messages.map((message, index) => (
40          <div
41            key={index}
42            className={`mb-4 ${message.author === 'Bot' ? 'text-blue-500' : 'text-green-500'}`}
43          >
44            <strong>{message.author}</strong>:{' '}
45            <ReactMarkdown>{message.content}</ReactMarkdown>
46          </div>
47        ))}
48        <div ref={messagesEndRef} />
49        {isLoading && <Loading />}
50      </div>
51      <form onSubmit={handleSubmit}>
52        <label htmlFor='chat' className='sr-only'>
53          Your message
54        </label>
55        <div className='flex items-center rounded-lg bg-gray-50 px-3 py-2 dark:bg-gray-700'>
56          <input
57            id='chat'
58            disabled={isLoading}
59            ref={inputRef}
60            className='mx-4 block w-full rounded-lg border border-gray-300 bg-white p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500'
61            placeholder='Your message...'
62            value={text}
63            onChange={(e) => setText(e.target.value)}
64          />
65          <button
66            type='submit'
67            disabled={isLoading}
68            className='inline-flex cursor-pointer justify-center rounded-full p-2 text-blue-600 hover:bg-blue-100 dark:text-blue-500 dark:hover:bg-gray-600'
69          >
70            <svg
71              className='h-5 w-5 rotate-90 rtl:-rotate-90'
72              aria-hidden='true'
73              xmlns='<http://www.w3.org/2000/svg>'
74              fill='currentColor'
75              viewBox='0 0 18 20'
76            >
77              <path d='m17.914 18.594-8-18a1 1 0 0 0-1.828 0l-8 18a1 1 0 0 0 1.157 1.376L8 18.281V9a1 1 0 0 1 2 0v9.281l6.758 1.689a1 1 0 0 0 1.156-1.376Z' />
78            </svg>
79            <span className='sr-only'>Send message</span>
80          </button>
81        </div>
82      </form>
83    </div>
84  )
85}

  • UseChat Hook: Esse hook gerencia o estado do chat, incluindo a entrada de texto, as mensagens, o estado de carregamento e o tratamento de erros.
  • UseRef: Usado para manter referências ao campo de entrada e ao final da lista de mensagens para rolagem.
  • useEffect: Usado para rolar até a última mensagem e focar o campo de entrada quando não estiver carregando.

components/loading.tsx

Esse componente exibe uma animação de carregamento enquanto aguarda a resposta do bot.

Esse componente é uma modificação refatorada da Flowbite:

Tailwind CSS Skeleton - Flowbite

1interface LoadingBarProps {
2  width: string;
3  animation: string;
4  bgColor: string;
5  marginLeft: string;
6}
7
8const LoadingBar: React.FC<LoadingBarProps> = ({ width, animation, bgColor, marginLeft }) => (
9  <div className={`h-2.5 ${width} ${animation} rounded-full ${bgColor} ${marginLeft}`}></div>
10);
11
12export default function Loading() {
13  const loadingBars = [
14    [
15      { width: 'w-32', animation: 'animate-pulse-slow', bgColor: 'bg-gray-200 dark:bg-gray-700', marginLeft: '' },
16      { width: 'w-24', animation: 'animate-pulse-normal', bgColor: 'bg-gray-300 dark:bg-gray-600', marginLeft: 'ms-2' },
17      { width: 'w-full', animation: 'animate-pulse-fast', bgColor: 'bg-gray-300 dark:bg-gray-600', marginLeft: 'ms-2' },
18    ],
19    [
20      { width: 'w-full', animation: 'animate-pulse-normal', bgColor: 'bg-gray-200 dark:bg-gray-700', marginLeft: '' },
21      { width: 'w-full', animation: 'animate-pulse-slow', bgColor: 'bg-gray-300 dark:bg-gray-600', marginLeft: 'ms-2' },
22      { width: 'w-24', animation: 'animate-pulse-fast', bgColor: 'bg-gray-300 dark:bg-gray-600', marginLeft: 'ms-2' },
23    ],
24    [
25      { width: 'w-full', animation: 'animate-pulse-fast', bgColor: 'bg-gray-300 dark:bg-gray-600', marginLeft: '' },
26      { width: 'w-80', animation: 'animate-pulse-normal', bgColor: 'bg-gray-200 dark:bg-gray-700', marginLeft: 'ms-2' },
27      { width: 'w-full', animation: 'animate-pulse-slow', bgColor: 'bg-gray-300 dark:bg-gray-600', marginLeft: 'ms-2' },
28    ],
29    [
30      { width: 'w-full', animation: 'animate-pulse-slow', bgColor: 'bg-gray-200 dark:bg-gray-700', marginLeft: 'ms-2' },
31      { width: 'w-full', animation: 'animate-pulse-normal', bgColor: 'bg-gray-300 dark:bg-gray-600', marginLeft: 'ms-2' },
32      { width: 'w-24', animation: 'animate-pulse-fast', bgColor: 'bg-gray-300 dark:bg-gray-600', marginLeft: 'ms-2' },
33    ],
34    [
35      { width: 'w-32', animation: 'animate-pulse-normal', bgColor: 'bg-gray-300 dark:bg-gray-600', marginLeft: 'ms-2' },
36      { width: 'w-24', animation: 'animate-pulse-slow', bgColor: 'bg-gray-300 dark:bg-gray-600', marginLeft: 'ms-2' },
37      { width: 'w-full', animation: 'animate-pulse-fast', bgColor: 'bg-gray-200 dark:bg-gray-700', marginLeft: 'ms-2' },
38    ],
39    [
40      { width: 'w-full', animation: 'animate-pulse-fast', bgColor: 'bg-gray-300 dark:bg-gray-600', marginLeft: 'ms-2' },
41      { width: 'w-80', animation: 'animate-pulse-normal', bgColor: 'bg-gray-200 dark:bg-gray-700', marginLeft: 'ms-2' },
42      { width: 'w-full', animation: 'animate-pulse-slow', bgColor: 'bg-gray-300 dark:bg-gray-600', marginLeft: 'ms-2' },
43    ],
44  ];
45
46  return (
47    <div role='status' className='mb-4 space-y-2.5 p-6 dark:bg-gray-900'>
48      {loadingBars.map((bars, index) => (
49        <div key={index} className='flex w-full items-center'>
50          {bars.map((bar, idx) => (
51            <LoadingBar key={idx} {...bar} />
52          ))}
53        </div>
54      ))}
55      <span className='sr-only'>Loading...</span>
56    </div>
57  );
58}
  • Loading Animation: Usa as classes TailwindCSS para criar um efeito de animação pulsante.

components/danger-error.tsx

Esse componente exibe uma mensagem de erro quando algo dá errado.

O styling desse component é feito a partir desse componente da Flowbite: Tailwind CSS Alerts - Flowbite

1interface DangerErrorTypes {
2  message: string
3  dismissError: () => void
4}
5
6export default function DangerError({
7  message,
8  dismissError,
9}: DangerErrorTypes) {
10  return (
11    <div id='alert-border-2' className='mb-4 flex items-center border-t-4 border-red-300 bg-red-50 p-4 text-red-800 dark:border-red-800 dark:bg-gray-800 dark:text-red-400' role='alert' >
12      <svg className='h-4 w-4 flex-shrink-0' aria-hidden='true' xmlns='<http://www.w3.org/2000/svg>' fill='currentColor' viewBox='0 0 20 20' >
13        <path d='M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z' />
14      </svg>
15      <div className='ms-3 text-sm font-medium'>{message}</div>
16      <button type='button' onClick={dismissError} className='-mx-1.5 -my-1.5 ms-auto inline-flex h-8 w-8 items-center justify-center rounded-lg bg-red-50 p-1.5 text-red-500 hover:bg-red-200 focus:ring-2 focus:ring-red-400 dark:bg-gray-800 dark:text-red-400 dark:hover:bg-gray-700' data-dismiss-target='#alert-border-2' aria-label='Close' >
17        <span className='sr-only'>Dismiss</span>
18        <svg className='h-3 w-3' aria-hidden='true' xmlns='<http://www.w3.org/2000/svg>' fill='none' viewBox='0 0 14 14' >
19          <path stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' strokeWidth='2' d='m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6' />
20        </svg>
21      </button>
22    </div>
23  )
24}
  • Tratamento de erros: Exibe uma mensagem de erro com um botão de ignorar.

Hook

hooks/useChat.ts

Esse hook gerencia o estado do chat, incluindo as respostas da API.

1import { useState, useEffect } from 'react'
2import axios, { AxiosError } from 'axios'
3
4const API_URL = process.env.NEXT_PUBLIC_API_URL
5
6type Message = {
7  author: string
8  content: string
9}
10
11export const useChat = () => {
12  const [text, setText] = useState('')
13  const [messages, setMessages] = useState<Message[]>([])
14  const [isLoading, setIsLoading] = useState(false)
15  const [errorMessage, setErrorMessage] = useState<string | null>(null)
16
17  useEffect(() => {
18    const savedMessages = sessionStorage.getItem('messages')
19    if (savedMessages) {
20      setMessages(JSON.parse(savedMessages))
21    }
22  }, [])
23
24  const handleSubmit = (e: React.FormEvent) => {
25    e.preventDefault()
26    setMessages((prevMessages) => {
27      const newMessages = [...prevMessages, { author: 'User', content: text }]
28      sessionStorage.setItem('messages', JSON.stringify(newMessages))
29      return newMessages
30    })
31    setText('')
32    setTimeout(getResponse, 0)
33  }
34
35  const getResponse = async () => {
36    setIsLoading(true)
37    try {
38      const response = await axios.get(`${API_URL}/prompt/${text}`)
39      const data = response.data
40      setMessages((prevMessages) => {
41        const newMessages = [
42          ...prevMessages,
43          { author: 'Bot', content: data.candidates[0].content },
44        ]
45        sessionStorage.setItem('messages', JSON.stringify(newMessages))
46        return newMessages
47      })
48    } catch (error) {
49      const axiosError = error as AxiosError
50      let errorMessage = 'An unexpected error occurred'
51      if (axiosError.response) {
52        errorMessage = axiosError.message
53      }
54      setErrorMessage(errorMessage)
55    }
56    setIsLoading(false)
57  }
58
59  const dismissError = () => {
60    setErrorMessage(null)
61  }
62
63  return {
64    text,
65    setText,
66    messages,
67    isLoading,
68    errorMessage,
69    handleSubmit,
70    dismissError,
71  }
72}
  • Gerenciamento de estado: Gerencia o estado da entrada de texto, das mensagens, do estado de carregamento e das mensagens de erro.
  • UseEffect: Carrega mensagens salvas do sessionStorage na renderização inicial.
  • handleSubmit: Lida com o envio de formulários, atualiza mensagens e aciona a resposta do bot.
  • getResponse: Obtém a resposta do bot da API e atualiza as mensagens.

Página principal

app/page.tsx

Renderizamos o componente Chat.

1import Chat from '../components/chat'
2
3export default function Home() {
4  return <Chat /> 
5}

Executando o aplicativo

Por fim, executamos a aplicação:

1npm run dev

Vá até http://localhost:3000 no navegador para ver o client do chatbot em funcionamento.

Conclusão

Esse código implementa um cliente Next.js com TypeScript e TailwindCSS para um chatbot. O componente Chat manipula a entrada do usuário, exibe mensagens e gerência os estados de carregamento e erro. O hook useChat gerencia o estado do chat e interage com a API do chatbot.

Repositório do código

Inspirado pelo vídeo - PaLM 2 API Course – Build Generative AI Apps da freeCodeCamp

Tabela de Conteúdos

  • Estrutura do projeto
  • Program.cs
  • Partes-chave:
  • Extensions/BuilderExtensions.cs
  • Métodos-chave
  • Extensions/GoogleCloudLanguageApiHealthCheck.cs
  • Pontos-chave
  • appsettings.json
  • Definições de chave:
  • Using.cs (Opcional)
  • Resumo
  • Cliente
  • Configuração do projeto
  • Componentes
  • components/chat.tsx
  • components/loading.tsx
  • components/danger-error.tsx
  • Hook
  • hooks/useChat.ts
  • Página principal
  • app/page.tsx
  • Executando o aplicativo
  • Conclusão