Udostępnij przez


Dodaj funkcję uzupełniania czatów OpenAI do aplikacji desktopowej WinUI 3 / Zestaw SDK aplikacji systemu Windows

Z tego przewodnika dowiesz się, jak zintegrować API OpenAI z aplikacją desktopową WinUI 3 / Windows App SDK. Utworzymy interfejs przypominający czat, który umożliwia generowanie odpowiedzi na wiadomości przy użyciu API generowania tekstu i podpowiedzi OpenAI.

Zrzut ekranu przedstawiający mniej minimalną aplikację do czatu WinUI.

Prerequisites

Tworzenie projektu

W programie Visual Studio utworzysz nowy projekt WinUI, wykonując kroki opisane w sekcji Tworzenie i uruchamianie pierwszej aplikacji WinUI w artykule Rozpoczynanie opracowywania aplikacji systemu Windows . W tym przykładzie wprowadź ChatGPT_WinUI3 jako nazwę projektu i ChatGPT_WinUI3 nazwę rozwiązania podczas wprowadzania szczegółów projektu w oknie dialogowym.

Ustawianie zmiennej środowiskowej

Aby korzystać z zestawu OpenAI SDK, należy ustawić zmienną środowiskową przy użyciu klucza interfejsu API. W tym przykładzie użyjemy zmiennej środowiskowej OPENAI_API_KEY . Po uzyskaniu klucza interfejsu API z pulpitu nawigacyjnego dewelopera OpenAI, możesz ustawić zmienną środowiskową z poziomu wiersza polecenia w następujący sposób:

setx OPENAI_API_KEY <your-api-key>

Należy pamiętać, że ta metoda działa dobrze w przypadku programowania, ale chcesz użyć bezpieczniejszej metody dla aplikacji produkcyjnych (na przykład: klucz interfejsu API można przechowywać w bezpiecznym magazynie kluczy, do którego usługa zdalna może uzyskiwać dostęp w imieniu aplikacji). Zobacz Najlepsze rozwiązania dotyczące bezpieczeństwa kluczy OpenAI.

Instalowanie biblioteki OpenAI

Z menu programu Visual Studio View wybierz pozycję Terminal. Powinno zostać wyświetlone wystąpienie Developer Powershell . Uruchom następujące polecenie z katalogu głównego projektu, aby zainstalować pakiet OpenAI .NET:

dotnet add package OpenAI

Inicjowanie biblioteki

W MainWindow.xaml.cs zainicjuj bibliotekę OpenAI przy użyciu swojego klucza API:

//...
using OpenAI;
using OpenAI.Chat;

namespace ChatGPT_WinUI3
{
    public sealed partial class MainWindow : Window
    {
        private OpenAIClient openAiService;

        public MainWindow()
        {
            this.InitializeComponent();
           
            var openAiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");

            openAiService = new(openAiKey);
        }
    }
}

Tworzenie interfejsu użytkownika czatu

Użyjemy elementu , StackPanel aby wyświetlić listę komunikatów i element , TextBox aby umożliwić użytkownikom wprowadzanie nowych wiadomości. Zaktualizuj MainWindow.xaml w następujący sposób:

<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>

Implementowanie wysyłania, odbierania i wyświetlania komunikatów

Dodaj procedurę obsługi zdarzeń SendButton_Click do obsługi wysyłania, odbierania i wyświetlania komunikatów:

public sealed partial class MainWindow : Window
{
    // ...

    private async void SendButton_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            string userInput = InputTextBox.Text;

            if (!string.IsNullOrEmpty(userInput))
            {
                AddMessageToConversation($"User: {userInput}");
                InputTextBox.Text = string.Empty;
                var chatClient = openAiService.GetChatClient("gpt-4o"); // or another model
                var chatOptions = new ChatCompletionOptions
                {
                    MaxOutputTokenCount = 300
                };

                // Assemble the chat prompt with a system message and the user's input
                var completionResult = await chatClient.CompleteChatAsync(
                    [
                        ChatMessage.CreateSystemMessage("You are a helpful assistant."),
                        ChatMessage.CreateUserMessage(userInput)
                    ],
                    chatOptions);

                if (completionResult != null && completionResult.Value.Content.Count > 0)
                {
                    AddMessageToConversation($"GPT: {completionResult.Value.Content.First().Text}");
                }
                else
                {
                    AddMessageToConversation($"GPT: Sorry, something bad happened: {completionResult?.Value.Refusal ?? "Unknown error."}");
                }
            }
        }
        catch (Exception ex)
        {
            AddMessageToConversation($"GPT: Sorry, something bad happened: {ex.Message}");
        }
    }

    private void AddMessageToConversation(string message)
    {
        ConversationList.Items.Add(message);
        ConversationList.ScrollIntoView(ConversationList.Items[ConversationList.Items.Last()]);
    }
}

Uruchom aplikację

Uruchom aplikację i spróbuj porozmawiać! Powinna zostać wyświetlona zawartość podobna do następującej:

Zrzut ekranu przedstawiający minimalną aplikację czatu WinUI.

Ulepszanie interfejsu czatu

Wprowadźmy następujące ulepszenia interfejsu czatu:

  • Dodaj element ScrollViewer do elementu StackPanel , aby włączyć przewijanie.
  • Dodaj element TextBlock, aby wyświetlić odpowiedź GPT w sposób, który wizualnie odróżnia się od wejścia użytkownika.
  • Dodaj element , ProgressBar aby wskazać, kiedy aplikacja oczekuje na odpowiedź z interfejsu API GPT.
  • Wyśrodkuj StackPanel w oknie, podobnie jak w przypadku interfejsu internetowego ChatGPT.
  • Upewnij się, że komunikaty przechodzą do następnego wiersza po osiągnięciu krawędzi okna.
  • Zwiększ TextBox i spraw, aby reagował na Enter klucz.

Począwszy od góry:

Dodaj ScrollViewer

Zawiń ListView w ScrollViewer, aby umożliwić przewijanie w pionie przy długich konwersacjach.

        <StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
            <ScrollViewer x:Name="ConversationScrollViewer" VerticalScrollBarVisibility="Auto" MaxHeight="500">
                <ListView x:Name="ConversationList" />
            </ScrollViewer>
            <!-- ... -->
        </StackPanel>

Użyj TextBlock

Zmodyfikuj metodę AddMessageToConversation, aby zmienić styl danych wejściowych użytkownika oraz styl odpowiedzi GPT na różne sposoby.

    // ...
    private void AddMessageToConversation(string message)
    {
        var messageBlock = new TextBlock
        {
            Text = message,
            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()); 
    }

Dodaj ProgressBar

Aby wskazać, kiedy aplikacja czeka na odpowiedź, dodaj element ProgressBar do elementu 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>

Następnie zaktualizuj SendButton_Click program obsługi zdarzeń, aby wyświetlić ProgressBar podczas oczekiwania na odpowiedź.

    private async void SendButton_Click(object sender, RoutedEventArgs e)
    {
        ResponseProgressBar.Visibility = Visibility.Visible; // new!
        string userInput = InputTextBox.Text;

        try
        {
            if (!string.IsNullOrEmpty(userInput))
            {
                AddMessageToConversation($"User: {userInput}");
                InputTextBox.Text = string.Empty;
                var chatClient = openAiService.GetChatClient("gpt-4o"); // or another model
                var chatOptions = new ChatCompletionOptions
                {
                    MaxOutputTokenCount = 300
                };

                // Assemble the chat prompt with a system message and the user's input
                var completionResult = await chatClient.CompleteChatAsync(
                    [
                        ChatMessage.CreateSystemMessage("You are a helpful assistant."),
                        ChatMessage.CreateUserMessage(userInput)
                    ],
                    chatOptions);

                if (completionResult != null && completionResult.Value.Content.Count > 0)
                {
                    AddMessageToConversation($"GPT: {completionResult.Value.Content.First().Text}");
                }
                else
                {
                    AddMessageToConversation($"GPT: Sorry, something bad happened: {completionResult?.Value.Refusal ?? "Unknown error."}");
                }
            }
        }
        catch (Exception ex)
        {
            AddMessageToConversation($"GPT: Sorry, something bad happened: {ex.Message}");
        }
        finally // new!
        {
            ResponseProgressBar.Visibility = Visibility.Collapsed; // new!
        }
    }

Wyśrodkuj StackPanel

Aby wyśrodkować StackPanel i ściągnąć komunikaty w kierunku TextBox, dostosuj ustawienia w Grid w MainWindow.xaml.

    <Grid VerticalAlignment="Bottom" HorizontalAlignment="Center">
        <!-- ... -->
    </Grid>

Zawijanie komunikatów

Aby zapewnić zawijanie komunikatów do następnego wiersza, gdy osiągną one krawędź okna, zaktualizuj MainWindow.xaml do korzystania z ItemsControl.

Zastąp następujące elementy:

    <ScrollViewer x:Name="ConversationScrollViewer" VerticalScrollBarVisibility="Auto" MaxHeight="500">
        <ListView x:Name="ConversationList" />
    </ScrollViewer>

Na ten:

    <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>

Następnie wprowadzimy klasę MessageItem ułatwiając powiązanie i kolorowanie:

    // ...
    public class MessageItem
    {
        public string Text { get; set; }
        public SolidColorBrush Color { get; set; }
    }
    // ...

Na koniec zaktualizuj metodę AddMessageToConversation , aby użyć nowej MessageItem klasy:

    // ...
    private void AddMessageToConversation(string message)
    {
        var messageItem = new MessageItem
        {
            Text = message,
            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);
    }
    // ...

Popraw TextBox

Aby powiększyć TextBox i sprawić, by reagował na klawisz Enter, zaktualizuj MainWindow.xaml w następujący sposób:

    <!-- ... -->
    <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>
    <!-- ... -->

Następnie dodaj procedurę obsługi zdarzenia InputTextBox_KeyDown, aby obsłużyć klawisz 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());
        }
    }
    //...

Uruchamianie ulepszonej aplikacji

Nowy i ulepszony interfejs czatu powinien wyglądać mniej więcej tak:

Zrzut ekranu przedstawiający mniej minimalną aplikację do czatu WinUI 3.

Recap

Oto, co udało Ci się osiągnąć w tym poradniku:

  1. Dodałeś funkcje API OpenAI do swojej aplikacji desktopowej WinUI 3 / Windows App SDK, instalując oficjalną bibliotekę OpenAI i inicjując ją za pomocą klucza API.
  2. Utworzono interfejs przypominający czat, który umożliwia generowanie odpowiedzi na komunikaty przy użyciu API OpenAI do generowania tekstu i tworzenia zapytań.
  3. Ulepszono interfejs czatu, wykonując następujące elementy:
    1. dodawanie elementu ScrollViewer,
    2. przy użyciu elementu TextBlock aby wyświetlić odpowiedź GPT
    3. dodanie elementu ProgressBar , aby wskazać, kiedy aplikacja czeka na odpowiedź z interfejsu API GPT,
    4. wyśrodkowanie StackPanel w oknie,
    5. zapewnienie, że komunikaty są zawijane do następnego wiersza, gdy dotrą do krawędzi okna i
    6. zwiększenie rozmiaru TextBox, umożliwienie zmiany jego rozmiaru i reaktywność na klawisz Enter.

Pełne pliki kodu

Poniższy kod jest pełnym przykładem aplikacji czatu zintegrowanej z funkcją dokończeń czatów OpenAI:

<?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.Chat;

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(openAiKey);
        }

        private async void SendButton_Click(object sender, RoutedEventArgs e)
        {
            ResponseProgressBar.Visibility = Visibility.Visible;
            string userInput = InputTextBox.Text;
    
            try
            {
                if (!string.IsNullOrEmpty(userInput))
                {
                    AddMessageToConversation($"User: {userInput}");
                    InputTextBox.Text = string.Empty;
                    var chatClient = openAiService.GetChatClient("gpt-4o"); // or another model
                    var chatOptions = new ChatCompletionOptions
                    {
                        MaxOutputTokenCount = 300
                    };

                    // Assemble the chat prompt with a system message and the user's input
                    var completionResult = await chatClient.CompleteChatAsync(
                        [
                            ChatMessage.CreateSystemMessage("You are a helpful assistant."),
                            ChatMessage.CreateUserMessage(userInput)
                        ],
                        chatOptions);
    
                    if (completionResult != null && completionResult.Value.Content.Count > 0)
                    {
                        AddMessageToConversation($"GPT: {completionResult.Value.Content.First().Text}");
                    }
                    else
                    {
                        AddMessageToConversation($"GPT: Sorry, something bad happened: {completionResult?.Value.Refusal ?? "Unknown error."}");
                    }
                }
            }
            catch (Exception ex)
            {
                AddMessageToConversation($"GPT: Sorry, something bad happened: {ex.Message}");
            }
            finally
            {
                ResponseProgressBar.Visibility = Visibility.Collapsed;
            }
        }

        private void AddMessageToConversation(string message)
        {
            var messageItem = new MessageItem
            {
                Text = message,
                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());
            }
        }
    }
}