Skip to content

Commit 06d705e

Browse files
authored
Merge pull request #115 from lyjeileen/files
feat: add multipart component
2 parents c4443f4 + 8d6c037 commit 06d705e

File tree

15 files changed

+249
-66
lines changed

15 files changed

+249
-66
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.rustic-file-preview {
2+
position: relative;
3+
display: flex;
4+
align-items: center;
5+
padding: 8px 8px 8px 16px;
6+
justify-content: space-between;
7+
width: 220px;
8+
height: 56px;
9+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import './filePreview.css'
2+
3+
import Card from '@mui/material/Card'
4+
import { useTheme } from '@mui/material/styles'
5+
import Typography from '@mui/material/Typography'
6+
import React from 'react'
7+
8+
import { shortenString } from '../helper'
9+
import type { FileData } from '../types'
10+
11+
export interface FilePreviewProps {
12+
file: FileData
13+
}
14+
15+
export default function FilePreview(
16+
props: React.PropsWithChildren<FilePreviewProps>
17+
) {
18+
const theme = useTheme()
19+
const maximumFileNameLength = 15
20+
return (
21+
<Card
22+
className="rustic-file-preview"
23+
data-cy="file-preview"
24+
variant="outlined"
25+
sx={{ boxShadow: theme.shadows[1] }}
26+
>
27+
<Typography variant="subtitle2" data-cy="file-name">
28+
{shortenString(props.file.name, maximumFileNameLength)}
29+
</Typography>
30+
{props.children}
31+
</Card>
32+
)
33+
}

src/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Video from './media/video/video'
1313
import PopoverMenu from './menu/popoverMenu'
1414
import MessageCanvas from './messageCanvas/messageCanvas'
1515
import MessageSpace from './messageSpace/messageSpace'
16+
import Multipart from './multipart/multipart'
1617
import NavBar from './navBar/navBar'
1718
import ParticipantsContainer from './participantsContainer/participantsContainer'
1819
import Table from './table/table'
@@ -31,6 +32,7 @@ export {
3132
MessageCanvas,
3233
MessageSpace,
3334
MultimodalInput,
35+
Multipart,
3436
NavBar,
3537
OpenLayersMap,
3638
ParticipantsContainer,

src/components/input/multimodal/multimodalInput/multimodalInput.css

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,3 @@
2626
.rustic-bottom-buttons {
2727
padding: 8px;
2828
}
29-
30-
.rustic-multimodal-input .rustic-files {
31-
padding: 16px;
32-
}

src/components/input/multimodal/multimodalInput/multimodalInput.stories.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Meta, StoryFn } from '@storybook/react'
22
import React from 'react'
33
import { v4 as getUUID } from 'uuid'
44

5-
import type { Message } from '../../../types'
5+
import type { FileData, Message } from '../../../types'
66
import MultimodalInput from './multimodalInput'
77

88
const meta: Meta<React.ComponentProps<typeof MultimodalInput>> = {
@@ -159,7 +159,9 @@ export const Default = {
159159
let fileMessage = ''
160160
let textMessage = ''
161161
if (message.data.files && message.data.files.length > 0) {
162-
const fileNames = message.data.files.join(', ')
162+
const fileNames = message.data.files
163+
.map((file: FileData) => file.name)
164+
.join(', ')
163165
fileMessage = `File(s): ${fileNames}`
164166
}
165167
if (message.data.text) {

src/components/input/multimodal/multimodalInput/multimodalInput.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
11
import './multimodalInput.css'
2+
import '../../../../index.css'
23

34
import Box from '@mui/material/Box'
45
import { useEffect, useRef, useState } from 'react'
56
import React from 'react'
67
import { v4 as getUUID } from 'uuid'
78

8-
import type { Message, MultimodalInputProps } from '../../../types'
9+
import type { FileData, Message, MultimodalInputProps } from '../../../types'
910
import BaseInput from '../../baseInput/baseInput'
1011
import Uploader from '../uploader/uploader'
1112

1213
export default function MultimodalInput(props: MultimodalInputProps) {
13-
const [fileNames, setFileNames] = useState<string[]>([])
14+
const [filesInfo, setFilesInfo] = useState<FileData[]>([])
1415
const [messageId, setMessageId] = useState(getUUID())
1516
const [filePreviewsContainer, setFilePreviewsContainer] =
1617
useState<HTMLDivElement>()
1718
const [errorMessagesContainer, setErrorMessagesContainer] =
1819
useState<HTMLDivElement>()
1920
const inputRef = useRef<HTMLDivElement>(null)
20-
const hasAddedFiles = fileNames.length > 0
21+
const hasAddedFiles = filesInfo.length > 0
2122

2223
function handleFileUpdates(action: 'add' | 'remove', fileName: string) {
2324
if (action === 'add') {
24-
setFileNames((prev) => [...prev, fileName])
25+
setFilesInfo((prev) => [...prev, { name: fileName }])
2526
} else {
26-
setFileNames((prev) => prev.filter((file) => file !== fileName))
27+
setFilesInfo((prev) => prev.filter((file) => file.name !== fileName))
2728
}
2829
}
2930

@@ -46,12 +47,12 @@ export default function MultimodalInput(props: MultimodalInputProps) {
4647
if (hasAddedFiles) {
4748
formattedMessage.id = messageId
4849
formattedMessage.format = 'multipart'
49-
formattedMessage.data.files = fileNames
50+
formattedMessage.data.files = filesInfo
5051
}
5152

5253
props.ws.send(formattedMessage)
5354
setMessageId(getUUID())
54-
setFileNames([])
55+
setFilesInfo([])
5556
}
5657

5758
return (
Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,3 @@
1-
.rustic-file-preview {
2-
position: relative;
3-
display: flex;
4-
align-items: center;
5-
padding: 8px;
6-
justify-content: space-between;
7-
width: 220px;
8-
}
9-
101
.rustic-file-preview .rustic-upload-progress {
112
width: 40px;
123
height: 8px;
@@ -21,12 +12,10 @@
2112
display: none;
2213
}
2314

24-
.rustic-files {
25-
display: flex;
26-
gap: 8px;
27-
flex-wrap: wrap;
28-
}
29-
3015
.rustic-error-message {
3116
display: block;
3217
}
18+
19+
.rustic-padding-16 {
20+
padding: 16px;
21+
}

src/components/input/multimodal/uploader/uploader.tsx

Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import './uploader.css'
22

33
import Box from '@mui/material/Box'
4-
import Card from '@mui/material/Card'
54
import IconButton from '@mui/material/IconButton'
65
import LinearProgress from '@mui/material/LinearProgress'
76
import Tooltip from '@mui/material/Tooltip'
@@ -12,10 +11,9 @@ import React, { useEffect, useRef, useState } from 'react'
1211
import { createPortal } from 'react-dom'
1312
import { v4 as getUUID } from 'uuid'
1413

15-
import { shortenString } from '../../../helper'
14+
import FilePreview from '../../../filePreview/filePreview'
1615
import type { UploaderProps } from '../../../types'
1716

18-
const maximumFileNameLength = 15
1917
const maximumLoadingProgress = 100
2018

2119
interface FileInfo {
@@ -251,41 +249,33 @@ function Uploader(props: UploaderProps) {
251249
}
252250
}
253251

254-
function renderFilePreview(file: FileInfo, index: number) {
255-
return (
256-
<Card className="rustic-file-preview" key={index}>
257-
<Typography variant="subtitle2" data-cy="file-name">
258-
{shortenString(file.name, maximumFileNameLength)}
259-
</Typography>
260-
261-
<Box className="rustic-flex-center">
262-
{file.loadingProgress < maximumLoadingProgress && (
263-
<LinearProgress
264-
variant="determinate"
265-
color="secondary"
266-
value={file.loadingProgress}
267-
className="rustic-upload-progress"
268-
/>
269-
)}
270-
<Tooltip title="Delete">
271-
<IconButton
272-
data-cy="delete-button"
273-
color="primary"
274-
onClick={() => handleDelete(file, index)}
275-
className="rustic-delete-button"
276-
aria-label="cancel file upload"
277-
>
278-
<span className="material-symbols-rounded">cancel</span>
279-
</IconButton>
280-
</Tooltip>
281-
</Box>
282-
</Card>
283-
)
284-
}
285-
286252
const filePreviews = (
287-
<Box className="rustic-files">
288-
{addedFiles.map((file, index) => renderFilePreview(file, index))}
253+
<Box className="rustic-files rustic-padding-16">
254+
{addedFiles.map((file, index) => (
255+
<FilePreview file={{ name: file.name }} key={index}>
256+
<Box className="rustic-flex-center">
257+
{file.loadingProgress < maximumLoadingProgress && (
258+
<LinearProgress
259+
variant="determinate"
260+
color="secondary"
261+
value={file.loadingProgress}
262+
className="rustic-upload-progress"
263+
/>
264+
)}
265+
<Tooltip title="Delete">
266+
<IconButton
267+
data-cy="delete-button"
268+
color="primary"
269+
onClick={() => handleDelete(file, index)}
270+
className="rustic-delete-button"
271+
aria-label="cancel file upload"
272+
>
273+
<span className="material-symbols-rounded">cancel</span>
274+
</IconButton>
275+
</Tooltip>
276+
</Box>
277+
</FilePreview>
278+
))}
289279
</Box>
290280
)
291281

src/components/messageSpace/messageSpace.stories.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
MarkedMarkdown,
88
MarkedStreamingMarkdown,
99
type Message,
10+
Multipart,
1011
OpenLayersMap,
1112
RechartsTimeSeries,
1213
Sound,
@@ -372,6 +373,25 @@ export const Default = {
372373
title: 'Video Title',
373374
},
374375
},
376+
{
377+
...humanMessageData,
378+
id: getUUID(),
379+
timestamp: '2024-01-02T00:20:00.000Z',
380+
format: 'text',
381+
data: {
382+
text: 'Could you show me an example of the multipart component?',
383+
},
384+
},
385+
{
386+
...agentMessageData,
387+
id: getUUID(),
388+
timestamp: '2024-01-02T00:21:00.000Z',
389+
format: 'multipart',
390+
data: {
391+
text: 'Here is an example of the multipart component:',
392+
files: [{ name: 'imageExample.png' }, { name: 'pdfExample.pdf' }],
393+
},
394+
},
375395
],
376396
supportedElements: {
377397
text: Text,
@@ -387,6 +407,7 @@ export const Default = {
387407
codeSnippet: CodeSnippet,
388408
sound: Sound,
389409
video: Video,
410+
multipart: Multipart,
390411
},
391412
getProfileComponent: (message: Message) => {
392413
if (message.sender.includes('Agent')) {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.rustic-multipart {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 16px;
5+
}

0 commit comments

Comments
 (0)