diff --git a/task-launcher/serve/serve.js b/task-launcher/serve/serve.js index e54291b6..87bb9e11 100644 --- a/task-launcher/serve/serve.js +++ b/task-launcher/serve/serve.js @@ -61,6 +61,7 @@ const sequentialStimulus = stringToBoolean(urlParams.get('sequentialStimulus'), const storeItemId = stringToBoolean(urlParams.get('storeItemId'), false); const cat = stringToBoolean(urlParams.get('cat'), false); const heavyInstructions = stringToBoolean(urlParams.get('heavyInstructions'), false); +const debug = stringToBoolean(urlParams.get('debug'), false); const emulatorConfig = EMULATORS ? firebaseJSON.emulators : undefined; // if running in demo mode, no data will be saved to Firestore @@ -104,6 +105,7 @@ async function startWebApp() { heavyInstructions, demoMode, version, + debug, }; const taskInfo = { diff --git a/task-launcher/src/styles/layout/_containers.scss b/task-launcher/src/styles/layout/_containers.scss index 6e805006..b24ee9ba 100644 --- a/task-launcher/src/styles/layout/_containers.scss +++ b/task-launcher/src/styles/layout/_containers.scss @@ -310,3 +310,21 @@ align-items: center; gap: 30px; } + +.theta-estimate-container { + position: absolute; + top: 30px; + left: 30px; + flex-direction: column; + justify-content: center; + align-items: center; + width: 30%; + max-height: 50%; + border: 1px solid red; + border-radius: 10px; + background-color: $bg-bubble-base; + color: black; + font-size: 1.5rem; + font-weight: $font-weight-bold; + padding: 10px; +} diff --git a/task-launcher/src/taskStore/index.ts b/task-launcher/src/taskStore/index.ts index 6a5d1a55..7250ff39 100644 --- a/task-launcher/src/taskStore/index.ts +++ b/task-launcher/src/taskStore/index.ts @@ -26,6 +26,7 @@ import { InputCapability } from '../utils/detectInput'; * @property {boolean} taskComplete - Whether the task has ended - if true, the user should return to dashboard. * @property {Object} assetsPerTask - Object containing list of assets belonging to each task. * @property {boolean} demoMode - Whether the task is running in demo mode (no interaction with Firestore), default is false. + * @property {boolean} debug - Shows theta estimate on the screen for cat debugging when enabled. * @property {number} currentCatBlock - The current block number to select trials from in a CAT. * @property {number[]} blockThresholds - Array of theta thresholds. * @property {number} totalTrialCount - Total number of trials, including practice and instructions. @@ -91,6 +92,7 @@ export type TaskStoreDataType = { language?: string; maxTime?: number; demoMode: boolean; + debug: boolean; version: number; currentCatBlock?: number; blockThresholds?: number[]; @@ -147,6 +149,7 @@ export const setTaskStore = (config: TaskStoreDataType) => { testPhase: false, maxTime: config.maxTime, demoMode: config.demoMode, + debug: config.debug, version: config.version || 1, currentStoryGroup: 0, }); diff --git a/task-launcher/src/tasks/same-different-selection/trials/afcMatch.ts b/task-launcher/src/tasks/same-different-selection/trials/afcMatch.ts index 12211e6a..092aef26 100644 --- a/task-launcher/src/tasks/same-different-selection/trials/afcMatch.ts +++ b/task-launcher/src/tasks/same-different-selection/trials/afcMatch.ts @@ -17,6 +17,7 @@ import { finishExperiment } from '../../shared/trials'; import { taskStore } from '../../../taskStore'; import { updateTheta } from '../../shared/helpers'; import { sdsProgressComponentFilled, sdsProgressComponentEmpty } from '../../shared/helpers/components'; +import { displayDebugInfo } from '../../shared/helpers/displayDebugInfo'; let selectedCards: string[] = []; let selectedCardIdxs: number[] = []; @@ -333,6 +334,8 @@ export const afcMatch = (trial?: StimulusType) => { }), ); } + + displayDebugInfo(stim); }, response_ends_trial: () => { return (trial || taskStore().nextStimulus).trialType === 'instructions' && taskStore().version === 2; diff --git a/task-launcher/src/tasks/same-different-selection/trials/legacyStimulus.ts b/task-launcher/src/tasks/same-different-selection/trials/legacyStimulus.ts index 779ae6a7..30e131db 100644 --- a/task-launcher/src/tasks/same-different-selection/trials/legacyStimulus.ts +++ b/task-launcher/src/tasks/same-different-selection/trials/legacyStimulus.ts @@ -15,6 +15,7 @@ import { isTouchScreen, jsPsych } from '../../taskSetup'; import { taskStore } from '../../../taskStore'; import { handleStaggeredButtons } from '../../shared/helpers/staggerButtons'; import { updateTheta } from '../../shared/helpers'; +import { displayDebugInfo } from '../../shared/helpers/displayDebugInfo'; const replayButtonHtmlId = 'replay-btn-revisited'; let incorrectPracticeResponses: string[] = []; @@ -249,6 +250,8 @@ export const legacyStimulus = (trial?: StimulusType) => { }); }); } + + displayDebugInfo(stimulus); }, on_finish: (data: any) => { const stim = trial || taskStore().nextStimulus; diff --git a/task-launcher/src/tasks/same-different-selection/trials/stimulus.ts b/task-launcher/src/tasks/same-different-selection/trials/stimulus.ts index 546b2f6b..9c1d0a49 100644 --- a/task-launcher/src/tasks/same-different-selection/trials/stimulus.ts +++ b/task-launcher/src/tasks/same-different-selection/trials/stimulus.ts @@ -16,6 +16,7 @@ import { isTouchScreen, jsPsych } from '../../taskSetup'; import { taskStore } from '../../../taskStore'; import { updateTheta } from '../../shared/helpers'; import { shouldTerminateCat } from '../../shared/helpers/shouldTerminateCat'; +import { displayDebugInfo } from '../../shared/helpers/displayDebugInfo'; const replayButtonHtmlId = 'replay-btn-revisited'; let incorrectPracticeResponses: string[] = []; @@ -401,6 +402,8 @@ export const stimulus = (trial?: StimulusType) => { }); }); } + + displayDebugInfo(stimulus); }, on_finish: (data: any) => { PageAudioHandler.stopAndDisconnectNode(); diff --git a/task-launcher/src/tasks/shared/helpers/config.ts b/task-launcher/src/tasks/shared/helpers/config.ts index 48e040aa..6d9d5b96 100644 --- a/task-launcher/src/tasks/shared/helpers/config.ts +++ b/task-launcher/src/tasks/shared/helpers/config.ts @@ -94,6 +94,7 @@ export const setSharedConfig = async ( semThreshold, startingTheta, demoMode, + debug, version, taskVersion, // deprecated; use `version` — kept for backward compatibility } = cleanParams; @@ -127,6 +128,7 @@ export const setSharedConfig = async ( semThreshold: Number(semThreshold), startingTheta: Number(startingTheta), demoMode: !!demoMode, + debug: !!debug, version: Number((version ?? taskVersion) || 1), displayPromptDurations: {}, }; diff --git a/task-launcher/src/tasks/shared/helpers/displayDebugInfo.ts b/task-launcher/src/tasks/shared/helpers/displayDebugInfo.ts new file mode 100644 index 00000000..7eba5c77 --- /dev/null +++ b/task-launcher/src/tasks/shared/helpers/displayDebugInfo.ts @@ -0,0 +1,34 @@ +import { taskStore } from "../../../taskStore"; +import { jsPsych, cat } from "../../taskSetup"; + +function isRealTrial(trial: any) { + return ( + trial.assessment_stage && + trial.assessment_stage !== 'practice_response' && + trial.assessment_stage !== 'instructions' + ); +} + +export function displayDebugInfo(stim: StimulusType) { + if (taskStore().debug && taskStore().runCat) { + const lastRealTrial = jsPsych.data.get().filterCustom(isRealTrial).last().values()[0]; + const thetaEstimate = cat.theta; + const currentTrialDifficulty = stim.difficulty; + const CurrentTrialUid = stim.itemUid; + + let previousResponse = 'N/A'; + if (lastRealTrial?.correct !== undefined) { + previousResponse = lastRealTrial.correct ? 'Correct' : 'Incorrect'; + } + + const thetaEstimateContainer = document.createElement('div'); + thetaEstimateContainer.classList.add('theta-estimate-container'); + thetaEstimateContainer.innerHTML = ` +
Theta estimate: ${thetaEstimate}
+Previous response: ${previousResponse}
+Current trial difficulty: ${currentTrialDifficulty}
+Current trial UID: ${CurrentTrialUid}
+ `; + document.body.appendChild(thetaEstimateContainer); + } +}; diff --git a/task-launcher/src/tasks/shared/trials/afcStimulus.ts b/task-launcher/src/tasks/shared/trials/afcStimulus.ts index 6de17e52..49a6ef78 100644 --- a/task-launcher/src/tasks/shared/trials/afcStimulus.ts +++ b/task-launcher/src/tasks/shared/trials/afcStimulus.ts @@ -1,7 +1,7 @@ // For all tasks except: H&F, Memory Game, Same Different Selection import jsPsychHtmlMultiResponse from '@jspsych-contrib/plugin-html-multi-response'; import _toNumber from 'lodash/toNumber'; -import { jsPsych, isTouchScreen } from '../../taskSetup'; +import { jsPsych, isTouchScreen, cat } from '../../taskSetup'; import { arrowKeyEmojis, replayButtonSvg, @@ -21,6 +21,7 @@ import { import { mediaAssets } from '../../..'; import { finishExperiment } from '.'; import { taskStore } from '../../../taskStore'; +import { displayDebugInfo } from '../helpers/displayDebugInfo'; const replayButtonHtmlId = 'replay-btn-revisited'; // Previously chosen responses for current practice trial @@ -291,6 +292,9 @@ function doOnLoad(layoutConfigMap: Record