using System.Diagnostics; using System.Net.Http.Headers; using System.Text.Json; using System.Text.RegularExpressions; using AuthorBuddy.Web.Models; namespace AuthorBuddy.Web.Services; public class OllamaCloudBackend : ILLMBackend { private readonly IHttpClientFactory _httpFactory; private readonly OllamaSettings _settings; public string Name => "Ollama Cloud"; public bool SupportsModelManagement => false; public OllamaCloudBackend(OllamaSettings settings, IHttpClientFactory httpFactory) { _settings = settings; _httpFactory = httpFactory; } private HttpClient CreateClient() { var client = _httpFactory.CreateClient(); client.Timeout = TimeSpan.FromSeconds(300); client.BaseAddress = new Uri(_settings.OllamaCloudUrl.TrimEnd('/') + "/"); if (!string.IsNullOrEmpty(_settings.OllamaCloudApiKey)) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _settings.OllamaCloudApiKey); } return client; } public async Task TestConnectionAsync() { try { var http = CreateClient(); var sw = Stopwatch.StartNew(); var response = await http.GetAsync("api/tags"); sw.Stop(); return response.IsSuccessStatusCode; } catch { return false; } } public async Task> GetModelsAsync() { try { var http = CreateClient(); var response = await http.GetAsync("api/tags"); if (!response.IsSuccessStatusCode) return Enumerable.Empty(); var content = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(content); if (!doc.RootElement.TryGetProperty("models", out var models)) return Enumerable.Empty(); var list = new List(); foreach (var m in models.EnumerateArray()) list.Add(new LLM_Model(m)); return list; } catch { return Enumerable.Empty(); } } public async Task ChatAsync(string model, string prompt, string? systemPrompt = null, double? temperature = null) { try { var request = new Dictionary { ["model"] = model, ["prompt"] = prompt, ["stream"] = false }; if (!string.IsNullOrEmpty(systemPrompt)) request["system"] = systemPrompt; if (temperature.HasValue) request["temperature"] = Math.Clamp(temperature.Value, 0.0, 2.0); var http = CreateClient(); var response = await http.PostAsJsonAsync("api/generate", request); if (!response.IsSuccessStatusCode) return "Fehler bei der Verarbeitung."; var content = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(content); if (doc.RootElement.TryGetProperty("response", out var resp)) return resp.GetString() ?? ""; return "Fehler bei der Verarbeitung."; } catch (Exception ex) { return $"Error: {ex.Message}"; } } public async Task ChatStreamAsync(string model, string prompt, Func onToken, string? systemPrompt = null, double? temperature = null) { var request = new Dictionary { ["model"] = model, ["prompt"] = prompt, ["stream"] = true }; if (!string.IsNullOrEmpty(systemPrompt)) request["system"] = systemPrompt; if (temperature.HasValue) request["temperature"] = Math.Clamp(temperature.Value, 0.0, 2.0); var http = CreateClient(); using var response = await http.PostAsync( "api/generate", new StringContent(JsonSerializer.Serialize(request), System.Text.Encoding.UTF8, "application/json") ); if (!response.IsSuccessStatusCode) return; using var stream = await response.Content.ReadAsStreamAsync(); using var reader = new StreamReader(stream); string? line; while ((line = await reader.ReadLineAsync()) != null) { var text = ExtractResponseText(line); if (!string.IsNullOrEmpty(text)) await onToken(text); } } private static string ExtractResponseText(string jsonLine) { if (string.IsNullOrWhiteSpace(jsonLine)) return string.Empty; try { using var doc = JsonDocument.Parse(jsonLine); if (doc.RootElement.TryGetProperty("response", out var resp)) return resp.GetString() ?? string.Empty; } catch { } return string.Empty; } public async Task GetEmbeddingAsync(string text, string? model = null) { try { var m = model ?? _settings.EmbedModel; var http = CreateClient(); var response = await http.PostAsJsonAsync("api/embeddings", new { model = m, prompt = text }); if (!response.IsSuccessStatusCode) return Array.Empty(); var content = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(content); if (!doc.RootElement.TryGetProperty("embedding", out var emb)) return Array.Empty(); var list = new List(); foreach (var v in emb.EnumerateArray()) list.Add(v.GetSingle()); return list.ToArray(); } catch { return Array.Empty(); } } public Task PullModelAsync(string name, IProgress? progress = null) { return Task.FromResult(false); } public Task DeleteModelAsync(string name) { return Task.FromResult(false); } public async Task GetContextSizeAsync(string model) { try { var http = CreateClient(); var response = await http.PostAsJsonAsync("api/show", new { model }); if (!response.IsSuccessStatusCode) return null; var content = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(content); if (doc.RootElement.TryGetProperty("modelfile", out var mf)) { var modelfile = mf.GetString() ?? ""; var match = Regex.Match(modelfile, @"num_ctx\s+(\d+)"); if (match.Success && int.TryParse(match.Groups[1].Value, out var ctx)) return ctx; } return 2048; } catch { return null; } } public async Task MeasureLatencyAsync(string model) { try { var http = CreateClient(); var sw = Stopwatch.StartNew(); var response = await http.PostAsJsonAsync("api/generate", new { model, prompt = "Hallo", stream = false }); sw.Stop(); return response.IsSuccessStatusCode ? sw.ElapsedMilliseconds : -1; } catch { return -1; } } public async Task ModelExistsAsync(string model) { if (string.IsNullOrWhiteSpace(model)) return false; try { var models = await GetModelsAsync(); return models.Any(m => m.Name == model || m.Name.StartsWith(model.Split(':')[0])); } catch { return false; } } public async Task<(bool Success, int ContextWindow)> GetModelContextWindowAsync(string model) { var ctx = await GetContextSizeAsync(model); return (ctx.HasValue, ctx ?? 0); } public bool TestEmbedding(string model) { try { var http = CreateClient(); var response = http.PostAsJsonAsync("api/embeddings", new { model, prompt = "test" }).GetAwaiter().GetResult(); if (!response.IsSuccessStatusCode) return false; var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); using var doc = JsonDocument.Parse(content); if (doc.RootElement.TryGetProperty("embedding", out var emb)) return emb.EnumerateArray().Any(); return false; } catch { return false; } } }