Files
Hy2026/backend/Infrastructure/Assist/IdeaAssistant.cs
T
Rafał Tyński 127ca92f63 feat(ideas): add idea creator, grant calls and applications
Implement the idea creator and application generator: idea status workflow, social canvas card, grant call windows with 409 when closed, and an LLM-backed idea assistant with offline fallback.
2026-10-04 03:13:45 +02:00

88 lines
3.0 KiB
C#

using TarninaHub.Application.Abstractions;
using TarninaHub.Application.Dtos;
using TarninaHub.Application.Matchmaking;
namespace TarninaHub.Infrastructure.Assist;
/// <summary>
/// Idea-creator assistant. Uses the configured LLM when available and falls
/// back to deterministic, stage-aware suggestions otherwise.
/// </summary>
public sealed class IdeaAssistant : IIdeaAssistant
{
private const string SystemPrompt =
"Jesteś asystentem kreatora innowacji społecznych w Małopolskim Hubie Innowacji Społecznych. " +
"Rozwijasz pomysł mieszkańca: podajesz konkretne, praktyczne podpowiedzi prostym językiem. " +
"Nie wymyślaj faktów ani danych liczbowych.";
private readonly IIdeaService _ideaService;
private readonly ILlmProvider _llmProvider;
private readonly ILlmRequestLimiter _llmRequestLimiter;
public IdeaAssistant(
IIdeaService ideaService,
ILlmProvider llmProvider,
ILlmRequestLimiter llmRequestLimiter)
{
_ideaService = ideaService;
_llmProvider = llmProvider;
_llmRequestLimiter = llmRequestLimiter;
}
public async Task<IdeaAssistResponse?> AssistAsync(
string ideaId,
IdeaAssistRequest request,
CancellationToken cancellationToken)
{
var idea = await _ideaService.GetAsync(ideaId, cancellationToken);
if (idea is null)
{
return null;
}
var improvedDescription = idea.Description;
var fromLlm = false;
if (_llmProvider.IsConfigured && _llmRequestLimiter.TryAcquire())
{
var userPrompt =
$"Pomysł: {idea.Title}\n" +
$"Opis: {idea.Description}\n" +
$"Etap: {idea.Card?.Stage ?? "idea"}\n" +
$"Odbiorca: {idea.Card?.Audience ?? "nieokreślony"}\n" +
$"Fokus: {request.Focus ?? "ogólny"}\n" +
"Rozwiń opis pomysłu i dodaj praktyczne wskazówki.";
var completion = await _llmProvider.CompleteAsync(SystemPrompt, userPrompt, cancellationToken);
if (!string.IsNullOrWhiteSpace(completion))
{
improvedDescription = completion.Trim();
fromLlm = true;
}
}
return new IdeaAssistResponse(BuildSuggestions(idea), improvedDescription, fromLlm);
}
private static IReadOnlyList<string> BuildSuggestions(IdeaResponse idea)
{
var stage = idea.Card?.Stage ?? "idea";
var suggestions = new List<string>
{
"Doprecyzuj grupę docelową i jej konkretną potrzebę.",
"Określ, jak zmierzysz efekt (wskaźniki, liczba odbiorców).",
"Zaplanuj mały pilotaż i wskaż partnerów (NGO/JST/firma).",
$"Dopasuj opis do etapu '{stage}' i sprawdź wymagania naboru.",
};
if (!string.IsNullOrWhiteSpace(idea.Card?.Audience))
{
suggestions.Add($"Rozwiń opis odbiorcy: {idea.Card.Audience}.");
}
return suggestions;
}
}