mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-23 20:47:25 -08:00
Merge branch 'feature/openai-support' into develop
This commit is contained in:
commit
86c6254fb2
16 changed files with 496 additions and 30 deletions
|
@ -35,6 +35,7 @@ Opensource Git GUI client.
|
|||
* Git LFS
|
||||
* Issue Link
|
||||
* Workspace
|
||||
* Using AI to generate commit message (C# port of [anjerodev/commitollama](https://github.com/anjerodev/commitollama))
|
||||
|
||||
> [!WARNING]
|
||||
> **Linux** only tested on **Debian 12** on both **X11** & **Wayland**.
|
||||
|
|
|
@ -46,6 +46,8 @@ namespace SourceGit
|
|||
[JsonSerializable(typeof(Models.ExternalToolPaths))]
|
||||
[JsonSerializable(typeof(Models.InteractiveRebaseJobCollection))]
|
||||
[JsonSerializable(typeof(Models.JetBrainsState))]
|
||||
[JsonSerializable(typeof(Models.OpenAIChatRequest))]
|
||||
[JsonSerializable(typeof(Models.OpenAIChatResponse))]
|
||||
[JsonSerializable(typeof(Models.ThemeOverrides))]
|
||||
[JsonSerializable(typeof(Models.Version))]
|
||||
[JsonSerializable(typeof(Models.RepositorySettings))]
|
||||
|
|
119
src/Commands/GenerateCommitMessage.cs
Normal file
119
src/Commands/GenerateCommitMessage.cs
Normal file
|
@ -0,0 +1,119 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// A C# version of https://github.com/anjerodev/commitollama
|
||||
/// </summary>
|
||||
public class GenerateCommitMessage
|
||||
{
|
||||
public class GetDiffContent : Command
|
||||
{
|
||||
public GetDiffContent(string repo, Models.DiffOption opt)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"diff --diff-algorithm=minimal {opt}";
|
||||
}
|
||||
}
|
||||
|
||||
public GenerateCommitMessage(string repo, List<Models.Change> changes, CancellationToken cancelToken, Action<string> onProgress)
|
||||
{
|
||||
_repo = repo;
|
||||
_changes = changes;
|
||||
_cancelToken = cancelToken;
|
||||
_onProgress = onProgress;
|
||||
}
|
||||
|
||||
public string Result()
|
||||
{
|
||||
try
|
||||
{
|
||||
var summaries = new List<string>();
|
||||
foreach (var change in _changes)
|
||||
{
|
||||
if (_cancelToken.IsCancellationRequested)
|
||||
return "";
|
||||
|
||||
_onProgress?.Invoke($"Analyzing {change.Path}...");
|
||||
var summary = GenerateChangeSummary(change);
|
||||
summaries.Add(summary);
|
||||
}
|
||||
|
||||
if (_cancelToken.IsCancellationRequested)
|
||||
return "";
|
||||
|
||||
_onProgress?.Invoke($"Generating commit message...");
|
||||
var builder = new StringBuilder();
|
||||
builder.Append(GenerateSubject(string.Join("", summaries)));
|
||||
builder.Append("\n");
|
||||
foreach (var summary in summaries)
|
||||
{
|
||||
builder.Append("\n- ");
|
||||
builder.Append(summary.Trim());
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
App.RaiseException(_repo, $"Failed to generate commit message: {e}");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateChangeSummary(Models.Change change)
|
||||
{
|
||||
var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd();
|
||||
var diff = rs.IsSuccess ? rs.StdOut : "unknown change";
|
||||
|
||||
var prompt = new StringBuilder();
|
||||
prompt.AppendLine("You are an expert developer specialist in creating commits.");
|
||||
prompt.AppendLine("Provide a super concise one sentence overall changes summary of the user `git diff` output following strictly the next rules:");
|
||||
prompt.AppendLine("- Do not use any code snippets, imports, file routes or bullets points.");
|
||||
prompt.AppendLine("- Do not mention the route of file that has been change.");
|
||||
prompt.AppendLine("- Simply describe the MAIN GOAL of the changes.");
|
||||
prompt.AppendLine("- Output directly the summary in plain text.`");
|
||||
|
||||
var rsp = Models.OpenAI.Chat(prompt.ToString(), $"Here is the `git diff` output: {diff}");
|
||||
if (rsp != null && rsp.Choices.Count > 0)
|
||||
return rsp.Choices[0].Message.Content;
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private string GenerateSubject(string summary)
|
||||
{
|
||||
var prompt = new StringBuilder();
|
||||
prompt.AppendLine("You are an expert developer specialist in creating commits messages.");
|
||||
prompt.AppendLine("Your only goal is to retrieve a single commit message.");
|
||||
prompt.AppendLine("Based on the provided user changes, combine them in ONE SINGLE commit message retrieving the global idea, following strictly the next rules:");
|
||||
prompt.AppendLine("- Assign the commit {type} according to the next conditions:");
|
||||
prompt.AppendLine(" feat: Only when adding a new feature.");
|
||||
prompt.AppendLine(" fix: When fixing a bug.");
|
||||
prompt.AppendLine(" docs: When updating documentation.");
|
||||
prompt.AppendLine(" style: When changing elements styles or design and/or making changes to the code style (formatting, missing semicolons, etc.) without changing the code logic.");
|
||||
prompt.AppendLine(" test: When adding or updating tests. ");
|
||||
prompt.AppendLine(" chore: When making changes to the build process or auxiliary tools and libraries. ");
|
||||
prompt.AppendLine(" revert: When undoing a previous commit.");
|
||||
prompt.AppendLine(" refactor: When restructuring code without changing its external behavior, or is any of the other refactor types.");
|
||||
prompt.AppendLine("- Do not add any issues numeration, explain your output nor introduce your answer.");
|
||||
prompt.AppendLine("- Output directly only one commit message in plain text with the next format: {type}: {commit_message}.");
|
||||
prompt.AppendLine("- Be as concise as possible, keep the message under 50 characters.");
|
||||
|
||||
var rsp = Models.OpenAI.Chat(prompt.ToString(), $"Here are the summaries changes: {summary}");
|
||||
if (rsp != null && rsp.Choices.Count > 0)
|
||||
return rsp.Choices[0].Message.Content;
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private string _repo;
|
||||
private List<Models.Change> _changes;
|
||||
private CancellationToken _cancelToken;
|
||||
private Action<string> _onProgress;
|
||||
}
|
||||
}
|
123
src/Models/OpenAI.cs
Normal file
123
src/Models/OpenAI.cs
Normal file
|
@ -0,0 +1,123 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class OpenAIChatMessage
|
||||
{
|
||||
[JsonPropertyName("role")]
|
||||
public string Role
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[JsonPropertyName("content")]
|
||||
public string Content
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
public class OpenAIChatChoice
|
||||
{
|
||||
[JsonPropertyName("index")]
|
||||
public int Index
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public OpenAIChatMessage Message
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
public class OpenAIChatResponse
|
||||
{
|
||||
[JsonPropertyName("choices")]
|
||||
public List<OpenAIChatChoice> Choices
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
}
|
||||
|
||||
public class OpenAIChatRequest
|
||||
{
|
||||
[JsonPropertyName("model")]
|
||||
public string Model
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[JsonPropertyName("messages")]
|
||||
public List<OpenAIChatMessage> Messages
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
|
||||
public void AddMessage(string role, string content)
|
||||
{
|
||||
Messages.Add(new OpenAIChatMessage { Role = role, Content = content });
|
||||
}
|
||||
}
|
||||
|
||||
public static class OpenAI
|
||||
{
|
||||
public static string Server
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public static string ApiKey
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public static string Model
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public static bool IsValid
|
||||
{
|
||||
get => !string.IsNullOrEmpty(Server) && !string.IsNullOrEmpty(ApiKey) && !string.IsNullOrEmpty(Model);
|
||||
}
|
||||
|
||||
public static OpenAIChatResponse Chat(string prompt, string question)
|
||||
{
|
||||
var chat = new OpenAIChatRequest() { Model = Model };
|
||||
chat.AddMessage("system", prompt);
|
||||
chat.AddMessage("user", question);
|
||||
|
||||
var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(60) };
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {ApiKey}");
|
||||
|
||||
var req = new StringContent(JsonSerializer.Serialize(chat, JsonCodeGen.Default.OpenAIChatRequest));
|
||||
var task = client.PostAsync(Server, req);
|
||||
task.Wait();
|
||||
|
||||
var rsp = task.Result;
|
||||
if (!rsp.IsSuccessStatusCode)
|
||||
throw new Exception($"AI service returns error code {rsp.StatusCode}");
|
||||
|
||||
var reader = rsp.Content.ReadAsStringAsync();
|
||||
reader.Wait();
|
||||
|
||||
return JsonSerializer.Deserialize(reader.Result, JsonCodeGen.Default.OpenAIChatResponse);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<StreamGeometry x:Key="Icons.AIAssist">M951 419a255 255 0 00-22-209 258 258 0 00-278-124A259 259 0 00213 178a255 255 0 00-171 124 258 258 0 0032 303 255 255 0 0022 210 258 258 0 00278 124A255 255 0 00566 1024a258 258 0 00246-179 256 256 0 00171-124 258 258 0 00-32-302zM566 957a191 191 0 01-123-44l6-3 204-118a34 34 0 0017-29v-287l86 50a3 3 0 012 2v238a192 192 0 01-192 192zM154 781a191 191 0 01-23-129l6 4 204 118a33 33 0 0033 0l249-144v99a3 3 0 01-1 3L416 851a192 192 0 01-262-70zM100 337a191 191 0 01101-84V495a33 33 0 0017 29l248 143-86 50a3 3 0 01-3 0l-206-119A192 192 0 01100 336zm708 164-249-145L645 307a3 3 0 013 0l206 119a192 192 0 01-29 346v-242a34 34 0 00-17-28zm86-129-6-4-204-119a33 33 0 00-33 0L401 394V294a3 3 0 011-3l206-119a192 192 0 01285 199zm-539 176-86-50a3 3 0 01-2-2V259a192 192 0 01315-147l-6 3-204 118a34 34 0 00-17 29zm47-101 111-64 111 64v128l-111 64-111-64z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Archive">M296 392h64v64h-64zM296 582v160h128V582h-64v-62h-64v62zm80 48v64h-32v-64h32zM360 328h64v64h-64zM296 264h64v64h-64zM360 456h64v64h-64zM360 200h64v64h-64zM855 289 639 73c-6-6-14-9-23-9H192c-18 0-32 14-32 32v832c0 18 14 32 32 32h640c18 0 32-14 32-32V311c0-9-3-17-9-23zM790 326H602V138L790 326zm2 562H232V136h64v64h64v-64h174v216c0 23 19 42 42 42h216v494z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Binary">M71 1024V0h661L953 219V1024H71zm808-731-220-219H145V951h735V293zM439 512h-220V219h220V512zm-74-219H292v146h74v-146zm0 512h74v73h-220v-73H292v-146H218V585h147v219zm294-366h74V512H512v-73h74v-146H512V219h147v219zm74 439H512V585h220v293zm-74-219h-74v146h74v-146z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Blame">M128 256h192a64 64 0 110 128H128a64 64 0 110-128zm576 192h192a64 64 0 010 128h-192a64 64 0 010-128zm-576 192h192a64 64 0 010 128H128a64 64 0 010-128zm576 0h192a64 64 0 010 128h-192a64 64 0 010-128zm0-384h192a64 64 0 010 128h-192a64 64 0 010-128zM128 448h192a64 64 0 110 128H128a64 64 0 110-128zm384-320a64 64 0 0164 64v640a64 64 0 01-128 0V192a64 64 0 0164-64z</StreamGeometry>
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
<x:String x:Key="Text.AddWorktree.Name.Placeholder" xml:space="preserve">Optional. Default is the destination folder name.</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.Tracking" xml:space="preserve">Track Branch:</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.Tracking.Toggle" xml:space="preserve">Tracking remote branch</x:String>
|
||||
<x:String x:Key="Text.AIAssistant" xml:space="preserve">OpenAI Assistant</x:String>
|
||||
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">Use OpenAI to generate commit message</x:String>
|
||||
<x:String x:Key="Text.Apply" xml:space="preserve">Patch</x:String>
|
||||
<x:String x:Key="Text.Apply.Error" xml:space="preserve">Error</x:String>
|
||||
<x:String x:Key="Text.Apply.Error.Desc" xml:space="preserve">Raise errors and refuses to apply the patch</x:String>
|
||||
|
@ -384,6 +386,10 @@
|
|||
<x:String x:Key="Text.Period.LastYear" xml:space="preserve">Last year</x:String>
|
||||
<x:String x:Key="Text.Period.YearsAgo" xml:space="preserve">{0} years ago</x:String>
|
||||
<x:String x:Key="Text.Preference" xml:space="preserve">Preference</x:String>
|
||||
<x:String x:Key="Text.Preference.AI" xml:space="preserve">OPEN AI</x:String>
|
||||
<x:String x:Key="Text.Preference.AI.Server" xml:space="preserve">Server</x:String>
|
||||
<x:String x:Key="Text.Preference.AI.ApiKey" xml:space="preserve">API Key</x:String>
|
||||
<x:String x:Key="Text.Preference.AI.Model" xml:space="preserve">Model</x:String>
|
||||
<x:String x:Key="Text.Preference.Appearance" xml:space="preserve">APPEARANCE</x:String>
|
||||
<x:String x:Key="Text.Preference.Appearance.DefaultFont" xml:space="preserve">Default Font</x:String>
|
||||
<x:String x:Key="Text.Preference.Appearance.DefaultFontSize" xml:space="preserve">Default Font Size</x:String>
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
<x:String x:Key="Text.AddWorktree.Name.Placeholder" xml:space="preserve">选填。默认使用目标文件夹名称。</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.Tracking" xml:space="preserve">跟踪分支</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.Tracking.Toggle" xml:space="preserve">设置上游跟踪分支</x:String>
|
||||
<x:String x:Key="Text.AIAssistant" xml:space="preserve">OpenAI助手</x:String>
|
||||
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">使用OpenAI助手生成提交信息</x:String>
|
||||
<x:String x:Key="Text.Apply" xml:space="preserve">应用补丁(apply)</x:String>
|
||||
<x:String x:Key="Text.Apply.Error" xml:space="preserve">错误</x:String>
|
||||
<x:String x:Key="Text.Apply.Error.Desc" xml:space="preserve">输出错误,并终止应用补丁</x:String>
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
<x:String x:Key="Text.AddWorktree.Name.Placeholder" xml:space="preserve">選填。預設使用目標資料夾名稱。</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.Tracking" xml:space="preserve">追蹤分支</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.Tracking.Toggle" xml:space="preserve">設定遠端追蹤分支</x:String>
|
||||
<x:String x:Key="Text.AIAssistant" xml:space="preserve">OpenAI 助手</x:String>
|
||||
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">使用 OpenAI 產生提交消息</x:String>
|
||||
<x:String x:Key="Text.Apply" xml:space="preserve">套用修補檔 (apply patch)</x:String>
|
||||
<x:String x:Key="Text.Apply.Error" xml:space="preserve">錯誤</x:String>
|
||||
<x:String x:Key="Text.Apply.Error.Desc" xml:space="preserve">輸出錯誤,並中止套用修補檔</x:String>
|
||||
|
|
|
@ -466,21 +466,6 @@
|
|||
<Setter Property="Fill" Value="{DynamicResource Brush.FG2}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.no_border">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<Style Selector="Button.no_border /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
</Style>
|
||||
<Style Selector="Button.no_border:pointerover Path">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}"/>
|
||||
</Style>
|
||||
<Style Selector="Button.no_border:pressed">
|
||||
<Setter Property="RenderTransform" Value="scale(1.0)"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.flat">
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Border2}"/>
|
||||
|
|
|
@ -274,6 +274,45 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
public string OpenAIServer
|
||||
{
|
||||
get => Models.OpenAI.Server;
|
||||
set
|
||||
{
|
||||
if (value != Models.OpenAI.Server)
|
||||
{
|
||||
Models.OpenAI.Server = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string OpenAIApiKey
|
||||
{
|
||||
get => Models.OpenAI.ApiKey;
|
||||
set
|
||||
{
|
||||
if (value != Models.OpenAI.ApiKey)
|
||||
{
|
||||
Models.OpenAI.ApiKey = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string OpenAIModel
|
||||
{
|
||||
get => Models.OpenAI.Model;
|
||||
set
|
||||
{
|
||||
if (value != Models.OpenAI.Model)
|
||||
{
|
||||
Models.OpenAI.Model = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int ExternalMergeToolType
|
||||
{
|
||||
get => _externalMergeToolType;
|
||||
|
|
|
@ -33,6 +33,11 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public class WorkingCopy : ObservableObject
|
||||
{
|
||||
public string RepoPath
|
||||
{
|
||||
get => _repo.FullPath;
|
||||
}
|
||||
|
||||
public bool IncludeUntracked
|
||||
{
|
||||
get => _repo.IncludeUntracked;
|
||||
|
|
60
src/Views/AIAssistant.axaml
Normal file
60
src/Views/AIAssistant.axaml
Normal file
|
@ -0,0 +1,60 @@
|
|||
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:v="using:SourceGit.Views"
|
||||
xmlns:vm="using:SourceGit.ViewModels"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="120"
|
||||
x:Class="SourceGit.Views.AIAssistant"
|
||||
x:DataType="vm:WorkingCopy"
|
||||
x:Name="ThisControl"
|
||||
Icon="/App.ico"
|
||||
Title="{DynamicResource Text.AIAssistant}"
|
||||
Width="400" Height="120"
|
||||
CanResize="False"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid RowDefinitions="Auto,Auto,Auto">
|
||||
<!-- TitleBar -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto" Height="30" IsVisible="{Binding !#ThisControl.UseSystemWindowFrame}">
|
||||
<Border Grid.Column="0" Grid.ColumnSpan="3"
|
||||
Background="{DynamicResource Brush.TitleBar}"
|
||||
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}"
|
||||
PointerPressed="BeginMoveWindow"/>
|
||||
|
||||
<Path Grid.Column="0"
|
||||
Width="14" Height="14"
|
||||
Margin="10,0,0,0"
|
||||
Data="{StaticResource Icons.AIAssist}"
|
||||
IsVisible="{OnPlatform True, macOS=False}"/>
|
||||
|
||||
<v:CaptionButtonsMacOS Grid.Column="0"
|
||||
Margin="0,2,0,0"
|
||||
IsCloseButtonOnly="True"
|
||||
IsVisible="{OnPlatform False, macOS=True}"/>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.ColumnSpan="3"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.AIAssistant}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
IsHitTestVisible="False"/>
|
||||
|
||||
<v:CaptionButtons Grid.Column="2"
|
||||
IsCloseButtonOnly="True"
|
||||
IsVisible="{OnPlatform True, macOS=False}"/>
|
||||
</Grid>
|
||||
|
||||
<!-- Animated Icon -->
|
||||
<v:LoadingIcon Grid.Row="1"
|
||||
Width="24" Height="24"
|
||||
Margin="0,16,0,0"/>
|
||||
|
||||
<!-- Message -->
|
||||
<TextBlock Grid.Row="2"
|
||||
x:Name="ProgressMessage"
|
||||
Margin="16"
|
||||
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}"
|
||||
HorizontalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</Grid>
|
||||
</v:ChromelessWindow>
|
61
src/Views/AIAssistant.axaml.cs
Normal file
61
src/Views/AIAssistant.axaml.cs
Normal file
|
@ -0,0 +1,61 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
public partial class AIAssistant : ChromelessWindow
|
||||
{
|
||||
public AIAssistant()
|
||||
{
|
||||
_cancel = new CancellationTokenSource();
|
||||
InitializeComponent();
|
||||
ProgressMessage.Text = "Generating commit message... Please wait!";
|
||||
}
|
||||
|
||||
public void GenerateCommitMessage()
|
||||
{
|
||||
if (DataContext is ViewModels.WorkingCopy vm)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
var message = new Commands.GenerateCommitMessage(vm.RepoPath, vm.Staged, _cancel.Token, SetDescription).Result();
|
||||
if (_cancel.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
if (DataContext is ViewModels.WorkingCopy wc)
|
||||
wc.CommitMessage = message;
|
||||
|
||||
Close();
|
||||
});
|
||||
}, _cancel.Token);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClosing(WindowClosingEventArgs e)
|
||||
{
|
||||
base.OnClosing(e);
|
||||
_cancel.Cancel();
|
||||
}
|
||||
|
||||
private void BeginMoveWindow(object _, PointerPressedEventArgs e)
|
||||
{
|
||||
BeginMoveDrag(e);
|
||||
}
|
||||
|
||||
private void SetDescription(string message)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
ProgressMessage.Text = message;
|
||||
});
|
||||
}
|
||||
|
||||
private CancellationTokenSource _cancel;
|
||||
}
|
||||
}
|
|
@ -348,6 +348,41 @@
|
|||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.AI}"/>
|
||||
</TabItem.Header>
|
||||
|
||||
<Grid Margin="8" RowDefinitions="32,32,32" ColumnDefinitions="Auto,*">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
Text="{DynamicResource Text.Preference.AI.Server}"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0,0,16,0"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1"
|
||||
Height="28"
|
||||
CornerRadius="3"
|
||||
Text="{Binding OpenAIServer, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0"
|
||||
Text="{DynamicResource Text.Preference.AI.ApiKey}"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0,0,16,0"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1"
|
||||
Height="28"
|
||||
CornerRadius="3"
|
||||
Text="{Binding OpenAIApiKey, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0"
|
||||
Text="{DynamicResource Text.Preference.AI.Model}"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0,0,16,0"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="1"
|
||||
Height="28"
|
||||
CornerRadius="3"
|
||||
Text="{Binding OpenAIModel, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.GPG}"/>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
xmlns:vm="using:SourceGit.ViewModels"
|
||||
xmlns:v="using:SourceGit.Views"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
|
||||
x:Class="SourceGit.Views.WorkingCopy"
|
||||
x:DataType="vm:WorkingCopy">
|
||||
<Grid>
|
||||
|
@ -173,34 +173,41 @@
|
|||
<v:CommitMessageTextBox Grid.Row="2" Text="{Binding CommitMessage, Mode=TwoWay}"/>
|
||||
|
||||
<!-- Commit Options -->
|
||||
<Grid Grid.Row="3" Margin="0,6,0,0" ColumnDefinitions="Auto,Auto,Auto,*,Auto,Auto,Auto">
|
||||
<Grid Grid.Row="3" Margin="0,6,0,0" ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto,Auto,Auto">
|
||||
<Button Grid.Column="0"
|
||||
Classes="no_border"
|
||||
Classes="icon_button"
|
||||
Margin="4,0,0,0" Padding="0"
|
||||
Click="OnOpenCommitMessagePicker">
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Menu}"/>
|
||||
<TextBlock Grid.Column="1" Margin="8,0,0,0" Text="{DynamicResource Text.WorkingCopy.CommitMessageHelper}"/>
|
||||
</Grid>
|
||||
Click="OnOpenCommitMessagePicker"
|
||||
ToolTip.Tip="{DynamicResource Text.WorkingCopy.CommitMessageHelper}">
|
||||
<Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Menu}"/>
|
||||
</Button>
|
||||
|
||||
<CheckBox Grid.Column="1"
|
||||
<Button Grid.Column="1"
|
||||
Classes="icon_button"
|
||||
Width="32"
|
||||
Margin="4,0,0,0"
|
||||
Click="OnOpenAIAssist"
|
||||
ToolTip.Tip="{DynamicResource Text.AIAssistant.Tip}">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.AIAssist}"/>
|
||||
</Button>
|
||||
|
||||
<CheckBox Grid.Column="2"
|
||||
Height="24"
|
||||
Margin="12,0,0,0"
|
||||
Margin="8,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
IsChecked="{Binding AutoStageBeforeCommit, Mode=TwoWay}"
|
||||
Content="{DynamicResource Text.WorkingCopy.AutoStage}"/>
|
||||
|
||||
<CheckBox Grid.Column="2"
|
||||
<CheckBox Grid.Column="3"
|
||||
Height="24"
|
||||
Margin="12,0,0,0"
|
||||
Margin="8,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
IsChecked="{Binding UseAmend, Mode=TwoWay}"
|
||||
Content="{DynamicResource Text.WorkingCopy.Amend}"/>
|
||||
|
||||
<v:LoadingIcon Grid.Column="4" Width="18" Height="18" IsVisible="{Binding IsCommitting}"/>
|
||||
<v:LoadingIcon Grid.Column="5" Width="18" Height="18" IsVisible="{Binding IsCommitting}"/>
|
||||
|
||||
<Button Grid.Column="5"
|
||||
<Button Grid.Column="6"
|
||||
Classes="flat primary"
|
||||
Content="{DynamicResource Text.WorkingCopy.Commit}"
|
||||
Height="28"
|
||||
|
@ -210,7 +217,7 @@
|
|||
HotKey="{OnPlatform Ctrl+Enter, macOS=⌘+Enter}"
|
||||
ToolTip.Tip="{OnPlatform Ctrl+Enter, macOS=⌘+Enter}"/>
|
||||
|
||||
<Button Grid.Column="6"
|
||||
<Button Grid.Column="7"
|
||||
Classes="flat"
|
||||
Content="{DynamicResource Text.WorkingCopy.CommitAndPush}"
|
||||
Height="28"
|
||||
|
|
|
@ -88,5 +88,23 @@ namespace SourceGit.Views
|
|||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOpenAIAssist(object _, RoutedEventArgs e)
|
||||
{
|
||||
if (!Models.OpenAI.IsValid)
|
||||
{
|
||||
App.RaiseException(null, $"Bad configuration for OpenAI");
|
||||
return;
|
||||
}
|
||||
|
||||
if (DataContext is ViewModels.WorkingCopy vm && vm.Staged is { Count: > 0 })
|
||||
{
|
||||
var dialog = new AIAssistant() { DataContext = vm };
|
||||
dialog.GenerateCommitMessage();
|
||||
App.OpenDialog(dialog);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue