將 OpenAI 聊天完成新增至 WinUI 3/Windows 應用程式 SDK 傳統型應用程式
這篇操作說明將教您如何將 OpenAI 的 API 整合到您的 WinUI 3/Windows 應用程式 SDK 傳統型應用程式。 我們將建置類似聊天的介面,可讓您使用 OpenAI 的聊天完成 API 來產生訊息的回應:
必要條件
- 設定您的開發電腦(請參閱 開始使用 WinUI)。
- 熟悉如何使用 C# 和 WinUI 3/Windows 應用程式 SDK 建置 Hello World 應用程式的核心概念 - 我們將以此作法為基礎來建置。
- OpenAI 開發人員儀表板的 OpenAI API 金鑰。
- 在您的專案中安裝的 OpenAI SDK。 如需社群程式庫清單,請參閱 OpenAI 文件 。 在此操作說明中,我們將使用 betalgo/openai。
建立專案
- 開啟 Visual Studio,並透過
File
>New
>Project
建立新專案。 - 搜尋
WinUI
並選擇Blank App, Packaged (WinUI 3 in Desktop)
C# 專案範本。 - 指定專案名稱、方案名稱和目錄。 在此範例中,我們的
ChatGPT_WinUI3
專案屬於將在C:\Projects\
中建立的ChatGPT_WinUI3
方案。
建立專案後,應在「方案總管」中看到以下預設檔案結構:
設定您的環境變數
若要使用 OpenAI SDK,您必須使用 API 金鑰來設定環境變數。 在此範例中,我們將使用 OPENAI_API_KEY
環境變數。 從 OpenAI 開發人員儀表板取得 API 金鑰之後,您可以從命令列設定環境變數,如下所示:
setx OPENAI_API_KEY <your-api-key>
請注意,此方法適用於開發作業,但您可以針對生產環境應用程式使用更安全的方法 (例如:您可以將 API 金鑰儲存在安全的金鑰保存庫中,由遠端服務代表您的應用程式存取)。 請參閱 OpenAI 金鑰安全的最佳做法。
安裝 OpenAI SDK
從 Visual Studio 的 [View
] 功能表中,選取 [Terminal
]。 您應該會看到 Developer Powershell
的執行個體出現。 在專案的根目錄下執行以下命令,以安裝 SDK:
dotnet add package Betalgo.OpenAI
初始化 SDK
在 MainWindow.xaml.cs
中 ,使用 API 金鑰初始化 SDK:
//...
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
});
}
}
}
建置聊天 UI
我們將使用 StackPanel
來顯示訊息清單,以及 TextBox
讓使用者輸入新訊息。 如下所示更新 MainWindow.xaml
:
<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>
實作訊息傳送、接收和顯示
新增 SendButton_Click
事件處理常式來處理訊息的傳送、接收和顯示:
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()]);
}
}
執行應用程式
執行應用程式並嘗試聊天! 您應該會看到類似這樣的畫面︰
改善聊天介面
讓我們對聊天介面進行下列改善:
- 將
ScrollViewer
新增至StackPanel
以啟用捲動功能。 - 新增
TextBlock
,讓 GPT 回應的顯示比使用者輸入的方式更具視覺上的差異。 - 新增
ProgressBar
,指出應用程式何時正在等候來自 GPT API 的回應。 - 將
StackPanel
於視窗置中,類似於 ChatGPT 的 Web 介面。 - 確定訊息到達視窗邊緣時,會換至下一行。
- 讓
TextBox
更大且回應Enter
金鑰。
從頂端開始:
新增 ScrollViewer
將 ListView
包裝在 ScrollViewer
中,以在長對話中啟用垂直捲動:
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
<ScrollViewer x:Name="ConversationScrollViewer" VerticalScrollBarVisibility="Auto" MaxHeight="500">
<ListView x:Name="ConversationList" />
</ScrollViewer>
<!-- ... -->
</StackPanel>
使用 TextBlock
修改 AddMessageToConversation
方法,以不同方式設定使用者輸入和 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());
}
新增 ProgressBar
若要指出應用程式正在等候回應,請將 ProgressBar
新增至 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>
然後,更新 SendButton_Click
事件處理常式,在等候回應時顯示 ProgressBar
:
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!
}
將 StackPanel
置中
若要將 StackPanel
置中,並將訊息向下提取至 TextBox
,請調整 MainWindow.xaml
中的 Grid
設定:
<Grid VerticalAlignment="Bottom" HorizontalAlignment="Center">
<!-- ... -->
</Grid>
訊息換行
若要確保訊息在到達視窗邊緣時換行至下一行,請更新 MainWindow.xaml
為使用 ItemsControl
。
取代這行:
<ScrollViewer x:Name="ConversationScrollViewer" VerticalScrollBarVisibility="Auto" MaxHeight="500">
<ListView x:Name="ConversationList" />
</ScrollViewer>
取代為下列項目:
<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>
接著,我們將引進 MessageItem
類別,以利繫結和著色:
// ...
public class MessageItem
{
public string Text { get; set; }
public SolidColorBrush Color { get; set; }
}
// ...
最後,更新 AddMessageToConversation
方法以使用新的 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);
}
// ...
改善 TextBox
若要讓 TextBox
更大且回應 Enter
金鑰,請更新 MainWindow.xaml
,如下所示:
<!-- ... -->
<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>
<!-- ... -->
然後,新增 InputTextBox_KeyDown
事件處理常式以處理 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());
}
}
//...
執行改良的應用程式
經過改良的新聊天介面看起來應該像這樣:
概括回顧
以下是您在此操作說明中完成的工作:
- 您已藉由安裝社群 SDK,並使用 API 金鑰初始化,將 OpenAI 的 API 功能新增至您的 WinUI 3/Windows 應用程式 SDK 傳統型應用程式。
- 您建置了類似聊天的介面,可讓您使用 OpenAI 的聊天完成 API 來產生訊息的回應。
- 您已透過下列方式改善聊天介面:
- 新增
ScrollViewer
; - 使用
TextBlock
來顯示 GPT 回應; - 新增
ProgressBar
,指出應用程式何時正在等候來自 GPT API 的回應; - 將
StackPanel
於視窗置中; - 確定訊息到達視窗邊緣時,會換至下一行,以及
TextBox
讓金鑰變大、可重設大小並回應Enter
。
- 新增
完整程式碼檔案
<?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());
}
}
}
}