Skip to content

Commit 5755ae6

Browse files
author
kim
committed
feat: update player view
1 parent ea5ff6e commit 5755ae6

19 files changed

+316
-267
lines changed

src/config/selectors.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ export const buildChatbotPromptContainerDataCy = (id: string): string =>
102102
export const buildCommentResponseBoxDataCy = (id: string): string =>
103103
`${COMMENT_RESPONSE_BOX_CY}-${id}`;
104104

105-
export const COMMENT_THREAD_CONTAINER_CYPRESS = 'comment_thread_container';
106105
export const ORPHAN_BUTTON_CYPRESS = 'orphan_button';
107106
export const CODE_EXECUTION_CONTAINER_CYPRESS = 'code_execution_container';
108107
export const CODE_EDITOR_ID_CY = 'code_editor';

src/langs/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"FILTER_BY_COMMON_KEYWORDS": "Filter by most frequent keywords",
6262
"SEARCH_BY_OTHER_KEYWORDS": "Search by other keywords or regex",
6363
"CHECK_WHOLE_CHAT": "Check the whole chat",
64-
"CONFIGURE_BUTTON": "Configure"
64+
"CONFIGURE_BUTTON": "Configure",
65+
"CLOSE": "Close"
6566
}
6667
}

src/modules/analytics/FrequentWords.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
44
import {
55
Button,
66
Chip,
7+
Dialog,
78
Grid2,
89
Stack,
910
TextField,
@@ -19,9 +20,9 @@ import {
1920
buildKeywordChipId,
2021
} from '@/config/selectors';
2122

23+
import { ConversationForUser } from '../comment/ConversationForUser';
2224
import KeywordChip from '../common/KeywordChip';
2325
import TextWithHighlightedKeywords from '../common/TextWithHighlightedKeywords';
24-
import PlayerView from '../main/PlayerView';
2526
import { createRegexFromString, getTopFrequentWords } from './utils';
2627

2728
type Props = {
@@ -152,15 +153,15 @@ function FrequentWords({
152153
</Stack>
153154
</Grid2>
154155
{chatMemberID && (
155-
<Grid2
156-
size={{ xs: 12, md: 6 }}
157-
sx={{ height: '100%', overflow: 'hidden' }}
156+
<Dialog
157+
open
158+
onClose={() => {
159+
setChatMemberID('');
160+
}}
158161
>
159-
<PlayerView
160-
id={chatMemberID}
161-
threadSx={{ overflow: 'auto', height: '100%' }}
162-
/>
163-
</Grid2>
162+
<ConversationForUser userId={chatMemberID} />
163+
<Button>{t('CLOSE')}</Button>
164+
</Dialog>
164165
)}
165166
</Grid2>
166167
</Stack>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from 'react';
2+
3+
import { Stack, Typography } from '@mui/material';
4+
5+
import ChatbotAvatar from '../common/ChatbotAvatar';
6+
7+
function ChatbotHeader({ name }: Readonly<{ name: string }>) {
8+
return (
9+
<Stack alignItems="center" width="100%" gap={1}>
10+
<ChatbotAvatar />
11+
<Typography variant="h4" component="h1">
12+
{name}
13+
</Typography>
14+
</Stack>
15+
);
16+
}
17+
18+
export default ChatbotHeader;

src/modules/comment/CommentContainer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { BIG_BORDER_RADIUS } from '../../constants';
55
const CommentContainer = styled('div')(({ theme }) => ({
66
backgroundColor: 'white',
77
border: 'solid silver 1px',
8-
padding: theme.spacing(1, 0),
8+
padding: theme.spacing(3, 0),
99
borderRadius: BIG_BORDER_RADIUS,
1010
}));
1111
export default CommentContainer;
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { useTranslation } from 'react-i18next';
2+
3+
import {
4+
Alert,
5+
Box,
6+
CircularProgress,
7+
Divider,
8+
Stack,
9+
SxProps,
10+
Theme,
11+
} from '@mui/material';
12+
13+
import { ChatbotPromptSettings } from '@/config/appSetting';
14+
import { DEFAULT_BOT_USERNAME } from '@/constants';
15+
16+
import CommentEditor from '../common/CommentEditor';
17+
import CommentThread from '../common/CommentThread';
18+
import { Comment } from '../common/useConversation';
19+
import ChatbotHeader from './ChatbotHeader';
20+
import CommentContainer from './CommentContainer';
21+
22+
function Conversation({
23+
threadSx,
24+
comments,
25+
chatbotPrompt,
26+
isLoading,
27+
}: Readonly<{
28+
chatbotPrompt?: ChatbotPromptSettings;
29+
threadSx?: SxProps<Theme>;
30+
isLoading?: boolean;
31+
comments: Comment[];
32+
}>) {
33+
const { t } = useTranslation();
34+
35+
if (chatbotPrompt) {
36+
const { chatbotCue, chatbotName } = chatbotPrompt;
37+
38+
// construct cue message if there is no messages yet
39+
let commentsToShow = comments;
40+
if (0 === comments.length) {
41+
commentsToShow = [
42+
{
43+
id: 'cue',
44+
isBot: true,
45+
createdAt: new Date().toISOString(),
46+
body: chatbotCue,
47+
username: DEFAULT_BOT_USERNAME,
48+
},
49+
];
50+
}
51+
52+
return (
53+
<Box
54+
sx={{
55+
px: { xs: 2, sm: 10 },
56+
maxWidth: '100ch',
57+
m: 'auto',
58+
height: '100%',
59+
}}
60+
>
61+
<CommentContainer>
62+
<Stack gap={3} px={2}>
63+
<ChatbotHeader name={chatbotName} />
64+
<Divider />
65+
<CommentThread threadSx={threadSx} comments={commentsToShow} />
66+
<CommentEditor chatbotPrompt={chatbotPrompt} />
67+
</Stack>
68+
</CommentContainer>
69+
</Box>
70+
);
71+
}
72+
73+
if (isLoading) {
74+
return <CircularProgress />;
75+
}
76+
77+
return <Alert severity="warning">{t('CHATBOT_CONFIGURATION_MISSING')}</Alert>;
78+
}
79+
80+
export default Conversation;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useConversation } from '../common/useConversation';
2+
import Conversation from './Conversation';
3+
4+
export const ConversationForUser = ({
5+
userId,
6+
}: Readonly<{ userId: string }>) => {
7+
const { comments, isLoading, chatbotPrompt } = useConversation(userId);
8+
9+
return (
10+
<Conversation
11+
isLoading={isLoading}
12+
comments={comments ?? []}
13+
threadSx={{ overflow: 'auto', height: '100%' }}
14+
chatbotPrompt={chatbotPrompt}
15+
/>
16+
);
17+
};

src/modules/common/ChatbotPrompt.tsx

Lines changed: 9 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,19 @@
1-
import { useTranslation } from 'react-i18next';
2-
3-
import { Alert, CardContent, CardHeader } from '@mui/material';
4-
5-
import { useLocalContext } from '@graasp/apps-query-client';
6-
import type { UUID } from '@graasp/sdk';
7-
8-
import type { CommentData } from '@/config/appData';
9-
import type { ChatbotPromptSettings } from '@/config/appSetting';
10-
import { SettingsKeys } from '@/config/appSetting';
11-
import { hooks } from '@/config/queryClient';
12-
import { buildChatbotPromptContainerDataCy } from '@/config/selectors';
131
import { DEFAULT_BOT_USERNAME } from '@/constants';
142

15-
import CustomCommentCard from '../comment/CustomCommentCard';
16-
import ChatbotAvatar from './ChatbotAvatar';
17-
import CommentBody from './CommentBody';
3+
import Comment from './Comment';
184

195
type Props = {
20-
id?: UUID;
6+
chatbotCue?: string;
7+
chatbotName?: string;
218
};
229

23-
function ChatbotPrompt({ id }: Props): JSX.Element | null {
24-
const { t } = useTranslation();
25-
const { data: appData } = hooks.useAppData<CommentData>();
26-
const {
27-
data: chatbotPrompts,
28-
isSuccess,
29-
isError,
30-
} = hooks.useAppSettings<ChatbotPromptSettings>({
31-
name: SettingsKeys.ChatbotPrompt,
32-
});
33-
const chatbotPrompt = chatbotPrompts?.[0];
34-
35-
const { accountId } = useLocalContext();
36-
37-
const comments = appData?.filter((c) => c.creator?.id === (id ?? accountId));
38-
39-
const realChatbotPromptExists = comments?.find(
40-
(c) => c.data.chatbotPromptSettingId !== undefined,
41-
);
42-
43-
if (!chatbotPrompt) {
44-
if (isSuccess) {
45-
return (
46-
<Alert severity="warning">{t('CHATBOT_CONFIGURATION_MISSING')}</Alert>
47-
);
48-
}
49-
if (isError) {
50-
return (
51-
<Alert severity="error">{t('CHATBOT_CONFIGURATION_FETCH_ERROR')}</Alert>
52-
);
53-
}
54-
// do not show anything if it has not finished fetching
55-
return null; // oxlint-disable-line eslint-plugin-unicorn/no-null
56-
}
57-
const chatbotName = chatbotPrompt?.data?.chatbotName || DEFAULT_BOT_USERNAME;
58-
59-
// display only if real chatbot prompt does not exist yet
60-
if (!realChatbotPromptExists) {
61-
if ('' === chatbotPrompt?.data?.chatbotCue) {
62-
return <>Please configure the chatbot prompt.</>;
63-
}
10+
function ChatbotPrompt({
11+
chatbotCue,
12+
chatbotName = DEFAULT_BOT_USERNAME,
13+
}: Readonly<Props>): JSX.Element | null {
14+
if (chatbotCue) {
6415
return (
65-
<>
66-
<CustomCommentCard
67-
elevation={0}
68-
data-cy={buildChatbotPromptContainerDataCy(chatbotPrompt.id)}
69-
>
70-
<CardHeader
71-
title={chatbotName}
72-
subheader={t('JUST_NOW_COMMENT_HEADER')}
73-
avatar={<ChatbotAvatar />}
74-
/>
75-
<CardContent sx={{ p: 2, py: 0, '&:last-child': { pb: 0 } }}>
76-
<CommentBody>{chatbotPrompt?.data?.chatbotCue}</CommentBody>
77-
</CardContent>
78-
</CustomCommentCard>
79-
</>
16+
<Comment id={'cue'} isBot body={chatbotCue} username={chatbotName} />
8017
);
8118
}
8219
return null;

src/modules/common/Comment.tsx

Lines changed: 28 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,47 @@
11
import { useRef } from 'react';
2-
import { useTranslation } from 'react-i18next';
32

4-
import type { CardProps } from '@mui/material';
5-
import { Card, CardContent, CardHeader, styled } from '@mui/material';
3+
import { Stack, Typography } from '@mui/material';
64

7-
import { useLocalContext } from '@graasp/apps-query-client';
8-
import { formatDate } from '@graasp/sdk';
9-
10-
import type { CommentAppData } from '@/config/appData';
11-
import { AppDataTypes } from '@/config/appData';
12-
import type { ChatbotPromptSettings } from '@/config/appSetting';
13-
import { ChatbotPromptSettingsKeys, SettingsKeys } from '@/config/appSetting';
14-
import { hooks } from '@/config/queryClient';
155
import { buildCommentContainerDataCy } from '@/config/selectors';
16-
import { BIG_BORDER_RADIUS, DEFAULT_BOT_USERNAME } from '@/constants';
176

187
import ChatbotAvatar from './ChatbotAvatar';
198
import CommentBody from './CommentBody';
209
import CustomAvatar from './CustomAvatar';
2110

22-
const CustomCard = styled(Card)<CardProps>({
23-
borderRadius: BIG_BORDER_RADIUS,
24-
});
25-
2611
type Props = {
27-
comment: CommentAppData;
12+
body: string;
13+
isBot: boolean;
14+
id: string;
15+
username: string;
2816
};
2917

30-
function Comment({ comment }: Props): JSX.Element {
31-
const { i18n } = useTranslation();
32-
33-
const { accountId } = useLocalContext();
34-
const { data: appContext } = hooks.useAppContext();
35-
const currentMember = appContext?.members.find((m) => m.id === accountId);
36-
const { data: chatbotPrompts } = hooks.useAppSettings<ChatbotPromptSettings>({
37-
name: SettingsKeys.ChatbotPrompt,
38-
});
39-
const chatbotPrompt = chatbotPrompts?.[0];
18+
function Comment({ id, isBot, body, username }: Readonly<Props>): JSX.Element {
4019
const commentRef = useRef<HTMLDivElement>(null);
4120

42-
const isBot = comment.type === AppDataTypes.BotComment;
21+
const avatar = isBot ? (
22+
<ChatbotAvatar />
23+
) : (
24+
<CustomAvatar username={username} />
25+
);
4326

4427
return (
45-
<CustomCard
46-
data-cy={buildCommentContainerDataCy(comment.id)}
47-
elevation={0}
48-
ref={commentRef}
49-
>
50-
<CardHeader
51-
title={
52-
isBot
53-
? (chatbotPrompt?.data[ChatbotPromptSettingsKeys.ChatbotName] ??
54-
DEFAULT_BOT_USERNAME)
55-
: currentMember?.name
56-
}
57-
subheader={formatDate(comment.updatedAt, { locale: i18n.language })}
58-
avatar={
59-
isBot ? <ChatbotAvatar /> : <CustomAvatar member={currentMember} />
60-
}
61-
/>
62-
<CardContent sx={{ p: 2, py: 0, '&:last-child': { pb: 0 } }}>
63-
<CommentBody>{comment.data.content}</CommentBody>
64-
</CardContent>
65-
</CustomCard>
28+
<Stack data-cy={buildCommentContainerDataCy(id)} ref={commentRef}>
29+
<Stack
30+
direction={isBot ? 'row' : 'row-reverse'}
31+
alignItems="end"
32+
justifyContent={isBot ? 'start' : 'end'}
33+
>
34+
<Stack>{avatar}</Stack>
35+
<Stack
36+
sx={{ p: 2, py: 0, '&:last-child': { pb: 0 } }}
37+
alignItems={isBot ? 'start' : 'end'}
38+
gap={1}
39+
>
40+
<Typography variant="subtitle2">{username}</Typography>
41+
<CommentBody>{body}</CommentBody>
42+
</Stack>
43+
</Stack>
44+
</Stack>
6645
);
6746
}
6847

src/modules/common/CommentBody.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import { BIG_BORDER_RADIUS } from '@/constants';
1111

1212
// oxlint-disable no-magic-numbers
1313
const StyledReactMarkdown = styled(ReactMarkdown)(({ theme }) => ({
14+
background: '#efefef',
15+
borderRadius: 8,
16+
padding: theme.spacing(1),
1417
'& .prism-code': {
1518
fontFamily: 'var(--monospace-fonts)',
1619
backgroundColor: 'transparent !important',

0 commit comments

Comments
 (0)