86 lines
3.7 KiB
C#
86 lines
3.7 KiB
C#
using System.Text.Json;
|
|
using Journal.Core.Dtos;
|
|
using Journal.Core.Models;
|
|
|
|
namespace Journal.Core.Services;
|
|
|
|
public sealed class PythonSidecarAiService : IAiService
|
|
{
|
|
private readonly PythonSidecarClient _client;
|
|
|
|
public PythonSidecarAiService(JournalConfig config)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(config.PythonAiSidecarPath))
|
|
throw new ArgumentException("Python AI sidecar path is required.");
|
|
if (!File.Exists(config.PythonAiSidecarPath))
|
|
throw new FileNotFoundException($"Python AI sidecar not found: {config.PythonAiSidecarPath}");
|
|
_client = new PythonSidecarClient(config);
|
|
}
|
|
|
|
public async Task<AiHealthDto> HealthAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var data = await _client.SendAsync("health", payload: new { }, cancellationToken);
|
|
if (data is not JsonElement payload || payload.ValueKind != JsonValueKind.Object)
|
|
return new AiHealthDto("python-sidecar", Enabled: true, Healthy: true, Message: "ok");
|
|
|
|
var provider = payload.TryGetProperty("provider", out var providerNode)
|
|
? providerNode.GetString() ?? "python-sidecar"
|
|
: "python-sidecar";
|
|
var message = payload.TryGetProperty("message", out var messageNode)
|
|
? messageNode.GetString() ?? "ok"
|
|
: "ok";
|
|
var healthy = !payload.TryGetProperty("healthy", out var healthyNode) ||
|
|
healthyNode.ValueKind is JsonValueKind.True ||
|
|
(healthyNode.ValueKind is not JsonValueKind.False);
|
|
return new AiHealthDto(provider, Enabled: true, Healthy: healthy, Message: message);
|
|
}
|
|
|
|
public async Task<string> SummarizeEntryAsync(string content, string? fileStem = null, CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(content))
|
|
throw new ArgumentException("Entry content is required.", nameof(content));
|
|
|
|
var data = await _client.SendAsync("summarize_entry", new { content, file_stem = fileStem }, cancellationToken);
|
|
return data?.GetString() ?? "";
|
|
}
|
|
|
|
public async Task<string> SummarizeAllAsync(IReadOnlyList<string> entries, CancellationToken cancellationToken = default)
|
|
{
|
|
entries ??= [];
|
|
var data = await _client.SendAsync("summarize_all", new { entries }, cancellationToken);
|
|
return data?.GetString() ?? "";
|
|
}
|
|
|
|
public async Task<string> ChatAsync(string prompt, CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(prompt))
|
|
throw new ArgumentException("Prompt is required.", nameof(prompt));
|
|
|
|
var data = await _client.SendAsync("chat", new { prompt }, cancellationToken);
|
|
return data?.GetString() ?? "";
|
|
}
|
|
|
|
public async Task<IReadOnlyList<double>> EmbedAsync(string content, CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(content))
|
|
throw new ArgumentException("Content is required.", nameof(content));
|
|
|
|
var data = await _client.SendAsync("embed", new { content }, cancellationToken);
|
|
if (data is null || data.Value.ValueKind == JsonValueKind.Null)
|
|
return [];
|
|
|
|
if (data.Value.ValueKind != JsonValueKind.Array)
|
|
throw new InvalidOperationException("Python AI sidecar embed response must be a numeric array.");
|
|
|
|
var values = new List<double>();
|
|
foreach (var item in data.Value.EnumerateArray())
|
|
{
|
|
if (item.ValueKind != JsonValueKind.Number)
|
|
throw new InvalidOperationException("Python AI sidecar embed response contains a non-numeric value.");
|
|
values.Add(item.GetDouble());
|
|
}
|
|
|
|
return values;
|
|
}
|
|
}
|