journal/Journal.Core/Services/Config/JournalConfigService.cs
Jacob Schmidt 0d77300c22 feat: Project Journal backend monorepo
Monorepo with centralized build props, npm workspaces, LlamaSharp AI,
SQLite/SQLCipher storage, Svelte frontend, and unified smoke tests.

Co-Authored-By: Oz <oz-agent@warp.dev>
2026-03-02 20:56:26 -06:00

113 lines
4.9 KiB
C#

using Journal.Core.Models;
namespace Journal.Core.Services.Config;
public sealed class JournalConfigService : IJournalConfigService
{
public JournalConfig Current => BuildConfig();
private static JournalConfig BuildConfig()
{
var projectRoot = ResolveProjectRoot();
var appDirectory = ResolvePath("JOURNAL_APP_DIR", Path.Combine(projectRoot, "journal"));
var vaultDirectory = ResolvePath("JOURNAL_VAULT_DIR", Path.Combine(appDirectory, "vault"));
var logDirectory = ResolvePath("JOURNAL_LOG_DIR", Path.Combine(projectRoot, "logs"));
var pidFile = ResolvePath("JOURNAL_PID_FILE", Path.Combine(logDirectory, "nicegui_server.pid"));
var serverControlFile = ResolvePath("JOURNAL_SERVER_CONTROL_FILE", Path.Combine(logDirectory, "server_control.action"));
var nlpBackend = (Environment.GetEnvironmentVariable("JOURNAL_NLP_BACKEND") ?? "auto").Trim().ToLowerInvariant();
if (nlpBackend is not ("auto" or "spacy" or "fallback"))
nlpBackend = "auto";
var aiProvider = (Environment.GetEnvironmentVariable("JOURNAL_AI_PROVIDER") ?? "llamasharp").Trim().ToLowerInvariant();
if (aiProvider is not ("none" or "llamasharp"))
aiProvider = "none";
return new JournalConfig(
ProjectRoot: projectRoot,
AppDirectory: appDirectory,
VaultDirectory: vaultDirectory,
LogDirectory: logDirectory,
PidFile: pidFile,
ServerControlFile: serverControlFile,
DatabaseFilename: Environment.GetEnvironmentVariable("JOURNAL_DATABASE_FILENAME") ?? "journal_cache.db",
CloudAiApiKey: Environment.GetEnvironmentVariable("CLOUDAI_API_KEY") ?? "",
CloudAiApiUrl: Environment.GetEnvironmentVariable("CLOUDAI_API_URL") ?? "",
LlamaCppUrl: Environment.GetEnvironmentVariable("LLAMA_CPP_URL") ?? "http://127.0.0.1:8085/v1/completions",
LlamaCppModel: Environment.GetEnvironmentVariable("LLAMA_CPP_MODEL") ?? "qwen/qwen3-4b",
LlamaCppTimeout: ParseInt("LLAMA_CPP_TIMEOUT", 6000),
EmbeddingApiUrl: Environment.GetEnvironmentVariable("EMBEDDING_API_URL") ?? "http://127.0.0.1:8086/v1/embeddings",
EmbeddingModelName: Environment.GetEnvironmentVariable("EMBEDDING_MODEL_NAME") ?? "text-embedding-nomic-embed-text-v2-moe",
ModelContextTokens: ParseInt("MODEL_CONTEXT_TOKENS", 131072),
ChunkTokenBudget: ParseInt("CHUNK_TOKEN_BUDGET", 120000),
MicrophoneDeviceIndex: ParseNullableInt("MICROPHONE_DEVICE_INDEX"),
SpeechRecognitionEngine: Environment.GetEnvironmentVariable("SPEECH_RECOGNITION_ENGINE") ?? "whisper",
WhisperModelSize: Environment.GetEnvironmentVariable("WHISPER_MODEL_SIZE") ?? "base",
NlpBackend: nlpBackend,
AiProvider: aiProvider,
GgufModelPath: ResolveGgufModelPath(projectRoot));
}
private static string ResolveProjectRoot()
{
var envRoot = Environment.GetEnvironmentVariable("JOURNAL_PROJECT_ROOT");
if (!string.IsNullOrWhiteSpace(envRoot))
return Path.GetFullPath(envRoot);
var cwd = Directory.GetCurrentDirectory();
if (Directory.Exists(Path.Combine(cwd, "journal")))
return Path.GetFullPath(cwd);
var upOne = Path.GetFullPath(Path.Combine(cwd, ".."));
if (Directory.Exists(Path.Combine(upOne, "journal")))
return upOne;
var upTwo = Path.GetFullPath(Path.Combine(cwd, "..", ".."));
if (Directory.Exists(Path.Combine(upTwo, "journal")))
return upTwo;
return Path.GetFullPath(cwd);
}
private static string ResolvePath(string envVar, string defaultPath)
{
var value = Environment.GetEnvironmentVariable(envVar);
var raw = string.IsNullOrWhiteSpace(value) ? defaultPath : value;
return Path.GetFullPath(raw);
}
private static int ParseInt(string envVar, int defaultValue)
{
var value = Environment.GetEnvironmentVariable(envVar);
return int.TryParse(value, out var parsed) ? parsed : defaultValue;
}
private static int? ParseNullableInt(string envVar)
{
var value = Environment.GetEnvironmentVariable(envVar);
if (string.IsNullOrWhiteSpace(value))
return null;
return int.TryParse(value, out var parsed) ? parsed : null;
}
private static string ResolveGgufModelPath(string projectRoot)
{
var fromEnv = Environment.GetEnvironmentVariable("JOURNAL_GGUF_MODEL_PATH");
if (!string.IsNullOrWhiteSpace(fromEnv))
return Path.GetFullPath(fromEnv);
var modelsDir = Path.Combine(projectRoot, "models");
if (Directory.Exists(modelsDir))
{
var first = Directory.EnumerateFiles(modelsDir, "*.gguf").FirstOrDefault();
if (first is not null)
return Path.GetFullPath(first);
}
return Path.Combine(modelsDir, "model.gguf");
}
}