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

feat: added option to modify icebreakers with ai #9268

Merged
merged 7 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import styled from '@emotion/styled'
import {Create as CreateIcon, Refresh as RefreshIcon} from '@mui/icons-material'
import graphql from 'babel-plugin-relay/macro'
import {convertToRaw, EditorState, SelectionState} from 'draft-js'
import {ContentState, convertToRaw, EditorState, SelectionState} from 'draft-js'
import React, {useRef, useState} from 'react'
import {useFragment} from 'react-relay'
import {NewCheckInQuestion_meeting$key} from '~/__generated__/NewCheckInQuestion_meeting.graphql'
Expand All @@ -13,8 +13,15 @@ import {MenuPosition} from '../../../../hooks/useCoords'
import useEditorState from '../../../../hooks/useEditorState'
import useTooltip from '../../../../hooks/useTooltip'
import UpdateNewCheckInQuestionMutation from '../../../../mutations/UpdateNewCheckInQuestionMutation'
import ModifyCheckInQuestionMutation from '../../../../mutations/ModifyCheckInQuestionMutation'
import {PALETTE} from '../../../../styles/paletteV3'
import convertToTaskContent from '../../../../utils/draftjs/convertToTaskContent'
import useMutationProps from '../../../../hooks/useMutationProps'
import {
ModifyCheckInQuestionMutation$data,
ModifyType
} from '../../../../__generated__/ModifyCheckInQuestionMutation.graphql'
import {Button} from '../../../../ui/Button/Button'

const CogIcon = styled('div')({
color: PALETTE.SLATE_700,
Expand Down Expand Up @@ -77,14 +84,34 @@ const NewCheckInQuestion = (props: Props) => {
checkInQuestion
}
}
team {
organization {
featureFlags {
aiIcebreakers
}
}
}
}
`,
meetingRef
)
const [isEditing, setIsEditing] = useState(false)
const {id: meetingId, localPhase, facilitatorUserId} = meeting
const [aiUpdatedIcebreaker, setAiUpdatedIcebreaker] = useState('')
const {
id: meetingId,
localPhase,
facilitatorUserId,
team: {
organization: {featureFlags}
}
} = meeting
const {checkInQuestion} = localPhase
const {viewerId} = atmosphere
const isFacilitating = facilitatorUserId === viewerId

const [editorState, setEditorState] = useEditorState(checkInQuestion)
const {submitting, submitMutation, onCompleted, onError} = useMutationProps()

const updateQuestion = (nextEditorState: EditorState) => {
const wasFocused = editorState.getSelection().getHasFocus()
const isFocused = nextEditorState.getSelection().getHasFocus()
Expand All @@ -94,11 +121,16 @@ const NewCheckInQuestion = (props: Props) => {
const nextCheckInQuestion = nextContent.hasText()
? JSON.stringify(convertToRaw(nextContent))
: ''

if (nextCheckInQuestion === checkInQuestion) return
UpdateNewCheckInQuestionMutation(atmosphere, {
meetingId,
checkInQuestion: nextCheckInQuestion
})
UpdateNewCheckInQuestionMutation(
atmosphere,
{
meetingId,
checkInQuestion: nextCheckInQuestion
},
{onCompleted, onError}
)
}
setEditorState(nextEditorState)
}
Expand All @@ -108,27 +140,16 @@ const NewCheckInQuestion = (props: Props) => {
const currentText = editorRef.current?.value
const nextCheckInQuestion = convertToTaskContent(currentText || '')
if (nextCheckInQuestion === checkInQuestion) return
UpdateNewCheckInQuestionMutation(atmosphere, {
meetingId,
checkInQuestion: nextCheckInQuestion
})
UpdateNewCheckInQuestionMutation(
atmosphere,
{
meetingId,
checkInQuestion: nextCheckInQuestion
},
{onCompleted, onError}
)
}

const focusQuestion = () => {
closeEditIcebreakerTooltip()
editorRef.current && editorRef.current.focus()
const selection = editorState.getSelection()
const contentState = editorState.getCurrentContent()
const jumpToEnd = (selection as any).merge({
anchorOffset: contentState.getLastBlock().getLength(),
focusOffset: contentState.getLastBlock().getLength()
}) as SelectionState
const nextEditorState = EditorState.forceSelection(editorState, jumpToEnd)
setEditorState(nextEditorState)
}
const {viewerId} = atmosphere
const isFacilitating = facilitatorUserId === viewerId
// eslint-disable-next-line react-hooks/rules-of-hooks
const {
tooltipPortal: editIcebreakerTooltipPortal,
openTooltip: openEditIcebreakerTooltip,
Expand All @@ -146,53 +167,177 @@ const NewCheckInQuestion = (props: Props) => {
disabled: !isFacilitating
})

const focusQuestion = () => {
closeEditIcebreakerTooltip()
editorRef.current && editorRef.current.focus()
const selection = editorState.getSelection()
const contentState = editorState.getCurrentContent()
const jumpToEnd = (selection as any).merge({
anchorOffset: contentState.getLastBlock().getLength(),
focusOffset: contentState.getLastBlock().getLength()
}) as SelectionState
const nextEditorState = EditorState.forceSelection(editorState, jumpToEnd)
setEditorState(nextEditorState)
}

const {
Copy link
Member

Choose a reason for hiding this comment

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

alternatively, relay's useMutation gives us those powers for free! would love to get more instances of the new pattern in the codebase

const [commit, submitting] = useMutation<TAcceptRequestToJoinDomainMutation>(mutation)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

omg how could I miss that. old habits die hard, fixed!

submitting: isModifyingCheckInQuestion,
submitMutation: submitModifyCheckInQuestion,
onError: onModifyCheckInQuestionError,
onCompleted: onModifyCheckInQuestionCompleted
} = useMutationProps()
const refresh = () => {
UpdateNewCheckInQuestionMutation(atmosphere, {
meetingId,
checkInQuestion: ''
})
UpdateNewCheckInQuestionMutation(
atmosphere,
{
meetingId,
checkInQuestion: ''
},
{onCompleted, onError}
)
setAiUpdatedIcebreaker('')
}

const updateCheckInQuestionWithGeneratedContent = () => {
submitMutation()
UpdateNewCheckInQuestionMutation(
atmosphere,
{
meetingId,
checkInQuestion: JSON.stringify(
convertToRaw(ContentState.createFromText(aiUpdatedIcebreaker))
)
},
{onCompleted, onError}
)
setAiUpdatedIcebreaker('')
}

const modify = (modifyType: ModifyType) => {
submitModifyCheckInQuestion()

const icebreakerToModify = aiUpdatedIcebreaker || checkInQuestion!
ModifyCheckInQuestionMutation(
atmosphere,
{
meetingId,
checkInQuestion: icebreakerToModify,
modifyType
},
{
onCompleted: (res: ModifyCheckInQuestionMutation$data) => {
onModifyCheckInQuestionCompleted()
const {modifyCheckInQuestion} = res
if (!modifyCheckInQuestion.modifiedCheckInQuestion) {
return
}

setAiUpdatedIcebreaker(modifyCheckInQuestion.modifiedCheckInQuestion)
},
onError: onModifyCheckInQuestionError
}
)
}

const shouldShowAiIcebreakers = featureFlags?.aiIcebreakers && isFacilitating

return (
<QuestionBlock>
{/* cannot set min width because iPhone 5 has a width of 320*/}
<EditorInputWrapper
ariaLabel={'Edit the icebreaker'}
editorState={editorState}
setEditorState={updateQuestion}
readOnly={!isFacilitating}
placeholder='e.g. How are you?'
editorRef={editorRef}
setEditorStateFallback={updateQuestionAndroidFallback}
/>
{isFacilitating && (
<div className='flex gap-x-2'>
<PlainButton
aria-label={'Edit icebreaker'}
onClick={focusQuestion}
onMouseEnter={openEditIcebreakerTooltip}
onMouseLeave={closeEditIcebreakerTooltip}
ref={editIcebreakerOriginRef}
>
<CogIcon>
<CreateIcon />
</CogIcon>
</PlainButton>
<PlainButton
aria-label={'Refresh icebreaker'}
onClick={refresh}
onMouseEnter={openRefreshIcebreakerTooltip}
onMouseLeave={closeRefreshIcebreakerTooltip}
ref={refreshIcebreakerOriginRef}
>
<CogIcon>
<RefreshIcon />
</CogIcon>
</PlainButton>
<>
<QuestionBlock id='test'>
{/* cannot set min width because iPhone 5 has a width of 320*/}
<EditorInputWrapper
ariaLabel={'Edit the icebreaker'}
editorState={editorState}
setEditorState={updateQuestion}
readOnly={!isFacilitating}
placeholder='e.g. How are you?'
editorRef={editorRef}
setEditorStateFallback={updateQuestionAndroidFallback}
/>
{isFacilitating && (
<div className='flex gap-x-2'>
<PlainButton
aria-label={'Edit icebreaker'}
onClick={focusQuestion}
onMouseEnter={openEditIcebreakerTooltip}
onMouseLeave={closeEditIcebreakerTooltip}
ref={editIcebreakerOriginRef}
>
<CogIcon>
<CreateIcon />
</CogIcon>
</PlainButton>
<PlainButton
aria-label={'Refresh icebreaker'}
onClick={refresh}
onMouseEnter={openRefreshIcebreakerTooltip}
onMouseLeave={closeRefreshIcebreakerTooltip}
ref={refreshIcebreakerOriginRef}
>
<CogIcon>
<RefreshIcon />
</CogIcon>
</PlainButton>
</div>
)}
</QuestionBlock>
{shouldShowAiIcebreakers && (
<div className='flex flex-col gap-6 rounded-lg bg-slate-100 p-4'>
<div className='flex flex-col items-center justify-center gap-2'>
<div className='inline-flex gap-2'>
<div className='font-semibold'>Modify current icebreaker with AI</div>
</div>
<div className='text-center text-sm italic'>
<div>As a facilitator, you can spice up the current icebreaker with AI.</div>
<div>Others will see the result only if you approve it.</div>
</div>
</div>
{aiUpdatedIcebreaker && <div className='p-2 text-center'>{aiUpdatedIcebreaker}</div>}
<div className='flex items-center justify-center gap-x-3'>
<Button
variant='outline'
shape='pill'
size='sm'
disabled={isModifyingCheckInQuestion}
onClick={() => modify('SERIOUS')}
>
More serious
</Button>
<Button
variant='outline'
shape='pill'
size='sm'
disabled={isModifyingCheckInQuestion}
onClick={() => modify('FUNNY')}
>
More funny
</Button>
<Button
variant='outline'
shape='pill'
size='sm'
disabled={isModifyingCheckInQuestion}
onClick={() => modify('EXCITING')}
>
More exciting
</Button>
</div>
<div className='flex items-center justify-center gap-x-3'>
<Button
variant='secondary'
shape='pill'
size='md'
disabled={aiUpdatedIcebreaker === '' || isModifyingCheckInQuestion || submitting}
onClick={updateCheckInQuestionWithGeneratedContent}
>
{isModifyingCheckInQuestion ? 'Modifying...' : 'Approve'}
</Button>
</div>
</div>
)}
{editIcebreakerTooltipPortal(<>Edit icebreaker</>)}
{refreshIcebreakerTooltipPortal(<>Refresh icebreaker</>)}
</QuestionBlock>
</>
)
}

Expand Down
46 changes: 46 additions & 0 deletions packages/client/mutations/ModifyCheckInQuestionMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import graphql from 'babel-plugin-relay/macro'
import {commitMutation} from 'react-relay'
import {StandardMutation} from '../types/relayMutations'
import {ModifyCheckInQuestionMutation as TModifyCheckInQuestionMutation} from '../__generated__/ModifyCheckInQuestionMutation.graphql'

graphql`
fragment ModifyCheckInQuestionMutation_meeting on ModifyCheckInQuestionSuccess {
modifiedCheckInQuestion
}
`

const mutation = graphql`
mutation ModifyCheckInQuestionMutation(
$meetingId: ID!
$checkInQuestion: String!
$modifyType: ModifyType!
) {
modifyCheckInQuestion(
meetingId: $meetingId
checkInQuestion: $checkInQuestion
modifyType: $modifyType
) {
... on ErrorPayload {
error {
message
}
}
...ModifyCheckInQuestionMutation_meeting @relay(mask: false)
}
}
`

const ModifyCheckInQuestionMutation: StandardMutation<TModifyCheckInQuestionMutation> = (
atmosphere,
variables,
{onCompleted, onError}
) => {
return commitMutation<TModifyCheckInQuestionMutation>(atmosphere, {
mutation,
variables,
onCompleted,
onError
})
}

export default ModifyCheckInQuestionMutation
Loading
Loading