diff --git a/.gitignore b/.gitignore
index 7a1e5ac79..1a9d879a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,4 @@
**/obj
*.DotSettings.user
**/.idea/
-
+**/.env
diff --git a/csharp/App/Backend/Backend.csproj b/csharp/App/Backend/Backend.csproj
index 518c98e37..3021dc3a3 100644
--- a/csharp/App/Backend/Backend.csproj
+++ b/csharp/App/Backend/Backend.csproj
@@ -43,6 +43,9 @@
PreserveNewest
+
+ PreserveNewest
+
diff --git a/csharp/App/Backend/Controller.cs b/csharp/App/Backend/Controller.cs
index 86c87633d..bf0fb6383 100644
--- a/csharp/App/Backend/Controller.cs
+++ b/csharp/App/Backend/Controller.cs
@@ -741,7 +741,7 @@ public class Controller : ControllerBase
///
/// Returns an AI-generated diagnosis for a single error/alarm description.
/// Responses are cached in memory — repeated calls for the same error code
- /// do not hit OpenAI again.
+ /// do not hit Mistral again.
///
[HttpGet(nameof(DiagnoseError))]
public async Task> DiagnoseError(Int64 installationId, string errorDescription, Token authToken)
@@ -791,7 +791,7 @@ public class Controller : ControllerBase
"Warning 500",
"Error 408",
"AFCI Fault",
- // Unknown alarm (should return null - would call OpenAI)
+ // Unknown alarm (should return null - would call Mistral)
"Some unknown alarm XYZ123"
};
@@ -803,7 +803,7 @@ public class Controller : ControllerBase
{
Alarm = alarm,
FoundInKnowledgeBase = diagnosis != null,
- Explanation = diagnosis?.Explanation ?? "NOT FOUND - Would call OpenAI API",
+ Explanation = diagnosis?.Explanation ?? "NOT FOUND - Would call Mistral API",
CausesCount = diagnosis?.Causes.Count ?? 0,
NextStepsCount = diagnosis?.NextSteps.Count ?? 0
});
@@ -814,7 +814,7 @@ public class Controller : ControllerBase
TestTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
TotalTests = testCases.Length,
FoundInKnowledgeBase = results.Count(r => ((dynamic)r).FoundInKnowledgeBase),
- WouldCallOpenAI = results.Count(r => !((dynamic)r).FoundInKnowledgeBase),
+ WouldCallMistral = results.Count(r => !((dynamic)r).FoundInKnowledgeBase),
Results = results
});
}
diff --git a/csharp/App/Backend/Program.cs b/csharp/App/Backend/Program.cs
index 56208e0f1..8bc1da94c 100644
--- a/csharp/App/Backend/Program.cs
+++ b/csharp/App/Backend/Program.cs
@@ -25,6 +25,7 @@ public static class Program
Watchdog.NotifyReady();
Db.Init();
+ LoadEnvFile();
DiagnosticService.Initialize();
var builder = WebApplication.CreateBuilder(args);
@@ -89,6 +90,33 @@ public static class Program
app.Run();
}
+ private static void LoadEnvFile()
+ {
+ var envPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ".env");
+
+ if (!File.Exists(envPath))
+ envPath = ".env"; // fallback for dev
+
+ if (!File.Exists(envPath))
+ return;
+
+ foreach (var line in File.ReadAllLines(envPath))
+ {
+ var trimmed = line.Trim();
+ if (trimmed.Length == 0 || trimmed.StartsWith('#'))
+ continue;
+
+ var idx = trimmed.IndexOf('=');
+ if (idx <= 0)
+ continue;
+
+ var key = trimmed[..idx].Trim();
+ var value = trimmed[(idx + 1)..].Trim();
+
+ Environment.SetEnvironmentVariable(key, value);
+ }
+ }
+
private static OpenApiInfo OpenApiInfo { get; } = new OpenApiInfo
{
Title = "Inesco Backend API",
diff --git a/csharp/App/Backend/Resources/openAiConfig.json b/csharp/App/Backend/Resources/openAiConfig.json
deleted file mode 100644
index 43338afc4..000000000
--- a/csharp/App/Backend/Resources/openAiConfig.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "ApiKey": "sk-your-openai-api-key-here"
-}
diff --git a/csharp/App/Backend/Services/AlarmKnowledgeBase.cs b/csharp/App/Backend/Services/AlarmKnowledgeBase.cs
index 09507fc87..6573f36c4 100644
--- a/csharp/App/Backend/Services/AlarmKnowledgeBase.cs
+++ b/csharp/App/Backend/Services/AlarmKnowledgeBase.cs
@@ -4,7 +4,7 @@ namespace InnovEnergy.App.Backend.Services;
///
/// Static knowledge base for Sinexcel and Growatt alarms.
-/// Provides pre-defined diagnostics without requiring OpenAI API calls.
+/// Provides pre-defined diagnostics without requiring Mistral API calls.
/// Data sourced from vendor alarm documentation.
///
public static class AlarmKnowledgeBase
diff --git a/csharp/App/Backend/Services/DiagnosticService.cs b/csharp/App/Backend/Services/DiagnosticService.cs
index ef9f0b22e..b2948f3de 100644
--- a/csharp/App/Backend/Services/DiagnosticService.cs
+++ b/csharp/App/Backend/Services/DiagnosticService.cs
@@ -7,7 +7,7 @@ using Newtonsoft.Json;
namespace InnovEnergy.App.Backend.Services;
///
-/// Calls OpenAI to generate plain-English diagnostics for errors/warnings.
+/// Calls Mistral AI to generate plain-English diagnostics for errors/warnings.
/// Caches responses in-memory keyed by error description so the same
/// error code is only sent to the API once.
///
@@ -22,30 +22,15 @@ public static class DiagnosticService
public static void Initialize()
{
- var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "openAiConfig.json");
+ var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY");
- if (!File.Exists(configPath))
+ if (string.IsNullOrWhiteSpace(apiKey))
{
- // Fallback: look relative to the working directory (useful in dev)
- configPath = Path.Combine("Resources", "openAiConfig.json");
- }
-
- if (!File.Exists(configPath))
- {
- Console.Error.WriteLine("[DiagnosticService] openAiConfig.json not found – AI diagnostics disabled.");
+ Console.Error.WriteLine("[DiagnosticService] MISTRAL_API_KEY not set – AI diagnostics disabled.");
return;
}
- var json = File.ReadAllText(configPath);
- var config = JsonConvert.DeserializeObject(json);
-
- if (config is null || string.IsNullOrWhiteSpace(config.ApiKey))
- {
- Console.Error.WriteLine("[DiagnosticService] ApiKey is empty – AI diagnostics disabled.");
- return;
- }
-
- _apiKey = config.ApiKey;
+ _apiKey = apiKey;
Console.WriteLine("[DiagnosticService] initialised.");
}
@@ -56,7 +41,7 @@ public static class DiagnosticService
///
/// Returns a diagnosis for .
/// First checks the static AlarmKnowledgeBase for known Sinexcel/Growatt alarms.
- /// Falls back to in-memory cache, then calls OpenAI only for unknown alarms.
+ /// Falls back to in-memory cache, then calls Mistral AI only for unknown alarms.
///
public static async Task DiagnoseAsync(Int64 installationId, string errorDescription)
{
@@ -89,10 +74,10 @@ public static class DiagnosticService
.Take(5)
.ToList();
- // 5. Build prompt and call OpenAI API (only for unknown alarms)
- Console.WriteLine($"[DiagnosticService] Calling OpenAI for unknown alarm: {errorDescription}");
+ // 5. Build prompt and call Mistral API (only for unknown alarms)
+ Console.WriteLine($"[DiagnosticService] Calling Mistral for unknown alarm: {errorDescription}");
var prompt = BuildPrompt(errorDescription, productName, recentDescriptions);
- var response = await CallOpenAiAsync(prompt);
+ var response = await CallMistralAsync(prompt);
if (response is null) return null;
@@ -121,17 +106,17 @@ Reply with ONLY valid JSON, no markdown:
";
}
- // ── OpenAI HTTP call ────────────────────────────────────────────
+ // ── Mistral HTTP call ────────────────────────────────────────────
- private static readonly string OpenAiUrl = "https://api.openai.com/v1/chat/completions";
+ private static readonly string MistralUrl = "https://api.mistral.ai/v1/chat/completions";
- private static async Task CallOpenAiAsync(string userPrompt)
+ private static async Task CallMistralAsync(string userPrompt)
{
try
{
var requestBody = new
{
- model = "gpt-4o-mini", // cost-efficient, fast; swap to "gpt-4" if quality needs tuning
+ model = "mistral-small-latest", // cost-efficient, fast; swap to "mistral-large-latest" if quality needs tuning
messages = new[]
{
new { role = "user", content = userPrompt }
@@ -140,19 +125,19 @@ Reply with ONLY valid JSON, no markdown:
temperature = 0.2 // low temperature for factual consistency
};
- var responseText = await OpenAiUrl
+ var responseText = await MistralUrl
.SetHeader("Authorization", $"Bearer {_apiKey}")
.SetHeader("Content-Type", "application/json")
.PostJsonAsync(requestBody)
.ReceiveString();
- // parse OpenAI envelope
+ // parse Mistral envelope (same structure as OpenAI)
var envelope = JsonConvert.DeserializeObject(responseText);
var content = (string?) envelope?.choices?[0]?.message?.content;
if (string.IsNullOrWhiteSpace(content))
{
- Console.Error.WriteLine("[DiagnosticService] OpenAI returned empty content.");
+ Console.Error.WriteLine("[DiagnosticService] Mistral returned empty content.");
return null;
}
@@ -175,11 +160,6 @@ Reply with ONLY valid JSON, no markdown:
// ── config / response models ────────────────────────────────────────────────
-public class OpenAiConfig
-{
- public string ApiKey { get; set; } = "";
-}
-
public class DiagnosticResponse
{
public string Explanation { get; set; } = "";