refactor: OpenAI integration (#996)

- Add `OpenAIResponse` to trim the `<think>...</think>` block
- Add an `Enable Streaming` option to fix the issue that some services do not support streaming output

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo 2025-02-19 18:01:16 +08:00
parent 69d107430a
commit c3eca0d7fd
No known key found for this signature in database
6 changed files with 145 additions and 85 deletions

View file

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Avalonia.Threading;
@ -36,6 +35,8 @@ namespace SourceGit.Commands
{
try
{
_onResponse?.Invoke("Waiting for pre-file analyzing to completed...\n\n");
var responseBuilder = new StringBuilder();
var summaryBuilder = new StringBuilder();
foreach (var change in _changes)
@ -49,18 +50,17 @@ 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 =>
ProcessChatResponse(update, ref hasFirstValidChar, thinkingBuffer,
(responseBuilder, text =>
_onResponse?.Invoke(
$"Waiting for pre-file analyzing to completed...\n\n{text}")),
(summaryBuilder, null)));
{
responseBuilder.Append(update);
summaryBuilder.Append(update);
_onResponse?.Invoke($"Waiting for pre-file analyzing to completed...\n\n{responseBuilder}");
});
}
responseBuilder.Append("\n");
@ -74,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 =>
ProcessChatResponse(update, ref hasSubjectFirstValidChar, subjectThinkingBuffer,
(subjectBuilder, text => _onResponse?.Invoke($"{text}\n\n{responseBody}"))));
{
subjectBuilder.Append(update);
_onResponse?.Invoke($"{subjectBuilder}\n\n{responseBody}");
});
}
catch (Exception e)
{
@ -90,67 +90,10 @@ namespace SourceGit.Commands
}
}
private void ProcessChatResponse(
string update,
ref bool hasFirstValidChar,
StringBuilder thinkingBuffer,
params (StringBuilder builder, Action<string> 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<Models.Change> _changes;
private CancellationToken _cancelToken;
private Action<string> _onResponse;
private static readonly Regex REG_COT = new(@"^<(think|thought|thinking|thought_chain)>(.*?)</\1>", RegexOptions.Singleline);
private static readonly Regex REG_THINK_START = new(@"^<(think|thought|thinking|thought_chain)>", RegexOptions.Singleline);
}
}