Freigeben über


Hinzufügen von OpenAI-Chatabschlüssen zu Ihrer WinUI 3-Desktop-App

In dieser Hilfe & Anleitung erfahren Sie, wie Sie die OpenAI-API in Ihre WinUI 3 / Windows App SDK Desktop-Anwendung integrieren können. Wir erstellen eine Chat-ähnliche Schnittstelle, über die Sie Antworten auf Nachrichten mithilfe der Chat-Abschluss-API von OpenAI generieren können:

Eine weniger minimalistische Chat-App.

Voraussetzungen

Erstellen eines Projekts

  1. Öffnen Sie Visual Studio und erstellen Sie ein neues Projekt über File>New>Project.
  2. Suchen Sie nach WinUI und wählen Sie die Blank App, Packaged (WinUI 3 in Desktop) C#-Projektvorlage.
  3. Geben Sie den Projektnamen, den Projektmappennamen und das Verzeichnis an. In diesem Beispiel gehört unser ChatGPT_WinUI3 Projekt zu einer ChatGPT_WinUI3 Projektmappe, die in C:\Projects\ erstellt wird.

Nachdem Sie Ihr Projekt erstellt haben, sollten Sie die folgende Standard-Dateistruktur in Ihrem Projektmappen-Explorer sehen:

Die Standardverzeichnisstruktur.

Festlegen Ihrer Umgebungsvariablen

Um das OpenAI SDK zu verwenden, müssen Sie eine Umgebungsvariable mit Ihrem API-Schlüssel festlegen. In diesem Beispiel verwenden wir die Umgebungsvariable OPENAI_API_KEY. Sobald Sie Ihren API-Schlüssel aus dem OpenAI-Entwickler-Dashboard haben, können Sie die Umgebungsvariable wie folgt von der Kommandozeile aus setzen:

setx OPENAI_API_KEY <your-api-key>

Beachten Sie, dass diese Methode gut für die Entwicklung geeignet ist. Für Produktions-Apps sollten Sie jedoch eine sicherere Methode verwenden (z.B. könnten Sie Ihren API-Schlüssel in einem sicheren Schlüsseltresor speichern, auf den ein Remote-Dienst im Namen Ihrer App zugreifen kann). Siehe Bewährte Praktiken für OpenAI-Schlüsselsicherheit.

Installieren des OpenAI SDK

Wählen Sie im Menü von Visual Studio View die Option Terminal aus. Es sollte eine Instanz von Developer Powershell angezeigt werden. Führen Sie den folgenden Befehl im Stammverzeichnis Ihres Projekts aus, um SDK zu installieren:

dotnet add package Betalgo.OpenAI

Initialisieren des SDK

Initialisieren Sie in MainWindow.xaml.cs das SDK mit Ihrem API-Schlüssel:

//...
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
            });
        }
    }
}

Erstellen der Chat-UI

Wir verwenden eine StackPanel, um eine Liste von Nachrichten anzuzeigen, und eine TextBox, damit Benutzer neue Nachrichten eingeben können. Aktualisieren Sie MainWindow.xaml wie folgt:

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

Senden, Empfangen und Anzeigen von Nachrichten implementieren

Fügen Sie einen SendButton_Click Ereignishandler hinzu, um das Senden, Empfangen und Anzeigen von Nachrichten zu steuern:

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

Ausführen der App

Führen Sie die App aus, und versuchen Sie, zu chatten! Die Ausgabe sollte in etwa wie folgt aussehen:

Eine minimale Chat-App.

Verbessern der Chatschnittstelle

Lassen Sie uns die folgenden Verbesserungen an der Chat-Schnittstelle vornehmen:

  • Fügen Sie eine ScrollViewer zu der StackPanel hinzu, um das Scrollen zu aktivieren.
  • Fügen Sie eine TextBlock hinzu, um die GPT-Antwort auf eine Weise anzuzeigen, die sich optisch von der Eingabe des Benutzers unterscheidet.
  • Fügen Sie eine ProgressBar hinzu, um anzuzeigen, dass die App auf eine Antwort von der GPT-API wartet.
  • Zentrieren Sie StackPanel im Fenster, ähnlich wie die Web-Schnittstelle von ChatGPT.
  • Stellen Sie sicher, dass Nachrichten in die nächste Zeile umgebrochen werden, wenn sie den Rand des Fensters erreichen.
  • Machen Sie die TextBox größer und ansprechbar auf den Schlüssel Enter.

Beginnend von oben:

Fügen Sie ScrollViewer hinzu.

Verpacken Sie die ListView in einer ScrollViewer, um das vertikale Scrollen bei langen Unterhaltungen zu ermöglichen:

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

Verwenden Sie TextBlock

Ändern Sie die AddMessageToConversation Methode so, dass die Eingabe des Benutzers und die GPT-Antwort anders formatiert werden:

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

Fügen Sie ProgressBar hinzu.

Fügen Sie eine ProgressBar zur StackPanel hinzu, um anzuzeigen, dass die App auf eine Antwort wartet:

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

Aktualisieren Sie dann den SendButton_Click Ereignishandler so, dass er ProgressBar beim Warten auf eine Antwort anzeigt:

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

Zentrieren Sie die StackPanel

Um die StackPanel zu zentrieren und die Nachrichten nach unten in Richtung der TextBox zu ziehen, passen Sie die Einstellungen Grid in MainWindow.xaml an:

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

Umbrechen von Nachrichten

Um sicher zu stellen, dass Nachrichten in die nächste Zeile umgebrochen werden, wenn sie den Rand des Fensters erreichen, aktualisieren Sie MainWindow.xaml, um ein ItemsControl zu verwenden.

Ersetzen Sie dies:

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

Damit:

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

Anschließend führen wir eine MessageItem Klasse ein, um die Bindung und Farbgebung zu vereinfachen:

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

Aktualisieren Sie zum Abschluss die AddMessageToConversation-Methode, um die neue MessageItem-Klasse zu verwenden:

    // ...
    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);
    }
    // ...

Verbessern sie die TextBox

Um die TextBox größer zu machen und reaktionsschneller auf den Enter Schlüssel zu gestalten, aktualisieren Sie MainWindow.xaml wie folgt:

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

Fügen Sie dann den InputTextBox_KeyDown Ereignishandler zum Behandeln des Enter Schlüssels hinzu:

    //...
    private void InputTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
    {
        if (e.Key == Windows.System.VirtualKey.Enter && !string.IsNullOrWhiteSpace(InputTextBox.Text))
        {
            SendButton_Click(this, new RoutedEventArgs());
        }
    }
    //...

Ausführen der verbesserten App

Ihre neue und verbesserte Chat-Schnittstelle sollte etwa wie folgt aussehen:

Eine weniger minimalistische Chat-App.

Zusammenfassung

Das haben Sie in dieser Hilfe & Anleitung gelernt:

  1. Sie haben die API-Funktionen von OpenAI zu Ihrer WinUI 3 / Windows App SDK Desktop-Anwendung hinzugefügt, indem Sie ein Community SDK installiert und mit Ihrem API-Schlüssel initialisiert haben.
  2. Sie haben eine Chat-ähnliche Schnittstelle erstellt, über die Sie Antworten auf Nachrichten mithilfe der Chat-Abschluss-API von OpenAI generieren können.
  3. Sie haben die Chat-Schnittstelle verbessert, indem Sie:
    1. eine ScrollViewer hinzugefügt haben,
    2. eine TextBlock genutzt haben, um die GPT-Antwort anzuzeigen,
    3. eine ProgressBar hinzu gefügt haben, um anzuzeigen, dass die App auf eine Antwort von der GPT-API wartet,
    4. die StackPanel im Fenster zentriert,
    5. sichergestellt, dass Nachrichten in die nächste Zeile umgebrochen werden, wenn sie den Rand des Fensters erreichen und
    6. Sodass die TextBox Größe größer, verkleinert und auf den Enter Schlüssel reagiert wird.

Vollständige Codedateien

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