| | | 1 | | using UIBlazor.Services.Settings; |
| | | 2 | | |
| | | 3 | | namespace UIBlazor.Services; |
| | | 4 | | |
| | 11 | 5 | | public class SystemPromptBuilder( |
| | 11 | 6 | | ICommonSettingsProvider commonSettingsProvider, |
| | 11 | 7 | | IProfileManager profileManager, |
| | 11 | 8 | | IToolManager toolManager, |
| | 11 | 9 | | ISkillService skillService, |
| | 11 | 10 | | IRuleService ruleService, |
| | 11 | 11 | | IVsCodeContextService vsCodeContextService) : ISystemPromptBuilder |
| | | 12 | | { |
| | 8 | 13 | | public ConnectionProfile Options => profileManager.ActiveProfile; |
| | | 14 | | |
| | | 15 | | public async Task<string> PrepareSystemPromptAsync(AppMode mode, CancellationToken cancellationToken) |
| | | 16 | | { |
| | | 17 | | // Загружаем метаданные скиллов и добавляем в системный промпт |
| | 7 | 18 | | var skillsMetadata = await skillService.GetSkillsMetadataAsync(cancellationToken); |
| | 7 | 19 | | var skillsSection = skillService.FormatSkillsForSystemPrompt(skillsMetadata); |
| | | 20 | | |
| | 7 | 21 | | var contextSection = new StringBuilder(); |
| | 7 | 22 | | var currentContext = vsCodeContextService.CurrentContext; |
| | 7 | 23 | | if (currentContext != null) |
| | | 24 | | { |
| | 4 | 25 | | var codeContext = new List<string>(); |
| | 4 | 26 | | if (commonSettingsProvider.Current.SendSolutionsStricture && currentContext.SolutionFiles.Count > 0) |
| | | 27 | | { |
| | 2 | 28 | | codeContext.Add($""" |
| | 2 | 29 | | Solution structure: |
| | 2 | 30 | | ``` |
| | 2 | 31 | | {BuildSolutionFiles(currentContext, true)} |
| | 2 | 32 | | ``` |
| | 2 | 33 | | """); |
| | | 34 | | } |
| | 4 | 35 | | if (commonSettingsProvider.Current.SendCurrentFile && !string.IsNullOrEmpty(currentContext.ActiveFilePath)) |
| | | 36 | | { |
| | 2 | 37 | | codeContext.Add($""" |
| | 2 | 38 | | ## Current (active) file |
| | 2 | 39 | | - Path: {currentContext.ActiveFilePath} |
| | 2 | 40 | | - Selected lines: {currentContext.SelectionStartLine} - {currentContext.SelectionEndLine |
| | 2 | 41 | | ``` |
| | 2 | 42 | | {currentContext.ActiveFileContent} |
| | 2 | 43 | | ``` |
| | 2 | 44 | | """); |
| | | 45 | | } |
| | 4 | 46 | | if (codeContext.Count > 0) |
| | | 47 | | { |
| | 3 | 48 | | contextSection.AppendLine("# CURRENT CODE CONTEXT"); |
| | 14 | 49 | | foreach (var item in codeContext) |
| | | 50 | | { |
| | 4 | 51 | | contextSection.AppendLine(item); |
| | | 52 | | } |
| | | 53 | | } |
| | | 54 | | } |
| | | 55 | | |
| | | 56 | | // Загружаем правила |
| | 7 | 57 | | var rules = await ruleService.GetRulesAsync(cancellationToken); |
| | | 58 | | // файл agents.md |
| | 7 | 59 | | var agents = await ruleService.GetAgentsMdAsync(cancellationToken); |
| | | 60 | | |
| | 7 | 61 | | List<string?> systemPromptBlocks = [Options.SystemPrompt, |
| | 7 | 62 | | toolManager.GetToolUseSystemInstructions(mode, skillsMetadata.Count != 0), |
| | 7 | 63 | | skillsSection, |
| | 7 | 64 | | contextSection.ToString(), |
| | 7 | 65 | | rules, |
| | 7 | 66 | | !string.IsNullOrEmpty(agents) ? string.Join("# Agents instructions\n", agents) : null, |
| | 7 | 67 | | $"Current date: {DateTime.Now:f}"]; |
| | | 68 | | |
| | 56 | 69 | | return string.Join(Environment.NewLine, systemPromptBlocks.Where(b => !string.IsNullOrEmpty(b))); |
| | 7 | 70 | | } |
| | | 71 | | |
| | | 72 | | public string BuildSolutionFiles(VsCodeContext currentContext, bool compress) |
| | | 73 | | { |
| | 5 | 74 | | var sb = new StringBuilder(); |
| | 5 | 75 | | var lastDir = string.Empty; |
| | 5 | 76 | | var difPrefix = VsCodeContext.DirPrefix.AsSpan(); |
| | 52 | 77 | | foreach (var item in currentContext.SolutionFiles) |
| | | 78 | | { |
| | 21 | 79 | | if (compress) |
| | | 80 | | { |
| | 21 | 81 | | var itemSpan = item.AsSpan(); |
| | 21 | 82 | | var pathIndex = -1; |
| | 21 | 83 | | if (item.StartsWith("Project")) |
| | | 84 | | { |
| | 2 | 85 | | lastDir = currentContext.SolutionPath; |
| | | 86 | | } |
| | | 87 | | else |
| | | 88 | | { |
| | 19 | 89 | | pathIndex = item.IndexOf(VsCodeContext.DirPrefix); |
| | | 90 | | } |
| | | 91 | | |
| | 21 | 92 | | if (pathIndex != -1) |
| | | 93 | | { |
| | | 94 | | // Берем часть после префикса и обрезаем пробелы без создания строк |
| | 7 | 95 | | var pathPart = itemSpan[(pathIndex + difPrefix.Length)..].TrimStart(); |
| | 7 | 96 | | lastDir = pathPart.ToString(); |
| | | 97 | | // В строке с префиксом (папкой) выводим item целиком |
| | 7 | 98 | | sb.Append(item).Append('\n'); |
| | | 99 | | } |
| | | 100 | | else |
| | | 101 | | { |
| | 14 | 102 | | var simplified = false; |
| | 14 | 103 | | if (!string.IsNullOrEmpty(lastDir)) |
| | | 104 | | { |
| | | 105 | | // Ищем, где в строке файла начинается путь. |
| | | 106 | | // Если формат файла похож на папку (есть какой-то отступ/префикс), |
| | | 107 | | // нужно найти индекс начала пути. Допустим, он всегда после какого-то символа |
| | | 108 | | // или просто ищем вхождение lastDir. |
| | 12 | 109 | | var dirPos = item.IndexOf(lastDir); |
| | 12 | 110 | | if (dirPos != -1) |
| | | 111 | | { |
| | | 112 | | // Пишем всё ДО пути + сам файл ПОСЛЕ пути |
| | 11 | 113 | | sb.Append(itemSpan[..dirPos]) |
| | 11 | 114 | | .Append(itemSpan[(dirPos + lastDir.Length + (lastDir[^1] == '\\' ? 0 : 1))..]) |
| | 11 | 115 | | .Append('\n'); |
| | 11 | 116 | | simplified = true; |
| | | 117 | | } |
| | | 118 | | } |
| | | 119 | | |
| | 14 | 120 | | if (!simplified) |
| | | 121 | | { |
| | 3 | 122 | | sb.Append(item).Append('\n'); |
| | | 123 | | } |
| | | 124 | | } |
| | | 125 | | } |
| | | 126 | | else |
| | | 127 | | { |
| | 0 | 128 | | sb.Append(item).Append('\n'); |
| | | 129 | | } |
| | | 130 | | } |
| | | 131 | | |
| | 5 | 132 | | return sb.ToString(); |
| | | 133 | | } |
| | | 134 | | } |