Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions Brainarr.Plugin/BrainarrImportListRefactored.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NzbDrone.Core.ImportLists;
using NzbDrone.Core.ImportLists.Brainarr.Services;
using NzbDrone.Core.ImportLists.Brainarr.Services.Core;
using NzbDrone.Core.ImportLists.Brainarr.Configuration;
using NzbDrone.Core.ImportLists.Brainarr.Models;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Music;
using NzbDrone.Common.Http;
using FluentValidation.Results;
using NLog;

namespace NzbDrone.Core.ImportLists.Brainarr
{
public class BrainarrRefactored : ImportListBase<BrainarrSettings>

Check failure on line 20 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 8.0.x)

The namespace 'NzbDrone.Core.ImportLists.Brainarr' already contains a definition for 'BrainarrRefactored'

Check failure on line 20 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 8.0.x)

The namespace 'NzbDrone.Core.ImportLists.Brainarr' already contains a definition for 'BrainarrRefactored'

Check failure on line 20 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 6.0.x)

The namespace 'NzbDrone.Core.ImportLists.Brainarr' already contains a definition for 'BrainarrRefactored'

Check failure on line 20 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 6.0.x)

The namespace 'NzbDrone.Core.ImportLists.Brainarr' already contains a definition for 'BrainarrRefactored'

Check failure on line 20 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (macos-latest, 8.0.x)

The namespace 'NzbDrone.Core.ImportLists.Brainarr' already contains a definition for 'BrainarrRefactored'

Check failure on line 20 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (macos-latest, 8.0.x)

The namespace 'NzbDrone.Core.ImportLists.Brainarr' already contains a definition for 'BrainarrRefactored'

Check failure on line 20 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Security Scan

The namespace 'NzbDrone.Core.ImportLists.Brainarr' already contains a definition for 'BrainarrRefactored'

Check failure on line 20 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Security Scan

The namespace 'NzbDrone.Core.ImportLists.Brainarr' already contains a definition for 'BrainarrRefactored'
{
private readonly IHttpClient _httpClient;
private readonly IArtistService _artistService;
private readonly IAlbumService _albumService;
private readonly IServiceConfiguration _services;
private readonly ISettingsActionHandler _actionHandler;
private readonly IRecommendationFetcher _fetcher;
private readonly IProviderValidator _validator;
private IAIProvider _provider;

public override string Name => "Brainarr AI Music Discovery";
public override ImportListType ListType => ImportListType.Program;
public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(6);

public BrainarrRefactored(

Check failure on line 35 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 8.0.x)

Type 'BrainarrRefactored' already defines a member called 'BrainarrRefactored' with the same parameter types

Check failure on line 35 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 8.0.x)

Type 'BrainarrRefactored' already defines a member called 'BrainarrRefactored' with the same parameter types

Check failure on line 35 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 6.0.x)

Type 'BrainarrRefactored' already defines a member called 'BrainarrRefactored' with the same parameter types

Check failure on line 35 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 6.0.x)

Type 'BrainarrRefactored' already defines a member called 'BrainarrRefactored' with the same parameter types

Check failure on line 35 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (macos-latest, 8.0.x)

Type 'BrainarrRefactored' already defines a member called 'BrainarrRefactored' with the same parameter types

Check failure on line 35 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (macos-latest, 8.0.x)

Type 'BrainarrRefactored' already defines a member called 'BrainarrRefactored' with the same parameter types

Check failure on line 35 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Security Scan

Type 'BrainarrRefactored' already defines a member called 'BrainarrRefactored' with the same parameter types

Check failure on line 35 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Security Scan

Type 'BrainarrRefactored' already defines a member called 'BrainarrRefactored' with the same parameter types
IHttpClient httpClient,
IImportListStatusService importListStatusService,
IConfigService configService,
IParsingService parsingService,
IArtistService artistService,
IAlbumService albumService,
Logger logger) : base(importListStatusService, configService, parsingService, logger)
{
_httpClient = httpClient;
_artistService = artistService;
_albumService = albumService;

_services = new ServiceConfiguration(httpClient, logger);
_actionHandler = new SettingsActionHandler(_services.ModelDetection, logger);
_fetcher = new RecommendationFetcher(_services, _artistService, _albumService, logger);
_validator = new ProviderValidator(_services, logger);
}

public override IList<ImportListItemInfo> Fetch()

Check failure on line 54 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 8.0.x)

Type 'BrainarrRefactored' already defines a member called 'Fetch' with the same parameter types

Check failure on line 54 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 8.0.x)

Type 'BrainarrRefactored' already defines a member called 'Fetch' with the same parameter types

Check failure on line 54 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 6.0.x)

Type 'BrainarrRefactored' already defines a member called 'Fetch' with the same parameter types

Check failure on line 54 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 6.0.x)

Type 'BrainarrRefactored' already defines a member called 'Fetch' with the same parameter types

Check failure on line 54 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (macos-latest, 8.0.x)

Type 'BrainarrRefactored' already defines a member called 'Fetch' with the same parameter types

Check failure on line 54 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (macos-latest, 8.0.x)

Type 'BrainarrRefactored' already defines a member called 'Fetch' with the same parameter types

Check failure on line 54 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Security Scan

Type 'BrainarrRefactored' already defines a member called 'Fetch' with the same parameter types

Check failure on line 54 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Security Scan

Type 'BrainarrRefactored' already defines a member called 'Fetch' with the same parameter types
{
return _fetcher.FetchRecommendations(Settings, Definition.Id);
}

public override object RequestAction(string action, IDictionary<string, string> query)

Check failure on line 59 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 8.0.x)

Type 'BrainarrRefactored' already defines a member called 'RequestAction' with the same parameter types

Check failure on line 59 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 6.0.x)

Type 'BrainarrRefactored' already defines a member called 'RequestAction' with the same parameter types

Check failure on line 59 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (macos-latest, 8.0.x)

Type 'BrainarrRefactored' already defines a member called 'RequestAction' with the same parameter types

Check failure on line 59 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Security Scan

Type 'BrainarrRefactored' already defines a member called 'RequestAction' with the same parameter types
{
return _actionHandler.HandleAction(action, Settings, query);
}

protected override void Test(List<ValidationFailure> failures)

Check failure on line 64 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 8.0.x)

Type 'BrainarrRefactored' already defines a member called 'Test' with the same parameter types

Check failure on line 64 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (ubuntu-latest, 6.0.x)

Type 'BrainarrRefactored' already defines a member called 'Test' with the same parameter types

Check failure on line 64 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Test & Build (macos-latest, 8.0.x)

Type 'BrainarrRefactored' already defines a member called 'Test' with the same parameter types

Check failure on line 64 in Brainarr.Plugin/BrainarrImportListRefactored.cs

View workflow job for this annotation

GitHub Actions / Security Scan

Type 'BrainarrRefactored' already defines a member called 'Test' with the same parameter types
{
_validator.ValidateProvider(Settings, failures);
}
}
}
75 changes: 75 additions & 0 deletions Brainarr.Plugin/Services/Core/ModelNameFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System.Text.RegularExpressions;

namespace NzbDrone.Core.ImportLists.Brainarr.Services.Core
{
public static class ModelNameFormatter
{
public static string FormatEnumName(string enumValue)
{
if (string.IsNullOrEmpty(enumValue)) return enumValue;

return enumValue
.Replace("_", " ")
.Replace("GPT4o", "GPT-4o")
.Replace("Claude35", "Claude 3.5")
.Replace("Claude3", "Claude 3")
.Replace("Llama33", "Llama 3.3")
.Replace("Llama32", "Llama 3.2")
.Replace("Llama31", "Llama 3.1")
.Replace("Gemini15", "Gemini 1.5")
.Replace("Gemini20", "Gemini 2.0");
}

public static string FormatModelName(string modelId)
{
if (string.IsNullOrEmpty(modelId)) return "Unknown Model";

if (modelId.Contains("/"))
{
var parts = modelId.Split('/');
if (parts.Length >= 2)
{
var org = parts[0];
var modelName = parts[1];
var cleanName = CleanModelName(modelName);
return $"{cleanName} ({org})";
}
}

if (modelId.Contains(":"))
{
var parts = modelId.Split(':');
if (parts.Length >= 2)
{
var modelName = CleanModelName(parts[0]);
var tag = parts[1];
return $"{modelName}:{tag}";
}
}

return CleanModelName(modelId);
}

private static string CleanModelName(string name)
{
if (string.IsNullOrEmpty(name)) return name;

var cleaned = name
.Replace("-", " ")
.Replace("_", " ")
.Replace(".", " ");

cleaned = Regex.Replace(cleaned, @"\bqwen\b", "Qwen", RegexOptions.IgnoreCase);
cleaned = Regex.Replace(cleaned, @"\bllama\b", "Llama", RegexOptions.IgnoreCase);
cleaned = Regex.Replace(cleaned, @"\bmistral\b", "Mistral", RegexOptions.IgnoreCase);
cleaned = Regex.Replace(cleaned, @"\bgemma\b", "Gemma", RegexOptions.IgnoreCase);
cleaned = Regex.Replace(cleaned, @"\bphi\b", "Phi", RegexOptions.IgnoreCase);
cleaned = Regex.Replace(cleaned, @"\bcoder\b", "Coder", RegexOptions.IgnoreCase);
cleaned = Regex.Replace(cleaned, @"\binstruct\b", "Instruct", RegexOptions.IgnoreCase);

cleaned = Regex.Replace(cleaned, @"\s+", " ").Trim();

return cleaned;
}
}
}
119 changes: 119 additions & 0 deletions Brainarr.Plugin/Services/Core/ProviderValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation.Results;
using NLog;
using NzbDrone.Core.ImportLists.Brainarr.Configuration;

namespace NzbDrone.Core.ImportLists.Brainarr.Services.Core
{
public class ProviderValidator : IProviderValidator
{
private readonly IServiceConfiguration _services;
private readonly Logger _logger;

public ProviderValidator(IServiceConfiguration services, Logger logger)
{
_services = services ?? throw new ArgumentNullException(nameof(services));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public void ValidateProvider(BrainarrSettings settings, List<ValidationFailure> failures)
{
try
{
var provider = _services.CreateProvider(settings);

if (provider == null)
{
failures.Add(new ValidationFailure(nameof(settings.Provider),
"AI provider not configured"));
return;
}

var connected = provider.TestConnectionAsync().GetAwaiter().GetResult();
if (!connected)
{
failures.Add(new ValidationFailure(string.Empty,
$"Cannot connect to {provider.ProviderName}"));
return;
}

ValidateLocalProviderModels(settings, failures);

_logger.Info($"Test successful: Connected to {provider.ProviderName}");
}
catch (Exception ex)
{
failures.Add(new ValidationFailure(string.Empty, $"Test failed: {ex.Message}"));
}
}

private void ValidateLocalProviderModels(BrainarrSettings settings, List<ValidationFailure> failures)
{
if (settings.Provider == AIProvider.Ollama)
{
ValidateOllamaModels(settings, failures);
}
else if (settings.Provider == AIProvider.LMStudio)
{
ValidateLMStudioModels(settings, failures);
}
else
{
_logger.Info($"✅ Connected successfully to {settings.Provider}");
}
}

private void ValidateOllamaModels(BrainarrSettings settings, List<ValidationFailure> failures)
{
var models = _services.ModelDetection.GetOllamaModelsAsync(settings.OllamaUrl)
.GetAwaiter().GetResult();

if (models.Any())
{
_logger.Info($"✅ Found {models.Count} Ollama models: {string.Join(", ", models)}");
settings.DetectedModels = models;

var topModels = models.Take(3).ToList();
var modelList = string.Join(", ", topModels);
if (models.Count > 3) modelList += $" (and {models.Count - 3} more)";

_logger.Info($"🎯 Recommended: Copy one of these models into the field above: {modelList}");
}
else
{
failures.Add(new ValidationFailure(string.Empty,
"No suitable models found. Install models like: ollama pull qwen2.5"));
}
}

private void ValidateLMStudioModels(BrainarrSettings settings, List<ValidationFailure> failures)
{
var models = _services.ModelDetection.GetLMStudioModelsAsync(settings.LMStudioUrl)
.GetAwaiter().GetResult();

if (models.Any())
{
_logger.Info($"✅ Found {models.Count} LM Studio models: {string.Join(", ", models)}");
settings.DetectedModels = models;

var topModels = models.Take(3).ToList();
var modelList = string.Join(", ", topModels);
if (models.Count > 3) modelList += $" (and {models.Count - 3} more)";

_logger.Info($"🎯 Recommended: Copy one of these models into the field above: {modelList}");
}
else
{
failures.Add(new ValidationFailure(string.Empty,
"No models loaded. Load a model in LM Studio first."));
}
}
}

public interface IProviderValidator
{
void ValidateProvider(BrainarrSettings settings, List<ValidationFailure> failures);
}
}
Loading
Loading