Adicionar preenchimento de chat do OpenAI ao aplicativo para desktop WinUI 3/SDK do Aplicativo Windows
Neste tutorial, você aprenderá a integrar a API do OpenAI ao seu aplicativo para desktop WinUI 3/SDK do Aplicativo Windows. Criaremos uma interface semelhante a um bate-papo que permite gerar respostas a mensagens usando a API de conclusão de bate-papo da OpenAI:
Pré-requisitos
- Configure seu computador de desenvolvimento (consulte Introdução ao WinUI).
- Familiaridade com os principais conceitos em Como criar um aplicativo Olá, Mundo usando C# e WinUI 3/SDK do Aplicativo Windows - vamos nos basear nesse tutorial neste.
- Uma chave de API OpenAI do painel do desenvolvedor de OpenAI.
- Um SDK OpenAI instalado no projeto. Consulte a documentação do OpenAI para obter uma lista completa de bibliotecas da comunidade. Nestas Instruções, usaremos betalgo/openai.
Criar um projeto
- Abra o Visual Studio e crie um projeto por meio de
File
>New
>Project
. - Pesquise o
WinUI
e selecione o modelo de projetoBlank App, Packaged (WinUI 3 in Desktop)
C#. - Especifique um nome de projeto, nome da solução e diretório. Neste exemplo, o projeto
ChatGPT_WinUI3
pertence a uma soluçãoChatGPT_WinUI3
, que será criada emC:\Projects\
.
Após criar o projeto, você verá a seguinte estrutura de arquivo padrão no Gerenciador de Soluções:
Definir as variáveis de ambiente
Para usar o SDK do OpenAI, você precisará definir uma variável de ambiente com a chave de API. Neste exemplo, usaremos a variável de ambiente OPENAI_API_KEY
. Depois de obter a chave de API no painel do desenvolvedor de OpenAI, é possível definir a variável de ambiente da linha de comando da seguinte maneira:
setx OPENAI_API_KEY <your-api-key>
Observe que esse método funciona bem para desenvolvimento, mas convém usar um método mais seguro para aplicativos de produção (por exemplo: é possível armazenar a chave de API em um cofre de chave de segurança que um serviço remoto pode acessar em nome do aplicativo). Consulte Práticas recomendadas para segurança de chaves OpenAI.
Instalar o SDK do OpenAI
No menu View
do Visual Studio, selecione Terminal
. Será exibida uma instância de Developer Powershell
. Execute o seguinte comando no diretório raiz do projeto para instalar o SDK:
dotnet add package Betalgo.OpenAI
Inicializar o SDK
Em MainWindow.xaml.cs
, inicialize o SDK com a chave de API:
//...
using OpenAI;
using OpenAI.Managers;
using OpenAI.ObjectModels.RequestModels;
using OpenAI.ObjectModels;
namespace ChatGPT_WinUI3
{
public sealed partial class MainWindow : Window
{
private OpenAIService openAiService;
public MainWindow()
{
this.InitializeComponent();
var openAiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
openAiService = new OpenAIService(new OpenAiOptions(){
ApiKey = openAiKey
});
}
}
}
Criar a interface do usuário do bate-papo
Usaremos um StackPanel
para exibir uma lista de mensagens e uma TextBox
para permitir que os usuários insiram novas mensagens. Atualize MainWindow.xaml
da seguinte maneira:
<Window
x:Class="ChatGPT_WinUI3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ChatGPT_WinUI3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
<ListView x:Name="ConversationList" />
<StackPanel Orientation="Horizontal">
<TextBox x:Name="InputTextBox" HorizontalAlignment="Stretch"/>
<Button x:Name="SendButton" Content="Send" Click="SendButton_Click"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
Implementar o envio, recebimento e a exibição de mensagens
Adicione um manipulador de eventos SendButton_Click
para manipular o envio, o recebimento e a exibição de mensagens:
public sealed partial class MainWindow : Window
{
// ...
private async void SendButton_Click(object sender, RoutedEventArgs e)
{
string userInput = InputTextBox.Text;
if (!string.IsNullOrEmpty(userInput))
{
AddMessageToConversation($"User: {userInput}");
InputTextBox.Text = string.Empty;
var completionResult = await openAiService.ChatCompletion.CreateCompletion(new ChatCompletionCreateRequest()
{
Messages = new List<ChatMessage>
{
ChatMessage.FromSystem("You are a helpful assistant."),
ChatMessage.FromUser(userInput)
},
Model = Models.Gpt_4_1106_preview,
MaxTokens = 300
});
if (completionResult != null && completionResult.Successful) {
AddMessageToConversation("GPT: " + completionResult.Choices.First().Message.Content);
} else {
AddMessageToConversation("GPT: Sorry, something bad happened: " + completionResult.Error?.Message);
}
}
}
private void AddMessageToConversation(string message)
{
ConversationList.Items.Add(message);
ConversationList.ScrollIntoView(ConversationList.Items[ConversationList.Items.Last()]);
}
}
Executar o aplicativo
Execute o aplicativo e tente conversar! Você deverá ver algo como:
Melhorar a interface de bate-papo
Vamos fazer as seguintes melhorias na interface de bate-papo:
- Adicione um
ScrollViewer
aoStackPanel
para ativar a rolagem. - Adicione um
TextBlock
para exibir a resposta do GPT de uma maneira visualmente mais distinta da entrada do usuário. - Adicione uma
ProgressBar
para indicar quando o aplicativo está aguardando uma resposta da API do GPT. - Centralize o
StackPanel
na janela, semelhante à interface web do ChatGPT. - Certifique-se de que as mensagens sejam quebradas na próxima linha quando atingirem a borda da janela.
- Aumente a
TextBox
e torne-a responsivo à teclaEnter
.
Começando pelo topo:
Adicione ScrollViewer
Empacote a ListView
em um ScrollViewer
para habilitar a rolagem vertical em conversas longas:
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
<ScrollViewer x:Name="ConversationScrollViewer" VerticalScrollBarVisibility="Auto" MaxHeight="500">
<ListView x:Name="ConversationList" />
</ScrollViewer>
<!-- ... -->
</StackPanel>
Use TextBlock
.
Modifique o método AddMessageToConversation
para estilizar a entrada do usuário e a resposta do GPT de forma diferente:
// ...
private void AddMessageToConversation(string message)
{
var messageBlock = new TextBlock();
messageBlock.Text = message;
messageBlock.Margin = new Thickness(5);
if (message.StartsWith("User:"))
{
messageBlock.Foreground = new SolidColorBrush(Colors.LightBlue);
}
else
{
messageBlock.Foreground = new SolidColorBrush(Colors.LightGreen);
}
ConversationList.Items.Add(messageBlock);
ConversationList.ScrollIntoView(ConversationList.Items.Last());
}
Adicione ProgressBar
Para indicar quando o aplicativo está aguardando uma resposta, adicione uma ProgressBar
ao StackPanel
.
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
<ScrollViewer x:Name="ConversationScrollViewer" VerticalScrollBarVisibility="Auto" MaxHeight="500">
<ListView x:Name="ConversationList" />
</ScrollViewer>
<ProgressBar x:Name="ResponseProgressBar" Height="5" IsIndeterminate="True" Visibility="Collapsed"/> <!-- new! -->
</StackPanel>
Em seguida, atualize o manipulador de eventos SendButton_Click
para mostrar a ProgressBar
enquanto aguarda uma resposta:
private async void SendButton_Click(object sender, RoutedEventArgs e)
{
ResponseProgressBar.Visibility = Visibility.Visible; // new!
string userInput = InputTextBox.Text;
if (!string.IsNullOrEmpty(userInput))
{
AddMessageToConversation("User: " + userInput);
InputTextBox.Text = string.Empty;
var completionResult = await openAiService.Completions.CreateCompletion(new CompletionCreateRequest()
{
Prompt = userInput,
Model = Models.TextDavinciV3
});
if (completionResult != null && completionResult.Successful) {
AddMessageToConversation("GPT: " + completionResult.Choices.First().Text);
} else {
AddMessageToConversation("GPT: Sorry, something bad happened: " + completionResult.Error?.Message);
}
}
ResponseProgressBar.Visibility = Visibility.Collapsed; // new!
}
Centralizar o StackPanel
Para centralizar o StackPanel
e transferir as mensagens para baixo em direção à TextBox
, ajuste as configurações de Grid
na MainWindow.xaml
:
<Grid VerticalAlignment="Bottom" HorizontalAlignment="Center">
<!-- ... -->
</Grid>
Quebra de linha de mensagens
Para garantir que as mensagens sejam quebradas para a próxima linha quando atingirem a borda da janela, atualize a MainWindow.xaml
para usar um ItemsControl
.
Substitua:
<ScrollViewer x:Name="ConversationScrollViewer" VerticalScrollBarVisibility="Auto" MaxHeight="500">
<ListView x:Name="ConversationList" />
</ScrollViewer>
Com isso:
<ScrollViewer x:Name="ConversationScrollViewer" VerticalScrollBarVisibility="Auto" MaxHeight="500">
<ItemsControl x:Name="ConversationList" Width="300">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" TextWrapping="Wrap" Margin="5" Foreground="{Binding Color}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
Em seguida, apresentaremos uma classe de MessageItem
para facilitar a associação e a coloração:
// ...
public class MessageItem
{
public string Text { get; set; }
public SolidColorBrush Color { get; set; }
}
// ...
Finalmente, atualize o método AddMessageToConversation
para usar a nova classe MessageItem
:
// ...
private void AddMessageToConversation(string message)
{
var messageItem = new MessageItem();
messageItem.Text = message;
messageItem.Color = message.StartsWith("User:") ? new SolidColorBrush(Colors.LightBlue) : new SolidColorBrush(Colors.LightGreen);
ConversationList.Items.Add(messageItem);
// handle scrolling
ConversationScrollViewer.UpdateLayout();
ConversationScrollViewer.ChangeView(null, ConversationScrollViewer.ScrollableHeight, null);
}
// ...
Melhorar o TextBox
Para aumentar a TextBox
e torná-la responsiva à tecla Enter
, atualize MainWindow.xaml
da seguinte maneira:
<!-- ... -->
<StackPanel Orientation="Vertical" Width="300">
<TextBox x:Name="InputTextBox" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" KeyDown="InputTextBox_KeyDown" TextWrapping="Wrap" MinHeight="100" MaxWidth="300"/>
<Button x:Name="SendButton" Content="Send" Click="SendButton_Click" HorizontalAlignment="Right"/>
</StackPanel>
<!-- ... -->
Em seguida, adicione o manipulador de eventos InputTextBox_KeyDown
para manipular a tecla Enter
:
//...
private void InputTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key == Windows.System.VirtualKey.Enter && !string.IsNullOrWhiteSpace(InputTextBox.Text))
{
SendButton_Click(this, new RoutedEventArgs());
}
}
//...
Executar o aplicativo melhorado
A interface de bate-papo nova e aprimorada deve ter a seguinte aparência:
Recapitulação
Veja aqui o que você realizou neste tutorial:
- Você adicionou os recursos de API do OpenAI ao aplicativo para desktop WinUI 3/SDK do Aplicativo Windows instalando um SDK da comunidade e inicializando-o com a chave de API.
- Criamos uma interface semelhante a um bate-papo que permite gerar respostas a mensagens usando a API de conclusão de bate-papo da OpenAI:
- Você melhorou a interface de bate-papo ao:
- adicionar um
ScrollViewer
, - usar um
TextBlock
para exibir a resposta do GPT, - adicionar uma
ProgressBar
para indicar quando o aplicativo está aguardando uma resposta da API do GPT. - centralizar o
StackPanel
na janela, - garantir que as mensagens sejam quebradas para a próxima linha quando atingirem a borda da janela e
- tornando o
TextBox
maior, redimensionável e responsivo àEnter
chave.
- adicionar um
Arquivos de código completos
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="ChatGPT_WinUI3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ChatGPT_WinUI3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid VerticalAlignment="Bottom" HorizontalAlignment="Center">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
<ScrollViewer x:Name="ConversationScrollViewer" VerticalScrollBarVisibility="Auto" MaxHeight="500">
<ItemsControl x:Name="ConversationList" Width="300">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" TextWrapping="Wrap" Margin="5" Foreground="{Binding Color}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<ProgressBar x:Name="ResponseProgressBar" Height="5" IsIndeterminate="True" Visibility="Collapsed"/>
<StackPanel Orientation="Vertical" Width="300">
<TextBox x:Name="InputTextBox" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" KeyDown="InputTextBox_KeyDown" TextWrapping="Wrap" MinHeight="100" MaxWidth="300"/>
<Button x:Name="SendButton" Content="Send" Click="SendButton_Click" HorizontalAlignment="Right"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using System;
using System.Collections.Generic;
using System.Linq;
using OpenAI;
using OpenAI.Managers;
using OpenAI.ObjectModels.RequestModels;
using OpenAI.ObjectModels;
namespace ChatGPT_WinUI3
{
public class MessageItem
{
public string Text { get; set; }
public SolidColorBrush Color { get; set; }
}
public sealed partial class MainWindow : Window
{
private OpenAIService openAiService;
public MainWindow()
{
this.InitializeComponent();
var openAiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
openAiService = new OpenAIService(new OpenAiOptions(){
ApiKey = openAiKey
});
}
private async void SendButton_Click(object sender, RoutedEventArgs e)
{
ResponseProgressBar.Visibility = Visibility.Visible;
string userInput = InputTextBox.Text;
if (!string.IsNullOrEmpty(userInput))
{
AddMessageToConversation("User: " + userInput);
InputTextBox.Text = string.Empty;
var completionResult = await openAiService.ChatCompletion.CreateCompletion(new ChatCompletionCreateRequest()
{
Messages = new List<ChatMessage>
{
ChatMessage.FromSystem("You are a helpful assistant."),
ChatMessage.FromUser(userInput)
},
Model = Models.Gpt_4_1106_preview,
MaxTokens = 300
});
Console.WriteLine(completionResult.ToString());
if (completionResult != null && completionResult.Successful)
{
AddMessageToConversation("GPT: " + completionResult.Choices.First().Message.Content);
}
else
{
AddMessageToConversation("GPT: Sorry, something bad happened: " + completionResult.Error?.Message);
}
}
ResponseProgressBar.Visibility = Visibility.Collapsed;
}
private void AddMessageToConversation(string message)
{
var messageItem = new MessageItem();
messageItem.Text = message;
messageItem.Color = message.StartsWith("User:") ? new SolidColorBrush(Colors.LightBlue) : new SolidColorBrush(Colors.LightGreen);
ConversationList.Items.Add(messageItem);
// handle scrolling
ConversationScrollViewer.UpdateLayout();
ConversationScrollViewer.ChangeView(null, ConversationScrollViewer.ScrollableHeight, null);
}
private void InputTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key == Windows.System.VirtualKey.Enter && !string.IsNullOrWhiteSpace(InputTextBox.Text))
{
SendButton_Click(this, new RoutedEventArgs());
}
}
}
}
Relacionados
Windows developer