@@ -549,6 +549,47 @@ function createStudyCluesWidget() {
549549 border: 1px solid #e5e7eb;
550550 }
551551
552+ .studyclues-message.studyclues-loading {
553+ color: #6b7280;
554+ font-style: italic;
555+ }
556+
557+ .studyclues-spinner {
558+ display: inline-flex;
559+ align-items: center;
560+ gap: 4px;
561+ }
562+
563+ .studyclues-spinner-dot {
564+ width: 6px;
565+ height: 6px;
566+ border-radius: 50%;
567+ background: #6b7280;
568+ opacity: 0.35;
569+ animation: studyclues-dot-pulse 1s infinite ease-in-out;
570+ }
571+
572+ .studyclues-spinner-dot:nth-child(2) {
573+ animation-delay: 0.15s;
574+ }
575+
576+ .studyclues-spinner-dot:nth-child(3) {
577+ animation-delay: 0.3s;
578+ }
579+
580+ @keyframes studyclues-dot-pulse {
581+ 0%,
582+ 80%,
583+ 100% {
584+ opacity: 0.35;
585+ transform: scale(1);
586+ }
587+ 40% {
588+ opacity: 1;
589+ transform: scale(1.2);
590+ }
591+ }
592+
552593 .studyclues-inputbar {
553594 display: flex;
554595 gap: 8px;
@@ -629,6 +670,14 @@ function createStudyCluesWidget() {
629670 inputEl . value = "" ;
630671 sendBtn . disabled = true ;
631672
673+ const loadingBubble = document . createElement ( "div" ) ;
674+ loadingBubble . className =
675+ "studyclues-message assistant studyclues-loading" ;
676+ loadingBubble . innerHTML =
677+ 'Thinking <span class="studyclues-spinner"><span class="studyclues-spinner-dot"></span><span class="studyclues-spinner-dot"></span><span class="studyclues-spinner-dot"></span></span>' ;
678+ messagesEl . appendChild ( loadingBubble ) ;
679+ messagesEl . scrollTop = messagesEl . scrollHeight ;
680+
632681 try {
633682 const response = await fetch (
634683 `/assignment/student/studyclues_query` ,
@@ -671,6 +720,7 @@ function createStudyCluesWidget() {
671720 ) ;
672721 console . error ( "StudyClues chat error:" , err ) ;
673722 } finally {
723+ loadingBubble . remove ( ) ;
674724 sendBtn . disabled = false ;
675725 inputEl . focus ( ) ;
676726 }
@@ -759,7 +809,7 @@ async function handlePageSetup() {
759809 document . dispatchEvent ( new Event ( "runestone:login" ) ) ;
760810 addReadingList ( ) ;
761811 // Only show the StudyClues widget for certain base courses and when the path includes "/ns/books/".
762- if ( [ "csawesome2" , "py4e-int" , "thinkcspy" , "PTXSB" ] . includes ( eBookConfig . basecourse )
812+ if ( [ "csawesome2" , "py4e-int" , "thinkcspy" , "PTXSB" ] . includes ( eBookConfig . basecourse )
763813 && location . pathname . includes ( "/ns/books/" ) ) {
764814 createStudyCluesWidget ( ) ;
765815 }
0 commit comments