mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-05-20 11:44:59 +00:00
235 lines
8.4 KiB
C#
235 lines
8.4 KiB
C#
using System;
|
|
using System.ClientModel;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using Azure.AI.OpenAI;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using OpenAI;
|
|
using OpenAI.Chat;
|
|
|
|
namespace SourceGit.Models
|
|
{
|
|
public partial class OpenAIResponse
|
|
{
|
|
public OpenAIResponse(Action<string> onUpdate)
|
|
{
|
|
_onUpdate = onUpdate;
|
|
}
|
|
|
|
public void Append(string text)
|
|
{
|
|
var buffer = text;
|
|
|
|
if (_thinkTail.Length > 0)
|
|
{
|
|
_thinkTail.Append(buffer);
|
|
buffer = _thinkTail.ToString();
|
|
_thinkTail.Clear();
|
|
}
|
|
|
|
buffer = REG_COT().Replace(buffer, "");
|
|
|
|
var startIdx = buffer.IndexOf('<', StringComparison.Ordinal);
|
|
if (startIdx >= 0)
|
|
{
|
|
if (startIdx > 0)
|
|
OnReceive(buffer.Substring(0, startIdx));
|
|
|
|
var endIdx = buffer.IndexOf(">", startIdx + 1, StringComparison.Ordinal);
|
|
if (endIdx <= startIdx)
|
|
{
|
|
if (buffer.Length - startIdx <= 15)
|
|
_thinkTail.Append(buffer.Substring(startIdx));
|
|
else
|
|
OnReceive(buffer.Substring(startIdx));
|
|
}
|
|
else if (endIdx < startIdx + 15)
|
|
{
|
|
var tag = buffer.Substring(startIdx + 1, endIdx - startIdx - 1);
|
|
if (_thinkTags.Contains(tag))
|
|
_thinkTail.Append(buffer.Substring(startIdx));
|
|
else
|
|
OnReceive(buffer.Substring(startIdx));
|
|
}
|
|
else
|
|
{
|
|
OnReceive(buffer.Substring(startIdx));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OnReceive(buffer);
|
|
}
|
|
}
|
|
|
|
public void End()
|
|
{
|
|
if (_thinkTail.Length > 0)
|
|
{
|
|
OnReceive(_thinkTail.ToString());
|
|
_thinkTail.Clear();
|
|
}
|
|
}
|
|
|
|
private void OnReceive(string text)
|
|
{
|
|
if (!_hasTrimmedStart)
|
|
{
|
|
text = text.TrimStart();
|
|
if (string.IsNullOrEmpty(text))
|
|
return;
|
|
|
|
_hasTrimmedStart = true;
|
|
}
|
|
|
|
_onUpdate.Invoke(text);
|
|
}
|
|
|
|
[GeneratedRegex(@"<(think|thought|thinking|thought_chain)>.*?</\1>", RegexOptions.Singleline)]
|
|
private static partial Regex REG_COT();
|
|
|
|
private Action<string> _onUpdate = null;
|
|
private StringBuilder _thinkTail = new StringBuilder();
|
|
private HashSet<string> _thinkTags = ["think", "thought", "thinking", "thought_chain"];
|
|
private bool _hasTrimmedStart = false;
|
|
}
|
|
|
|
public class OpenAIService : ObservableObject
|
|
{
|
|
public string Name
|
|
{
|
|
get => _name;
|
|
set => SetProperty(ref _name, value);
|
|
}
|
|
|
|
public string Server
|
|
{
|
|
get => _server;
|
|
set => SetProperty(ref _server, value);
|
|
}
|
|
|
|
public string ApiKey
|
|
{
|
|
get => _apiKey;
|
|
set => SetProperty(ref _apiKey, value);
|
|
}
|
|
|
|
public string Model
|
|
{
|
|
get => _model;
|
|
set => SetProperty(ref _model, value);
|
|
}
|
|
|
|
public bool Streaming
|
|
{
|
|
get => _streaming;
|
|
set => SetProperty(ref _streaming, value);
|
|
}
|
|
|
|
public string AnalyzeDiffPrompt
|
|
{
|
|
get => _analyzeDiffPrompt;
|
|
set => SetProperty(ref _analyzeDiffPrompt, value);
|
|
}
|
|
|
|
public string GenerateSubjectPrompt
|
|
{
|
|
get => _generateSubjectPrompt;
|
|
set => SetProperty(ref _generateSubjectPrompt, value);
|
|
}
|
|
|
|
public OpenAIService()
|
|
{
|
|
AnalyzeDiffPrompt = """
|
|
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.
|
|
- Write clear, concise, and descriptive messages that explain the MAIN GOAL made of the changes.
|
|
- Use the present tense and active voice in the message, for example, "Fix bug" instead of "Fixed bug.".
|
|
- Use the imperative mood, which gives the message a sense of command, e.g. "Add feature" instead of "Added feature".
|
|
- Avoid using general terms like "update" or "change", be specific about what was updated or changed.
|
|
- Avoid using terms like "The main goal of", just output directly the summary in plain text
|
|
""";
|
|
|
|
GenerateSubjectPrompt = """
|
|
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 void Chat(string prompt, string question, CancellationToken cancellation, Action<string> onUpdate)
|
|
{
|
|
var server = new Uri(_server);
|
|
var key = new ApiKeyCredential(_apiKey);
|
|
var client = null as ChatClient;
|
|
if (_server.Contains("openai.azure.com/", StringComparison.Ordinal))
|
|
{
|
|
var azure = new AzureOpenAIClient(server, key);
|
|
client = azure.GetChatClient(_model);
|
|
}
|
|
else
|
|
{
|
|
var openai = new OpenAIClient(key, new() { Endpoint = server });
|
|
client = openai.GetChatClient(_model);
|
|
}
|
|
|
|
var messages = new List<ChatMessage>();
|
|
messages.Add(_model.Equals("o1-mini", StringComparison.Ordinal) ? new UserChatMessage(prompt) : new SystemChatMessage(prompt));
|
|
messages.Add(new UserChatMessage(question));
|
|
|
|
try
|
|
{
|
|
var rsp = new OpenAIResponse(onUpdate);
|
|
|
|
if (_streaming)
|
|
{
|
|
var updates = client.CompleteChatStreaming(messages, null, cancellation);
|
|
|
|
foreach (var update in updates)
|
|
{
|
|
if (update.ContentUpdate.Count > 0)
|
|
rsp.Append(update.ContentUpdate[0].Text);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var completion = client.CompleteChat(messages, null, cancellation);
|
|
|
|
if (completion.Value.Content.Count > 0)
|
|
rsp.Append(completion.Value.Content[0].Text);
|
|
}
|
|
|
|
rsp.End();
|
|
}
|
|
catch
|
|
{
|
|
if (!cancellation.IsCancellationRequested)
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private string _name;
|
|
private string _server;
|
|
private string _apiKey;
|
|
private string _model;
|
|
private bool _streaming = true;
|
|
private string _analyzeDiffPrompt;
|
|
private string _generateSubjectPrompt;
|
|
}
|
|
}
|