Files
PerfReviewSummarizer/DEVELOPER_CHEATSHEET.md
ZakirovT 057e1f526b init
2026-01-22 00:45:41 +03:00

12 KiB
Raw Permalink Blame History

🚀 Шпаргалка разработчика - OpenAI Integration

Быстрые ссылки

Задача Файл Строка
Добавить OpenAI сервис Services/OpenAiService.cs -
Зарегистрировать сервис Program.cs ~7
Создать endpoint Program.cs ~84
Конфигурировать API ключ appsettings.json -
Обновить UI wwwroot/index.html ~24
Добавить JavaScript wwwroot/app.js ~20
Стилизировать wwwroot/styles.css ~276

🔑 Ключевые компоненты

1. IOpenAiService - Интерфейс

// Services/IOpenAiService.cs
public interface IOpenAiService
{
    Task<string> GenerateSummaryFromCommitsAsync(List<CommitInfo> commits);
    Task<string> GenerateSummaryFromCommitMessagesAsync(List<string> commitMessages);
}

Использование:

// Через DI
public MyClass(IOpenAiService openAiService) { ... }

// Вызов
var summary = await openAiService.GenerateSummaryFromCommitMessagesAsync(messages);

2. OpenAiService - Реализация

// Services/OpenAiService.cs
public class OpenAiService : IOpenAiService
{
    public async Task<string> GenerateSummaryFromCommitMessagesAsync(...)
    {
        // 1. Подготовка промпта
        var prompt = "Проанализируй коммиты...";
        
        // 2. Создание запроса
        var request = new ChatCompletionRequest { ... };
        
        // 3. Отправка в OpenAI
        var response = await _httpClient.PostAsync(...);
        
        // 4. Парсинг ответа
        var message = response.Content...GetString();
        
        return message;
    }
}

3. Endpoint в Program.cs

// Program.cs ~84
app.MapPost("/api/summary/ai", async (OpenAiSummaryRequest request, 
    IGitService gitService, IOpenAiService openAiService, IConfiguration config) =>
{
    // 1. Получить коммиты
    var commits = await gitService.GetCommitsAsync(...);
    
    // 2. Сгенерировать AI саммари
    var summary = await openAiService.GenerateSummaryFromCommitMessagesAsync(
        commits.Select(c => c.Message).ToList()
    );
    
    // 3. Вернуть результат
    return Results.Ok(new OpenAiSummaryResponse { ... });
})
.WithName("GetAiSummary")
.Produces<OpenAiSummaryResponse>(StatusCodes.Status200OK);

📝 Конфигурация

appsettings.json

{
  "OpenAI": {
    "ApiKey": "sk-..."  // Ваш API ключ от OpenAI
  }
}

Переменная окружения

OPENAI__APIKEY=sk-...

Проверка при запуске

var apiKey = configuration["OpenAI:ApiKey"];
if (string.IsNullOrEmpty(apiKey))
    throw new InvalidOperationException("API ключ не найден");

🔄 Процесс обработки запроса

1. POST /api/summary/ai
   ↓
2. Парсинг OpenAiSummaryRequest
   ├─ repositoryPath
   ├─ startDate
   ├─ endDate
   └─ period
   ↓
3. Вычисление дат (если period указан)
   ↓
4. GitService.GetCommitsAsync()
   └─ Возвращает List<CommitInfo>
   ↓
5. Извлечение сообщений коммитов
   └─ commitMessages: List<string>
   ↓
6. OpenAiService.GenerateSummaryFromCommitMessagesAsync()
   ├─ Создание промпта
   ├─ Отправка в OpenAI API
   └─ Парсинг ответа
   ↓
7. Создание OpenAiSummaryResponse
   ├─ summary (от GPT)
   ├─ periodInfo
   ├─ commitsCount
   └─ commitMessages
   ↓
8. Results.Ok(response)
   ↓
9. JSON Response
   ↓
10. JavaScript обработка + отображение

🌐 Frontend логика

HTML структура (index.html)

<!-- Вкладки -->
<div class="tabs">
  <button class="tab-button active" data-tab="traditional">
    Traditional Summary
  </button>
  <button class="tab-button" data-tab="ai">
    AI Summary (OpenAI)
  </button>
</div>

<!-- Формы (скрыты/показаны в зависимости от вкладки) -->
<form id="aiSummaryForm" class="tab-content" data-tab="ai">
  <!-- Поля формы -->
</form>

<!-- Результаты -->
<div id="aiSummaryResult">
  <div id="aiSummaryText"><!-- Резюме от GPT --></div>
  <p id="aiCommitsCount"><!-- Кол-во коммитов --></p>
</div>

JavaScript обработка (app.js)

// Переключение табов
function switchTab(tabName) {
  // Скрыть все контенты
  // Показать выбранный контент
  // Выделить активную кнопку
}

// Обработка формы AI Summary
aiForm.addEventListener('submit', async (e) => {
  e.preventDefault();
  
  // Собрать данные формы
  const data = {
    repositoryPath: document.getElementById('aiRepositoryPath').value,
    period: document.getElementById('aiPeriod').value,
    // ...
  };
  
  // Отправить POST запрос
  const response = await fetch(`${API_BASE_URL}/api/summary/ai`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  });
  
  // Обработать результат
  const result = await response.json();
  displayResults(result, true); // true = это AI результат
});

// Отображение результатов
function displayResults(data, isAiSummary) {
  if (isAiSummary) {
    document.getElementById('aiSummaryText').innerHTML = 
      escapeHtml(data.summary);
    document.getElementById('aiCommitsCount').textContent = 
      data.commitsCount;
  }
}

CSS стили (styles.css)

/* Вкладки */
.tabs { display: flex; border-bottom: 2px solid #e0e0e0; }
.tab-button { padding: 12px 20px; cursor: pointer; }
.tab-button.active { color: #667eea; border-bottom: 3px solid #667eea; }

/* Контент */
.tab-content { display: none; }
.tab-content.active { display: block; }

/* AI Саммари блок */
.ai-summary-box {
  background: linear-gradient(135deg, #f0f4ff 0%, #f5f0ff 100%);
  border: 2px solid #667eea;
  border-radius: 8px;
  padding: 25px;
  margin-bottom: 30px;
}

.ai-summary-text {
  background: white;
  padding: 20px;
  border-radius: 6px;
  line-height: 1.8;
  color: #333;
  font-size: 1em;
}

.commits-count {
  margin-top: 15px;
  text-align: right;
  color: #666;
  font-size: 0.95em;
}

🧪 Тестирование компонентов

Тест 1: Проверить сервис

// Unit test
[Fact]
public async Task GenerateSummaryFromCommitMessages_ShouldReturnString()
{
    // Arrange
    var service = new OpenAiService(config);
    var messages = new List<string> { "Fix: bug", "Feat: feature" };
    
    // Act
    var result = await service.GenerateSummaryFromCommitMessagesAsync(messages);
    
    // Assert
    Assert.NotEmpty(result);
    Assert.Contains("текст", result); // На русском
}

Тест 2: Проверить endpoint

// Integration test
[Fact]
public async Task PostAiSummary_ShouldReturn200WithValidRequest()
{
    // Arrange
    var client = factory.CreateClient();
    var request = new { period = "quarter" };
    
    // Act
    var response = await client.PostAsJsonAsync("/api/summary/ai", request);
    
    // Assert
    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

Тест 3: Проверить API вызов

curl -X POST http://localhost:5000/api/summary/ai \
  -H "Content-Type: application/json" \
  -d '{"period":"quarter"}'

🐛 Отладка

Логирование в OpenAiService

_logger.LogDebug("Отправляю запрос в OpenAI API...");
_logger.LogDebug("Модель: gpt-3.5-turbo, Токены: 1000");
_logger.LogDebug("Промпт: {prompt}", prompt);

var response = await _httpClient.PostAsync(url, content);

if (!response.IsSuccessStatusCode)
    _logger.LogError("Ошибка OpenAI: {status} - {error}", 
        response.StatusCode, await response.Content.ReadAsStringAsync());

Отладка в JavaScript

console.log('Отправляю запрос:', data);

fetch(url, options)
  .then(r => {
    console.log('Статус:', r.status);
    return r.json();
  })
  .then(data => {
    console.log('Результат:', data);
  })
  .catch(e => console.error('Ошибка:', e));

Проверка конфигурации

# PowerShell
$config = Get-Content appsettings.json | ConvertFrom-Json
Write-Host $config.OpenAI.ApiKey

# Linux
grep -A 2 '"OpenAI"' appsettings.json

🔐 Безопасность

Правильно:

// API ключ из конфигурации
var apiKey = configuration["OpenAI:ApiKey"];

// Валидация
if (string.IsNullOrEmpty(apiKey))
    throw new InvalidOperationException("API ключ не найден");

// Использование
_httpClient.DefaultRequestHeaders.Authorization = 
    new AuthenticationHeaderValue("Bearer", apiKey);

Неправильно:

// Не вставляйте ключ в код!
var apiKey = "sk-xxx...";

// Не отправляйте в клиент!
return Ok(new { apiKey = apiKey });

// Не логируйте!
Console.WriteLine($"API ключ: {apiKey}");

📈 Оптимизация

Кэширование результатов (можно добавить)

// Добавить в памяти кэш
private readonly IMemoryCache _cache;

// Проверить кэш перед запросом
var cacheKey = $"ai_summary_{repositoryPath}_{period}";
if (_cache.TryGetValue(cacheKey, out string cachedResult))
    return cachedResult;

// Сохранить в кэш
_cache.Set(cacheKey, result, TimeSpan.FromHours(1));

Параллельная обработка

// Если много коммитов, разбить на части
var batches = commitMessages
    .Chunk(50) // По 50 коммитов
    .ToList();

var summaries = await Task.WhenAll(
    batches.Select(b => GenerateSummary(b))
);

📚 Документация API

Swagger автоматически генерирует из атрибутов:

app.MapPost("/api/summary/ai", ...)
    .WithName("GetAiSummary")
    .WithOpenApi()
    .Produces<OpenAiSummaryResponse>(StatusCodes.Status200OK)
    .Produces(StatusCodes.Status400BadRequest)
    .Produces(StatusCodes.Status500InternalServerError);

🚀 Развёртывание

Docker

FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app/publish .
ENV OPENAI__APIKEY=${OPENAI_API_KEY}
ENTRYPOINT ["dotnet", "PerfReviewSummarizer.Api.dll"]

Environment Variables (продакшн)

export OPENAI__APIKEY=sk-...
export ASPNETCORE_ENVIRONMENT=Production
dotnet PerfReviewSummarizer.Api.dll

🎯 Чек-лист добавления новой функции

  • Создать интерфейс (ISomethingService.cs)
  • Реализовать класс (SomethingService.cs)
  • Зарегистрировать в Program.cs
  • Создать модели данных если нужны
  • Добавить endpoint в Program.cs
  • Обновить UI в index.html
  • Добавить JavaScript логику в app.js
  • Добавить CSS стили в styles.css
  • Написать тесты
  • Обновить документацию
  • Протестировать вручную

Готово к разработке! 💪

Используйте эту шпаргалку при работе с проектом.