diff --git a/ClinicalCoach.php b/ClinicalCoach.php index 149bc5a..16ef81d 100644 --- a/ClinicalCoach.php +++ b/ClinicalCoach.php @@ -92,39 +92,88 @@ public function redcap_module_ajax($action, $payload, $project_id, $record, $ins case "callAI": $messages = $payload; - // Retrieve the system context reflection from project settings - $reflection_context = $this->getProjectSetting("system_context_reflection_1"); + // Retrieve the main system context reflection from project settings + $main_system_context = $this->getProjectSetting("system_context_summarize"); + + // Reflection contexts (could be null, so we check them before appending) + $reflection_contexts = [ + $this->getProjectSetting("system_context_reflection_1"), + $this->getProjectSetting("system_context_reflection_2"), + $this->getProjectSetting("system_context_reflection_3"), + $this->getProjectSetting("system_context_reflection_4"), + $this->getProjectSetting("system_context_reflection_5"), + $this->getProjectSetting("system_context_reflection_6") + ]; + + // Array to hold results from all API calls + $allResults = []; + + // TODO NEED TO ENGIENEER PROMPT TO GIVE RELATIVE SCORE? + function assignScore($responseContent) { + // Example logic: scoring based on the length of the content + $length = strlen($responseContent); + if ($length < 100) { + return 1; // Bad response + } elseif ($length < 300) { + return 2; // Average response + } else { + return 3; // Good response + } + } - // Use the appendSystemContext function to handle system context - $messages = $this->appendSystemContext($messages, $reflection_context); + // Loop through each reflection context + foreach ($reflection_contexts as $index => $reflection_context) { + if (!empty($reflection_context)) { + // Append main system context and current reflection context + $currentMessages = $this->appendSystemContext($messages, $main_system_context); + $currentMessages = $this->appendSystemContext($currentMessages, $reflection_context); - $this->emDebug("chatml Messages array to API", $messages); + $this->emDebug("chatml Messages array to API for reflection context " . ($index + 1), $currentMessages); - // CALL API ENDPOINT WITH AUGMENTED CHATML - $model = "gpt-4o"; - $params = array("messages" => $messages); + // Prepare parameters for the API call + $model = "gpt-4o"; + $params = array("messages" => $currentMessages); - if ($this->getProjectSetting("gpt-temperature")) { - $params["temperature"] = floatval($this->getProjectSetting("gpt-temperature")); - } - if ($this->getProjectSetting("gpt-top-p")) { - $params["top_p"] = floatval($this->getProjectSetting("gpt-top-p")); - } - if ($this->getProjectSetting("gpt-frequency-penalty")) { - $params["frequency_penalty"] = floatval($this->getProjectSetting("gpt-frequency-penalty")); - } - if ($this->getProjectSetting("presence_penalty")) { - $params["presence_penalty"] = floatval($this->getProjectSetting("presence_penalty")); - } - if ($this->getProjectSetting("gpt-max-tokens")) { - $params["max_tokens"] = intval($this->getProjectSetting("gpt-max-tokens")); + if ($this->getProjectSetting("gpt-temperature")) { + $params["temperature"] = floatval($this->getProjectSetting("gpt-temperature")); + } + if ($this->getProjectSetting("gpt-top-p")) { + $params["top_p"] = floatval($this->getProjectSetting("gpt-top-p")); + } + if ($this->getProjectSetting("gpt-frequency-penalty")) { + $params["frequency_penalty"] = floatval($this->getProjectSetting("gpt-frequency-penalty")); + } + if ($this->getProjectSetting("presence_penalty")) { + $params["presence_penalty"] = floatval($this->getProjectSetting("presence_penalty")); + } + if ($this->getProjectSetting("gpt-max-tokens")) { + $params["max_tokens"] = intval($this->getProjectSetting("gpt-max-tokens")); + } + + // Make the API call for the current context + $response = $this->getSecureChatInstance()->callAI($model, $params, PROJECT_ID); + $result = $this->formatResponse($response); + + // Extract the response content for scoring + $responseContent = $result['response']['content'] ?? ''; + $score = assignScore($responseContent); + + // Add the result to the allResults array, including the score + $allResults[] = [ + "reflection_context" => "Reflection " . ($index + 1), + "response" => $result, + "score" => $score + ]; + + $this->emDebug("API result for reflection context " . ($index + 1), $result); + } } - $response = $this->getSecureChatInstance()->callAI($model, $params, PROJECT_ID); - $result = $this->formatResponse($response); + // Return all results as a JSON array + $this->emDebug("All API results", $allResults); + return json_encode($allResults); + - $this->emDebug("calling SecureChatAI.callAI()", $result); - return json_encode($result); case "transcribeAudio": $messages = $payload; diff --git a/MVP/src/assets/mvp.css b/MVP/src/assets/mvp.css index 668e7d1..7bdd1f0 100644 --- a/MVP/src/assets/mvp.css +++ b/MVP/src/assets/mvp.css @@ -272,3 +272,24 @@ button:hover { font-style: italic; } +.status-indicator { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 0; +} + +.status-bar { + flex:1; + min-width: 60px; + height: 10px; + margin-bottom: 5px; + margin-right: 1px; +} + +.status-label { + width: 100%; + font-size: 65%; + color: black; + text-align: center; +} diff --git a/MVP/src/components/Home.jsx b/MVP/src/components/Home.jsx index f3721d3..c0a287a 100644 --- a/MVP/src/components/Home.jsx +++ b/MVP/src/components/Home.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import StatusIndicator from './StatusIndicator'; import { useStudents } from '../contexts/Students'; import { FaUserCircle } from 'react-icons/fa'; @@ -16,31 +17,58 @@ function Home() { { role: "user", content: selectedStudent.transcription } ]; - console.log("chatmlPayload", chatmlPayload); - + // Call AI function and handle the callback callAI(chatmlPayload, (aiContent) => { - console.log("aiContent callback", aiContent); - if (aiContent) { - updateAIResponse(aiContent); // Store AI response in context + if (aiContent && Array.isArray(aiContent)) { + const updatedStudent = { + ...selectedStudent, + aiResponse: aiContent // Store the AI response in the student data + }; + + console.log("HOME handleSubmitToAI Updating student with AI response:", updatedStudent); + + // Instead of using updateStudentData, use updateAIResponse + updateAIResponse(selectedStudent.id, aiContent); } else { - console.error("Failed to get a response from the AI."); + console.error("HOME handleSubmitToAI No content received from AI or invalid format"); } }); }; + const callAI = (chatmlPayload, callback) => { - window.clicnical_coach_jsmo_module.callAI(chatmlPayload, (res) => { - if (res) { - if (callback) callback(res); - } else { - console.log("Unexpected AI response format:", res); + window.clicnical_coach_jsmo_module.callAI( + chatmlPayload, + (res) => { + if (!res) { + console.error("HOME callAI Response is null or undefined"); + callback(undefined); // Pass undefined to indicate failure + return; + } + + try { + const parsedRes = Array.isArray(res) ? res : typeof res === 'object' ? res : JSON.parse(res); + + if (Array.isArray(parsedRes)) { + console.log("HOME callAI Parsed response before callback:", parsedRes); + callback(parsedRes); + } else { + console.error("HOME callAI Unexpected response format:", parsedRes); + callback(undefined); // Call with undefined for failure + } + } catch (error) { + console.error("HOME callAI Error parsing response:", error); + callback(undefined); // Call with undefined for failure + } + }, + (err) => { + console.error("HOME callAI AI call failed:", err); + callback(undefined); // Ensure callback is called on error } - }, (err) => { - console.log("callAI error:", err); - if (callback) callback(); - }); + ); }; + return (
@@ -59,13 +87,14 @@ function Home() {

Session Completed

+ + {selectedStudent.transcription && (
{selectedStudent.transcription}
+
)} - -
) : ( diff --git a/MVP/src/components/StatusIndicator.jsx b/MVP/src/components/StatusIndicator.jsx new file mode 100644 index 0000000..5d23d26 --- /dev/null +++ b/MVP/src/components/StatusIndicator.jsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { useStudents } from '../contexts/Students'; // Adjust the import path as necessary + +const scoreColors = { + 3: 'green', + 2: '#FFD700', + 1: 'red' +}; + +const StatusIndicator = () => { + const { selectedStudent } = useStudents(); + + if (!selectedStudent || !selectedStudent.reflections) { + return
No data available
; + } + + const { reflections } = selectedStudent; + + return ( +
+ {Object.entries(reflections).map(([key, { score }]) => ( +
+
+
+ {key.charAt(0).toUpperCase() + key.slice(1)} +
+
+ ))} +
+ ); +}; + +export default StatusIndicator; diff --git a/MVP/src/components/VoiceRecorder.jsx b/MVP/src/components/VoiceRecorder.jsx index 31d2470..f74a5ae 100644 --- a/MVP/src/components/VoiceRecorder.jsx +++ b/MVP/src/components/VoiceRecorder.jsx @@ -97,19 +97,19 @@ const VoiceRecorder = ({ setIsRecording, handleStopRecording }) => { if (silenceStartRef.current === null) { silenceStartRef.current = Date.now(); silenceDurationRef.current = 0; - console.log(`Silence started at: ${silenceStartRef.current}`); + // console.log(`Silence started at: ${silenceStartRef.current}`); } else { const silenceDurationElapsed = Date.now() - silenceStartRef.current; silenceDurationRef.current += 100; // console.log(`Silence duration elapsed: ${silenceDurationRef.current}ms`); if (silenceDurationRef.current >= silenceDurationMs) { - console.log('Silence threshold reached, stopping recording'); + // console.log('Silence threshold reached, stopping recording'); stopRecording(); // Stop the recording when silence is detected } } } else { if (silenceStartRef.current !== null) { - console.log('Silence ended, resetting silenceStartRef'); + // console.log('Silence ended, resetting silenceStartRef'); silenceStartRef.current = null; silenceDurationRef.current = 0; } diff --git a/MVP/src/contexts/Students.jsx b/MVP/src/contexts/Students.jsx index 22f59ec..4dea3f9 100644 --- a/MVP/src/contexts/Students.jsx +++ b/MVP/src/contexts/Students.jsx @@ -9,13 +9,12 @@ export const useStudents = () => useContext(StudentsContext); // Provider component to wrap the app export const StudentsProvider = ({ children }) => { const [students, setStudents] = useState([ - { id: 1, name: "John Wick" }, - { id: 2, name: "Selina Kyle" }, - { id: 3, name: "Brock Purdy" } + { id: 1, name: "John Wick", reflections: {} }, + { id: 2, name: "Selina Kyle", reflections: {} }, + { id: 3, name: "Brock Purdy", reflections: {} } ]); const [selectedStudent, setSelectedStudent] = useState(null); - const [aiResponse, setAiResponse] = useState(''); // Function to select a student const selectStudent = (studentId) => { @@ -24,7 +23,6 @@ export const StudentsProvider = ({ children }) => { }; const updateTranscription = (studentId, transcription) => { - console.log("updateTranscription", studentId, transcription); setStudents((prevStudents) => prevStudents.map((student) => student.id === studentId ? { ...student, transcription } : student @@ -32,11 +30,54 @@ export const StudentsProvider = ({ children }) => { ); }; - const updateAIResponse = (response) => { - setAiResponse(response); + // Function to update AI reflections for the student + const updateAIResponse = (studentId, reflectionResults) => { + console.log("updateAIResponse - studentId:", studentId); + console.log("updateAIResponse - reflectionResults:", reflectionResults); + + setStudents((prevStudents) => { + const updatedStudents = prevStudents.map((student) => + student.id === studentId + ? { + ...student, + reflections: { + strategy: { + content: reflectionResults.find((r) => r.reflection_context === "Reflection 1")?.response.response.content || "No strategy content", + score: reflectionResults.find((r) => r.reflection_context === "Reflection 1")?.score || "No strategy score" + }, + solution: { + content: reflectionResults.find((r) => r.reflection_context === "Reflection 2")?.response.response.content || "No solution content", + score: reflectionResults.find((r) => r.reflection_context === "Reflection 2")?.score || "No solution score" + }, + knowledge: { + content: reflectionResults.find((r) => r.reflection_context === "Reflection 3")?.response.response.content || "No knowledge content", + score: reflectionResults.find((r) => r.reflection_context === "Reflection 3")?.score || "No knowledge score" + }, + problem: { + content: reflectionResults.find((r) => r.reflection_context === "Reflection 4")?.response.response.content || "No problem content", + score: reflectionResults.find((r) => r.reflection_context === "Reflection 4")?.score || "No problem score" + }, + data: { + content: reflectionResults.find((r) => r.reflection_context === "Reflection 5")?.response.response.content || "No data content", + score: reflectionResults.find((r) => r.reflection_context === "Reflection 5")?.score || "No data score" + }, + mind: { + content: reflectionResults.find((r) => r.reflection_context === "Reflection 6")?.response.response.content || "No mind content", + score: reflectionResults.find((r) => r.reflection_context === "Reflection 6")?.score || "No mind score" + } + } + } + : student + ); + + console.log("updateAIResponse - updatedStudents:", updatedStudents); + + return updatedStudents; + }); }; - // Sync selectedStudent with updated transcription + + // Sync selectedStudent with updated reflection data useEffect(() => { if (selectedStudent) { const updatedStudent = students.find((s) => s.id === selectedStudent.id); @@ -50,7 +91,6 @@ export const StudentsProvider = ({ children }) => { selectedStudent, selectStudent, updateTranscription, - aiResponse, updateAIResponse }}> {children} diff --git a/assets/jsmo.js b/assets/jsmo.js index b26b85a..5424411 100644 --- a/assets/jsmo.js +++ b/assets/jsmo.js @@ -69,18 +69,31 @@ callAI: async (payload, callback, errorCallback) => { try { const res = await module.ajax('callAI', payload); + + // Log the full response for debugging + // console.log("JSMO Raw response from backend:", res); + + // Try parsing the response let parsedRes = JSON.parse(res); - if (parsedRes?.response?.content) { - // Extract the content directly and pass it to the callback + + // Log the parsed response to inspect its structure + // console.log("JSMO Parsed AI response:", parsedRes); + + // Adjust the structure checks based on the actual response format + if (Array.isArray(parsedRes)) { + // Pass the whole parsed response to the callback for further processing + callback(parsedRes); + } else if (parsedRes?.response?.content) { + // If it's a singular response with content callback(parsedRes.response.content); } else { - console.error("Failed to parse response:", res); + console.error("JSMO Unexpected response format:", parsedRes); errorCallback(res); } } catch (err) { - console.error("Error in callAI: ", err); + console.error("JSMO rror in callAI:", err); errorCallback(err); } - }, + } }); }