using AuthorBuddy.Web.Models; using System.Text.Json; using static System.Net.Mime.MediaTypeNames; namespace AuthorBuddy.Web.Services; public class OllamaService : IOllamaService { private readonly HttpClient _httpClient; private readonly OllamaSettings _settings; private string _baseUrl = "http://localhost:11434"; public OllamaService(OllamaSettings settings) { _settings = settings; _baseUrl = settings.OllamaUrl; _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(300) }; } public void Reconfigure(string url) { _baseUrl = url; } public async Task TestEmbeddingAsync(string model) { try { var request = new { model = model, prompt = "test" }; var response = await _httpClient.PostAsJsonAsync($"{_baseUrl}/api/embeddings", request); if (!response.IsSuccessStatusCode) return false; var content = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(content); if (doc.RootElement.TryGetProperty("embedding", out var embedding)) return embedding.EnumerateArray().Any(); return false; } catch { return false; } } public async Task GetEmbeddingAsync(string text) { try { var request = new { model = _settings.EmbedModel, prompt = text }; var response = await _httpClient.PostAsJsonAsync($"{_baseUrl}/api/embeddings", request); if (response.IsSuccessStatusCode) { var content = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(content); if (doc.RootElement.TryGetProperty("embedding", out var embedding)) { var list = new List(); foreach (var val in embedding.EnumerateArray()) list.Add(val.GetSingle()); return list.ToArray(); } } return Array.Empty(); } catch { return Array.Empty(); } } public async Task> GetLocalModelsAsync() { try { var response = await _httpClient.GetAsync($"{_baseUrl}/api/tags"); if (response.IsSuccessStatusCode) { var content = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(content); var root = doc.RootElement; if (root.TryGetProperty("models", out var models)) { var modelList = new List(); foreach (var model in models.EnumerateArray()) { modelList.Add(new LLM_Model(model)); } return modelList; } } return new List(); } catch (Exception) { return new List(); } } public async Task QuickSpellCheckAsync(string text) { return await Chat(_settings.SpellingModel, text, _settings.SpellingPrompt); } public async Task StreamStyleCheckAsync(string text) { var systemPrompt = _settings.LektorSystemPrompt; return await Chat(_settings.StyleModel, text, systemPrompt); } public async Task AnswerBasedOnFactsAsync(string question, string facts) { var prompt = _settings.GetFactCheckPrompt(question, facts); return await Chat(_settings.EmbedModel, prompt); } public async Task Chat(string model, string userMessage, string? systemPrompt = null) { try { var request = new { model = model, prompt = userMessage, system = systemPrompt, stream = false }; var response = await _httpClient.PostAsJsonAsync($"{_baseUrl}/api/generate", request); if (response.IsSuccessStatusCode) { var content = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(content); var root = doc.RootElement; if (root.TryGetProperty("response", out var responseText)) { return responseText.GetString() ?? ""; } } return "Fehler bei der Verarbeitung."; } catch (Exception ex) { return $"Error: {ex.Message}"; } } public async IAsyncEnumerable ChatStream(string model, string userMessage, string? systemPrompt = null) { var request = new { model = model, prompt = userMessage, stream = true }; HttpResponseMessage? response = null; try { response = await _httpClient.PostAsync( $"{_baseUrl}/api/generate", new StringContent(JsonSerializer.Serialize(request), System.Text.Encoding.UTF8, "application/json") ); if (response.IsSuccessStatusCode) { 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)) { yield return text; } } } } finally { response?.Dispose(); } } private string ExtractResponseText(string jsonLine) { if (string.IsNullOrWhiteSpace(jsonLine)) return string.Empty; try { using var doc = JsonDocument.Parse(jsonLine); var root = doc.RootElement; if (root.TryGetProperty("response", out var responseText)) { return responseText.GetString() ?? string.Empty; } } catch { } return string.Empty; } public async Task DeleteModel(string modelName) { try { var request = new HttpRequestMessage(HttpMethod.Delete, $"{_baseUrl}/api/delete"); request.Content = new StringContent(JsonSerializer.Serialize(new { name = modelName }), System.Text.Encoding.UTF8, "application/json"); var response = await _httpClient.SendAsync(request); return response.IsSuccessStatusCode; } catch (Exception) { return false; } } public async Task PullModelAsync(string modelName, IProgress progress) { try { var request = new { name = modelName, stream = true }; var response = await _httpClient.PostAsync( $"{_baseUrl}/api/pull", new StringContent(JsonSerializer.Serialize(request), System.Text.Encoding.UTF8, "application/json") ); if (response.IsSuccessStatusCode) { using var stream = await response.Content.ReadAsStreamAsync(); using var reader = new StreamReader(stream); string? line; while ((line = await reader.ReadLineAsync()) != null) { if (string.IsNullOrWhiteSpace(line)) continue; try { using var doc = JsonDocument.Parse(line); var root = doc.RootElement; if (root.TryGetProperty("status", out var status)) { var statusText = status.GetString() ?? ""; var msg = statusText; if (root.TryGetProperty("completed", out var completed) && root.TryGetProperty("total", out var total)) { var comp = completed.GetInt64(); var tot = total.GetInt64(); if (tot > 0) { var pct = (int)(comp * 100 / tot); msg = $"{statusText}: {comp}/{tot} ({pct}%)"; } } progress?.Report(msg); if (statusText == "success") return true; } } catch { } } } return false; } catch (Exception) { return false; } } }