Bagikan melalui


Tambahkan penyelesaian obrolan OpenAI ke aplikasi desktop WinUI 3 / SDK Aplikasi Windows Anda

Dalam panduan ini, Anda akan mempelajari cara mengintegrasikan API OpenAI ke dalam aplikasi desktop WinUI 3 / SDK Aplikasi Windows Anda. Kami akan membangun antarmuka seperti obrolan yang memungkinkan Anda menghasilkan respons terhadap pesan menggunakan API penyelesaian obrolan OpenAI:

Aplikasi obrolan yang kurang minimal.

Prasyarat

Membuat proyek

  1. Buka Visual Studio dan buat proyek baru melalui FileProject>New>.
  2. Cari WinUI dan pilih Blank App, Packaged (WinUI 3 in Desktop) templat proyek C#.
  3. Tentukan nama proyek, nama solusi, dan direktori. Dalam contoh ini, proyek kami ChatGPT_WinUI3 milik solusi ChatGPT_WinUI3 , yang akan dibuat di C:\Projects\.

Setelah membuat proyek, Anda akan melihat struktur file default berikut di Penjelajah Solusi Anda:

Struktur direktori default.

Mengatur variabel lingkungan Anda

Untuk menggunakan OpenAI SDK, Anda harus mengatur variabel lingkungan dengan kunci API Anda. Dalam contoh ini, kita akan menggunakan OPENAI_API_KEY variabel lingkungan. Setelah Anda memiliki kunci API dari dasbor pengembang OpenAI, Anda dapat mengatur variabel lingkungan dari baris perintah sebagai berikut:

setx OPENAI_API_KEY <your-api-key>

Perhatikan bahwa metode ini berfungsi dengan baik untuk pengembangan, tetapi Anda mungkin ingin menggunakan metode yang lebih aman untuk aplikasi produksi (misalnya: Anda dapat menyimpan kunci API Anda dalam brankas kunci aman yang dapat diakses layanan jarak jauh atas nama aplikasi Anda). Lihat Praktik terbaik untuk keamanan kunci OpenAI.

Menginstal OpenAI SDK

Dari menu Visual Studio View , pilih Terminal. Anda akan melihat instans Developer Powershell muncul. Jalankan perintah berikut dari direktori akar proyek Anda untuk menginstal SDK:

dotnet add package Betalgo.OpenAI

Menginisialisasi SDK

Di MainWindow.xaml.cs, inisialisasi SDK dengan kunci API Anda:

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

Membangun UI obrolan

Kita akan menggunakan StackPanel untuk menampilkan daftar pesan, dan TextBox untuk memungkinkan pengguna memasukkan pesan baru. Perbarui MainWindow.xaml sebagai berikut:

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

Menerapkan pengiriman, penerimaan, dan tampilan pesan

SendButton_Click Tambahkan penanganan aktivitas untuk menangani pengiriman, penerimaan, dan tampilan pesan:

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

Menjalankan aplikasi

Jalankan aplikasi dan coba mengobrol! Anda seharusnya melihat sesuatu seperti berikut:

Aplikasi obrolan minimal.

Meningkatkan antarmuka obrolan

Mari kita lakukan peningkatan berikut pada antarmuka obrolan:

  • ScrollViewer Tambahkan ke StackPanel untuk mengaktifkan pengguliran.
  • TextBlock Tambahkan untuk menampilkan respons GPT dengan cara yang lebih berbeda secara visual dari input pengguna.
  • ProgressBar Tambahkan untuk menunjukkan kapan aplikasi sedang menunggu respons dari API GPT.
  • Tengahkan StackPanel di jendela, mirip dengan antarmuka web ChatGPT.
  • Pastikan pesan dibungkus ke baris berikutnya saat mencapai tepi jendela.
  • Buat yang TextBox lebih besar dan responsif terhadap Enter kunci.

Mulai dari atas:

Menambahkan ScrollViewer

Bungkus ListView dalam ScrollViewer untuk mengaktifkan pengguliran vertikal pada percakapan panjang:

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

Menggunakan TextBlock

AddMessageToConversation Ubah metode untuk menata input pengguna dan respons GPT secara berbeda:

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

Menambahkan ProgressBar

Untuk menunjukkan kapan aplikasi menunggu respons, tambahkan ProgressBar ke 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>

Kemudian, perbarui penanganan SendButton_Click aktivitas untuk menampilkan ProgressBar saat menunggu respons:

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

Tengahkan StackPanel

Untuk memerah StackPanel dan menarik pesan ke bawah ke arah TextBox, sesuaikan Grid pengaturan di MainWindow.xaml:

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

Membungkus pesan

Untuk memastikan bahwa pesan dibungkus ke baris berikutnya saat mencapai tepi jendela, perbarui MainWindow.xaml untuk menggunakan ItemsControl.

Ganti ini:

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

Dengan ini:

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

Kami kemudian akan memperkenalkan MessageItem kelas untuk memfasilitasi pengikatan dan pewarnaan:

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

Terakhir, perbarui AddMessageToConversation metode untuk menggunakan kelas baru 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);
    }
    // ...

Meningkatkan TextBox

Untuk membuat lebih TextBox besar dan responsif terhadap Enter kunci, perbarui MainWindow.xaml sebagai berikut:

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

Kemudian, tambahkan penanganan InputTextBox_KeyDown aktivitas untuk menangani Enter kunci:

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

Menjalankan aplikasi yang ditingkatkan

Antarmuka obrolan baru dan yang ditingkatkan akan terlihat seperti ini:

Aplikasi obrolan yang kurang minimal.

Rekap

Inilah yang Anda capai dalam cara ini:

  1. Anda menambahkan kemampuan API OpenAI ke aplikasi desktop WinUI 3 / SDK Aplikasi Windows Anda dengan menginstal SDK komunitas dan menginisialisasinya dengan kunci API Anda.
  2. Anda membangun antarmuka seperti obrolan yang memungkinkan Anda menghasilkan respons terhadap pesan menggunakan API penyelesaian obrolan OpenAI.
  3. Anda meningkatkan antarmuka obrolan dengan:
    1. ScrollViewermenambahkan ,
    2. TextBlock menggunakan untuk menampilkan respons GPT,
    3. ProgressBar menambahkan untuk menunjukkan kapan aplikasi menunggu respons dari API GPT,
    4. tengah di StackPanel jendela,
    5. memastikan bahwa pesan dibungkus ke baris berikutnya saat mencapai tepi jendela, dan
    6. membuat yang TextBox lebih besar, dapat diubah ukurannya, dan responsif terhadap Enter kunci.

File kode lengkap

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