using System.Net; using System.Text.Json; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; using Google.GenAI; using System.Text.Json.Serialization; namespace CognitiveClarity.api { public class ChatMessage { [JsonPropertyName("role")] public string Role { get; set; } = string.Empty; // "user" or "model" [JsonPropertyName("text")] public string Text { get; set; } = string.Empty; } public class ChatRequest { [JsonPropertyName("history")] public List History { get; set; } = new(); [JsonPropertyName("systemInstruction")] public string SystemInstruction { get; set; } = string.Empty; } public class ChatFunction { private readonly ILogger _logger; public ChatFunction(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(); } [Function("ChatProgression")] public async Task Run( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "chat")] HttpRequestData req) { _logger.LogInformation("C# HTTP trigger function processed a chat progression request."); var requestBody = await new StreamReader(req.Body).ReadToEndAsync(); var data = JsonSerializer.Deserialize(requestBody); if (data == null || data.History.Count == 0) { var badResponse = req.CreateResponse(HttpStatusCode.BadRequest); await badResponse.WriteStringAsync("Please provide chat history in the request body"); return badResponse; } var apiKey = Environment.GetEnvironmentVariable("GEMINI_API_KEY"); if (string.IsNullOrEmpty(apiKey)) { _logger.LogError("GEMINI_API_KEY is missing from environment variables."); return req.CreateResponse(HttpStatusCode.InternalServerError); } // Initialize the Gemini Client var ai = new GeminiClient(apiKey: apiKey); // Construct contents from history var contents = new List(); foreach (var msg in data.History) { contents.Add(new Content { Role = msg.Role, Parts = new List { new Part { Text = msg.Text } } }); } var roleplaySystemInstruction = $@"{data.SystemInstruction} [CONTEXT] You are participating in a clinical CBT roleplay simulation. The instructions above define your character, the scenario, and your internal CBT logic. [TASK] Respond to the user naturally in character to advance the roleplay scenario. [CONSTRAINTS] - CRITICAL: Stay strictly in character as the persona defined in the scenario. - CRITICAL: NEVER provide clinical feedback, advice, or meta-commentary about the user's progress. - CRITICAL: NEVER use phrases like ""Great job holding your boundary"" or ""Feedback:"". - CRITICAL: Do not congratulate the user on using CBT techniques; simply react as the character would. - If the user successfully concludes the scenario, end the dialogue naturally. [OUTPUT REQUIREMENTS] Return the character's direct dialogue response. Keep it conversational and proportional to the user's input. "; try { var response = await ai.Models.GenerateContentAsync( model: "gemini-3.0-flash", contents: contents, new GenerateContentConfig { SystemInstruction = new Content { Parts = new List { new Part { Text = roleplaySystemInstruction } } }, // Streaming isn't natively supported in standard HTTP Functions easily without Server-Sent Events (SSE). // We will return a standard block response for now, which simplifies the architecture. }); var successResponse = req.CreateResponse(HttpStatusCode.OK); successResponse.Headers.Add("Content-Type", "application/json"); // Return just the text in a JSON wrapper var resultObj = new { text = response.Text }; await successResponse.WriteStringAsync(JsonSerializer.Serialize(resultObj)); return successResponse; } catch (Exception ex) { _logger.LogError(ex, "Failed to generate chat progression via Gemini API."); var errorResponse = req.CreateResponse(HttpStatusCode.InternalServerError); await errorResponse.WriteStringAsync("Error communicating with Gemini API."); return errorResponse; } } } }