Compare commits

...

24 commits

Author SHA1 Message Date
github-actions[bot]
651d313496 doc: Update translation status and missing keys
Some checks are pending
Continuous Integration / Build (push) Waiting to run
Continuous Integration / Prepare version string (push) Waiting to run
Continuous Integration / Package (push) Blocked by required conditions
Localization Check / localization-check (push) Waiting to run
2024-10-28 13:37:13 +00:00
leo
498d2b54ae
feature: add per-repository setting for prefered OpenAI service
* If there is only one OpenAI service available, discard the setting of prefered OpenAI service. Instead, use it directly
* If there are multiple OpenAI service available, try to find the prefered one or show a context menu for users to choose the one they want to use

Signed-off-by: leo <longshuang@msn.cn>
2024-10-28 21:36:10 +08:00
leo
48725a7937
code_review: PR #612
Signed-off-by: leo <longshuang@msn.cn>
2024-10-28 19:42:54 +08:00
ybeapps
a363f67f31
bug: fix int out of bounds for branch names with long numbers (#612) 2024-10-28 19:24:43 +08:00
leo
6c390d2f04
ux: layout of stage page
Signed-off-by: leo <longshuang@msn.cn>
2024-10-28 18:06:27 +08:00
leo
6cc0c54ac1
enhance: remember last selection of some options while stashing changes (#610)
Signed-off-by: leo <longshuang@msn.cn>
2024-10-28 17:55:32 +08:00
leo
1418591b0b
refactor: rewrite command git stash push
Signed-off-by: leo <longshuang@msn.cn>
2024-10-28 16:58:53 +08:00
github-actions[bot]
a7cccd5c1d doc: Update translation status and missing keys 2024-10-28 08:52:00 +00:00
leo
566d36ca59
feature: add option to enable --keep-index option of git stash push command (#610)
Signed-off-by: leo <longshuang@msn.cn>
2024-10-28 16:51:42 +08:00
leo
3e6e0befaa
readme: update macOS usage (#609)
Signed-off-by: leo <longshuang@msn.cn>
2024-10-28 16:37:57 +08:00
github-actions[bot]
216d2d02bd doc: Update translation status and missing keys 2024-10-28 07:56:57 +00:00
leo
134d69d403
feature: supports generate commit message by OpenAI with selected staged changes (#608)
Signed-off-by: leo <longshuang@msn.cn>
2024-10-28 15:56:38 +08:00
github-actions[bot]
9d1840f78c doc: Update translation status and missing keys 2024-10-28 03:55:59 +00:00
leo
bb47dc28ef
localization: copy Text.Preference.AI.Name from Text.Name for ru_RU
Signed-off-by: leo <longshuang@msn.cn>
2024-10-28 11:55:39 +08:00
github-actions[bot]
f1dc46c251 doc: Update translation status and missing keys 2024-10-28 03:27:59 +00:00
leo
148e2fa1e5
fix: delete wrong key by mistake
Signed-off-by: leo <longshuang@msn.cn>
2024-10-28 11:27:38 +08:00
github-actions[bot]
3490d62f3c doc: Update translation status and missing keys 2024-10-28 03:00:34 +00:00
leo
1044915be1
refactor: OpenAI integration
* supports configure multiple services
* supports select service when generate commit message by OpenAI

Signed-off-by: leo <longshuang@msn.cn>
2024-10-28 11:00:11 +08:00
GadflyFang
8280287362
doc: add link on badges (#607)
Signed-off-by: Gadfly <gadfly@gadfly.vip>
2024-10-28 09:45:16 +08:00
leo
ff0354d606
Merge branch 'master' into develop 2024-10-28 09:33:41 +08:00
leo
bb29476a80
Merge branch 'release/v8.36' 2024-10-28 09:33:22 +08:00
leo
9caa20cef0
version: Release 8.36
Signed-off-by: leo <longshuang@msn.cn>
2024-10-28 09:33:10 +08:00
github-actions[bot]
6447590491 doc: Update translation status and missing keys 2024-10-28 01:28:10 +00:00
AquariusStar
bde648eae8
Localization: updating the translation (#606) 2024-10-28 09:27:58 +08:00
23 changed files with 562 additions and 317 deletions

View file

@ -1,6 +1,10 @@
# SourceGit - Opensource Git GUI client.
![stars](https://img.shields.io/github/stars/sourcegit-scm/sourcegit.svg) ![forks](https://img.shields.io/github/forks/sourcegit-scm/sourcegit.svg) ![license](https://img.shields.io/github/license/sourcegit-scm/sourcegit.svg) ![latest](https://img.shields.io/github/v/release/sourcegit-scm/sourcegit.svg) ![downloads](https://img.shields.io/github/downloads/sourcegit-scm/sourcegit/total)
[![stars](https://img.shields.io/github/stars/sourcegit-scm/sourcegit.svg)](https://github.com/sourcegit-scm/sourcegit/stargazers)
[![forks](https://img.shields.io/github/forks/sourcegit-scm/sourcegit.svg)](https://github.com/sourcegit-scm/sourcegit/forks)
[![license](https://img.shields.io/github/license/sourcegit-scm/sourcegit.svg)](LICENSE)
[![latest](https://img.shields.io/github/v/release/sourcegit-scm/sourcegit.svg)](https://github.com/sourcegit-scm/sourcegit/releases/latest)
[![downloads](https://img.shields.io/github/downloads/sourcegit-scm/sourcegit/total)](https://github.com/sourcegit-scm/sourcegit/releases)
## Highlights
@ -43,7 +47,7 @@
## Translation Status
[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-98.95%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-90.36%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-93.52%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-98.80%25-yellow)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-99.10%25-yellow)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md)
[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-98.21%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-89.69%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-92.83%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-99.25%25-yellow)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md)
## How to Use
@ -80,11 +84,16 @@ For **Windows** users:
For **macOS** users:
* Download `sourcegit_x.y.osx-x64.zip` or `sourcegit_x.y.osx-arm64.zip` from Releases. `x64` for Intel and `arm64` for Apple Silicon.
* Move `SourceGit.app` to `Applications` folder.
* Make sure your mac trusts all software from anywhere. For more information, search `spctl --master-disable`.
* Thanks [@ybeapps](https://github.com/ybeapps) for making `SourceGit` available on `Homebrew`. You can simply install it with following command:
```shell
brew tap ybeapps/homebrew-sourcegit
brew install --cask --no-quarantine sourcegit
```
* If you want to install `SourceGit.app` from Github Release manually, you need run following command to make sure it works:
```shell
sudo xattr -cr /Applications/SourceGit.app
```
* Make sure [git-credential-manager](https://github.com/git-ecosystem/git-credential-manager/releases) is installed on your mac.
* You may need to run `sudo xattr -cr /Applications/SourceGit.app` to make sure the software works.
* You can run `echo $PATH > ~/Library/Application\ Support/SourceGit/PATH` to generate a custom PATH env file to introduce `PATH` env to SourceGit.
For **Linux** users:

View file

@ -1,20 +1,25 @@
### de_DE.axaml: 98.95%
### de_DE.axaml: 98.21%
<details>
<summary>Missing Keys</summary>
- Text.ChangeCM.GenerateCommitMessage
- Text.Configure.Git.EnableSignOff
- Text.Configure.IssueTracker.AddSampleGitLabIssue
- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest
- Text.Preference.Advanced
- Text.Configure.OpenAI
- Text.Configure.OpenAI.Prefered
- Text.Configure.OpenAI.Prefered.Tip
- Text.Preference.AI.AnalyzeDiffPrompt
- Text.Preference.AI.GenerateSubjectPrompt
- Text.Preference.AI.Name
- Text.Stash.KeepIndex
- Text.WorkingCopy.ConfirmCommitWithoutFiles
</details>
### fr_FR.axaml: 90.36%
### fr_FR.axaml: 89.69%
<details>
@ -23,6 +28,7 @@
- Text.About.Chart
- Text.AIAssistant
- Text.AIAssistant.Tip
- Text.ChangeCM.GenerateCommitMessage
- Text.CherryPick.AppendSourceToMessage
- Text.CherryPick.Mainline
- Text.CherryPick.Mainline.Tips
@ -33,6 +39,9 @@
- Text.Configure.Git.EnableSignOff
- Text.Configure.IssueTracker.AddSampleGitLabIssue
- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest
- Text.Configure.OpenAI
- Text.Configure.OpenAI.Prefered
- Text.Configure.OpenAI.Prefered.Tip
- Text.ConfigureWorkspace
- Text.ConfigureWorkspace.Color
- Text.ConfigureWorkspace.Restore
@ -55,12 +64,12 @@
- Text.Hotkeys.Repo.DiscardSelected
- Text.MoveRepositoryNode
- Text.MoveRepositoryNode.Target
- Text.Preference.Advanced
- Text.Preference.AI
- Text.Preference.AI.AnalyzeDiffPrompt
- Text.Preference.AI.ApiKey
- Text.Preference.AI.GenerateSubjectPrompt
- Text.Preference.AI.Model
- Text.Preference.AI.Name
- Text.Preference.AI.Server
- Text.Preference.General.ShowAuthorTime
- Text.Preference.Integration
@ -73,6 +82,7 @@
- Text.ScanRepositories
- Text.ScanRepositories.RootDir
- Text.Squash.Into
- Text.Stash.KeepIndex
- Text.Stash.OnlyStagedChanges
- Text.Stash.TipForSelectedFiles
- Text.Statistics.Overview
@ -87,7 +97,7 @@
</details>
### pt_BR.axaml: 93.52%
### pt_BR.axaml: 92.83%
<details>
@ -96,6 +106,7 @@
- Text.About.Chart
- Text.AIAssistant
- Text.AIAssistant.Tip
- Text.ChangeCM.GenerateCommitMessage
- Text.CherryPick.AppendSourceToMessage
- Text.CherryPick.Mainline
- Text.CherryPick.Mainline.Tips
@ -108,6 +119,9 @@
- Text.Configure.Git.EnableSignOff
- Text.Configure.IssueTracker.AddSampleGitLabIssue
- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest
- Text.Configure.OpenAI
- Text.Configure.OpenAI.Prefered
- Text.Configure.OpenAI.Prefered.Tip
- Text.ConfigureWorkspace
- Text.ConfigureWorkspace.Color
- Text.ConfigureWorkspace.Restore
@ -125,9 +139,10 @@
- Text.GitLFS.Locks.OnlyMine
- Text.MoveRepositoryNode
- Text.MoveRepositoryNode.Target
- Text.Preference.Advanced
- Text.Preference.AI.Name
- Text.Push.CheckSubmodules
- Text.Squash.Into
- Text.Stash.KeepIndex
- Text.Stash.OnlyStagedChanges
- Text.Stash.TipForSelectedFiles
- Text.Statistics.Overview
@ -139,35 +154,27 @@
</details>
### ru_RU.axaml: 98.80%
### ru_RU.axaml: 99.25%
<details>
<summary>Missing Keys</summary>
- Text.Configure.Git.EnableSignOff
- Text.Configure.IssueTracker.AddSampleGitLabIssue
- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest
- Text.Preference.Advanced
- Text.Preference.AI.AnalyzeDiffPrompt
- Text.Preference.AI.GenerateSubjectPrompt
- Text.Repository.EnableReflog
- Text.WorkingCopy.ConfirmCommitWithoutFiles
- Text.ChangeCM.GenerateCommitMessage
- Text.Configure.OpenAI
- Text.Configure.OpenAI.Prefered
- Text.Configure.OpenAI.Prefered.Tip
- Text.Stash.KeepIndex
</details>
### zh_CN.axaml: 99.10%
### zh_CN.axaml: 100.00%
<details>
<summary>Missing Keys</summary>
- Text.Preference.AI
- Text.Preference.AI.AnalyzeDiffPrompt
- Text.Preference.AI.ApiKey
- Text.Preference.AI.GenerateSubjectPrompt
- Text.Preference.AI.Model
- Text.Preference.AI.Server
</details>

View file

@ -1 +1 @@
8.35
8.36

View file

@ -20,8 +20,9 @@ namespace SourceGit.Commands
}
}
public GenerateCommitMessage(string repo, List<Models.Change> changes, CancellationToken cancelToken, Action<string> onProgress)
public GenerateCommitMessage(Models.OpenAIService service, string repo, List<Models.Change> changes, CancellationToken cancelToken, Action<string> onProgress)
{
_service = service;
_repo = repo;
_changes = changes;
_cancelToken = cancelToken;
@ -75,7 +76,7 @@ namespace SourceGit.Commands
var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd();
var diff = rs.IsSuccess ? rs.StdOut : "unknown change";
var rsp = Models.OpenAI.Chat(Models.OpenAI.AnalyzeDiffPrompt, $"Here is the `git diff` output: {diff}", _cancelToken);
var rsp = _service.Chat(_service.AnalyzeDiffPrompt, $"Here is the `git diff` output: {diff}", _cancelToken);
if (rsp != null && rsp.Choices.Count > 0)
return rsp.Choices[0].Message.Content;
@ -84,13 +85,14 @@ namespace SourceGit.Commands
private string GenerateSubject(string summary)
{
var rsp = Models.OpenAI.Chat(Models.OpenAI.GenerateSubjectPrompt, $"Here are the summaries changes:\n{summary}", _cancelToken);
var rsp = _service.Chat(_service.GenerateSubjectPrompt, $"Here are the summaries changes:\n{summary}", _cancelToken);
if (rsp != null && rsp.Choices.Count > 0)
return rsp.Choices[0].Message.Content;
return string.Empty;
}
private Models.OpenAIService _service;
private string _repo;
private List<Models.Change> _changes;
private CancellationToken _cancelToken;

View file

@ -17,24 +17,29 @@ namespace SourceGit.Commands
return Exec();
}
public bool Push(List<Models.Change> changes, string message, bool onlyStaged)
public bool Push(List<Models.Change> changes, string message, bool onlyStaged, bool keepIndex)
{
var pathsBuilder = new StringBuilder();
var builder = new StringBuilder();
builder.Append("stash push ");
if (onlyStaged)
builder.Append("--staged ");
if (keepIndex)
builder.Append("--keep-index ");
builder.Append("-m \"");
builder.Append(message);
builder.Append("\" -- ");
if (onlyStaged)
{
foreach (var c in changes)
pathsBuilder.Append($"\"{c.Path}\" ");
var paths = pathsBuilder.ToString();
Args = $"stash push --staged -m \"{message}\" -- {paths}";
builder.Append($"\"{c.Path}\" ");
}
else
{
var needAdd = new List<Models.Change>();
foreach (var c in changes)
{
pathsBuilder.Append($"\"{c.Path}\" ");
builder.Append($"\"{c.Path}\" ");
if (c.WorkTree == Models.ChangeState.Added || c.WorkTree == Models.ChangeState.Untracked)
{
@ -51,11 +56,9 @@ namespace SourceGit.Commands
new Add(WorkingDirectory, needAdd).Exec();
needAdd.Clear();
}
var paths = pathsBuilder.ToString();
Args = $"stash push -m \"{message}\" -- {paths}";
}
Args = builder.ToString();
return Exec();
}

View file

@ -50,15 +50,9 @@
string sub2 = new string(tmp2, 0, loc2);
int result;
if (isDigit1 && isDigit2)
{
int num1 = int.Parse(sub1);
int num2 = int.Parse(sub2);
result = num1 - num2;
}
result = loc1 == loc2 ? string.CompareOrdinal(sub1, sub2) : loc1 - loc2;
else
{
result = string.Compare(sub1, sub2, System.StringComparison.Ordinal);
}
result = string.CompareOrdinal(sub1, sub2);
if (result != 0)
return result;

View file

@ -6,6 +6,8 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.Models
{
public class OpenAIChatMessage
@ -74,44 +76,78 @@ namespace SourceGit.Models
}
}
public static class OpenAI
public class OpenAIService : ObservableObject
{
public static string Server
public string Name
{
get;
set;
get => _name;
set => SetProperty(ref _name, value);
}
public static string ApiKey
public string Server
{
get;
set;
get => _server;
set => SetProperty(ref _server, value);
}
public static string Model
public string ApiKey
{
get;
set;
get => _apiKey;
set => SetProperty(ref _apiKey, value);
}
public static string AnalyzeDiffPrompt
public string Model
{
get;
set;
get => _model;
set => SetProperty(ref _model, value);
}
public static string GenerateSubjectPrompt
public string AnalyzeDiffPrompt
{
get;
set;
get => _analyzeDiffPrompt;
set => SetProperty(ref _analyzeDiffPrompt, value);
}
public static bool IsValid
public string GenerateSubjectPrompt
{
get => !string.IsNullOrEmpty(Server) && !string.IsNullOrEmpty(Model);
get => _generateSubjectPrompt;
set => SetProperty(ref _generateSubjectPrompt, value);
}
public static OpenAIChatResponse Chat(string prompt, string question, CancellationToken cancellation)
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 OpenAIChatResponse Chat(string prompt, string question, CancellationToken cancellation)
{
var chat = new OpenAIChatRequest() { Model = Model };
chat.AddMessage("system", prompt);
@ -144,5 +180,12 @@ namespace SourceGit.Models
throw;
}
}
private string _name;
private string _server;
private string _apiKey;
private string _model;
private string _analyzeDiffPrompt;
private string _generateSubjectPrompt;
}
}

View file

@ -112,6 +112,30 @@ namespace SourceGit.Models
set;
} = false;
public bool IncludeUntrackedWhenStash
{
get;
set;
} = true;
public bool OnlyStagedWhenStash
{
get;
set;
} = false;
public bool KeepIndexWhenStash
{
get;
set;
} = false;
public string PreferedOpenAIService
{
get;
set;
} = "---";
public void PushCommitMessage(string message)
{
var existIdx = CommitMessages.IndexOf(message);

View file

@ -69,6 +69,7 @@
<x:String x:Key="Text.Cancel" xml:space="preserve">CANCEL</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">Reset to This Revision</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">Reset to Parent Revision</x:String>
<x:String x:Key="Text.ChangeCM.GenerateCommitMessage" xml:space="preserve">Generate commit message</x:String>
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">CHANGE DISPLAY MODE</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Show as File and Dir List</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Show as Path List</x:String>
@ -154,6 +155,9 @@
<x:String x:Key="Text.Configure.IssueTracker.RuleName" xml:space="preserve">Rule Name:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate" xml:space="preserve">Result URL:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate.Tip" xml:space="preserve">Please use $1, $2 to access regex groups values.</x:String>
<x:String x:Key="Text.Configure.OpenAI" xml:space="preserve">OPEN AI</x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered" xml:space="preserve">Prefered Service:</x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered.Tip" xml:space="preserve">If the 'Prefered Service' is set, SourceGit will only use it in this repository. Otherwise, if there is more than one service available, a context menu to choose one of them will be shown.</x:String>
<x:String x:Key="Text.Configure.Proxy" xml:space="preserve">HTTP Proxy</x:String>
<x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">HTTP proxy used by this repository</x:String>
<x:String x:Key="Text.Configure.User" xml:space="preserve">User Name</x:String>
@ -401,12 +405,12 @@
<x:String x:Key="Text.Period.LastYear" xml:space="preserve">Last year</x:String>
<x:String x:Key="Text.Period.YearsAgo" xml:space="preserve">{0} years ago</x:String>
<x:String x:Key="Text.Preference" xml:space="preserve">Preference</x:String>
<x:String x:Key="Text.Preference.Advanced" xml:space="preserve">Advanced Options</x:String>
<x:String x:Key="Text.Preference.AI" xml:space="preserve">OPEN AI</x:String>
<x:String x:Key="Text.Preference.AI.AnalyzeDiffPrompt" xml:space="preserve">Analyze Diff Prompt</x:String>
<x:String x:Key="Text.Preference.AI.ApiKey" xml:space="preserve">API Key</x:String>
<x:String x:Key="Text.Preference.AI.GenerateSubjectPrompt" xml:space="preserve">Generate Subject Prompt</x:String>
<x:String x:Key="Text.Preference.AI.Model" xml:space="preserve">Model</x:String>
<x:String x:Key="Text.Preference.AI.Name" xml:space="preserve">Name</x:String>
<x:String x:Key="Text.Preference.AI.Server" xml:space="preserve">Server</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>
@ -572,6 +576,7 @@
<x:String x:Key="Text.Start" xml:space="preserve">START</x:String>
<x:String x:Key="Text.Stash" xml:space="preserve">Stash</x:String>
<x:String x:Key="Text.Stash.IncludeUntracked" xml:space="preserve">Include untracked files</x:String>
<x:String x:Key="Text.Stash.KeepIndex" xml:space="preserve">Keep staged files</x:String>
<x:String x:Key="Text.Stash.Message" xml:space="preserve">Message:</x:String>
<x:String x:Key="Text.Stash.Message.Placeholder" xml:space="preserve">Optional. Name of this stash</x:String>
<x:String x:Key="Text.Stash.OnlyStagedChanges" xml:space="preserve">Only staged changes</x:String>

View file

@ -74,8 +74,8 @@
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">Сбросить родительскую ревизию</x:String>
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">ИЗМЕНИТЬ РЕЖИМ ОТОБРАЖЕНИЯ</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Показывать в виде списка файлов и каталогов</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Показать в виде списка путей</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Tree" xml:space="preserve">Показать в виде дерева файловой системы</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Показывать в виде списка путей</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Tree" xml:space="preserve">Показывать в виде дерева файловой системы</x:String>
<x:String x:Key="Text.Checkout" xml:space="preserve">Проверить ветку</x:String>
<x:String x:Key="Text.Checkout.Commit" xml:space="preserve">Проверить фиксацию</x:String>
<x:String x:Key="Text.Checkout.Commit.Warning" xml:space="preserve">Предупреждение: При выполнении проверки фиксации ваша голова будет отсоединена</x:String>
@ -146,9 +146,12 @@
<x:String x:Key="Text.Configure.Git.AutoFetch" xml:space="preserve">Автоматическое извлечение внешних хранилищ</x:String>
<x:String x:Key="Text.Configure.Git.AutoFetchIntervalSuffix" xml:space="preserve">Минут(а/ы)</x:String>
<x:String x:Key="Text.Configure.Git.DefaultRemote" xml:space="preserve">Удалённое хранилище по-умолчанию</x:String>
<x:String x:Key="Text.Configure.Git.EnableSignOff" xml:space="preserve">Разрешить --signoff для фиксации</x:String>
<x:String x:Key="Text.Configure.IssueTracker" xml:space="preserve">ОТСЛЕЖИВАНИЕ ПРОБЛЕМ</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleGithub" xml:space="preserve">Добавить пример правила для Git</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleJira" xml:space="preserve">Добавить пример правила Jira</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleGitLabIssue" xml:space="preserve">Добавить пример правила выдачи GitLab</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleGitLabMergeRequest" xml:space="preserve">Добавить пример правила запроса на слияние в GitLab</x:String>
<x:String x:Key="Text.Configure.IssueTracker.NewRule" xml:space="preserve">Новое правило</x:String>
<x:String x:Key="Text.Configure.IssueTracker.Regex" xml:space="preserve">Проблема с регулярным выражением:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.RuleName" xml:space="preserve">Имя правила:</x:String>
@ -220,7 +223,7 @@
<x:String x:Key="Text.Diff.Copy" xml:space="preserve">Копировать</x:String>
<x:String x:Key="Text.Diff.FileModeChanged" xml:space="preserve">Режим файла изменён</x:String>
<x:String x:Key="Text.Diff.IgnoreWhitespace" xml:space="preserve">Игнорировать изменение пробелов</x:String>
<x:String x:Key="Text.Diff.ShowHiddenSymbols" xml:space="preserve">Показать скрытые символы</x:String>
<x:String x:Key="Text.Diff.ShowHiddenSymbols" xml:space="preserve">Показывать скрытые символы</x:String>
<x:String x:Key="Text.Diff.LFS" xml:space="preserve">ИЗМЕНЕНИЕ ОБЪЕКТА ХБФ</x:String>
<x:String x:Key="Text.Diff.Next" xml:space="preserve">Следующее различие</x:String>
<x:String x:Key="Text.Diff.NoChange" xml:space="preserve">НИКАКИХ ИЗМЕНЕНИЙ ИЛИ МЕНЯЕТСЯ ТОЛЬКО EOL</x:String>
@ -305,7 +308,7 @@
<x:String x:Key="Text.GitLFS.Fetch.Title" xml:space="preserve">Извлечь объекты ХБФ</x:String>
<x:String x:Key="Text.GitLFS.Fetch.Tips" xml:space="preserve">Запустить `git lfs fetch", чтобы загрузить объекты ХБФ Git. При этом рабочая копия не обновляется.</x:String>
<x:String x:Key="Text.GitLFS.Install" xml:space="preserve">Установить перехват ХБФ Git</x:String>
<x:String x:Key="Text.GitLFS.Locks" xml:space="preserve">Показать блокировки</x:String>
<x:String x:Key="Text.GitLFS.Locks" xml:space="preserve">Показывать блокировки</x:String>
<x:String x:Key="Text.GitLFS.Locks.Empty" xml:space="preserve">Нет заблокированных файлов</x:String>
<x:String x:Key="Text.GitLFS.Locks.Lock" xml:space="preserve">Блокировка</x:String>
<x:String x:Key="Text.GitLFS.Locks.OnlyMine" xml:space="preserve">Показывать только мои блокировки</x:String>
@ -403,9 +406,12 @@
<x:String x:Key="Text.Period.YearsAgo" xml:space="preserve">{0} лет назад</x:String>
<x:String x:Key="Text.Preference" xml:space="preserve">Параметры</x:String>
<x:String x:Key="Text.Preference.AI" xml:space="preserve">ОТКРЫТЬ ИИ</x:String>
<x:String x:Key="Text.Preference.AI.Server" xml:space="preserve">Сервер</x:String>
<x:String x:Key="Text.Preference.AI.ApiKey" xml:space="preserve">Ключ API</x:String>
<x:String x:Key="Text.Preference.AI.AnalyzeDiffPrompt" xml:space="preserve">Запрос на анализ различий</x:String>
<x:String x:Key="Text.Preference.AI.GenerateSubjectPrompt" xml:space="preserve">Сгенерировать запрос на тему</x:String>
<x:String x:Key="Text.Preference.AI.Model" xml:space="preserve">Модель</x:String>
<x:String x:Key="Text.Preference.AI.Name" xml:space="preserve">Имя:</x:String>
<x:String x:Key="Text.Preference.AI.Server" xml:space="preserve">Сервер</x:String>
<x:String x:Key="Text.Preference.Appearance" xml:space="preserve">ВИД</x:String>
<x:String x:Key="Text.Preference.Appearance.DefaultFont" xml:space="preserve">Шрифт по-умолчанию</x:String>
<x:String x:Key="Text.Preference.Appearance.DefaultFontSize" xml:space="preserve">Размер шрифта по-умолчанию</x:String>
@ -423,7 +429,7 @@
<x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">Проверить обновления при старте</x:String>
<x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">Язык</x:String>
<x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">История фиксаций</x:String>
<x:String x:Key="Text.Preference.General.ShowAuthorTime" xml:space="preserve">Показать время автора вместо времени фиксации на графике</x:String>
<x:String x:Key="Text.Preference.General.ShowAuthorTime" xml:space="preserve">Показывать время автора вместо времени фиксации на графике</x:String>
<x:String x:Key="Text.Preference.General.SubjectGuideLength" xml:space="preserve">Длина темы фиксации</x:String>
<x:String x:Key="Text.Preference.Git" xml:space="preserve">GIT</x:String>
<x:String x:Key="Text.Preference.Git.CRLF" xml:space="preserve">Включить автозавершение CRLF</x:String>
@ -508,6 +514,7 @@
<x:String x:Key="Text.Repository.ClearAllCommitsFilter" xml:space="preserve">Очистить всё</x:String>
<x:String x:Key="Text.Repository.Configure" xml:space="preserve">Настройка этого хранилища</x:String>
<x:String x:Key="Text.Repository.Continue" xml:space="preserve">ПРОДОЛЖИТЬ</x:String>
<x:String x:Key="Text.Repository.EnableReflog" xml:space="preserve">Разрешить опцию '--reflog'</x:String>
<x:String x:Key="Text.Repository.Explore" xml:space="preserve">Открыть в файловом менеджере</x:String>
<x:String x:Key="Text.Repository.Filter" xml:space="preserve">Поиск веток, меток и подмодулей</x:String>
<x:String x:Key="Text.Repository.FilterCommitPrefix" xml:space="preserve">ОТФИЛЬТРОВАНО ОТ:</x:String>
@ -527,7 +534,7 @@
<x:String x:Key="Text.Repository.Search.BySHA" xml:space="preserve">SHA</x:String>
<x:String x:Key="Text.Repository.Search.ByUser" xml:space="preserve">Автор и исполнитель</x:String>
<x:String x:Key="Text.Repository.Search.InCurrentBranch" xml:space="preserve">Текущая ветка</x:String>
<x:String x:Key="Text.Repository.ShowTagsAsTree" xml:space="preserve">Показать метки как дерево</x:String>
<x:String x:Key="Text.Repository.ShowTagsAsTree" xml:space="preserve">Показывать метки как дерево</x:String>
<x:String x:Key="Text.Repository.Statistics" xml:space="preserve">Статистики </x:String>
<x:String x:Key="Text.Repository.Submodules" xml:space="preserve">ПОДМОДУЛИ</x:String>
<x:String x:Key="Text.Repository.Submodules.Add" xml:space="preserve">ДОБАВИТЬ ПОДМОДУЛЬ</x:String>
@ -638,6 +645,7 @@
<x:String x:Key="Text.WorkingCopy.CommitMessageHelper" xml:space="preserve">Шаблон/Истории</x:String>
<x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">Запустить событие щелчка</x:String>
<x:String x:Key="Text.WorkingCopy.CommitWithAutoStage" xml:space="preserve">Подготовить все изменения и зафиксировать</x:String>
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithoutFiles" xml:space="preserve">Обнаружена пустая фиксация! Вы хотите продолжить (--allow-empty)?</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">ОБНАРУЖЕНЫ КОНФЛИКТЫ</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">КОНФЛИКТЫ ФАЙЛОВ РАЗРЕШЕНЫ</x:String>
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">ВКЛЮЧИТЬ НЕОТСЛЕЖИВАЕМЫЕ ФАЙЛЫ</x:String>

View file

@ -72,6 +72,7 @@
<x:String x:Key="Text.Cancel" xml:space="preserve">取 消</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">重置文件到该版本</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">重置文件到上一版本</x:String>
<x:String x:Key="Text.ChangeCM.GenerateCommitMessage" xml:space="preserve">生成提交信息</x:String>
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切换变更显示模式</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">文件名+路径列表模式</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">全路径列表模式</x:String>
@ -157,6 +158,9 @@
<x:String x:Key="Text.Configure.IssueTracker.RuleName" xml:space="preserve">规则名 </x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate" xml:space="preserve">为ISSUE生成的URL链接 </x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate.Tip" xml:space="preserve">可在URL中使用$1$2等变量填入正则表达式匹配的内容</x:String>
<x:String x:Key="Text.Configure.OpenAI" xml:space="preserve">OPEN AI</x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered" xml:space="preserve">启用特定服务 </x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered.Tip" xml:space="preserve">当【启用特定服务】被设置时SourceGit将在本仓库中仅使用该服务。否则将弹出可用的OpenAI服务列表供用户选择。</x:String>
<x:String x:Key="Text.Configure.Proxy" xml:space="preserve">HTTP代理</x:String>
<x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">HTTP网络代理</x:String>
<x:String x:Key="Text.Configure.User" xml:space="preserve">用户名</x:String>
@ -404,7 +408,13 @@
<x:String x:Key="Text.Period.LastYear" xml:space="preserve">一年前</x:String>
<x:String x:Key="Text.Period.YearsAgo" xml:space="preserve">{0}年前</x:String>
<x:String x:Key="Text.Preference" xml:space="preserve">偏好设置</x:String>
<x:String x:Key="Text.Preference.Advanced" xml:space="preserve">高级设置</x:String>
<x:String x:Key="Text.Preference.AI" xml:space="preserve">OPEN AI</x:String>
<x:String x:Key="Text.Preference.AI.AnalyzeDiffPrompt" xml:space="preserve">Analyze Diff Prompt</x:String>
<x:String x:Key="Text.Preference.AI.ApiKey" xml:space="preserve">API密钥</x:String>
<x:String x:Key="Text.Preference.AI.GenerateSubjectPrompt" xml:space="preserve">Generate Subject Prompt</x:String>
<x:String x:Key="Text.Preference.AI.Model" xml:space="preserve">模型</x:String>
<x:String x:Key="Text.Preference.AI.Name" xml:space="preserve">配置名称</x:String>
<x:String x:Key="Text.Preference.AI.Server" xml:space="preserve">服务地址</x:String>
<x:String x:Key="Text.Preference.Appearance" xml:space="preserve">外观配置</x:String>
<x:String x:Key="Text.Preference.Appearance.DefaultFont" xml:space="preserve">缺省字体</x:String>
<x:String x:Key="Text.Preference.Appearance.DefaultFontSize" xml:space="preserve">默认字体大小</x:String>
@ -569,6 +579,7 @@
<x:String x:Key="Text.Start" xml:space="preserve">开 始</x:String>
<x:String x:Key="Text.Stash" xml:space="preserve">贮藏(stash)</x:String>
<x:String x:Key="Text.Stash.IncludeUntracked" xml:space="preserve">包含未跟踪的文件</x:String>
<x:String x:Key="Text.Stash.KeepIndex" xml:space="preserve">保留暂存区文件</x:String>
<x:String x:Key="Text.Stash.Message" xml:space="preserve">信息 </x:String>
<x:String x:Key="Text.Stash.Message.Placeholder" xml:space="preserve">选填,用于命名此贮藏</x:String>
<x:String x:Key="Text.Stash.OnlyStagedChanges" xml:space="preserve">仅贮藏暂存区的变更</x:String>

View file

@ -72,6 +72,7 @@
<x:String x:Key="Text.Cancel" xml:space="preserve">取 消</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">重設檔案為此版本</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">重設檔案到上一版本</x:String>
<x:String x:Key="Text.ChangeCM.GenerateCommitMessage" xml:space="preserve">產生提交訊息</x:String>
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切換變更顯示模式</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">檔案名稱 + 路徑列表模式</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">全路徑列表模式</x:String>
@ -157,6 +158,9 @@
<x:String x:Key="Text.Configure.IssueTracker.RuleName" xml:space="preserve">規則名稱:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate" xml:space="preserve">為 Issue 產生的網址連結:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate.Tip" xml:space="preserve">可在網址中使用 $1、$2 等變數填入正則表示式相符的內容</x:String>
<x:String x:Key="Text.Configure.OpenAI" xml:space="preserve">OPEN AI</x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered" xml:space="preserve">启用特定服务 </x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered.Tip" xml:space="preserve">当【启用特定服务】被设置时SourceGit将在本仓库中仅使用该服务。否则将弹出可用的OpenAI服务列表供用户选择。</x:String>
<x:String x:Key="Text.Configure.Proxy" xml:space="preserve">HTTP 代理</x:String>
<x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">HTTP 網路代理</x:String>
<x:String x:Key="Text.Configure.User" xml:space="preserve">使用者名稱</x:String>
@ -404,11 +408,11 @@
<x:String x:Key="Text.Period.LastYear" xml:space="preserve">一年前</x:String>
<x:String x:Key="Text.Period.YearsAgo" xml:space="preserve">{0} 年前</x:String>
<x:String x:Key="Text.Preference" xml:space="preserve">偏好設定</x:String>
<x:String x:Key="Text.Preference.Advanced" xml:space="preserve">進階設定</x:String>
<x:String x:Key="Text.Preference.AI" xml:space="preserve">OpenAI</x:String>
<x:String x:Key="Text.Preference.AI.Server" xml:space="preserve">伺服器</x:String>
<x:String x:Key="Text.Preference.AI.ApiKey" xml:space="preserve">API 金鑰</x:String>
<x:String x:Key="Text.Preference.AI.Model" xml:space="preserve">模型</x:String>
<x:String x:Key="Text.Preference.AI.Name" xml:space="preserve">名稱</x:String>
<x:String x:Key="Text.Preference.AI.AnalyzeDiffPrompt" xml:space="preserve">分析變更差異提示詞</x:String>
<x:String x:Key="Text.Preference.AI.GenerateSubjectPrompt" xml:space="preserve">產生提交訊息提示詞</x:String>
<x:String x:Key="Text.Preference.Appearance" xml:space="preserve">外觀設定</x:String>
@ -575,6 +579,7 @@
<x:String x:Key="Text.Start" xml:space="preserve">開 始</x:String>
<x:String x:Key="Text.Stash" xml:space="preserve">擱置變更 (stash)</x:String>
<x:String x:Key="Text.Stash.IncludeUntracked" xml:space="preserve">包含未追蹤的檔案</x:String>
<x:String x:Key="Text.Stash.KeepIndex" xml:space="preserve">保留已暫存的變更</x:String>
<x:String x:Key="Text.Stash.Message" xml:space="preserve">擱置變更訊息:</x:String>
<x:String x:Key="Text.Stash.Message.Placeholder" xml:space="preserve">選填,用於命名此擱置變更</x:String>
<x:String x:Key="Text.Stash.OnlyStagedChanges" xml:space="preserve">僅擱置已暫存的變更</x:String>

View file

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
@ -25,7 +25,6 @@ namespace SourceGit.ViewModels
_instance.PrepareGit();
_instance.PrepareShellOrTerminal();
_instance.PrepareWorkspaces();
_instance.PrepareOpenAIPrompt();
return _instance;
}
@ -277,71 +276,6 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _externalMergeToolPath, value);
}
public string OpenAIServer
{
get => Models.OpenAI.Server;
set
{
if (value != Models.OpenAI.Server)
{
Models.OpenAI.Server = value;
OnPropertyChanged();
}
}
}
public string OpenAIApiKey
{
get => Models.OpenAI.ApiKey;
set
{
if (value != Models.OpenAI.ApiKey)
{
Models.OpenAI.ApiKey = value;
OnPropertyChanged();
}
}
}
public string OpenAIModel
{
get => Models.OpenAI.Model;
set
{
if (value != Models.OpenAI.Model)
{
Models.OpenAI.Model = value;
OnPropertyChanged();
}
}
}
public string OpenAIAnalyzeDiffPrompt
{
get => Models.OpenAI.AnalyzeDiffPrompt;
set
{
if (value != Models.OpenAI.AnalyzeDiffPrompt)
{
Models.OpenAI.AnalyzeDiffPrompt = value;
OnPropertyChanged();
}
}
}
public string OpenAIGenerateSubjectPrompt
{
get => Models.OpenAI.GenerateSubjectPrompt;
set
{
if (value != Models.OpenAI.GenerateSubjectPrompt)
{
Models.OpenAI.GenerateSubjectPrompt = value;
OnPropertyChanged();
}
}
}
public uint StatisticsSampleColor
{
get => _statisticsSampleColor;
@ -360,6 +294,12 @@ namespace SourceGit.ViewModels
set;
} = [];
public AvaloniaList<Models.OpenAIService> OpenAIServices
{
get;
set;
} = [];
public double LastCheckUpdateTime
{
get => _lastCheckUpdateTime;
@ -554,45 +494,6 @@ namespace SourceGit.ViewModels
}
}
private void PrepareOpenAIPrompt()
{
if (string.IsNullOrEmpty(Models.OpenAI.AnalyzeDiffPrompt))
{
Models.OpenAI.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
""";
}
if (string.IsNullOrEmpty(Models.OpenAI.GenerateSubjectPrompt))
{
Models.OpenAI.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.
""";
}
}
private RepositoryNode FindNodeRecursive(string id, List<RepositoryNode> collection)
{
foreach (var node in collection)

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
@ -108,6 +109,18 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _selectedIssueTrackerRule, value);
}
public List<string> AvailableOpenAIServices
{
get;
private set;
}
public string PreferedOpenAIService
{
get => _repo.Settings.PreferedOpenAIService;
set => _repo.Settings.PreferedOpenAIService = value;
}
public RepositoryConfigure(Repository repo)
{
_repo = repo;
@ -116,6 +129,13 @@ namespace SourceGit.ViewModels
foreach (var remote in _repo.Remotes)
Remotes.Add(remote.Name);
AvailableOpenAIServices = new List<string>() { "---" };
foreach (var service in Preference.Instance.OpenAIServices)
AvailableOpenAIServices.Add(service.Name);
if (AvailableOpenAIServices.IndexOf(PreferedOpenAIService) == -1)
PreferedOpenAIService = "---";
_cached = new Commands.Config(repo.FullPath).ListAll();
if (_cached.TryGetValue("user.name", out var name))
UserName = name;

View file

@ -18,24 +18,27 @@ namespace SourceGit.ViewModels
public bool IncludeUntracked
{
get;
set;
get => _repo.Settings.IncludeUntrackedWhenStash;
set => _repo.Settings.IncludeUntrackedWhenStash = value;
}
public bool OnlyStaged
{
get;
set;
get => _repo.Settings.OnlyStagedWhenStash;
set => _repo.Settings.OnlyStagedWhenStash = value;
}
public bool KeepIndex
{
get => _repo.Settings.KeepIndexWhenStash;
set => _repo.Settings.KeepIndexWhenStash = value;
}
public StashChanges(Repository repo, List<Models.Change> changes, bool hasSelectedFiles)
{
_repo = repo;
_changes = changes;
HasSelectedFiles = hasSelectedFiles;
IncludeUntracked = true;
OnlyStaged = false;
View = new Views.StashChanges() { DataContext = this };
}
@ -63,7 +66,7 @@ namespace SourceGit.ViewModels
return Task.Run(() =>
{
var succ = new Commands.Stash(_repo.FullPath).Push(jobs, Message, !HasSelectedFiles && OnlyStaged);
var succ = new Commands.Stash(_repo.FullPath).Push(jobs, Message, !HasSelectedFiles && OnlyStaged, KeepIndex);
CallUIThread(() =>
{
_repo.MarkWorkingCopyDirtyManually();

View file

@ -403,25 +403,6 @@ namespace SourceGit.ViewModels
}
}
public void GenerateCommitMessageByAI()
{
if (!Models.OpenAI.IsValid)
{
App.RaiseException(_repo.FullPath, "Bad configuration for OpenAI");
return;
}
if (_staged is { Count: > 0 })
{
var dialog = new Views.AIAssistant(_repo.FullPath, _staged, generated => CommitMessage = generated);
App.OpenDialog(dialog);
}
else
{
App.RaiseException(_repo.FullPath, "No files added to commit!");
}
}
public void Commit()
{
DoCommit(false, false, false);
@ -900,6 +881,44 @@ namespace SourceGit.ViewModels
return null;
var menu = new ContextMenu();
var ai = null as MenuItem;
var services = GetPreferedOpenAIServices();
if (services.Count > 0)
{
ai = new MenuItem();
ai.Icon = App.CreateMenuIcon("Icons.AIAssist");
ai.Header = App.Text("ChangeCM.GenerateCommitMessage");
if (services.Count == 1)
{
ai.Click += (_, e) =>
{
var dialog = new Views.AIAssistant(services[0], _repo.FullPath, _selectedStaged, generated => CommitMessage = generated);
App.OpenDialog(dialog);
e.Handled = true;
};
}
else
{
foreach (var service in services)
{
var dup = service;
var item = new MenuItem();
item.Header = service.Name;
item.Click += (_, e) =>
{
var dialog = new Views.AIAssistant(dup, _repo.FullPath, _selectedStaged, generated => CommitMessage = generated);
App.OpenDialog(dialog);
e.Handled = true;
};
ai.Items.Add(item);
}
}
}
if (_selectedStaged.Count == 1)
{
var change = _selectedStaged[0];
@ -980,24 +999,6 @@ namespace SourceGit.ViewModels
e.Handled = true;
};
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyPath.Click += (_, e) =>
{
App.CopyText(change.Path);
e.Handled = true;
};
var copyFileName = new MenuItem();
copyFileName.Header = App.Text("CopyFileName");
copyFileName.Icon = App.CreateMenuIcon("Icons.Copy");
copyFileName.Click += (_, e) =>
{
App.CopyText(Path.GetFileName(change.Path));
e.Handled = true;
};
menu.Items.Add(explore);
menu.Items.Add(openWith);
menu.Items.Add(new MenuItem() { Header = "-" });
@ -1089,6 +1090,30 @@ namespace SourceGit.ViewModels
menu.Items.Add(new MenuItem() { Header = "-" });
}
if (ai != null)
{
menu.Items.Add(ai);
menu.Items.Add(new MenuItem() { Header = "-" });
}
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyPath.Click += (_, e) =>
{
App.CopyText(change.Path);
e.Handled = true;
};
var copyFileName = new MenuItem();
copyFileName.Header = App.Text("CopyFileName");
copyFileName.Icon = App.CreateMenuIcon("Icons.Copy");
copyFileName.Click += (_, e) =>
{
App.CopyText(Path.GetFileName(change.Path));
e.Handled = true;
};
menu.Items.Add(copyPath);
menu.Items.Add(copyFileName);
}
@ -1142,6 +1167,12 @@ namespace SourceGit.ViewModels
menu.Items.Add(unstage);
menu.Items.Add(stash);
menu.Items.Add(patch);
if (ai != null)
{
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(ai);
}
}
return menu;
@ -1211,6 +1242,51 @@ namespace SourceGit.ViewModels
return menu;
}
public ContextMenu CreateContextForOpenAI()
{
if (_staged == null || _staged.Count == 0)
{
App.RaiseException(_repo.FullPath, "No files added to commit!");
return null;
}
var services = GetPreferedOpenAIServices();
if (services.Count == 0)
{
App.RaiseException(_repo.FullPath, "Bad configuration for OpenAI");
return null;
}
if (services.Count == 1)
{
var dialog = new Views.AIAssistant(services[0], _repo.FullPath, _staged, generated => CommitMessage = generated);
App.OpenDialog(dialog);
return null;
}
else
{
var menu = new ContextMenu() { Placement = PlacementMode.TopEdgeAlignedLeft };
foreach (var service in services)
{
var dup = service;
var item = new MenuItem();
item.Header = service.Name;
item.Click += (_, e) =>
{
var dialog = new Views.AIAssistant(dup, _repo.FullPath, _staged, generated => CommitMessage = generated);
App.OpenDialog(dialog);
e.Handled = true;
};
menu.Items.Add(item);
}
return menu;
}
}
private List<Models.Change> GetStagedChanges()
{
if (_useAmend)
@ -1363,6 +1439,25 @@ namespace SourceGit.ViewModels
return false;
}
private IList<Models.OpenAIService> GetPreferedOpenAIServices()
{
var services = Preference.Instance.OpenAIServices;
if (services == null || services.Count == 0)
return [];
if (services.Count == 1)
return services;
var prefered = _repo.Settings.PreferedOpenAIService;
foreach (var service in services)
{
if (service.Name.Equals(prefered, StringComparison.Ordinal))
return [service];
}
return services;
}
private Repository _repo = null;
private bool _isLoadingData = false;
private bool _isStaging = false;

View file

@ -17,12 +17,14 @@ namespace SourceGit.Views
InitializeComponent();
}
public AIAssistant(string repo, List<Models.Change> changes, Action<string> onDone)
public AIAssistant(Models.OpenAIService service, string repo, List<Models.Change> changes, Action<string> onDone)
{
_service = service;
_repo = repo;
_changes = changes;
_onDone = onDone;
_cancel = new CancellationTokenSource();
InitializeComponent();
}
@ -35,7 +37,7 @@ namespace SourceGit.Views
Task.Run(() =>
{
var message = new Commands.GenerateCommitMessage(_repo, _changes, _cancel.Token, SetDescription).Result();
var message = new Commands.GenerateCommitMessage(_service, _repo, _changes, _cancel.Token, SetDescription).Result();
if (_cancel.IsCancellationRequested)
return;
@ -63,6 +65,7 @@ namespace SourceGit.Views
Dispatcher.UIThread.Invoke(() => ProgressMessage.Text = message);
}
private Models.OpenAIService _service;
private string _repo;
private List<Models.Change> _changes;
private Action<string> _onDone;

View file

@ -412,7 +412,7 @@
<TextBlock Classes="bold" Margin="4,0,0,0" Text="{DynamicResource Text.Preference.DiffMerge}"/>
</StackPanel>
<Rectangle Margin="0,8" Fill="{DynamicResource Brush.Border2}" Height=".6" HorizontalAlignment="Stretch"/>
<Grid Margin="8,0,0,0" RowDefinitions="32,Auto">
<Grid Margin="8,0,0,8" RowDefinitions="32,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="IntegrationLabel"/>
<ColumnDefinition Width="*"/>
@ -459,82 +459,117 @@
</TextBox.InnerRightContent>
</TextBox>
</Grid>
<StackPanel Orientation="Horizontal" Margin="0,24,0,0">
<Path Width="12" Height="12" Data="{StaticResource Icons.AIAssist}"/>
<TextBlock Classes="bold" Margin="4,0,0,0" Text="{DynamicResource Text.Preference.AI}"/>
</StackPanel>
<Rectangle Margin="0,8" Fill="{DynamicResource Brush.Border2}" Height=".6" HorizontalAlignment="Stretch"/>
<Grid Margin="8,0,0,0" RowDefinitions="32,32,32,32,Auto,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="IntegrationLabel"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
Text="{DynamicResource Text.Preference.AI.Server}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<TextBox Grid.Row="0" Grid.Column="1"
Height="28"
CornerRadius="3"
Text="{Binding OpenAIServer, Mode=TwoWay}"/>
<TextBlock Grid.Row="1" Grid.Column="0"
Text="{DynamicResource Text.Preference.AI.Model}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<TextBox Grid.Row="1" Grid.Column="1"
Height="28"
CornerRadius="3"
Text="{Binding OpenAIModel, Mode=TwoWay}"/>
<TextBlock Grid.Row="2" Grid.Column="0"
Text="{DynamicResource Text.Preference.AI.ApiKey}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<TextBox Grid.Row="2" Grid.Column="1"
Height="28"
CornerRadius="3"
PasswordChar="*"
Text="{Binding OpenAIApiKey, Mode=TwoWay}"/>
<ToggleButton Grid.Row="3" Grid.Column="1" Classes="group_expander" x:Name="OpenAIAdvancedOptions" HorizontalAlignment="Right">
<TextBlock Margin="0" Text="{DynamicResource Text.Preference.Advanced}"/>
</ToggleButton>
<TextBlock Grid.Row="4" Grid.Column="0"
Text="{DynamicResource Text.Preference.AI.AnalyzeDiffPrompt}"
HorizontalAlignment="Right"
Margin="0,0,16,0"
IsVisible="{Binding #OpenAIAdvancedOptions.IsChecked}"/>
<TextBox Grid.Row="4" Grid.Column="1"
Height="120"
Margin="0,2"
CornerRadius="3"
VerticalContentAlignment="Top"
Text="{Binding OpenAIAnalyzeDiffPrompt, Mode=TwoWay}"
AcceptsReturn="true"
TextWrapping="Wrap"
IsVisible="{Binding #OpenAIAdvancedOptions.IsChecked}"/>
<TextBlock Grid.Row="5" Grid.Column="0"
Text="{DynamicResource Text.Preference.AI.GenerateSubjectPrompt}"
HorizontalAlignment="Right"
Margin="0,0,16,0"
IsVisible="{Binding #OpenAIAdvancedOptions.IsChecked}"/>
<TextBox Grid.Row="5" Grid.Column="1"
Height="120"
Margin="0,2"
CornerRadius="3"
VerticalContentAlignment="Top"
Text="{Binding OpenAIGenerateSubjectPrompt, Mode=TwoWay}"
AcceptsReturn="true"
TextWrapping="Wrap"
IsVisible="{Binding #OpenAIAdvancedOptions.IsChecked}"/>
</Grid>
</StackPanel>
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.AI}"/>
</TabItem.Header>
<Grid ColumnDefinitions="200,*" Margin="0,8,0,16" MinHeight="400">
<Border Grid.Column="0"
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"
Background="{DynamicResource Brush.Contents}">
<Grid RowDefinitions="*,1,Auto">
<ListBox Grid.Row="0"
Background="Transparent"
ItemsSource="{Binding OpenAIServices}"
SelectedItem="{Binding #ThisControl.SelectedOpenAIService, Mode=TwoWay}"
SelectionMode="Single">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Height" Value="26"/>
<Setter Property="Padding" Value="4,2"/>
</Style>
</ListBox.Styles>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="m:OpenAIService">
<Grid ColumnDefinitions="Auto,*">
<Path Grid.Column="0" Width="14" Height="14" Data="{StaticResource Icons.AIAssist}"/>
<TextBlock Grid.Column="1" Text="{Binding Name}" Margin="8,0" TextTrimming="CharacterEllipsis"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Rectangle Grid.Row="1" Height="1" Fill="{DynamicResource Brush.Border2}" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>
<StackPanel Grid.Row="2" Orientation="Horizontal" Background="{DynamicResource Brush.ToolBar}">
<Button Classes="icon_button" Click="OnAddOpenAIService">
<Path Width="14" Height="14" Data="{StaticResource Icons.Plus}"/>
</Button>
<Rectangle Width="1" Fill="{DynamicResource Brush.Border2}" HorizontalAlignment="Left" VerticalAlignment="Stretch"/>
<Button Classes="icon_button" Click="OnRemoveSelectedOpenAIService">
<Path Width="14" Height="14" Data="{StaticResource Icons.Window.Minimize}"/>
</Button>
<Rectangle Width="1" Fill="{DynamicResource Brush.Border2}" HorizontalAlignment="Left" VerticalAlignment="Stretch"/>
</StackPanel>
</Grid>
</Border>
<ContentControl Grid.Column="1" Margin="16,0,0,0">
<ContentControl.Content>
<Binding Path="#ThisControl.SelectedOpenAIService">
<Binding.TargetNullValue>
<Path Width="64" Height="64"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Fill="{DynamicResource Brush.FG2}"
Data="{StaticResource Icons.Empty}"/>
</Binding.TargetNullValue>
</Binding>
</ContentControl.Content>
<ContentControl.DataTemplates>
<DataTemplate DataType="m:OpenAIService">
<StackPanel Orientation="Vertical" MaxWidth="680">
<TextBlock Text="{DynamicResource Text.Preference.AI.Name}"/>
<TextBox Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding Name, Mode=TwoWay}"/>
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.Preference.AI.Server}"/>
<TextBox Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding Server, Mode=TwoWay}"/>
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.Preference.AI.Model}"/>
<TextBox Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding Model, Mode=TwoWay}"/>
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.Preference.AI.ApiKey}"/>
<TextBox Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding ApiKey, Mode=TwoWay}" PasswordChar="*"/>
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.Preference.AI.AnalyzeDiffPrompt}"/>
<TextBox Height="120"
Margin="0,4,0,0"
CornerRadius="3"
VerticalContentAlignment="Top"
Text="{Binding AnalyzeDiffPrompt, Mode=TwoWay}"
AcceptsReturn="true"
TextWrapping="Wrap"/>
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.Preference.AI.GenerateSubjectPrompt}"/>
<TextBox Height="120"
Margin="0,4,0,0"
CornerRadius="3"
VerticalContentAlignment="Top"
Text="{Binding GenerateSubjectPrompt, Mode=TwoWay}"
AcceptsReturn="true"
TextWrapping="Wrap"/>
</StackPanel>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</Grid>
</TabItem>
</TabControl>
</Border>
</Grid>

View file

@ -74,6 +74,15 @@ namespace SourceGit.Views
set;
}
public static readonly StyledProperty<Models.OpenAIService> SelectedOpenAIServiceProperty =
AvaloniaProperty.Register<Preference, Models.OpenAIService>(nameof(SelectedOpenAIService));
public Models.OpenAIService SelectedOpenAIService
{
get => GetValue(SelectedOpenAIServiceProperty);
set => SetValue(SelectedOpenAIServiceProperty, value);
}
public Preference()
{
var pref = ViewModels.Preference.Instance;
@ -312,5 +321,24 @@ namespace SourceGit.Views
e.Handled = true;
}
private void OnAddOpenAIService(object sender, RoutedEventArgs e)
{
var service = new Models.OpenAIService() { Name = "Unnamed Service" };
ViewModels.Preference.Instance.OpenAIServices.Add(service);
SelectedOpenAIService = service;
e.Handled = true;
}
private void OnRemoveSelectedOpenAIService(object sender, RoutedEventArgs e)
{
if (SelectedOpenAIService == null)
return;
ViewModels.Preference.Instance.OpenAIServices.Remove(SelectedOpenAIService);
SelectedOpenAIService = null;
e.Handled = true;
}
}
}

View file

@ -340,6 +340,39 @@
</ContentControl>
</Grid>
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Configure.OpenAI}"/>
</TabItem.Header>
<Grid Margin="16,4,16,8" RowDefinitions="32,Auto" ColumnDefinitions="Auto,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.Configure.OpenAI.Prefered}"/>
<ComboBox Grid.Row="0" Grid.Column="1"
Height="28" Padding="8,0"
VerticalAlignment="Center" HorizontalAlignment="Stretch"
ItemsSource="{Binding AvailableOpenAIServices}"
SelectedItem="{Binding PreferedOpenAIService, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20" VerticalAlignment="Center">
<Path Width="12" Height="12" Data="{StaticResource Icons.AIAssist}" Fill="{DynamicResource Brush.FG1}"/>
<TextBlock Margin="6,0,0,0" Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="1" Grid.Column="1"
Margin="0,6,0,0"
Text="{DynamicResource Text.Configure.OpenAI.Prefered.Tip}"
TextWrapping="Wrap"
Foreground="{DynamicResource Brush.FG2}"/>
</Grid>
</TabItem>
</TabControl>
</Grid>
</v:ChromelessWindow>

View file

@ -11,7 +11,7 @@
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.Stash.Title}"/>
<Grid Margin="8,16,0,0" RowDefinitions="32,Auto,Auto" ColumnDefinitions="120,*">
<Grid Margin="8,16,0,0" RowDefinitions="32,Auto,Auto,32,Auto" ColumnDefinitions="120,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right"
Margin="8,0"
@ -23,13 +23,6 @@
Watermark="{DynamicResource Text.Stash.Message.Placeholder}"
v:AutoFocusBehaviour.IsEnabled="True"/>
<TextBlock Grid.Row="1" Grid.Column="1"
Margin="0,4,0,0"
Text="{DynamicResource Text.Stash.TipForSelectedFiles}"
TextWrapping="Wrap"
Foreground="{DynamicResource Brush.FG2}"
IsVisible="{Binding HasSelectedFiles}"/>
<CheckBox Grid.Row="1" Grid.Column="1"
Height="32"
Content="{DynamicResource Text.Stash.IncludeUntracked}"
@ -41,6 +34,18 @@
Content="{DynamicResource Text.Stash.OnlyStagedChanges}"
IsChecked="{Binding OnlyStaged, Mode=TwoWay}"
IsVisible="{Binding !HasSelectedFiles}"/>
<CheckBox Grid.Row="3" Grid.Column="1"
Height="32"
Content="{DynamicResource Text.Stash.KeepIndex}"
IsChecked="{Binding KeepIndex, Mode=TwoWay}"/>
<TextBlock Grid.Row="4" Grid.Column="1"
Margin="0,4,0,0"
Text="{DynamicResource Text.Stash.TipForSelectedFiles}"
TextWrapping="Wrap"
Foreground="{DynamicResource Brush.FG2}"
IsVisible="{Binding HasSelectedFiles}"/>
</Grid>
</StackPanel>
</UserControl>

View file

@ -199,7 +199,7 @@
<Button Grid.Column="1"
Classes="icon_button"
Margin="4,2,0,0"
Command="{Binding GenerateCommitMessageByAI}"
Click="OnOpenOpenAIHelper"
ToolTip.Tip="{DynamicResource Text.AIAssistant.Tip}"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="0">

View file

@ -120,6 +120,17 @@ namespace SourceGit.Views
e.Handled = true;
}
private void OnOpenOpenAIHelper(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.WorkingCopy vm)
{
var menu = vm.CreateContextForOpenAI();
(sender as Button)?.OpenContextMenu(menu);
}
e.Handled = true;
}
private void OnOpenConventionalCommitHelper(object _, RoutedEventArgs e)
{
if (DataContext is ViewModels.WorkingCopy vm)