- Re-architect VoiceSession into full-bleed studio cockpit with overlay slide-over companion drawer - Eliminate width squishing defect on LiveInsightPanel and add native dark/light theme support - Integrate authentic Google Gemini 3.8 Flash TTS with Fenrir persona and fallback audio pipeline - Implement comprehensive Playwright E2E visual regression suite across Desktop, iPad, and iPhone (21 tests, 45 snapshots) - Migrate core application to Svelte 5 runes (, , ) with reactive stores - Maintain .NET 8 Azure Functions serverless endpoints and clean build hygiene
140 lines
5.5 KiB
C#
140 lines
5.5 KiB
C#
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using Microsoft.Azure.Functions.Worker;
|
|
using Microsoft.Azure.Functions.Worker.Http;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace CognitiveClarity.api
|
|
{
|
|
public class AnalysisRequest
|
|
{
|
|
[JsonPropertyName("turnContext")]
|
|
public string TurnContext { get; set; } = string.Empty;
|
|
|
|
[JsonPropertyName("scenarioTitle")]
|
|
public string ScenarioTitle { get; set; } = string.Empty;
|
|
|
|
[JsonPropertyName("scenarioDescription")]
|
|
public string ScenarioDescription { get; set; } = string.Empty;
|
|
|
|
[JsonPropertyName("cbtFocus")]
|
|
public List<string> CBTFocus { get; set; } = new();
|
|
|
|
[JsonPropertyName("model")]
|
|
public string Model { get; set; } = "google/gemini-3.8-flash";
|
|
}
|
|
|
|
public class AnalysisFunction
|
|
{
|
|
private readonly ILogger _logger;
|
|
private readonly IHttpClientFactory _httpClientFactory;
|
|
|
|
public AnalysisFunction(ILoggerFactory loggerFactory, IHttpClientFactory httpClientFactory)
|
|
{
|
|
_logger = loggerFactory.CreateLogger<AnalysisFunction>();
|
|
_httpClientFactory = httpClientFactory;
|
|
}
|
|
|
|
[Function("ClinicalAnalysis")]
|
|
public async Task<HttpResponseData> Run(
|
|
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "analysis")] HttpRequestData req)
|
|
{
|
|
_logger.LogInformation("C# HTTP trigger function processed a clinical analysis request via OpenRouter.");
|
|
|
|
var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
|
|
var data = JsonSerializer.Deserialize<AnalysisRequest>(requestBody);
|
|
|
|
if (data == null || string.IsNullOrWhiteSpace(data.TurnContext))
|
|
{
|
|
var badResponse = req.CreateResponse(HttpStatusCode.BadRequest);
|
|
await badResponse.WriteStringAsync("Please provide turnContext in the request body");
|
|
return badResponse;
|
|
}
|
|
|
|
var apiKey = System.Environment.GetEnvironmentVariable("OPENROUTER_API_KEY");
|
|
if (string.IsNullOrEmpty(apiKey))
|
|
{
|
|
_logger.LogError("OPENROUTER_API_KEY is missing from environment variables.");
|
|
return req.CreateResponse(HttpStatusCode.InternalServerError);
|
|
}
|
|
|
|
var systemAnalysisPrompt = $@"You are a master clinical psychologist specializing in Cognitive Behavioral Therapy (CBT).
|
|
You are supervising an interactive CBT simulation.
|
|
SCENARIO: {data.ScenarioTitle} - {data.ScenarioDescription}
|
|
FOCUS AREAS: {string.Join(", ", data.CBTFocus)}
|
|
|
|
Return ONLY a valid JSON object matching this schema:
|
|
{{
|
|
""analysis"": {{
|
|
""distortions"": [""string""],
|
|
""isAdaptive"": boolean,
|
|
""evidence"": ""string""
|
|
}},
|
|
""rating"": {{
|
|
""score"": number (1-5),
|
|
""reason"": ""string""
|
|
}},
|
|
""suggestions"": {{
|
|
""alternativeThought"": ""string"",
|
|
""behavioralStep"": ""string""
|
|
}}
|
|
}}";
|
|
|
|
var payload = new
|
|
{
|
|
model = string.IsNullOrEmpty(data.Model) ? "google/gemini-3.8-flash" : data.Model,
|
|
messages = new object[]
|
|
{
|
|
new { role = "system", content = systemAnalysisPrompt },
|
|
new { role = "user", content = data.TurnContext }
|
|
},
|
|
response_format = new { type = "json_object" },
|
|
temperature = 0.2
|
|
};
|
|
|
|
try
|
|
{
|
|
var client = _httpClientFactory.CreateClient();
|
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
|
|
client.DefaultRequestHeaders.Add("HTTP-Referer", "https://cognitive-clarity.app");
|
|
client.DefaultRequestHeaders.Add("X-Title", "Cognitive Clarity");
|
|
|
|
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
|
|
var response = await client.PostAsync("https://openrouter.ai/api/v1/chat/completions", content);
|
|
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
var errText = await response.Content.ReadAsStringAsync();
|
|
_logger.LogError("OpenRouter returned error {Status}: {Error}", response.StatusCode, errText);
|
|
var errResponse = req.CreateResponse(response.StatusCode);
|
|
await errResponse.WriteStringAsync(errText);
|
|
return errResponse;
|
|
}
|
|
|
|
var resJson = await response.Content.ReadAsStringAsync();
|
|
using var doc = JsonDocument.Parse(resJson);
|
|
var rawResult = doc.RootElement
|
|
.GetProperty("choices")[0]
|
|
.GetProperty("message")
|
|
.GetProperty("content")
|
|
.GetString() ?? "{}";
|
|
|
|
var successResponse = req.CreateResponse(HttpStatusCode.OK);
|
|
successResponse.Headers.Add("Content-Type", "application/json");
|
|
await successResponse.WriteStringAsync(rawResult);
|
|
return successResponse;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to generate analysis via OpenRouter API.");
|
|
var errorResponse = req.CreateResponse(HttpStatusCode.InternalServerError);
|
|
await errorResponse.WriteStringAsync("Error communicating with OpenRouter API.");
|
|
return errorResponse;
|
|
}
|
|
}
|
|
}
|
|
}
|