diff --git a/src/Commands/GenerateCommitMessage.cs b/src/Commands/GenerateCommitMessage.cs index c207786f..4b18a561 100644 --- a/src/Commands/GenerateCommitMessage.cs +++ b/src/Commands/GenerateCommitMessage.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using Avalonia.Threading; @@ -48,16 +49,18 @@ namespace SourceGit.Commands var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd(); if (rs.IsSuccess) { + var hasFirstValidChar = false; + var thinkingBuffer = new StringBuilder(); _service.Chat( _service.AnalyzeDiffPrompt, $"Here is the `git diff` output: {rs.StdOut}", _cancelToken, update => - { - responseBuilder.Append(update); - summaryBuilder.Append(update); - _onResponse?.Invoke("Waiting for pre-file analyzing to complated...\n\n" + responseBuilder.ToString()); - }); + ProcessChatResponse(update, ref hasFirstValidChar, thinkingBuffer, + (responseBuilder, text => + _onResponse?.Invoke( + $"Waiting for pre-file analyzing to completed...\n\n{text}")), + (summaryBuilder, null))); } responseBuilder.Append("\n"); @@ -71,15 +74,15 @@ namespace SourceGit.Commands var responseBody = responseBuilder.ToString(); var subjectBuilder = new StringBuilder(); + var hasSubjectFirstValidChar = false; + var subjectThinkingBuffer = new StringBuilder(); _service.Chat( _service.GenerateSubjectPrompt, $"Here are the summaries changes:\n{summaryBuilder}", _cancelToken, update => - { - subjectBuilder.Append(update); - _onResponse?.Invoke($"{subjectBuilder}\n\n{responseBody}"); - }); + ProcessChatResponse(update, ref hasSubjectFirstValidChar, subjectThinkingBuffer, + (subjectBuilder, text => _onResponse?.Invoke($"{text}\n\n{responseBody}")))); } catch (Exception e) { @@ -87,10 +90,67 @@ namespace SourceGit.Commands } } + private void ProcessChatResponse( + string update, + ref bool hasFirstValidChar, + StringBuilder thinkingBuffer, + params (StringBuilder builder, Action callback)[] outputs) + { + if (!hasFirstValidChar) + { + update = update.TrimStart(); + if (string.IsNullOrEmpty(update)) + return; + if (update.StartsWith("<", StringComparison.Ordinal)) + thinkingBuffer.Append(update); + hasFirstValidChar = true; + } + + if (thinkingBuffer.Length > 0) + thinkingBuffer.Append(update); + + if (thinkingBuffer.Length > 15) + { + var match = REG_COT.Match(thinkingBuffer.ToString()); + if (match.Success) + { + update = REG_COT.Replace(thinkingBuffer.ToString(), "").TrimStart(); + if (update.Length > 0) + { + foreach (var output in outputs) + output.builder.Append(update); + thinkingBuffer.Clear(); + } + return; + } + + match = REG_THINK_START.Match(thinkingBuffer.ToString()); + if (!match.Success) + { + foreach (var output in outputs) + output.builder.Append(thinkingBuffer); + thinkingBuffer.Clear(); + return; + } + } + + if (thinkingBuffer.Length == 0) + { + foreach (var output in outputs) + { + output.builder.Append(update); + output.callback?.Invoke(output.builder.ToString()); + } + } + } + private Models.OpenAIService _service; private string _repo; private List _changes; private CancellationToken _cancelToken; private Action _onResponse; + + private static readonly Regex REG_COT = new(@"^<(think|thought|thinking|thought_chain)>(.*?)", RegexOptions.Singleline); + private static readonly Regex REG_THINK_START = new(@"^<(think|thought|thinking|thought_chain)>", RegexOptions.Singleline); } } diff --git a/src/Models/OpenAI.cs b/src/Models/OpenAI.cs index e791efcf..a6648c11 100644 --- a/src/Models/OpenAI.cs +++ b/src/Models/OpenAI.cs @@ -19,7 +19,15 @@ namespace SourceGit.Models public string Server { get => _server; - set => SetProperty(ref _server, value); + set + { + // migrate old server value + if (!string.IsNullOrEmpty(value) && value.EndsWith("/chat/completions", StringComparison.Ordinal)) + { + value = value.Substring(0, value.Length - "/chat/completions".Length); + } + SetProperty(ref _server, value); + } } public string ApiKey