Incorporación de finalizaciones de chat de OpenAI a la aplicación de escritorio winUI 3 o al SDK de Aplicaciones para Windows
En este procedimiento, aprenderá a integrar la API de OpenAI en la aplicación de escritorio WinUI 3 o el SDK de Aplicaciones para Windows. Crearemos una interfaz similar al chat que le permita generar respuestas a mensajes mediante la API de finalizaciones de chat de OpenAI:
Requisitos previos
- Configure el equipo de desarrollo (consulte Introducción a WinUI).
- Familiaridad con los conceptos básicos de los artículos Cómo compilar una aplicación de tipo "Hola mundo" mediante C# y WinUI 3 o el SDK de Aplicaciones para Windows: se realizará la compilación según el procedimiento que aquí se indica.
- Una clave de API de OpenAI desde el panel para desarrolladores de OpenAI.
- Un SDK de OpenAI instalado en el proyecto. Consulte la documentación de OpenAI para obtener una lista completa de bibliotecas comunitarias. En estas instrucciones, usaremos betalgo/openai.
Creación de un proyecto
- Abra Visual Studio y cree un proyecto nuevo mediante
File
>New
>Project
. - Busque
WinUI
y seleccione la plantilla de proyecto de C#Blank App, Packaged (WinUI 3 in Desktop)
. - Especifique el nombre del proyecto, el nombre de la solución y el directorio. En este ejemplo, el proyecto
ChatGPT_WinUI3
pertenece a una soluciónChatGPT_WinUI3
, que se creará enC:\Projects\
.
Después de crear el proyecto, debería ver la siguiente estructura predeterminada de archivos en el Explorador de soluciones:
Establecimiento de la variable de entorno
Para usar el SDK de OpenAI, deberá establecer una variable de entorno con la clave de API. En este ejemplo, usaremos la variable de entorno OPENAI_API_KEY
. Una vez que tenga la clave de API del panel para desarrolladores de OpenAI, puede establecer la variable de entorno desde la línea de comandos de la manera siguiente:
setx OPENAI_API_KEY <your-api-key>
Tenga en cuenta que este método funciona bien en la fase de desarrollo, pero posiblemente quiera usar un método más seguro para las aplicaciones de producción (por ejemplo, puede almacenar la clave de API en un almacén de claves seguro al que un servicio remoto pueda acceder en nombre de la aplicación). Consulte Procedimientos recomendados para la seguridad de claves de OpenAI.
Instalación del SDK de OpenAI
En el menú View
de Visual Studio, seleccione Terminal
. Verá que aparece una instancia de Developer Powershell
. Ejecute el siguiente comando en el directorio raíz del proyecto para instalar el SDK:
dotnet add package Betalgo.OpenAI
Inicializar el SDK
En MainWindow.xaml.cs
, inicie el SDK con la clave 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
});
}
}
}
Generación de la interfaz de usuario del chat
Usará StackPanel
para mostrar una lista de mensajes y TextBox
para permitir que los usuarios escriban nuevos mensajes. Actualice MainWindow.xaml
de la siguiente manera:
<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>
Implementación del envío, recepción y visualización de mensajes
Agregue un controlador de eventos SendButton_Click
para controlar el envío, la recepción y la visualización de mensajes:
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()]);
}
}
Ejecución de la aplicación
Ejecute la aplicación y pruebe a chatear. Deberíamos ver algo parecido a lo siguiente:
Mejora de la interfaz del chat
Vamos a realizar las siguientes mejoras en la interfaz del chat:
- Agregue
ScrollViewer
aStackPanel
para habilitar el desplazamiento. - Agregue
TextBlock
para mostrar la respuesta GPT de una manera que se diferencie aún más de la entrada del usuario. - Agregue
ProgressBar
para indicar en momento en que la aplicación está esperando una respuesta de la API de GPT. - Centre
StackPanel
en la ventana, para que quede similar a la interfaz web de ChatGPT. - Asegúrese de que los mensajes se ajusten a la línea siguiente cuando lleguen al borde de la ventana.
- Haga que
TextBox
sea mayor y responda a la claveEnter
.
A partir de la parte superior:
Agregue ScrollViewer
.
Ajuste ListView
en ScrollViewer
para habilitar el desplazamiento vertical en conversaciones largas:
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
<ScrollViewer x:Name="ConversationScrollViewer" VerticalScrollBarVisibility="Auto" MaxHeight="500">
<ListView x:Name="ConversationList" />
</ScrollViewer>
<!-- ... -->
</StackPanel>
Use TextBlock
Modifique el método AddMessageToConversation
para dar diferentes estilos a la entrada de usuario y a la respuesta de GPT:
// ...
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());
}
Agregue ProgressBar
.
Para indicar si la aplicación está esperando una respuesta, agregue ProgressBar
a 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>
A continuación, actualice el controlador de eventos SendButton_Click
para mostrar ProgressBar
mientras espera una respuesta:
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!
}
Centrado de StackPanel
Para centrar StackPanel
y desplegar los mensajes hacia TextBox
, ajuste la configuración Grid
en MainWindow.xaml
:
<Grid VerticalAlignment="Bottom" HorizontalAlignment="Center">
<!-- ... -->
</Grid>
Ajuste de mensajes
Para asegurarse de que los mensajes se ajusten a la línea siguiente cuando lleguen al borde de la ventana, actualice MainWindow.xaml
para usar ItemsControl
.
Reemplace esto:
<ScrollViewer x:Name="ConversationScrollViewer" VerticalScrollBarVisibility="Auto" MaxHeight="500">
<ListView x:Name="ConversationList" />
</ScrollViewer>
Con esto:
<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>
A continuación, presentaremos una clase MessageItem
para facilitar el enlace y el coloreado:
// ...
public class MessageItem
{
public string Text { get; set; }
public SolidColorBrush Color { get; set; }
}
// ...
Por último, actualice el método AddMessageToConversation
para usar la nueva clase 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);
}
// ...
Mejora de TextBox
Para que TextBox
sea más grande y responda a la clave Enter
, actualice MainWindow.xaml
de la siguiente manera:
<!-- ... -->
<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>
<!-- ... -->
A continuación, agregue el controlador de eventos InputTextBox_KeyDown
para controlar la clave 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());
}
}
//...
Ejecución de la aplicación mejorada
La interfaz de chat nueva y mejorada debe tener un aspecto similar al siguiente:
Resumen
Esto es lo que ha logado en estas instrucciones:
- Ha agregado las funcionalidades de API de OpenAI a la aplicación de escritorio winUI 3 o el SDK de Aplicaciones para Windows mediante la instalación de un SDK de la comunidad y la inicialización con la clave de API.
- Ha compilado una interfaz similar al chat que le permite generar respuestas a mensajes mediante la API de finalizaciones de chat de OpenAI.
- Ha mejorado la interfaz de chat:
- al agregar
ScrollViewer
, - al usar
TextBlock
para mostrar la respuesta GPT, - al agregar
ProgressBar
para indicar el momento en que la aplicación está esperando una respuesta de la API de GPT, - al centrar
StackPanel
en la ventana, - al asegurarse de que los mensajes se ajustan a la siguiente línea cuando llegan al borde de la ventana y
- hacer que sea
TextBox
mayor, redimensionable y responda a laEnter
clave.
- al agregar
Archivos de código completo
<?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());
}
}
}
}