diff --git a/Examples/Examples/Chat/ChatExample.cs b/Examples/Examples/Chat/ChatExample.cs index 3d97ccf..989cac6 100644 --- a/Examples/Examples/Chat/ChatExample.cs +++ b/Examples/Examples/Chat/ChatExample.cs @@ -12,6 +12,7 @@ public async Task Start() // Using strongly-typed model await AIHub.Chat() .WithModel() + .EnsureModelDownloaded() .WithMessage("Where do hedgehogs goes at night?") .CompleteAsync(interactive: true); } diff --git a/src/MaIN.Core/Hub/Contexts/AgentContext.cs b/src/MaIN.Core/Hub/Contexts/AgentContext.cs index b9fdd09..f22e961 100644 --- a/src/MaIN.Core/Hub/Contexts/AgentContext.cs +++ b/src/MaIN.Core/Hub/Contexts/AgentContext.cs @@ -1,16 +1,17 @@ using MaIN.Core.Hub.Contexts.Interfaces.AgentContext; +using MaIN.Core.Hub.Utils; using MaIN.Domain.Configuration; using MaIN.Domain.Entities; using MaIN.Domain.Entities.Agents; using MaIN.Domain.Entities.Agents.AgentSource; -using MaIN.Domain.Models; -using MaIN.Services.Services.Abstract; -using MaIN.Services.Services.Models; -using MaIN.Core.Hub.Utils; using MaIN.Domain.Entities.Agents.Knowledge; using MaIN.Domain.Entities.Tools; using MaIN.Domain.Exceptions.Agents; +using MaIN.Domain.Models; +using MaIN.Domain.Models.Abstract; using MaIN.Services.Constants; +using MaIN.Services.Services.Abstract; +using MaIN.Services.Services.Models; namespace MaIN.Core.Hub.Contexts; @@ -20,6 +21,7 @@ public sealed class AgentContext : IAgentBuilderEntryPoint, IAgentConfigurationB private InferenceParams? _inferenceParams; private MemoryParams? _memoryParams; private bool _disableCache; + private bool _ensureModelDownloaded; private readonly Agent _agent; internal Knowledge? _knowledge; @@ -60,8 +62,8 @@ internal AgentContext(IAgentService agentService, Agent existingAgent) public async Task GetAgentById(string id) => await _agentService.GetAgentById(id); public async Task Delete() => await _agentService.DeleteAgent(_agent.Id); public async Task Exists() => await _agentService.AgentExists(_agent.Id); - - + + public IAgentConfigurationBuilder WithModel(string model) { _agent.Model = model; @@ -82,18 +84,18 @@ public async Task FromExisting(string agentId) { throw new AgentNotFoundException(agentId); } - + var context = new AgentContext(_agentService, existingAgent); context.LoadExistingKnowledgeIfExists(); return context; } - + public IAgentConfigurationBuilder WithInitialPrompt(string prompt) { _agent.Context.Instruction = prompt; return this; } - + public IAgentConfigurationBuilder WithId(string id) { _agent.Id = id; @@ -112,6 +114,12 @@ public IAgentConfigurationBuilder DisableCache() return this; } + public IAgentConfigurationBuilder EnsureModelDownloaded() + { + _ensureModelDownloaded = true; + return this; + } + public IAgentConfigurationBuilder WithSource(IAgentSource source, AgentSourceType type) { _agent.Context.Source = new AgentSource() @@ -121,7 +129,7 @@ public IAgentConfigurationBuilder WithSource(IAgentSource source, AgentSourceTyp }; return this; } - + public IAgentConfigurationBuilder WithName(string name) { _agent.Name = name; @@ -143,7 +151,7 @@ public IAgentConfigurationBuilder WithMcpConfig(Mcp mcpConfig) _agent.Context.McpConfig = mcpConfig; return this; } - + public IAgentConfigurationBuilder WithInferenceParams(InferenceParams inferenceParams) { _inferenceParams = inferenceParams; @@ -174,7 +182,7 @@ public IAgentConfigurationBuilder WithKnowledge(KnowledgeBuilder knowledge) _knowledge = knowledge.ForAgent(_agent).Build(); return this; } - + public IAgentConfigurationBuilder WithKnowledge(Knowledge knowledge) { _knowledge = knowledge; @@ -189,7 +197,7 @@ public IAgentConfigurationBuilder WithInMemoryKnowledge(Func(); @@ -200,10 +208,19 @@ public IAgentConfigurationBuilder WithBehaviour(string name, string instruction) public async Task CreateAsync(bool flow = false, bool interactiveResponse = false) { + if (_ensureModelDownloaded && !string.IsNullOrWhiteSpace(_agent.Model)) + { + var model = ModelRegistry.GetById(_agent.Model); + if (model is LocalModel) + { + await AIHub.Model().EnsureDownloadedAsync(_agent.Model); + } + } + await _agentService.CreateAgent(_agent, flow, interactiveResponse, _inferenceParams, _memoryParams, _disableCache); return this; } - + public IAgentContextExecutor Create(bool flow = false, bool interactiveResponse = false) { _ = _agentService.CreateAgent(_agent, flow, interactiveResponse, _inferenceParams, _memoryParams, _disableCache).Result; @@ -215,7 +232,7 @@ public IAgentConfigurationBuilder WithTools(ToolsConfiguration toolsConfiguratio _agent.ToolsConfiguration = toolsConfiguration; return this; } - + internal void LoadExistingKnowledgeIfExists() { _knowledge ??= new Knowledge(_agent); @@ -229,7 +246,7 @@ internal void LoadExistingKnowledgeIfExists() Console.WriteLine("Knowledge cannot be loaded - new one will be created"); } } - + public async Task ProcessAsync(Chat chat, bool translate = false) { if (_knowledge == null) @@ -247,7 +264,7 @@ public async Task ProcessAsync(Chat chat, bool translate = false) CreatedAt = DateTime.Now }; } - + public async Task ProcessAsync( string message, bool translate = false, @@ -276,8 +293,8 @@ public async Task ProcessAsync( CreatedAt = DateTime.Now }; } - - public async Task ProcessAsync(Message message, + + public async Task ProcessAsync(Message message, bool translate = false, Func? tokenCallback = null, Func? toolCallback = null) @@ -288,7 +305,7 @@ public async Task ProcessAsync(Message message, } var chat = await _agentService.GetChatByAgent(_agent.Id); chat.Messages.Add(message); - var result = await _agentService.Process(chat, _agent.Id, _knowledge, translate, tokenCallback, toolCallback);; + var result = await _agentService.Process(chat, _agent.Id, _knowledge, translate, tokenCallback, toolCallback); var messageResult = result.Messages.LastOrDefault()!; return new ChatResult() { @@ -298,7 +315,7 @@ public async Task ProcessAsync(Message message, CreatedAt = DateTime.Now }; } - + public async Task ProcessAsync( IEnumerable messages, bool translate = false, @@ -317,7 +334,7 @@ public async Task ProcessAsync( chat.Messages.Add(systemMsg); } chat.Messages.AddRange(messages); - var result = await _agentService.Process(chat, _agent.Id, _knowledge, translate, tokenCallback, toolCallback);; + var result = await _agentService.Process(chat, _agent.Id, _knowledge, translate, tokenCallback, toolCallback); var messageResult = result.Messages.LastOrDefault()!; return new ChatResult() { @@ -335,7 +352,7 @@ public static async Task FromExisting(IAgentService agentService, { throw new AgentNotFoundException(agentId); } - + var context = new AgentContext(agentService, existingAgent); context.LoadExistingKnowledgeIfExists(); return context; @@ -345,8 +362,8 @@ public static async Task FromExisting(IAgentService agentService, public static class AgentExtensions { public static async Task ProcessAsync( - this Task agentTask, - string message, + this Task agentTask, + string message, bool translate = false) { var agent = await agentTask; diff --git a/src/MaIN.Core/Hub/Contexts/ChatContext.cs b/src/MaIN.Core/Hub/Contexts/ChatContext.cs index 01bc947..5a369dd 100644 --- a/src/MaIN.Core/Hub/Contexts/ChatContext.cs +++ b/src/MaIN.Core/Hub/Contexts/ChatContext.cs @@ -18,6 +18,7 @@ public sealed class ChatContext : IChatBuilderEntryPoint, IChatMessageBuilder, I { private readonly IChatService _chatService; private bool _preProcess; + private bool _ensureModelDownloaded; private readonly Chat _chat; private List _files = []; @@ -88,7 +89,13 @@ public IChatMessageBuilder EnableVisual() _chat.Visual = true; return this; } - + + public IChatMessageBuilder EnsureModelDownloaded() + { + _ensureModelDownloaded = true; + return this; + } + public IChatConfigurationBuilder WithInferenceParams(InferenceParams inferenceParams) { _chat.InterferenceParams = inferenceParams; @@ -194,7 +201,7 @@ public IChatConfigurationBuilder DisableCache() _chat.Properties.AddProperty(ServiceConstants.Properties.DisableCacheProperty); return this; } - + public async Task CompleteAsync( bool translate = false, // Move to WithTranslate bool interactive = false, // Move to WithInteractive @@ -208,13 +215,18 @@ public async Task CompleteAsync( { throw new EmptyChatException(_chat.Id); } - + + if (_ensureModelDownloaded && _chat.ModelInstance is LocalModel) + { + await AIHub.Model().EnsureDownloadedAsync(_chat.ModelId); + } + _chat.Messages.Last().Files = _files; - if(_preProcess) + if (_preProcess) { _chat.Messages.Last().Properties.AddProperty(ServiceConstants.Properties.PreProcessProperty); } - + if (!await ChatExists(_chat.Id)) { await _chatService.Create(_chat); @@ -227,8 +239,8 @@ public async Task CompleteAsync( public async Task FromExisting(string chatId) { var existing = await _chatService.GetById(chatId); - return existing == null - ? throw new ChatNotFoundException(chatId) + return existing == null + ? throw new ChatNotFoundException(chatId) : new ChatContext(_chatService, existing); } @@ -244,12 +256,9 @@ private async Task ChatExists(string id) return false; } } - - IChatMessageBuilder IChatMessageBuilder.EnableVisual() => EnableVisual(); - public string GetChatId() => _chat.Id; - + public async Task GetCurrentChat() { if (_chat.Id == null) @@ -271,7 +280,7 @@ public async Task DeleteChat() await _chatService.Delete(_chat.Id); } - + public List GetChatHistory() { return [.. _chat.Messages.Select(x => new MessageShort() diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentConfigurationBuilder.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentConfigurationBuilder.cs index 9ff1218..d5fda2d 100644 --- a/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentConfigurationBuilder.cs +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentConfigurationBuilder.cs @@ -9,6 +9,14 @@ namespace MaIN.Core.Hub.Contexts.Interfaces.AgentContext; public interface IAgentConfigurationBuilder : IAgentActions { + /// + /// Flags the agent to automatically ensure the selected local model is downloaded before creation. + /// If the model is already present the download is skipped; cloud models are silently ignored. + /// The actual download is deferred until is called. + /// + /// The context instance implementing for method chaining. + IAgentConfigurationBuilder EnsureModelDownloaded(); + /// /// Sets the initial prompt for the agent. This prompt serves as an instruction or context that guides the agent's behavior during its execution. /// diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatMessageBuilder.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatMessageBuilder.cs index 4896169..396349f 100644 --- a/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatMessageBuilder.cs +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatMessageBuilder.cs @@ -10,6 +10,14 @@ public interface IChatMessageBuilder : IChatActions /// /// The context instance implementing for method chaining. IChatMessageBuilder EnableVisual(); + + /// + /// Flags the chat to automatically ensure the selected local model is downloaded before completing. + /// If the model is already present the download is skipped; cloud models are silently ignored. + /// The actual download is deferred until is called. + /// + /// The context instance implementing for method chaining. + IChatMessageBuilder EnsureModelDownloaded(); /// /// Adds a user message to the chat. This method captures the message content and assigns the "User" role to it. diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/ModelContext/IModelContext.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/ModelContext/IModelContext.cs index 8e2255f..05a7929 100644 --- a/src/MaIN.Core/Hub/Contexts/Interfaces/ModelContext/IModelContext.cs +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/ModelContext/IModelContext.cs @@ -39,7 +39,7 @@ public interface IModelContext /// /// The id of the model to check for existence. /// A boolean value indicating whether the model file exists locally. - bool Exists(string modelId); + bool IsDownloaded(string modelId); /// /// Asynchronously downloads a known model from its configured download URL. This method handles the complete download process @@ -52,33 +52,27 @@ public interface IModelContext Task DownloadAsync(string modelId, CancellationToken cancellationToken = default); /// - /// Asynchronously downloads a custom model from a specified URL. This method allows downloading models that are not part - /// of the known models collection, adding them to the system after download. + /// Ensures a known local model is downloaded before use. If the model is already present on disk the call + /// returns immediately; if not, the model is downloaded. Cloud models are silently skipped. + /// Thread-safe: concurrent calls for the same model will not trigger duplicate downloads. /// - /// The name to assign to the downloaded model. - /// The URL from which to download the model. - /// A task that represents the asynchronous download operation that completes when the download finishes, - /// returning the context instance implementing for method chaining. - Task DownloadAsync(string model, string url, CancellationToken cancellationToken = default); - - /// - /// Synchronously downloads a known model from its configured download URL. This is the blocking version of the download operation - /// with progress tracking. - /// - /// The name of the model to download. - /// The context instance implementing for method chaining. - [Obsolete("Use DownloadAsync instead")] - IModelContext Download(string modelName); + /// The id of the model to ensure is downloaded. + /// Optional cancellation token to abort the download operation. + /// A task that represents the asynchronous operation, returning the context instance implementing + /// for method chaining. + Task EnsureDownloadedAsync(string modelId, CancellationToken cancellationToken = default); /// - /// Synchronously downloads a custom model from a specified URL. This method provides blocking download functionality - /// for custom models not in the known models collection. + /// Ensures a known local model is downloaded before use using a strongly-typed model reference. + /// If the model is already present on disk the call returns immediately; if not, the model is downloaded. + /// Cloud models are silently skipped. + /// Thread-safe: concurrent calls for the same model will not trigger duplicate downloads. /// - /// The name to assign to the downloaded model. - /// The URL from which to download the model. - /// The context instance implementing for method chaining. - [Obsolete("Use DownloadAsync instead")] - IModelContext Download(string model, string url); + /// A type with a parameterless constructor. + /// Optional cancellation token to abort the download operation. + /// A task that represents the asynchronous operation, returning the context instance implementing + /// for method chaining. + Task EnsureDownloadedAsync(CancellationToken cancellationToken = default) where TModel : LocalModel, new(); /// /// Loads a model into the memory cache for faster access during inference operations. This method preloads the model to avoid loading diff --git a/src/MaIN.Core/Hub/Contexts/ModelContext.cs b/src/MaIN.Core/Hub/Contexts/ModelContext.cs index 0ec36b8..ab3a5f3 100644 --- a/src/MaIN.Core/Hub/Contexts/ModelContext.cs +++ b/src/MaIN.Core/Hub/Contexts/ModelContext.cs @@ -1,5 +1,4 @@ -using System.Diagnostics; -using System.Net; +using AsyncKeyedLock; using MaIN.Core.Hub.Contexts.Interfaces.ModelContext; using MaIN.Domain.Configuration; using MaIN.Domain.Exceptions.Models; @@ -8,13 +7,16 @@ using MaIN.Domain.Models.Concrete; using MaIN.Services.Constants; using MaIN.Services.Services.LLMService.Utils; +using System.Diagnostics; namespace MaIN.Core.Hub.Contexts; public sealed class ModelContext : IModelContext { - private readonly MaINSettings _settings; private readonly IHttpClientFactory _httpClientFactory; + private readonly string? _defaultModelsPath; + + private readonly AsyncKeyedLocker _downloadLocks = new(); private const int DefaultBufferSize = 8192; private const int FileStreamBufferSize = 65536; @@ -23,8 +25,8 @@ public sealed class ModelContext : IModelContext internal ModelContext(MaINSettings settings, IHttpClientFactory httpClientFactory) { - _settings = settings ?? throw new ArgumentNullException(nameof(settings)); _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); + _defaultModelsPath = Environment.GetEnvironmentVariable("MaIN_ModelsPath") ?? settings.ModelsPath; } public IEnumerable GetAll() => ModelRegistry.GetAll(); @@ -35,7 +37,7 @@ internal ModelContext(MaINSettings settings, IHttpClientFactory httpClientFactor public AIModel GetEmbeddingModel() => new Nomic_Embedding(); - public bool Exists(string modelId) + public bool IsDownloaded(string modelId) { if (string.IsNullOrWhiteSpace(modelId)) { @@ -47,143 +49,101 @@ public bool Exists(string modelId) { return false; // Cloud models don't have local files } - - var modelPath = GetModelFilePath(localModel.FileName); - return File.Exists(modelPath); + + return localModel.IsDownloaded(_defaultModelsPath); } - public async Task DownloadAsync(string modelId, CancellationToken cancellationToken = default) + public async Task EnsureDownloadedAsync(string modelId, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(modelId)) { throw new MissingModelIdException(nameof(modelId)); } - var model = ModelRegistry.GetById(modelId) ?? throw new ModelNotSupportedException(modelId); - - if (model is not LocalModel localModel) - { - throw new InvalidModelTypeException(nameof(LocalModel)); - } - - if (localModel.DownloadUrl is null) - { - throw new DownloadUrlNullOrEmptyException(); - } - - await DownloadModelAsync(localModel.DownloadUrl.ToString(), localModel.FileName, cancellationToken); - return this; - } + var model = ModelRegistry.GetById(modelId); - public async Task DownloadAsync(string modelId, string url, CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(modelId)) + if (model is not LocalModel localModel || localModel.IsDownloaded(_defaultModelsPath)) { - throw new MissingModelIdException(nameof(modelId)); + return this; } - if (string.IsNullOrWhiteSpace(url)) + using (await _downloadLocks.LockAsync(modelId, cancellationToken)) { - throw new DownloadUrlNullOrEmptyException(); + // Double-check + if (!localModel.IsDownloaded(_defaultModelsPath)) + { + await DownloadModelAsync(localModel, cancellationToken); + } } - var fileName = $"{modelId}.gguf"; - await DownloadModelAsync(url, fileName, cancellationToken); - - // Register the newly downloaded model - var newModel = new GenericLocalModel( - FileName: fileName, - Name: modelId, - Id: modelId, - DownloadUrl: new Uri(url) - ); - ModelRegistry.RegisterOrReplace(newModel); - return this; } - [Obsolete("Use async method instead")] - public IModelContext Download(string modelId) + public async Task EnsureDownloadedAsync(CancellationToken cancellationToken = default) where TModel : LocalModel, new() + { + var model = new TModel(); + return await EnsureDownloadedAsync(model.Id, cancellationToken); + } + + public async Task DownloadAsync(string modelId, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(modelId)) { throw new MissingModelIdException(nameof(modelId)); } - var model = ModelRegistry.GetById(modelId) ?? throw new ModelNotSupportedException(modelId); + var model = ModelRegistry.GetById(modelId); + if (model is not LocalModel localModel) { throw new InvalidModelTypeException(nameof(LocalModel)); } - if (localModel.DownloadUrl is null) + using (await _downloadLocks.LockAsync(modelId, cancellationToken)) { - throw new DownloadUrlNullOrEmptyException(); + await DownloadModelAsync(localModel, cancellationToken); } - DownloadModelSync(localModel.DownloadUrl.ToString(), localModel.FileName); return this; } - [Obsolete("Use async method instead")] - public IModelContext Download(string modelId, string url) + public IModelContext LoadToCache(LocalModel model) { - if (string.IsNullOrWhiteSpace(modelId)) - { - throw new MissingModelIdException(nameof(modelId)); - } - - if (string.IsNullOrWhiteSpace(url)) + var path = model.CustomPath ?? _defaultModelsPath; + if (string.IsNullOrEmpty(path)) { - throw new DownloadUrlNullOrEmptyException(); + throw new ModelPathNullOrEmptyException(); } - var fileName = $"{modelId}.gguf"; - DownloadModelSync(url, fileName); - - // Register the newly downloaded model - var newModel = new GenericLocalModel( - FileName: fileName, - Name: modelId, - Id: modelId, - DownloadUrl: new Uri(url) - ); - ModelRegistry.RegisterOrReplace(newModel); - - return this; - } - - public IModelContext LoadToCache(LocalModel model) - { - ArgumentNullException.ThrowIfNull(model); - - var modelsPath = ResolvePath(_settings.ModelsPath); - ModelLoader.GetOrLoadModel(modelsPath, model.FileName); + ModelLoader.GetOrLoadModel(path, model.FileName); return this; } public async Task LoadToCacheAsync(LocalModel model) { ArgumentNullException.ThrowIfNull(model); - - var modelsPath = ResolvePath(_settings.ModelsPath); - await ModelLoader.GetOrLoadModelAsync(modelsPath, model.FileName); + await ModelLoader.GetOrLoadModelAsync(GetModelFilePath(model), model.FileName); return this; } - private async Task DownloadModelAsync(string url, string fileName, CancellationToken cancellationToken) + private async Task DownloadModelAsync(LocalModel localModel, CancellationToken cancellationToken) { using var httpClient = CreateConfiguredHttpClient(); - var filePath = GetModelFilePath(fileName); - - Console.WriteLine($"Starting download of {fileName}..."); + var filePath = GetModelFilePath(localModel); + + if (localModel.DownloadUrl is null) + { + throw new DownloadUrlNullOrEmptyException(); + } + + Console.WriteLine($"Starting download of {localModel.FileName}..."); try { - using var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + using var response = await httpClient.GetAsync(localModel.DownloadUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken); response.EnsureSuccessStatusCode(); - await DownloadWithProgressAsync(response, filePath, fileName, cancellationToken); + await DownloadWithProgressAsync(response, filePath, localModel.FileName, cancellationToken); } catch (Exception ex) { @@ -234,57 +194,6 @@ private static async Task DownloadWithProgressAsync(HttpResponseMessage response ShowFinalProgress(totalBytesRead, totalStopwatch, fileName); } - [Obsolete("Use async method instead")] - private void DownloadModelSync(string url, string fileName) - { - var filePath = GetModelFilePath(fileName); - - Console.WriteLine($"Starting download of {fileName}..."); - - using var webClient = CreateConfiguredWebClient(); - var totalStopwatch = Stopwatch.StartNew(); - var progressStopwatch = Stopwatch.StartNew(); - - webClient.DownloadProgressChanged += (sender, e) => - { - if (ShouldUpdateProgress(progressStopwatch)) - { - ShowProgress(e.BytesReceived, e.TotalBytesToReceive > 0 ? e.TotalBytesToReceive : null, totalStopwatch); - progressStopwatch.Restart(); - } - }; - - webClient.DownloadFileCompleted += (sender, e) => - { - totalStopwatch.Stop(); - if (e.Error != null) - { - Console.WriteLine($"\nDownload failed: {e.Error.Message}"); - } - else - { - var totalTime = totalStopwatch.Elapsed; - Console.WriteLine($"\nDownload completed: {fileName}. Time: {totalTime:hh\\:mm\\:ss}"); - } - }; - - try - { - webClient.DownloadFile(url, filePath); - } - catch (Exception ex) - { - Console.WriteLine($"Download failed: {ex.Message}"); - - if (File.Exists(filePath)) - { - File.Delete(filePath); - } - - throw; - } - } - private HttpClient CreateConfiguredHttpClient() { var httpClient = _httpClientFactory.CreateClient(ServiceConstants.HttpClients.ModelContextDownloadClient); @@ -292,17 +201,10 @@ private HttpClient CreateConfiguredHttpClient() return httpClient; } - [Obsolete("Obsolete")] - private static WebClient CreateConfiguredWebClient() - { - var webClient = new WebClient(); - webClient.Headers.Add("User-Agent", "YourApp/1.0"); - return webClient; - } + private string GetModelFilePath(LocalModel localModel) => + localModel.GetFullPath(_defaultModelsPath); - private string GetModelFilePath(string fileName) => Path.Combine(ResolvePath(_settings.ModelsPath), fileName); - - private static bool ShouldUpdateProgress(Stopwatch progressStopwatch) => + private static bool ShouldUpdateProgress(Stopwatch progressStopwatch) => progressStopwatch.ElapsedMilliseconds >= ProgressUpdateIntervalMilliseconds; private static void ShowProgress(long totalBytesRead, long? totalBytes, Stopwatch totalStopwatch) @@ -369,9 +271,4 @@ private static string FormatBytes(long bytes) return "0 Bytes"; } - - private static string ResolvePath(string? settingsModelsPath) => - settingsModelsPath - ?? Environment.GetEnvironmentVariable("MaIN_ModelsPath") - ?? throw new ModelsPathNotFoundException(); -} \ No newline at end of file +} diff --git a/src/MaIN.Core/MaIN.Core.csproj b/src/MaIN.Core/MaIN.Core.csproj index 30f7526..f6f1f34 100644 --- a/src/MaIN.Core/MaIN.Core.csproj +++ b/src/MaIN.Core/MaIN.Core.csproj @@ -7,6 +7,7 @@ + diff --git a/src/MaIN.Domain/Models/Abstract/AIModel.cs b/src/MaIN.Domain/Models/Abstract/AIModel.cs index 610fc8e..6c65169 100644 --- a/src/MaIN.Domain/Models/Abstract/AIModel.cs +++ b/src/MaIN.Domain/Models/Abstract/AIModel.cs @@ -68,6 +68,14 @@ public bool IsDownloaded(string? basePath) } } + /// + /// Combines the specified base path with the file name to generate a full file path. + /// + /// This method ensures that a valid path is generated by requiring at least one of the + /// CustomPath or basePath to be provided. + /// The base path to combine with the file name. If null or empty, the method uses the custom path. + /// A string representing the full file path formed by combining the base path and the file name. + /// Thrown if both CustomPath and basePath are null or empty. public string GetFullPath(string? basePath = null) { if (string.IsNullOrEmpty(CustomPath) && string.IsNullOrEmpty(basePath))