12 KiB
12 KiB
🚀 Шпаргалка разработчика - 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
- Написать тесты
- Обновить документацию
- Протестировать вручную
Готово к разработке! 💪
Используйте эту шпаргалку при работе с проектом.