Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AI Tutor] CT-253: Teachers can view flagged chats #57669

Merged
merged 17 commits into from Apr 9, 2024

Conversation

ebeastlake
Copy link
Contributor

@ebeastlake ebeastlake commented Mar 29, 2024

This PR:

  • Adds three dropdowns to the AI Tutor teacher dashboard to filter chats by status, student name, and time.
  • Refactors a couple of the shared constants related to AI Tutor and updates the names everywhere they're used.
  • Exports the CheckboxOption type from CheckboxDropdown so it can be used in TypeScript code that creates the options for the dropdown.

Follow-ups:

  • Iterate on UI
  • Add pagination

Video of filters in action:

initial_dropdown_work.mov

Links

Jira ticket: https://codedotorg.atlassian.net/browse/CT-253

Testing story

I tested manually. My manual testing was not the most comprehensive, but there's a lot of follow-up work to flag and persist the PII/profane status in the database, so I expect to continue testing this in the coming days.

TODO:

  • Confirm it's safe to change shared constants and there aren't additional steps to take

PR Checklist:

  • Tests provide adequate coverage
  • Privacy and Security impacts have been assessed
  • Code is well-commented
  • New features are translatable or updates will not break translations
  • Relevant documentation has been added or updated
  • User impact is well-understood and desirable
  • Pull Request is labeled appropriately
  • Follow-up work items (including potential tech debt) are tracked and linked

@ebeastlake ebeastlake changed the base branch from staging to eb-single-student-api-endpoints-for-ai-tutor March 29, 2024 23:14
@@ -96,8 +96,9 @@ def main
CHILD_ACCOUNT_COMPLIANCE_STATES
CENSUS_CONSTANTS
DANCE_SONG_MANIFEST_FILENAME
AI_TUTOR_INTERACTION_SAVE_STATUS
AI_TUTOR_INTERACTION_STATUS
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the rationale below for refactoring here. Is it dangerous to refactor shared constants?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems ok to me, especially since it's something pretty clearly owned/consumed by your team?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about Ben's comment at https://github.com/code-dot-org/code-dot-org/pull/57669/files#r1548141051 - are their plans to use STATUS on the backend for aichat @sanchitmalhotra126 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could but it's a bit unclear since our API design is still being finalized. I'm okay with keeping this in the AI Tutor space for now and revisiting/refactoring when the API is more solidified.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I said "dangerous," what I was thinking of was, "Is there some script or something I need to reseed this constant file?" If tests are passing, I'll assume not. I'm happy to refactor this more down the line if it's useful for generative AI.

export type AITutorTypesValue =
(typeof AITutorTypes)[keyof typeof AITutorTypes];
export type AITutorInteractionStatusValue =
(typeof AITutorInteractionStatus)[keyof typeof AITutorInteractionStatus];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was operating at the edge of my TypeScript knowledge, so I'd appreciate a second set of eyes. I'm also tripped up by naming conventions here and would appreciate any guidance.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't seen this syntax before, but let's try to figure it out! AITutorInteractionStatus is

 AI_TUTOR_INTERACTION_STATUS = {
    ERROR: 'error',
    PII_VIOLATION: 'pii_violation',
    PROFANITY_VIOLATION: 'profanity_violation',
    OK: 'ok',
    UNKNOWN: 'unknown',
}

keyof typeof AITutorInteractionStatus gives you all the keys in an object (helpful article). In this case it is 'ERROR' | 'PII_VIOLATION' | 'PROFANITY_VIOLATION' | 'OK' | 'UNKNOWN'

typeof AITutorInteractionStatus is (referencing that same article) {ERROR: string, PII_VIOLATION: string, ...}.

If you put those together, we are getting the types for each key in typeof AITutorInteractionStatus, which ends up being string | string | string ..., which gets combined into just string (I tried this out locally and that's what I ended up with, assuming my initial assumption on the object was correct). If you hover over AITutorInteractionStatusValue do you see string or something else? I'm guessing we actually wanted the values of the object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find an easy way to turn the values into a type if that's what we want.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The as const assertion can be handy here - if you add that to the original object, TS treat the types as the literal values (instead of string)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example -

const Status = {
  key1: 'value1',
  key2: 'value2',
};

// type is string
type StatusValues = (typeof Status)[keyof typeof Status];

const StatusConst = {
  key1: 'value1',
  key2: 'value2',
} as const;

// type is 'value1' | 'value2'
type StatusConstValue = (typeof StatusConst)[keyof typeof StatusConst];

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for the deep dive here, Molly and Sanchit!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrinkle: The imported constant comes from here:

raw = source_module.const_get(shared_const_name)
begin
.

I tried

import { AiTutorTypes } from '@cdo/apps/util/sharedConstants';

const AITutorTypes = AiTutorTypes as const;

And that doesn't work. One idea is to modify generateSharedConstants to re-export with const assertion, but that doesn't seem worth the trouble assuming it's possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this doesn't seem to work either:

export type AITutorInteractionStatusValue =
  | AITutorInteractionStatus.ERROR
  | AITutorInteractionStatus.PII_VIOLATION
  | AITutorInteractionStatus.PROFANITY_VIOLATION
  | AITutorInteractionStatus.OK
  | AITutorInteractionStatus.UNKNOWN;

I'm wondering if it's just easiest to have...

export type AITutorInteractionStatusValue =
  | 'error'
  | 'pii_violation'
  | 'profanity_violation'
  | 'ok'
  | 'unknown';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I think just copying the constants over seems fine here

studentName: string;
type: AITutorTypesValue;
updatedAt?: string;
userId: number;
Copy link
Contributor Author

@ebeastlake ebeastlake Apr 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These fields seemed missing but were on the chatMessage on the front end.


export type Level = {
export interface Level {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per recommendation from @molly-moen, prefer interface to type where possible.

import {
AiTutorInteractionStatus as AITutorInteractionStatus,
PiiTypes as PII,
} from '@cdo/apps/util/sharedConstants';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this file is used much anymore, but it shares types with AI tutor code. Tagging @sanchitmalhotra126 @bencodeorg @fisher-alice for confirmation this is okay.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new "generative AI" lab is using being built on the aichat lab, so it's back in use!

@@ -26,6 +26,12 @@ import {
} from '@cdo/apps/componentLibrary/common/contexts/DropdownContext';
import {dropdownColors} from '@cdo/apps/componentLibrary/common/constants';

export interface CheckboxOption {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't exported for use, so I added it because I have a lot of functions creating filters in TypeScript that benefitted from it. @levadadenys Is it okay that I added it?

PROFANITY: 'profanity',
INAPPROPRIATE: 'inappropriate',
PII_VIOLATION: 'pii_violation',
PROFANITY_VIOLATION: 'profanity_violation',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes in these two constants + rationale:

  1. Rename AI_TUTOR_INTERACTION_SAVE_STATUS to AI_TUTOR_INTERACTION_STATUS because, in this case, "saved" and "ok" are the same, so the original name included the success case described within it.
  2. Rename "personal" to "PII_VIOLATION" and "profanity" to "PROFANITY_VIOLATION" to match the AI TA code: . I think this also hints that these two types of messages map directly to checks we already have in our system (WebPurify checking for PII and profanity), instead of generically being personal, for example.
  3. Move "email", "address", and "phone" into their own constant. These are not interaction statuses, they're types of PII that might be flagged, so I think they should live somewhere else.

Doing this refactor also allowed me to build my initial status filter to map directly to the states of this "AI_TUTOR_INTERACTION_STATUS" constant without unused fields.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems reasonable to me! did you find any usages of the email/phone/address values in Ruby? It looks like they might have been moved there in an earlier refactor, might make sense for them just to live in a JS file:

https://github.com/code-dot-org/code-dot-org/pull/56361/files

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expected to, but I didn't, Ben! Some of my tasks include sending AI Tutor chats through the PII/profanity filters and ensuring they're flagged appropriately. Do you mind leaving this here until I decide whether to use them on the backend? I filed a follow-up task to remove if I don't end up using them: https://codedotorg.atlassian.net/browse/CT-470

const generalChat = tutorType === TutorType.GENERAL_CHAT;
const compilation = tutorType === TutorType.COMPILATION;
const validation = tutorType === TutorType.VALIDATION;
const generalChat = tutorType === TutorTypes.GENERAL_CHAT;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got a little refactor happy and renamed TutorType to TutorTypes to indicate that this references an object that stores all of the types/multiple fields 😅

Base automatically changed from eb-single-student-api-endpoints-for-ai-tutor to staging April 2, 2024 04:34
Copy link
Contributor

@bencodeorg bencodeorg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just looked at the stuff in the aichat directory / shared code, but that all looks reasonable to me!

@@ -1,11 +1,19 @@
import {LevelProperties} from '@cdo/apps/lab2/types';
import {AiTutorInteractionSaveStatus} from '@cdo/apps/util/sharedConstants';
import {
AiTutorInteractionStatus as AITutorInteractionStatus,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly out of scope, but should this be called AiInteractionStatus if it's used across aichat and aitutor?

Copy link
Contributor Author

@ebeastlake ebeastlake Apr 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bencodeorg I think my ultimate goal is in the spirit of this comment, which is to condense/standardize things. I don't know how long the AI Chat use case will continue to be relevant.

Per @sanchitmalhotra126's comment above, the best way to achieve clarity/reusability might be to 1) wait until we have a better understanding of the API for the gen AI unit, 2) use it to make these values more reusable across gen AI and AI tutor (and then rename them then if applicable), and 3) ultimately look to deprecate any aichat code that doesn't move to AI Tutor or Gen AI code? As far as I know, the AI Chat allthethings level is no longer used...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you all creating a new directory or using the aichat directory?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're actively doing development of the gen AI unit using the AI Chat allthethings level! AI Chat is being repurposed as gen AI. The API will probably be replaced though, to Sanchit's point. Fine to leave as-is for now, just a thought :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry just saw comment re: directory, using the aichat directory!

import {
AiTutorInteractionStatus as AITutorInteractionStatus,
PiiTypes as PII,
} from '@cdo/apps/util/sharedConstants';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new "generative AI" lab is using being built on the aichat lab, so it's back in use!

@@ -96,8 +96,9 @@ def main
CHILD_ACCOUNT_COMPLIANCE_STATES
CENSUS_CONSTANTS
DANCE_SONG_MANIFEST_FILENAME
AI_TUTOR_INTERACTION_SAVE_STATUS
AI_TUTOR_INTERACTION_STATUS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems ok to me, especially since it's something pretty clearly owned/consumed by your team?

PROFANITY: 'profanity',
INAPPROPRIATE: 'inappropriate',
PII_VIOLATION: 'pii_violation',
PROFANITY_VIOLATION: 'profanity_violation',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems reasonable to me! did you find any usages of the email/phone/address values in Ruby? It looks like they might have been moved there in an earlier refactor, might make sense for them just to live in a JS file:

https://github.com/code-dot-org/code-dot-org/pull/56361/files

Copy link
Contributor

@sanchitmalhotra126 sanchitmalhotra126 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The aichat changes look good to me as well!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I expect that for Gen AI we may just create a new API file since we're no longer using this chat API. If it makes more sense to just move this to the aiTutor directory, feel free!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right that refactoring this code makes sense. It looks like the only way this file is used is to import {getChatCompletionMessage} from '../chatApi'; in two files: aichatRedux.ts and aiTutorRedux.ts. There will be a fairly significant diff with that refactor, so I'd like to address it as a follow-up task!

const messageDate = moment(message.createdAt);
let timeMatch = false;
switch (selectedTimeFilter) {
case 'lastHour':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a way for us to have these values as constants instead of hard-coding them in multiple places? Could we have an enum that we use to generate the formatted dropdown options?

allOptions={statusOptions}
checkedOptions={selectedStatuses}
color="black"
labelText="Filter by Status"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth turning these into translated strings now or are things still too in flux for that? Should a follow-up task be followed for translation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, @molly-moen. Given the quick timeline for the closed beta and uncertainty about whether we will be offering AI Tutor in languages other than English, I'd like permission to handle this as a follow-up task: https://codedotorg.atlassian.net/browse/CT-469

I've added it to the closed beta release because I think the easiest time to add the translated strings is when you write the code, but noted it as non-blocking.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep a follow up seems reasonable!

Copy link
Contributor

@molly-moen molly-moen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

(typeof AITutorInteractionStatus)[keyof typeof AITutorInteractionStatus];
// TODO: Update this once https://codedotorg.atlassian.net/browse/CT-471 is resolved
export type AITutorTypesValue = string;
export type AITutorInteractionStatusValue = string;
Copy link
Contributor Author

@ebeastlake ebeastlake Apr 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionally, this was only checking for a string before. Narrowing the type-checking here was difficult, and throwing many errors because these are generated constants. I filed https://codedotorg.atlassian.net/browse/CT-471 to address it.

@ebeastlake ebeastlake merged commit 9a4ae51 into staging Apr 9, 2024
2 checks passed
@ebeastlake ebeastlake deleted the emily/ct-253/view-flagged-chats branch April 9, 2024 01:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants