diff --git a/Journal.AI/ServiceCollectionExtensions.cs b/Journal.AI/ServiceCollectionExtensions.cs
index a8a40cb..48ddaa6 100644
--- a/Journal.AI/ServiceCollectionExtensions.cs
+++ b/Journal.AI/ServiceCollectionExtensions.cs
@@ -1,6 +1,5 @@
using Journal.Core.Services.Ai;
using Journal.Core.Services.Config;
-using Journal.Core.Services.Sidecar;
using Microsoft.Extensions.DependencyInjection;
namespace Journal.AI;
@@ -34,21 +33,6 @@ public static class ServiceCollectionExtensions
}
}
- if (string.Equals(config.AiProvider, "python-sidecar", StringComparison.OrdinalIgnoreCase))
- {
- try
- {
- return new PythonSidecarAiService(config);
- }
- catch (Exception ex)
- {
- return new DisabledAiService(
- provider: "python-sidecar",
- message: $"Python AI sidecar unavailable: {ex.Message}",
- healthy: false);
- }
- }
-
return new DisabledAiService(config.AiProvider);
});
diff --git a/Journal.App/src-tauri/icons/128x128.png b/Journal.App/src-tauri/icons/128x128.png
index 6be5e50..2d911cd 100644
Binary files a/Journal.App/src-tauri/icons/128x128.png and b/Journal.App/src-tauri/icons/128x128.png differ
diff --git a/Journal.App/src-tauri/icons/128x128@2x.png b/Journal.App/src-tauri/icons/128x128@2x.png
index e81bece..8d4a5e8 100644
Binary files a/Journal.App/src-tauri/icons/128x128@2x.png and b/Journal.App/src-tauri/icons/128x128@2x.png differ
diff --git a/Journal.App/src-tauri/icons/32x32.png b/Journal.App/src-tauri/icons/32x32.png
index a437dd5..7b41a78 100644
Binary files a/Journal.App/src-tauri/icons/32x32.png and b/Journal.App/src-tauri/icons/32x32.png differ
diff --git a/Journal.App/src-tauri/icons/64x64.png b/Journal.App/src-tauri/icons/64x64.png
new file mode 100644
index 0000000..9286e8d
Binary files /dev/null and b/Journal.App/src-tauri/icons/64x64.png differ
diff --git a/Journal.App/src-tauri/icons/Square107x107Logo.png b/Journal.App/src-tauri/icons/Square107x107Logo.png
index 0ca4f27..a195b03 100644
Binary files a/Journal.App/src-tauri/icons/Square107x107Logo.png and b/Journal.App/src-tauri/icons/Square107x107Logo.png differ
diff --git a/Journal.App/src-tauri/icons/Square142x142Logo.png b/Journal.App/src-tauri/icons/Square142x142Logo.png
index b81f820..a52d49b 100644
Binary files a/Journal.App/src-tauri/icons/Square142x142Logo.png and b/Journal.App/src-tauri/icons/Square142x142Logo.png differ
diff --git a/Journal.App/src-tauri/icons/Square150x150Logo.png b/Journal.App/src-tauri/icons/Square150x150Logo.png
index 624c7bf..97bf71f 100644
Binary files a/Journal.App/src-tauri/icons/Square150x150Logo.png and b/Journal.App/src-tauri/icons/Square150x150Logo.png differ
diff --git a/Journal.App/src-tauri/icons/Square284x284Logo.png b/Journal.App/src-tauri/icons/Square284x284Logo.png
index c021d2b..9388caf 100644
Binary files a/Journal.App/src-tauri/icons/Square284x284Logo.png and b/Journal.App/src-tauri/icons/Square284x284Logo.png differ
diff --git a/Journal.App/src-tauri/icons/Square30x30Logo.png b/Journal.App/src-tauri/icons/Square30x30Logo.png
index 6219700..6fe3e8a 100644
Binary files a/Journal.App/src-tauri/icons/Square30x30Logo.png and b/Journal.App/src-tauri/icons/Square30x30Logo.png differ
diff --git a/Journal.App/src-tauri/icons/Square310x310Logo.png b/Journal.App/src-tauri/icons/Square310x310Logo.png
index f9bc048..a15a3f9 100644
Binary files a/Journal.App/src-tauri/icons/Square310x310Logo.png and b/Journal.App/src-tauri/icons/Square310x310Logo.png differ
diff --git a/Journal.App/src-tauri/icons/Square44x44Logo.png b/Journal.App/src-tauri/icons/Square44x44Logo.png
index d5fbfb2..3ac2272 100644
Binary files a/Journal.App/src-tauri/icons/Square44x44Logo.png and b/Journal.App/src-tauri/icons/Square44x44Logo.png differ
diff --git a/Journal.App/src-tauri/icons/Square71x71Logo.png b/Journal.App/src-tauri/icons/Square71x71Logo.png
index 63440d7..90d71f5 100644
Binary files a/Journal.App/src-tauri/icons/Square71x71Logo.png and b/Journal.App/src-tauri/icons/Square71x71Logo.png differ
diff --git a/Journal.App/src-tauri/icons/Square89x89Logo.png b/Journal.App/src-tauri/icons/Square89x89Logo.png
index f3f705a..731faa5 100644
Binary files a/Journal.App/src-tauri/icons/Square89x89Logo.png and b/Journal.App/src-tauri/icons/Square89x89Logo.png differ
diff --git a/Journal.App/src-tauri/icons/StoreLogo.png b/Journal.App/src-tauri/icons/StoreLogo.png
index 4556388..860f9c2 100644
Binary files a/Journal.App/src-tauri/icons/StoreLogo.png and b/Journal.App/src-tauri/icons/StoreLogo.png differ
diff --git a/Journal.App/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/Journal.App/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..2ffbf24
--- /dev/null
+++ b/Journal.App/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Journal.App/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/Journal.App/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..5f45f4d
Binary files /dev/null and b/Journal.App/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ
diff --git a/Journal.App/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/Journal.App/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..825369d
Binary files /dev/null and b/Journal.App/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/Journal.App/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/Journal.App/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..42faa23
Binary files /dev/null and b/Journal.App/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/Journal.App/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/Journal.App/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..d37d7af
Binary files /dev/null and b/Journal.App/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ
diff --git a/Journal.App/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/Journal.App/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..ba45af7
Binary files /dev/null and b/Journal.App/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/Journal.App/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/Journal.App/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..7d67494
Binary files /dev/null and b/Journal.App/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/Journal.App/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/Journal.App/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..b503076
Binary files /dev/null and b/Journal.App/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ
diff --git a/Journal.App/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/Journal.App/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..df1c2f3
Binary files /dev/null and b/Journal.App/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/Journal.App/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/Journal.App/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..6413a9d
Binary files /dev/null and b/Journal.App/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/Journal.App/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/Journal.App/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..feb3485
Binary files /dev/null and b/Journal.App/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/Journal.App/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/Journal.App/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..9a4e6a8
Binary files /dev/null and b/Journal.App/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/Journal.App/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/Journal.App/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..3ecdfea
Binary files /dev/null and b/Journal.App/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/Journal.App/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/Journal.App/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..6802d92
Binary files /dev/null and b/Journal.App/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/Journal.App/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/Journal.App/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..d49935f
Binary files /dev/null and b/Journal.App/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/Journal.App/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/Journal.App/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..4ac2a01
Binary files /dev/null and b/Journal.App/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/Journal.App/src-tauri/icons/android/values/ic_launcher_background.xml b/Journal.App/src-tauri/icons/android/values/ic_launcher_background.xml
new file mode 100644
index 0000000..ea9c223
--- /dev/null
+++ b/Journal.App/src-tauri/icons/android/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #fff
+
\ No newline at end of file
diff --git a/Journal.App/src-tauri/icons/icon.icns b/Journal.App/src-tauri/icons/icon.icns
index 12a5bce..722efed 100644
Binary files a/Journal.App/src-tauri/icons/icon.icns and b/Journal.App/src-tauri/icons/icon.icns differ
diff --git a/Journal.App/src-tauri/icons/icon.ico b/Journal.App/src-tauri/icons/icon.ico
index 00f68f0..d7d5e76 100644
Binary files a/Journal.App/src-tauri/icons/icon.ico and b/Journal.App/src-tauri/icons/icon.ico differ
diff --git a/Journal.App/src-tauri/icons/icon.png b/Journal.App/src-tauri/icons/icon.png
index e1cd261..a1712ec 100644
Binary files a/Journal.App/src-tauri/icons/icon.png and b/Journal.App/src-tauri/icons/icon.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-20x20@1x.png b/Journal.App/src-tauri/icons/ios/AppIcon-20x20@1x.png
new file mode 100644
index 0000000..d46466a
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-20x20@1x.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/Journal.App/src-tauri/icons/ios/AppIcon-20x20@2x-1.png
new file mode 100644
index 0000000..4c47628
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-20x20@2x.png b/Journal.App/src-tauri/icons/ios/AppIcon-20x20@2x.png
new file mode 100644
index 0000000..4c47628
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-20x20@2x.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-20x20@3x.png b/Journal.App/src-tauri/icons/ios/AppIcon-20x20@3x.png
new file mode 100644
index 0000000..b9e2fcc
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-20x20@3x.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-29x29@1x.png b/Journal.App/src-tauri/icons/ios/AppIcon-29x29@1x.png
new file mode 100644
index 0000000..b459572
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-29x29@1x.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/Journal.App/src-tauri/icons/ios/AppIcon-29x29@2x-1.png
new file mode 100644
index 0000000..536bc3e
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-29x29@2x.png b/Journal.App/src-tauri/icons/ios/AppIcon-29x29@2x.png
new file mode 100644
index 0000000..536bc3e
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-29x29@2x.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-29x29@3x.png b/Journal.App/src-tauri/icons/ios/AppIcon-29x29@3x.png
new file mode 100644
index 0000000..f1ddfd7
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-29x29@3x.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-40x40@1x.png b/Journal.App/src-tauri/icons/ios/AppIcon-40x40@1x.png
new file mode 100644
index 0000000..4c47628
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-40x40@1x.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/Journal.App/src-tauri/icons/ios/AppIcon-40x40@2x-1.png
new file mode 100644
index 0000000..4348b83
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-40x40@2x.png b/Journal.App/src-tauri/icons/ios/AppIcon-40x40@2x.png
new file mode 100644
index 0000000..4348b83
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-40x40@2x.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-40x40@3x.png b/Journal.App/src-tauri/icons/ios/AppIcon-40x40@3x.png
new file mode 100644
index 0000000..70c1a37
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-40x40@3x.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-512@2x.png b/Journal.App/src-tauri/icons/ios/AppIcon-512@2x.png
new file mode 100644
index 0000000..768a82f
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-512@2x.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-60x60@2x.png b/Journal.App/src-tauri/icons/ios/AppIcon-60x60@2x.png
new file mode 100644
index 0000000..70c1a37
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-60x60@2x.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-60x60@3x.png b/Journal.App/src-tauri/icons/ios/AppIcon-60x60@3x.png
new file mode 100644
index 0000000..68b754d
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-60x60@3x.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-76x76@1x.png b/Journal.App/src-tauri/icons/ios/AppIcon-76x76@1x.png
new file mode 100644
index 0000000..8da1fb1
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-76x76@1x.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-76x76@2x.png b/Journal.App/src-tauri/icons/ios/AppIcon-76x76@2x.png
new file mode 100644
index 0000000..af1c309
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-76x76@2x.png differ
diff --git a/Journal.App/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/Journal.App/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
new file mode 100644
index 0000000..6127e87
Binary files /dev/null and b/Journal.App/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ
diff --git a/Journal.Core/Models/JournalConfig.cs b/Journal.Core/Models/JournalConfig.cs
index 7abcae6..0108f92 100644
--- a/Journal.Core/Models/JournalConfig.cs
+++ b/Journal.Core/Models/JournalConfig.cs
@@ -22,7 +22,4 @@ public sealed record JournalConfig(
string WhisperModelSize,
string NlpBackend,
string AiProvider,
- string PythonExecutable,
- string PythonAiSidecarPath,
- int AiSidecarTimeoutMs,
string GgufModelPath);
diff --git a/Journal.Core/ServiceCollectionExtensions.cs b/Journal.Core/ServiceCollectionExtensions.cs
index 852cd18..b157dc0 100644
--- a/Journal.Core/ServiceCollectionExtensions.cs
+++ b/Journal.Core/ServiceCollectionExtensions.cs
@@ -30,35 +30,10 @@ public static class ServiceCollectionExtensions
services.AddSingleton(provider =>
{
var config = provider.GetRequiredService().Current;
- if (!string.Equals(config.AiProvider, "python-sidecar", StringComparison.OrdinalIgnoreCase))
- return new DisabledAiService(config.AiProvider);
-
- try
- {
- return new PythonSidecarAiService(config);
- }
- catch (Exception ex)
- {
- return new DisabledAiService(
- provider: "python-sidecar",
- message: $"Python AI sidecar unavailable: {ex.Message}",
- healthy: false);
- }
- });
- services.AddSingleton(provider =>
- {
- var config = provider.GetRequiredService().Current;
- try
- {
- return new PythonSidecarSpeechService(config);
- }
- catch (Exception ex)
- {
- return new DisabledSpeechBridgeService(
- provider: "python-sidecar",
- message: $"Python speech sidecar unavailable: {ex.Message}");
- }
+ return new DisabledAiService(config.AiProvider);
});
+ services.AddSingleton(
+ new DisabledSpeechBridgeService("none"));
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
diff --git a/Journal.Core/Services/Ai/PythonSidecarAiService.cs b/Journal.Core/Services/Ai/PythonSidecarAiService.cs
deleted file mode 100644
index b1c0474..0000000
--- a/Journal.Core/Services/Ai/PythonSidecarAiService.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-using System.Text.Json;
-using Journal.Core.Dtos;
-using Journal.Core.Models;
-using Journal.Core.Services.Sidecar;
-
-namespace Journal.Core.Services.Ai;
-
-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 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 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 SummarizeAllAsync(IReadOnlyList entries, CancellationToken cancellationToken = default)
- {
- entries ??= [];
- var data = await _client.SendAsync("summarize_all", new { entries }, cancellationToken);
- return data?.GetString() ?? "";
- }
-
- public async Task 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 Task ChatWithHistoryAsync(IReadOnlyList<(string Role, string Text)> history,
- string prompt, CancellationToken cancellationToken = default)
- {
- // Python sidecar does not support multi-turn — fall back to single-turn
- return ChatAsync(prompt, cancellationToken);
- }
-
- public async Task> 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();
- 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;
- }
-}
diff --git a/Journal.Core/Services/Config/JournalConfigService.cs b/Journal.Core/Services/Config/JournalConfigService.cs
index d20f531..c73b3b6 100644
--- a/Journal.Core/Services/Config/JournalConfigService.cs
+++ b/Journal.Core/Services/Config/JournalConfigService.cs
@@ -22,17 +22,9 @@ public sealed class JournalConfigService : IJournalConfigService
nlpBackend = "auto";
var aiProvider = (Environment.GetEnvironmentVariable("JOURNAL_AI_PROVIDER") ?? "llamasharp").Trim().ToLowerInvariant();
- if (aiProvider is not ("none" or "python-sidecar" or "llamasharp"))
+ if (aiProvider is not ("none" or "llamasharp"))
aiProvider = "none";
- var pythonExecutable = Environment.GetEnvironmentVariable("JOURNAL_PYTHON_EXE");
- if (string.IsNullOrWhiteSpace(pythonExecutable))
- pythonExecutable = "python";
-
- var defaultAiSidecarPath = Path.Combine(projectRoot, "journal", "ai", "sidecar.py");
- var pythonAiSidecarPath = ResolvePath("JOURNAL_AI_SIDECAR_PATH", defaultAiSidecarPath);
- var aiSidecarTimeoutMs = ParseInt("JOURNAL_AI_TIMEOUT_MS", 45000);
-
return new JournalConfig(
ProjectRoot: projectRoot,
AppDirectory: appDirectory,
@@ -55,9 +47,6 @@ public sealed class JournalConfigService : IJournalConfigService
WhisperModelSize: Environment.GetEnvironmentVariable("WHISPER_MODEL_SIZE") ?? "base",
NlpBackend: nlpBackend,
AiProvider: aiProvider,
- PythonExecutable: pythonExecutable,
- PythonAiSidecarPath: pythonAiSidecarPath,
- AiSidecarTimeoutMs: aiSidecarTimeoutMs,
GgufModelPath: ResolveGgufModelPath(projectRoot));
}
diff --git a/Journal.Core/Services/Sidecar/PythonSidecarClient.cs b/Journal.Core/Services/Sidecar/PythonSidecarClient.cs
deleted file mode 100644
index d744bf8..0000000
--- a/Journal.Core/Services/Sidecar/PythonSidecarClient.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-using System.Diagnostics;
-using System.Text.Json;
-using Journal.Core.Models;
-
-namespace Journal.Core.Services.Sidecar;
-
-public sealed class PythonSidecarClient(JournalConfig config)
-{
- private static readonly JsonSerializerOptions JsonOptions = new()
- {
- PropertyNameCaseInsensitive = true
- };
-
- private readonly JournalConfig _config = config;
-
- public async Task SendAsync(string action, object payload, CancellationToken cancellationToken)
- {
- var request = JsonSerializer.Serialize(new { action, payload }, JsonOptions);
-
- using var process = new Process();
- process.StartInfo = new ProcessStartInfo
- {
- FileName = _config.PythonExecutable,
- UseShellExecute = false,
- RedirectStandardInput = true,
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- CreateNoWindow = true,
- WorkingDirectory = _config.ProjectRoot
- };
- process.StartInfo.ArgumentList.Add(_config.PythonAiSidecarPath);
-
- if (!process.Start())
- throw new InvalidOperationException("Failed to start Python sidecar process.");
-
- await process.StandardInput.WriteLineAsync(request);
- process.StandardInput.Close();
-
- using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
- timeoutCts.CancelAfter(_config.AiSidecarTimeoutMs);
-
- try
- {
- await process.WaitForExitAsync(timeoutCts.Token);
- }
- catch (OperationCanceledException)
- {
- TryKill(process);
- throw new TimeoutException($"Python sidecar timed out after {_config.AiSidecarTimeoutMs} ms.");
- }
-
- var stdout = await process.StandardOutput.ReadToEndAsync();
- var stderr = await process.StandardError.ReadToEndAsync();
- var line = LastJsonLine(stdout);
- if (string.IsNullOrWhiteSpace(line))
- throw new InvalidOperationException($"Python sidecar returned no JSON response. stderr: {stderr}".Trim());
-
- JsonDocument doc;
- try
- {
- doc = JsonDocument.Parse(line);
- }
- catch (JsonException ex)
- {
- throw new InvalidOperationException($"Invalid JSON from Python sidecar: {line}", ex);
- }
-
- using (doc)
- {
- var root = doc.RootElement;
- if (!root.TryGetProperty("ok", out var okNode) || okNode.ValueKind != JsonValueKind.True && okNode.ValueKind != JsonValueKind.False)
- throw new InvalidOperationException("Python sidecar response missing boolean 'ok' field.");
-
- if (!okNode.GetBoolean())
- {
- var error = root.TryGetProperty("error", out var errorNode)
- ? errorNode.GetString() ?? "Unknown sidecar error."
- : "Unknown sidecar error.";
- throw new InvalidOperationException(error);
- }
-
- if (!root.TryGetProperty("data", out var dataNode))
- return null;
-
- return dataNode.Clone();
- }
- }
-
- private static string LastJsonLine(string text)
- {
- var lines = text.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
- for (var i = lines.Length - 1; i >= 0; i--)
- {
- var line = lines[i].Trim();
- if (line.StartsWith('{') && line.EndsWith('}'))
- return line;
- }
-
- return "";
- }
-
- private static void TryKill(Process process)
- {
- try
- {
- if (!process.HasExited)
- process.Kill(entireProcessTree: true);
- }
- catch
- {
- // Ignore cleanup errors while handling timeout/failure path.
- }
- }
-}
diff --git a/Journal.Core/Services/Speech/PythonSidecarSpeechService.cs b/Journal.Core/Services/Speech/PythonSidecarSpeechService.cs
deleted file mode 100644
index 7936f0d..0000000
--- a/Journal.Core/Services/Speech/PythonSidecarSpeechService.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-using System.Text.Json;
-using Journal.Core.Dtos;
-using Journal.Core.Models;
-using Journal.Core.Services.Sidecar;
-
-namespace Journal.Core.Services.Speech;
-
-public sealed class PythonSidecarSpeechService : ISpeechBridgeService
-{
- private readonly PythonSidecarClient _client;
-
- public PythonSidecarSpeechService(JournalConfig config)
- {
- if (string.IsNullOrWhiteSpace(config.PythonAiSidecarPath))
- throw new ArgumentException("Python sidecar path is required.");
- if (!File.Exists(config.PythonAiSidecarPath))
- throw new FileNotFoundException($"Python sidecar not found: {config.PythonAiSidecarPath}");
- _client = new PythonSidecarClient(config);
- }
-
- public async Task ListDevicesAsync(CancellationToken cancellationToken = default)
- {
- var data = await _client.SendAsync("speech.devices.list", new { }, cancellationToken);
- if (data is null || data.Value.ValueKind != JsonValueKind.Object)
- return new SpeechDevicesResultDto([], "Unexpected speech device response from Python sidecar.");
-
- var warning = data.Value.TryGetProperty("warning", out var warningNode)
- ? warningNode.GetString()
- : null;
-
- var devices = new List();
- if (data.Value.TryGetProperty("devices", out var devicesNode) && devicesNode.ValueKind == JsonValueKind.Array)
- {
- foreach (var device in devicesNode.EnumerateArray())
- {
- if (device.ValueKind != JsonValueKind.Object)
- continue;
-
- var index = device.TryGetProperty("index", out var indexNode) && indexNode.ValueKind == JsonValueKind.Number
- ? indexNode.GetInt32()
- : -1;
- var name = device.TryGetProperty("name", out var nameNode)
- ? nameNode.GetString() ?? ""
- : "";
- devices.Add(new SpeechDeviceDto(index, name));
- }
- }
-
- return new SpeechDevicesResultDto(devices, warning);
- }
-
- public async Task TranscribeAsync(
- SpeechTranscribeRequestDto request,
- CancellationToken cancellationToken = default)
- {
- ArgumentNullException.ThrowIfNull(request);
-
- var data = await _client.SendAsync("speech.transcribe", new
- {
- audio_base64 = request.AudioBase64,
- engine = request.Engine,
- whisper_model = request.WhisperModel,
- text = request.Text,
- simulate_delay_ms = request.SimulateDelayMs
- }, cancellationToken);
-
- if (data is null || data.Value.ValueKind != JsonValueKind.Object)
- throw new InvalidOperationException("Python sidecar speech response must be a JSON object.");
-
- var text = data.Value.TryGetProperty("text", out var textNode)
- ? textNode.GetString() ?? ""
- : "";
- var engine = data.Value.TryGetProperty("engine", out var engineNode)
- ? engineNode.GetString() ?? (request.Engine ?? "whisper")
- : (request.Engine ?? "whisper");
- var warning = data.Value.TryGetProperty("warning", out var warningNode)
- ? warningNode.GetString()
- : null;
- return new SpeechTranscribeResultDto(text, engine, warning);
- }
-}
diff --git a/Journal.SmokeTests/GlobalUsings.cs b/Journal.SmokeTests/GlobalUsings.cs
index b94867a..7ad9a11 100644
--- a/Journal.SmokeTests/GlobalUsings.cs
+++ b/Journal.SmokeTests/GlobalUsings.cs
@@ -11,8 +11,8 @@ global using Journal.Core.Services.Database;
global using Journal.Core.Services.Entries;
global using Journal.Core.Services.Fragments;
global using Journal.Core.Services.Logging;
-global using Journal.Core.Services.Speech;
global using Journal.Core.Services.Sidecar;
+global using Journal.Core.Services.Speech;
global using Journal.Core.Services.Lists;
global using Journal.Core.Services.Todos;
global using Journal.Core.Services.Conversations;
diff --git a/Journal.SmokeTests/Program.AiSpeechTests.cs b/Journal.SmokeTests/Program.AiSpeechTests.cs
index 50f7fce..bd80ced 100644
--- a/Journal.SmokeTests/Program.AiSpeechTests.cs
+++ b/Journal.SmokeTests/Program.AiSpeechTests.cs
@@ -130,213 +130,5 @@ internal static partial class Program
var warning = data.TryGetProperty("Warning", out var warningNode) ? warningNode.GetString() ?? "" : "";
Assert(warning.Contains("disabled", StringComparison.OrdinalIgnoreCase), "Expected disabled speech warning.");
}
-
- static async Task TestPythonSidecarAiServiceJsonLineAsync()
- {
- var root = Path.Combine(Path.GetTempPath(), "journal-ai-smoke", Guid.NewGuid().ToString("N"));
- Directory.CreateDirectory(root);
- var scriptPath = Path.Combine(root, "fake_ai_sidecar.py");
- File.WriteAllText(scriptPath, """
-import json, sys
-request = json.loads(sys.stdin.readline())
-action = request.get("action", "")
-print("DEBUG prelude")
-if action == "health":
- print(json.dumps({"ok": True, "data": {"provider": "python-sidecar", "healthy": True, "message": "ok"}}))
-elif action == "summarize_entry":
- payload = request.get("payload") or {}
- print(json.dumps({"ok": True, "data": "ENTRY::" + str(payload.get("content", ""))}))
-elif action == "summarize_all":
- payload = request.get("payload") or {}
- entries = payload.get("entries") or []
- print(json.dumps({"ok": True, "data": "ALL::" + str(len(entries))}))
-elif action == "chat":
- payload = request.get("payload") or {}
- print(json.dumps({"ok": True, "data": "CHAT::" + str(payload.get("prompt", ""))}))
-elif action == "embed":
- payload = request.get("payload") or {}
- text = str(payload.get("content", ""))
- print(json.dumps({"ok": True, "data": [float(len(text)), 2.5, -1.0]}))
-else:
- print(json.dumps({"ok": False, "error": "unknown action"}))
-""");
-
- try
- {
- var config = BuildAiConfig(scriptPath, timeoutMs: 4000);
- IAiService service = new PythonSidecarAiService(config);
-
- var health = await service.HealthAsync();
- Assert(health.Enabled, "Expected enabled=true for python-sidecar health.");
- Assert(health.Healthy, "Expected healthy=true from fake sidecar health.");
-
- var one = await service.SummarizeEntryAsync("hello");
- Assert(one == "ENTRY::hello", "Unexpected summarize_entry response.");
-
- var all = await service.SummarizeAllAsync(["a", "b", "c"]);
- Assert(all == "ALL::3", "Unexpected summarize_all response.");
-
- var chat = await service.ChatAsync("hello");
- Assert(chat == "CHAT::hello", "Unexpected chat response.");
-
- var vector = await service.EmbedAsync("hello");
- Assert(vector.Count == 3, "Unexpected embed vector length.");
- Assert(Math.Abs(vector[0] - 5d) < 0.0001d, "Unexpected embed vector first value.");
- Assert(Math.Abs(vector[1] - 2.5d) < 0.0001d, "Unexpected embed vector second value.");
- Assert(Math.Abs(vector[2] + 1.0d) < 0.0001d, "Unexpected embed vector third value.");
- }
- finally
- {
- if (Directory.Exists(root))
- Directory.Delete(root, recursive: true);
- }
- }
-
- static async Task TestPythonSidecarAiServiceErrorAsync()
- {
- var root = Path.Combine(Path.GetTempPath(), "journal-ai-smoke", Guid.NewGuid().ToString("N"));
- Directory.CreateDirectory(root);
- var scriptPath = Path.Combine(root, "fake_ai_sidecar_error.py");
- File.WriteAllText(scriptPath, """
-import json, sys
-_ = json.loads(sys.stdin.readline())
-print(json.dumps({"ok": False, "error": "simulated failure"}))
-""");
-
- try
- {
- var config = BuildAiConfig(scriptPath, timeoutMs: 4000);
- IAiService service = new PythonSidecarAiService(config);
-
- try
- {
- _ = await service.SummarizeEntryAsync("hello");
- }
- catch (InvalidOperationException ex) when (ex.Message.Contains("simulated failure", StringComparison.OrdinalIgnoreCase))
- {
- return;
- }
-
- throw new InvalidOperationException("Expected summarize_entry to surface sidecar error.");
- }
- finally
- {
- if (Directory.Exists(root))
- Directory.Delete(root, recursive: true);
- }
- }
-
- static async Task TestPythonSidecarSpeechServiceNoDevicesAsync()
- {
- var root = Path.Combine(Path.GetTempPath(), "journal-speech-smoke", Guid.NewGuid().ToString("N"));
- Directory.CreateDirectory(root);
- var scriptPath = Path.Combine(root, "fake_speech_sidecar_nodes.py");
- File.WriteAllText(scriptPath, """
-import json, sys
-request = json.loads(sys.stdin.readline())
-action = request.get("action", "")
-if action == "speech.devices.list":
- print(json.dumps({"ok": True, "data": {"devices": [], "warning": "no devices"}}))
-elif action == "speech.transcribe":
- payload = request.get("payload") or {}
- print(json.dumps({"ok": True, "data": {"text": payload.get("text", ""), "engine": payload.get("engine", "whisper")}}))
-else:
- print(json.dumps({"ok": False, "error": "unknown action"}))
-""");
-
- try
- {
- var config = BuildAiConfig(scriptPath, timeoutMs: 4000);
- ISpeechBridgeService service = new PythonSidecarSpeechService(config);
-
- var devices = await service.ListDevicesAsync();
- Assert(devices.Devices.Count == 0, "Expected empty devices list.");
- Assert((devices.Warning ?? "").Contains("no devices", StringComparison.OrdinalIgnoreCase), "Expected no-devices warning.");
-
- var transcript = await service.TranscribeAsync(new SpeechTranscribeRequestDto(Text: "fixture text", Engine: "whisper"));
- Assert(transcript.Text == "fixture text", "Expected passthrough transcript text.");
- Assert(transcript.Engine == "whisper", "Expected passthrough transcript engine.");
- }
- finally
- {
- if (Directory.Exists(root))
- Directory.Delete(root, recursive: true);
- }
- }
-
- static async Task TestPythonSidecarSpeechServiceErrorAsync()
- {
- var root = Path.Combine(Path.GetTempPath(), "journal-speech-smoke", Guid.NewGuid().ToString("N"));
- Directory.CreateDirectory(root);
- var scriptPath = Path.Combine(root, "fake_speech_sidecar_error.py");
- File.WriteAllText(scriptPath, """
-import json, sys
-request = json.loads(sys.stdin.readline())
-action = request.get("action", "")
-if action == "speech.transcribe":
- print(json.dumps({"ok": False, "error": "engine unavailable"}))
-else:
- print(json.dumps({"ok": True, "data": {"devices": []}}))
-""");
-
- try
- {
- var config = BuildAiConfig(scriptPath, timeoutMs: 4000);
- ISpeechBridgeService service = new PythonSidecarSpeechService(config);
-
- try
- {
- _ = await service.TranscribeAsync(new SpeechTranscribeRequestDto(Text: "fixture", Engine: "faster-whisper"));
- }
- catch (InvalidOperationException ex) when (ex.Message.Contains("engine unavailable", StringComparison.OrdinalIgnoreCase))
- {
- return;
- }
-
- throw new InvalidOperationException("Expected speech transcribe to surface sidecar engine error.");
- }
- finally
- {
- if (Directory.Exists(root))
- Directory.Delete(root, recursive: true);
- }
- }
-
- static async Task TestPythonSidecarSpeechServiceTimeoutAsync()
- {
- var root = Path.Combine(Path.GetTempPath(), "journal-speech-smoke", Guid.NewGuid().ToString("N"));
- Directory.CreateDirectory(root);
- var scriptPath = Path.Combine(root, "fake_speech_sidecar_timeout.py");
- File.WriteAllText(scriptPath, """
-import json, sys, time
-request = json.loads(sys.stdin.readline())
-payload = request.get("payload") or {}
-sleep_ms = int(payload.get("simulate_delay_ms") or 0)
-time.sleep(max(0, sleep_ms) / 1000.0)
-print(json.dumps({"ok": True, "data": {"text": "", "engine": "whisper"}}))
-""");
-
- try
- {
- var config = BuildAiConfig(scriptPath, timeoutMs: 100);
- ISpeechBridgeService service = new PythonSidecarSpeechService(config);
-
- try
- {
- _ = await service.TranscribeAsync(new SpeechTranscribeRequestDto(Text: "fixture", SimulateDelayMs: 500));
- }
- catch (TimeoutException)
- {
- return;
- }
-
- throw new InvalidOperationException("Expected speech transcribe timeout path.");
- }
- finally
- {
- if (Directory.Exists(root))
- Directory.Delete(root, recursive: true);
- }
- }
}
diff --git a/Journal.SmokeTests/Program.DatabaseConfigTests.cs b/Journal.SmokeTests/Program.DatabaseConfigTests.cs
index 5ddcf9d..9dfeb32 100644
--- a/Journal.SmokeTests/Program.DatabaseConfigTests.cs
+++ b/Journal.SmokeTests/Program.DatabaseConfigTests.cs
@@ -120,9 +120,6 @@ internal static partial class Program
Assert(current.SpeechRecognitionEngine == "whisper", "Config SpeechRecognitionEngine default mismatch.");
Assert(current.WhisperModelSize == "base", "Config WhisperModelSize default mismatch.");
Assert(current.AiProvider == "none", "Config AiProvider default mismatch.");
- Assert(current.PythonExecutable == "python", "Config PythonExecutable default mismatch.");
- Assert(current.AiSidecarTimeoutMs == 45000, "Config AiSidecarTimeoutMs default mismatch.");
- Assert(current.PythonAiSidecarPath.EndsWith(Path.Combine("journal", "ai", "sidecar.py"), StringComparison.OrdinalIgnoreCase), "Config PythonAiSidecarPath default mismatch.");
return Task.CompletedTask;
}
diff --git a/Journal.SmokeTests/Program.Shared.cs b/Journal.SmokeTests/Program.Shared.cs
index 1f07488..ed1a5cb 100644
--- a/Journal.SmokeTests/Program.Shared.cs
+++ b/Journal.SmokeTests/Program.Shared.cs
@@ -160,22 +160,6 @@ fragment three
}
}
- static JournalConfig BuildAiConfig(string sidecarScriptPath, int timeoutMs)
- {
- var baseConfig = new JournalConfigService().Current;
- var pythonExe = Environment.GetEnvironmentVariable("JOURNAL_PYTHON_EXE");
- if (string.IsNullOrWhiteSpace(pythonExe))
- pythonExe = "python";
-
- return baseConfig with
- {
- AiProvider = "python-sidecar",
- PythonExecutable = pythonExe,
- PythonAiSidecarPath = sidecarScriptPath,
- AiSidecarTimeoutMs = timeoutMs
- };
- }
-
static async Task> LoadTransportFixturesAsync()
{
var path = Path.Combine(AppContext.BaseDirectory, "Fixtures", "transport_cases.json");
diff --git a/Journal.SmokeTests/Program.cs b/Journal.SmokeTests/Program.cs
index 88d2c88..3640da5 100644
--- a/Journal.SmokeTests/Program.cs
+++ b/Journal.SmokeTests/Program.cs
@@ -63,11 +63,6 @@ internal static partial class Program
("Entry ai.embed returns empty vector when disabled", TestEntryAiEmbedDisabledAsync),
("Entry speech.devices.list returns envelope when disabled", TestEntrySpeechDevicesListDisabledAsync),
("Entry speech.transcribe returns envelope when disabled", TestEntrySpeechTranscribeDisabledAsync),
- ("Python sidecar AI service parses last JSON line", TestPythonSidecarAiServiceJsonLineAsync),
- ("Python sidecar AI service surfaces sidecar errors", TestPythonSidecarAiServiceErrorAsync),
- ("Python sidecar speech service handles empty devices payload", TestPythonSidecarSpeechServiceNoDevicesAsync),
- ("Python sidecar speech service surfaces unavailable engine errors", TestPythonSidecarSpeechServiceErrorAsync),
- ("Python sidecar speech service times out deterministically", TestPythonSidecarSpeechServiceTimeoutAsync),
("Entry vault.load_all succeeds for empty vault directory", TestEntryVaultLoadAllEmptyAsync),
("Entry vault.load_all wrong password keeps database session locked", TestEntryVaultLoadWrongPasswordKeepsSessionLockedAsync),
("Entry vault.clear_data_directory compatibility command succeeds", TestEntryVaultClearDataDirectoryAsync),