[Aichat] Teacher view of student chat history - UI updates - #60483
#60483[Aichat] Teacher view of student chat history - UI updates#60483fisher-alice merged 9 commits intostagingcode-dot-org/code-dot-org:stagingfrom alice/student-chat-history-part1code-dot-org/code-dot-org:alice/student-chat-history-part1Copy head branch name to clipboard
Conversation
| interface ChatEventsListProps { | ||
| events: ChatEvent[]; | ||
| showWaitingAnimation: () => React.ReactNode; | ||
| showWaitingAnimation: boolean; |
There was a problem hiding this comment.
@sanchitmalhotra126 - addressing comment from #60322 (comment).
There was a problem hiding this comment.
FYI - refactored so prop no longer needed based on #60483 (comment).
thomasoniii
left a comment
There was a problem hiding this comment.
minor syntactic suggestion, but otherwise lgtm 🚀
|
Hi @thomasoniii - I re-requested your review bc I added a substantive commit that allows auto-scrolling since you approved. thanks! |
| if ( | ||
| profaneMessageViewToggle === ProfaneMessageViewToggle.VIEW | ||
| ) { | ||
| setProfaneMessageViewToggle(ProfaneMessageViewToggle.HIDE); | ||
| } else { | ||
| setProfaneMessageViewToggle(ProfaneMessageViewToggle.VIEW); | ||
| } |
There was a problem hiding this comment.
I must've missed commenting on this...another suggestion as opposed to a necessity, but this is the sort of thing I'd tend to put in a ternary:
setProfaneMessageViewToggle(
profaneMessageViewToggle === ProfaneMessageViewToggle.VIEW
? ProfaneMessageViewToggle.HIDE
: ProfaneMessageViewToggle.HIDE
);
Main benefit is to not have the function call in two places.
And, further waxing philosophical as opposed to making a serious suggestion here, state machines can work great for this sort of thing.
// defined..somewhere
const nextMessageState = {
ProfaneMessageViewToggle.HIDE : ProfaneMessageViewToggle.VIEW,
ProfaneMessageViewToggle.VIEW : ProfaneMessageViewToggle.HIDE,
}
// and then in the onClick
onClick = { () => setProfaneMessageViewToggle( nextMessageState[profaneMessageViewToggle] ) }
Since we've only got two states I think sticking with the ternary is smart, this strategy is really most useful for when there are more states. I just figured I'd jot down the concept somewhere. 😄
There was a problem hiding this comment.
Oh cool (the nextMessageState idea)! Great for future reference.
There was a problem hiding this comment.
Will update to ternary.
| /> | ||
| ))} | ||
| {showWaitingAnimation()} | ||
| {isWaitingForChatResponse && displayWaitingAnimation()} |
There was a problem hiding this comment.
I'm sorry, I must have zipped past this too fast last time - isWaitingForChatResponse is redundant here.
Don't get me wrong - it's okay as is, but I'm gonna forge ahead with my suggestion here. isWaitingForChatResponse is already baked into the callback for displayWaitingAnimation. The function would return an undefined if it's false already, so the logical wrapper doesn't add to it.
I'm pretty sure the displayWaitingAnimation function would need an else { return null } to make react happy, but otherwise it'd be fine. Then it could just be { displayWaitingAnimation() }
Taking it a step further, I can see a couple of different ways to refine it further.
- instead of a
useCallback, it could beuseMemoand skip the function call during the render, just embedding{ displayWaitingAnimation } - I might not write this as a hook at all - I think I'd prefer to have it as a regular function (defined outside the component) that takes a boolean arg (
shouldDisplay?), and then it'd be called as{ displayWaitingAnimation( isWaitingForChatResponse ) }. That way it's a little more generic (but I doubt this'd ever be re-used externally) and it makes it a little more clear that it depends upon theisWaitingForChatReponsevariable. Yes, I'm aware that my earlier comment refactored away the conditional that explicitly had that variable. Refactoring has a lot of backtracking 😅 - Last thing, taking that function-with-arg a step further, it could be pulled out into a separate component (either inline or external). And then maybe it'd be called as
<WaitingAnimation shouldDisplay={isWaitingForChatResponse} />(where this component would basically look exactly like the function). Just to put a component in the display instead of a variable. I tend to skew towards components over inline functions/variables where possible.
Again, not that any of this is necessary, just more musing outloud to see if anything looks preferable.
There was a problem hiding this comment.
Making an inline component makes sense to me. I really appreciate how you outlined your thought process here. Thanks!
There was a problem hiding this comment.
Oooo - now I can remove unnec showWaitingAnimation prop. Thanks again @thomasoniii !
thomasoniii
left a comment
There was a problem hiding this comment.
still lgtm 🚀
(though I did add a few more non-blocking suggestions, since I clearly paid more attention in my second pass 😅 )
sanchitmalhotra126
left a comment
There was a problem hiding this comment.
Looks good overall! Some minor comments/questions
|
|
||
| .userProfaneMessageButton { | ||
| width: 120px; | ||
| margin-top: -10px; |
There was a problem hiding this comment.
What's this needed for?
There was a problem hiding this comment.
The button that hides/shows the profane user message.
There was a problem hiding this comment.
Oh yep, was wondering specifically why we needed the negative margin? Is it to cut into the chat message margin?
There was a problem hiding this comment.
oh yeah - so that the button was a little closer to its associate chat message.
There was a problem hiding this comment.
Ah interesting - I don't think the chat message itself has any extra margin, so I'm wondering if it might be better to nest the button inside the container div and add a bit of padding/margin/gap there instead? I'm just thinking that the -10px makes some assumptions about how chat messages are spaced out which may potentially differ from app to app?
There was a problem hiding this comment.
Yeah - I tried that at first, but then the button and message were set in a row.

So then I tried updating flex-direction to column, but then it messed with the user chat message styling (should be aligned to the right but after style update the message takes up entire width).

Here's what it should look like:

I think I'll leave as-is for now and we can address in a follow-up if you have ideas to resolve this. Thanks!
| {showWaitingAnimation()} | ||
| <WaitingAnimation shouldDisplay={isWaitingForChatResponse} /> |
| } | ||
|
|
||
| .tabPanels { | ||
| .tabPanelsChatWithModelActive { |
There was a problem hiding this comment.
Why does this class not need the display: flex and height: 100% in its inner div?
There was a problem hiding this comment.
Ahh...when I included
> div {
display: flex;
height: 100%;
}
The hidden attribute was overridden and the content from both tab panels were displayed.
Screen.Recording.2024-08-20.at.3.34.51.PM.mov
But I overlooked that now when the 'Test student model' tab is active, auto-scrolling no longer works.
There was a problem hiding this comment.
We chatted offline and now using the attribute selector hidden to address this. Will updated - thanks so much!
sanchitmalhotra126
left a comment
There was a problem hiding this comment.
LGTM!
One overall note (discussed offline) - as other AI features adopt this component, we may want to pull out some of the logic from this component (i.e. determining what text to display) and keep it more presentational. For now thought I think it makes sense to keep it all in here since AI Chat is the only consumer of this component and we can always refactor later.
This PR makes UI updates to the teacher view of student chat history in aichat levels which includes:
-ability to view/hide user messages marked as profane
Alertinformation UI (blue)Note that I checked with @markabarrett about these UI updates in this Slack thread.
I also included updating the prop
showWaitingAnimationto be abooleanto address #60322 (comment).The auto-scrolling to the most recent chat event was quite tricky. Thanks to @sanchitmalhotra126 for helping me figure out that the parent of the student chat history div (
_TabPaneldiv) requiresdisplay: flexandheight: 100%in order for the computed scroll height to have effect. Withoutheight: 100%, the parentdivwas set to the height of the entire chat history.I don't think I fully understand whydisplay: flexis necessary - I welcome thoughts on this. 😄display: flexis needed since the child component (scrollable container) hasflex-grow: 1but this doesn't have any effect unless the parent (tab panel) hasdisplay: flex. (Thanks Sanchit!)The other update I needed to include was a condition on which tab was active. If I didn't include the condition that the style above was only when the student chat history tab was active, then the chat history was in view even when 'Test student model' tab was active. Initially, I thought I needed to add additionalclassNamesfor theTabscomponent for the active/hidden tabs, but thankfully can just update style according to which tab is active.Based on #60483 (comment) - updated style for the tab panels container so that for the active panel, display is set to
flexand for the hidden panel, display is set tonone.Screencast video:
Screen.Recording.2024-08-19.at.10.26.07.AM.mov
Links
jra
Testing story
I tested locally in aichat levels in /allthethings with a teacher account that has a section with 3 students.
Deployment strategy
Follow-up work
Remove experiment flag after testing on production.
Privacy
Security
Caching
PR Checklist: