Implement AI chat feature and OpenAI configuration settings#2
Open
Lightczx wants to merge 7 commits into
Open
Conversation
添加AI Chat页面、ChatViewModel、IChatService/OpenAIChatService实现, 并在设置页新增OpenAI API Key、Base URL、默认模型配置项。
There was a problem hiding this comment.
Pull request overview
This PR adds an in-app AI chat experience backed by an OpenAI-compatible chat streaming client, along with new Settings fields to configure the API key, base URL, and default model.
Changes:
- Added a new Chat page + view model to send messages and stream assistant responses.
- Added OpenAI configuration fields (API key, base URL, default model) to app settings and Settings UI.
- Introduced a basic Markdown-to-WinUI rendering helper for assistant messages.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Snap.Nicole/ViewModels/SettingsViewModel.cs | Exposes OpenAI-related settings properties and raises property-changed notifications. |
| src/Snap.Nicole/ViewModels/ChatViewModel.cs | Implements chat state, send/stop/clear commands, and streams responses into a message list. |
| src/Snap.Nicole/UI/Xaml/Windows/MainWindow.xaml | Adds navigation entry for the new Chat page. |
| src/Snap.Nicole/UI/Xaml/Pages/SettingsPage.xaml.cs | Wires PasswordBox changes to the settings ViewModel for the API key. |
| src/Snap.Nicole/UI/Xaml/Pages/SettingsPage.xaml | Adds UI fields for API key, base URL, and default model. |
| src/Snap.Nicole/UI/Xaml/Pages/ChatPage.xaml.cs | Builds chat bubbles in code-behind based on ViewModel message collection changes. |
| src/Snap.Nicole/UI/Xaml/Pages/ChatPage.xaml | Adds the Chat page layout (messages + input + send/clear buttons). |
| src/Snap.Nicole/UI/Xaml/Helpers/MarkdownHelper.cs | Adds a Markdown-ish renderer for assistant responses (headers, lists, tables, code blocks, links). |
| src/Snap.Nicole/Services/Settings/AppSettings.cs | Adds persisted OpenAI configuration properties to settings model. |
| src/Snap.Nicole/Services/AI/OpenAIChatService.cs | Adds OpenAI streaming chat implementation using configured settings. |
| src/Snap.Nicole/Services/AI/Models/ToolDefinition.cs | Adds tool definition model (currently unused by service). |
| src/Snap.Nicole/Services/AI/Models/ToolCall.cs | Adds tool call model (currently unused by service). |
| src/Snap.Nicole/Services/AI/Models/ChatRole.cs | Adds chat role enum used across chat stack. |
| src/Snap.Nicole/Services/AI/Models/ChatMessage.cs | Adds chat message model used by UI + service. |
| src/Snap.Nicole/Services/AI/Models/ChatCompletionOptions.cs | Adds chat request options model (class/file naming mismatch noted). |
| src/Snap.Nicole/Services/AI/IChatService.cs | Adds chat service abstraction used by ChatViewModel. |
| src/Snap.Nicole/Resources/SR.resx | Adds new localized strings (zh) for Chat + OpenAI settings labels. |
| src/Snap.Nicole/Resources/SR.en.resx | Adds new localized strings (en) for Chat + OpenAI settings labels. |
| src/Snap.Nicole/Program.cs | Registers ChatViewModel and OpenAIChatService in DI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+6
to
+8
|
|
||
| public string? OpenAIApiKey { get; set; } | ||
|
|
|
|
||
| <TextBlock Text="{snuxm:StringResource Name=UIXamlPagesSettingsPageLabelOpenAIBaseUrl}" /> | ||
| <TextBox | ||
| Text="{Binding OpenAIBaseUrl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> |
|
|
||
| <TextBlock Text="{snuxm:StringResource Name=UIXamlPagesSettingsPageLabelDefaultModel}" /> | ||
| <TextBox | ||
| Text="{Binding DefaultModel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> |
Comment on lines
11
to
+14
| InitializeComponent(); | ||
| DataContext = App.Host.Services.GetRequiredService<SettingsViewModel>(); | ||
| OpenAIApiKeyBox.Password = ViewModel.OpenAIApiKey; | ||
| } |
Comment on lines
+36
to
+49
| RebuildMessages(); | ||
| } | ||
|
|
||
| private void RebuildMessages() | ||
| { | ||
| MessagesPanel.Children.Clear(); | ||
|
|
||
| foreach (ChatMessage message in ViewModel.Messages) | ||
| { | ||
| FrameworkElement bubble = MarkdownHelper.CreateMessageBubble(message.Role, message.Content, message.ModelId); | ||
| MessagesPanel.Children.Add(bubble); | ||
| } | ||
|
|
||
| // Scroll to bottom |
Comment on lines
+74
to
+81
| string[] lines = markdown.Split('\n'); | ||
| bool inCodeBlock = false; | ||
| string codeBuffer = ""; | ||
| string codeLanguage = ""; | ||
|
|
||
| for (int i = 0; i < lines.Length; i++) | ||
| { | ||
| string line = lines[i]; |
Comment on lines
+292
to
+296
| List<string> cells = ParseTableCells(tableLines[r]); | ||
| // Pad if fewer cells than columns | ||
| while (cells.Count < columnCount) cells.Add(""); | ||
| AddTableRow(tableGrid, cells, r - 1, isHeader: false); | ||
| } |
| } | ||
| catch | ||
| { | ||
| return new SolidColorBrush(Colors.LightGray); |
Comment on lines
+17
to
+21
| public static FrameworkElement CreateMessageBubble(ChatRole role, string content, string? modelId = null) | ||
| { | ||
| StackPanel panel = new() { Spacing = 4, HorizontalAlignment = HorizontalAlignment.Stretch }; | ||
|
|
||
| string headerText = role == ChatRole.User ? "You" : (modelId ?? "AI"); |
|
|
||
| namespace Snap.Nicole.Services.AI.Models; | ||
|
|
||
| internal sealed class ChatRequestOptions |
- 新增 `ExtendedAgentResponseUpdate` 和 `ChatRoleKind` 模型替代旧版 - 实现支持分段内容(文本/推理/工具调用/工具结果)的聊天界面渲染 - 重构 `OpenAIChatService` 使用新的 AI 代理接口 - 更新温度参数默认值为 0.3 并新增 TopP 参数 - 移除不再使用的旧模型类 - 修复文件末尾缺少换行符的问题
- 将IChatService重构为IAgentService,支持更灵活的AI代理功能 - 添加模型配置管理功能,支持多模型配置和切换 - 引入WellKnownLocations集中管理常用文件路径 - 更新UI以支持模型配置选择和显示 - 修复拼写错误和方法命名问题 - 清理无用代码和优化导入
- 添加 MiSans 字体资源并实现字体替换功能 - 引入 ICopyFrom 和 IIdentifiable 接口 - 重构设置服务使用 IOptionsProvider 统一接口 - 实现可排序的模型配置列表 - 优化设置页面 UI 和交互逻辑 - 添加 XAML 过渡动画效果 - 修复窗口标题栏样式问题
Comment on lines
+54
to
+60
| ModelProfiles.Add(profile); | ||
| if (ModelProfiles.Count == 1) | ||
| { | ||
| options.CurrentValue.SelectedModelProfileId = profile.Id; | ||
| } | ||
|
|
||
| SelectedProfile = profile; |
Comment on lines
+37
to
+41
| <TextBlock Text="{snuxm:StringResource Name=UIXamlPagesSettingsPageLabelProfileName}" /> | ||
| <TextBox Text="{Binding SelectedProfile.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> | ||
|
|
||
| <TextBlock Text="{snuxm:StringResource Name=UIXamlPagesSettingsPageLabelProfileEndpoint}" /> | ||
| <TextBox Text="{Binding SelectedProfile.Endpoint, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> |
Comment on lines
+43
to
+44
| <TextBlock Text="{snuxm:StringResource Name=UIXamlPagesSettingsPageLabelProfileApiKey}" /> | ||
| <TextBox Text="{Binding SelectedProfile.ApiKey, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> |
Comment on lines
+103
to
+116
| if (!updating) | ||
| { | ||
| setAction(optionsProvider.CurrentValue, Items); | ||
| optionsProvider.Update(); | ||
| } | ||
| } | ||
|
|
||
| private void OnItemPropertyChanged(object? sender, PropertyChangedEventArgs e) | ||
| { | ||
| if (!updating) | ||
| { | ||
| setAction(optionsProvider.CurrentValue, Items); | ||
| optionsProvider.Update(); | ||
| } |
Comment on lines
+21
to
+27
| public ChatViewModel(IServiceProvider serviceProvider) | ||
| { | ||
| chatService = serviceProvider.GetRequiredService<IAgentService>(); | ||
| options = serviceProvider.GetRequiredService<IOptionsProvider<AppSettings>>(); | ||
|
|
||
| options.OnChange(OnSettingsChanged); | ||
| } |
Comment on lines
+6
to
+28
| static HMODULE moduleHandle = GetModuleHandleW(L"Microsoft.UI.Xaml.dll"); | ||
| static std::wstring s_resourceString; | ||
|
|
||
| static HRESULT Endpoint(LPCVOID /* PALFontAndScriptServices* */ _this, void* /* xstring_ptr* */ pstrDefaultFontNameString) | ||
| { | ||
| // HRESULT __fastcall xstring_ptr::CloneBuffer(const wchar_t *buffer, xstring_ptr *pstrCloned) | ||
| using CloneBufferFunc = HRESULT(*)(const WCHAR*, void*); | ||
| CloneBufferFunc cloneBuffer = reinterpret_cast<CloneBufferFunc>(reinterpret_cast<BYTE*>(moduleHandle) + 0x3B260C); | ||
| RETURN_IF_FAILED(cloneBuffer(s_resourceString.c_str(), pstrDefaultFontNameString)); | ||
|
|
||
| return S_OK; | ||
| } | ||
|
|
||
| HRESULT PatchFontAndScriptServicesGetDefaultFontNameString(LPCWSTR pResource) | ||
| { | ||
| s_resourceString = pResource; | ||
|
|
||
| RETURN_IF_WIN32_ERROR(DetourTransactionBegin()); | ||
| RETURN_IF_WIN32_ERROR(DetourUpdateThread(GetCurrentThread())); | ||
|
|
||
| // __int64 __fastcall PALFontAndScriptServices::GetDefaultFontNameString(PALFontAndScriptServices *this, xstring_ptr *pstrDefaultFontNameString) | ||
| LPVOID target = reinterpret_cast<LPVOID>(reinterpret_cast<BYTE*>(moduleHandle) + 0x191C90); | ||
| RETURN_IF_WIN32_ERROR(DetourAttach(&target, &Endpoint)); |
Comment on lines
+7
to
+8
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // |
|
|
||
| internal static class WellKnownLocations | ||
| { | ||
| public static string AppIcon = Path.Combine(AppContext.BaseDirectory, "Assets", "Logo.ico"); |
| @@ -0,0 +1,6 @@ | |||
| namespace Snap.Nicole.Core; | |||
|
|
|||
| public interface ICopyFrom<T> | |||
Comment on lines
+41
to
+46
| public ChatClientAgent AsAIAgent(IList<AITool>? tools = default) | ||
| { | ||
| OpenAIClient client = new(new ApiKeyCredential(ApiKey!), new OpenAIClientOptions() | ||
| { | ||
| Endpoint = Endpoint.ToUri(), | ||
| }); |
1. 移除不必要的静态JsonSerializerOptions字段 2. 简化聊天消息存储逻辑,仅处理有响应消息的场景 3. 优化推理内容的补丁设置逻辑,修复RawRepresentation赋值问题 4. 移除冗余的调试输出代码 5. 添加System.Linq命名空间引用 6. 新增SCME0001代码警告抑制项
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.