Skip to content

Commit f2a2c09

Browse files
committed
refactor: move Models.AIProvider to AI.Service
Signed-off-by: leo <longshuang@msn.cn>
1 parent 4206764 commit f2a2c09

File tree

9 files changed

+107
-105
lines changed

9 files changed

+107
-105
lines changed

src/AI/Agent.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Azure.AI.OpenAI;
7+
using OpenAI;
8+
using OpenAI.Chat;
9+
10+
namespace SourceGit.AI
11+
{
12+
public class Agent
13+
{
14+
public Agent(Service service)
15+
{
16+
_service = service;
17+
}
18+
19+
public async Task GenerateCommitMessage(string repo, string changeList, Action<string> onUpdate, CancellationToken cancellation)
20+
{
21+
var endPoint = new Uri(_service.Server);
22+
var client = _service.Server.Contains("openai.azure.com/", StringComparison.Ordinal)
23+
? new AzureOpenAIClient(endPoint, _service.Credential)
24+
: new OpenAIClient(_service.Credential, new() { Endpoint = endPoint });
25+
26+
var chatClient = client.GetChatClient(_service.Model);
27+
var options = new ChatCompletionOptions() { Tools = { ChatTools.Tool_GetDetailChangesInFile } };
28+
29+
var userMessageBuilder = new StringBuilder();
30+
userMessageBuilder
31+
.AppendLine("Generate a commit message (follow the rule of conventional commit message) for given git repository.")
32+
.AppendLine("- Read all given changed files before generating. Do not skip any one file.")
33+
.AppendLine("- Output the conventional commit message (with detail changes in list) directly. Do not explain your output nor introduce your answer.")
34+
.AppendLine(string.IsNullOrEmpty(_service.AdditionalPrompt) ? string.Empty : _service.AdditionalPrompt)
35+
.Append("Reposiory path: ").AppendLine(repo.Quoted())
36+
.AppendLine("Changed files: ")
37+
.Append(changeList);
38+
39+
var messages = new List<ChatMessage>() { new UserChatMessage(userMessageBuilder.ToString()) };
40+
41+
do
42+
{
43+
ChatCompletion completion = await chatClient.CompleteChatAsync(messages, options, cancellation);
44+
var inProgress = false;
45+
46+
switch (completion.FinishReason)
47+
{
48+
case ChatFinishReason.Stop:
49+
onUpdate?.Invoke(string.Empty);
50+
onUpdate?.Invoke("[Assistant]:");
51+
if (completion.Content.Count > 0)
52+
onUpdate?.Invoke(completion.Content[0].Text);
53+
else
54+
onUpdate?.Invoke("[No content was generated.]");
55+
break;
56+
case ChatFinishReason.Length:
57+
throw new Exception("The response was cut off because it reached the maximum length. Consider increasing the max tokens limit.");
58+
case ChatFinishReason.ToolCalls:
59+
{
60+
messages.Add(new AssistantChatMessage(completion));
61+
62+
foreach (var call in completion.ToolCalls)
63+
{
64+
var result = await ChatTools.Process(call, onUpdate);
65+
messages.Add(result);
66+
}
67+
68+
inProgress = true;
69+
break;
70+
}
71+
case ChatFinishReason.ContentFilter:
72+
throw new Exception("Ommitted content due to a content filter flag");
73+
default:
74+
break;
75+
}
76+
77+
if (!inProgress)
78+
break;
79+
} while (true);
80+
}
81+
82+
private readonly Service _service;
83+
}
84+
}

src/AI/ChatTools.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public static class ChatTools
2525
},
2626
"originalFile": {
2727
"type": "string",
28-
"description": "The path to the original file when it has been renamed."
28+
"description": "The path to the original file when it has been renamed (marked as 'R' or 'C')."
2929
}
3030
},
3131
"required": ["repo", "file"]

src/AI/Service.cs

Lines changed: 7 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,16 @@
11
using System;
22
using System.ClientModel;
3-
using System.Collections.Generic;
4-
using System.Text;
5-
using System.Threading;
6-
using System.Threading.Tasks;
7-
using Azure.AI.OpenAI;
8-
using OpenAI;
9-
using OpenAI.Chat;
103

114
namespace SourceGit.AI
125
{
136
public class Service
147
{
15-
public Service(Models.AIProvider ai)
16-
{
17-
_ai = ai;
18-
}
19-
20-
public async Task GenerateCommitMessage(string repo, string changeList, Action<string> onUpdate, CancellationToken cancellation)
21-
{
22-
var key = _ai.ReadApiKeyFromEnv ? Environment.GetEnvironmentVariable(_ai.ApiKey) : _ai.ApiKey;
23-
var endPoint = new Uri(_ai.Server);
24-
var credential = new ApiKeyCredential(key);
25-
var client = _ai.Server.Contains("openai.azure.com/", StringComparison.Ordinal)
26-
? new AzureOpenAIClient(endPoint, credential)
27-
: new OpenAIClient(credential, new() { Endpoint = endPoint });
28-
29-
var chatClient = client.GetChatClient(_ai.Model);
30-
var options = new ChatCompletionOptions() { Tools = { ChatTools.Tool_GetDetailChangesInFile } };
31-
32-
var userMessageBuilder = new StringBuilder();
33-
userMessageBuilder
34-
.AppendLine("Generate a commit message (follow the rule of conventional commit message) for given git repository.")
35-
.AppendLine("- Read all given changed files before generating. Do not skip any one file.")
36-
.AppendLine("- Output the conventional commit message (with detail changes in list) directly. Do not explain your output nor introduce your answer.")
37-
.AppendLine(string.IsNullOrEmpty(_ai.AdditionalPrompt) ? string.Empty : _ai.AdditionalPrompt)
38-
.Append("Reposiory path: ").AppendLine(repo.Quoted())
39-
.AppendLine("Changed files: ")
40-
.Append(changeList);
41-
42-
var messages = new List<ChatMessage>() { new UserChatMessage(userMessageBuilder.ToString()) };
43-
44-
do
45-
{
46-
ChatCompletion completion = await chatClient.CompleteChatAsync(messages, options, cancellation);
47-
var inProgress = false;
48-
49-
switch (completion.FinishReason)
50-
{
51-
case ChatFinishReason.Stop:
52-
onUpdate?.Invoke(string.Empty);
53-
onUpdate?.Invoke("[Assistant]:");
54-
if (completion.Content.Count > 0)
55-
onUpdate?.Invoke(completion.Content[0].Text);
56-
else
57-
onUpdate?.Invoke("[No content was generated.]");
58-
break;
59-
case ChatFinishReason.Length:
60-
throw new Exception("The response was cut off because it reached the maximum length. Consider increasing the max tokens limit.");
61-
case ChatFinishReason.ToolCalls:
62-
{
63-
messages.Add(new AssistantChatMessage(completion));
64-
65-
foreach (var call in completion.ToolCalls)
66-
{
67-
var result = await ChatTools.Process(call, onUpdate);
68-
messages.Add(result);
69-
}
70-
71-
inProgress = true;
72-
break;
73-
}
74-
case ChatFinishReason.ContentFilter:
75-
throw new Exception("Ommitted content due to a content filter flag");
76-
default:
77-
break;
78-
}
79-
80-
if (!inProgress)
81-
break;
82-
} while (true);
83-
}
84-
85-
private readonly Models.AIProvider _ai;
8+
public string Name { get; set; }
9+
public string Server { get; set; }
10+
public string Model { get; set; }
11+
public string ApiKey { get; set; }
12+
public bool ReadApiKeyFromEnv { get; set; }
13+
public string AdditionalPrompt { get; set; }
14+
public ApiKeyCredential Credential => new ApiKeyCredential(ReadApiKeyFromEnv ? Environment.GetEnvironmentVariable(ApiKey) : ApiKey);
8615
}
8716
}

src/Models/AIProvider.cs

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/ViewModels/AIAssistant.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ public string Text
2424
private set => SetProperty(ref _text, value);
2525
}
2626

27-
public AIAssistant(string repo, Models.AIProvider provider, List<Models.Change> changes)
27+
public AIAssistant(string repo, AI.Service service, List<Models.Change> changes)
2828
{
2929
_repo = repo;
30-
_provider = provider;
30+
_service = service;
3131
_cancel = new CancellationTokenSource();
3232

3333
var builder = new StringBuilder();
@@ -80,14 +80,14 @@ private void Gen()
8080
_cancel = new CancellationTokenSource();
8181
Task.Run(async () =>
8282
{
83-
var server = new AI.Service(_provider);
83+
var agent = new AI.Agent(_service);
8484
var builder = new StringBuilder();
8585
builder.AppendLine("Asking AI to generate commit message...").AppendLine();
8686
Dispatcher.UIThread.Post(() => Text = builder.ToString());
8787

8888
try
8989
{
90-
await server.GenerateCommitMessage(_repo, _changeList, message =>
90+
await agent.GenerateCommitMessage(_repo, _changeList, message =>
9191
{
9292
builder.AppendLine(message);
9393
Dispatcher.UIThread.Post(() => Text = builder.ToString());
@@ -103,7 +103,7 @@ await server.GenerateCommitMessage(_repo, _changeList, message =>
103103
}
104104

105105
private readonly string _repo = null;
106-
private readonly Models.AIProvider _provider = null;
106+
private readonly AI.Service _service = null;
107107
private readonly string _changeList = null;
108108
private CancellationTokenSource _cancel = null;
109109
private bool _isGenerating = false;

src/ViewModels/Preferences.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ public AvaloniaList<Models.CustomAction> CustomActions
480480
set;
481481
} = [];
482482

483-
public AvaloniaList<Models.AIProvider> OpenAIServices
483+
public AvaloniaList<AI.Service> OpenAIServices
484484
{
485485
get;
486486
set;

src/ViewModels/Repository.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1599,7 +1599,7 @@ public async Task UnlockWorktreeAsync(Worktree worktree)
15991599
log.Complete();
16001600
}
16011601

1602-
public List<Models.AIProvider> GetPreferredOpenAIServices()
1602+
public List<AI.Service> GetPreferredOpenAIServices()
16031603
{
16041604
var services = Preferences.Instance.OpenAIServices;
16051605
if (services == null || services.Count == 0)
@@ -1609,7 +1609,7 @@ public async Task UnlockWorktreeAsync(Worktree worktree)
16091609
return [services[0]];
16101610

16111611
var preferred = _settings.PreferredOpenAIService;
1612-
var all = new List<Models.AIProvider>();
1612+
var all = new List<AI.Service>();
16131613
foreach (var service in services)
16141614
{
16151615
if (service.Name.Equals(preferred, StringComparison.Ordinal))

src/Views/Preferences.axaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
44
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
55
xmlns:s="using:SourceGit"
6+
xmlns:ai="using:SourceGit.AI"
67
xmlns:m="using:SourceGit.Models"
78
xmlns:c="using:SourceGit.Converters"
89
xmlns:vm="using:SourceGit.ViewModels"
@@ -822,7 +823,7 @@
822823
</ListBox.ItemsPanel>
823824

824825
<ListBox.ItemTemplate>
825-
<DataTemplate DataType="m:AIProvider">
826+
<DataTemplate DataType="ai:Service">
826827
<Grid ColumnDefinitions="Auto,*">
827828
<Path Grid.Column="0" Width="14" Height="14" Data="{StaticResource Icons.AIAssist}"/>
828829
<TextBlock Grid.Column="1" Text="{Binding Name}" Margin="8,0" TextTrimming="CharacterEllipsis"/>
@@ -859,7 +860,7 @@
859860
</ContentControl.Content>
860861

861862
<ContentControl.DataTemplates>
862-
<DataTemplate DataType="m:AIProvider">
863+
<DataTemplate DataType="ai:Service">
863864
<StackPanel Orientation="Vertical" MaxWidth="680">
864865
<TextBlock Text="{DynamicResource Text.Preferences.AI.Name}"/>
865866
<TextBox Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding Name, Mode=TwoWay}"/>

src/Views/Preferences.axaml.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,10 @@ public bool EnableHTTPSSLVerify
9595
set;
9696
} = false;
9797

98-
public static readonly StyledProperty<Models.AIProvider> SelectedOpenAIServiceProperty =
99-
AvaloniaProperty.Register<Preferences, Models.AIProvider>(nameof(SelectedOpenAIService));
98+
public static readonly StyledProperty<AI.Service> SelectedOpenAIServiceProperty =
99+
AvaloniaProperty.Register<Preferences, AI.Service>(nameof(SelectedOpenAIService));
100100

101-
public Models.AIProvider SelectedOpenAIService
101+
public AI.Service SelectedOpenAIService
102102
{
103103
get => GetValue(SelectedOpenAIServiceProperty);
104104
set => SetValue(SelectedOpenAIServiceProperty, value);
@@ -397,7 +397,7 @@ private void OnGitInstallPathChanged(object sender, TextChangedEventArgs e)
397397

398398
private void OnAddOpenAIService(object sender, RoutedEventArgs e)
399399
{
400-
var service = new Models.AIProvider() { Name = "Unnamed Service" };
400+
var service = new AI.Service() { Name = "Unnamed Service" };
401401
ViewModels.Preferences.Instance.OpenAIServices.Add(service);
402402
SelectedOpenAIService = service;
403403

0 commit comments

Comments
 (0)