@schoolio/player 1.4.1 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -80,6 +80,12 @@ var QuizApiClient = class {
80
80
  `/api/external/question-chat/${questionId}/${childId}`
81
81
  );
82
82
  }
83
+ async getChatsByAttempt(attemptId) {
84
+ return this.request(
85
+ "GET",
86
+ `/api/external/quiz-attempts/${attemptId}/chats`
87
+ );
88
+ }
83
89
  async getTextToSpeech(text, voice = "nova") {
84
90
  const headers = {
85
91
  "Content-Type": "application/json"
@@ -662,7 +668,8 @@ function QuestionChatPanel({
662
668
  childId,
663
669
  parentId,
664
670
  lessonId,
665
- courseId
671
+ courseId,
672
+ answerResult
666
673
  }) {
667
674
  const [messages, setMessages] = useState2([]);
668
675
  const [inputValue, setInputValue] = useState2("");
@@ -672,6 +679,7 @@ function QuestionChatPanel({
672
679
  const [isListening, setIsListening] = useState2(false);
673
680
  const [speakingIndex, setSpeakingIndex] = useState2(null);
674
681
  const [audioReadyMap, setAudioReadyMap] = useState2(/* @__PURE__ */ new Map());
682
+ const [hasOfferedHelp, setHasOfferedHelp] = useState2(false);
675
683
  const messagesContainerRef = useRef2(null);
676
684
  const messagesEndRef = useRef2(null);
677
685
  const recognitionRef = useRef2(null);
@@ -791,6 +799,7 @@ function QuestionChatPanel({
791
799
  setMessages([]);
792
800
  setChatId(null);
793
801
  setInputValue("");
802
+ setHasOfferedHelp(false);
794
803
  const loadHistory = async () => {
795
804
  try {
796
805
  const history = await apiClient.getChatHistory(question.id, childId);
@@ -805,6 +814,20 @@ function QuestionChatPanel({
805
814
  };
806
815
  loadHistory();
807
816
  }, [question.id, childId, apiClient, preCacheAudio]);
817
+ useEffect2(() => {
818
+ if (answerResult?.wasIncorrect && !hasOfferedHelp) {
819
+ setHasOfferedHelp(true);
820
+ const selectedAnswerText = answerResult.selectedAnswer || "that answer";
821
+ const helpMessage = `Looks like you chose "${selectedAnswerText}" which was incorrect. Would you like me to help explain the correct answer?`;
822
+ const assistantMessage = {
823
+ role: "assistant",
824
+ content: helpMessage,
825
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
826
+ };
827
+ setMessages((prev) => [...prev, assistantMessage]);
828
+ preCacheAudio(helpMessage);
829
+ }
830
+ }, [answerResult, hasOfferedHelp, preCacheAudio]);
808
831
  const initializeChat = async () => {
809
832
  if (chatId) return chatId;
810
833
  try {
@@ -997,15 +1020,30 @@ var defaultStyles = {
997
1020
  container: {
998
1021
  fontFamily: "system-ui, -apple-system, sans-serif",
999
1022
  width: "100%",
1023
+ height: "100%",
1024
+ maxHeight: "calc(100vh - 40px)",
1000
1025
  padding: "20px",
1001
1026
  backgroundColor: "#ffffff",
1002
1027
  borderRadius: "12px",
1003
- boxSizing: "border-box"
1028
+ boxSizing: "border-box",
1029
+ display: "flex",
1030
+ flexDirection: "column",
1031
+ overflow: "hidden"
1004
1032
  },
1005
1033
  header: {
1006
1034
  marginBottom: "20px",
1007
1035
  borderBottom: "1px solid #e5e7eb",
1008
- paddingBottom: "16px"
1036
+ paddingBottom: "16px",
1037
+ display: "flex",
1038
+ flexDirection: "column"
1039
+ },
1040
+ headerTop: {
1041
+ display: "flex",
1042
+ justifyContent: "space-between",
1043
+ alignItems: "flex-start"
1044
+ },
1045
+ headerLeft: {
1046
+ flex: 1
1009
1047
  },
1010
1048
  title: {
1011
1049
  fontSize: "24px",
@@ -1044,6 +1082,7 @@ var defaultStyles = {
1044
1082
  gap: "8px"
1045
1083
  },
1046
1084
  option: {
1085
+ width: "100%",
1047
1086
  padding: "12px 16px",
1048
1087
  border: "2px solid #e5e7eb",
1049
1088
  borderRadius: "8px",
@@ -1053,7 +1092,8 @@ var defaultStyles = {
1053
1092
  boxShadow: "none",
1054
1093
  backgroundColor: "#ffffff",
1055
1094
  WebkitTapHighlightColor: "transparent",
1056
- userSelect: "none"
1095
+ userSelect: "none",
1096
+ boxSizing: "border-box"
1057
1097
  },
1058
1098
  optionSelected: {
1059
1099
  borderColor: "#6721b0",
@@ -1132,16 +1172,25 @@ var defaultStyles = {
1132
1172
  },
1133
1173
  mainLayout: {
1134
1174
  display: "flex",
1135
- gap: "24px"
1175
+ gap: "24px",
1176
+ flex: 1,
1177
+ minHeight: 0,
1178
+ alignItems: "stretch",
1179
+ overflow: "hidden"
1136
1180
  },
1137
1181
  quizContent: {
1138
1182
  flex: 1,
1139
- minWidth: 0
1183
+ minWidth: 0,
1184
+ minHeight: 0,
1185
+ overflow: "auto"
1140
1186
  },
1141
1187
  chatPanel: {
1142
1188
  width: "320px",
1143
1189
  flexShrink: 0,
1144
- height: "460px"
1190
+ display: "flex",
1191
+ flexDirection: "column",
1192
+ minHeight: 0,
1193
+ overflow: "hidden"
1145
1194
  },
1146
1195
  timer: {
1147
1196
  fontSize: "14px",
@@ -1349,6 +1398,16 @@ function QuizPlayer({
1349
1398
  const apiClient = useRef3(null);
1350
1399
  const timerRef = useRef3(null);
1351
1400
  const startTimeRef = useRef3(0);
1401
+ const onCompleteRef = useRef3(onComplete);
1402
+ const onErrorRef = useRef3(onError);
1403
+ const onProgressRef = useRef3(onProgress);
1404
+ const onGenerateMoreQuestionsRef = useRef3(onGenerateMoreQuestions);
1405
+ useEffect3(() => {
1406
+ onCompleteRef.current = onComplete;
1407
+ onErrorRef.current = onError;
1408
+ onProgressRef.current = onProgress;
1409
+ onGenerateMoreQuestionsRef.current = onGenerateMoreQuestions;
1410
+ });
1352
1411
  useEffect3(() => {
1353
1412
  apiClient.current = new QuizApiClient({ baseUrl: apiBaseUrl, authToken });
1354
1413
  }, [apiBaseUrl, authToken]);
@@ -1395,11 +1454,11 @@ function QuizPlayer({
1395
1454
  const message = err instanceof Error ? err.message : "Failed to load quiz";
1396
1455
  setError(message);
1397
1456
  setIsLoading(false);
1398
- onError?.(err instanceof Error ? err : new Error(message));
1457
+ onErrorRef.current?.(err instanceof Error ? err : new Error(message));
1399
1458
  }
1400
1459
  }
1401
1460
  initialize();
1402
- }, [quizId, lessonId, assignLessonId, courseId, childId, parentId, forceNewAttempt, onError]);
1461
+ }, [quizId, lessonId, assignLessonId, courseId, childId, parentId, forceNewAttempt]);
1403
1462
  useEffect3(() => {
1404
1463
  if (timerStarted && !isCompleted && !error) {
1405
1464
  startTimeRef.current = Date.now();
@@ -1425,14 +1484,14 @@ function QuizPlayer({
1425
1484
  const totalQuestions = allQuestions.length;
1426
1485
  const maxQuestions = 50;
1427
1486
  useEffect3(() => {
1428
- if (quiz && onProgress) {
1429
- onProgress({
1487
+ if (quiz && onProgressRef.current) {
1488
+ onProgressRef.current({
1430
1489
  currentQuestion: currentQuestionIndex + 1,
1431
1490
  totalQuestions,
1432
1491
  answeredQuestions: answers.size
1433
1492
  });
1434
1493
  }
1435
- }, [currentQuestionIndex, answers.size, quiz, onProgress, totalQuestions]);
1494
+ }, [currentQuestionIndex, answers.size, quiz, totalQuestions]);
1436
1495
  const currentQuestion = allQuestions[currentQuestionIndex];
1437
1496
  const handleAnswerChange = useCallback2((value) => {
1438
1497
  if (!currentQuestion) return;
@@ -1477,7 +1536,7 @@ function QuizPlayer({
1477
1536
  if (totalQuestions >= maxQuestions) return;
1478
1537
  setIsGeneratingExtra(true);
1479
1538
  try {
1480
- const result2 = await onGenerateMoreQuestions(attempt.id, totalQuestions);
1539
+ const result2 = await onGenerateMoreQuestionsRef.current(attempt.id, totalQuestions);
1481
1540
  if (result2.extraQuestions && result2.extraQuestions.length > 0) {
1482
1541
  const slotsAvailable = maxQuestions - totalQuestions;
1483
1542
  const questionsToAppend = result2.extraQuestions.slice(0, slotsAvailable);
@@ -1490,11 +1549,11 @@ function QuizPlayer({
1490
1549
  }
1491
1550
  } catch (err) {
1492
1551
  console.error("Failed to generate extra questions:", err);
1493
- onError?.(err instanceof Error ? err : new Error("Failed to generate extra questions"));
1552
+ onErrorRef.current?.(err instanceof Error ? err : new Error("Failed to generate extra questions"));
1494
1553
  } finally {
1495
1554
  setIsGeneratingExtra(false);
1496
1555
  }
1497
- }, [attempt, onGenerateMoreQuestions, isGeneratingExtra, totalQuestions, maxQuestions, onError]);
1556
+ }, [attempt, isGeneratingExtra, totalQuestions, maxQuestions]);
1498
1557
  const handleSubmit = useCallback2(async () => {
1499
1558
  if (!quiz || !attempt || !apiClient.current) return;
1500
1559
  setIsSubmitting(true);
@@ -1533,15 +1592,15 @@ function QuizPlayer({
1533
1592
  if (timerRef.current) {
1534
1593
  clearInterval(timerRef.current);
1535
1594
  }
1536
- onComplete?.(quizResult);
1595
+ onCompleteRef.current?.(quizResult);
1537
1596
  } catch (err) {
1538
1597
  const message = err instanceof Error ? err.message : "Failed to submit quiz";
1539
1598
  setError(message);
1540
- onError?.(err instanceof Error ? err : new Error(message));
1599
+ onErrorRef.current?.(err instanceof Error ? err : new Error(message));
1541
1600
  } finally {
1542
1601
  setIsSubmitting(false);
1543
1602
  }
1544
- }, [quiz, attempt, currentQuestion, answers, answersDetail, onComplete, onError, totalQuestions, timerStarted, elapsedSeconds]);
1603
+ }, [quiz, attempt, currentQuestion, answers, answersDetail, totalQuestions, timerStarted, elapsedSeconds]);
1545
1604
  const isExtraQuestion = currentQuestion && extraQuestions.some((q) => q.id === currentQuestion.id);
1546
1605
  const handleSkipQuestion = useCallback2(async (reason, comment) => {
1547
1606
  if (!currentQuestion || !apiClient.current || !attempt) return;
@@ -1577,7 +1636,7 @@ function QuizPlayer({
1577
1636
  timeSpentSeconds: elapsedSeconds
1578
1637
  };
1579
1638
  setResult(quizResult);
1580
- onComplete?.(quizResult);
1639
+ onCompleteRef.current?.(quizResult);
1581
1640
  } else if (currentQuestionIndex >= newTotalQuestions) {
1582
1641
  setCurrentQuestionIndex(newTotalQuestions - 1);
1583
1642
  }
@@ -1589,7 +1648,7 @@ function QuizPlayer({
1589
1648
  } finally {
1590
1649
  setIsSkipping(false);
1591
1650
  }
1592
- }, [currentQuestion, apiClient, attempt, quiz, childId, parentId, lessonId, courseId, assignLessonId, skippedQuestionIds, extraQuestions, currentQuestionIndex, elapsedSeconds, onComplete]);
1651
+ }, [currentQuestion, apiClient, attempt, quiz, childId, parentId, lessonId, courseId, assignLessonId, skippedQuestionIds, extraQuestions, currentQuestionIndex, elapsedSeconds]);
1593
1652
  const handleReportQuestion = useCallback2(async (comment) => {
1594
1653
  if (!currentQuestion || !apiClient.current || !attempt || !comment.trim()) return;
1595
1654
  setIsReporting(true);
@@ -1959,533 +2018,537 @@ function QuizPlayer({
1959
2018
  const remainingSlots = maxQuestions - totalQuestions;
1960
2019
  const questionsToAdd = Math.min(5, remainingSlots);
1961
2020
  const canAddMore = onGenerateMoreQuestions && remainingSlots > 0;
1962
- return /* @__PURE__ */ jsxs3("div", { className, style: defaultStyles.container, children: [
1963
- /* @__PURE__ */ jsxs3("div", { style: defaultStyles.header, children: [
1964
- /* @__PURE__ */ jsx3("div", { style: defaultStyles.title, children: quiz.title }),
1965
- /* @__PURE__ */ jsxs3("div", { style: defaultStyles.progress, children: [
1966
- "Question ",
1967
- currentQuestionIndex + 1,
1968
- " of ",
1969
- totalQuestions
2021
+ return /* @__PURE__ */ jsx3("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ jsxs3("div", { style: defaultStyles.mainLayout, children: [
2022
+ /* @__PURE__ */ jsxs3("div", { style: defaultStyles.quizContent, children: [
2023
+ /* @__PURE__ */ jsxs3("div", { style: defaultStyles.header, children: [
2024
+ /* @__PURE__ */ jsx3("div", { style: defaultStyles.title, children: quiz.title }),
2025
+ /* @__PURE__ */ jsxs3("div", { style: defaultStyles.progress, children: [
2026
+ "Question ",
2027
+ currentQuestionIndex + 1,
2028
+ " of ",
2029
+ totalQuestions
2030
+ ] }),
2031
+ /* @__PURE__ */ jsx3("div", { style: defaultStyles.progressBar, children: /* @__PURE__ */ jsx3("div", { style: { ...defaultStyles.progressFill, width: `${progressPercent}%` } }) })
1970
2032
  ] }),
1971
- /* @__PURE__ */ jsx3("div", { style: defaultStyles.progressBar, children: /* @__PURE__ */ jsx3("div", { style: { ...defaultStyles.progressFill, width: `${progressPercent}%` } }) })
1972
- ] }),
1973
- /* @__PURE__ */ jsxs3("div", { style: defaultStyles.mainLayout, children: [
1974
- /* @__PURE__ */ jsxs3("div", { style: defaultStyles.quizContent, children: [
1975
- /* @__PURE__ */ jsxs3("div", { style: { ...defaultStyles.question, position: "relative", paddingBottom: "40px" }, children: [
1976
- /* @__PURE__ */ jsx3("div", { style: defaultStyles.questionText, children: /* @__PURE__ */ jsx3(TextToSpeech, { text: currentQuestion.question, inline: true, size: "md" }) }),
1977
- isExtraQuestion && /* @__PURE__ */ jsxs3(
1978
- "button",
2033
+ /* @__PURE__ */ jsxs3("div", { style: { ...defaultStyles.question, position: "relative", paddingBottom: "40px" }, children: [
2034
+ /* @__PURE__ */ jsx3("div", { style: defaultStyles.questionText, children: /* @__PURE__ */ jsx3(TextToSpeech, { text: currentQuestion.question, inline: true, size: "md" }) }),
2035
+ isExtraQuestion && /* @__PURE__ */ jsxs3(
2036
+ "button",
2037
+ {
2038
+ onClick: () => setShowSkipModal(true),
2039
+ title: "Skip question",
2040
+ style: {
2041
+ position: "absolute",
2042
+ bottom: "8px",
2043
+ left: "0",
2044
+ background: "transparent",
2045
+ border: "none",
2046
+ cursor: "pointer",
2047
+ padding: "6px 10px",
2048
+ borderRadius: "6px",
2049
+ color: "#9ca3af",
2050
+ display: "flex",
2051
+ alignItems: "center",
2052
+ justifyContent: "center",
2053
+ gap: "4px",
2054
+ fontSize: "12px",
2055
+ opacity: 0.6,
2056
+ transition: "opacity 0.2s, color 0.2s"
2057
+ },
2058
+ onMouseEnter: (e) => {
2059
+ e.currentTarget.style.opacity = "1";
2060
+ e.currentTarget.style.color = "#6b7280";
2061
+ },
2062
+ onMouseLeave: (e) => {
2063
+ e.currentTarget.style.opacity = "0.6";
2064
+ e.currentTarget.style.color = "#9ca3af";
2065
+ },
2066
+ "data-testid": "button-skip-question",
2067
+ children: [
2068
+ /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2069
+ /* @__PURE__ */ jsx3("polygon", { points: "5 4 15 12 5 20 5 4" }),
2070
+ /* @__PURE__ */ jsx3("line", { x1: "19", y1: "5", x2: "19", y2: "19" })
2071
+ ] }),
2072
+ /* @__PURE__ */ jsx3("span", { children: "Skip" })
2073
+ ]
2074
+ }
2075
+ ),
2076
+ !isExtraQuestion && /* @__PURE__ */ jsxs3(
2077
+ "button",
2078
+ {
2079
+ onClick: () => setShowReportModal(true),
2080
+ title: "Report an issue with this question",
2081
+ style: {
2082
+ position: "absolute",
2083
+ bottom: "8px",
2084
+ left: "0",
2085
+ background: "transparent",
2086
+ border: "none",
2087
+ cursor: "pointer",
2088
+ padding: "6px 10px",
2089
+ borderRadius: "6px",
2090
+ color: "#9ca3af",
2091
+ display: "flex",
2092
+ alignItems: "center",
2093
+ justifyContent: "center",
2094
+ gap: "4px",
2095
+ fontSize: "12px",
2096
+ opacity: 0.6,
2097
+ transition: "opacity 0.2s, color 0.2s"
2098
+ },
2099
+ onMouseEnter: (e) => {
2100
+ e.currentTarget.style.opacity = "1";
2101
+ e.currentTarget.style.color = "#ef4444";
2102
+ },
2103
+ onMouseLeave: (e) => {
2104
+ e.currentTarget.style.opacity = "0.6";
2105
+ e.currentTarget.style.color = "#9ca3af";
2106
+ },
2107
+ "data-testid": "button-report-question",
2108
+ children: [
2109
+ /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2110
+ /* @__PURE__ */ jsx3("path", { d: "M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z" }),
2111
+ /* @__PURE__ */ jsx3("line", { x1: "4", y1: "22", x2: "4", y2: "15" })
2112
+ ] }),
2113
+ /* @__PURE__ */ jsx3("span", { children: "Report" })
2114
+ ]
2115
+ }
2116
+ ),
2117
+ (currentQuestion.type === "single" || currentQuestion.type === "true-false") && /* @__PURE__ */ jsx3("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
2118
+ const isSelected = selectedAnswer === option;
2119
+ const isCorrectOption = currentQuestion.correctAnswer === option;
2120
+ let optionStyle = { ...defaultStyles.option };
2121
+ if (showFeedback) {
2122
+ if (isCorrectOption) {
2123
+ optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
2124
+ } else if (isSelected && !isCorrectOption) {
2125
+ optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
2126
+ }
2127
+ } else if (isSelected) {
2128
+ optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
2129
+ }
2130
+ return /* @__PURE__ */ jsxs3(
2131
+ "div",
1979
2132
  {
1980
- onClick: () => setShowSkipModal(true),
1981
- title: "Skip question",
1982
2133
  style: {
1983
- position: "absolute",
1984
- bottom: "8px",
1985
- left: "0",
1986
- background: "transparent",
1987
- border: "none",
1988
- cursor: "pointer",
1989
- padding: "6px 10px",
1990
- borderRadius: "6px",
1991
- color: "#9ca3af",
2134
+ ...optionStyle,
2135
+ cursor: showFeedback ? "default" : "pointer",
1992
2136
  display: "flex",
1993
2137
  alignItems: "center",
1994
- justifyContent: "center",
1995
- gap: "4px",
1996
- fontSize: "12px",
1997
- opacity: 0.6,
1998
- transition: "opacity 0.2s, color 0.2s"
1999
- },
2000
- onMouseEnter: (e) => {
2001
- e.currentTarget.style.opacity = "1";
2002
- e.currentTarget.style.color = "#6b7280";
2138
+ gap: "8px"
2003
2139
  },
2004
- onMouseLeave: (e) => {
2005
- e.currentTarget.style.opacity = "0.6";
2006
- e.currentTarget.style.color = "#9ca3af";
2007
- },
2008
- "data-testid": "button-skip-question",
2140
+ onClick: () => !showFeedback && handleAnswerChange(option),
2009
2141
  children: [
2010
- /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2011
- /* @__PURE__ */ jsx3("polygon", { points: "5 4 15 12 5 20 5 4" }),
2012
- /* @__PURE__ */ jsx3("line", { x1: "19", y1: "5", x2: "19", y2: "19" })
2013
- ] }),
2014
- /* @__PURE__ */ jsx3("span", { children: "Skip" })
2142
+ /* @__PURE__ */ jsx3("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx3(TextToSpeech, { text: option, size: "sm" }) }),
2143
+ /* @__PURE__ */ jsx3("span", { style: { flex: 1 }, children: option })
2015
2144
  ]
2145
+ },
2146
+ idx
2147
+ );
2148
+ }) }),
2149
+ currentQuestion.type === "multiple" && /* @__PURE__ */ jsx3("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
2150
+ const selected = Array.isArray(selectedAnswer) && selectedAnswer.includes(option);
2151
+ const correctAnswers = Array.isArray(currentQuestion.correctAnswer) ? currentQuestion.correctAnswer : currentQuestion.correctAnswer ? [currentQuestion.correctAnswer] : [];
2152
+ const isCorrectOption = correctAnswers.includes(option);
2153
+ let optionStyle = { ...defaultStyles.option };
2154
+ if (showFeedback) {
2155
+ if (isCorrectOption) {
2156
+ optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
2157
+ } else if (selected && !isCorrectOption) {
2158
+ optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
2016
2159
  }
2017
- ),
2018
- !isExtraQuestion && /* @__PURE__ */ jsxs3(
2019
- "button",
2160
+ } else if (selected) {
2161
+ optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
2162
+ }
2163
+ return /* @__PURE__ */ jsxs3(
2164
+ "div",
2020
2165
  {
2021
- onClick: () => setShowReportModal(true),
2022
- title: "Report an issue with this question",
2023
2166
  style: {
2024
- position: "absolute",
2025
- bottom: "8px",
2026
- left: "0",
2027
- background: "transparent",
2028
- border: "none",
2029
- cursor: "pointer",
2030
- padding: "6px 10px",
2031
- borderRadius: "6px",
2032
- color: "#9ca3af",
2167
+ ...optionStyle,
2168
+ cursor: showFeedback ? "default" : "pointer",
2033
2169
  display: "flex",
2034
2170
  alignItems: "center",
2035
- justifyContent: "center",
2036
- gap: "4px",
2037
- fontSize: "12px",
2038
- opacity: 0.6,
2039
- transition: "opacity 0.2s, color 0.2s"
2171
+ gap: "8px"
2040
2172
  },
2041
- onMouseEnter: (e) => {
2042
- e.currentTarget.style.opacity = "1";
2043
- e.currentTarget.style.color = "#ef4444";
2044
- },
2045
- onMouseLeave: (e) => {
2046
- e.currentTarget.style.opacity = "0.6";
2047
- e.currentTarget.style.color = "#9ca3af";
2173
+ onClick: () => {
2174
+ if (showFeedback) return;
2175
+ const current = Array.isArray(selectedAnswer) ? selectedAnswer : [];
2176
+ if (selected) {
2177
+ handleAnswerChange(current.filter((o) => o !== option));
2178
+ } else {
2179
+ handleAnswerChange([...current, option]);
2180
+ }
2048
2181
  },
2049
- "data-testid": "button-report-question",
2050
2182
  children: [
2051
- /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2052
- /* @__PURE__ */ jsx3("path", { d: "M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z" }),
2053
- /* @__PURE__ */ jsx3("line", { x1: "4", y1: "22", x2: "4", y2: "15" })
2054
- ] }),
2055
- /* @__PURE__ */ jsx3("span", { children: "Report" })
2183
+ /* @__PURE__ */ jsx3("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx3(TextToSpeech, { text: option, size: "sm" }) }),
2184
+ /* @__PURE__ */ jsx3("span", { style: { flex: 1 }, children: option })
2056
2185
  ]
2186
+ },
2187
+ idx
2188
+ );
2189
+ }) }),
2190
+ (currentQuestion.type === "free" || currentQuestion.type === "essay") && /* @__PURE__ */ jsx3(
2191
+ "textarea",
2192
+ {
2193
+ style: { ...defaultStyles.input, minHeight: currentQuestion.type === "essay" ? "150px" : "60px" },
2194
+ value: selectedAnswer || "",
2195
+ onChange: (e) => handleAnswerChange(e.target.value),
2196
+ placeholder: "Type your answer here...",
2197
+ disabled: showFeedback
2198
+ }
2199
+ ),
2200
+ currentQuestion.type === "fill" && /* @__PURE__ */ jsx3("div", { style: defaultStyles.options, children: currentQuestion.blanks?.map((_, idx) => /* @__PURE__ */ jsx3(
2201
+ "input",
2202
+ {
2203
+ style: defaultStyles.input,
2204
+ value: (Array.isArray(selectedAnswer) ? selectedAnswer[idx] : "") || "",
2205
+ onChange: (e) => {
2206
+ const current = Array.isArray(selectedAnswer) ? [...selectedAnswer] : [];
2207
+ current[idx] = e.target.value;
2208
+ handleAnswerChange(current);
2209
+ },
2210
+ placeholder: `Blank ${idx + 1}`,
2211
+ disabled: showFeedback
2212
+ },
2213
+ idx
2214
+ )) }),
2215
+ showFeedback && currentAnswerDetail && /* @__PURE__ */ jsxs3("div", { style: {
2216
+ ...defaultStyles.feedback,
2217
+ ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackCorrect : defaultStyles.feedbackIncorrect
2218
+ }, children: [
2219
+ /* @__PURE__ */ jsx3("div", { style: {
2220
+ ...defaultStyles.feedbackTitle,
2221
+ ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackTitleCorrect : defaultStyles.feedbackTitleIncorrect
2222
+ }, children: currentAnswerDetail.isCorrect ? "\u2713 Correct!" : "\u2717 Incorrect" }),
2223
+ currentQuestion.explanation && /* @__PURE__ */ jsx3("div", { style: defaultStyles.feedbackExplanation, children: currentQuestion.explanation })
2224
+ ] })
2225
+ ] }),
2226
+ showSkipModal && /* @__PURE__ */ jsx3("div", { style: {
2227
+ position: "fixed",
2228
+ top: 0,
2229
+ left: 0,
2230
+ right: 0,
2231
+ bottom: 0,
2232
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
2233
+ display: "flex",
2234
+ alignItems: "center",
2235
+ justifyContent: "center",
2236
+ zIndex: 1e3
2237
+ }, children: /* @__PURE__ */ jsxs3("div", { style: {
2238
+ backgroundColor: "#ffffff",
2239
+ borderRadius: "12px",
2240
+ padding: "24px",
2241
+ maxWidth: "400px",
2242
+ width: "90%",
2243
+ boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)"
2244
+ }, children: [
2245
+ /* @__PURE__ */ jsx3("h3", { style: { margin: "0 0 8px 0", fontSize: "18px", fontWeight: "600", color: "#1f2937" }, children: "Skip Question" }),
2246
+ /* @__PURE__ */ jsx3("p", { style: { margin: "0 0 16px 0", fontSize: "14px", color: "#6b7280" }, children: "Help us improve by telling us why you're skipping this question." }),
2247
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "8px", marginBottom: "16px" }, children: [
2248
+ /* @__PURE__ */ jsx3(
2249
+ "button",
2250
+ {
2251
+ onClick: () => setSelectedSkipReason("question_issue"),
2252
+ disabled: isSkipping,
2253
+ style: {
2254
+ padding: "12px 16px",
2255
+ borderRadius: "8px",
2256
+ border: selectedSkipReason === "question_issue" ? "2px solid #8b5cf6" : "1px solid #e5e7eb",
2257
+ backgroundColor: selectedSkipReason === "question_issue" ? "#f5f3ff" : "#f9fafb",
2258
+ cursor: isSkipping ? "not-allowed" : "pointer",
2259
+ fontSize: "14px",
2260
+ fontWeight: "500",
2261
+ color: "#374151",
2262
+ textAlign: "left",
2263
+ opacity: isSkipping ? 0.6 : 1
2264
+ },
2265
+ "data-testid": "button-skip-reason-issue",
2266
+ children: "Question has an issue"
2057
2267
  }
2058
2268
  ),
2059
- (currentQuestion.type === "single" || currentQuestion.type === "true-false") && /* @__PURE__ */ jsx3("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
2060
- const isSelected = selectedAnswer === option;
2061
- const isCorrectOption = currentQuestion.correctAnswer === option;
2062
- let optionStyle = { ...defaultStyles.option };
2063
- if (showFeedback) {
2064
- if (isCorrectOption) {
2065
- optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
2066
- } else if (isSelected && !isCorrectOption) {
2067
- optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
2068
- }
2069
- } else if (isSelected) {
2070
- optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
2071
- }
2072
- return /* @__PURE__ */ jsxs3(
2073
- "div",
2074
- {
2075
- style: {
2076
- ...optionStyle,
2077
- cursor: showFeedback ? "default" : "pointer",
2078
- display: "flex",
2079
- alignItems: "center",
2080
- gap: "8px"
2081
- },
2082
- onClick: () => !showFeedback && handleAnswerChange(option),
2083
- children: [
2084
- /* @__PURE__ */ jsx3("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx3(TextToSpeech, { text: option, size: "sm" }) }),
2085
- /* @__PURE__ */ jsx3("span", { style: { flex: 1 }, children: option })
2086
- ]
2269
+ /* @__PURE__ */ jsx3(
2270
+ "button",
2271
+ {
2272
+ onClick: () => setSelectedSkipReason("dont_know"),
2273
+ disabled: isSkipping,
2274
+ style: {
2275
+ padding: "12px 16px",
2276
+ borderRadius: "8px",
2277
+ border: selectedSkipReason === "dont_know" ? "2px solid #8b5cf6" : "1px solid #e5e7eb",
2278
+ backgroundColor: selectedSkipReason === "dont_know" ? "#f5f3ff" : "#f9fafb",
2279
+ cursor: isSkipping ? "not-allowed" : "pointer",
2280
+ fontSize: "14px",
2281
+ fontWeight: "500",
2282
+ color: "#374151",
2283
+ textAlign: "left",
2284
+ opacity: isSkipping ? 0.6 : 1
2087
2285
  },
2088
- idx
2089
- );
2090
- }) }),
2091
- currentQuestion.type === "multiple" && /* @__PURE__ */ jsx3("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
2092
- const selected = Array.isArray(selectedAnswer) && selectedAnswer.includes(option);
2093
- const correctAnswers = Array.isArray(currentQuestion.correctAnswer) ? currentQuestion.correctAnswer : currentQuestion.correctAnswer ? [currentQuestion.correctAnswer] : [];
2094
- const isCorrectOption = correctAnswers.includes(option);
2095
- let optionStyle = { ...defaultStyles.option };
2096
- if (showFeedback) {
2097
- if (isCorrectOption) {
2098
- optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
2099
- } else if (selected && !isCorrectOption) {
2100
- optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
2101
- }
2102
- } else if (selected) {
2103
- optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
2286
+ "data-testid": "button-skip-reason-dont-know",
2287
+ children: "I don't know the answer"
2104
2288
  }
2105
- return /* @__PURE__ */ jsxs3(
2106
- "div",
2107
- {
2108
- style: {
2109
- ...optionStyle,
2110
- cursor: showFeedback ? "default" : "pointer",
2111
- display: "flex",
2112
- alignItems: "center",
2113
- gap: "8px"
2114
- },
2115
- onClick: () => {
2116
- if (showFeedback) return;
2117
- const current = Array.isArray(selectedAnswer) ? selectedAnswer : [];
2118
- if (selected) {
2119
- handleAnswerChange(current.filter((o) => o !== option));
2120
- } else {
2121
- handleAnswerChange([...current, option]);
2122
- }
2123
- },
2124
- children: [
2125
- /* @__PURE__ */ jsx3("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx3(TextToSpeech, { text: option, size: "sm" }) }),
2126
- /* @__PURE__ */ jsx3("span", { style: { flex: 1 }, children: option })
2127
- ]
2128
- },
2129
- idx
2130
- );
2131
- }) }),
2132
- (currentQuestion.type === "free" || currentQuestion.type === "essay") && /* @__PURE__ */ jsx3(
2289
+ )
2290
+ ] }),
2291
+ /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "16px" }, children: [
2292
+ /* @__PURE__ */ jsx3("label", { style: { display: "block", fontSize: "13px", fontWeight: "500", color: "#374151", marginBottom: "6px" }, children: "Additional details (optional)" }),
2293
+ /* @__PURE__ */ jsx3(
2133
2294
  "textarea",
2134
2295
  {
2135
- style: { ...defaultStyles.input, minHeight: currentQuestion.type === "essay" ? "150px" : "60px" },
2136
- value: selectedAnswer || "",
2137
- onChange: (e) => handleAnswerChange(e.target.value),
2138
- placeholder: "Type your answer here...",
2139
- disabled: showFeedback
2296
+ value: skipComment,
2297
+ onChange: (e) => setSkipComment(e.target.value.slice(0, 200)),
2298
+ placeholder: "Tell us more about the issue...",
2299
+ disabled: isSkipping,
2300
+ style: {
2301
+ width: "100%",
2302
+ minHeight: "80px",
2303
+ padding: "10px 12px",
2304
+ borderRadius: "8px",
2305
+ border: "1px solid #e5e7eb",
2306
+ fontSize: "14px",
2307
+ resize: "vertical",
2308
+ fontFamily: "inherit",
2309
+ boxSizing: "border-box"
2310
+ },
2311
+ "data-testid": "input-skip-comment"
2140
2312
  }
2141
2313
  ),
2142
- currentQuestion.type === "fill" && /* @__PURE__ */ jsx3("div", { style: defaultStyles.options, children: currentQuestion.blanks?.map((_, idx) => /* @__PURE__ */ jsx3(
2143
- "input",
2314
+ /* @__PURE__ */ jsxs3("div", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px", textAlign: "right" }, children: [
2315
+ skipComment.length,
2316
+ "/200"
2317
+ ] })
2318
+ ] }),
2319
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "10px" }, children: [
2320
+ /* @__PURE__ */ jsx3(
2321
+ "button",
2144
2322
  {
2145
- style: defaultStyles.input,
2146
- value: (Array.isArray(selectedAnswer) ? selectedAnswer[idx] : "") || "",
2147
- onChange: (e) => {
2148
- const current = Array.isArray(selectedAnswer) ? [...selectedAnswer] : [];
2149
- current[idx] = e.target.value;
2150
- handleAnswerChange(current);
2323
+ onClick: () => {
2324
+ setShowSkipModal(false);
2325
+ setSkipComment("");
2326
+ setSelectedSkipReason(null);
2151
2327
  },
2152
- placeholder: `Blank ${idx + 1}`,
2153
- disabled: showFeedback
2154
- },
2155
- idx
2156
- )) }),
2157
- showFeedback && currentAnswerDetail && /* @__PURE__ */ jsxs3("div", { style: {
2158
- ...defaultStyles.feedback,
2159
- ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackCorrect : defaultStyles.feedbackIncorrect
2160
- }, children: [
2161
- /* @__PURE__ */ jsx3("div", { style: {
2162
- ...defaultStyles.feedbackTitle,
2163
- ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackTitleCorrect : defaultStyles.feedbackTitleIncorrect
2164
- }, children: currentAnswerDetail.isCorrect ? "\u2713 Correct!" : "\u2717 Incorrect" }),
2165
- currentQuestion.explanation && /* @__PURE__ */ jsx3("div", { style: defaultStyles.feedbackExplanation, children: currentQuestion.explanation })
2328
+ style: {
2329
+ flex: 1,
2330
+ padding: "10px 16px",
2331
+ borderRadius: "8px",
2332
+ border: "1px solid #e5e7eb",
2333
+ backgroundColor: "#ffffff",
2334
+ cursor: "pointer",
2335
+ fontSize: "14px",
2336
+ fontWeight: "500",
2337
+ color: "#6b7280"
2338
+ },
2339
+ "data-testid": "button-skip-cancel",
2340
+ children: "Cancel"
2341
+ }
2342
+ ),
2343
+ /* @__PURE__ */ jsx3(
2344
+ "button",
2345
+ {
2346
+ onClick: () => selectedSkipReason && handleSkipQuestion(selectedSkipReason, skipComment),
2347
+ disabled: isSkipping || !selectedSkipReason,
2348
+ style: {
2349
+ flex: 1,
2350
+ padding: "10px 16px",
2351
+ borderRadius: "8px",
2352
+ border: "none",
2353
+ backgroundColor: selectedSkipReason ? "#8b5cf6" : "#d1d5db",
2354
+ cursor: isSkipping || !selectedSkipReason ? "not-allowed" : "pointer",
2355
+ fontSize: "14px",
2356
+ fontWeight: "500",
2357
+ color: "#ffffff",
2358
+ opacity: isSkipping ? 0.6 : 1
2359
+ },
2360
+ "data-testid": "button-skip-submit",
2361
+ children: isSkipping ? "Skipping..." : "Skip Question"
2362
+ }
2363
+ )
2364
+ ] })
2365
+ ] }) }),
2366
+ showReportModal && /* @__PURE__ */ jsx3("div", { style: {
2367
+ position: "fixed",
2368
+ top: 0,
2369
+ left: 0,
2370
+ right: 0,
2371
+ bottom: 0,
2372
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
2373
+ display: "flex",
2374
+ alignItems: "center",
2375
+ justifyContent: "center",
2376
+ zIndex: 1e3
2377
+ }, children: /* @__PURE__ */ jsxs3("div", { style: {
2378
+ backgroundColor: "#ffffff",
2379
+ borderRadius: "12px",
2380
+ padding: "24px",
2381
+ maxWidth: "400px",
2382
+ width: "90%",
2383
+ boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)"
2384
+ }, children: [
2385
+ /* @__PURE__ */ jsx3("h3", { style: { margin: "0 0 8px 0", fontSize: "18px", fontWeight: "600", color: "#1f2937" }, children: "Report Question" }),
2386
+ /* @__PURE__ */ jsx3("p", { style: { margin: "0 0 16px 0", fontSize: "14px", color: "#6b7280" }, children: "What's wrong with this question?" }),
2387
+ /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "16px" }, children: [
2388
+ /* @__PURE__ */ jsx3(
2389
+ "textarea",
2390
+ {
2391
+ value: reportComment,
2392
+ onChange: (e) => setReportComment(e.target.value.slice(0, 300)),
2393
+ placeholder: "Describe the issue with this question...",
2394
+ disabled: isReporting,
2395
+ style: {
2396
+ width: "100%",
2397
+ minHeight: "120px",
2398
+ padding: "10px 12px",
2399
+ borderRadius: "8px",
2400
+ border: "1px solid #e5e7eb",
2401
+ fontSize: "14px",
2402
+ resize: "vertical",
2403
+ fontFamily: "inherit",
2404
+ boxSizing: "border-box"
2405
+ },
2406
+ "data-testid": "input-report-comment"
2407
+ }
2408
+ ),
2409
+ /* @__PURE__ */ jsxs3("div", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px", textAlign: "right" }, children: [
2410
+ reportComment.length,
2411
+ "/300"
2166
2412
  ] })
2167
2413
  ] }),
2168
- showSkipModal && /* @__PURE__ */ jsx3("div", { style: {
2169
- position: "fixed",
2170
- top: 0,
2171
- left: 0,
2172
- right: 0,
2173
- bottom: 0,
2174
- backgroundColor: "rgba(0, 0, 0, 0.5)",
2175
- display: "flex",
2176
- alignItems: "center",
2177
- justifyContent: "center",
2178
- zIndex: 1e3
2179
- }, children: /* @__PURE__ */ jsxs3("div", { style: {
2180
- backgroundColor: "#ffffff",
2181
- borderRadius: "12px",
2182
- padding: "24px",
2183
- maxWidth: "400px",
2184
- width: "90%",
2185
- boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)"
2186
- }, children: [
2187
- /* @__PURE__ */ jsx3("h3", { style: { margin: "0 0 8px 0", fontSize: "18px", fontWeight: "600", color: "#1f2937" }, children: "Skip Question" }),
2188
- /* @__PURE__ */ jsx3("p", { style: { margin: "0 0 16px 0", fontSize: "14px", color: "#6b7280" }, children: "Help us improve by telling us why you're skipping this question." }),
2189
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "8px", marginBottom: "16px" }, children: [
2190
- /* @__PURE__ */ jsx3(
2191
- "button",
2192
- {
2193
- onClick: () => setSelectedSkipReason("question_issue"),
2194
- disabled: isSkipping,
2195
- style: {
2196
- padding: "12px 16px",
2197
- borderRadius: "8px",
2198
- border: selectedSkipReason === "question_issue" ? "2px solid #8b5cf6" : "1px solid #e5e7eb",
2199
- backgroundColor: selectedSkipReason === "question_issue" ? "#f5f3ff" : "#f9fafb",
2200
- cursor: isSkipping ? "not-allowed" : "pointer",
2201
- fontSize: "14px",
2202
- fontWeight: "500",
2203
- color: "#374151",
2204
- textAlign: "left",
2205
- opacity: isSkipping ? 0.6 : 1
2206
- },
2207
- "data-testid": "button-skip-reason-issue",
2208
- children: "Question has an issue"
2209
- }
2210
- ),
2211
- /* @__PURE__ */ jsx3(
2212
- "button",
2213
- {
2214
- onClick: () => setSelectedSkipReason("dont_know"),
2215
- disabled: isSkipping,
2216
- style: {
2217
- padding: "12px 16px",
2218
- borderRadius: "8px",
2219
- border: selectedSkipReason === "dont_know" ? "2px solid #8b5cf6" : "1px solid #e5e7eb",
2220
- backgroundColor: selectedSkipReason === "dont_know" ? "#f5f3ff" : "#f9fafb",
2221
- cursor: isSkipping ? "not-allowed" : "pointer",
2222
- fontSize: "14px",
2223
- fontWeight: "500",
2224
- color: "#374151",
2225
- textAlign: "left",
2226
- opacity: isSkipping ? 0.6 : 1
2227
- },
2228
- "data-testid": "button-skip-reason-dont-know",
2229
- children: "I don't know the answer"
2230
- }
2231
- )
2232
- ] }),
2233
- /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "16px" }, children: [
2234
- /* @__PURE__ */ jsx3("label", { style: { display: "block", fontSize: "13px", fontWeight: "500", color: "#374151", marginBottom: "6px" }, children: "Additional details (optional)" }),
2235
- /* @__PURE__ */ jsx3(
2236
- "textarea",
2237
- {
2238
- value: skipComment,
2239
- onChange: (e) => setSkipComment(e.target.value.slice(0, 200)),
2240
- placeholder: "Tell us more about the issue...",
2241
- disabled: isSkipping,
2242
- style: {
2243
- width: "100%",
2244
- minHeight: "80px",
2245
- padding: "10px 12px",
2246
- borderRadius: "8px",
2247
- border: "1px solid #e5e7eb",
2248
- fontSize: "14px",
2249
- resize: "vertical",
2250
- fontFamily: "inherit",
2251
- boxSizing: "border-box"
2252
- },
2253
- "data-testid": "input-skip-comment"
2254
- }
2255
- ),
2256
- /* @__PURE__ */ jsxs3("div", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px", textAlign: "right" }, children: [
2257
- skipComment.length,
2258
- "/200"
2259
- ] })
2260
- ] }),
2261
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "10px" }, children: [
2262
- /* @__PURE__ */ jsx3(
2263
- "button",
2264
- {
2265
- onClick: () => {
2266
- setShowSkipModal(false);
2267
- setSkipComment("");
2268
- setSelectedSkipReason(null);
2269
- },
2270
- style: {
2271
- flex: 1,
2272
- padding: "10px 16px",
2273
- borderRadius: "8px",
2274
- border: "1px solid #e5e7eb",
2275
- backgroundColor: "#ffffff",
2276
- cursor: "pointer",
2277
- fontSize: "14px",
2278
- fontWeight: "500",
2279
- color: "#6b7280"
2280
- },
2281
- "data-testid": "button-skip-cancel",
2282
- children: "Cancel"
2283
- }
2284
- ),
2285
- /* @__PURE__ */ jsx3(
2286
- "button",
2287
- {
2288
- onClick: () => selectedSkipReason && handleSkipQuestion(selectedSkipReason, skipComment),
2289
- disabled: isSkipping || !selectedSkipReason,
2290
- style: {
2291
- flex: 1,
2292
- padding: "10px 16px",
2293
- borderRadius: "8px",
2294
- border: "none",
2295
- backgroundColor: selectedSkipReason ? "#8b5cf6" : "#d1d5db",
2296
- cursor: isSkipping || !selectedSkipReason ? "not-allowed" : "pointer",
2297
- fontSize: "14px",
2298
- fontWeight: "500",
2299
- color: "#ffffff",
2300
- opacity: isSkipping ? 0.6 : 1
2301
- },
2302
- "data-testid": "button-skip-submit",
2303
- children: isSkipping ? "Skipping..." : "Skip Question"
2304
- }
2305
- )
2306
- ] })
2307
- ] }) }),
2308
- showReportModal && /* @__PURE__ */ jsx3("div", { style: {
2309
- position: "fixed",
2310
- top: 0,
2311
- left: 0,
2312
- right: 0,
2313
- bottom: 0,
2314
- backgroundColor: "rgba(0, 0, 0, 0.5)",
2315
- display: "flex",
2316
- alignItems: "center",
2317
- justifyContent: "center",
2318
- zIndex: 1e3
2319
- }, children: /* @__PURE__ */ jsxs3("div", { style: {
2320
- backgroundColor: "#ffffff",
2321
- borderRadius: "12px",
2322
- padding: "24px",
2323
- maxWidth: "400px",
2324
- width: "90%",
2325
- boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)"
2326
- }, children: [
2327
- /* @__PURE__ */ jsx3("h3", { style: { margin: "0 0 8px 0", fontSize: "18px", fontWeight: "600", color: "#1f2937" }, children: "Report Question" }),
2328
- /* @__PURE__ */ jsx3("p", { style: { margin: "0 0 16px 0", fontSize: "14px", color: "#6b7280" }, children: "What's wrong with this question?" }),
2329
- /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "16px" }, children: [
2330
- /* @__PURE__ */ jsx3(
2331
- "textarea",
2332
- {
2333
- value: reportComment,
2334
- onChange: (e) => setReportComment(e.target.value.slice(0, 300)),
2335
- placeholder: "Describe the issue with this question...",
2336
- disabled: isReporting,
2337
- style: {
2338
- width: "100%",
2339
- minHeight: "120px",
2340
- padding: "10px 12px",
2341
- borderRadius: "8px",
2342
- border: "1px solid #e5e7eb",
2343
- fontSize: "14px",
2344
- resize: "vertical",
2345
- fontFamily: "inherit",
2346
- boxSizing: "border-box"
2347
- },
2348
- "data-testid": "input-report-comment"
2349
- }
2350
- ),
2351
- /* @__PURE__ */ jsxs3("div", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px", textAlign: "right" }, children: [
2352
- reportComment.length,
2353
- "/300"
2354
- ] })
2355
- ] }),
2356
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "10px" }, children: [
2357
- /* @__PURE__ */ jsx3(
2358
- "button",
2359
- {
2360
- onClick: () => {
2361
- setShowReportModal(false);
2362
- setReportComment("");
2363
- },
2364
- style: {
2365
- flex: 1,
2366
- padding: "10px 16px",
2367
- borderRadius: "8px",
2368
- border: "1px solid #e5e7eb",
2369
- backgroundColor: "#ffffff",
2370
- cursor: "pointer",
2371
- fontSize: "14px",
2372
- fontWeight: "500",
2373
- color: "#6b7280"
2374
- },
2375
- "data-testid": "button-report-cancel",
2376
- children: "Cancel"
2377
- }
2378
- ),
2379
- /* @__PURE__ */ jsx3(
2380
- "button",
2381
- {
2382
- onClick: () => handleReportQuestion(reportComment),
2383
- disabled: isReporting || !reportComment.trim(),
2384
- style: {
2385
- flex: 1,
2386
- padding: "10px 16px",
2387
- borderRadius: "8px",
2388
- border: "none",
2389
- backgroundColor: reportComment.trim() ? "#ef4444" : "#d1d5db",
2390
- cursor: isReporting || !reportComment.trim() ? "not-allowed" : "pointer",
2391
- fontSize: "14px",
2392
- fontWeight: "500",
2393
- color: "#ffffff",
2394
- opacity: isReporting ? 0.6 : 1
2395
- },
2396
- "data-testid": "button-report-submit",
2397
- children: isReporting ? "Reporting..." : "Report"
2398
- }
2399
- )
2400
- ] })
2401
- ] }) }),
2402
- /* @__PURE__ */ jsxs3("div", { style: defaultStyles.buttonsColumn, children: [
2403
- showFeedback && isLastQuestion && canAddMore && /* @__PURE__ */ jsx3(
2414
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "10px" }, children: [
2415
+ /* @__PURE__ */ jsx3(
2404
2416
  "button",
2405
2417
  {
2418
+ onClick: () => {
2419
+ setShowReportModal(false);
2420
+ setReportComment("");
2421
+ },
2406
2422
  style: {
2407
- ...defaultStyles.buttonAddMore,
2408
- ...isGeneratingExtra ? defaultStyles.buttonAddMoreDisabled : {}
2423
+ flex: 1,
2424
+ padding: "10px 16px",
2425
+ borderRadius: "8px",
2426
+ border: "1px solid #e5e7eb",
2427
+ backgroundColor: "#ffffff",
2428
+ cursor: "pointer",
2429
+ fontSize: "14px",
2430
+ fontWeight: "500",
2431
+ color: "#6b7280"
2409
2432
  },
2410
- onClick: handleAddMoreQuestions,
2411
- disabled: isGeneratingExtra,
2412
- "data-testid": "button-add-more-questions",
2413
- children: isGeneratingExtra ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
2414
- /* @__PURE__ */ jsx3(Spinner, { size: 16, color: "#9ca3af" }),
2415
- "Generating Questions..."
2416
- ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
2417
- "+ Add ",
2418
- questionsToAdd,
2419
- " More Question",
2420
- questionsToAdd !== 1 ? "s" : ""
2421
- ] })
2433
+ "data-testid": "button-report-cancel",
2434
+ children: "Cancel"
2422
2435
  }
2423
2436
  ),
2424
- /* @__PURE__ */ jsx3("div", { style: { ...defaultStyles.buttons, justifyContent: "flex-end" }, children: showFeedback ? (
2425
- // After viewing feedback
2426
- isLastQuestion ? /* @__PURE__ */ jsx3(
2427
- "button",
2428
- {
2429
- style: {
2430
- ...defaultStyles.button,
2431
- ...isSubmitting || isGeneratingExtra ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
2432
- },
2433
- onClick: handleSubmit,
2434
- disabled: isSubmitting || isGeneratingExtra,
2435
- "data-testid": "button-submit-quiz",
2436
- children: isSubmitting ? /* @__PURE__ */ jsx3(Spinner, { size: 16, color: "#9ca3af" }) : "Submit Quiz"
2437
- }
2438
- ) : /* @__PURE__ */ jsx3(
2439
- "button",
2440
- {
2441
- style: {
2442
- ...defaultStyles.button,
2443
- ...defaultStyles.buttonPrimary
2444
- },
2445
- onClick: handleContinue,
2446
- "data-testid": "button-continue",
2447
- children: "Continue"
2448
- }
2449
- )
2450
- ) : (
2451
- // Before checking answer
2452
- /* @__PURE__ */ jsx3(
2453
- "button",
2454
- {
2455
- style: {
2456
- ...defaultStyles.button,
2457
- ...isNavigating || selectedAnswer === void 0 ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
2458
- },
2459
- onClick: handleCheckAnswer,
2460
- disabled: isNavigating || selectedAnswer === void 0,
2461
- "data-testid": "button-check-answer",
2462
- children: isNavigating ? /* @__PURE__ */ jsx3(Spinner, { size: 16, color: "#9ca3af" }) : "Check Answer"
2463
- }
2464
- )
2465
- ) })
2437
+ /* @__PURE__ */ jsx3(
2438
+ "button",
2439
+ {
2440
+ onClick: () => handleReportQuestion(reportComment),
2441
+ disabled: isReporting || !reportComment.trim(),
2442
+ style: {
2443
+ flex: 1,
2444
+ padding: "10px 16px",
2445
+ borderRadius: "8px",
2446
+ border: "none",
2447
+ backgroundColor: reportComment.trim() ? "#ef4444" : "#d1d5db",
2448
+ cursor: isReporting || !reportComment.trim() ? "not-allowed" : "pointer",
2449
+ fontSize: "14px",
2450
+ fontWeight: "500",
2451
+ color: "#ffffff",
2452
+ opacity: isReporting ? 0.6 : 1
2453
+ },
2454
+ "data-testid": "button-report-submit",
2455
+ children: isReporting ? "Reporting..." : "Report"
2456
+ }
2457
+ )
2466
2458
  ] })
2467
- ] }),
2468
- /* @__PURE__ */ jsx3("div", { style: defaultStyles.chatPanel, children: apiClient.current && /* @__PURE__ */ jsx3(
2469
- QuestionChatPanel,
2470
- {
2471
- apiClient: apiClient.current,
2472
- question: {
2473
- id: currentQuestion.id,
2474
- question: currentQuestion.question,
2475
- type: currentQuestion.type,
2476
- options: currentQuestion.options,
2477
- correctAnswer: currentQuestion.correctAnswer,
2478
- explanation: currentQuestion.explanation
2479
- },
2480
- quizId: quiz.id,
2481
- childId,
2482
- parentId,
2483
- lessonId,
2484
- courseId
2485
- }
2486
- ) })
2487
- ] })
2488
- ] });
2459
+ ] }) }),
2460
+ /* @__PURE__ */ jsxs3("div", { style: defaultStyles.buttonsColumn, children: [
2461
+ showFeedback && isLastQuestion && canAddMore && /* @__PURE__ */ jsx3(
2462
+ "button",
2463
+ {
2464
+ style: {
2465
+ ...defaultStyles.buttonAddMore,
2466
+ ...isGeneratingExtra ? defaultStyles.buttonAddMoreDisabled : {}
2467
+ },
2468
+ onClick: handleAddMoreQuestions,
2469
+ disabled: isGeneratingExtra,
2470
+ "data-testid": "button-add-more-questions",
2471
+ children: isGeneratingExtra ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
2472
+ /* @__PURE__ */ jsx3(Spinner, { size: 16, color: "#9ca3af" }),
2473
+ "Generating Questions..."
2474
+ ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
2475
+ "+ Add ",
2476
+ questionsToAdd,
2477
+ " More Question",
2478
+ questionsToAdd !== 1 ? "s" : ""
2479
+ ] })
2480
+ }
2481
+ ),
2482
+ /* @__PURE__ */ jsx3("div", { style: { ...defaultStyles.buttons, justifyContent: "flex-end" }, children: showFeedback ? (
2483
+ // After viewing feedback
2484
+ isLastQuestion ? /* @__PURE__ */ jsx3(
2485
+ "button",
2486
+ {
2487
+ style: {
2488
+ ...defaultStyles.button,
2489
+ ...isSubmitting || isGeneratingExtra ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
2490
+ },
2491
+ onClick: handleSubmit,
2492
+ disabled: isSubmitting || isGeneratingExtra,
2493
+ "data-testid": "button-submit-quiz",
2494
+ children: isSubmitting ? /* @__PURE__ */ jsx3(Spinner, { size: 16, color: "#9ca3af" }) : "Submit Quiz"
2495
+ }
2496
+ ) : /* @__PURE__ */ jsx3(
2497
+ "button",
2498
+ {
2499
+ style: {
2500
+ ...defaultStyles.button,
2501
+ ...defaultStyles.buttonPrimary
2502
+ },
2503
+ onClick: handleContinue,
2504
+ "data-testid": "button-continue",
2505
+ children: "Continue"
2506
+ }
2507
+ )
2508
+ ) : (
2509
+ // Before checking answer
2510
+ /* @__PURE__ */ jsx3(
2511
+ "button",
2512
+ {
2513
+ style: {
2514
+ ...defaultStyles.button,
2515
+ ...isNavigating || selectedAnswer === void 0 ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
2516
+ },
2517
+ onClick: handleCheckAnswer,
2518
+ disabled: isNavigating || selectedAnswer === void 0,
2519
+ "data-testid": "button-check-answer",
2520
+ children: isNavigating ? /* @__PURE__ */ jsx3(Spinner, { size: 16, color: "#9ca3af" }) : "Check Answer"
2521
+ }
2522
+ )
2523
+ ) })
2524
+ ] })
2525
+ ] }),
2526
+ /* @__PURE__ */ jsx3("div", { style: defaultStyles.chatPanel, children: apiClient.current && /* @__PURE__ */ jsx3(
2527
+ QuestionChatPanel,
2528
+ {
2529
+ apiClient: apiClient.current,
2530
+ question: {
2531
+ id: currentQuestion.id,
2532
+ question: currentQuestion.question,
2533
+ type: currentQuestion.type,
2534
+ options: currentQuestion.options,
2535
+ correctAnswer: currentQuestion.correctAnswer,
2536
+ explanation: currentQuestion.explanation
2537
+ },
2538
+ quizId: quiz.id,
2539
+ childId,
2540
+ parentId,
2541
+ lessonId,
2542
+ courseId,
2543
+ answerResult: showFeedback && currentAnswerDetail ? {
2544
+ wasIncorrect: !currentAnswerDetail.isCorrect,
2545
+ selectedAnswer: typeof selectedAnswer === "string" ? selectedAnswer : Array.isArray(selectedAnswer) ? selectedAnswer.join(", ") : void 0,
2546
+ correctAnswer: typeof currentQuestion.correctAnswer === "string" ? currentQuestion.correctAnswer : Array.isArray(currentQuestion.correctAnswer) ? currentQuestion.correctAnswer.join(", ") : void 0,
2547
+ explanation: currentQuestion.explanation
2548
+ } : void 0
2549
+ }
2550
+ ) })
2551
+ ] }) });
2489
2552
  }
2490
2553
 
2491
2554
  // src/AttemptViewer.tsx
@@ -2611,6 +2674,46 @@ var defaultStyles2 = {
2611
2674
  fontSize: "14px",
2612
2675
  color: "#581c87"
2613
2676
  },
2677
+ chatHistorySection: {
2678
+ marginTop: "12px",
2679
+ borderTop: "1px solid #e5e7eb",
2680
+ paddingTop: "12px"
2681
+ },
2682
+ chatToggleButton: {
2683
+ display: "flex",
2684
+ alignItems: "center",
2685
+ gap: "6px",
2686
+ padding: "6px 12px",
2687
+ backgroundColor: "#f3f4f6",
2688
+ border: "none",
2689
+ borderRadius: "6px",
2690
+ fontSize: "13px",
2691
+ color: "#6b7280",
2692
+ cursor: "pointer",
2693
+ fontWeight: "500"
2694
+ },
2695
+ chatMessages: {
2696
+ marginTop: "12px",
2697
+ display: "flex",
2698
+ flexDirection: "column",
2699
+ gap: "8px"
2700
+ },
2701
+ chatMessage: {
2702
+ padding: "8px 12px",
2703
+ borderRadius: "8px",
2704
+ fontSize: "13px",
2705
+ maxWidth: "85%"
2706
+ },
2707
+ chatMessageUser: {
2708
+ backgroundColor: "#6721b0",
2709
+ color: "#ffffff",
2710
+ alignSelf: "flex-end"
2711
+ },
2712
+ chatMessageAssistant: {
2713
+ backgroundColor: "#f3f4f6",
2714
+ color: "#111827",
2715
+ alignSelf: "flex-start"
2716
+ },
2614
2717
  loading: {
2615
2718
  textAlign: "center",
2616
2719
  padding: "40px 20px"
@@ -2668,11 +2771,14 @@ function AttemptViewer({
2668
2771
  onError,
2669
2772
  className,
2670
2773
  showExplanations = true,
2774
+ showConversation = false,
2671
2775
  title
2672
2776
  }) {
2673
2777
  const [attempt, setAttempt] = useState4(null);
2674
2778
  const [loading, setLoading] = useState4(true);
2675
2779
  const [error, setError] = useState4(null);
2780
+ const [chatHistories, setChatHistories] = useState4({});
2781
+ const [expandedChats, setExpandedChats] = useState4(/* @__PURE__ */ new Set());
2676
2782
  useEffect4(() => {
2677
2783
  const apiClient = new QuizApiClient({
2678
2784
  baseUrl: apiBaseUrl,
@@ -2690,6 +2796,14 @@ function AttemptViewer({
2690
2796
  }
2691
2797
  const data = await response.json();
2692
2798
  setAttempt(data);
2799
+ if (showConversation) {
2800
+ try {
2801
+ const chats = await apiClient.getChatsByAttempt(attemptId);
2802
+ setChatHistories(chats);
2803
+ } catch (chatErr) {
2804
+ console.error("Failed to load chat histories:", chatErr);
2805
+ }
2806
+ }
2693
2807
  } catch (err) {
2694
2808
  const errorMessage = err instanceof Error ? err.message : "Failed to load attempt";
2695
2809
  setError(errorMessage);
@@ -2699,7 +2813,18 @@ function AttemptViewer({
2699
2813
  }
2700
2814
  }
2701
2815
  fetchAttempt();
2702
- }, [attemptId, apiBaseUrl, authToken, onError]);
2816
+ }, [attemptId, apiBaseUrl, authToken, onError, showConversation]);
2817
+ const toggleChatExpanded = (questionId) => {
2818
+ setExpandedChats((prev) => {
2819
+ const newSet = new Set(prev);
2820
+ if (newSet.has(questionId)) {
2821
+ newSet.delete(questionId);
2822
+ } else {
2823
+ newSet.add(questionId);
2824
+ }
2825
+ return newSet;
2826
+ });
2827
+ };
2703
2828
  const handleRetry = () => {
2704
2829
  setLoading(true);
2705
2830
  setError(null);
@@ -2791,6 +2916,34 @@ function AttemptViewer({
2791
2916
  /* @__PURE__ */ jsx4("strong", { children: "Explanation:" }),
2792
2917
  " ",
2793
2918
  answer.explanation
2919
+ ] }),
2920
+ showConversation && chatHistories[answer.questionId] && chatHistories[answer.questionId].messages.length > 0 && /* @__PURE__ */ jsxs4("div", { style: defaultStyles2.chatHistorySection, children: [
2921
+ /* @__PURE__ */ jsxs4(
2922
+ "button",
2923
+ {
2924
+ style: defaultStyles2.chatToggleButton,
2925
+ onClick: () => toggleChatExpanded(answer.questionId),
2926
+ "data-testid": `button-toggle-chat-${answer.questionId}`,
2927
+ children: [
2928
+ /* @__PURE__ */ jsx4("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx4("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) }),
2929
+ expandedChats.has(answer.questionId) ? "Hide" : "View",
2930
+ " Chat History (",
2931
+ chatHistories[answer.questionId].messages.length,
2932
+ " messages)"
2933
+ ]
2934
+ }
2935
+ ),
2936
+ expandedChats.has(answer.questionId) && /* @__PURE__ */ jsx4("div", { style: defaultStyles2.chatMessages, children: chatHistories[answer.questionId].messages.map((msg, msgIndex) => /* @__PURE__ */ jsx4(
2937
+ "div",
2938
+ {
2939
+ style: {
2940
+ ...defaultStyles2.chatMessage,
2941
+ ...msg.role === "user" ? defaultStyles2.chatMessageUser : defaultStyles2.chatMessageAssistant
2942
+ },
2943
+ children: msg.content
2944
+ },
2945
+ msgIndex
2946
+ )) })
2794
2947
  ] })
2795
2948
  ]
2796
2949
  },