feat: improve commit message generation with AI prompts (#596)

- Refactor the commit message generation process to utilize default prompts and enhance clarity while eliminating redundancy.
- Added new properties for subject and summary prompts, while improving cancellation support in async task handling.
- feat: add AI prompts for commit message generation.
- Updated the formatting of the package reference for consistency in the project file.
- Add properties for managing OpenAI subject and summary prompts in the Preference view model.
- Refactor layout and add new input fields for AI subject and summary prompts in the preferences view.
This commit is contained in:
Douglas Cunha 2024-10-23 22:31:05 -03:00 committed by GitHub
parent 547c28adb8
commit 2f68aed817
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 141 additions and 38 deletions

View file

@ -10,6 +10,33 @@ namespace SourceGit.Commands
/// </summary> /// </summary>
public class GenerateCommitMessage public class GenerateCommitMessage
{ {
private const string DEFAULT_SUMMARY_PROMPT = """
You are an expert developer specialist in creating commits.
Provide a super concise one sentence overall changes summary of the user `git diff` output following strictly the next rules:
- Do not use any code snippets, imports, file routes or bullets points.
- Do not mention the route of file that has been change.
- Simply describe the MAIN GOAL of the changes.
- Output directly the summary in plain text.
""";
private const string DEFAULT_SUBJECT_PROMPT = """
You are an expert developer specialist in creating commits messages.
Your only goal is to retrieve a single commit message.
Based on the provided user changes, combine them in ONE SINGLE commit message retrieving the global idea, following strictly the next rules:
- Assign the commit {type} according to the next conditions:
feat: Only when adding a new feature.
fix: When fixing a bug.
docs: When updating documentation.
style: When changing elements styles or design and/or making changes to the code style (formatting, missing semicolons, etc.) without changing the code logic.
test: When adding or updating tests.
chore: When making changes to the build process or auxiliary tools and libraries.
revert: When undoing a previous commit.
refactor: When restructuring code without changing its external behavior, or is any of the other refactor types.
- Do not add any issues numeration, explain your output nor introduce your answer.
- Output directly only one commit message in plain text with the next format: {type}: {commit_message}.
- Be as concise as possible, keep the message under 50 characters.
""";
public class GetDiffContent : Command public class GetDiffContent : Command
{ {
public GetDiffContent(string repo, Models.DiffOption opt) public GetDiffContent(string repo, Models.DiffOption opt)
@ -70,15 +97,12 @@ namespace SourceGit.Commands
var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd(); var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd();
var diff = rs.IsSuccess ? rs.StdOut : "unknown change"; var diff = rs.IsSuccess ? rs.StdOut : "unknown change";
var prompt = new StringBuilder(); var prompt = string.IsNullOrWhiteSpace(Models.OpenAI.SummaryPrompt)
prompt.AppendLine("You are an expert developer specialist in creating commits."); ? DEFAULT_SUMMARY_PROMPT
prompt.AppendLine("Provide a super concise one sentence overall changes summary of the user `git diff` output following strictly the next rules:"); : Models.OpenAI.SummaryPrompt;
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."); var rsp = Models.OpenAI.Chat(prompt, $"Here is the `git diff` output: {diff}", _cancelToken);
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}", _cancelToken);
if (rsp != null && rsp.Choices.Count > 0) if (rsp != null && rsp.Choices.Count > 0)
return rsp.Choices[0].Message.Content; return rsp.Choices[0].Message.Content;
@ -87,24 +111,12 @@ namespace SourceGit.Commands
private string GenerateSubject(string summary) private string GenerateSubject(string summary)
{ {
var prompt = new StringBuilder(); var prompt = string.IsNullOrWhiteSpace(Models.OpenAI.SubjectPrompt)
prompt.AppendLine("You are an expert developer specialist in creating commits messages."); ? DEFAULT_SUBJECT_PROMPT
prompt.AppendLine("Your only goal is to retrieve a single commit message."); : Models.OpenAI.SubjectPrompt;
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:"); var rsp = Models.OpenAI.Chat(prompt, $"Here are the summaries changes: {summary}", _cancelToken);
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}", _cancelToken);
if (rsp != null && rsp.Choices.Count > 0) if (rsp != null && rsp.Choices.Count > 0)
return rsp.Choices[0].Message.Content; return rsp.Choices[0].Message.Content;

View file

@ -94,6 +94,18 @@ namespace SourceGit.Models
set; set;
} }
public static string SubjectPrompt
{
get;
set;
}
public static string SummaryPrompt
{
get;
set;
}
public static bool IsValid public static bool IsValid
{ {
get => !string.IsNullOrEmpty(Server) && !string.IsNullOrEmpty(Model); get => !string.IsNullOrEmpty(Server) && !string.IsNullOrEmpty(Model);
@ -113,14 +125,14 @@ namespace SourceGit.Models
try try
{ {
var task = client.PostAsync(Server, req, cancellation); var task = client.PostAsync(Server, req, cancellation);
task.Wait(); task.Wait(cancellation);
var rsp = task.Result; var rsp = task.Result;
if (!rsp.IsSuccessStatusCode) if (!rsp.IsSuccessStatusCode)
throw new Exception($"AI service returns error code {rsp.StatusCode}"); throw new Exception($"AI service returns error code {rsp.StatusCode}");
var reader = rsp.Content.ReadAsStringAsync(cancellation); var reader = rsp.Content.ReadAsStringAsync(cancellation);
reader.Wait(); reader.Wait(cancellation);
return JsonSerializer.Deserialize(reader.Result, JsonCodeGen.Default.OpenAIChatResponse); return JsonSerializer.Deserialize(reader.Result, JsonCodeGen.Default.OpenAIChatResponse);
} }

View file

@ -403,6 +403,31 @@
<x:String x:Key="Text.Preference.AI.Server" xml:space="preserve">Server</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.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.AI.Model" xml:space="preserve">Model</x:String>
<x:String x:Key="Text.Preference.AI.SummaryPrompt" xml:space="preserve">Summary Prompt</x:String>
<x:String x:Key="Text.Preference.AI.SubjectPrompt" xml:space="preserve">Subject Prompt</x:String>
<x:String x:Key="Text.Preference.AI.SummaryPromptHint" xml:space="preserve">You are an expert developer specialist in creating commits.
Provide a super concise one sentence overall changes summary of the user `git diff` output following strictly the next rules:
- Do not use any code snippets, imports, file routes or bullets points.
- Do not mention the route of file that has been change.
- Simply describe the MAIN GOAL of the changes.
- Output directly the summary in plain text.
</x:String>
<x:String x:Key="Text.Preference.AI.SubjectPromptHint" xml:space="preserve">You are an expert developer specialist in creating commits messages.
Your only goal is to retrieve a single commit message.
Based on the provided user changes, combine them in ONE SINGLE commit message retrieving the global idea, following strictly the next rules:
- Assign the commit {type} according to the next conditions:
feat: Only when adding a new feature.
fix: When fixing a bug.
docs: When updating documentation.
style: When changing elements styles or design and/or making changes to the code style (formatting, missing semicolons, etc.) without changing the code logic.
test: When adding or updating tests.
chore: When making changes to the build process or auxiliary tools and libraries.
revert: When undoing a previous commit.
refactor: When restructuring code without changing its external behavior, or is any of the other refactor types.
- Do not add any issues numeration, explain your output nor introduce your answer.
- Output directly only one commit message in plain text with the next format: {type}: {commit_message}.
- Be as concise as possible, keep the message under 50 characters.
</x:String>
<x:String x:Key="Text.Preference.Appearance" xml:space="preserve">APPEARANCE</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.DefaultFont" xml:space="preserve">Default Font</x:String>
<x:String x:Key="Text.Preference.Appearance.DefaultFontSize" xml:space="preserve">Default Font Size</x:String> <x:String x:Key="Text.Preference.Appearance.DefaultFontSize" xml:space="preserve">Default Font Size</x:String>

View file

@ -315,6 +315,32 @@ namespace SourceGit.ViewModels
} }
} }
public string OpenAISubjectPrompt
{
get => Models.OpenAI.SubjectPrompt;
set
{
if (value != Models.OpenAI.SubjectPrompt)
{
Models.OpenAI.SubjectPrompt = value;
OnPropertyChanged();
}
}
}
public string OpenAISummaryPrompt
{
get => Models.OpenAI.SummaryPrompt;
set
{
if (value != Models.OpenAI.SummaryPrompt)
{
Models.OpenAI.SummaryPrompt = value;
OnPropertyChanged();
}
}
}
public uint StatisticsSampleColor public uint StatisticsSampleColor
{ {
get => _statisticsSampleColor; get => _statisticsSampleColor;

View file

@ -465,7 +465,7 @@
<TextBlock Classes="bold" Margin="4,0,0,0" Text="{DynamicResource Text.Preference.AI}"/> <TextBlock Classes="bold" Margin="4,0,0,0" Text="{DynamicResource Text.Preference.AI}"/>
</StackPanel> </StackPanel>
<Rectangle Margin="0,8" Fill="{DynamicResource Brush.Border2}" Height=".6" HorizontalAlignment="Stretch"/> <Rectangle Margin="0,8" Fill="{DynamicResource Brush.Border2}" Height=".6" HorizontalAlignment="Stretch"/>
<Grid Margin="8,0,0,0" RowDefinitions="32,32,32"> <Grid Margin="8,0,0,0" RowDefinitions="32,32,32,128,128">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="IntegrationLabel"/> <ColumnDefinition Width="Auto" SharedSizeGroup="IntegrationLabel"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
@ -497,6 +497,34 @@
Height="28" Height="28"
CornerRadius="3" CornerRadius="3"
Text="{Binding OpenAIApiKey, Mode=TwoWay}"/> Text="{Binding OpenAIApiKey, Mode=TwoWay}"/>
<TextBlock Grid.Row="3" Grid.Column="0"
Text="{DynamicResource Text.Preference.AI.SubjectPrompt}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<TextBox Grid.Row="3" Grid.Column="1"
Height="120"
CornerRadius="3"
VerticalContentAlignment="Top"
Text="{Binding OpenAISubjectPrompt, Mode=TwoWay}"
AcceptsReturn="true"
Watermark="{DynamicResource Text.Preference.AI.SubjectPromptHint}"
TextWrapping="Wrap"/>
<TextBlock Grid.Row="4" Grid.Column="0"
Text="{DynamicResource Text.Preference.AI.SummaryPrompt}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<TextBox Grid.Row="4" Grid.Column="1"
Height="120"
CornerRadius="3"
VerticalContentAlignment="Top"
Text="{Binding OpenAISummaryPrompt, Mode=TwoWay}"
AcceptsReturn="true"
Watermark="{DynamicResource Text.Preference.AI.SummaryPromptHint}"
TextWrapping="Wrap"/>
</Grid> </Grid>
</StackPanel> </StackPanel>
</TabItem> </TabItem>